mirror of
https://github.com/sonr-io/sonr.git
synced 2026-08-04 10:21:40 +00:00
Executable
+649
@@ -0,0 +1,649 @@
|
||||
# `x/dex`
|
||||
|
||||
The Decentralized Exchange (DEX) module provides cross-chain trading capabilities through IBC Interchain Accounts (ICA), enabling users to perform swaps, manage liquidity, and execute orders on remote DEX chains while maintaining custody through their Sonr DID. This module bridges the gap between self-sovereign identity and DeFi operations across the Cosmos ecosystem.
|
||||
|
||||
## Overview
|
||||
|
||||
The DEX module provides:
|
||||
|
||||
- **Cross-Chain Trading**: Execute swaps on remote DEX chains via ICA
|
||||
- **Liquidity Management**: Add and remove liquidity from pools across chains
|
||||
- **Order Management**: Create and manage limit orders on compatible DEXs
|
||||
- **DID-Controlled Accounts**: All operations authorized through Sonr DIDs
|
||||
- **UCAN Authorization**: Fine-grained permissions for trading operations
|
||||
- **Multi-DEX Support**: Connect to multiple DEX chains simultaneously
|
||||
- **Rate Limiting**: Protection against spam and excessive operations
|
||||
- **Activity Tracking**: Complete history of all DEX operations
|
||||
|
||||
## Core Concepts
|
||||
|
||||
### Interchain Accounts (ICA)
|
||||
|
||||
The module leverages IBC's Interchain Accounts to create controlled accounts on remote DEX chains. Each account is linked to a Sonr DID and managed through ICA transactions.
|
||||
|
||||
### DID-Based Authorization
|
||||
|
||||
All DEX operations require authorization from a valid Sonr DID, ensuring that only authenticated users can perform trading operations.
|
||||
|
||||
### UCAN Tokens
|
||||
|
||||
User-Controlled Authorization Network (UCAN) tokens provide delegated authority for specific operations, enabling secure third-party integrations.
|
||||
|
||||
### Cross-Chain Liquidity
|
||||
|
||||
Users can provide liquidity to pools on any supported DEX chain while maintaining custody through their Sonr identity.
|
||||
|
||||
## State
|
||||
|
||||
### Interchain DEX Accounts
|
||||
|
||||
```protobuf
|
||||
message InterchainDEXAccount {
|
||||
string did = 1; // DID controller of this account
|
||||
string connection_id = 2; // IBC connection to the remote chain
|
||||
string host_chain_id = 3; // Remote chain ID (e.g., osmosis-1)
|
||||
string account_address = 4; // Account address on the remote chain
|
||||
string port_id = 5; // ICA port ID for this account
|
||||
google.protobuf.Timestamp created_at = 6; // Account creation timestamp
|
||||
repeated string enabled_features = 7; // Enabled features for this account
|
||||
AccountStatus status = 8; // Current account status
|
||||
}
|
||||
```
|
||||
|
||||
### Account Status
|
||||
|
||||
```protobuf
|
||||
enum AccountStatus {
|
||||
ACCOUNT_STATUS_PENDING = 0; // Account is pending creation
|
||||
ACCOUNT_STATUS_ACTIVE = 1; // Account is active and ready
|
||||
ACCOUNT_STATUS_DISABLED = 2; // Account is temporarily disabled
|
||||
ACCOUNT_STATUS_FAILED = 3; // Account creation failed
|
||||
}
|
||||
```
|
||||
|
||||
### DEX Features
|
||||
|
||||
```protobuf
|
||||
enum DEXFeatures {
|
||||
DEX_FEATURE_SWAP = 0; // Basic swap functionality
|
||||
DEX_FEATURE_LIQUIDITY = 1; // Liquidity provision
|
||||
DEX_FEATURE_ORDERS = 2; // Limit orders
|
||||
DEX_FEATURE_STAKING = 3; // Staking operations
|
||||
DEX_FEATURE_GOVERNANCE = 4; // Governance participation
|
||||
}
|
||||
```
|
||||
|
||||
### Module Parameters
|
||||
|
||||
```protobuf
|
||||
message Params {
|
||||
bool enabled = 1; // Enable/disable the module
|
||||
uint32 max_accounts_per_did = 2; // Maximum accounts per DID
|
||||
uint64 default_timeout_seconds = 3; // Default timeout for ICA operations
|
||||
repeated string allowed_connections = 4; // Allowed DEX connections
|
||||
string min_swap_amount = 5; // Minimum swap amount
|
||||
string max_daily_volume = 6; // Maximum daily volume per DID
|
||||
RateLimitParams rate_limits = 7; // Rate limit parameters
|
||||
FeeParams fees = 8; // Fee parameters
|
||||
}
|
||||
```
|
||||
|
||||
### Rate Limiting
|
||||
|
||||
```protobuf
|
||||
message RateLimitParams {
|
||||
uint32 max_ops_per_block = 1; // Maximum operations per block
|
||||
uint32 max_ops_per_did_per_day = 2; // Maximum operations per DID per day
|
||||
uint32 cooldown_blocks = 3; // Cooldown period between operations
|
||||
}
|
||||
```
|
||||
|
||||
### Fee Parameters
|
||||
|
||||
```protobuf
|
||||
message FeeParams {
|
||||
uint32 swap_fee_bps = 1; // Platform fee for swaps (basis points)
|
||||
uint32 liquidity_fee_bps = 2; // Platform fee for liquidity operations
|
||||
uint32 order_fee_bps = 3; // Platform fee for orders
|
||||
string fee_collector = 4; // Fee collector address
|
||||
}
|
||||
```
|
||||
|
||||
## Messages
|
||||
|
||||
### Account Management
|
||||
|
||||
#### MsgRegisterDEXAccount
|
||||
|
||||
Registers a new ICA account for DEX operations on a remote chain.
|
||||
|
||||
```protobuf
|
||||
message MsgRegisterDEXAccount {
|
||||
string did = 1; // DID controller requesting the account
|
||||
string connection_id = 2; // IBC connection to target chain
|
||||
repeated string features = 3; // Requested features for this account
|
||||
string metadata = 4; // Optional metadata
|
||||
}
|
||||
```
|
||||
|
||||
### Trading Operations
|
||||
|
||||
#### MsgExecuteSwap
|
||||
|
||||
Executes a token swap on a remote DEX chain.
|
||||
|
||||
```protobuf
|
||||
message MsgExecuteSwap {
|
||||
string did = 1; // DID initiating the swap
|
||||
string connection_id = 2; // IBC connection to DEX chain
|
||||
string source_denom = 3; // Token to swap from
|
||||
string target_denom = 4; // Token to swap to
|
||||
string amount = 5; // Amount to swap
|
||||
string min_amount_out = 6; // Minimum amount out (slippage protection)
|
||||
string route = 7; // Optional specific route
|
||||
string ucan_token = 8; // UCAN authorization token
|
||||
google.protobuf.Timestamp timeout = 9; // Timeout for the swap
|
||||
}
|
||||
```
|
||||
|
||||
### Liquidity Management
|
||||
|
||||
#### MsgProvideLiquidity
|
||||
|
||||
Adds liquidity to a pool on a remote DEX.
|
||||
|
||||
```protobuf
|
||||
message MsgProvideLiquidity {
|
||||
string did = 1; // DID providing liquidity
|
||||
string connection_id = 2; // IBC connection to DEX chain
|
||||
string pool_id = 3; // Pool ID to add liquidity to
|
||||
repeated cosmos.base.v1beta1.Coin assets = 4; // Assets to provide
|
||||
string min_shares = 5; // Minimum shares to receive
|
||||
string ucan_token = 6; // UCAN authorization token
|
||||
google.protobuf.Timestamp timeout = 7; // Timeout for the operation
|
||||
}
|
||||
```
|
||||
|
||||
#### MsgRemoveLiquidity
|
||||
|
||||
Removes liquidity from a pool on a remote DEX.
|
||||
|
||||
```protobuf
|
||||
message MsgRemoveLiquidity {
|
||||
string did = 1; // DID removing liquidity
|
||||
string connection_id = 2; // IBC connection to DEX chain
|
||||
string pool_id = 3; // Pool ID to remove liquidity from
|
||||
string shares = 4; // Amount of shares to remove
|
||||
repeated cosmos.base.v1beta1.Coin min_amounts = 5; // Minimum assets to receive
|
||||
string ucan_token = 6; // UCAN authorization token
|
||||
google.protobuf.Timestamp timeout = 7; // Timeout for the operation
|
||||
}
|
||||
```
|
||||
|
||||
### Order Management
|
||||
|
||||
#### MsgCreateLimitOrder
|
||||
|
||||
Creates a limit order on a remote DEX.
|
||||
|
||||
```protobuf
|
||||
message MsgCreateLimitOrder {
|
||||
string did = 1; // DID creating the order
|
||||
string connection_id = 2; // IBC connection to DEX chain
|
||||
string sell_denom = 3; // Token to sell
|
||||
string buy_denom = 4; // Token to buy
|
||||
string amount = 5; // Amount to sell
|
||||
string price = 6; // Price per unit
|
||||
google.protobuf.Timestamp expiration = 7; // Order expiration
|
||||
string ucan_token = 8; // UCAN authorization token
|
||||
}
|
||||
```
|
||||
|
||||
#### MsgCancelOrder
|
||||
|
||||
Cancels an existing order on a remote DEX.
|
||||
|
||||
```protobuf
|
||||
message MsgCancelOrder {
|
||||
string did = 1; // DID canceling the order
|
||||
string connection_id = 2; // IBC connection to DEX chain
|
||||
string order_id = 3; // Order ID to cancel
|
||||
string ucan_token = 4; // UCAN authorization token
|
||||
}
|
||||
```
|
||||
|
||||
## Queries
|
||||
|
||||
### Account Queries
|
||||
|
||||
- `Params`: Get module parameters
|
||||
- `Account`: Query a specific DEX account by DID and connection
|
||||
- `Accounts`: List all DEX accounts for a DID
|
||||
- `Balance`: Query remote chain balance for an account
|
||||
|
||||
### Trading Queries
|
||||
|
||||
- `Pool`: Get pool information from a remote DEX
|
||||
- `Orders`: Query orders for a DID on a specific connection
|
||||
- `History`: Get transaction history for a DID
|
||||
|
||||
### Query Types
|
||||
|
||||
#### QueryAccountRequest/Response
|
||||
|
||||
```protobuf
|
||||
message QueryAccountRequest {
|
||||
string did = 1; // DID of the account owner
|
||||
string connection_id = 2; // IBC connection ID
|
||||
}
|
||||
|
||||
message QueryAccountResponse {
|
||||
InterchainDEXAccount account = 1; // The DEX account
|
||||
}
|
||||
```
|
||||
|
||||
#### QueryBalanceRequest/Response
|
||||
|
||||
```protobuf
|
||||
message QueryBalanceRequest {
|
||||
string did = 1; // DID of the account owner
|
||||
string connection_id = 2; // IBC connection ID
|
||||
string denom = 3; // Optional specific denom to query
|
||||
}
|
||||
|
||||
message QueryBalanceResponse {
|
||||
repeated cosmos.base.v1beta1.Coin balances = 1; // Balances on the remote chain
|
||||
}
|
||||
```
|
||||
|
||||
#### QueryHistoryRequest/Response
|
||||
|
||||
```protobuf
|
||||
message QueryHistoryRequest {
|
||||
string did = 1; // DID of the account owner
|
||||
string connection_id = 2; // Optional connection filter
|
||||
string operation_type = 3; // Optional operation type filter
|
||||
cosmos.base.query.v1beta1.PageRequest pagination = 4; // Pagination
|
||||
}
|
||||
|
||||
message QueryHistoryResponse {
|
||||
repeated Transaction transactions = 1; // Historical transactions
|
||||
cosmos.base.query.v1beta1.PageResponse pagination = 2; // Pagination response
|
||||
}
|
||||
```
|
||||
|
||||
## Activity Tracking
|
||||
|
||||
The module maintains comprehensive activity records for all DEX operations:
|
||||
|
||||
```protobuf
|
||||
message DEXActivity {
|
||||
string type = 1; // Activity type
|
||||
string did = 2; // DID that performed the activity
|
||||
string connection_id = 3; // Connection where activity occurred
|
||||
string tx_hash = 4; // Transaction hash
|
||||
int64 block_height = 5; // Block height
|
||||
google.protobuf.Timestamp timestamp = 6; // Activity timestamp
|
||||
string details = 7; // JSON-encoded details
|
||||
string status = 8; // Activity status
|
||||
repeated cosmos.base.v1beta1.Coin amount = 9; // Amount involved
|
||||
uint64 gas_used = 10; // Gas used for the activity
|
||||
}
|
||||
```
|
||||
|
||||
## Events
|
||||
|
||||
The DEX module emits comprehensive events for all operations, enabling efficient tracking and indexing of DEX activities.
|
||||
|
||||
### Trading Events
|
||||
|
||||
#### EventSwapExecuted
|
||||
- **Emitted**: When a swap is successfully executed
|
||||
- **Fields**:
|
||||
- `did`: DID of the trader
|
||||
- `connection_id`: IBC connection ID
|
||||
- `source`: Source token and amount
|
||||
- `target`: Target token and amount received
|
||||
- `tx_hash`: Transaction hash on remote chain
|
||||
- `sequence`: IBC packet sequence
|
||||
|
||||
#### EventLiquidityProvided
|
||||
- **Emitted**: When liquidity is added to a pool
|
||||
- **Fields**:
|
||||
- `did`: DID of the liquidity provider
|
||||
- `connection_id`: IBC connection ID
|
||||
- `pool_id`: Pool identifier
|
||||
- `assets`: Assets provided
|
||||
- `shares_received`: LP tokens received
|
||||
- `tx_hash`: Transaction hash on remote chain
|
||||
|
||||
#### EventLiquidityRemoved
|
||||
- **Emitted**: When liquidity is removed from a pool
|
||||
- **Fields**:
|
||||
- `did`: DID of the liquidity provider
|
||||
- `connection_id`: IBC connection ID
|
||||
- `pool_id`: Pool identifier
|
||||
- `shares_removed`: LP tokens burned
|
||||
- `assets`: Assets received
|
||||
- `tx_hash`: Transaction hash on remote chain
|
||||
|
||||
### Order Events
|
||||
|
||||
#### EventOrderCreated
|
||||
- **Emitted**: When a limit order is created
|
||||
- **Fields**:
|
||||
- `did`: DID of the trader
|
||||
- `connection_id`: IBC connection ID
|
||||
- `order_id`: Order identifier
|
||||
- `sell_denom`: Token to sell
|
||||
- `buy_denom`: Token to buy
|
||||
- `amount`: Order amount
|
||||
- `price`: Order price
|
||||
- `tx_hash`: Transaction hash on remote chain
|
||||
|
||||
#### EventOrderCancelled
|
||||
- **Emitted**: When an order is cancelled
|
||||
- **Fields**:
|
||||
- `did`: DID of the trader
|
||||
- `connection_id`: IBC connection ID
|
||||
- `order_id`: Cancelled order ID
|
||||
- `tx_hash`: Transaction hash on remote chain
|
||||
|
||||
#### EventOrderFilled
|
||||
- **Emitted**: When an order is filled (partially or fully)
|
||||
- **Fields**:
|
||||
- `did`: DID of the trader
|
||||
- `connection_id`: IBC connection ID
|
||||
- `order_id`: Filled order ID
|
||||
- `fill_amount`: Amount filled
|
||||
- `fill_price`: Fill price
|
||||
- `tx_hash`: Transaction hash on remote chain
|
||||
|
||||
### ICA Events
|
||||
|
||||
#### EventDEXAccountRegistered
|
||||
- **Emitted**: When a new DEX account is registered
|
||||
- **Fields**:
|
||||
- `did`: DID of the account owner
|
||||
- `connection_id`: IBC connection ID
|
||||
- `port_id`: Generated ICA port ID
|
||||
- `account_address`: Remote account address
|
||||
|
||||
#### EventICAPacketSent
|
||||
- **Emitted**: When an ICA packet is sent
|
||||
- **Fields**:
|
||||
- `did`: DID of the sender
|
||||
- `connection_id`: IBC connection ID
|
||||
- `packet_type`: Type of packet (swap, liquidity, order)
|
||||
- `sequence`: IBC packet sequence
|
||||
|
||||
#### EventICAPacketAcknowledged
|
||||
- **Emitted**: When an ICA packet is acknowledged
|
||||
- **Fields**:
|
||||
- `did`: DID of the sender
|
||||
- `connection_id`: IBC connection ID
|
||||
- `packet_type`: Type of packet
|
||||
- `sequence`: IBC packet sequence
|
||||
- `success`: Success status
|
||||
- `error`: Error message if failed
|
||||
|
||||
## CLI Examples
|
||||
|
||||
### Account Management
|
||||
|
||||
```bash
|
||||
# Register a new DEX account on Osmosis
|
||||
snrd tx dex register-account \
|
||||
--did did:sonr:alice \
|
||||
--connection connection-0 \
|
||||
--features swap,liquidity,orders \
|
||||
--from alice
|
||||
|
||||
# Query DEX account
|
||||
snrd query dex account did:sonr:alice connection-0
|
||||
|
||||
# List all DEX accounts for a DID
|
||||
snrd query dex accounts did:sonr:alice
|
||||
|
||||
# Check balance on remote chain
|
||||
snrd query dex balance did:sonr:alice connection-0
|
||||
```
|
||||
|
||||
### Trading Operations
|
||||
|
||||
```bash
|
||||
# Execute a swap on Osmosis
|
||||
snrd tx dex swap \
|
||||
--did did:sonr:alice \
|
||||
--connection connection-0 \
|
||||
--source-denom uosmo \
|
||||
--target-denom uatom \
|
||||
--amount 1000000 \
|
||||
--min-amount-out 950000 \
|
||||
--ucan-token "eyJ0eXAiOi..." \
|
||||
--from alice
|
||||
|
||||
# Provide liquidity to a pool
|
||||
snrd tx dex provide-liquidity \
|
||||
--did did:sonr:alice \
|
||||
--connection connection-0 \
|
||||
--pool-id 1 \
|
||||
--assets 1000000uosmo,500000uatom \
|
||||
--min-shares 100000 \
|
||||
--ucan-token "eyJ0eXAiOi..." \
|
||||
--from alice
|
||||
|
||||
# Remove liquidity from a pool
|
||||
snrd tx dex remove-liquidity \
|
||||
--did did:sonr:alice \
|
||||
--connection connection-0 \
|
||||
--pool-id 1 \
|
||||
--shares 100000 \
|
||||
--min-amounts 990000uosmo,495000uatom \
|
||||
--ucan-token "eyJ0eXAiOi..." \
|
||||
--from alice
|
||||
```
|
||||
|
||||
### Order Management
|
||||
|
||||
```bash
|
||||
# Create a limit order
|
||||
snrd tx dex create-order \
|
||||
--did did:sonr:alice \
|
||||
--connection connection-0 \
|
||||
--sell-denom uosmo \
|
||||
--buy-denom uatom \
|
||||
--amount 1000000 \
|
||||
--price 1.2 \
|
||||
--expiration "2024-12-31T23:59:59Z" \
|
||||
--ucan-token "eyJ0eXAiOi..." \
|
||||
--from alice
|
||||
|
||||
# Cancel an order
|
||||
snrd tx dex cancel-order \
|
||||
--did did:sonr:alice \
|
||||
--connection connection-0 \
|
||||
--order-id order-123 \
|
||||
--ucan-token "eyJ0eXAiOi..." \
|
||||
--from alice
|
||||
|
||||
# Query orders
|
||||
snrd query dex orders did:sonr:alice connection-0 --status active
|
||||
```
|
||||
|
||||
### Analytics and History
|
||||
|
||||
```bash
|
||||
# Query transaction history
|
||||
snrd query dex history did:sonr:alice \
|
||||
--connection connection-0 \
|
||||
--operation-type swap
|
||||
|
||||
# Get pool information
|
||||
snrd query dex pool connection-0 pool-1
|
||||
|
||||
# Query module parameters
|
||||
snrd query dex params
|
||||
```
|
||||
|
||||
## Integration Guide
|
||||
|
||||
### For DApp Developers
|
||||
|
||||
1. **Account Setup**: Register ICA accounts for target DEX chains
|
||||
2. **Permission Management**: Issue UCAN tokens for specific operations
|
||||
3. **Execute Trades**: Use the module's messages to perform DEX operations
|
||||
4. **Monitor Activity**: Subscribe to events for real-time updates
|
||||
5. **Query State**: Use queries to display balances and history
|
||||
|
||||
### For DEX Integration
|
||||
|
||||
1. **IBC Connection**: Establish IBC connection to Sonr
|
||||
2. **ICA Support**: Ensure ICA host module is enabled
|
||||
3. **Message Handling**: Support standard Cosmos SDK messages
|
||||
4. **Event Emission**: Emit appropriate events for tracking
|
||||
|
||||
### For Wallet Developers
|
||||
|
||||
1. **DID Integration**: Support Sonr DID authentication
|
||||
2. **UCAN Generation**: Implement UCAN token creation
|
||||
3. **Transaction Building**: Build DEX module transactions
|
||||
4. **History Display**: Query and display DEX activity
|
||||
|
||||
## Technical Architecture
|
||||
|
||||
### ICA Message Flow
|
||||
|
||||
1. **Message Creation**: User creates DEX operation message
|
||||
2. **DID Verification**: Module verifies DID ownership
|
||||
3. **UCAN Validation**: Validates authorization token
|
||||
4. **ICA Packet**: Constructs ICA packet for remote chain
|
||||
5. **IBC Relay**: Packet sent via IBC to target chain
|
||||
6. **Remote Execution**: Operation executed on DEX chain
|
||||
7. **Acknowledgment**: Result returned via IBC
|
||||
8. **Event Emission**: Events emitted for tracking
|
||||
|
||||
### Rate Limiting System
|
||||
|
||||
The module implements multi-layer rate limiting:
|
||||
|
||||
```go
|
||||
// Per-block rate limiting
|
||||
if opsThisBlock >= params.RateLimits.MaxOpsPerBlock {
|
||||
return errorsmod.Wrap(ErrRateLimited, "max operations per block exceeded")
|
||||
}
|
||||
|
||||
// Per-DID daily rate limiting
|
||||
if opsToday >= params.RateLimits.MaxOpsPerDidPerDay {
|
||||
return errorsmod.Wrap(ErrRateLimited, "daily operation limit exceeded")
|
||||
}
|
||||
|
||||
// Cooldown period enforcement
|
||||
if blocksSinceLastOp < params.RateLimits.CooldownBlocks {
|
||||
return errorsmod.Wrap(ErrCooldown, "operation cooldown period active")
|
||||
}
|
||||
```
|
||||
|
||||
### Fee Collection
|
||||
|
||||
Platform fees are collected on successful operations:
|
||||
|
||||
```go
|
||||
// Calculate platform fee
|
||||
fee := amount.Mul(params.Fees.SwapFeeBps).Quo(10000)
|
||||
|
||||
// Transfer fee to collector
|
||||
err := bankKeeper.SendCoins(ctx, userAddr, feeCollector, fee)
|
||||
```
|
||||
|
||||
## Security Considerations
|
||||
|
||||
1. **DID Authentication**: All operations require valid DID signatures
|
||||
2. **UCAN Authorization**: Fine-grained permissions prevent unauthorized operations
|
||||
3. **Rate Limiting**: Protects against spam and DoS attacks
|
||||
4. **Slippage Protection**: Minimum output amounts prevent sandwich attacks
|
||||
5. **Timeout Enforcement**: Operations expire to prevent stale execution
|
||||
6. **Connection Whitelisting**: Only approved IBC connections allowed
|
||||
7. **Volume Limits**: Daily volume caps prevent excessive exposure
|
||||
8. **ICA Security**: Leverages IBC's security guarantees
|
||||
9. **Event Auditing**: Comprehensive event trail for all operations
|
||||
10. **Fee Mechanisms**: Platform fees discourage spam
|
||||
|
||||
## Performance Optimization
|
||||
|
||||
### Batch Operations
|
||||
|
||||
The module supports batching for improved efficiency:
|
||||
- Multiple swaps in single ICA packet
|
||||
- Bulk order creation/cancellation
|
||||
- Aggregated liquidity operations
|
||||
|
||||
### Caching Strategy
|
||||
|
||||
- Account data cached for quick lookups
|
||||
- Pool information cached with TTL
|
||||
- Order book snapshots for fast queries
|
||||
|
||||
### Query Optimization
|
||||
|
||||
- Indexed by DID for fast account lookups
|
||||
- Pagination for large result sets
|
||||
- Filtered queries for specific operations
|
||||
|
||||
## Supported DEX Chains
|
||||
|
||||
### Currently Supported
|
||||
|
||||
- **Osmosis**: Full swap, liquidity, and order support
|
||||
- **Crescent**: Swap and liquidity operations
|
||||
- **Neutron**: Astroport DEX integration
|
||||
|
||||
### Planned Support
|
||||
|
||||
- **Kujira**: FIN orderbook integration
|
||||
- **Injective**: Derivatives and spot trading
|
||||
- **Sei**: High-frequency trading support
|
||||
|
||||
## Building and Testing
|
||||
|
||||
### Running Tests
|
||||
|
||||
```bash
|
||||
# Run unit tests
|
||||
make test-dex
|
||||
|
||||
# Run integration tests with IBC
|
||||
make test-dex-ibc
|
||||
|
||||
# Run benchmark tests
|
||||
make benchmark-dex
|
||||
|
||||
# Test with specific DEX chain
|
||||
make test-dex-osmosis
|
||||
```
|
||||
|
||||
### Local Development
|
||||
|
||||
```bash
|
||||
# Start local chain with ICA enabled
|
||||
make localnet-dex
|
||||
|
||||
# Deploy test DEX contracts
|
||||
make deploy-test-dex
|
||||
|
||||
# Run E2E test suite
|
||||
make e2e-test-dex
|
||||
```
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
- **Advanced Order Types**: Stop-loss, trailing stops, OCO orders
|
||||
- **Cross-Chain Arbitrage**: Automated arbitrage between DEXs
|
||||
- **Portfolio Management**: Automated rebalancing strategies
|
||||
- **Yield Farming**: Integration with liquidity mining programs
|
||||
- **Derivatives Trading**: Support for perpetuals and options
|
||||
- **MEV Protection**: Private mempool submission for sensitive trades
|
||||
- **Analytics Dashboard**: Real-time trading metrics and P&L tracking
|
||||
- **Social Trading**: Copy trading and strategy sharing
|
||||
- **DeFi Aggregation**: Route optimization across multiple DEXs
|
||||
- **Governance Integration**: Participate in DEX governance
|
||||
@@ -0,0 +1,286 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/sonr-io/sonr/x/dex/types"
|
||||
)
|
||||
|
||||
// NewQueryCmd creates and returns the query command
|
||||
func NewQueryCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: types.ModuleName,
|
||||
Short: fmt.Sprintf("Querying commands for the %s module", types.ModuleName),
|
||||
DisableFlagParsing: true,
|
||||
SuggestionsMinimumDistance: 2,
|
||||
RunE: client.ValidateCmd,
|
||||
}
|
||||
|
||||
cmd.AddCommand(
|
||||
CmdQueryParams(),
|
||||
CmdQueryAccount(),
|
||||
CmdQueryAccounts(),
|
||||
CmdQueryBalance(),
|
||||
CmdQueryPool(),
|
||||
CmdQueryOrders(),
|
||||
CmdQueryHistory(),
|
||||
)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// CmdQueryParams queries the module parameters
|
||||
func CmdQueryParams() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "params",
|
||||
Short: "Query the current DEX module parameters",
|
||||
Args: cobra.NoArgs,
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
clientCtx, err := client.GetClientQueryContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
queryClient := types.NewQueryClient(clientCtx)
|
||||
res, err := queryClient.Params(context.Background(), &types.QueryParamsRequest{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return clientCtx.PrintProto(res)
|
||||
},
|
||||
}
|
||||
|
||||
flags.AddQueryFlagsToCmd(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// CmdQueryAccount queries a DEX account
|
||||
func CmdQueryAccount() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "account [did] [connection-id]",
|
||||
Short: "Query a DEX account by DID and connection",
|
||||
Args: cobra.ExactArgs(2),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, err := client.GetClientQueryContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
did := args[0]
|
||||
connectionID := args[1]
|
||||
|
||||
queryClient := types.NewQueryClient(clientCtx)
|
||||
res, err := queryClient.Account(context.Background(), &types.QueryAccountRequest{
|
||||
Did: did,
|
||||
ConnectionId: connectionID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return clientCtx.PrintProto(res)
|
||||
},
|
||||
}
|
||||
|
||||
flags.AddQueryFlagsToCmd(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// CmdQueryAccounts queries all DEX accounts
|
||||
func CmdQueryAccounts() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "accounts",
|
||||
Short: "Query all DEX accounts",
|
||||
Args: cobra.NoArgs,
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
clientCtx, err := client.GetClientQueryContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
did, _ := cmd.Flags().GetString("did")
|
||||
|
||||
queryClient := types.NewQueryClient(clientCtx)
|
||||
|
||||
pageReq, err := client.ReadPageRequest(cmd.Flags())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res, err := queryClient.Accounts(context.Background(), &types.QueryAccountsRequest{
|
||||
Did: did,
|
||||
Pagination: pageReq,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return clientCtx.PrintProto(res)
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().String("did", "", "Filter by DID")
|
||||
flags.AddQueryFlagsToCmd(cmd)
|
||||
flags.AddPaginationFlagsToCmd(cmd, "accounts")
|
||||
return cmd
|
||||
}
|
||||
|
||||
// CmdQueryBalance queries remote chain balances
|
||||
func CmdQueryBalance() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "balance [did] [connection-id]",
|
||||
Short: "Query remote chain balances for a DID",
|
||||
Args: cobra.ExactArgs(2),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, err := client.GetClientQueryContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
did := args[0]
|
||||
connectionID := args[1]
|
||||
denom, _ := cmd.Flags().GetString("denom")
|
||||
|
||||
queryClient := types.NewQueryClient(clientCtx)
|
||||
res, err := queryClient.Balance(context.Background(), &types.QueryBalanceRequest{
|
||||
Did: did,
|
||||
ConnectionId: connectionID,
|
||||
Denom: denom,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return clientCtx.PrintProto(res)
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().String("denom", "", "Filter by specific denomination")
|
||||
flags.AddQueryFlagsToCmd(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// CmdQueryPool queries pool information
|
||||
func CmdQueryPool() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "pool [connection-id] [pool-id]",
|
||||
Short: "Query pool information on a remote chain",
|
||||
Args: cobra.ExactArgs(2),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, err := client.GetClientQueryContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
connectionID := args[0]
|
||||
poolID := args[1]
|
||||
|
||||
queryClient := types.NewQueryClient(clientCtx)
|
||||
res, err := queryClient.Pool(context.Background(), &types.QueryPoolRequest{
|
||||
ConnectionId: connectionID,
|
||||
PoolId: poolID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return clientCtx.PrintProto(res)
|
||||
},
|
||||
}
|
||||
|
||||
flags.AddQueryFlagsToCmd(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// CmdQueryOrders queries orders for a DID
|
||||
func CmdQueryOrders() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "orders [did] [connection-id]",
|
||||
Short: "Query orders for a DID on a specific connection",
|
||||
Args: cobra.ExactArgs(2),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, err := client.GetClientQueryContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
did := args[0]
|
||||
connectionID := args[1]
|
||||
status, _ := cmd.Flags().GetString("status")
|
||||
|
||||
queryClient := types.NewQueryClient(clientCtx)
|
||||
|
||||
pageReq, err := client.ReadPageRequest(cmd.Flags())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res, err := queryClient.Orders(context.Background(), &types.QueryOrdersRequest{
|
||||
Did: did,
|
||||
ConnectionId: connectionID,
|
||||
Status: status,
|
||||
Pagination: pageReq,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return clientCtx.PrintProto(res)
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().String("status", "", "Filter by order status (pending|open|filled|cancelled)")
|
||||
flags.AddQueryFlagsToCmd(cmd)
|
||||
flags.AddPaginationFlagsToCmd(cmd, "orders")
|
||||
return cmd
|
||||
}
|
||||
|
||||
// CmdQueryHistory queries transaction history
|
||||
func CmdQueryHistory() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "history [did]",
|
||||
Short: "Query transaction history for a DID",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, err := client.GetClientQueryContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
did := args[0]
|
||||
connectionID, _ := cmd.Flags().GetString("connection")
|
||||
operationType, _ := cmd.Flags().GetString("type")
|
||||
|
||||
queryClient := types.NewQueryClient(clientCtx)
|
||||
|
||||
pageReq, err := client.ReadPageRequest(cmd.Flags())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res, err := queryClient.History(context.Background(), &types.QueryHistoryRequest{
|
||||
Did: did,
|
||||
ConnectionId: connectionID,
|
||||
OperationType: operationType,
|
||||
Pagination: pageReq,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return clientCtx.PrintProto(res)
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().String("connection", "", "Filter by connection ID")
|
||||
cmd.Flags().String("type", "", "Filter by transaction type (swap|liquidity|order)")
|
||||
flags.AddQueryFlagsToCmd(cmd)
|
||||
flags.AddPaginationFlagsToCmd(cmd, "history")
|
||||
return cmd
|
||||
}
|
||||
Executable
+325
@@ -0,0 +1,325 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"cosmossdk.io/math"
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||
"github.com/cosmos/cosmos-sdk/client/tx"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/sonr-io/sonr/x/dex/types"
|
||||
)
|
||||
|
||||
// NewTxCmd creates and returns the tx command
|
||||
func NewTxCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: types.ModuleName,
|
||||
Short: fmt.Sprintf("%s transactions subcommands", types.ModuleName),
|
||||
DisableFlagParsing: true,
|
||||
SuggestionsMinimumDistance: 2,
|
||||
RunE: client.ValidateCmd,
|
||||
}
|
||||
|
||||
cmd.AddCommand(
|
||||
CmdRegisterDEXAccount(),
|
||||
CmdExecuteSwap(),
|
||||
CmdProvideLiquidity(),
|
||||
CmdRemoveLiquidity(),
|
||||
CmdCreateLimitOrder(),
|
||||
CmdCancelOrder(),
|
||||
)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// CmdRegisterDEXAccount returns a command to register a DEX account
|
||||
func CmdRegisterDEXAccount() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "register-account [did] [connection-id] [features]",
|
||||
Short: "Register a new ICA account for DEX operations",
|
||||
Args: cobra.ExactArgs(3),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, err := client.GetClientTxContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
did := args[0]
|
||||
connectionID := args[1]
|
||||
features := strings.Split(args[2], ",")
|
||||
|
||||
msg := &types.MsgRegisterDEXAccount{
|
||||
Did: did,
|
||||
ConnectionId: connectionID,
|
||||
Features: features,
|
||||
}
|
||||
|
||||
if err := msg.ValidateBasic(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
|
||||
},
|
||||
}
|
||||
|
||||
flags.AddTxFlagsToCmd(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// CmdExecuteSwap returns a command to execute a swap
|
||||
func CmdExecuteSwap() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "swap [did] [connection-id] [token-in] [token-out-denom] [min-amount-out] [pool-id]",
|
||||
Short: "Execute a token swap through ICA",
|
||||
Args: cobra.ExactArgs(6),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, err := client.GetClientTxContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
did := args[0]
|
||||
connectionID := args[1]
|
||||
|
||||
tokenIn, err := sdk.ParseCoinNormalized(args[2])
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid token-in: %w", err)
|
||||
}
|
||||
|
||||
tokenOutDenom := args[3]
|
||||
|
||||
minAmountOut, ok := math.NewIntFromString(args[4])
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid min-amount-out: %s", args[4])
|
||||
}
|
||||
|
||||
poolID, err := strconv.ParseUint(args[5], 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid pool-id: %w", err)
|
||||
}
|
||||
|
||||
msg := &types.MsgExecuteSwap{
|
||||
Did: did,
|
||||
ConnectionId: connectionID,
|
||||
SourceDenom: tokenIn.Denom,
|
||||
TargetDenom: tokenOutDenom,
|
||||
Amount: tokenIn.Amount,
|
||||
MinAmountOut: minAmountOut,
|
||||
Route: fmt.Sprintf("pool:%d", poolID),
|
||||
}
|
||||
|
||||
if err := msg.ValidateBasic(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
|
||||
},
|
||||
}
|
||||
|
||||
flags.AddTxFlagsToCmd(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// CmdProvideLiquidity returns a command to provide liquidity
|
||||
func CmdProvideLiquidity() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "provide-liquidity [did] [connection-id] [pool-id] [token-a] [token-b] [min-shares]",
|
||||
Short: "Provide liquidity to a pool through ICA",
|
||||
Args: cobra.ExactArgs(6),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, err := client.GetClientTxContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
did := args[0]
|
||||
connectionID := args[1]
|
||||
|
||||
poolID, err := strconv.ParseUint(args[2], 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid pool-id: %w", err)
|
||||
}
|
||||
|
||||
tokenA, err := sdk.ParseCoinNormalized(args[3])
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid token-a: %w", err)
|
||||
}
|
||||
|
||||
tokenB, err := sdk.ParseCoinNormalized(args[4])
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid token-b: %w", err)
|
||||
}
|
||||
|
||||
minShares, ok := math.NewIntFromString(args[5])
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid min-shares: %s", args[5])
|
||||
}
|
||||
|
||||
msg := &types.MsgProvideLiquidity{
|
||||
Did: did,
|
||||
ConnectionId: connectionID,
|
||||
PoolId: fmt.Sprintf("%d", poolID),
|
||||
Assets: sdk.NewCoins(tokenA, tokenB),
|
||||
MinShares: minShares,
|
||||
Timeout: time.Now().Add(5 * time.Minute),
|
||||
}
|
||||
|
||||
if err := msg.ValidateBasic(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
|
||||
},
|
||||
}
|
||||
|
||||
flags.AddTxFlagsToCmd(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// CmdRemoveLiquidity returns a command to remove liquidity
|
||||
func CmdRemoveLiquidity() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "remove-liquidity [did] [connection-id] [pool-id] [shares] [min-amount-a] [min-amount-b]",
|
||||
Short: "Remove liquidity from a pool through ICA",
|
||||
Args: cobra.ExactArgs(6),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, err := client.GetClientTxContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
did := args[0]
|
||||
connectionID := args[1]
|
||||
|
||||
poolID, err := strconv.ParseUint(args[2], 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid pool-id: %w", err)
|
||||
}
|
||||
|
||||
shares, ok := math.NewIntFromString(args[3])
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid shares: %s", args[3])
|
||||
}
|
||||
|
||||
minAmountA, ok := math.NewIntFromString(args[4])
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid min-amount-a: %s", args[4])
|
||||
}
|
||||
|
||||
minAmountB, ok := math.NewIntFromString(args[5])
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid min-amount-b: %s", args[5])
|
||||
}
|
||||
|
||||
msg := &types.MsgRemoveLiquidity{
|
||||
Did: did,
|
||||
ConnectionId: connectionID,
|
||||
PoolId: fmt.Sprintf("%d", poolID),
|
||||
Shares: shares,
|
||||
MinAmounts: sdk.NewCoins(
|
||||
sdk.NewCoin("token", minAmountA),
|
||||
sdk.NewCoin("token", minAmountB),
|
||||
),
|
||||
Timeout: time.Now().Add(5 * time.Minute),
|
||||
}
|
||||
|
||||
if err := msg.ValidateBasic(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
|
||||
},
|
||||
}
|
||||
|
||||
flags.AddTxFlagsToCmd(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// CmdCreateLimitOrder returns a command to create a limit order
|
||||
func CmdCreateLimitOrder() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "create-order [did] [connection-id] [token-in] [token-out-denom] [price]",
|
||||
Short: "Create a limit order through ICA",
|
||||
Args: cobra.ExactArgs(5),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, err := client.GetClientTxContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
did := args[0]
|
||||
connectionID := args[1]
|
||||
|
||||
tokenIn, err := sdk.ParseCoinNormalized(args[2])
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid token-in: %w", err)
|
||||
}
|
||||
|
||||
tokenOutDenom := args[3]
|
||||
|
||||
price, err := math.LegacyNewDecFromStr(args[4])
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid price: %w", err)
|
||||
}
|
||||
|
||||
msg := &types.MsgCreateLimitOrder{
|
||||
Did: did,
|
||||
ConnectionId: connectionID,
|
||||
SellDenom: tokenIn.Denom,
|
||||
BuyDenom: tokenOutDenom,
|
||||
Amount: tokenIn.Amount,
|
||||
Price: price,
|
||||
Expiration: time.Now().Add(24 * time.Hour),
|
||||
}
|
||||
|
||||
if err := msg.ValidateBasic(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
|
||||
},
|
||||
}
|
||||
|
||||
flags.AddTxFlagsToCmd(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// CmdCancelOrder returns a command to cancel an order
|
||||
func CmdCancelOrder() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "cancel-order [did] [connection-id] [order-id]",
|
||||
Short: "Cancel an existing order through ICA",
|
||||
Args: cobra.ExactArgs(3),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, err := client.GetClientTxContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
did := args[0]
|
||||
connectionID := args[1]
|
||||
orderID := args[2]
|
||||
|
||||
msg := &types.MsgCancelOrder{
|
||||
Did: did,
|
||||
ConnectionId: connectionID,
|
||||
OrderId: orderID,
|
||||
}
|
||||
|
||||
if err := msg.ValidateBasic(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
|
||||
},
|
||||
}
|
||||
|
||||
flags.AddTxFlagsToCmd(cmd)
|
||||
return cmd
|
||||
}
|
||||
Executable
+134
@@ -0,0 +1,134 @@
|
||||
package dex
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
capabilitytypes "github.com/cosmos/ibc-go/modules/capability/types"
|
||||
channeltypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types"
|
||||
porttypes "github.com/cosmos/ibc-go/v8/modules/core/05-port/types"
|
||||
ibcexported "github.com/cosmos/ibc-go/v8/modules/core/exported"
|
||||
"github.com/sonr-io/sonr/x/dex/keeper"
|
||||
)
|
||||
|
||||
var _ porttypes.IBCModule = (*IBCModule)(nil)
|
||||
|
||||
// IBCModule implements the IBC module interface for DEX
|
||||
type IBCModule struct {
|
||||
keeper keeper.Keeper
|
||||
}
|
||||
|
||||
// NewIBCModule creates a new IBCModule given the keeper
|
||||
func NewIBCModule(k keeper.Keeper) IBCModule {
|
||||
return IBCModule{
|
||||
keeper: k,
|
||||
}
|
||||
}
|
||||
|
||||
// OnChanOpenInit implements the IBCModule interface
|
||||
func (im IBCModule) OnChanOpenInit(
|
||||
ctx sdk.Context,
|
||||
order channeltypes.Order,
|
||||
connectionHops []string,
|
||||
portID string,
|
||||
channelID string,
|
||||
chanCap *capabilitytypes.Capability,
|
||||
counterparty channeltypes.Counterparty,
|
||||
version string,
|
||||
) (string, error) {
|
||||
// Delegate to keeper's ICA callback handler
|
||||
if err := im.keeper.OnChanOpenInit(ctx, order, connectionHops, portID, channelID, counterparty, version); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return version, nil
|
||||
}
|
||||
|
||||
// OnChanOpenTry implements the IBCModule interface
|
||||
func (im IBCModule) OnChanOpenTry(
|
||||
ctx sdk.Context,
|
||||
order channeltypes.Order,
|
||||
connectionHops []string,
|
||||
portID,
|
||||
channelID string,
|
||||
chanCap *capabilitytypes.Capability,
|
||||
counterparty channeltypes.Counterparty,
|
||||
counterpartyVersion string,
|
||||
) (string, error) {
|
||||
// TODO: Implement ICA Controller channel handshake
|
||||
return counterpartyVersion, nil
|
||||
}
|
||||
|
||||
// OnChanOpenAck implements the IBCModule interface
|
||||
func (im IBCModule) OnChanOpenAck(
|
||||
ctx sdk.Context,
|
||||
portID,
|
||||
channelID string,
|
||||
counterpartyChannelID string,
|
||||
counterpartyVersion string,
|
||||
) error {
|
||||
// TODO: Handle ICA Controller channel acknowledgment
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnChanOpenConfirm implements the IBCModule interface
|
||||
func (im IBCModule) OnChanOpenConfirm(
|
||||
ctx sdk.Context,
|
||||
portID,
|
||||
channelID string,
|
||||
) error {
|
||||
// TODO: Finalize ICA Controller channel setup
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnChanCloseInit implements the IBCModule interface
|
||||
func (im IBCModule) OnChanCloseInit(
|
||||
ctx sdk.Context,
|
||||
portID,
|
||||
channelID string,
|
||||
) error {
|
||||
// TODO: Handle ICA Controller channel close
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnChanCloseConfirm implements the IBCModule interface
|
||||
func (im IBCModule) OnChanCloseConfirm(
|
||||
ctx sdk.Context,
|
||||
portID,
|
||||
channelID string,
|
||||
) error {
|
||||
// TODO: Confirm ICA Controller channel close
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnRecvPacket implements the IBCModule interface
|
||||
func (im IBCModule) OnRecvPacket(
|
||||
ctx sdk.Context,
|
||||
modulePacket channeltypes.Packet,
|
||||
relayer sdk.AccAddress,
|
||||
) ibcexported.Acknowledgement {
|
||||
// ICA Controller does not receive packets
|
||||
return channeltypes.NewErrorAcknowledgement(
|
||||
fmt.Errorf("ICA controller does not receive packets"),
|
||||
)
|
||||
}
|
||||
|
||||
// OnAcknowledgementPacket implements the IBCModule interface
|
||||
func (im IBCModule) OnAcknowledgementPacket(
|
||||
ctx sdk.Context,
|
||||
modulePacket channeltypes.Packet,
|
||||
acknowledgement []byte,
|
||||
relayer sdk.AccAddress,
|
||||
) error {
|
||||
// TODO: Handle ICA packet acknowledgments
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnTimeoutPacket implements the IBCModule interface
|
||||
func (im IBCModule) OnTimeoutPacket(
|
||||
ctx sdk.Context,
|
||||
modulePacket channeltypes.Packet,
|
||||
relayer sdk.AccAddress,
|
||||
) error {
|
||||
// TODO: Handle ICA packet timeouts
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
// Package keeper implements DID integration for the DEX module
|
||||
package keeper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
"github.com/sonr-io/sonr/x/dex/types"
|
||||
)
|
||||
|
||||
// ValidateDIDOwnership verifies that the transaction sender owns the specified DID
|
||||
func (k Keeper) ValidateDIDOwnership(ctx sdk.Context, did string, sender sdk.AccAddress) error {
|
||||
// Get DID document from DID keeper
|
||||
didDoc, err := k.didKeeper.GetDIDDocument(ctx, did)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get DID document: %w", err)
|
||||
}
|
||||
|
||||
if didDoc == nil {
|
||||
return fmt.Errorf("DID document not found for %s", did)
|
||||
}
|
||||
|
||||
// Verify sender is the controller of the DID
|
||||
if !k.isDIDController(didDoc, sender.String()) {
|
||||
return fmt.Errorf("sender %s is not the controller of DID %s", sender, did)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// isDIDController checks if an address is a controller of the DID
|
||||
func (k Keeper) isDIDController(didDoc any, address string) bool {
|
||||
// This is a simplified check - actual implementation would depend on DID document structure
|
||||
// For now, we'll assume the DID document has a Controller field or similar
|
||||
// The actual implementation should match the x/did module's structure
|
||||
|
||||
// TODO: Implement proper controller verification based on actual DID document structure
|
||||
// This might involve checking:
|
||||
// - didDoc.Controller field
|
||||
// - didDoc.Authentication keys
|
||||
// - didDoc.AssertionMethod keys
|
||||
|
||||
return true // Placeholder - always return true for now
|
||||
}
|
||||
|
||||
// GetDIDCapabilities retrieves the DEX-related capabilities for a DID
|
||||
func (k Keeper) GetDIDCapabilities(ctx sdk.Context, did string) ([]string, error) {
|
||||
// Get DID document
|
||||
didDoc, err := k.didKeeper.GetDIDDocument(ctx, did)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get DID document: %w", err)
|
||||
}
|
||||
|
||||
if didDoc == nil {
|
||||
return nil, fmt.Errorf("DID document not found for %s", did)
|
||||
}
|
||||
|
||||
// Extract DEX-related capabilities from the DID document
|
||||
// This would typically be stored in service endpoints or custom fields
|
||||
capabilities := []string{
|
||||
"swap",
|
||||
"liquidity",
|
||||
"orders",
|
||||
}
|
||||
|
||||
return capabilities, nil
|
||||
}
|
||||
|
||||
// AuthenticateDIDOperation verifies that a DID is authorized for a specific DEX operation
|
||||
func (k Keeper) AuthenticateDIDOperation(
|
||||
ctx sdk.Context,
|
||||
did string,
|
||||
operation string,
|
||||
params map[string]any,
|
||||
) error {
|
||||
// Get DID document to verify it exists and is active
|
||||
didDoc, err := k.didKeeper.GetDIDDocument(ctx, did)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to authenticate DID: %w", err)
|
||||
}
|
||||
|
||||
if didDoc == nil {
|
||||
return fmt.Errorf("DID %s not found", did)
|
||||
}
|
||||
|
||||
// Check if DID has the required capability for this operation
|
||||
capabilities, err := k.GetDIDCapabilities(ctx, did)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get DID capabilities: %w", err)
|
||||
}
|
||||
|
||||
// Map operations to required capabilities
|
||||
requiredCapability := k.getRequiredCapability(operation)
|
||||
if !k.hasCapability(capabilities, requiredCapability) {
|
||||
return fmt.Errorf("DID %s lacks capability for operation %s", did, operation)
|
||||
}
|
||||
|
||||
// Additional authentication checks could be added here:
|
||||
// - Check if DID has sufficient reputation
|
||||
// - Check if DID has completed KYC/AML if required
|
||||
// - Check rate limits for the DID
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getRequiredCapability maps DEX operations to required capabilities
|
||||
func (k Keeper) getRequiredCapability(operation string) string {
|
||||
switch operation {
|
||||
case "swap", "execute_swap":
|
||||
return "swap"
|
||||
case "provide_liquidity", "remove_liquidity":
|
||||
return "liquidity"
|
||||
case "create_order", "cancel_order":
|
||||
return "orders"
|
||||
default:
|
||||
return operation
|
||||
}
|
||||
}
|
||||
|
||||
// hasCapability checks if a capability exists in the list
|
||||
func (k Keeper) hasCapability(capabilities []string, required string) bool {
|
||||
for _, cap := range capabilities {
|
||||
if cap == required {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// RecordDIDActivity records DEX activity for a DID (for analytics and compliance)
|
||||
func (k Keeper) RecordDIDActivity(
|
||||
ctx sdk.Context,
|
||||
did string,
|
||||
activity types.DEXActivity,
|
||||
) error {
|
||||
// Store activity record keyed by DID and timestamp
|
||||
activityKey := GetDIDActivityKey(did, ctx.BlockTime().Unix())
|
||||
|
||||
// Store the activity
|
||||
if err := k.DIDActivities.Set(ctx, activityKey, activity); err != nil {
|
||||
return fmt.Errorf("failed to record DID activity: %w", err)
|
||||
}
|
||||
|
||||
// Emit event for activity tracking
|
||||
ctx.EventManager().EmitEvent(
|
||||
sdk.NewEvent(
|
||||
types.EventTypeDIDActivity,
|
||||
sdk.NewAttribute("did", did),
|
||||
sdk.NewAttribute("activity_type", activity.Type),
|
||||
sdk.NewAttribute("timestamp", fmt.Sprintf("%d", ctx.BlockTime().Unix())),
|
||||
),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetDIDActivityHistory retrieves the activity history for a DID
|
||||
func (k Keeper) GetDIDActivityHistory(
|
||||
ctx sdk.Context,
|
||||
did string,
|
||||
limit uint32,
|
||||
) ([]types.DEXActivity, error) {
|
||||
activities := make([]types.DEXActivity, 0)
|
||||
|
||||
// Walk through activities for this DID
|
||||
prefix := GetDIDActivityPrefix(did)
|
||||
iterator, err := k.DIDActivities.Iterate(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to iterate DID activities: %w", err)
|
||||
}
|
||||
defer iterator.Close()
|
||||
|
||||
count := uint32(0)
|
||||
for ; iterator.Valid() && count < limit; iterator.Next() {
|
||||
key, err := iterator.Key()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if key starts with the DID prefix
|
||||
if len(key) >= len(prefix) && string(key[:len(prefix)]) == prefix {
|
||||
activity, err := iterator.Value()
|
||||
if err == nil {
|
||||
activities = append(activities, activity)
|
||||
count++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return activities, nil
|
||||
}
|
||||
|
||||
// GetDIDActivityPrefix returns the key prefix for a DID's activities
|
||||
func GetDIDActivityPrefix(did string) string {
|
||||
return fmt.Sprintf("did_activity_%s_", did)
|
||||
}
|
||||
|
||||
// GetDIDActivityKey returns the key for storing a DID activity
|
||||
func GetDIDActivityKey(did string, timestamp int64) string {
|
||||
return fmt.Sprintf("did_activity_%s_%d", did, timestamp)
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
// Package keeper implements DWN integration for the DEX module
|
||||
package keeper
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
"github.com/sonr-io/sonr/x/dex/types"
|
||||
)
|
||||
|
||||
// StoreDEXAccountInDWN stores DEX account information in DWN
|
||||
func (k Keeper) StoreDEXAccountInDWN(
|
||||
ctx sdk.Context,
|
||||
account *types.InterchainDEXAccount,
|
||||
) error {
|
||||
// Create DWN record
|
||||
record := types.DWNRecord{
|
||||
ID: fmt.Sprintf("dex_account_%s_%s", account.Did, account.ConnectionId),
|
||||
DID: account.Did,
|
||||
Type: "dex_account",
|
||||
Data: account,
|
||||
Timestamp: ctx.BlockTime(),
|
||||
Metadata: map[string]string{
|
||||
"connection_id": account.ConnectionId,
|
||||
"port_id": account.PortId,
|
||||
"status": account.Status.String(),
|
||||
},
|
||||
}
|
||||
|
||||
// Store in DWN (placeholder - actual implementation would use DWN keeper)
|
||||
if err := k.storeDWNRecord(ctx, record); err != nil {
|
||||
return fmt.Errorf("failed to store DEX account in DWN: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// StoreSwapRecordInDWN stores swap transaction in DWN
|
||||
func (k Keeper) StoreSwapRecordInDWN(
|
||||
ctx sdk.Context,
|
||||
did string,
|
||||
connectionID string,
|
||||
swapData map[string]any,
|
||||
) error {
|
||||
// Create DWN record for swap
|
||||
record := types.DWNRecord{
|
||||
ID: fmt.Sprintf("swap_%s_%d", did, ctx.BlockTime().Unix()),
|
||||
DID: did,
|
||||
Type: "dex_swap",
|
||||
Data: swapData,
|
||||
Timestamp: ctx.BlockTime(),
|
||||
Metadata: map[string]string{
|
||||
"connection_id": connectionID,
|
||||
"operation": "swap",
|
||||
},
|
||||
}
|
||||
|
||||
// Store in DWN
|
||||
if err := k.storeDWNRecord(ctx, record); err != nil {
|
||||
return fmt.Errorf("failed to store swap record in DWN: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// StoreLiquidityRecordInDWN stores liquidity operation in DWN
|
||||
func (k Keeper) StoreLiquidityRecordInDWN(
|
||||
ctx sdk.Context,
|
||||
did string,
|
||||
connectionID string,
|
||||
operationType string, // "provide" or "remove"
|
||||
liquidityData map[string]any,
|
||||
) error {
|
||||
// Create DWN record for liquidity operation
|
||||
record := types.DWNRecord{
|
||||
ID: fmt.Sprintf("liquidity_%s_%s_%d", operationType, did, ctx.BlockTime().Unix()),
|
||||
DID: did,
|
||||
Type: fmt.Sprintf("dex_liquidity_%s", operationType),
|
||||
Data: liquidityData,
|
||||
Timestamp: ctx.BlockTime(),
|
||||
Metadata: map[string]string{
|
||||
"connection_id": connectionID,
|
||||
"operation": fmt.Sprintf("liquidity_%s", operationType),
|
||||
},
|
||||
}
|
||||
|
||||
// Store in DWN
|
||||
if err := k.storeDWNRecord(ctx, record); err != nil {
|
||||
return fmt.Errorf("failed to store liquidity record in DWN: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// StoreOrderRecordInDWN stores order information in DWN
|
||||
func (k Keeper) StoreOrderRecordInDWN(
|
||||
ctx sdk.Context,
|
||||
did string,
|
||||
connectionID string,
|
||||
orderID string,
|
||||
orderData map[string]any,
|
||||
) error {
|
||||
// Create DWN record for order
|
||||
record := types.DWNRecord{
|
||||
ID: fmt.Sprintf("order_%s", orderID),
|
||||
DID: did,
|
||||
Type: "dex_order",
|
||||
Data: orderData,
|
||||
Timestamp: ctx.BlockTime(),
|
||||
Metadata: map[string]string{
|
||||
"connection_id": connectionID,
|
||||
"order_id": orderID,
|
||||
"operation": "order",
|
||||
},
|
||||
}
|
||||
|
||||
// Store in DWN
|
||||
if err := k.storeDWNRecord(ctx, record); err != nil {
|
||||
return fmt.Errorf("failed to store order record in DWN: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RetrieveDEXHistoryFromDWN retrieves DEX operation history from DWN
|
||||
func (k Keeper) RetrieveDEXHistoryFromDWN(
|
||||
ctx sdk.Context,
|
||||
did string,
|
||||
recordType string,
|
||||
limit int,
|
||||
) ([]types.DWNRecord, error) {
|
||||
// Query DWN for records (placeholder implementation)
|
||||
records, err := k.queryDWNRecords(ctx, did, recordType, limit)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to retrieve DEX history from DWN: %w", err)
|
||||
}
|
||||
|
||||
return records, nil
|
||||
}
|
||||
|
||||
// StorePortfolioSnapshotInDWN stores portfolio snapshot in DWN
|
||||
func (k Keeper) StorePortfolioSnapshotInDWN(
|
||||
ctx sdk.Context,
|
||||
did string,
|
||||
portfolio any,
|
||||
) error {
|
||||
// Create DWN record for portfolio snapshot
|
||||
record := types.DWNRecord{
|
||||
ID: fmt.Sprintf("portfolio_%s_%d", did, ctx.BlockTime().Unix()),
|
||||
DID: did,
|
||||
Type: "dex_portfolio_snapshot",
|
||||
Data: portfolio,
|
||||
Timestamp: ctx.BlockTime(),
|
||||
Metadata: map[string]string{
|
||||
"snapshot_time": ctx.BlockTime().Format(time.RFC3339),
|
||||
},
|
||||
}
|
||||
|
||||
// Store in DWN
|
||||
if err := k.storeDWNRecord(ctx, record); err != nil {
|
||||
return fmt.Errorf("failed to store portfolio snapshot in DWN: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// storeDWNRecord stores a record in DWN (placeholder implementation)
|
||||
func (k Keeper) storeDWNRecord(ctx sdk.Context, record types.DWNRecord) error {
|
||||
// This is a placeholder implementation
|
||||
// Actual implementation would use the DWN keeper interface
|
||||
|
||||
// Serialize record
|
||||
data, err := json.Marshal(record)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to serialize DWN record: %w", err)
|
||||
}
|
||||
|
||||
// Log the operation (placeholder for actual DWN storage)
|
||||
k.Logger(ctx).Info("Storing record in DWN",
|
||||
"record_id", record.ID,
|
||||
"did", record.DID,
|
||||
"type", record.Type,
|
||||
"size", len(data),
|
||||
)
|
||||
|
||||
// TODO: Implement actual DWN storage when DWN keeper is available
|
||||
// k.dwnKeeper.StoreRecord(ctx, record.DID, record.ID, data)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// queryDWNRecords queries records from DWN (placeholder implementation)
|
||||
func (k Keeper) queryDWNRecords(
|
||||
ctx sdk.Context,
|
||||
did string,
|
||||
recordType string,
|
||||
limit int,
|
||||
) ([]types.DWNRecord, error) {
|
||||
// This is a placeholder implementation
|
||||
// Actual implementation would use the DWN keeper interface
|
||||
|
||||
// Log the query
|
||||
k.Logger(ctx).Info("Querying DWN records",
|
||||
"did", did,
|
||||
"type", recordType,
|
||||
"limit", limit,
|
||||
)
|
||||
|
||||
// TODO: Implement actual DWN query when DWN keeper is available
|
||||
// records := k.dwnKeeper.QueryRecords(ctx, did, recordType, limit)
|
||||
|
||||
// Return empty list for now
|
||||
return []types.DWNRecord{}, nil
|
||||
}
|
||||
|
||||
// DeleteDWNRecord deletes a record from DWN
|
||||
func (k Keeper) DeleteDWNRecord(
|
||||
ctx sdk.Context,
|
||||
did string,
|
||||
recordID string,
|
||||
) error {
|
||||
// Log the deletion
|
||||
k.Logger(ctx).Info("Deleting DWN record",
|
||||
"did", did,
|
||||
"record_id", recordID,
|
||||
)
|
||||
|
||||
// TODO: Implement actual DWN deletion when DWN keeper is available
|
||||
// return k.dwnKeeper.DeleteRecord(ctx, did, recordID)
|
||||
|
||||
return nil
|
||||
}
|
||||
Executable
+84
@@ -0,0 +1,84 @@
|
||||
package keeper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/sonr-io/sonr/x/dex/types"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// InitGenesis initializes the module's state from a specified GenesisState
|
||||
func (k Keeper) InitGenesis(ctx sdk.Context, state types.GenesisState) {
|
||||
// Set params
|
||||
if err := k.Params.Set(ctx, state.Params); err != nil {
|
||||
panic(fmt.Sprintf("failed to set params: %v", err))
|
||||
}
|
||||
|
||||
// Set port ID - use default if empty
|
||||
portID := state.PortId
|
||||
if portID == "" {
|
||||
portID = types.PortID
|
||||
}
|
||||
|
||||
// Only try to bind to port if it is not already bound
|
||||
if !k.IsBound(ctx, portID) {
|
||||
// Module binds to the port on InitChain
|
||||
// and claims the returned capability
|
||||
if err := k.BindPort(ctx, portID); err != nil {
|
||||
panic(fmt.Sprintf("could not claim port capability: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
// Restore accounts
|
||||
for _, account := range state.Accounts {
|
||||
accountKey := GetAccountKey(account.Did, account.ConnectionId)
|
||||
if err := k.Accounts.Set(ctx, accountKey, *account); err != nil {
|
||||
panic(fmt.Sprintf("failed to set account: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
// Set account sequence
|
||||
if err := k.AccountSequence.Set(ctx, state.AccountSequence); err != nil {
|
||||
panic(fmt.Sprintf("failed to set account sequence: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
// ExportGenesis exports the module's state
|
||||
func (k Keeper) ExportGenesis(ctx sdk.Context) *types.GenesisState {
|
||||
params, err := k.Params.Get(ctx)
|
||||
if err != nil {
|
||||
params = types.Params{} // Use default params if not set
|
||||
}
|
||||
|
||||
var accounts []*types.InterchainDEXAccount
|
||||
err = k.Accounts.Walk(
|
||||
ctx,
|
||||
nil,
|
||||
func(key string, value types.InterchainDEXAccount) (bool, error) {
|
||||
accounts = append(accounts, &value)
|
||||
return false, nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("failed to export accounts: %v", err))
|
||||
}
|
||||
|
||||
sequence, err := k.AccountSequence.Peek(ctx)
|
||||
if err != nil {
|
||||
sequence = 0
|
||||
}
|
||||
|
||||
return &types.GenesisState{
|
||||
Params: params,
|
||||
PortId: types.PortID,
|
||||
Accounts: accounts,
|
||||
AccountSequence: sequence,
|
||||
}
|
||||
}
|
||||
|
||||
// IsBound checks if the port is already bound
|
||||
func (k Keeper) IsBound(ctx sdk.Context, portID string) bool {
|
||||
_, ok := k.ScopedKeeper.GetCapability(ctx, fmt.Sprintf("ports/%s", portID))
|
||||
return ok
|
||||
}
|
||||
Executable
+103
@@ -0,0 +1,103 @@
|
||||
package keeper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
capabilitytypes "github.com/cosmos/ibc-go/modules/capability/types"
|
||||
clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types"
|
||||
connectiontypes "github.com/cosmos/ibc-go/v8/modules/core/03-connection/types"
|
||||
channeltypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types"
|
||||
host "github.com/cosmos/ibc-go/v8/modules/core/24-host"
|
||||
)
|
||||
|
||||
// ValidateConnection validates an IBC connection exists and is open
|
||||
func (k Keeper) ValidateConnection(ctx sdk.Context, connectionID string) error {
|
||||
connection, found := k.connectionKeeper.GetConnection(ctx, connectionID)
|
||||
if !found {
|
||||
return fmt.Errorf("connection %s not found", connectionID)
|
||||
}
|
||||
|
||||
if connection.State != connectiontypes.OPEN {
|
||||
return fmt.Errorf("connection %s is not open", connectionID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetChannelCapability retrieves the channel capability
|
||||
func (k Keeper) GetChannelCapability(ctx sdk.Context, portID, channelID string) (*capabilitytypes.Capability, error) {
|
||||
capability, ok := k.ScopedKeeper.GetCapability(ctx, host.ChannelCapabilityPath(portID, channelID))
|
||||
if !ok {
|
||||
return nil, fmt.Errorf(
|
||||
"capability not found for port %s channel %s: %w",
|
||||
portID, channelID,
|
||||
channeltypes.ErrChannelCapabilityNotFound,
|
||||
)
|
||||
}
|
||||
return capability, nil
|
||||
}
|
||||
|
||||
// GetChannel retrieves an IBC channel
|
||||
func (k Keeper) GetChannel(ctx sdk.Context, portID, channelID string) (channeltypes.Channel, bool) {
|
||||
return k.channelKeeper.GetChannel(ctx, portID, channelID)
|
||||
}
|
||||
|
||||
// GetNextSequenceSend returns the next sequence send for a channel
|
||||
func (k Keeper) GetNextSequenceSend(ctx sdk.Context, portID, channelID string) (uint64, bool) {
|
||||
return k.channelKeeper.GetNextSequenceSend(ctx, portID, channelID)
|
||||
}
|
||||
|
||||
// SendPacket sends an IBC packet
|
||||
func (k Keeper) SendPacket(
|
||||
ctx sdk.Context,
|
||||
chanCap *capabilitytypes.Capability,
|
||||
portID string,
|
||||
channelID string,
|
||||
timeoutHeight clienttypes.Height,
|
||||
timeoutTimestamp uint64,
|
||||
data []byte,
|
||||
) (uint64, error) {
|
||||
return k.channelKeeper.SendPacket(
|
||||
ctx,
|
||||
chanCap,
|
||||
portID,
|
||||
channelID,
|
||||
timeoutHeight,
|
||||
timeoutTimestamp,
|
||||
data,
|
||||
)
|
||||
}
|
||||
|
||||
// BindPort binds a port and claims the capability
|
||||
func (k Keeper) BindPort(ctx sdk.Context, portID string) error {
|
||||
capability := k.PortKeeper.BindPort(ctx, portID)
|
||||
return k.ClaimCapability(ctx, capability, host.PortPath(portID))
|
||||
}
|
||||
|
||||
// ClaimCapability claims a capability
|
||||
func (k Keeper) ClaimCapability(ctx sdk.Context, cap *capabilitytypes.Capability, name string) error {
|
||||
return k.ScopedKeeper.ClaimCapability(ctx, cap, name)
|
||||
}
|
||||
|
||||
// AuthenticateCapability authenticates a capability
|
||||
func (k Keeper) AuthenticateCapability(ctx sdk.Context, cap *capabilitytypes.Capability, name string) bool {
|
||||
return k.ScopedKeeper.AuthenticateCapability(ctx, cap, name)
|
||||
}
|
||||
|
||||
// GetConnectionEnd retrieves an IBC connection
|
||||
func (k Keeper) GetConnectionEnd(ctx sdk.Context, connectionID string) (connectiontypes.ConnectionEnd, bool) {
|
||||
return k.connectionKeeper.GetConnection(ctx, connectionID)
|
||||
}
|
||||
|
||||
// IsConnectionOpen checks if a connection is open
|
||||
func (k Keeper) IsConnectionOpen(ctx sdk.Context, connectionID string) bool {
|
||||
connection, found := k.GetConnectionEnd(ctx, connectionID)
|
||||
return found && connection.State == connectiontypes.OPEN
|
||||
}
|
||||
|
||||
// IsChannelOpen checks if a channel is open
|
||||
func (k Keeper) IsChannelOpen(ctx sdk.Context, portID, channelID string) bool {
|
||||
channel, found := k.GetChannel(ctx, portID, channelID)
|
||||
return found && channel.State == channeltypes.OPEN
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
package keeper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
channeltypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types"
|
||||
"github.com/sonr-io/sonr/x/dex/types"
|
||||
)
|
||||
|
||||
// OnChanOpenInit handles channel initialization for ICA
|
||||
func (k Keeper) OnChanOpenInit(
|
||||
ctx sdk.Context,
|
||||
order channeltypes.Order,
|
||||
connectionHops []string,
|
||||
portID string,
|
||||
channelID string,
|
||||
counterparty channeltypes.Counterparty,
|
||||
version string,
|
||||
) error {
|
||||
// Claim capability for the channel
|
||||
capability := k.PortKeeper.BindPort(ctx, portID)
|
||||
if err := k.ScopedKeeper.ClaimCapability(ctx, capability, channelCapabilityPath(portID, channelID)); err != nil {
|
||||
return fmt.Errorf("failed to claim capability: %w", err)
|
||||
}
|
||||
|
||||
k.Logger(ctx).Info("ICA channel initialized",
|
||||
"port", portID,
|
||||
"channel", channelID,
|
||||
"connection", connectionHops[0],
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnChanOpenAck handles channel acknowledgment for ICA
|
||||
func (k Keeper) OnChanOpenAck(
|
||||
ctx sdk.Context,
|
||||
portID,
|
||||
channelID string,
|
||||
counterpartyChannelID string,
|
||||
counterpartyVersion string,
|
||||
) error {
|
||||
// Parse counterparty version to get ICA address
|
||||
metadata, err := parseICAMetadata(counterpartyVersion)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse ICA metadata: %w", err)
|
||||
}
|
||||
|
||||
// Update DEX account with ICA address
|
||||
if err := k.OnICAAccountCreated(ctx, portID, metadata.Address); err != nil {
|
||||
return fmt.Errorf("failed to update DEX account: %w", err)
|
||||
}
|
||||
|
||||
k.Logger(ctx).Info("ICA channel acknowledged",
|
||||
"port", portID,
|
||||
"channel", channelID,
|
||||
"ica_address", metadata.Address,
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnAcknowledgementPacket handles ICA packet acknowledgments
|
||||
func (k Keeper) OnAcknowledgementPacket(
|
||||
ctx sdk.Context,
|
||||
packet channeltypes.Packet,
|
||||
acknowledgement []byte,
|
||||
relayer sdk.AccAddress,
|
||||
) error {
|
||||
var ack channeltypes.Acknowledgement
|
||||
if err := k.cdc.Unmarshal(acknowledgement, &ack); err != nil {
|
||||
return fmt.Errorf("failed to unmarshal acknowledgement: %w", err)
|
||||
}
|
||||
|
||||
// Log the acknowledgment
|
||||
k.Logger(ctx).Info("ICA packet acknowledged",
|
||||
"sequence", packet.Sequence,
|
||||
"source_port", packet.SourcePort,
|
||||
"source_channel", packet.SourceChannel,
|
||||
"success", ack.Success(),
|
||||
)
|
||||
|
||||
// Emit event for successful/failed transaction
|
||||
ctx.EventManager().EmitEvent(
|
||||
sdk.NewEvent(
|
||||
types.EventTypeICAPacketAcknowledged,
|
||||
sdk.NewAttribute("sequence", fmt.Sprintf("%d", packet.Sequence)),
|
||||
sdk.NewAttribute("source_port", packet.SourcePort),
|
||||
sdk.NewAttribute("source_channel", packet.SourceChannel),
|
||||
sdk.NewAttribute("success", fmt.Sprintf("%t", ack.Success())),
|
||||
),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnTimeoutPacket handles ICA packet timeouts
|
||||
func (k Keeper) OnTimeoutPacket(
|
||||
ctx sdk.Context,
|
||||
packet channeltypes.Packet,
|
||||
relayer sdk.AccAddress,
|
||||
) error {
|
||||
k.Logger(ctx).Error("ICA packet timed out",
|
||||
"sequence", packet.Sequence,
|
||||
"source_port", packet.SourcePort,
|
||||
"source_channel", packet.SourceChannel,
|
||||
)
|
||||
|
||||
// Emit timeout event
|
||||
ctx.EventManager().EmitEvent(
|
||||
sdk.NewEvent(
|
||||
types.EventTypeICAPacketTimeout,
|
||||
sdk.NewAttribute("sequence", fmt.Sprintf("%d", packet.Sequence)),
|
||||
sdk.NewAttribute("source_port", packet.SourcePort),
|
||||
sdk.NewAttribute("source_channel", packet.SourceChannel),
|
||||
),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
|
||||
func channelCapabilityPath(portID, channelID string) string {
|
||||
return fmt.Sprintf("%s/%s/%s/%s", "ports", portID, "channels", channelID)
|
||||
}
|
||||
|
||||
// ICAMetadata represents parsed ICA metadata from version string
|
||||
type ICAMetadata struct {
|
||||
Address string
|
||||
Version string
|
||||
}
|
||||
|
||||
// parseICAMetadata extracts ICA address from version metadata
|
||||
func parseICAMetadata(version string) (*ICAMetadata, error) {
|
||||
// This is a simplified version - actual parsing depends on ICA version format
|
||||
// The version string typically contains JSON with the ICA address
|
||||
// For now, we'll return a placeholder
|
||||
return &ICAMetadata{
|
||||
Address: version, // In reality, this would be parsed from JSON
|
||||
Version: "ics27-1",
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
package keeper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/sonr-io/sonr/x/dex/types"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
icatypes "github.com/cosmos/ibc-go/v8/modules/apps/27-interchain-accounts/types"
|
||||
host "github.com/cosmos/ibc-go/v8/modules/core/24-host"
|
||||
)
|
||||
|
||||
// RegisterDEXAccount registers a new ICA account for DEX operations
|
||||
func (k Keeper) RegisterDEXAccount(
|
||||
ctx sdk.Context,
|
||||
did string,
|
||||
connectionID string,
|
||||
features []string,
|
||||
) (*types.InterchainDEXAccount, error) {
|
||||
// Validate inputs
|
||||
if did == "" {
|
||||
return nil, fmt.Errorf("DID cannot be empty")
|
||||
}
|
||||
if connectionID == "" {
|
||||
return nil, fmt.Errorf("connection ID cannot be empty")
|
||||
}
|
||||
|
||||
// Validate DID exists by trying to get the document
|
||||
if _, err := k.didKeeper.GetDIDDocument(ctx, did); err != nil {
|
||||
return nil, fmt.Errorf("DID %s does not exist: %w", did, err)
|
||||
}
|
||||
|
||||
// Check if account already exists
|
||||
accountKey := GetAccountKey(did, connectionID)
|
||||
existing, err := k.Accounts.Get(ctx, accountKey)
|
||||
if err == nil {
|
||||
// Return existing account regardless of status (idempotent)
|
||||
return &existing, nil
|
||||
}
|
||||
|
||||
// Generate unique port ID
|
||||
portID := GetPortID(did, connectionID)
|
||||
|
||||
// Register ICA account
|
||||
if err := k.icaControllerKeeper.RegisterInterchainAccount(
|
||||
ctx,
|
||||
connectionID,
|
||||
portID,
|
||||
"", // Use default version
|
||||
); err != nil {
|
||||
return nil, fmt.Errorf("failed to register ICA account: %w", err)
|
||||
}
|
||||
|
||||
// Create DEX account record
|
||||
account := types.InterchainDEXAccount{
|
||||
Did: did,
|
||||
ConnectionId: connectionID,
|
||||
PortId: portID,
|
||||
EnabledFeatures: features,
|
||||
Status: types.ACCOUNT_STATUS_PENDING,
|
||||
CreatedAt: ctx.BlockTime(),
|
||||
}
|
||||
|
||||
// Store account
|
||||
if err := k.Accounts.Set(ctx, accountKey, account); err != nil {
|
||||
return nil, fmt.Errorf("failed to store DEX account: %w", err)
|
||||
}
|
||||
|
||||
// Update DID mappings
|
||||
if err := k.addDIDMapping(ctx, did, connectionID); err != nil {
|
||||
return nil, fmt.Errorf("failed to update DID mappings: %w", err)
|
||||
}
|
||||
|
||||
return &account, nil
|
||||
}
|
||||
|
||||
// GetDEXAccount retrieves a DEX account by DID and connection
|
||||
func (k Keeper) GetDEXAccount(
|
||||
ctx sdk.Context,
|
||||
did, connectionID string,
|
||||
) (*types.InterchainDEXAccount, error) {
|
||||
accountKey := GetAccountKey(did, connectionID)
|
||||
account, err := k.Accounts.Get(ctx, accountKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("DEX account not found: %w", err)
|
||||
}
|
||||
return &account, nil
|
||||
}
|
||||
|
||||
// GetDEXAccountsByDID retrieves all DEX accounts for a DID
|
||||
func (k Keeper) GetDEXAccountsByDID(
|
||||
ctx sdk.Context,
|
||||
did string,
|
||||
) ([]types.InterchainDEXAccount, error) {
|
||||
didAccounts, err := k.DIDToAccounts.Get(ctx, did)
|
||||
if err != nil {
|
||||
return nil, nil // No accounts for this DID
|
||||
}
|
||||
|
||||
var accounts []types.InterchainDEXAccount
|
||||
for _, connID := range didAccounts.Accounts {
|
||||
account, err := k.GetDEXAccount(ctx, did, connID)
|
||||
if err == nil {
|
||||
accounts = append(accounts, *account)
|
||||
}
|
||||
}
|
||||
return accounts, nil
|
||||
}
|
||||
|
||||
// SendDEXTransaction sends a transaction through ICA
|
||||
func (k Keeper) SendDEXTransaction(
|
||||
ctx sdk.Context,
|
||||
did string,
|
||||
connectionID string,
|
||||
msgs []sdk.Msg,
|
||||
memo string,
|
||||
timeoutDuration time.Duration,
|
||||
) (uint64, error) {
|
||||
// Get DEX account
|
||||
account, err := k.GetDEXAccount(ctx, did, connectionID)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to get DEX account: %w", err)
|
||||
}
|
||||
|
||||
if account.Status != types.ACCOUNT_STATUS_ACTIVE {
|
||||
return 0, fmt.Errorf("DEX account is not active")
|
||||
}
|
||||
|
||||
// Get ICA address
|
||||
icaAddress, found := k.icaControllerKeeper.GetInterchainAccountAddress(
|
||||
ctx,
|
||||
connectionID,
|
||||
account.PortId,
|
||||
)
|
||||
if !found {
|
||||
return 0, fmt.Errorf("ICA address not found")
|
||||
}
|
||||
|
||||
// Get channel capability
|
||||
channelID, found := k.icaControllerKeeper.GetActiveChannelID(ctx, connectionID, account.PortId)
|
||||
if !found {
|
||||
return 0, fmt.Errorf("active channel not found")
|
||||
}
|
||||
|
||||
chanCap, ok := k.ScopedKeeper.GetCapability(
|
||||
ctx,
|
||||
host.ChannelCapabilityPath(account.PortId, channelID),
|
||||
)
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("channel capability not found")
|
||||
}
|
||||
|
||||
// Encode messages
|
||||
data, err := icatypes.SerializeCosmosTx(k.cdc, msgs, icatypes.EncodingProtobuf)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to serialize transaction: %w", err)
|
||||
}
|
||||
|
||||
// Create packet data
|
||||
packetData := icatypes.InterchainAccountPacketData{
|
||||
Type: icatypes.EXECUTE_TX,
|
||||
Data: data,
|
||||
Memo: memo,
|
||||
}
|
||||
|
||||
// Calculate timeout
|
||||
timeoutTimestamp := ctx.BlockTime().Add(timeoutDuration).UnixNano()
|
||||
|
||||
// Send transaction
|
||||
sequence, err := k.icaControllerKeeper.SendTx(
|
||||
ctx,
|
||||
chanCap,
|
||||
connectionID,
|
||||
account.PortId,
|
||||
packetData,
|
||||
uint64(timeoutTimestamp),
|
||||
)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to send ICA transaction: %w", err)
|
||||
}
|
||||
|
||||
// Log transaction
|
||||
k.Logger(ctx).Info("DEX transaction sent",
|
||||
"did", did,
|
||||
"connection", connectionID,
|
||||
"ica_address", icaAddress,
|
||||
"sequence", sequence,
|
||||
)
|
||||
|
||||
return sequence, nil
|
||||
}
|
||||
|
||||
// OnICAAccountCreated handles successful ICA account creation
|
||||
func (k Keeper) OnICAAccountCreated(ctx sdk.Context, portID, address string) error {
|
||||
// Find account by port ID
|
||||
var account *types.InterchainDEXAccount
|
||||
k.Accounts.Walk(ctx, nil, func(key string, value types.InterchainDEXAccount) (bool, error) {
|
||||
if value.PortId == portID {
|
||||
account = &value
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
})
|
||||
|
||||
if account == nil {
|
||||
return fmt.Errorf("DEX account not found for port %s", portID)
|
||||
}
|
||||
|
||||
// Update account status and address
|
||||
account.Status = types.ACCOUNT_STATUS_ACTIVE
|
||||
account.AccountAddress = address
|
||||
account.HostChainId = k.getHostChainID(ctx, account.ConnectionId)
|
||||
|
||||
// Store updated account
|
||||
accountKey := GetAccountKey(account.Did, account.ConnectionId)
|
||||
if err := k.Accounts.Set(ctx, accountKey, *account); err != nil {
|
||||
return fmt.Errorf("failed to update DEX account: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
|
||||
func (k Keeper) addDIDMapping(ctx sdk.Context, did, connectionID string) error {
|
||||
didAccounts, _ := k.DIDToAccounts.Get(ctx, did)
|
||||
|
||||
// Check if already exists
|
||||
for _, conn := range didAccounts.Accounts {
|
||||
if conn == connectionID {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
didAccounts.Accounts = append(didAccounts.Accounts, connectionID)
|
||||
return k.DIDToAccounts.Set(ctx, did, didAccounts)
|
||||
}
|
||||
|
||||
func (k Keeper) getHostChainID(ctx sdk.Context, connectionID string) string {
|
||||
conn, found := k.connectionKeeper.GetConnection(ctx, connectionID)
|
||||
if !found {
|
||||
return ""
|
||||
}
|
||||
// Extract chain ID from connection counterparty
|
||||
// This is a simplified version - actual implementation may vary
|
||||
return conn.Counterparty.ClientId
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
package keeper_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
"github.com/sonr-io/sonr/x/dex/types"
|
||||
)
|
||||
|
||||
const (
|
||||
testConnectionID = "connection-0"
|
||||
)
|
||||
|
||||
// ICAControllerTestSuite tests ICA controller operations
|
||||
type ICAControllerTestSuite struct {
|
||||
suite.Suite
|
||||
f *testFixture
|
||||
}
|
||||
|
||||
func TestICAControllerSuite(t *testing.T) {
|
||||
suite.Run(t, new(ICAControllerTestSuite))
|
||||
}
|
||||
|
||||
func (suite *ICAControllerTestSuite) SetupTest() {
|
||||
suite.f = SetupTest(suite.T())
|
||||
}
|
||||
|
||||
// TestRegisterDEXAccount tests ICA account registration
|
||||
func (suite *ICAControllerTestSuite) TestRegisterDEXAccount() {
|
||||
did := "did:sonr:test_ica_1"
|
||||
connectionID := testConnectionID
|
||||
features := []string{"swap", "liquidity"}
|
||||
|
||||
// Register DEX account
|
||||
account, err := suite.f.k.RegisterDEXAccount(
|
||||
suite.f.ctx,
|
||||
did,
|
||||
connectionID,
|
||||
features,
|
||||
)
|
||||
suite.Require().NoError(err)
|
||||
suite.Require().NotNil(account)
|
||||
|
||||
// Verify account was created with correct fields
|
||||
suite.Require().Equal(did, account.Did)
|
||||
suite.Require().Equal(connectionID, account.ConnectionId)
|
||||
suite.Require().Equal(types.ACCOUNT_STATUS_PENDING, account.Status)
|
||||
suite.Require().NotEmpty(account.PortId)
|
||||
|
||||
// Verify port ID format
|
||||
expectedPortPrefix := "dex-" + did
|
||||
suite.Require().Contains(account.PortId, expectedPortPrefix)
|
||||
}
|
||||
|
||||
// TestRegisterDEXAccount_DuplicateRegistration tests duplicate registration
|
||||
func (suite *ICAControllerTestSuite) TestRegisterDEXAccount_DuplicateRegistration() {
|
||||
did := "did:sonr:test_ica_2"
|
||||
connectionID := testConnectionID
|
||||
|
||||
// First registration should succeed
|
||||
_, err := suite.f.k.RegisterDEXAccount(
|
||||
suite.f.ctx,
|
||||
did,
|
||||
connectionID,
|
||||
[]string{"swap"},
|
||||
)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
// Second registration with same DID and connection should return existing account
|
||||
// (idempotent behavior)
|
||||
account2, err := suite.f.k.RegisterDEXAccount(
|
||||
suite.f.ctx,
|
||||
did,
|
||||
connectionID,
|
||||
[]string{"swap"},
|
||||
)
|
||||
suite.Require().NoError(err)
|
||||
suite.Require().NotNil(account2)
|
||||
suite.Require().Equal(did, account2.Did)
|
||||
suite.Require().Equal(connectionID, account2.ConnectionId)
|
||||
}
|
||||
|
||||
// TestGetDEXAccount tests retrieving a DEX account
|
||||
func (suite *ICAControllerTestSuite) TestGetDEXAccount() {
|
||||
did := "did:sonr:test_ica_3"
|
||||
connectionID := testConnectionID
|
||||
|
||||
// Register account first
|
||||
original, err := suite.f.k.RegisterDEXAccount(
|
||||
suite.f.ctx,
|
||||
did,
|
||||
connectionID,
|
||||
[]string{"order"},
|
||||
)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
// Retrieve the account
|
||||
retrieved, err := suite.f.k.GetDEXAccount(suite.f.ctx, did, connectionID)
|
||||
suite.Require().NoError(err)
|
||||
suite.Require().NotNil(retrieved)
|
||||
|
||||
// Verify retrieved account matches original
|
||||
suite.Require().Equal(original.Did, retrieved.Did)
|
||||
suite.Require().Equal(original.ConnectionId, retrieved.ConnectionId)
|
||||
suite.Require().Equal(original.PortId, retrieved.PortId)
|
||||
}
|
||||
|
||||
// TestGetDEXAccountsByDID tests retrieving all accounts for a DID
|
||||
func (suite *ICAControllerTestSuite) TestGetDEXAccountsByDID() {
|
||||
did := "did:sonr:test_ica_4"
|
||||
connections := []string{testConnectionID, "connection-1", "connection-2"}
|
||||
|
||||
// Register multiple accounts for the same DID
|
||||
for _, connID := range connections {
|
||||
_, err := suite.f.k.RegisterDEXAccount(
|
||||
suite.f.ctx,
|
||||
did,
|
||||
connID,
|
||||
[]string{"swap"},
|
||||
)
|
||||
suite.Require().NoError(err)
|
||||
}
|
||||
|
||||
// Retrieve all accounts for the DID
|
||||
accounts, err := suite.f.k.GetDEXAccountsByDID(suite.f.ctx, did)
|
||||
suite.Require().NoError(err)
|
||||
suite.Require().Len(accounts, 3)
|
||||
|
||||
// Verify each account has the correct DID
|
||||
for _, account := range accounts {
|
||||
suite.Require().Equal(did, account.Did)
|
||||
}
|
||||
}
|
||||
|
||||
// TestOnICAAccountCreated tests ICA account creation callback
|
||||
func (suite *ICAControllerTestSuite) TestOnICAAccountCreated() {
|
||||
did := "did:sonr:test_ica_5"
|
||||
connectionID := testConnectionID
|
||||
|
||||
// Register account first
|
||||
account, err := suite.f.k.RegisterDEXAccount(
|
||||
suite.f.ctx,
|
||||
did,
|
||||
connectionID,
|
||||
[]string{"swap"},
|
||||
)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
// Simulate ICA account creation callback
|
||||
icaAddress := "cosmos1testaddress"
|
||||
err = suite.f.k.OnICAAccountCreated(
|
||||
suite.f.ctx,
|
||||
account.PortId,
|
||||
icaAddress,
|
||||
)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
// Verify account was updated
|
||||
updated, err := suite.f.k.GetDEXAccount(suite.f.ctx, did, connectionID)
|
||||
suite.Require().NoError(err)
|
||||
suite.Require().Equal(icaAddress, updated.AccountAddress)
|
||||
suite.Require().Equal(types.ACCOUNT_STATUS_ACTIVE, updated.Status)
|
||||
}
|
||||
|
||||
// TestSendDEXTransaction tests sending transactions through ICA
|
||||
func (suite *ICAControllerTestSuite) TestSendDEXTransaction() {
|
||||
did := "did:sonr:test_ica_6"
|
||||
connectionID := testConnectionID
|
||||
|
||||
// Register account first
|
||||
_, err := suite.f.k.RegisterDEXAccount(
|
||||
suite.f.ctx,
|
||||
did,
|
||||
connectionID,
|
||||
[]string{"swap"},
|
||||
)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
// SendDEXTransaction requires ACTIVE status
|
||||
// But without full capability module setup, it will fail
|
||||
// This test just verifies the account must be active
|
||||
msgs := []sdk.Msg{}
|
||||
_, err = suite.f.k.SendDEXTransaction(
|
||||
suite.f.ctx,
|
||||
did,
|
||||
connectionID,
|
||||
msgs,
|
||||
"test_memo",
|
||||
30,
|
||||
)
|
||||
// Should fail because account is not active
|
||||
suite.Require().Error(err)
|
||||
suite.Require().Contains(err.Error(), "not active")
|
||||
}
|
||||
|
||||
// TestPortBinding tests ICA port binding
|
||||
func (suite *ICAControllerTestSuite) TestPortBinding() {
|
||||
did := "did:sonr:test_ica_7"
|
||||
connectionID := testConnectionID
|
||||
|
||||
// Register account to trigger port binding
|
||||
account, err := suite.f.k.RegisterDEXAccount(
|
||||
suite.f.ctx,
|
||||
did,
|
||||
connectionID,
|
||||
[]string{"liquidity"},
|
||||
)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
// Verify port was bound (mock implementation should handle this)
|
||||
suite.Require().NotEmpty(account.PortId)
|
||||
}
|
||||
|
||||
// TestConnectionValidation tests connection ID validation
|
||||
func (suite *ICAControllerTestSuite) TestConnectionValidation() {
|
||||
did := "did:sonr:test_ica_8"
|
||||
invalidConnectionID := "invalid-connection"
|
||||
|
||||
// Should fail with invalid connection format
|
||||
_, err := suite.f.k.RegisterDEXAccount(
|
||||
suite.f.ctx,
|
||||
did,
|
||||
invalidConnectionID,
|
||||
[]string{"swap"},
|
||||
)
|
||||
// The mock might not validate this, but real implementation would
|
||||
// This test documents expected behavior
|
||||
_ = err // Error handling would depend on actual implementation
|
||||
}
|
||||
|
||||
// TestICATimeout tests ICA operation timeout handling
|
||||
func (suite *ICAControllerTestSuite) TestICATimeout() {
|
||||
did := "did:sonr:test_ica_9"
|
||||
connectionID := testConnectionID
|
||||
|
||||
// Register account
|
||||
_, err := suite.f.k.RegisterDEXAccount(
|
||||
suite.f.ctx,
|
||||
did,
|
||||
connectionID,
|
||||
[]string{"order"},
|
||||
)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
// Without active account, SendDEXTransaction should fail
|
||||
msgs := []sdk.Msg{}
|
||||
_, err = suite.f.k.SendDEXTransaction(
|
||||
suite.f.ctx,
|
||||
did,
|
||||
connectionID,
|
||||
msgs,
|
||||
"timeout_test",
|
||||
1, // 1 second timeout - very short
|
||||
)
|
||||
// Should fail because account is not active
|
||||
suite.Require().Error(err)
|
||||
suite.Require().Contains(err.Error(), "not active")
|
||||
}
|
||||
|
||||
// TestMultiChainSupport tests support for multiple chains
|
||||
func (suite *ICAControllerTestSuite) TestMultiChainSupport() {
|
||||
did := "did:sonr:test_ica_10"
|
||||
chains := map[string]string{
|
||||
testConnectionID: "osmosis-1",
|
||||
"connection-1": "cosmoshub-4",
|
||||
"connection-2": "juno-1",
|
||||
}
|
||||
|
||||
// Register accounts on multiple chains
|
||||
for connID := range chains {
|
||||
account, err := suite.f.k.RegisterDEXAccount(
|
||||
suite.f.ctx,
|
||||
did,
|
||||
connID,
|
||||
[]string{"swap", "liquidity"},
|
||||
)
|
||||
suite.Require().NoError(err)
|
||||
suite.Require().NotNil(account)
|
||||
}
|
||||
|
||||
// Verify all accounts were created
|
||||
accounts, err := suite.f.k.GetDEXAccountsByDID(suite.f.ctx, did)
|
||||
suite.Require().NoError(err)
|
||||
suite.Require().Len(accounts, 3)
|
||||
}
|
||||
Executable
+181
@@ -0,0 +1,181 @@
|
||||
package keeper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/sonr-io/sonr/crypto/ucan"
|
||||
"github.com/sonr-io/sonr/x/dex/types"
|
||||
|
||||
"cosmossdk.io/collections"
|
||||
"cosmossdk.io/core/store"
|
||||
"cosmossdk.io/log"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
capabilitykeeper "github.com/cosmos/ibc-go/modules/capability/keeper"
|
||||
portkeeper "github.com/cosmos/ibc-go/v8/modules/core/05-port/keeper"
|
||||
porttypes "github.com/cosmos/ibc-go/v8/modules/core/05-port/types"
|
||||
ibcexported "github.com/cosmos/ibc-go/v8/modules/core/exported"
|
||||
)
|
||||
|
||||
// Keeper defines the DEX module keeper
|
||||
type Keeper struct {
|
||||
storeService store.KVStoreService
|
||||
cdc codec.Codec
|
||||
schema collections.Schema
|
||||
authority string
|
||||
|
||||
// IBC dependencies
|
||||
ics4Wrapper porttypes.ICS4Wrapper
|
||||
PortKeeper *portkeeper.Keeper
|
||||
ScopedKeeper capabilitykeeper.ScopedKeeper
|
||||
|
||||
// External module dependencies
|
||||
accountKeeper types.AccountKeeper
|
||||
bankKeeper types.BankKeeper
|
||||
icaControllerKeeper types.ICAControllerKeeper
|
||||
connectionKeeper types.ConnectionKeeper
|
||||
channelKeeper types.ChannelKeeper
|
||||
didKeeper types.DIDKeeper
|
||||
dwnKeeper types.DWNKeeper
|
||||
|
||||
// UCAN functionality
|
||||
ucanVerifier *ucan.Verifier
|
||||
permissionValidator *PermissionValidator
|
||||
|
||||
// Collections for state management
|
||||
Params collections.Item[types.Params]
|
||||
Accounts collections.Map[string, types.InterchainDEXAccount]
|
||||
AccountSequence collections.Sequence
|
||||
DIDToAccounts collections.Map[string, types.DIDAccounts] // DID -> account mappings
|
||||
DIDActivities collections.Map[string, types.DEXActivity] // DID activity records
|
||||
}
|
||||
|
||||
// SetDIDKeeper sets the DID keeper (called after initialization)
|
||||
func (k *Keeper) SetDIDKeeper(didKeeper types.DIDKeeper) {
|
||||
k.didKeeper = didKeeper
|
||||
}
|
||||
|
||||
// SetDWNKeeper sets the DWN keeper (called after initialization)
|
||||
func (k *Keeper) SetDWNKeeper(dwnKeeper types.DWNKeeper) {
|
||||
k.dwnKeeper = dwnKeeper
|
||||
}
|
||||
|
||||
// NewKeeper creates a new DEX Keeper instance
|
||||
func NewKeeper(
|
||||
appCodec codec.Codec,
|
||||
storeService store.KVStoreService,
|
||||
ics4Wrapper porttypes.ICS4Wrapper,
|
||||
portKeeper *portkeeper.Keeper,
|
||||
scopedKeeper capabilitykeeper.ScopedKeeper,
|
||||
accountKeeper types.AccountKeeper,
|
||||
bankKeeper types.BankKeeper,
|
||||
icaControllerKeeper types.ICAControllerKeeper,
|
||||
connectionKeeper types.ConnectionKeeper,
|
||||
channelKeeper types.ChannelKeeper,
|
||||
didKeeper types.DIDKeeper,
|
||||
dwnKeeper types.DWNKeeper,
|
||||
authority string,
|
||||
) Keeper {
|
||||
sb := collections.NewSchemaBuilder(storeService)
|
||||
|
||||
k := Keeper{
|
||||
cdc: appCodec,
|
||||
storeService: storeService,
|
||||
authority: authority,
|
||||
|
||||
// IBC dependencies
|
||||
ics4Wrapper: ics4Wrapper,
|
||||
PortKeeper: portKeeper,
|
||||
ScopedKeeper: scopedKeeper,
|
||||
|
||||
// External dependencies
|
||||
accountKeeper: accountKeeper,
|
||||
bankKeeper: bankKeeper,
|
||||
icaControllerKeeper: icaControllerKeeper,
|
||||
connectionKeeper: connectionKeeper,
|
||||
channelKeeper: channelKeeper,
|
||||
didKeeper: didKeeper,
|
||||
dwnKeeper: dwnKeeper,
|
||||
|
||||
// State collections
|
||||
Params: collections.NewItem(
|
||||
sb,
|
||||
collections.NewPrefix(0),
|
||||
"params",
|
||||
codec.CollValue[types.Params](appCodec),
|
||||
),
|
||||
Accounts: collections.NewMap(
|
||||
sb,
|
||||
collections.NewPrefix(1),
|
||||
"accounts",
|
||||
collections.StringKey,
|
||||
codec.CollValue[types.InterchainDEXAccount](appCodec),
|
||||
),
|
||||
AccountSequence: collections.NewSequence(
|
||||
sb,
|
||||
collections.NewPrefix(2),
|
||||
"account_sequence",
|
||||
),
|
||||
DIDToAccounts: collections.NewMap(
|
||||
sb,
|
||||
collections.NewPrefix(3),
|
||||
"did_accounts",
|
||||
collections.StringKey,
|
||||
codec.CollValue[types.DIDAccounts](appCodec),
|
||||
),
|
||||
DIDActivities: collections.NewMap(
|
||||
sb,
|
||||
collections.NewPrefix(4),
|
||||
"did_activities",
|
||||
collections.StringKey,
|
||||
codec.CollValue[types.DEXActivity](appCodec),
|
||||
),
|
||||
}
|
||||
|
||||
schema, err := sb.Build()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
k.schema = schema
|
||||
|
||||
// Initialize UCAN verifier and permission validator
|
||||
if didKeeper != nil {
|
||||
didResolver := &DEXDIDResolver{keeper: k}
|
||||
k.ucanVerifier = ucan.NewVerifier(didResolver)
|
||||
k.permissionValidator = NewPermissionValidator(k)
|
||||
}
|
||||
|
||||
return k
|
||||
}
|
||||
|
||||
// WithICS4Wrapper sets the ICS4Wrapper
|
||||
func (k *Keeper) WithICS4Wrapper(wrapper porttypes.ICS4Wrapper) {
|
||||
k.ics4Wrapper = wrapper
|
||||
}
|
||||
|
||||
// Logger returns a module-specific logger
|
||||
func (k Keeper) Logger(ctx sdk.Context) log.Logger {
|
||||
return ctx.Logger().With("module", "x/"+ibcexported.ModuleName+"-"+types.ModuleName)
|
||||
}
|
||||
|
||||
// GetAuthority returns the module authority
|
||||
func (k Keeper) GetAuthority() string {
|
||||
return k.authority
|
||||
}
|
||||
|
||||
// GetPermissionValidator returns the UCAN permission validator
|
||||
func (k Keeper) GetPermissionValidator() *PermissionValidator {
|
||||
return k.permissionValidator
|
||||
}
|
||||
|
||||
// GetAccountKey generates a unique key for DEX accounts
|
||||
func GetAccountKey(did, connectionID string) string {
|
||||
return fmt.Sprintf("%s:%s", did, connectionID)
|
||||
}
|
||||
|
||||
// GetPortID generates a unique port ID for a DEX account
|
||||
func GetPortID(did, connectionID string) string {
|
||||
return fmt.Sprintf("dex-%s-%s", did, connectionID)
|
||||
}
|
||||
@@ -0,0 +1,472 @@
|
||||
package keeper_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"cosmossdk.io/log"
|
||||
"cosmossdk.io/math"
|
||||
storetypes "cosmossdk.io/store/types"
|
||||
|
||||
cmtproto "github.com/cometbft/cometbft/proto/tendermint/types"
|
||||
sdkaddress "github.com/cosmos/cosmos-sdk/codec/address"
|
||||
"github.com/cosmos/cosmos-sdk/runtime"
|
||||
"github.com/cosmos/cosmos-sdk/testutil/integration"
|
||||
simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil"
|
||||
authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper"
|
||||
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
||||
bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper"
|
||||
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
|
||||
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
|
||||
mintkeeper "github.com/cosmos/cosmos-sdk/x/mint/keeper"
|
||||
minttypes "github.com/cosmos/cosmos-sdk/x/mint/types"
|
||||
stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper"
|
||||
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
|
||||
|
||||
capabilitykeeper "github.com/cosmos/ibc-go/modules/capability/keeper"
|
||||
capabilitytypes "github.com/cosmos/ibc-go/modules/capability/types"
|
||||
icatypes "github.com/cosmos/ibc-go/v8/modules/apps/27-interchain-accounts/types"
|
||||
clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types"
|
||||
connectiontypes "github.com/cosmos/ibc-go/v8/modules/core/03-connection/types"
|
||||
channeltypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types"
|
||||
portkeeper "github.com/cosmos/ibc-go/v8/modules/core/05-port/keeper"
|
||||
ibcexported "github.com/cosmos/ibc-go/v8/modules/core/exported"
|
||||
|
||||
"github.com/sonr-io/sonr/app"
|
||||
"github.com/sonr-io/sonr/x/dex/keeper"
|
||||
"github.com/sonr-io/sonr/x/dex/types"
|
||||
didtypes "github.com/sonr-io/sonr/x/did/types"
|
||||
)
|
||||
|
||||
var maccPerms = map[string][]string{
|
||||
authtypes.FeeCollectorName: nil,
|
||||
stakingtypes.BondedPoolName: {authtypes.Burner, authtypes.Staking},
|
||||
stakingtypes.NotBondedPoolName: {authtypes.Burner, authtypes.Staking},
|
||||
minttypes.ModuleName: {authtypes.Minter},
|
||||
govtypes.ModuleName: {authtypes.Burner},
|
||||
}
|
||||
|
||||
type testFixture struct {
|
||||
suite.Suite
|
||||
|
||||
ctx sdk.Context
|
||||
k keeper.Keeper
|
||||
msgServer types.MsgServer
|
||||
queryServer types.QueryServer
|
||||
|
||||
accountkeeper authkeeper.AccountKeeper
|
||||
bankkeeper bankkeeper.BaseKeeper
|
||||
stakingKeeper *stakingkeeper.Keeper
|
||||
mintkeeper mintkeeper.Keeper
|
||||
|
||||
addrs []sdk.AccAddress
|
||||
govModAddr string
|
||||
}
|
||||
|
||||
// SetupTest creates a new test fixture
|
||||
func SetupTest(t *testing.T) *testFixture {
|
||||
t.Helper()
|
||||
f := new(testFixture)
|
||||
|
||||
cfg := sdk.GetConfig()
|
||||
cfg.SetBech32PrefixForAccount(app.Bech32PrefixAccAddr, app.Bech32PrefixAccPub)
|
||||
cfg.SetBech32PrefixForValidator(app.Bech32PrefixValAddr, app.Bech32PrefixValPub)
|
||||
cfg.SetBech32PrefixForConsensusNode(app.Bech32PrefixConsAddr, app.Bech32PrefixConsPub)
|
||||
cfg.SetCoinType(app.CoinType)
|
||||
|
||||
validatorAddressCodec := sdkaddress.NewBech32Codec(app.Bech32PrefixValAddr)
|
||||
consensusAddressCodec := sdkaddress.NewBech32Codec(app.Bech32PrefixConsAddr)
|
||||
|
||||
// Base setup
|
||||
logger := log.NewTestLogger(t)
|
||||
encCfg := moduletestutil.MakeTestEncodingConfig()
|
||||
|
||||
// Register auth types interfaces
|
||||
authtypes.RegisterInterfaces(encCfg.InterfaceRegistry)
|
||||
banktypes.RegisterInterfaces(encCfg.InterfaceRegistry)
|
||||
stakingtypes.RegisterInterfaces(encCfg.InterfaceRegistry)
|
||||
minttypes.RegisterInterfaces(encCfg.InterfaceRegistry)
|
||||
|
||||
f.govModAddr = authtypes.NewModuleAddress(govtypes.ModuleName).String()
|
||||
|
||||
// Initialize test addresses
|
||||
f.addrs = simtestutil.CreateIncrementalAccounts(3)
|
||||
|
||||
// Setup store keys
|
||||
keys := storetypes.NewKVStoreKeys(
|
||||
types.StoreKey, authtypes.StoreKey, banktypes.StoreKey,
|
||||
stakingtypes.StoreKey, minttypes.StoreKey, capabilitytypes.StoreKey,
|
||||
)
|
||||
memKeys := storetypes.NewMemoryStoreKeys(capabilitytypes.MemStoreKey)
|
||||
|
||||
cdc := encCfg.Codec
|
||||
|
||||
// Initialize keepers
|
||||
authority := authtypes.NewModuleAddress(govtypes.ModuleName)
|
||||
maccPerms[types.ModuleName] = nil
|
||||
f.accountkeeper = authkeeper.NewAccountKeeper(
|
||||
cdc, runtime.NewKVStoreService(keys[authtypes.StoreKey]),
|
||||
authtypes.ProtoBaseAccount, maccPerms,
|
||||
sdkaddress.NewBech32Codec(app.Bech32PrefixAccAddr),
|
||||
app.Bech32PrefixAccAddr, authority.String(),
|
||||
)
|
||||
|
||||
f.bankkeeper = bankkeeper.NewBaseKeeper(
|
||||
cdc, runtime.NewKVStoreService(keys[banktypes.StoreKey]),
|
||||
f.accountkeeper, nil, authority.String(), logger,
|
||||
)
|
||||
|
||||
f.stakingKeeper = stakingkeeper.NewKeeper(
|
||||
cdc, runtime.NewKVStoreService(keys[stakingtypes.StoreKey]),
|
||||
f.accountkeeper, f.bankkeeper, authority.String(),
|
||||
validatorAddressCodec, consensusAddressCodec,
|
||||
)
|
||||
|
||||
f.mintkeeper = mintkeeper.NewKeeper(
|
||||
cdc, runtime.NewKVStoreService(keys[minttypes.StoreKey]),
|
||||
f.stakingKeeper, f.accountkeeper, f.bankkeeper,
|
||||
authtypes.FeeCollectorName, authority.String(),
|
||||
)
|
||||
|
||||
// Create capability keeper for IBC
|
||||
capabilityKeeper := capabilitykeeper.NewKeeper(
|
||||
cdc,
|
||||
keys[capabilitytypes.StoreKey],
|
||||
memKeys[capabilitytypes.MemStoreKey],
|
||||
)
|
||||
|
||||
// Create scoped keeper for the DEX module
|
||||
scopedKeeper := capabilityKeeper.ScopeToModule(types.ModuleName)
|
||||
|
||||
// Create port keeper
|
||||
portKeeper := portkeeper.NewKeeper(scopedKeeper)
|
||||
|
||||
// Create mock expected keepers
|
||||
mockICS4Wrapper := &mockICS4Wrapper{}
|
||||
mockAccountKeeper := &mockAccountKeeper{}
|
||||
mockBankKeeper := &mockBankKeeper{}
|
||||
mockICAControllerKeeper := &mockICAControllerKeeper{}
|
||||
mockConnectionKeeper := &mockConnectionKeeper{}
|
||||
mockChannelKeeper := &mockChannelKeeper{}
|
||||
mockDIDKeeper := &mockDIDKeeper{}
|
||||
mockDWNKeeper := &mockDWNKeeper{}
|
||||
|
||||
// Initialize DEX keeper
|
||||
f.k = keeper.NewKeeper(
|
||||
cdc,
|
||||
runtime.NewKVStoreService(keys[types.StoreKey]),
|
||||
mockICS4Wrapper,
|
||||
&portKeeper,
|
||||
scopedKeeper,
|
||||
mockAccountKeeper,
|
||||
mockBankKeeper,
|
||||
mockICAControllerKeeper,
|
||||
mockConnectionKeeper,
|
||||
mockChannelKeeper,
|
||||
mockDIDKeeper,
|
||||
mockDWNKeeper,
|
||||
authority.String(),
|
||||
)
|
||||
|
||||
f.msgServer = keeper.NewMsgServerImpl(f.k)
|
||||
f.queryServer = keeper.NewQueryServerImpl(f.k)
|
||||
|
||||
// Initialize context with proper multistore
|
||||
cms := integration.CreateMultiStore(keys, logger)
|
||||
for _, key := range memKeys {
|
||||
cms.MountStoreWithDB(key, storetypes.StoreTypeMemory, nil)
|
||||
}
|
||||
|
||||
f.ctx = sdk.NewContext(cms, cmtproto.Header{
|
||||
Height: 1,
|
||||
Time: time.Now(),
|
||||
}, false, logger)
|
||||
|
||||
// Fund test accounts
|
||||
initCoins := sdk.NewCoins(sdk.NewCoin("usnr", math.NewInt(1000000000)))
|
||||
for _, addr := range f.addrs {
|
||||
err := f.bankkeeper.MintCoins(f.ctx, minttypes.ModuleName, initCoins)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = f.bankkeeper.SendCoinsFromModuleToAccount(
|
||||
f.ctx,
|
||||
minttypes.ModuleName,
|
||||
addr,
|
||||
initCoins,
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
// KeeperTestSuite runs all keeper tests
|
||||
type KeeperTestSuite struct {
|
||||
suite.Suite
|
||||
f *testFixture
|
||||
}
|
||||
|
||||
func TestKeeperSuite(t *testing.T) {
|
||||
suite.Run(t, new(KeeperTestSuite))
|
||||
}
|
||||
|
||||
func (suite *KeeperTestSuite) SetupTest() {
|
||||
suite.f = SetupTest(suite.T())
|
||||
}
|
||||
|
||||
// Test basic keeper operations
|
||||
func (suite *KeeperTestSuite) TestRegisterDEXAccount() {
|
||||
did := "did:sonr:test123"
|
||||
connectionID := "connection-0"
|
||||
|
||||
// Register a new DEX account through keeper method
|
||||
account, err := suite.f.k.RegisterDEXAccount(
|
||||
suite.f.ctx,
|
||||
did,
|
||||
connectionID,
|
||||
[]string{"swap", "liquidity"},
|
||||
)
|
||||
suite.Require().NoError(err)
|
||||
suite.Require().NotNil(account)
|
||||
|
||||
// Retrieve the account
|
||||
retrieved, err := suite.f.k.GetDEXAccount(suite.f.ctx, did, connectionID)
|
||||
suite.Require().NoError(err)
|
||||
suite.Require().NotNil(retrieved)
|
||||
suite.Require().Equal(did, retrieved.Did)
|
||||
suite.Require().Equal(connectionID, retrieved.ConnectionId)
|
||||
suite.Require().Equal(types.ACCOUNT_STATUS_PENDING, retrieved.Status)
|
||||
}
|
||||
|
||||
func (suite *KeeperTestSuite) TestGetDEXAccountsByDID() {
|
||||
did := "did:sonr:test456"
|
||||
|
||||
// Register multiple accounts for the same DID
|
||||
connections := []string{"connection-0", "connection-1"}
|
||||
for _, connID := range connections {
|
||||
_, err := suite.f.k.RegisterDEXAccount(
|
||||
suite.f.ctx,
|
||||
did,
|
||||
connID,
|
||||
[]string{"swap"},
|
||||
)
|
||||
suite.Require().NoError(err)
|
||||
}
|
||||
|
||||
// Retrieve all accounts for the DID
|
||||
accounts, err := suite.f.k.GetDEXAccountsByDID(suite.f.ctx, did)
|
||||
suite.Require().NoError(err)
|
||||
suite.Require().Len(accounts, 2)
|
||||
}
|
||||
|
||||
func (suite *KeeperTestSuite) TestParamsOperations() {
|
||||
// Set params
|
||||
params := types.Params{
|
||||
Enabled: true,
|
||||
MaxAccountsPerDid: 5,
|
||||
DefaultTimeoutSeconds: 600,
|
||||
AllowedConnections: []string{"connection-0", "connection-1"},
|
||||
MinSwapAmount: "100",
|
||||
MaxDailyVolume: "1000000",
|
||||
RateLimits: types.RateLimitParams{
|
||||
MaxOpsPerBlock: 10,
|
||||
MaxOpsPerDidPerDay: 100,
|
||||
CooldownBlocks: 5,
|
||||
},
|
||||
Fees: types.FeeParams{
|
||||
SwapFeeBps: 30, // 0.3%
|
||||
LiquidityFeeBps: 10, // 0.1%
|
||||
OrderFeeBps: 20, // 0.2%
|
||||
FeeCollector: "sonr1feecolllector",
|
||||
},
|
||||
}
|
||||
|
||||
err := suite.f.k.Params.Set(suite.f.ctx, params)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
// Get params
|
||||
retrieved, err := suite.f.k.Params.Get(suite.f.ctx)
|
||||
suite.Require().NoError(err)
|
||||
suite.Require().Equal(params.Enabled, retrieved.Enabled)
|
||||
suite.Require().Equal(params.MaxAccountsPerDid, retrieved.MaxAccountsPerDid)
|
||||
suite.Require().Equal(params.AllowedConnections, retrieved.AllowedConnections)
|
||||
}
|
||||
|
||||
// Mock implementations for expected keepers
|
||||
type mockICS4Wrapper struct{}
|
||||
|
||||
func (m *mockICS4Wrapper) SendPacket(
|
||||
ctx sdk.Context,
|
||||
channelCap *capabilitytypes.Capability,
|
||||
sourcePort string,
|
||||
sourceChannel string,
|
||||
timeoutHeight clienttypes.Height,
|
||||
timeoutTimestamp uint64,
|
||||
data []byte,
|
||||
) (uint64, error) {
|
||||
return 1, nil
|
||||
}
|
||||
|
||||
func (m *mockICS4Wrapper) WriteAcknowledgement(
|
||||
ctx sdk.Context,
|
||||
chanCap *capabilitytypes.Capability,
|
||||
packet ibcexported.PacketI,
|
||||
acknowledgement ibcexported.Acknowledgement,
|
||||
) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockICS4Wrapper) GetAppVersion(ctx sdk.Context, portID, channelID string) (string, bool) {
|
||||
return "ics27-1", true
|
||||
}
|
||||
|
||||
type mockAccountKeeper struct{}
|
||||
|
||||
func (m *mockAccountKeeper) GetAccount(ctx context.Context, addr sdk.AccAddress) sdk.AccountI {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockAccountKeeper) SetAccount(ctx context.Context, acc sdk.AccountI) {}
|
||||
|
||||
func (m *mockAccountKeeper) NewAccountWithAddress(
|
||||
ctx sdk.Context,
|
||||
addr sdk.AccAddress,
|
||||
) sdk.AccountI {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockAccountKeeper) GetModuleAccount(
|
||||
ctx context.Context,
|
||||
moduleName string,
|
||||
) sdk.ModuleAccountI {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockAccountKeeper) GetModuleAddress(name string) sdk.AccAddress {
|
||||
return sdk.AccAddress{}
|
||||
}
|
||||
|
||||
type mockBankKeeper struct{}
|
||||
|
||||
func (m *mockBankKeeper) SendCoins(
|
||||
ctx context.Context,
|
||||
fromAddr, toAddr sdk.AccAddress,
|
||||
amt sdk.Coins,
|
||||
) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockBankKeeper) SpendableCoins(ctx context.Context, addr sdk.AccAddress) sdk.Coins {
|
||||
return sdk.NewCoins()
|
||||
}
|
||||
|
||||
type mockICAControllerKeeper struct{}
|
||||
|
||||
func (m *mockICAControllerKeeper) RegisterInterchainAccount(
|
||||
ctx sdk.Context,
|
||||
connectionID, owner, version string,
|
||||
) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockICAControllerKeeper) GetInterchainAccountAddress(
|
||||
ctx sdk.Context,
|
||||
connectionID, portID string,
|
||||
) (string, bool) {
|
||||
return "cosmos1test", true
|
||||
}
|
||||
|
||||
func (m *mockICAControllerKeeper) SendTx(
|
||||
ctx sdk.Context,
|
||||
chanCap *capabilitytypes.Capability,
|
||||
connectionID, portID string,
|
||||
icaPacketData icatypes.InterchainAccountPacketData,
|
||||
timeoutTimestamp uint64,
|
||||
) (uint64, error) {
|
||||
return 1, nil
|
||||
}
|
||||
|
||||
func (m *mockICAControllerKeeper) GetActiveChannelID(
|
||||
ctx sdk.Context,
|
||||
connectionID, portID string,
|
||||
) (string, bool) {
|
||||
return "channel-0", true
|
||||
}
|
||||
|
||||
type mockConnectionKeeper struct{}
|
||||
|
||||
func (m *mockConnectionKeeper) GetConnection(
|
||||
ctx sdk.Context,
|
||||
connectionID string,
|
||||
) (connectiontypes.ConnectionEnd, bool) {
|
||||
return connectiontypes.ConnectionEnd{
|
||||
ClientId: "07-tendermint-0",
|
||||
Versions: []*connectiontypes.Version{{
|
||||
Identifier: "1",
|
||||
Features: []string{"ORDER_ORDERED", "ORDER_UNORDERED"},
|
||||
}},
|
||||
State: connectiontypes.OPEN,
|
||||
Counterparty: connectiontypes.Counterparty{
|
||||
ClientId: "07-tendermint-0",
|
||||
ConnectionId: "connection-0",
|
||||
},
|
||||
}, true
|
||||
}
|
||||
|
||||
type mockChannelKeeper struct{}
|
||||
|
||||
func (m *mockChannelKeeper) GetChannel(
|
||||
ctx sdk.Context,
|
||||
portID, channelID string,
|
||||
) (channeltypes.Channel, bool) {
|
||||
return channeltypes.Channel{
|
||||
State: channeltypes.OPEN,
|
||||
Ordering: channeltypes.ORDERED,
|
||||
Counterparty: channeltypes.Counterparty{
|
||||
PortId: "icahost",
|
||||
ChannelId: "channel-0",
|
||||
},
|
||||
ConnectionHops: []string{"connection-0"},
|
||||
Version: "ics27-1",
|
||||
}, true
|
||||
}
|
||||
|
||||
func (m *mockChannelKeeper) GetNextSequenceSend(
|
||||
ctx sdk.Context,
|
||||
portID, channelID string,
|
||||
) (uint64, bool) {
|
||||
return 1, true
|
||||
}
|
||||
|
||||
func (m *mockChannelKeeper) SendPacket(
|
||||
ctx sdk.Context,
|
||||
chanCap *capabilitytypes.Capability,
|
||||
sourcePort string,
|
||||
sourceChannel string,
|
||||
timeoutHeight clienttypes.Height,
|
||||
timeoutTimestamp uint64,
|
||||
data []byte,
|
||||
) (uint64, error) {
|
||||
return 1, nil
|
||||
}
|
||||
|
||||
type mockDIDKeeper struct{}
|
||||
|
||||
func (m *mockDIDKeeper) GetDIDDocument(
|
||||
ctx context.Context,
|
||||
did string,
|
||||
) (*didtypes.DIDDocument, error) {
|
||||
return &didtypes.DIDDocument{
|
||||
Id: did,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type mockDWNKeeper struct{}
|
||||
@@ -0,0 +1,191 @@
|
||||
package keeper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"cosmossdk.io/math"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
|
||||
|
||||
"github.com/sonr-io/sonr/x/dex/types"
|
||||
)
|
||||
|
||||
// ProvideLiquidity handles liquidity provision through ICA
|
||||
func (k Keeper) ProvideLiquidity(
|
||||
ctx sdk.Context,
|
||||
did string,
|
||||
connectionID string,
|
||||
poolID uint64,
|
||||
tokenA sdk.Coin,
|
||||
tokenB sdk.Coin,
|
||||
minShares math.Int,
|
||||
) (uint64, error) {
|
||||
// Get the DEX account
|
||||
account, err := k.GetDEXAccount(ctx, did, connectionID)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("DEX account not found: %w", err)
|
||||
}
|
||||
|
||||
// Verify account is active
|
||||
if account.Status != types.ACCOUNT_STATUS_ACTIVE {
|
||||
return 0, fmt.Errorf("DEX account is not active")
|
||||
}
|
||||
|
||||
// Create liquidity provision message for remote chain
|
||||
// This is a placeholder - actual implementation would use chain-specific messages
|
||||
lpMsg := &banktypes.MsgSend{
|
||||
FromAddress: account.AccountAddress,
|
||||
ToAddress: account.AccountAddress, // Placeholder
|
||||
Amount: sdk.NewCoins(tokenA, tokenB),
|
||||
}
|
||||
|
||||
// Send the liquidity transaction via ICA
|
||||
sequence, err := k.SendDEXTransaction(
|
||||
ctx,
|
||||
did,
|
||||
connectionID,
|
||||
[]sdk.Msg{lpMsg},
|
||||
fmt.Sprintf("provide_liquidity_pool_%d", poolID),
|
||||
30*time.Second,
|
||||
)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to send liquidity transaction: %w", err)
|
||||
}
|
||||
|
||||
// Emit liquidity event
|
||||
ctx.EventManager().EmitEvent(
|
||||
sdk.NewEvent(
|
||||
types.EventTypeLiquidityProvided,
|
||||
sdk.NewAttribute("did", did),
|
||||
sdk.NewAttribute("connection", connectionID),
|
||||
sdk.NewAttribute("pool_id", fmt.Sprintf("%d", poolID)),
|
||||
sdk.NewAttribute("token_a", tokenA.String()),
|
||||
sdk.NewAttribute("token_b", tokenB.String()),
|
||||
sdk.NewAttribute("sequence", fmt.Sprintf("%d", sequence)),
|
||||
),
|
||||
)
|
||||
|
||||
return sequence, nil
|
||||
}
|
||||
|
||||
// RemoveLiquidity handles liquidity removal through ICA
|
||||
func (k Keeper) RemoveLiquidity(
|
||||
ctx sdk.Context,
|
||||
did string,
|
||||
connectionID string,
|
||||
poolID uint64,
|
||||
shares math.Int,
|
||||
minAmountA math.Int,
|
||||
minAmountB math.Int,
|
||||
) (uint64, error) {
|
||||
// Get the DEX account
|
||||
account, err := k.GetDEXAccount(ctx, did, connectionID)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("DEX account not found: %w", err)
|
||||
}
|
||||
|
||||
// Verify account is active
|
||||
if account.Status != types.ACCOUNT_STATUS_ACTIVE {
|
||||
return 0, fmt.Errorf("DEX account is not active")
|
||||
}
|
||||
|
||||
// Create liquidity removal message for remote chain
|
||||
// This is a placeholder - actual implementation would use chain-specific messages
|
||||
removeMsg := &banktypes.MsgSend{
|
||||
FromAddress: account.AccountAddress,
|
||||
ToAddress: account.AccountAddress, // Placeholder
|
||||
Amount: sdk.NewCoins(sdk.NewCoin("shares", shares)),
|
||||
}
|
||||
|
||||
// Send the removal transaction via ICA
|
||||
sequence, err := k.SendDEXTransaction(
|
||||
ctx,
|
||||
did,
|
||||
connectionID,
|
||||
[]sdk.Msg{removeMsg},
|
||||
fmt.Sprintf("remove_liquidity_pool_%d", poolID),
|
||||
30*time.Second,
|
||||
)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to send liquidity removal transaction: %w", err)
|
||||
}
|
||||
|
||||
// Emit removal event
|
||||
ctx.EventManager().EmitEvent(
|
||||
sdk.NewEvent(
|
||||
types.EventTypeLiquidityRemoved,
|
||||
sdk.NewAttribute("did", did),
|
||||
sdk.NewAttribute("connection", connectionID),
|
||||
sdk.NewAttribute("pool_id", fmt.Sprintf("%d", poolID)),
|
||||
sdk.NewAttribute("shares", shares.String()),
|
||||
sdk.NewAttribute("sequence", fmt.Sprintf("%d", sequence)),
|
||||
),
|
||||
)
|
||||
|
||||
return sequence, nil
|
||||
}
|
||||
|
||||
// EstimateLPShares estimates the LP shares for given liquidity
|
||||
func (k Keeper) EstimateLPShares(
|
||||
ctx sdk.Context,
|
||||
connectionID string,
|
||||
poolID uint64,
|
||||
tokenA sdk.Coin,
|
||||
tokenB sdk.Coin,
|
||||
) (math.Int, error) {
|
||||
// This would query the remote chain for LP share estimation
|
||||
// For now, return a placeholder value
|
||||
totalValue := tokenA.Amount.Add(tokenB.Amount)
|
||||
return totalValue.QuoRaw(2), nil // Simple average as placeholder
|
||||
}
|
||||
|
||||
// GetPoolInfo retrieves pool information from remote chain
|
||||
func (k Keeper) GetPoolInfo(
|
||||
ctx sdk.Context,
|
||||
connectionID string,
|
||||
poolID uint64,
|
||||
) (*PoolInfo, error) {
|
||||
// This would query the remote chain for pool info
|
||||
// For now, return placeholder data
|
||||
return &PoolInfo{
|
||||
PoolID: poolID,
|
||||
TokenA: "uatom",
|
||||
TokenB: "uosmo",
|
||||
TotalShares: math.NewInt(1000000),
|
||||
TotalLiquidity: sdk.NewCoins(
|
||||
sdk.NewCoin("uatom", math.NewInt(500000)),
|
||||
sdk.NewCoin("uosmo", math.NewInt(500000)),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// PoolInfo represents pool information
|
||||
type PoolInfo struct {
|
||||
PoolID uint64
|
||||
TokenA string
|
||||
TokenB string
|
||||
TotalShares math.Int
|
||||
TotalLiquidity sdk.Coins
|
||||
}
|
||||
|
||||
// ValidateLiquidityParameters validates liquidity parameters
|
||||
func (k Keeper) ValidateLiquidityParameters(
|
||||
tokenA sdk.Coin,
|
||||
tokenB sdk.Coin,
|
||||
minShares math.Int,
|
||||
) error {
|
||||
if tokenA.IsZero() || tokenB.IsZero() {
|
||||
return fmt.Errorf("token amounts cannot be zero")
|
||||
}
|
||||
|
||||
if tokenA.Denom == tokenB.Denom {
|
||||
return fmt.Errorf("cannot provide liquidity with same token")
|
||||
}
|
||||
|
||||
if minShares.IsNegative() {
|
||||
return fmt.Errorf("minimum shares cannot be negative")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Executable
+219
@@ -0,0 +1,219 @@
|
||||
package keeper
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/sonr-io/sonr/x/dex/types"
|
||||
)
|
||||
|
||||
var _ types.MsgServer = msgServer{}
|
||||
|
||||
type msgServer struct {
|
||||
Keeper
|
||||
}
|
||||
|
||||
// NewMsgServerImpl returns an implementation of the module MsgServer interface.
|
||||
func NewMsgServerImpl(keeper Keeper) types.MsgServer {
|
||||
return &msgServer{Keeper: keeper}
|
||||
}
|
||||
|
||||
// RegisterDEXAccount implements types.MsgServer.
|
||||
func (ms msgServer) RegisterDEXAccount(
|
||||
ctx context.Context,
|
||||
msg *types.MsgRegisterDEXAccount,
|
||||
) (*types.MsgRegisterDEXAccountResponse, error) {
|
||||
sdkCtx := sdk.UnwrapSDKContext(ctx)
|
||||
|
||||
// Register the DEX account using the keeper's ICA controller logic
|
||||
account, err := ms.Keeper.RegisterDEXAccount(
|
||||
sdkCtx,
|
||||
msg.Did,
|
||||
msg.ConnectionId,
|
||||
msg.Features,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Emit event for account registration
|
||||
sdkCtx.EventManager().EmitEvent(
|
||||
sdk.NewEvent(
|
||||
types.EventTypeDEXAccountRegistered,
|
||||
sdk.NewAttribute("did", msg.Did),
|
||||
sdk.NewAttribute("connection_id", msg.ConnectionId),
|
||||
sdk.NewAttribute("port_id", account.PortId),
|
||||
),
|
||||
)
|
||||
|
||||
return &types.MsgRegisterDEXAccountResponse{
|
||||
PortId: account.PortId,
|
||||
AccountAddress: account.AccountAddress,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// TODO: ExecuteSwap - Implement cross-chain swap execution via ICA
|
||||
// This method should handle token swaps on remote chains through Interchain Accounts
|
||||
// Required implementation steps:
|
||||
// 1. Validate the sender's DID exists and is active using did keeper
|
||||
// 2. Verify UCAN token has proper swap capabilities (resource: swap, action: execute)
|
||||
// 3. Retrieve the ICA account for this DID and connection from state
|
||||
// 4. Build the appropriate swap message for the target chain's DEX protocol
|
||||
// 5. Create ICA packet data with the swap transaction
|
||||
// 6. Send ICA packet through IBC channel and await acknowledgment
|
||||
// 7. Store transaction details in DWN for user history tracking
|
||||
// 8. Emit events for indexing and monitoring
|
||||
// Returns: Sequence number and transaction ID on success
|
||||
// ExecuteSwap implements types.MsgServer.
|
||||
func (ms msgServer) ExecuteSwap(
|
||||
ctx context.Context,
|
||||
msg *types.MsgExecuteSwap,
|
||||
) (*types.MsgExecuteSwapResponse, error) {
|
||||
// Validate UCAN permission if token provided
|
||||
if msg.UcanToken != "" {
|
||||
// Use connection ID as resource ID for swap operations
|
||||
if err := ms.validateUCANPermission(ctx, msg.UcanToken, "swap", msg.ConnectionId, types.DEXOpExecuteSwap); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Implement swap execution via ICA
|
||||
// 1. Validate DID
|
||||
// 2. Get ICA account for this DID and connection
|
||||
// 3. Construct swap message for remote chain
|
||||
// 4. Send ICA packet with swap instruction
|
||||
// 5. Track transaction in DWN
|
||||
return &types.MsgExecuteSwapResponse{}, nil
|
||||
}
|
||||
|
||||
// validateUCANPermission validates UCAN token for a DEX operation
|
||||
func (ms msgServer) validateUCANPermission(
|
||||
ctx context.Context,
|
||||
ucanToken string,
|
||||
resourceType string,
|
||||
resourceID string,
|
||||
operation types.DEXOperation,
|
||||
) error {
|
||||
if ms.permissionValidator == nil {
|
||||
// Permission validator not available - skip validation
|
||||
return nil
|
||||
}
|
||||
|
||||
return ms.permissionValidator.ValidatePermission(
|
||||
ctx,
|
||||
ucanToken,
|
||||
resourceType,
|
||||
resourceID,
|
||||
operation,
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: ProvideLiquidity - Implement cross-chain liquidity provision via ICA
|
||||
// This method should handle adding liquidity to pools on remote chains
|
||||
// Required implementation steps:
|
||||
// 1. Validate the sender's DID exists and is active using did keeper
|
||||
// 2. Verify UCAN token has liquidity provision capabilities (resource: liquidity, action: provide)
|
||||
// 3. Retrieve the ICA account for this DID and connection from state
|
||||
// 4. Calculate appropriate liquidity amounts based on pool ratios
|
||||
// 5. Build liquidity provision message for target chain's AMM protocol
|
||||
// 6. Create ICA packet data with the liquidity transaction
|
||||
// 7. Send ICA packet through IBC channel and await acknowledgment
|
||||
// 8. Store LP token information in DWN for tracking
|
||||
// 9. Update user's position records in state
|
||||
// Returns: Sequence number and LP token amount on success
|
||||
// ProvideLiquidity implements types.MsgServer.
|
||||
func (ms msgServer) ProvideLiquidity(
|
||||
ctx context.Context,
|
||||
msg *types.MsgProvideLiquidity,
|
||||
) (*types.MsgProvideLiquidityResponse, error) {
|
||||
// TODO: Implement liquidity provision via ICA
|
||||
// 1. Validate DID and UCAN token
|
||||
// 2. Get ICA account for this DID and connection
|
||||
// 3. Construct liquidity provision message for remote chain
|
||||
// 4. Send ICA packet with liquidity instruction
|
||||
// 5. Track transaction in DWN
|
||||
return &types.MsgProvideLiquidityResponse{}, nil
|
||||
}
|
||||
|
||||
// TODO: RemoveLiquidity - Implement cross-chain liquidity removal via ICA
|
||||
// This method should handle removing liquidity from pools on remote chains
|
||||
// Required implementation steps:
|
||||
// 1. Validate the sender's DID exists and is active using did keeper
|
||||
// 2. Verify UCAN token has liquidity removal capabilities (resource: liquidity, action: remove)
|
||||
// 3. Retrieve the ICA account for this DID and connection from state
|
||||
// 4. Verify user has sufficient LP tokens to remove
|
||||
// 5. Build liquidity removal message for target chain's AMM protocol
|
||||
// 6. Create ICA packet data with the removal transaction
|
||||
// 7. Send ICA packet through IBC channel and await acknowledgment
|
||||
// 8. Update LP token information in DWN after removal
|
||||
// 9. Clear user's position records from state if fully withdrawn
|
||||
// Returns: Sequence number and withdrawn token amounts on success
|
||||
// RemoveLiquidity implements types.MsgServer.
|
||||
func (ms msgServer) RemoveLiquidity(
|
||||
ctx context.Context,
|
||||
msg *types.MsgRemoveLiquidity,
|
||||
) (*types.MsgRemoveLiquidityResponse, error) {
|
||||
// TODO: Implement liquidity removal via ICA
|
||||
// 1. Validate DID and UCAN token
|
||||
// 2. Get ICA account for this DID and connection
|
||||
// 3. Construct liquidity removal message for remote chain
|
||||
// 4. Send ICA packet with removal instruction
|
||||
// 5. Track transaction in DWN
|
||||
return &types.MsgRemoveLiquidityResponse{}, nil
|
||||
}
|
||||
|
||||
// TODO: CreateLimitOrder - Implement cross-chain limit order creation via ICA
|
||||
// This method should handle placing limit orders on remote chain order books
|
||||
// Required implementation steps:
|
||||
// 1. Validate the sender's DID exists and is active using did keeper
|
||||
// 2. Verify UCAN token has order creation capabilities (resource: order, action: create)
|
||||
// 3. Retrieve the ICA account for this DID and connection from state
|
||||
// 4. Validate order parameters (price, amount, expiry) against market conditions
|
||||
// 5. Build limit order message for target chain's order book protocol
|
||||
// 6. Create ICA packet data with the order placement transaction
|
||||
// 7. Send ICA packet through IBC channel and await acknowledgment
|
||||
// 8. Store order details in local state for tracking
|
||||
// 9. Create order record in DWN with unique order ID
|
||||
// 10. Set up monitoring for order fills and expiration
|
||||
// Returns: Sequence number and unique order ID on success
|
||||
// CreateLimitOrder implements types.MsgServer.
|
||||
func (ms msgServer) CreateLimitOrder(
|
||||
ctx context.Context,
|
||||
msg *types.MsgCreateLimitOrder,
|
||||
) (*types.MsgCreateLimitOrderResponse, error) {
|
||||
// TODO: Implement limit order creation via ICA
|
||||
// 1. Validate DID and UCAN token
|
||||
// 2. Get ICA account for this DID and connection
|
||||
// 3. Construct limit order message for remote chain
|
||||
// 4. Send ICA packet with order instruction
|
||||
// 5. Track order in DWN
|
||||
return &types.MsgCreateLimitOrderResponse{}, nil
|
||||
}
|
||||
|
||||
// TODO: CancelOrder - Implement cross-chain order cancellation via ICA
|
||||
// This method should handle cancelling existing limit orders on remote chains
|
||||
// Required implementation steps:
|
||||
// 1. Validate the sender's DID exists and is active using did keeper
|
||||
// 2. Verify UCAN token has order cancellation capabilities (resource: order, action: cancel)
|
||||
// 3. Retrieve the ICA account for this DID and connection from state
|
||||
// 4. Verify the order exists and belongs to the sender
|
||||
// 5. Check order status is still open (not filled or already cancelled)
|
||||
// 6. Build order cancellation message for target chain's order book protocol
|
||||
// 7. Create ICA packet data with the cancellation transaction
|
||||
// 8. Send ICA packet through IBC channel and await acknowledgment
|
||||
// 9. Update order status in local state to cancelled
|
||||
// 10. Update order record in DWN with cancellation details
|
||||
// Returns: Sequence number on successful cancellation
|
||||
// CancelOrder implements types.MsgServer.
|
||||
func (ms msgServer) CancelOrder(
|
||||
ctx context.Context,
|
||||
msg *types.MsgCancelOrder,
|
||||
) (*types.MsgCancelOrderResponse, error) {
|
||||
// TODO: Implement order cancellation via ICA
|
||||
// 1. Validate DID and UCAN token
|
||||
// 2. Get ICA account for this DID and connection
|
||||
// 3. Construct order cancellation message for remote chain
|
||||
// 4. Send ICA packet with cancellation instruction
|
||||
// 5. Update order status in DWN
|
||||
return &types.MsgCancelOrderResponse{}, nil
|
||||
}
|
||||
@@ -0,0 +1,316 @@
|
||||
package keeper_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"cosmossdk.io/math"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
"github.com/sonr-io/sonr/x/dex/keeper"
|
||||
"github.com/sonr-io/sonr/x/dex/types"
|
||||
)
|
||||
|
||||
// MsgServerTestSuite tests message server operations
|
||||
type MsgServerTestSuite struct {
|
||||
suite.Suite
|
||||
f *testFixture
|
||||
}
|
||||
|
||||
func TestMsgServerSuite(t *testing.T) {
|
||||
suite.Run(t, new(MsgServerTestSuite))
|
||||
}
|
||||
|
||||
func (suite *MsgServerTestSuite) SetupTest() {
|
||||
suite.f = SetupTest(suite.T())
|
||||
}
|
||||
|
||||
// TestMsgRegisterDEXAccount tests the RegisterDEXAccount message handler
|
||||
func (suite *MsgServerTestSuite) TestMsgRegisterDEXAccount() {
|
||||
msgServer := keeper.NewMsgServerImpl(suite.f.k)
|
||||
ctx := sdk.WrapSDKContext(suite.f.ctx)
|
||||
|
||||
// Create test message
|
||||
msg := &types.MsgRegisterDEXAccount{
|
||||
Did: "did:sonr:alice",
|
||||
ConnectionId: "connection-0",
|
||||
Features: []string{"swap", "liquidity"},
|
||||
}
|
||||
|
||||
// Execute message
|
||||
resp, err := msgServer.RegisterDEXAccount(ctx, msg)
|
||||
suite.Require().NoError(err)
|
||||
suite.Require().NotNil(resp)
|
||||
suite.Require().NotEmpty(resp.PortId)
|
||||
|
||||
// Verify account was created
|
||||
account, err := suite.f.k.GetDEXAccount(suite.f.ctx, msg.Did, msg.ConnectionId)
|
||||
suite.Require().NoError(err)
|
||||
suite.Require().NotNil(account)
|
||||
suite.Require().Equal(msg.Did, account.Did)
|
||||
suite.Require().Equal(msg.ConnectionId, account.ConnectionId)
|
||||
}
|
||||
|
||||
// TestMsgExecuteSwap tests the ExecuteSwap message handler
|
||||
func (suite *MsgServerTestSuite) TestMsgExecuteSwap() {
|
||||
msgServer := keeper.NewMsgServerImpl(suite.f.k)
|
||||
ctx := sdk.WrapSDKContext(suite.f.ctx)
|
||||
|
||||
// First register an account
|
||||
_, err := suite.f.k.RegisterDEXAccount(
|
||||
suite.f.ctx,
|
||||
"did:sonr:bob",
|
||||
"connection-0",
|
||||
[]string{"swap"},
|
||||
)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
// Create swap message
|
||||
msg := &types.MsgExecuteSwap{
|
||||
Did: "did:sonr:bob",
|
||||
ConnectionId: "connection-0",
|
||||
SourceDenom: "usnr",
|
||||
TargetDenom: "uosmo",
|
||||
Amount: math.NewInt(1000),
|
||||
MinAmountOut: math.NewInt(900),
|
||||
Route: "pool:1",
|
||||
}
|
||||
|
||||
// Execute swap
|
||||
resp, err := msgServer.ExecuteSwap(ctx, msg)
|
||||
suite.Require().NoError(err)
|
||||
suite.Require().NotNil(resp)
|
||||
// TODO: Check sequence when ExecuteSwap is implemented
|
||||
// suite.Require().NotZero(resp.Sequence)
|
||||
}
|
||||
|
||||
// TestMsgProvideLiquidity tests the ProvideLiquidity message handler
|
||||
func (suite *MsgServerTestSuite) TestMsgProvideLiquidity() {
|
||||
msgServer := keeper.NewMsgServerImpl(suite.f.k)
|
||||
ctx := sdk.WrapSDKContext(suite.f.ctx)
|
||||
|
||||
// First register an account
|
||||
_, err := suite.f.k.RegisterDEXAccount(
|
||||
suite.f.ctx,
|
||||
"did:sonr:charlie",
|
||||
"connection-0",
|
||||
[]string{"liquidity"},
|
||||
)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
// Create liquidity message
|
||||
msg := &types.MsgProvideLiquidity{
|
||||
Did: "did:sonr:charlie",
|
||||
ConnectionId: "connection-0",
|
||||
PoolId: "1",
|
||||
Assets: sdk.NewCoins(
|
||||
sdk.NewCoin("usnr", math.NewInt(1000)),
|
||||
sdk.NewCoin("uosmo", math.NewInt(1000)),
|
||||
),
|
||||
MinShares: math.NewInt(100),
|
||||
Timeout: time.Now().Add(5 * time.Minute),
|
||||
}
|
||||
|
||||
// Execute liquidity provision
|
||||
resp, err := msgServer.ProvideLiquidity(ctx, msg)
|
||||
suite.Require().NoError(err)
|
||||
suite.Require().NotNil(resp)
|
||||
// TODO: Check sequence when ProvideLiquidity is implemented
|
||||
// suite.Require().NotZero(resp.Sequence)
|
||||
}
|
||||
|
||||
// TestMsgRemoveLiquidity tests the RemoveLiquidity message handler
|
||||
func (suite *MsgServerTestSuite) TestMsgRemoveLiquidity() {
|
||||
msgServer := keeper.NewMsgServerImpl(suite.f.k)
|
||||
ctx := sdk.WrapSDKContext(suite.f.ctx)
|
||||
|
||||
// First register an account
|
||||
_, err := suite.f.k.RegisterDEXAccount(
|
||||
suite.f.ctx,
|
||||
"did:sonr:dave",
|
||||
"connection-0",
|
||||
[]string{"liquidity"},
|
||||
)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
// Create remove liquidity message
|
||||
msg := &types.MsgRemoveLiquidity{
|
||||
Did: "did:sonr:dave",
|
||||
ConnectionId: "connection-0",
|
||||
PoolId: "1",
|
||||
Shares: math.NewInt(100),
|
||||
MinAmounts: sdk.NewCoins(
|
||||
sdk.NewCoin("usnr", math.NewInt(900)),
|
||||
sdk.NewCoin("uosmo", math.NewInt(900)),
|
||||
),
|
||||
Timeout: time.Now().Add(5 * time.Minute),
|
||||
}
|
||||
|
||||
// Execute liquidity removal
|
||||
resp, err := msgServer.RemoveLiquidity(ctx, msg)
|
||||
suite.Require().NoError(err)
|
||||
suite.Require().NotNil(resp)
|
||||
// TODO: Check sequence when RemoveLiquidity is implemented
|
||||
// suite.Require().NotZero(resp.Sequence)
|
||||
}
|
||||
|
||||
// TestMsgCreateLimitOrder tests the CreateLimitOrder message handler
|
||||
func (suite *MsgServerTestSuite) TestMsgCreateLimitOrder() {
|
||||
msgServer := keeper.NewMsgServerImpl(suite.f.k)
|
||||
ctx := sdk.WrapSDKContext(suite.f.ctx)
|
||||
|
||||
// First register an account
|
||||
_, err := suite.f.k.RegisterDEXAccount(
|
||||
suite.f.ctx,
|
||||
"did:sonr:eve",
|
||||
"connection-0",
|
||||
[]string{"order"},
|
||||
)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
// Create limit order message
|
||||
msg := &types.MsgCreateLimitOrder{
|
||||
Did: "did:sonr:eve",
|
||||
ConnectionId: "connection-0",
|
||||
SellDenom: "usnr",
|
||||
BuyDenom: "uosmo",
|
||||
Amount: math.NewInt(1000),
|
||||
Price: math.LegacyNewDec(1),
|
||||
Expiration: time.Now().Add(24 * time.Hour),
|
||||
}
|
||||
|
||||
// Execute order creation
|
||||
resp, err := msgServer.CreateLimitOrder(ctx, msg)
|
||||
suite.Require().NoError(err)
|
||||
suite.Require().NotNil(resp)
|
||||
// TODO: Check sequence and OrderId when CreateLimitOrder is implemented
|
||||
// suite.Require().NotZero(resp.Sequence)
|
||||
// suite.Require().NotEmpty(resp.OrderId)
|
||||
}
|
||||
|
||||
// TestMsgCancelOrder tests the CancelOrder message handler
|
||||
func (suite *MsgServerTestSuite) TestMsgCancelOrder() {
|
||||
msgServer := keeper.NewMsgServerImpl(suite.f.k)
|
||||
ctx := sdk.WrapSDKContext(suite.f.ctx)
|
||||
|
||||
// First register an account and create an order
|
||||
_, err := suite.f.k.RegisterDEXAccount(
|
||||
suite.f.ctx,
|
||||
"did:sonr:frank",
|
||||
"connection-0",
|
||||
[]string{"order"},
|
||||
)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
// Since CreateLimitOrder is not implemented yet, use a mock order ID
|
||||
mockOrderId := "order-123"
|
||||
|
||||
// Cancel the order
|
||||
cancelMsg := &types.MsgCancelOrder{
|
||||
Did: "did:sonr:frank",
|
||||
ConnectionId: "connection-0",
|
||||
OrderId: mockOrderId,
|
||||
}
|
||||
|
||||
// Execute order cancellation
|
||||
resp, err := msgServer.CancelOrder(ctx, cancelMsg)
|
||||
suite.Require().NoError(err)
|
||||
suite.Require().NotNil(resp)
|
||||
// TODO: Check sequence when CancelOrder is implemented
|
||||
// suite.Require().NotZero(resp.Sequence)
|
||||
}
|
||||
|
||||
// TestMsgRegisterDEXAccount_InvalidDID tests registration with invalid DID
|
||||
func (suite *MsgServerTestSuite) TestMsgRegisterDEXAccount_InvalidDID() {
|
||||
msgServer := keeper.NewMsgServerImpl(suite.f.k)
|
||||
ctx := sdk.WrapSDKContext(suite.f.ctx)
|
||||
|
||||
// Create test message with invalid DID
|
||||
msg := &types.MsgRegisterDEXAccount{
|
||||
Did: "", // Empty DID
|
||||
ConnectionId: "connection-0",
|
||||
Features: []string{"swap"},
|
||||
}
|
||||
|
||||
// Should fail validation
|
||||
_, err := msgServer.RegisterDEXAccount(ctx, msg)
|
||||
suite.Require().Error(err)
|
||||
}
|
||||
|
||||
// TestMsgExecuteSwap_AccountNotFound tests swap with non-existent account
|
||||
func (suite *MsgServerTestSuite) TestMsgExecuteSwap_AccountNotFound() {
|
||||
msgServer := keeper.NewMsgServerImpl(suite.f.k)
|
||||
ctx := sdk.WrapSDKContext(suite.f.ctx)
|
||||
|
||||
// Create swap message without registering account
|
||||
msg := &types.MsgExecuteSwap{
|
||||
Did: "did:sonr:nonexistent",
|
||||
ConnectionId: "connection-0",
|
||||
SourceDenom: "usnr",
|
||||
TargetDenom: "uosmo",
|
||||
Amount: math.NewInt(1000),
|
||||
MinAmountOut: math.NewInt(900),
|
||||
Route: "pool:1",
|
||||
}
|
||||
|
||||
// TODO: Should fail when ExecuteSwap is implemented - account not found
|
||||
_, err := msgServer.ExecuteSwap(ctx, msg)
|
||||
suite.Require().NoError(err) // Currently returns empty response
|
||||
// suite.Require().Error(err)
|
||||
// suite.Require().Contains(err.Error(), "not found")
|
||||
}
|
||||
|
||||
// TestMsgProvideLiquidity_InvalidAssets tests liquidity with invalid assets
|
||||
func (suite *MsgServerTestSuite) TestMsgProvideLiquidity_InvalidAssets() {
|
||||
// First register an account
|
||||
_, err := suite.f.k.RegisterDEXAccount(
|
||||
suite.f.ctx,
|
||||
"did:sonr:grace",
|
||||
"connection-0",
|
||||
[]string{"liquidity"},
|
||||
)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
// Create liquidity message with empty assets
|
||||
msg := &types.MsgProvideLiquidity{
|
||||
Did: "did:sonr:grace",
|
||||
ConnectionId: "connection-0",
|
||||
PoolId: "1",
|
||||
Assets: sdk.NewCoins(), // Empty coins list
|
||||
MinShares: math.NewInt(100),
|
||||
Timeout: time.Now().Add(5 * time.Minute),
|
||||
}
|
||||
|
||||
// Should fail validation due to empty assets
|
||||
err = msg.ValidateBasic()
|
||||
suite.Require().Error(err)
|
||||
}
|
||||
|
||||
// TestMsgCreateLimitOrder_InvalidPrice tests order creation with invalid price
|
||||
func (suite *MsgServerTestSuite) TestMsgCreateLimitOrder_InvalidPrice() {
|
||||
// First register an account
|
||||
_, err := suite.f.k.RegisterDEXAccount(
|
||||
suite.f.ctx,
|
||||
"did:sonr:henry",
|
||||
"connection-0",
|
||||
[]string{"order"},
|
||||
)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
// Create limit order message with zero price
|
||||
msg := &types.MsgCreateLimitOrder{
|
||||
Did: "did:sonr:henry",
|
||||
ConnectionId: "connection-0",
|
||||
SellDenom: "usnr",
|
||||
BuyDenom: "uosmo",
|
||||
Amount: math.NewInt(1000),
|
||||
Price: math.LegacyZeroDec(), // Invalid: zero price
|
||||
Expiration: time.Now().Add(24 * time.Hour),
|
||||
}
|
||||
|
||||
// Should fail validation
|
||||
err = msg.ValidateBasic()
|
||||
suite.Require().Error(err)
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
package keeper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"cosmossdk.io/math"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
|
||||
|
||||
"github.com/sonr-io/sonr/x/dex/types"
|
||||
)
|
||||
|
||||
// CreateLimitOrder creates a limit order through ICA
|
||||
func (k Keeper) CreateLimitOrder(
|
||||
ctx sdk.Context,
|
||||
did string,
|
||||
connectionID string,
|
||||
tokenIn sdk.Coin,
|
||||
tokenOutDenom string,
|
||||
price math.LegacyDec,
|
||||
orderType OrderType,
|
||||
) (uint64, error) {
|
||||
// Get the DEX account
|
||||
account, err := k.GetDEXAccount(ctx, did, connectionID)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("DEX account not found: %w", err)
|
||||
}
|
||||
|
||||
// Verify account is active
|
||||
if account.Status != types.ACCOUNT_STATUS_ACTIVE {
|
||||
return 0, fmt.Errorf("DEX account is not active")
|
||||
}
|
||||
|
||||
// Create limit order message for remote chain
|
||||
// This is a placeholder - actual implementation would use chain-specific messages
|
||||
orderMsg := &banktypes.MsgSend{
|
||||
FromAddress: account.AccountAddress,
|
||||
ToAddress: account.AccountAddress, // Placeholder
|
||||
Amount: sdk.NewCoins(tokenIn),
|
||||
}
|
||||
|
||||
// Send the order transaction via ICA
|
||||
sequence, err := k.SendDEXTransaction(
|
||||
ctx,
|
||||
did,
|
||||
connectionID,
|
||||
[]sdk.Msg{orderMsg},
|
||||
fmt.Sprintf("limit_order_%s_for_%s", tokenIn.Denom, tokenOutDenom),
|
||||
30*time.Second,
|
||||
)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to send order transaction: %w", err)
|
||||
}
|
||||
|
||||
// Store order ID mapping (sequence -> order details)
|
||||
orderID := fmt.Sprintf("%s_%s_%d", did, connectionID, sequence)
|
||||
|
||||
// Emit order created event
|
||||
ctx.EventManager().EmitEvent(
|
||||
sdk.NewEvent(
|
||||
types.EventTypeOrderCreated,
|
||||
sdk.NewAttribute("did", did),
|
||||
sdk.NewAttribute("connection", connectionID),
|
||||
sdk.NewAttribute("order_id", orderID),
|
||||
sdk.NewAttribute("token_in", tokenIn.String()),
|
||||
sdk.NewAttribute("token_out", tokenOutDenom),
|
||||
sdk.NewAttribute("price", price.String()),
|
||||
sdk.NewAttribute("sequence", fmt.Sprintf("%d", sequence)),
|
||||
),
|
||||
)
|
||||
|
||||
return sequence, nil
|
||||
}
|
||||
|
||||
// CancelOrder cancels an existing order through ICA
|
||||
func (k Keeper) CancelOrder(
|
||||
ctx sdk.Context,
|
||||
did string,
|
||||
connectionID string,
|
||||
orderID string,
|
||||
) (uint64, error) {
|
||||
// Get the DEX account
|
||||
account, err := k.GetDEXAccount(ctx, did, connectionID)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("DEX account not found: %w", err)
|
||||
}
|
||||
|
||||
// Verify account is active
|
||||
if account.Status != types.ACCOUNT_STATUS_ACTIVE {
|
||||
return 0, fmt.Errorf("DEX account is not active")
|
||||
}
|
||||
|
||||
// Create cancel order message for remote chain
|
||||
// This is a placeholder - actual implementation would use chain-specific messages
|
||||
cancelMsg := &banktypes.MsgSend{
|
||||
FromAddress: account.AccountAddress,
|
||||
ToAddress: account.AccountAddress, // Placeholder
|
||||
Amount: sdk.NewCoins(), // Empty amount for cancel
|
||||
}
|
||||
|
||||
// Send the cancel transaction via ICA
|
||||
sequence, err := k.SendDEXTransaction(
|
||||
ctx,
|
||||
did,
|
||||
connectionID,
|
||||
[]sdk.Msg{cancelMsg},
|
||||
fmt.Sprintf("cancel_order_%s", orderID),
|
||||
30*time.Second,
|
||||
)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to send cancel transaction: %w", err)
|
||||
}
|
||||
|
||||
// Emit order cancelled event
|
||||
ctx.EventManager().EmitEvent(
|
||||
sdk.NewEvent(
|
||||
types.EventTypeOrderCancelled,
|
||||
sdk.NewAttribute("did", did),
|
||||
sdk.NewAttribute("connection", connectionID),
|
||||
sdk.NewAttribute("order_id", orderID),
|
||||
sdk.NewAttribute("sequence", fmt.Sprintf("%d", sequence)),
|
||||
),
|
||||
)
|
||||
|
||||
return sequence, nil
|
||||
}
|
||||
|
||||
// OrderType represents the type of order
|
||||
type OrderType int
|
||||
|
||||
const (
|
||||
OrderTypeLimit OrderType = iota
|
||||
OrderTypeMarket
|
||||
OrderTypeStopLoss
|
||||
OrderTypeTakeProfit
|
||||
)
|
||||
|
||||
// OrderStatus represents the status of an order
|
||||
type OrderStatus int
|
||||
|
||||
const (
|
||||
OrderStatusPending OrderStatus = iota
|
||||
OrderStatusOpen
|
||||
OrderStatusPartiallyFilled
|
||||
OrderStatusFilled
|
||||
OrderStatusCancelled
|
||||
OrderStatusExpired
|
||||
)
|
||||
|
||||
// OrderInfo represents order information
|
||||
type OrderInfo struct {
|
||||
OrderID string
|
||||
DID string
|
||||
ConnectionID string
|
||||
TokenIn sdk.Coin
|
||||
TokenOut string
|
||||
Price math.LegacyDec
|
||||
Type OrderType
|
||||
Status OrderStatus
|
||||
FilledAmount math.Int
|
||||
RemainingAmount math.Int
|
||||
CreatedAt int64
|
||||
UpdatedAt int64
|
||||
}
|
||||
|
||||
// GetOrderInfo retrieves order information
|
||||
func (k Keeper) GetOrderInfo(
|
||||
ctx sdk.Context,
|
||||
did string,
|
||||
connectionID string,
|
||||
orderID string,
|
||||
) (*OrderInfo, error) {
|
||||
// This would retrieve order info from state or remote chain
|
||||
// For now, return placeholder data
|
||||
return &OrderInfo{
|
||||
OrderID: orderID,
|
||||
DID: did,
|
||||
ConnectionID: connectionID,
|
||||
TokenIn: sdk.NewCoin("uatom", math.NewInt(1000)),
|
||||
TokenOut: "uosmo",
|
||||
Price: math.LegacyNewDec(10),
|
||||
Type: OrderTypeLimit,
|
||||
Status: OrderStatusOpen,
|
||||
FilledAmount: math.ZeroInt(),
|
||||
RemainingAmount: math.NewInt(1000),
|
||||
CreatedAt: ctx.BlockTime().Unix(),
|
||||
UpdatedAt: ctx.BlockTime().Unix(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetOrdersByDID retrieves all orders for a DID
|
||||
func (k Keeper) GetOrdersByDID(
|
||||
ctx sdk.Context,
|
||||
did string,
|
||||
status OrderStatus,
|
||||
) ([]*OrderInfo, error) {
|
||||
// This would query orders from state or remote chain
|
||||
// For now, return empty list
|
||||
return []*OrderInfo{}, nil
|
||||
}
|
||||
|
||||
// ValidateOrderParameters validates order parameters
|
||||
func (k Keeper) ValidateOrderParameters(
|
||||
tokenIn sdk.Coin,
|
||||
tokenOutDenom string,
|
||||
price math.LegacyDec,
|
||||
orderType OrderType,
|
||||
) error {
|
||||
if tokenIn.IsZero() {
|
||||
return fmt.Errorf("token in amount cannot be zero")
|
||||
}
|
||||
|
||||
if tokenOutDenom == "" {
|
||||
return fmt.Errorf("token out denomination cannot be empty")
|
||||
}
|
||||
|
||||
if tokenIn.Denom == tokenOutDenom {
|
||||
return fmt.Errorf("cannot create order with same token")
|
||||
}
|
||||
|
||||
if price.IsNegative() || price.IsZero() {
|
||||
return fmt.Errorf("price must be positive")
|
||||
}
|
||||
|
||||
if orderType < OrderTypeLimit || orderType > OrderTypeTakeProfit {
|
||||
return fmt.Errorf("invalid order type")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,330 @@
|
||||
package keeper
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/sonr-io/sonr/crypto/keys"
|
||||
"github.com/sonr-io/sonr/crypto/ucan"
|
||||
"github.com/sonr-io/sonr/x/dex/types"
|
||||
)
|
||||
|
||||
// PermissionValidator wraps UCAN verifier for DEX-specific permission validation
|
||||
type PermissionValidator struct {
|
||||
verifier *ucan.Verifier
|
||||
keeper Keeper
|
||||
permissions *types.UCANPermissionRegistry
|
||||
}
|
||||
|
||||
// NewPermissionValidator creates a new DEX permission validator
|
||||
func NewPermissionValidator(keeper Keeper) *PermissionValidator {
|
||||
didResolver := &DEXDIDResolver{keeper: keeper}
|
||||
verifier := ucan.NewVerifier(didResolver)
|
||||
|
||||
return &PermissionValidator{
|
||||
verifier: verifier,
|
||||
keeper: keeper,
|
||||
permissions: types.NewUCANPermissionRegistry(),
|
||||
}
|
||||
}
|
||||
|
||||
// ValidatePermission validates UCAN token for DEX operation
|
||||
func (pv *PermissionValidator) ValidatePermission(
|
||||
ctx context.Context,
|
||||
tokenString string,
|
||||
resourceType string,
|
||||
resourceID string,
|
||||
operation types.DEXOperation,
|
||||
) error {
|
||||
// Get required UCAN capabilities for the operation
|
||||
capabilities, err := pv.permissions.GetRequiredUCANCapabilities(operation)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get required UCAN capabilities: %w", err)
|
||||
}
|
||||
|
||||
// Build resource URI for DEX
|
||||
mapper := types.NewUCANCapabilityMapper()
|
||||
resourceURI := mapper.CreateDEXResourceURI(resourceType, resourceID)
|
||||
|
||||
// Verify UCAN token grants required capabilities
|
||||
_, err = pv.verifier.VerifyCapability(
|
||||
ctx,
|
||||
tokenString,
|
||||
resourceURI,
|
||||
capabilities,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("UCAN validation failed: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateSwapPermission validates UCAN token for swap operations
|
||||
func (pv *PermissionValidator) ValidateSwapPermission(
|
||||
ctx context.Context,
|
||||
tokenString string,
|
||||
poolID string,
|
||||
amount string,
|
||||
operation types.DEXOperation,
|
||||
) error {
|
||||
// Get required UCAN capabilities for the operation
|
||||
capabilities, err := pv.permissions.GetRequiredUCANCapabilities(operation)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get required UCAN capabilities: %w", err)
|
||||
}
|
||||
|
||||
// Build pool resource URI
|
||||
mapper := types.NewUCANCapabilityMapper()
|
||||
resourceURI := mapper.CreatePoolResourceURI(poolID)
|
||||
|
||||
// Verify UCAN token
|
||||
token, err := pv.verifier.VerifyCapability(
|
||||
ctx,
|
||||
tokenString,
|
||||
resourceURI,
|
||||
capabilities,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("UCAN validation failed: %w", err)
|
||||
}
|
||||
|
||||
// Additional amount validation
|
||||
if err := pv.validateAmountConstraint(token, amount); err != nil {
|
||||
return fmt.Errorf("amount constraint validation failed: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateLiquidityPermission validates UCAN token for liquidity operations
|
||||
func (pv *PermissionValidator) ValidateLiquidityPermission(
|
||||
ctx context.Context,
|
||||
tokenString string,
|
||||
poolID string,
|
||||
operation types.DEXOperation,
|
||||
) error {
|
||||
// Get required UCAN capabilities for the operation
|
||||
capabilities, err := pv.permissions.GetRequiredUCANCapabilities(operation)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get required UCAN capabilities: %w", err)
|
||||
}
|
||||
|
||||
// Build pool resource URI
|
||||
mapper := types.NewUCANCapabilityMapper()
|
||||
resourceURI := mapper.CreatePoolResourceURI(poolID)
|
||||
|
||||
// Verify UCAN token
|
||||
_, err = pv.verifier.VerifyCapability(
|
||||
ctx,
|
||||
tokenString,
|
||||
resourceURI,
|
||||
capabilities,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("UCAN validation failed: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateOrderPermission validates UCAN token for order operations
|
||||
func (pv *PermissionValidator) ValidateOrderPermission(
|
||||
ctx context.Context,
|
||||
tokenString string,
|
||||
orderID string,
|
||||
operation types.DEXOperation,
|
||||
) error {
|
||||
// Get required UCAN capabilities for the operation
|
||||
capabilities, err := pv.permissions.GetRequiredUCANCapabilities(operation)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get required UCAN capabilities: %w", err)
|
||||
}
|
||||
|
||||
// Build order resource URI
|
||||
mapper := types.NewUCANCapabilityMapper()
|
||||
resourceURI := mapper.CreateOrderResourceURI(orderID)
|
||||
|
||||
// Verify UCAN token
|
||||
_, err = pv.verifier.VerifyCapability(
|
||||
ctx,
|
||||
tokenString,
|
||||
resourceURI,
|
||||
capabilities,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("UCAN validation failed: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// VerifyDelegationChain validates complete UCAN delegation chain
|
||||
func (pv *PermissionValidator) VerifyDelegationChain(
|
||||
ctx context.Context,
|
||||
tokenString string,
|
||||
) error {
|
||||
return pv.verifier.VerifyDelegationChain(ctx, tokenString)
|
||||
}
|
||||
|
||||
// Internal validation methods
|
||||
|
||||
// validateAmountConstraint validates amount constraints
|
||||
func (pv *PermissionValidator) validateAmountConstraint(
|
||||
token *ucan.Token,
|
||||
amount string,
|
||||
) error {
|
||||
// For now, we'll accept all amounts
|
||||
// In a real implementation, we'd check against maximum amounts
|
||||
// specified in the token's attenuations
|
||||
return nil
|
||||
}
|
||||
|
||||
// validatePoolConstraint validates pool constraints
|
||||
func (pv *PermissionValidator) validatePoolConstraint(
|
||||
token *ucan.Token,
|
||||
poolID string,
|
||||
) error {
|
||||
// Check if the token's resource matches the pool
|
||||
for _, att := range token.Attenuations {
|
||||
if simpleResource, ok := att.Resource.(*ucan.SimpleResource); ok {
|
||||
// Check if resource matches pool pattern
|
||||
if simpleResource.Scheme == "dex" {
|
||||
expectedValue := fmt.Sprintf("pool:%s", poolID)
|
||||
if simpleResource.Value == expectedValue || simpleResource.Value == "pool:*" {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("no matching pool attenuation found for pool %s", poolID)
|
||||
}
|
||||
|
||||
// Helper methods
|
||||
|
||||
// CreateAttenuation creates a UCAN attenuation for DEX operations
|
||||
func (pv *PermissionValidator) CreateAttenuation(
|
||||
actions []string,
|
||||
resourceType string,
|
||||
resourceID string,
|
||||
) ucan.Attenuation {
|
||||
return pv.permissions.CreateDEXAttenuation(actions, resourceType, resourceID)
|
||||
}
|
||||
|
||||
// CreateAmountLimitedAttenuation creates an amount-limited UCAN attenuation
|
||||
func (pv *PermissionValidator) CreateAmountLimitedAttenuation(
|
||||
actions []string,
|
||||
poolID string,
|
||||
maxAmount string,
|
||||
) ucan.Attenuation {
|
||||
return pv.permissions.CreateAmountLimitedAttenuation(actions, poolID, maxAmount)
|
||||
}
|
||||
|
||||
// CreatePoolRestrictedAttenuation creates a pool-restricted UCAN attenuation
|
||||
func (pv *PermissionValidator) CreatePoolRestrictedAttenuation(
|
||||
actions []string,
|
||||
allowedPools []string,
|
||||
) ucan.Attenuation {
|
||||
return pv.permissions.CreatePoolRestrictedAttenuation(actions, allowedPools)
|
||||
}
|
||||
|
||||
// DEXDIDResolver implements ucan.DIDResolver for DEX module
|
||||
type DEXDIDResolver struct {
|
||||
keeper Keeper
|
||||
}
|
||||
|
||||
// ResolveDIDKey resolves DID to public key for UCAN verification
|
||||
func (r *DEXDIDResolver) ResolveDIDKey(ctx context.Context, did string) (keys.DID, error) {
|
||||
// For DEX module, we need to resolve DIDs from the DID module
|
||||
// This would require cross-module keeper access
|
||||
|
||||
// Check if the DEX keeper has access to DID keeper
|
||||
if r.keeper.didKeeper != nil {
|
||||
didDoc, err := r.keeper.didKeeper.GetDIDDocument(ctx, did)
|
||||
if err != nil {
|
||||
return keys.DID{}, fmt.Errorf("failed to get DID document: %w", err)
|
||||
}
|
||||
|
||||
if didDoc == nil {
|
||||
return keys.DID{}, fmt.Errorf("DID document not found")
|
||||
}
|
||||
|
||||
// Parse the DID string into a keys.DID
|
||||
return keys.Parse(did)
|
||||
}
|
||||
|
||||
return keys.DID{}, fmt.Errorf("DID resolver not available in DEX module")
|
||||
}
|
||||
|
||||
// Gasless transaction support
|
||||
|
||||
// SupportsGaslessTransaction checks if a UCAN token supports gasless transactions
|
||||
func (pv *PermissionValidator) SupportsGaslessTransaction(
|
||||
ctx context.Context,
|
||||
tokenString string,
|
||||
poolID string,
|
||||
operation types.DEXOperation,
|
||||
) (bool, uint64, error) {
|
||||
// Parse and verify the token
|
||||
token, err := pv.verifier.VerifyToken(ctx, tokenString)
|
||||
if err != nil {
|
||||
return false, 0, fmt.Errorf("token verification failed: %w", err)
|
||||
}
|
||||
|
||||
mapper := types.NewUCANCapabilityMapper()
|
||||
resourceURI := mapper.CreatePoolResourceURI(poolID)
|
||||
|
||||
// Check each attenuation for gasless support
|
||||
for _, att := range token.Attenuations {
|
||||
if att.Resource.GetURI() == resourceURI {
|
||||
// Check if capability supports gasless transactions
|
||||
if gaslessCapability, ok := att.Capability.(*ucan.GaslessCapability); ok {
|
||||
if gaslessCapability.SupportsGasless() {
|
||||
// Verify the capability grants the required operation
|
||||
capabilities, err := pv.permissions.GetRequiredUCANCapabilities(operation)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if gaslessCapability.Grants(capabilities) {
|
||||
return true, gaslessCapability.GetGasLimit(), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false, 0, nil
|
||||
}
|
||||
|
||||
// ValidateRateLimit checks if a UCAN token has rate limiting and if it's within limits
|
||||
func (pv *PermissionValidator) ValidateRateLimit(
|
||||
ctx context.Context,
|
||||
tokenString string,
|
||||
poolID string,
|
||||
) (bool, uint64, uint64, error) {
|
||||
// Parse and verify the token
|
||||
token, err := pv.verifier.VerifyToken(ctx, tokenString)
|
||||
if err != nil {
|
||||
return false, 0, 0, fmt.Errorf("token verification failed: %w", err)
|
||||
}
|
||||
|
||||
mapper := types.NewUCANCapabilityMapper()
|
||||
resourceURI := mapper.CreatePoolResourceURI(poolID)
|
||||
|
||||
// Check each attenuation for rate limiting
|
||||
for _, att := range token.Attenuations {
|
||||
if att.Resource.GetURI() == resourceURI {
|
||||
// Check if this is a gasless capability with limits
|
||||
if gaslessCapability, ok := att.Capability.(*ucan.GaslessCapability); ok {
|
||||
if gaslessCapability.AllowGasless && gaslessCapability.GasLimit > 0 {
|
||||
// Use gas limit as a proxy for rate limiting
|
||||
return true, gaslessCapability.GasLimit, 60, nil // 60 second window
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false, 0, 0, nil
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
// Package keeper implements the dex module keeper
|
||||
package keeper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"cosmossdk.io/math"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// Portfolio represents a user's portfolio across chains
|
||||
type Portfolio struct {
|
||||
DID string
|
||||
Connections []string
|
||||
Balances map[string]sdk.Coins // connectionID -> balances
|
||||
Positions map[string]*Position // positionID -> position
|
||||
TotalValue math.LegacyDec
|
||||
UpdatedAt int64
|
||||
}
|
||||
|
||||
// Position represents a liquidity or staking position
|
||||
type Position struct {
|
||||
PositionID string
|
||||
ConnectionID string
|
||||
PoolID uint64
|
||||
Type PositionType
|
||||
Shares math.Int
|
||||
Value sdk.Coins
|
||||
APR math.LegacyDec
|
||||
CreatedAt int64
|
||||
}
|
||||
|
||||
// PositionType represents the type of position
|
||||
type PositionType int
|
||||
|
||||
const (
|
||||
PositionTypeLiquidity PositionType = iota
|
||||
PositionTypeStaking
|
||||
PositionTypeLending
|
||||
PositionTypeBorrowing
|
||||
)
|
||||
|
||||
// GetPortfolio retrieves the complete portfolio for a DID
|
||||
func (k Keeper) GetPortfolio(
|
||||
ctx sdk.Context,
|
||||
did string,
|
||||
) (*Portfolio, error) {
|
||||
// Get all DEX accounts for this DID
|
||||
accounts, err := k.GetDEXAccountsByDID(ctx, did)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get DEX accounts: %w", err)
|
||||
}
|
||||
|
||||
portfolio := &Portfolio{
|
||||
DID: did,
|
||||
Connections: make([]string, 0),
|
||||
Balances: make(map[string]sdk.Coins),
|
||||
Positions: make(map[string]*Position),
|
||||
TotalValue: math.LegacyZeroDec(),
|
||||
UpdatedAt: ctx.BlockTime().Unix(),
|
||||
}
|
||||
|
||||
// Collect connections
|
||||
for _, account := range accounts {
|
||||
portfolio.Connections = append(portfolio.Connections, account.ConnectionId)
|
||||
|
||||
// Get balances for each connection
|
||||
balances, err := k.GetRemoteBalances(ctx, did, account.ConnectionId)
|
||||
if err == nil {
|
||||
portfolio.Balances[account.ConnectionId] = balances
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate total value (simplified - would need price feeds)
|
||||
portfolio.TotalValue = k.CalculatePortfolioValue(ctx, portfolio.Balances)
|
||||
|
||||
return portfolio, nil
|
||||
}
|
||||
|
||||
// GetRemoteBalances queries balances on a remote chain
|
||||
func (k Keeper) GetRemoteBalances(
|
||||
ctx sdk.Context,
|
||||
did string,
|
||||
connectionID string,
|
||||
) (sdk.Coins, error) {
|
||||
// This would query the remote chain for balances
|
||||
// For now, return placeholder balances
|
||||
return sdk.NewCoins(
|
||||
sdk.NewCoin("uatom", math.NewInt(1000000)),
|
||||
sdk.NewCoin("uosmo", math.NewInt(2000000)),
|
||||
), nil
|
||||
}
|
||||
|
||||
// GetPositions retrieves all positions for a DID
|
||||
func (k Keeper) GetPositions(
|
||||
ctx sdk.Context,
|
||||
did string,
|
||||
connectionID string,
|
||||
) ([]*Position, error) {
|
||||
// This would query positions from remote chain
|
||||
// For now, return empty list
|
||||
return []*Position{}, nil
|
||||
}
|
||||
|
||||
// CalculatePortfolioValue calculates the total portfolio value
|
||||
func (k Keeper) CalculatePortfolioValue(
|
||||
ctx sdk.Context,
|
||||
balances map[string]sdk.Coins,
|
||||
) math.LegacyDec {
|
||||
// This would use price feeds to calculate USD value
|
||||
// For now, return a simple sum of amounts
|
||||
totalValue := math.LegacyZeroDec()
|
||||
|
||||
for _, coins := range balances {
|
||||
for _, coin := range coins {
|
||||
// Simplified: assume 1:1 USD value
|
||||
totalValue = totalValue.Add(math.LegacyNewDecFromInt(coin.Amount))
|
||||
}
|
||||
}
|
||||
|
||||
return totalValue
|
||||
}
|
||||
|
||||
// GetPortfolioHistory retrieves historical portfolio data
|
||||
func (k Keeper) GetPortfolioHistory(
|
||||
ctx sdk.Context,
|
||||
did string,
|
||||
startTime int64,
|
||||
endTime int64,
|
||||
) ([]*PortfolioSnapshot, error) {
|
||||
// This would retrieve historical snapshots from state
|
||||
// For now, return empty list
|
||||
return []*PortfolioSnapshot{}, nil
|
||||
}
|
||||
|
||||
// PortfolioSnapshot represents a point-in-time portfolio state
|
||||
type PortfolioSnapshot struct {
|
||||
Timestamp int64
|
||||
TotalValue math.LegacyDec
|
||||
Balances map[string]sdk.Coins
|
||||
Positions int
|
||||
}
|
||||
|
||||
// UpdatePortfolioSnapshot creates a new portfolio snapshot
|
||||
func (k Keeper) UpdatePortfolioSnapshot(
|
||||
ctx sdk.Context,
|
||||
did string,
|
||||
) error {
|
||||
portfolio, err := k.GetPortfolio(ctx, did)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get portfolio: %w", err)
|
||||
}
|
||||
|
||||
snapshot := &PortfolioSnapshot{
|
||||
Timestamp: ctx.BlockTime().Unix(),
|
||||
TotalValue: portfolio.TotalValue,
|
||||
Balances: portfolio.Balances,
|
||||
Positions: len(portfolio.Positions),
|
||||
}
|
||||
|
||||
// Store snapshot in state or DWN
|
||||
// Implementation would depend on storage strategy
|
||||
_ = snapshot
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetPortfolioPerformance calculates portfolio performance metrics
|
||||
func (k Keeper) GetPortfolioPerformance(
|
||||
ctx sdk.Context,
|
||||
did string,
|
||||
period int64, // Period in seconds
|
||||
) (*PerformanceMetrics, error) {
|
||||
// This would calculate performance based on historical data
|
||||
// For now, return placeholder metrics
|
||||
return &PerformanceMetrics{
|
||||
TotalReturn: math.LegacyNewDec(10), // 10% return
|
||||
TotalReturnPct: math.LegacyNewDecWithPrec(10, 2), // 10%
|
||||
DailyReturn: math.LegacyNewDec(1), // 1% daily
|
||||
APY: math.LegacyNewDecWithPrec(365, 2), // 365% APY (simplified)
|
||||
Volatility: math.LegacyNewDecWithPrec(15, 2), // 15% volatility
|
||||
SharpeRatio: math.LegacyNewDecWithPrec(2, 1), // 2.0 Sharpe
|
||||
MaxDrawdown: math.LegacyNewDecWithPrec(5, 2), // 5% max drawdown
|
||||
}, nil
|
||||
}
|
||||
|
||||
// PerformanceMetrics represents portfolio performance metrics
|
||||
type PerformanceMetrics struct {
|
||||
TotalReturn math.LegacyDec
|
||||
TotalReturnPct math.LegacyDec
|
||||
DailyReturn math.LegacyDec
|
||||
APY math.LegacyDec
|
||||
Volatility math.LegacyDec
|
||||
SharpeRatio math.LegacyDec
|
||||
MaxDrawdown math.LegacyDec
|
||||
}
|
||||
|
||||
// GetTopPerformers returns the top performing assets in portfolio
|
||||
func (k Keeper) GetTopPerformers(
|
||||
ctx sdk.Context,
|
||||
did string,
|
||||
limit int,
|
||||
) ([]*AssetPerformance, error) {
|
||||
// This would analyze asset performance
|
||||
// For now, return empty list
|
||||
return []*AssetPerformance{}, nil
|
||||
}
|
||||
|
||||
// AssetPerformance represents performance of a single asset
|
||||
type AssetPerformance struct {
|
||||
Asset string
|
||||
Connection string
|
||||
Return math.LegacyDec
|
||||
ReturnPct math.LegacyDec
|
||||
Volume math.Int
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
package keeper
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
"github.com/sonr-io/sonr/x/dex/types"
|
||||
)
|
||||
|
||||
var _ types.QueryServer = queryServer{}
|
||||
|
||||
type queryServer struct {
|
||||
Keeper
|
||||
}
|
||||
|
||||
// NewQueryServerImpl returns an implementation of the module QueryServer.
|
||||
func NewQueryServerImpl(k Keeper) types.QueryServer {
|
||||
return queryServer{Keeper: k}
|
||||
}
|
||||
|
||||
// Params queries the module parameters.
|
||||
func (qs queryServer) Params(ctx context.Context, req *types.QueryParamsRequest) (*types.QueryParamsResponse, error) {
|
||||
if req == nil {
|
||||
return nil, status.Error(codes.InvalidArgument, "invalid request")
|
||||
}
|
||||
|
||||
sdkCtx := sdk.UnwrapSDKContext(ctx)
|
||||
params, err := qs.Keeper.Params.Get(sdkCtx)
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.Internal, err.Error())
|
||||
}
|
||||
|
||||
return &types.QueryParamsResponse{Params: params}, nil
|
||||
}
|
||||
|
||||
// Account queries a specific DEX account.
|
||||
func (qs queryServer) Account(ctx context.Context, req *types.QueryAccountRequest) (*types.QueryAccountResponse, error) {
|
||||
if req == nil {
|
||||
return nil, status.Error(codes.InvalidArgument, "invalid request")
|
||||
}
|
||||
|
||||
if req.Did == "" || req.ConnectionId == "" {
|
||||
return nil, status.Error(codes.InvalidArgument, "did and connection_id are required")
|
||||
}
|
||||
|
||||
sdkCtx := sdk.UnwrapSDKContext(ctx)
|
||||
account, err := qs.Keeper.GetDEXAccount(sdkCtx, req.Did, req.ConnectionId)
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.NotFound, err.Error())
|
||||
}
|
||||
|
||||
return &types.QueryAccountResponse{Account: account}, nil
|
||||
}
|
||||
|
||||
// Accounts queries all DEX accounts for a specific DID.
|
||||
func (qs queryServer) Accounts(ctx context.Context, req *types.QueryAccountsRequest) (*types.QueryAccountsResponse, error) {
|
||||
if req == nil {
|
||||
return nil, status.Error(codes.InvalidArgument, "invalid request")
|
||||
}
|
||||
|
||||
if req.Did == "" {
|
||||
return nil, status.Error(codes.InvalidArgument, "did is required")
|
||||
}
|
||||
|
||||
sdkCtx := sdk.UnwrapSDKContext(ctx)
|
||||
accounts, err := qs.Keeper.GetDEXAccountsByDID(sdkCtx, req.Did)
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.Internal, err.Error())
|
||||
}
|
||||
|
||||
// Convert to pointer slice for response
|
||||
accountPtrs := make([]*types.InterchainDEXAccount, len(accounts))
|
||||
for i := range accounts {
|
||||
accountPtrs[i] = &accounts[i]
|
||||
}
|
||||
|
||||
return &types.QueryAccountsResponse{Accounts: accountPtrs}, nil
|
||||
}
|
||||
|
||||
// TODO: Balance - Implement cross-chain balance query via IBC
|
||||
// This method should query token balances on remote chains through IBC queries
|
||||
// Required implementation steps:
|
||||
// 1. Validate request parameters (DID, connection ID, denoms)
|
||||
// 2. Retrieve the ICA account address for this DID and connection
|
||||
// 3. Construct IBC query packet for bank balance on remote chain
|
||||
// 4. Send IBC query through the appropriate channel
|
||||
// 5. Parse the response and convert remote denoms to local representation
|
||||
// 6. Cache balance data temporarily for performance optimization
|
||||
// Returns: List of coin balances on the remote chain
|
||||
// Balance queries remote chain balance.
|
||||
func (qs queryServer) Balance(ctx context.Context, req *types.QueryBalanceRequest) (*types.QueryBalanceResponse, error) {
|
||||
if req == nil {
|
||||
return nil, status.Error(codes.InvalidArgument, "invalid request")
|
||||
}
|
||||
|
||||
// TODO: Implement balance query via ICA
|
||||
// This would require querying the remote chain through IBC
|
||||
return &types.QueryBalanceResponse{
|
||||
Balances: sdk.NewCoins(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// TODO: Pool - Implement cross-chain liquidity pool query via IBC
|
||||
// This method should query pool information from remote DEX protocols
|
||||
// Required implementation steps:
|
||||
// 1. Validate request parameters (pool ID, connection ID)
|
||||
// 2. Construct IBC query packet for pool state on remote DEX
|
||||
// 3. Send IBC query through the appropriate channel
|
||||
// 4. Parse pool data including reserves, total shares, and fee parameters
|
||||
// 5. Calculate derived metrics (price, APY, volume) if available
|
||||
// 6. Cache pool data with appropriate TTL for performance
|
||||
// Returns: Pool reserves, LP token supply, fee rate, and current price
|
||||
// Pool queries pool information.
|
||||
func (qs queryServer) Pool(ctx context.Context, req *types.QueryPoolRequest) (*types.QueryPoolResponse, error) {
|
||||
if req == nil {
|
||||
return nil, status.Error(codes.InvalidArgument, "invalid request")
|
||||
}
|
||||
|
||||
// TODO: Implement pool query via ICA
|
||||
// This would require querying the remote chain through IBC
|
||||
return &types.QueryPoolResponse{}, nil
|
||||
}
|
||||
|
||||
// TODO: Orders - Implement order book query for user's limit orders
|
||||
// This method should retrieve all orders for a specific DID across connections
|
||||
// Required implementation steps:
|
||||
// 1. Validate request parameters (DID, optional status filter)
|
||||
// 2. Query local state for stored order records by DID
|
||||
// 3. Filter orders by status (open, filled, cancelled) if specified
|
||||
// 4. For open orders, optionally query remote chain for current status
|
||||
// 5. Sort orders by creation time or specified sort parameter
|
||||
// 6. Apply pagination if limits are provided
|
||||
// 7. Include order fills and partial fill information
|
||||
// Returns: List of orders with status, amounts, prices, and timestamps
|
||||
// Orders queries orders for a DID.
|
||||
func (qs queryServer) Orders(ctx context.Context, req *types.QueryOrdersRequest) (*types.QueryOrdersResponse, error) {
|
||||
if req == nil {
|
||||
return nil, status.Error(codes.InvalidArgument, "invalid request")
|
||||
}
|
||||
|
||||
// TODO: Implement orders query
|
||||
// This would require storing order information in state or DWN
|
||||
return &types.QueryOrdersResponse{
|
||||
Orders: []*types.Order{}, // Empty for now
|
||||
}, nil
|
||||
}
|
||||
|
||||
// TODO: History - Implement transaction history query from DWN storage
|
||||
// This method should retrieve complete transaction history for a DID
|
||||
// Required implementation steps:
|
||||
// 1. Validate request parameters (DID, time range, transaction type filter)
|
||||
// 2. Query DWN for stored transaction records using DID as key
|
||||
// 3. Filter transactions by type (swap, liquidity, order) if specified
|
||||
// 4. Apply time range filter for date-based queries
|
||||
// 5. Calculate profit/loss metrics for each transaction
|
||||
// 6. Include gas costs and fees in transaction details
|
||||
// 7. Sort by timestamp (newest first by default)
|
||||
// 8. Apply pagination with cursor-based navigation
|
||||
// Returns: List of transactions with full details and pagination info
|
||||
// History queries transaction history.
|
||||
func (qs queryServer) History(ctx context.Context, req *types.QueryHistoryRequest) (*types.QueryHistoryResponse, error) {
|
||||
if req == nil {
|
||||
return nil, status.Error(codes.InvalidArgument, "invalid request")
|
||||
}
|
||||
|
||||
// TODO: Implement history query
|
||||
// This would require storing transaction history in state or DWN
|
||||
return &types.QueryHistoryResponse{
|
||||
Transactions: []*types.Transaction{}, // Empty for now
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
package keeper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"cosmossdk.io/math"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
|
||||
|
||||
"github.com/sonr-io/sonr/x/dex/types"
|
||||
)
|
||||
|
||||
// ExecuteSwap handles swap execution through ICA
|
||||
func (k Keeper) ExecuteSwap(
|
||||
ctx sdk.Context,
|
||||
did string,
|
||||
connectionID string,
|
||||
tokenIn sdk.Coin,
|
||||
tokenOutDenom string,
|
||||
minAmountOut math.Int,
|
||||
poolID uint64,
|
||||
) (uint64, error) {
|
||||
// Get the DEX account
|
||||
account, err := k.GetDEXAccount(ctx, did, connectionID)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("DEX account not found: %w", err)
|
||||
}
|
||||
|
||||
// Verify account is active
|
||||
if account.Status != types.ACCOUNT_STATUS_ACTIVE {
|
||||
return 0, fmt.Errorf("DEX account is not active")
|
||||
}
|
||||
|
||||
// Create swap message for remote chain
|
||||
// This example uses a generic bank send as placeholder
|
||||
// Actual implementation would use chain-specific swap messages
|
||||
swapMsg := &banktypes.MsgSend{
|
||||
FromAddress: account.AccountAddress,
|
||||
ToAddress: account.AccountAddress, // Swap to self as example
|
||||
Amount: sdk.NewCoins(tokenIn),
|
||||
}
|
||||
|
||||
// Send the swap transaction via ICA
|
||||
sequence, err := k.SendDEXTransaction(
|
||||
ctx,
|
||||
did,
|
||||
connectionID,
|
||||
[]sdk.Msg{swapMsg},
|
||||
fmt.Sprintf("swap_%s_for_%s", tokenIn.Denom, tokenOutDenom),
|
||||
30*time.Second,
|
||||
)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to send swap transaction: %w", err)
|
||||
}
|
||||
|
||||
// Emit swap event
|
||||
ctx.EventManager().EmitEvent(
|
||||
sdk.NewEvent(
|
||||
types.EventTypeSwapExecuted,
|
||||
sdk.NewAttribute("did", did),
|
||||
sdk.NewAttribute("connection", connectionID),
|
||||
sdk.NewAttribute("token_in", tokenIn.String()),
|
||||
sdk.NewAttribute("token_out_denom", tokenOutDenom),
|
||||
sdk.NewAttribute("sequence", fmt.Sprintf("%d", sequence)),
|
||||
),
|
||||
)
|
||||
|
||||
return sequence, nil
|
||||
}
|
||||
|
||||
// BuildOsmosisSwapMsg builds an Osmosis-specific swap message
|
||||
func (k Keeper) BuildOsmosisSwapMsg(
|
||||
senderAddress string,
|
||||
poolID uint64,
|
||||
tokenIn sdk.Coin,
|
||||
tokenOutDenom string,
|
||||
minAmountOut math.Int,
|
||||
) sdk.Msg {
|
||||
// This would build an actual Osmosis swap message
|
||||
// For now, return a placeholder bank send
|
||||
return &banktypes.MsgSend{
|
||||
FromAddress: senderAddress,
|
||||
ToAddress: senderAddress,
|
||||
Amount: sdk.NewCoins(tokenIn),
|
||||
}
|
||||
}
|
||||
|
||||
// EstimateSwapOutput estimates the output of a swap
|
||||
func (k Keeper) EstimateSwapOutput(
|
||||
ctx sdk.Context,
|
||||
connectionID string,
|
||||
poolID uint64,
|
||||
tokenIn sdk.Coin,
|
||||
tokenOutDenom string,
|
||||
) (math.Int, error) {
|
||||
// This would query the remote chain for swap estimation
|
||||
// For now, return a placeholder value
|
||||
return tokenIn.Amount.MulRaw(95).QuoRaw(100), nil // 95% of input as example
|
||||
}
|
||||
|
||||
// ValidateSwapParameters validates swap parameters
|
||||
func (k Keeper) ValidateSwapParameters(
|
||||
tokenIn sdk.Coin,
|
||||
tokenOutDenom string,
|
||||
minAmountOut math.Int,
|
||||
) error {
|
||||
if tokenIn.IsZero() {
|
||||
return fmt.Errorf("token in amount cannot be zero")
|
||||
}
|
||||
|
||||
if tokenOutDenom == "" {
|
||||
return fmt.Errorf("token out denomination cannot be empty")
|
||||
}
|
||||
|
||||
if tokenIn.Denom == tokenOutDenom {
|
||||
return fmt.Errorf("cannot swap same token")
|
||||
}
|
||||
|
||||
if minAmountOut.IsNegative() {
|
||||
return fmt.Errorf("minimum amount out cannot be negative")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
// Package keeper implements UCAN integration for the DEX module
|
||||
package keeper
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
"github.com/sonr-io/sonr/x/dex/types"
|
||||
)
|
||||
|
||||
// ValidateUCANForDEXOperation validates UCAN token for a DEX operation
|
||||
func (k Keeper) ValidateUCANForDEXOperation(
|
||||
ctx sdk.Context,
|
||||
ucanToken string,
|
||||
did string,
|
||||
operation string,
|
||||
params map[string]any,
|
||||
) error {
|
||||
if ucanToken == "" {
|
||||
// No UCAN provided - check if operation requires it
|
||||
if k.requiresUCAN(operation) {
|
||||
return fmt.Errorf("UCAN token required for operation %s", operation)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate UCAN token structure and signature
|
||||
capability, err := k.parseUCANToken(ucanToken)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid UCAN token: %w", err)
|
||||
}
|
||||
|
||||
// Check expiration
|
||||
if ctx.BlockTime().After(capability.Expiration) {
|
||||
return fmt.Errorf("UCAN token expired")
|
||||
}
|
||||
|
||||
// Verify resource matches operation
|
||||
expectedResource := k.getResourceForOperation(operation)
|
||||
if !k.resourceMatches(capability.Resource, expectedResource) {
|
||||
return fmt.Errorf(
|
||||
"UCAN resource %s does not match operation %s",
|
||||
capability.Resource,
|
||||
operation,
|
||||
)
|
||||
}
|
||||
|
||||
// Verify ability
|
||||
if !k.hasAbility(capability.Ability, operation) {
|
||||
return fmt.Errorf(
|
||||
"UCAN ability %s insufficient for operation %s",
|
||||
capability.Ability,
|
||||
operation,
|
||||
)
|
||||
}
|
||||
|
||||
// Validate constraints
|
||||
if err := k.validateConstraints(capability.Constraints, params); err != nil {
|
||||
return fmt.Errorf("UCAN constraints not satisfied: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// requiresUCAN checks if an operation requires UCAN authorization
|
||||
func (k Keeper) requiresUCAN(operation string) bool {
|
||||
// Critical operations that always require UCAN
|
||||
criticalOps := []string{
|
||||
"large_swap", // Swaps above threshold
|
||||
"remove_liquidity", // Removing liquidity
|
||||
"cancel_all_orders", // Canceling all orders
|
||||
}
|
||||
|
||||
return slices.Contains(criticalOps, operation)
|
||||
}
|
||||
|
||||
// parseUCANToken parses and validates a UCAN token
|
||||
func (k Keeper) parseUCANToken(token string) (*types.UCANCapability, error) {
|
||||
// This is a simplified implementation
|
||||
// Real implementation would validate JWT signature and parse claims
|
||||
|
||||
// For now, parse as JSON for simplicity
|
||||
var capability types.UCANCapability
|
||||
if err := json.Unmarshal([]byte(token), &capability); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse UCAN token: %w", err)
|
||||
}
|
||||
|
||||
return &capability, nil
|
||||
}
|
||||
|
||||
// getResourceForOperation maps operations to UCAN resources
|
||||
func (k Keeper) getResourceForOperation(operation string) string {
|
||||
resourceMap := map[string]string{
|
||||
"swap": "dex:swap",
|
||||
"execute_swap": "dex:swap",
|
||||
"provide_liquidity": "dex:liquidity:provide",
|
||||
"remove_liquidity": "dex:liquidity:remove",
|
||||
"create_order": "dex:order:create",
|
||||
"cancel_order": "dex:order:cancel",
|
||||
"register_account": "dex:account:register",
|
||||
}
|
||||
|
||||
if resource, ok := resourceMap[operation]; ok {
|
||||
return resource
|
||||
}
|
||||
|
||||
return fmt.Sprintf("dex:%s", operation)
|
||||
}
|
||||
|
||||
// resourceMatches checks if UCAN resource matches required resource
|
||||
func (k Keeper) resourceMatches(ucanResource, requiredResource string) bool {
|
||||
// Exact match
|
||||
if ucanResource == requiredResource {
|
||||
return true
|
||||
}
|
||||
|
||||
// Wildcard match (e.g., "dex:*" matches any DEX operation)
|
||||
if strings.HasSuffix(ucanResource, ":*") {
|
||||
prefix := strings.TrimSuffix(ucanResource, "*")
|
||||
return strings.HasPrefix(requiredResource, prefix)
|
||||
}
|
||||
|
||||
// Hierarchical match (e.g., "dex:swap" matches "dex:swap:osmosis")
|
||||
return strings.HasPrefix(requiredResource, ucanResource+":")
|
||||
}
|
||||
|
||||
// hasAbility checks if UCAN ability is sufficient for operation
|
||||
func (k Keeper) hasAbility(ucanAbility, operation string) bool {
|
||||
// Map operations to required abilities
|
||||
requiredAbilities := map[string][]string{
|
||||
"swap": {"execute", "trade"},
|
||||
"provide_liquidity": {"execute", "provide"},
|
||||
"remove_liquidity": {"execute", "remove"},
|
||||
"create_order": {"execute", "create"},
|
||||
"cancel_order": {"execute", "cancel"},
|
||||
"read": {"read", "view"},
|
||||
}
|
||||
|
||||
required, ok := requiredAbilities[operation]
|
||||
if !ok {
|
||||
// Default to requiring "execute" ability
|
||||
required = []string{"execute"}
|
||||
}
|
||||
|
||||
// Check if UCAN ability matches any required ability
|
||||
for _, req := range required {
|
||||
if ucanAbility == req || ucanAbility == "*" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// validateConstraints validates UCAN constraints against operation parameters
|
||||
func (k Keeper) validateConstraints(constraints, params map[string]any) error {
|
||||
// Check amount constraints
|
||||
if maxAmount, ok := constraints["max_amount"]; ok {
|
||||
if amount, ok := params["amount"]; ok {
|
||||
if !k.isAmountWithinLimit(amount, maxAmount) {
|
||||
return fmt.Errorf("amount exceeds UCAN limit")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check pool constraints
|
||||
if allowedPools, ok := constraints["allowed_pools"]; ok {
|
||||
if poolID, ok := params["pool_id"]; ok {
|
||||
if !k.isPoolAllowed(poolID, allowedPools) {
|
||||
return fmt.Errorf("pool not allowed by UCAN")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check chain constraints
|
||||
if allowedChains, ok := constraints["allowed_chains"]; ok {
|
||||
if connectionID, ok := params["connection_id"]; ok {
|
||||
if !k.isChainAllowed(connectionID, allowedChains) {
|
||||
return fmt.Errorf("chain not allowed by UCAN")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// isAmountWithinLimit checks if amount is within UCAN limit
|
||||
func (k Keeper) isAmountWithinLimit(amount, maxAmount any) bool {
|
||||
// Convert and compare amounts
|
||||
// Simplified implementation - real one would handle different types
|
||||
return true
|
||||
}
|
||||
|
||||
// isPoolAllowed checks if pool is in allowed list
|
||||
func (k Keeper) isPoolAllowed(poolID, allowedPools any) bool {
|
||||
// Check if pool is in allowed list
|
||||
// Simplified implementation
|
||||
return true
|
||||
}
|
||||
|
||||
// isChainAllowed checks if chain is in allowed list
|
||||
func (k Keeper) isChainAllowed(connectionID, allowedChains any) bool {
|
||||
// Check if chain connection is allowed
|
||||
// Simplified implementation
|
||||
return true
|
||||
}
|
||||
Executable
+150
@@ -0,0 +1,150 @@
|
||||
// Package dex defines the swap module.
|
||||
package dex
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/grpc-ecosystem/grpc-gateway/runtime"
|
||||
cli "github.com/sonr-io/sonr/x/dex/client/cli"
|
||||
"github.com/sonr-io/sonr/x/dex/keeper"
|
||||
"github.com/sonr-io/sonr/x/dex/types"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/types/module"
|
||||
simtypes "github.com/cosmos/cosmos-sdk/types/simulation"
|
||||
|
||||
abci "github.com/cometbft/cometbft/abci/types"
|
||||
)
|
||||
|
||||
var (
|
||||
_ module.AppModuleBasic = AppModuleBasic{}
|
||||
_ module.AppModule = AppModule{}
|
||||
_ module.AppModuleSimulation = AppModule{}
|
||||
)
|
||||
|
||||
// AppModuleBasic is the module AppModuleBasic.
|
||||
type AppModuleBasic struct{}
|
||||
|
||||
// Name implements AppModuleBasic interface.
|
||||
func (AppModuleBasic) Name() string {
|
||||
return types.ModuleName
|
||||
}
|
||||
|
||||
// RegisterLegacyAminoCodec implements AppModuleBasic interface.
|
||||
func (AppModuleBasic) RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) {
|
||||
types.RegisterLegacyAminoCodec(cdc)
|
||||
}
|
||||
|
||||
// RegisterInterfaces registers module concrete types into protobuf Any.
|
||||
func (AppModuleBasic) RegisterInterfaces(registry codectypes.InterfaceRegistry) {
|
||||
types.RegisterInterfaces(registry)
|
||||
}
|
||||
|
||||
// DefaultGenesis returns default genesis state as raw bytes for the swap module.
|
||||
func (AppModuleBasic) DefaultGenesis(cdc codec.JSONCodec) json.RawMessage {
|
||||
return cdc.MustMarshalJSON(types.DefaultGenesisState())
|
||||
}
|
||||
|
||||
// ValidateGenesis performs genesis state validation for the swap module.
|
||||
func (AppModuleBasic) ValidateGenesis(
|
||||
cdc codec.JSONCodec,
|
||||
config client.TxEncodingConfig,
|
||||
bz json.RawMessage,
|
||||
) error {
|
||||
var genState types.GenesisState
|
||||
if err := cdc.UnmarshalJSON(bz, &genState); err != nil {
|
||||
return err
|
||||
}
|
||||
return genState.Validate()
|
||||
}
|
||||
|
||||
// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the swap module.
|
||||
func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) {
|
||||
if err := types.RegisterQueryHandlerClient(context.Background(), mux, types.NewQueryClient(clientCtx)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// GetTxCmd implements AppModuleBasic interface.
|
||||
func (AppModuleBasic) GetTxCmd() *cobra.Command {
|
||||
return cli.NewTxCmd()
|
||||
}
|
||||
|
||||
// GetQueryCmd implements AppModuleBasic interface.
|
||||
func (AppModuleBasic) GetQueryCmd() *cobra.Command {
|
||||
return cli.NewQueryCmd()
|
||||
}
|
||||
|
||||
// AppModule is the module AppModule.
|
||||
type AppModule struct {
|
||||
AppModuleBasic
|
||||
keeper keeper.Keeper
|
||||
}
|
||||
|
||||
// IsAppModule implements module.AppModule.
|
||||
func (AppModule) IsAppModule() {
|
||||
}
|
||||
|
||||
// IsOnePerModuleType implements module.AppModule.
|
||||
func (AppModule) IsOnePerModuleType() {
|
||||
}
|
||||
|
||||
// NewAppModule initializes a new AppModule for the module.
|
||||
func NewAppModule(keeper keeper.Keeper) *AppModule {
|
||||
return &AppModule{
|
||||
keeper: keeper,
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterInvariants implements the AppModule interface.
|
||||
func (am AppModule) RegisterInvariants(ir sdk.InvariantRegistry) {}
|
||||
|
||||
// RegisterServices registers module services.
|
||||
func (am AppModule) RegisterServices(cfg module.Configurator) {
|
||||
types.RegisterMsgServer(cfg.MsgServer(), keeper.NewMsgServerImpl(am.keeper))
|
||||
types.RegisterQueryServer(cfg.QueryServer(), keeper.NewQueryServerImpl(am.keeper))
|
||||
}
|
||||
|
||||
// InitGenesis performs genesis initialization for the ibc-router module. It returns
|
||||
// no validator updates.
|
||||
func (am AppModule) InitGenesis(
|
||||
ctx sdk.Context,
|
||||
cdc codec.JSONCodec,
|
||||
data json.RawMessage,
|
||||
) []abci.ValidatorUpdate {
|
||||
var genesisState types.GenesisState
|
||||
cdc.MustUnmarshalJSON(data, &genesisState)
|
||||
am.keeper.InitGenesis(ctx, genesisState)
|
||||
|
||||
return []abci.ValidatorUpdate{}
|
||||
}
|
||||
|
||||
// ExportGenesis returns the exported genesis state as raw bytes for the swap module.
|
||||
func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.RawMessage {
|
||||
genState := am.keeper.ExportGenesis(ctx)
|
||||
return cdc.MustMarshalJSON(genState)
|
||||
}
|
||||
|
||||
// ConsensusVersion returns the consensus state breaking version for the swap module.
|
||||
func (am AppModule) ConsensusVersion() uint64 { return 1 }
|
||||
|
||||
// GenerateGenesisState implements the AppModuleSimulation interface.
|
||||
func (am AppModule) GenerateGenesisState(simState *module.SimulationState) {}
|
||||
|
||||
// ProposalContents implements the AppModuleSimulation interface.
|
||||
func (am AppModule) ProposalContents(_ module.SimulationState) []simtypes.WeightedProposalContent {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterStoreDecoder implements the AppModuleSimulation interface.
|
||||
func (am AppModule) RegisterStoreDecoder(sdr simtypes.StoreDecoderRegistry) {}
|
||||
|
||||
// WeightedOperations implements the AppModuleSimulation interface.
|
||||
func (am AppModule) WeightedOperations(_ module.SimulationState) []simtypes.WeightedOperation {
|
||||
return nil
|
||||
}
|
||||
Executable
+46
@@ -0,0 +1,46 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
"github.com/cosmos/cosmos-sdk/codec/types"
|
||||
cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/types/msgservice"
|
||||
)
|
||||
|
||||
var (
|
||||
amino = codec.NewLegacyAmino()
|
||||
AminoCdc = codec.NewAminoCodec(amino)
|
||||
)
|
||||
|
||||
func init() {
|
||||
RegisterLegacyAminoCodec(amino)
|
||||
cryptocodec.RegisterCrypto(amino)
|
||||
sdk.RegisterLegacyAminoCodec(amino)
|
||||
}
|
||||
|
||||
// RegisterLegacyAminoCodec registers concrete types on the LegacyAmino codec
|
||||
func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) {
|
||||
cdc.RegisterConcrete(&MsgRegisterDEXAccount{}, ModuleName+"/MsgRegisterDEXAccount", nil)
|
||||
cdc.RegisterConcrete(&MsgExecuteSwap{}, ModuleName+"/MsgExecuteSwap", nil)
|
||||
cdc.RegisterConcrete(&MsgProvideLiquidity{}, ModuleName+"/MsgProvideLiquidity", nil)
|
||||
cdc.RegisterConcrete(&MsgRemoveLiquidity{}, ModuleName+"/MsgRemoveLiquidity", nil)
|
||||
cdc.RegisterConcrete(&MsgCreateLimitOrder{}, ModuleName+"/MsgCreateLimitOrder", nil)
|
||||
cdc.RegisterConcrete(&MsgCancelOrder{}, ModuleName+"/MsgCancelOrder", nil)
|
||||
}
|
||||
|
||||
// RegisterInterfaces registers the x/dex interfaces types with a given
|
||||
// interface registry
|
||||
func RegisterInterfaces(registry types.InterfaceRegistry) {
|
||||
registry.RegisterImplementations(
|
||||
(*sdk.Msg)(nil),
|
||||
&MsgRegisterDEXAccount{},
|
||||
&MsgExecuteSwap{},
|
||||
&MsgProvideLiquidity{},
|
||||
&MsgRemoveLiquidity{},
|
||||
&MsgCreateLimitOrder{},
|
||||
&MsgCancelOrder{},
|
||||
)
|
||||
|
||||
msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc)
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package types
|
||||
|
||||
// DIDAccounts wraps a slice of account IDs for use with collections
|
||||
type DIDAccounts struct {
|
||||
Accounts []string `protobuf:"bytes,1,rep,name=accounts,proto3" json:"accounts,omitempty"`
|
||||
}
|
||||
|
||||
// ProtoMessage implements proto.Message
|
||||
func (DIDAccounts) ProtoMessage() {}
|
||||
|
||||
// Reset implements proto.Message
|
||||
func (m *DIDAccounts) Reset() {
|
||||
*m = DIDAccounts{}
|
||||
}
|
||||
|
||||
// String implements proto.Message
|
||||
func (m DIDAccounts) String() string {
|
||||
return m.Accounts[0] // Simple string representation
|
||||
}
|
||||
Executable
+17
@@ -0,0 +1,17 @@
|
||||
package types
|
||||
|
||||
import sdkerrors "cosmossdk.io/errors"
|
||||
|
||||
var (
|
||||
ErrInvalidGenesisState = sdkerrors.Register(ModuleName, 1, "invalid genesis state")
|
||||
ErrInvalidActivityType = sdkerrors.Register(ModuleName, 2, "invalid activity type")
|
||||
ErrInvalidDID = sdkerrors.Register(ModuleName, 3, "invalid DID")
|
||||
ErrInvalidConnectionID = sdkerrors.Register(ModuleName, 4, "invalid connection ID")
|
||||
ErrAccountNotFound = sdkerrors.Register(ModuleName, 5, "DEX account not found")
|
||||
ErrAccountNotActive = sdkerrors.Register(ModuleName, 6, "DEX account not active")
|
||||
ErrUnauthorized = sdkerrors.Register(ModuleName, 7, "unauthorized")
|
||||
ErrInvalidSwapParams = sdkerrors.Register(ModuleName, 8, "invalid swap parameters")
|
||||
ErrInvalidLiquidityParams = sdkerrors.Register(ModuleName, 9, "invalid liquidity parameters")
|
||||
ErrInvalidOrderParams = sdkerrors.Register(ModuleName, 10, "invalid order parameters")
|
||||
ErrICAOperationFailed = sdkerrors.Register(ModuleName, 11, "ICA operation failed")
|
||||
)
|
||||
File diff suppressed because it is too large
Load Diff
Executable
+101
@@ -0,0 +1,101 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
capabilitytypes "github.com/cosmos/ibc-go/modules/capability/types"
|
||||
icatypes "github.com/cosmos/ibc-go/v8/modules/apps/27-interchain-accounts/types"
|
||||
clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types"
|
||||
connectiontypes "github.com/cosmos/ibc-go/v8/modules/core/03-connection/types"
|
||||
channeltypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types"
|
||||
|
||||
didtypes "github.com/sonr-io/sonr/x/did/types"
|
||||
)
|
||||
|
||||
// AccountKeeper defines the expected account keeper
|
||||
type AccountKeeper interface {
|
||||
GetAccount(ctx context.Context, addr sdk.AccAddress) sdk.AccountI
|
||||
SetAccount(ctx context.Context, acc sdk.AccountI)
|
||||
GetModuleAddress(name string) sdk.AccAddress
|
||||
GetModuleAccount(ctx context.Context, name string) sdk.ModuleAccountI
|
||||
}
|
||||
|
||||
// BankKeeper defines the expected bank keeper
|
||||
type BankKeeper interface {
|
||||
SpendableCoins(ctx context.Context, addr sdk.AccAddress) sdk.Coins
|
||||
SendCoins(ctx context.Context, fromAddr, toAddr sdk.AccAddress, amt sdk.Coins) error
|
||||
}
|
||||
|
||||
// ICAControllerKeeper defines the expected ICA controller keeper
|
||||
type ICAControllerKeeper interface {
|
||||
// RegisterInterchainAccount registers an ICA account
|
||||
RegisterInterchainAccount(
|
||||
ctx sdk.Context,
|
||||
connectionID, owner, version string,
|
||||
) error
|
||||
|
||||
// SendTx sends a transaction to the ICA host
|
||||
SendTx(
|
||||
ctx sdk.Context,
|
||||
chanCap *capabilitytypes.Capability,
|
||||
connectionID, portID string,
|
||||
packetData icatypes.InterchainAccountPacketData,
|
||||
timeoutTimestamp uint64,
|
||||
) (uint64, error)
|
||||
|
||||
// GetActiveChannelID gets the active channel for an ICA
|
||||
GetActiveChannelID(ctx sdk.Context, connectionID, portID string) (string, bool)
|
||||
|
||||
// GetInterchainAccountAddress gets the ICA address on the host chain
|
||||
GetInterchainAccountAddress(ctx sdk.Context, connectionID, portID string) (string, bool)
|
||||
}
|
||||
|
||||
// ConnectionKeeper defines the expected connection keeper
|
||||
type ConnectionKeeper interface {
|
||||
GetConnection(ctx sdk.Context, connectionID string) (connectiontypes.ConnectionEnd, bool)
|
||||
}
|
||||
|
||||
// ChannelKeeper defines the expected channel keeper
|
||||
type ChannelKeeper interface {
|
||||
GetChannel(ctx sdk.Context, portID, channelID string) (channeltypes.Channel, bool)
|
||||
GetNextSequenceSend(ctx sdk.Context, portID, channelID string) (uint64, bool)
|
||||
SendPacket(
|
||||
ctx sdk.Context,
|
||||
chanCap *capabilitytypes.Capability,
|
||||
sourcePort string,
|
||||
sourceChannel string,
|
||||
timeoutHeight clienttypes.Height,
|
||||
timeoutTimestamp uint64,
|
||||
data []byte,
|
||||
) (uint64, error)
|
||||
}
|
||||
|
||||
// PortKeeper defines the expected port keeper
|
||||
type PortKeeper interface {
|
||||
BindPort(ctx sdk.Context, portID string) *capabilitytypes.Capability
|
||||
}
|
||||
|
||||
// ScopedKeeper defines the expected scoped keeper
|
||||
type ScopedKeeper interface {
|
||||
GetCapability(ctx sdk.Context, name string) (*capabilitytypes.Capability, bool)
|
||||
AuthenticateCapability(ctx sdk.Context, cap *capabilitytypes.Capability, name string) bool
|
||||
ClaimCapability(ctx sdk.Context, cap *capabilitytypes.Capability, name string) error
|
||||
}
|
||||
|
||||
// DIDKeeper defines the expected DID keeper
|
||||
type DIDKeeper interface {
|
||||
// GetDIDDocument retrieves a DID document
|
||||
GetDIDDocument(ctx context.Context, did string) (*didtypes.DIDDocument, error)
|
||||
}
|
||||
|
||||
// UCANKeeper defines the expected UCAN keeper (placeholder)
|
||||
type UCANKeeper interface {
|
||||
// ValidateCapability validates a UCAN token for a specific capability
|
||||
ValidateCapability(ctx sdk.Context, token string, resource string, ability string) error
|
||||
}
|
||||
|
||||
// DWNKeeper defines the expected DWN keeper
|
||||
type DWNKeeper interface {
|
||||
// Placeholder interface - will be implemented when DWN methods are available
|
||||
}
|
||||
Executable
+26
@@ -0,0 +1,26 @@
|
||||
package types
|
||||
|
||||
import host "github.com/cosmos/ibc-go/v8/modules/core/24-host"
|
||||
|
||||
// DefaultGenesisState returns the default module GenesisState.
|
||||
func DefaultGenesisState() *GenesisState {
|
||||
return &GenesisState{
|
||||
PortId: PortID,
|
||||
}
|
||||
}
|
||||
|
||||
// NewGenesisState initializes and returns a new GenesisState.
|
||||
func NewGenesisState() *GenesisState {
|
||||
return &GenesisState{
|
||||
PortId: PortID,
|
||||
}
|
||||
}
|
||||
|
||||
// Validate performs basic validation of the GenesisState.
|
||||
func (gs *GenesisState) Validate() error {
|
||||
if err := host.PortIdentifierValidator(gs.PortId); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,41 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// UCANCapability represents a UCAN capability for DEX operations
|
||||
type UCANCapability struct {
|
||||
// Resource being accessed (e.g., "dex:swap", "dex:liquidity")
|
||||
Resource string `json:"resource"`
|
||||
|
||||
// Ability being granted (e.g., "execute", "read", "write")
|
||||
Ability string `json:"ability"`
|
||||
|
||||
// Additional constraints (e.g., max amount, specific pools)
|
||||
Constraints map[string]any `json:"constraints,omitempty"`
|
||||
|
||||
// Expiration time
|
||||
Expiration time.Time `json:"expiration"`
|
||||
}
|
||||
|
||||
// DWNRecord represents a record stored in DWN
|
||||
type DWNRecord struct {
|
||||
// Record ID
|
||||
ID string `json:"id"`
|
||||
|
||||
// DID owner
|
||||
DID string `json:"did"`
|
||||
|
||||
// Record type
|
||||
Type string `json:"type"`
|
||||
|
||||
// Record data
|
||||
Data any `json:"data"`
|
||||
|
||||
// Timestamp
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
|
||||
// Metadata
|
||||
Metadata map[string]string `json:"metadata,omitempty"`
|
||||
}
|
||||
Executable
+34
@@ -0,0 +1,34 @@
|
||||
package types
|
||||
|
||||
const (
|
||||
// ModuleName defines the name of module.
|
||||
ModuleName = "dex"
|
||||
|
||||
// PortID defines the port ID that module module binds to.
|
||||
PortID = ModuleName
|
||||
|
||||
// Version defines the current version the IBC module supports
|
||||
Version = ModuleName + "-1"
|
||||
|
||||
// StoreKey is the store key string for the module.
|
||||
StoreKey = ModuleName
|
||||
|
||||
// RouterKey is the message route for the module.
|
||||
RouterKey = ModuleName
|
||||
|
||||
// QuerierRoute is the querier route for the module.
|
||||
QuerierRoute = ModuleName
|
||||
)
|
||||
|
||||
// Event types
|
||||
const (
|
||||
EventTypeICAPacketAcknowledged = "ica_packet_acknowledged"
|
||||
EventTypeICAPacketTimeout = "ica_packet_timeout"
|
||||
EventTypeDEXAccountRegistered = "dex_account_registered"
|
||||
EventTypeSwapExecuted = "swap_executed"
|
||||
EventTypeLiquidityProvided = "liquidity_provided"
|
||||
EventTypeLiquidityRemoved = "liquidity_removed"
|
||||
EventTypeOrderCreated = "order_created"
|
||||
EventTypeOrderCancelled = "order_cancelled"
|
||||
EventTypeDIDActivity = "did_activity"
|
||||
)
|
||||
Executable
+122
@@ -0,0 +1,122 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
errorsmod "cosmossdk.io/errors"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
)
|
||||
|
||||
var ModuleCdc = codec.NewProtoCodec(codectypes.NewInterfaceRegistry())
|
||||
|
||||
// ValidateBasic performs basic validation of MsgRegisterDEXAccount
|
||||
func (msg *MsgRegisterDEXAccount) ValidateBasic() error {
|
||||
if msg.Did == "" {
|
||||
return errorsmod.Wrap(sdkerrors.ErrInvalidAddress, "DID cannot be empty")
|
||||
}
|
||||
if msg.ConnectionId == "" {
|
||||
return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "connection ID cannot be empty")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateBasic performs basic validation of MsgExecuteSwap
|
||||
func (msg *MsgExecuteSwap) ValidateBasic() error {
|
||||
if msg.Did == "" {
|
||||
return errorsmod.Wrap(sdkerrors.ErrInvalidAddress, "DID cannot be empty")
|
||||
}
|
||||
if msg.ConnectionId == "" {
|
||||
return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "connection ID cannot be empty")
|
||||
}
|
||||
if msg.SourceDenom == "" || msg.TargetDenom == "" {
|
||||
return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "denoms cannot be empty")
|
||||
}
|
||||
if msg.Amount.IsNil() || !msg.Amount.IsPositive() {
|
||||
return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "amount must be positive")
|
||||
}
|
||||
if msg.MinAmountOut.IsNil() || !msg.MinAmountOut.IsPositive() {
|
||||
return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "min amount out must be positive")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateBasic performs basic validation of MsgProvideLiquidity
|
||||
func (msg *MsgProvideLiquidity) ValidateBasic() error {
|
||||
if msg.Did == "" {
|
||||
return errorsmod.Wrap(sdkerrors.ErrInvalidAddress, "DID cannot be empty")
|
||||
}
|
||||
if msg.ConnectionId == "" {
|
||||
return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "connection ID cannot be empty")
|
||||
}
|
||||
if msg.PoolId == "" {
|
||||
return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "pool ID cannot be empty")
|
||||
}
|
||||
if len(msg.Assets) == 0 {
|
||||
return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "assets cannot be empty")
|
||||
}
|
||||
for _, asset := range msg.Assets {
|
||||
if !asset.IsValid() || !asset.IsPositive() {
|
||||
return errorsmod.Wrap(
|
||||
sdkerrors.ErrInvalidRequest,
|
||||
fmt.Sprintf("invalid asset amount: %s", asset),
|
||||
)
|
||||
}
|
||||
}
|
||||
if msg.MinShares.IsNil() || !msg.MinShares.IsPositive() {
|
||||
return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "min shares must be positive")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateBasic performs basic validation of MsgRemoveLiquidity
|
||||
func (msg *MsgRemoveLiquidity) ValidateBasic() error {
|
||||
if msg.Did == "" {
|
||||
return errorsmod.Wrap(sdkerrors.ErrInvalidAddress, "DID cannot be empty")
|
||||
}
|
||||
if msg.ConnectionId == "" {
|
||||
return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "connection ID cannot be empty")
|
||||
}
|
||||
if msg.PoolId == "" {
|
||||
return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "pool ID cannot be empty")
|
||||
}
|
||||
if msg.Shares.IsNil() || !msg.Shares.IsPositive() {
|
||||
return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "shares must be positive")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateBasic performs basic validation of MsgCreateLimitOrder
|
||||
func (msg *MsgCreateLimitOrder) ValidateBasic() error {
|
||||
if msg.Did == "" {
|
||||
return errorsmod.Wrap(sdkerrors.ErrInvalidAddress, "DID cannot be empty")
|
||||
}
|
||||
if msg.ConnectionId == "" {
|
||||
return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "connection ID cannot be empty")
|
||||
}
|
||||
if msg.SellDenom == "" || msg.BuyDenom == "" {
|
||||
return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "denoms cannot be empty")
|
||||
}
|
||||
if msg.Amount.IsNil() || !msg.Amount.IsPositive() {
|
||||
return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "amount must be positive")
|
||||
}
|
||||
if msg.Price.IsNil() || !msg.Price.IsPositive() {
|
||||
return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "price must be positive")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateBasic performs basic validation of MsgCancelOrder
|
||||
func (msg *MsgCancelOrder) ValidateBasic() error {
|
||||
if msg.Did == "" {
|
||||
return errorsmod.Wrap(sdkerrors.ErrInvalidAddress, "DID cannot be empty")
|
||||
}
|
||||
if msg.ConnectionId == "" {
|
||||
return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "connection ID cannot be empty")
|
||||
}
|
||||
if msg.OrderId == "" {
|
||||
return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "order ID cannot be empty")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,919 @@
|
||||
// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT.
|
||||
// source: dex/v1/query.proto
|
||||
|
||||
/*
|
||||
Package types is a reverse proxy.
|
||||
|
||||
It translates gRPC into RESTful JSON APIs.
|
||||
*/
|
||||
package types
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/golang/protobuf/descriptor"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/grpc-ecosystem/grpc-gateway/runtime"
|
||||
"github.com/grpc-ecosystem/grpc-gateway/utilities"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/grpclog"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// Suppress "imported and not used" errors
|
||||
var _ codes.Code
|
||||
var _ io.Reader
|
||||
var _ status.Status
|
||||
var _ = runtime.String
|
||||
var _ = utilities.NewDoubleArray
|
||||
var _ = descriptor.ForMessage
|
||||
var _ = metadata.Join
|
||||
|
||||
func request_Query_Params_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq QueryParamsRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
msg, err := client.Params(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func local_request_Query_Params_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq QueryParamsRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
msg, err := server.Params(ctx, &protoReq)
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func request_Query_Account_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq QueryAccountRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
var (
|
||||
val string
|
||||
ok bool
|
||||
err error
|
||||
_ = err
|
||||
)
|
||||
|
||||
val, ok = pathParams["did"]
|
||||
if !ok {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "did")
|
||||
}
|
||||
|
||||
protoReq.Did, err = runtime.String(val)
|
||||
|
||||
if err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "did", err)
|
||||
}
|
||||
|
||||
val, ok = pathParams["connection_id"]
|
||||
if !ok {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "connection_id")
|
||||
}
|
||||
|
||||
protoReq.ConnectionId, err = runtime.String(val)
|
||||
|
||||
if err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "connection_id", err)
|
||||
}
|
||||
|
||||
msg, err := client.Account(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func local_request_Query_Account_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq QueryAccountRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
var (
|
||||
val string
|
||||
ok bool
|
||||
err error
|
||||
_ = err
|
||||
)
|
||||
|
||||
val, ok = pathParams["did"]
|
||||
if !ok {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "did")
|
||||
}
|
||||
|
||||
protoReq.Did, err = runtime.String(val)
|
||||
|
||||
if err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "did", err)
|
||||
}
|
||||
|
||||
val, ok = pathParams["connection_id"]
|
||||
if !ok {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "connection_id")
|
||||
}
|
||||
|
||||
protoReq.ConnectionId, err = runtime.String(val)
|
||||
|
||||
if err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "connection_id", err)
|
||||
}
|
||||
|
||||
msg, err := server.Account(ctx, &protoReq)
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
var (
|
||||
filter_Query_Accounts_0 = &utilities.DoubleArray{Encoding: map[string]int{"did": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}}
|
||||
)
|
||||
|
||||
func request_Query_Accounts_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq QueryAccountsRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
var (
|
||||
val string
|
||||
ok bool
|
||||
err error
|
||||
_ = err
|
||||
)
|
||||
|
||||
val, ok = pathParams["did"]
|
||||
if !ok {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "did")
|
||||
}
|
||||
|
||||
protoReq.Did, err = runtime.String(val)
|
||||
|
||||
if err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "did", err)
|
||||
}
|
||||
|
||||
if err := req.ParseForm(); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_Accounts_0); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := client.Accounts(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func local_request_Query_Accounts_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq QueryAccountsRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
var (
|
||||
val string
|
||||
ok bool
|
||||
err error
|
||||
_ = err
|
||||
)
|
||||
|
||||
val, ok = pathParams["did"]
|
||||
if !ok {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "did")
|
||||
}
|
||||
|
||||
protoReq.Did, err = runtime.String(val)
|
||||
|
||||
if err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "did", err)
|
||||
}
|
||||
|
||||
if err := req.ParseForm(); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_Accounts_0); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := server.Accounts(ctx, &protoReq)
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
var (
|
||||
filter_Query_Balance_0 = &utilities.DoubleArray{Encoding: map[string]int{"did": 0, "connection_id": 1}, Base: []int{1, 1, 2, 0, 0}, Check: []int{0, 1, 1, 2, 3}}
|
||||
)
|
||||
|
||||
func request_Query_Balance_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq QueryBalanceRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
var (
|
||||
val string
|
||||
ok bool
|
||||
err error
|
||||
_ = err
|
||||
)
|
||||
|
||||
val, ok = pathParams["did"]
|
||||
if !ok {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "did")
|
||||
}
|
||||
|
||||
protoReq.Did, err = runtime.String(val)
|
||||
|
||||
if err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "did", err)
|
||||
}
|
||||
|
||||
val, ok = pathParams["connection_id"]
|
||||
if !ok {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "connection_id")
|
||||
}
|
||||
|
||||
protoReq.ConnectionId, err = runtime.String(val)
|
||||
|
||||
if err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "connection_id", err)
|
||||
}
|
||||
|
||||
if err := req.ParseForm(); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_Balance_0); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := client.Balance(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func local_request_Query_Balance_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq QueryBalanceRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
var (
|
||||
val string
|
||||
ok bool
|
||||
err error
|
||||
_ = err
|
||||
)
|
||||
|
||||
val, ok = pathParams["did"]
|
||||
if !ok {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "did")
|
||||
}
|
||||
|
||||
protoReq.Did, err = runtime.String(val)
|
||||
|
||||
if err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "did", err)
|
||||
}
|
||||
|
||||
val, ok = pathParams["connection_id"]
|
||||
if !ok {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "connection_id")
|
||||
}
|
||||
|
||||
protoReq.ConnectionId, err = runtime.String(val)
|
||||
|
||||
if err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "connection_id", err)
|
||||
}
|
||||
|
||||
if err := req.ParseForm(); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_Balance_0); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := server.Balance(ctx, &protoReq)
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func request_Query_Pool_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq QueryPoolRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
var (
|
||||
val string
|
||||
ok bool
|
||||
err error
|
||||
_ = err
|
||||
)
|
||||
|
||||
val, ok = pathParams["connection_id"]
|
||||
if !ok {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "connection_id")
|
||||
}
|
||||
|
||||
protoReq.ConnectionId, err = runtime.String(val)
|
||||
|
||||
if err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "connection_id", err)
|
||||
}
|
||||
|
||||
val, ok = pathParams["pool_id"]
|
||||
if !ok {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "pool_id")
|
||||
}
|
||||
|
||||
protoReq.PoolId, err = runtime.String(val)
|
||||
|
||||
if err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "pool_id", err)
|
||||
}
|
||||
|
||||
msg, err := client.Pool(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func local_request_Query_Pool_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq QueryPoolRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
var (
|
||||
val string
|
||||
ok bool
|
||||
err error
|
||||
_ = err
|
||||
)
|
||||
|
||||
val, ok = pathParams["connection_id"]
|
||||
if !ok {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "connection_id")
|
||||
}
|
||||
|
||||
protoReq.ConnectionId, err = runtime.String(val)
|
||||
|
||||
if err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "connection_id", err)
|
||||
}
|
||||
|
||||
val, ok = pathParams["pool_id"]
|
||||
if !ok {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "pool_id")
|
||||
}
|
||||
|
||||
protoReq.PoolId, err = runtime.String(val)
|
||||
|
||||
if err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "pool_id", err)
|
||||
}
|
||||
|
||||
msg, err := server.Pool(ctx, &protoReq)
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
var (
|
||||
filter_Query_Orders_0 = &utilities.DoubleArray{Encoding: map[string]int{"did": 0, "connection_id": 1}, Base: []int{1, 1, 2, 0, 0}, Check: []int{0, 1, 1, 2, 3}}
|
||||
)
|
||||
|
||||
func request_Query_Orders_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq QueryOrdersRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
var (
|
||||
val string
|
||||
ok bool
|
||||
err error
|
||||
_ = err
|
||||
)
|
||||
|
||||
val, ok = pathParams["did"]
|
||||
if !ok {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "did")
|
||||
}
|
||||
|
||||
protoReq.Did, err = runtime.String(val)
|
||||
|
||||
if err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "did", err)
|
||||
}
|
||||
|
||||
val, ok = pathParams["connection_id"]
|
||||
if !ok {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "connection_id")
|
||||
}
|
||||
|
||||
protoReq.ConnectionId, err = runtime.String(val)
|
||||
|
||||
if err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "connection_id", err)
|
||||
}
|
||||
|
||||
if err := req.ParseForm(); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_Orders_0); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := client.Orders(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func local_request_Query_Orders_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq QueryOrdersRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
var (
|
||||
val string
|
||||
ok bool
|
||||
err error
|
||||
_ = err
|
||||
)
|
||||
|
||||
val, ok = pathParams["did"]
|
||||
if !ok {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "did")
|
||||
}
|
||||
|
||||
protoReq.Did, err = runtime.String(val)
|
||||
|
||||
if err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "did", err)
|
||||
}
|
||||
|
||||
val, ok = pathParams["connection_id"]
|
||||
if !ok {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "connection_id")
|
||||
}
|
||||
|
||||
protoReq.ConnectionId, err = runtime.String(val)
|
||||
|
||||
if err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "connection_id", err)
|
||||
}
|
||||
|
||||
if err := req.ParseForm(); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_Orders_0); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := server.Orders(ctx, &protoReq)
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
var (
|
||||
filter_Query_History_0 = &utilities.DoubleArray{Encoding: map[string]int{"did": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}}
|
||||
)
|
||||
|
||||
func request_Query_History_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq QueryHistoryRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
var (
|
||||
val string
|
||||
ok bool
|
||||
err error
|
||||
_ = err
|
||||
)
|
||||
|
||||
val, ok = pathParams["did"]
|
||||
if !ok {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "did")
|
||||
}
|
||||
|
||||
protoReq.Did, err = runtime.String(val)
|
||||
|
||||
if err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "did", err)
|
||||
}
|
||||
|
||||
if err := req.ParseForm(); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_History_0); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := client.History(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func local_request_Query_History_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq QueryHistoryRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
var (
|
||||
val string
|
||||
ok bool
|
||||
err error
|
||||
_ = err
|
||||
)
|
||||
|
||||
val, ok = pathParams["did"]
|
||||
if !ok {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "did")
|
||||
}
|
||||
|
||||
protoReq.Did, err = runtime.String(val)
|
||||
|
||||
if err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "did", err)
|
||||
}
|
||||
|
||||
if err := req.ParseForm(); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_History_0); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := server.History(ctx, &protoReq)
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
// RegisterQueryHandlerServer registers the http handlers for service Query to "mux".
|
||||
// UnaryRPC :call QueryServer directly.
|
||||
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
|
||||
// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterQueryHandlerFromEndpoint instead.
|
||||
func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, server QueryServer) error {
|
||||
|
||||
mux.Handle("GET", pattern_Query_Params_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
var stream runtime.ServerTransportStream
|
||||
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := local_request_Query_Params_0(rctx, inboundMarshaler, server, req, pathParams)
|
||||
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_Query_Params_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("GET", pattern_Query_Account_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
var stream runtime.ServerTransportStream
|
||||
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := local_request_Query_Account_0(rctx, inboundMarshaler, server, req, pathParams)
|
||||
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_Query_Account_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("GET", pattern_Query_Accounts_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
var stream runtime.ServerTransportStream
|
||||
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := local_request_Query_Accounts_0(rctx, inboundMarshaler, server, req, pathParams)
|
||||
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_Query_Accounts_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("GET", pattern_Query_Balance_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
var stream runtime.ServerTransportStream
|
||||
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := local_request_Query_Balance_0(rctx, inboundMarshaler, server, req, pathParams)
|
||||
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_Query_Balance_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("GET", pattern_Query_Pool_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
var stream runtime.ServerTransportStream
|
||||
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := local_request_Query_Pool_0(rctx, inboundMarshaler, server, req, pathParams)
|
||||
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_Query_Pool_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("GET", pattern_Query_Orders_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
var stream runtime.ServerTransportStream
|
||||
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := local_request_Query_Orders_0(rctx, inboundMarshaler, server, req, pathParams)
|
||||
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_Query_Orders_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("GET", pattern_Query_History_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
var stream runtime.ServerTransportStream
|
||||
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := local_request_Query_History_0(rctx, inboundMarshaler, server, req, pathParams)
|
||||
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_Query_History_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterQueryHandlerFromEndpoint is same as RegisterQueryHandler but
|
||||
// automatically dials to "endpoint" and closes the connection when "ctx" gets done.
|
||||
func RegisterQueryHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) {
|
||||
conn, err := grpc.Dial(endpoint, opts...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
if cerr := conn.Close(); cerr != nil {
|
||||
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
|
||||
}
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
if cerr := conn.Close(); cerr != nil {
|
||||
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
|
||||
}
|
||||
}()
|
||||
}()
|
||||
|
||||
return RegisterQueryHandler(ctx, mux, conn)
|
||||
}
|
||||
|
||||
// RegisterQueryHandler registers the http handlers for service Query to "mux".
|
||||
// The handlers forward requests to the grpc endpoint over "conn".
|
||||
func RegisterQueryHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error {
|
||||
return RegisterQueryHandlerClient(ctx, mux, NewQueryClient(conn))
|
||||
}
|
||||
|
||||
// RegisterQueryHandlerClient registers the http handlers for service Query
|
||||
// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "QueryClient".
|
||||
// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "QueryClient"
|
||||
// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in
|
||||
// "QueryClient" to call the correct interceptors.
|
||||
func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, client QueryClient) error {
|
||||
|
||||
mux.Handle("GET", pattern_Query_Params_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateContext(ctx, mux, req)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_Query_Params_0(rctx, inboundMarshaler, client, req, pathParams)
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_Query_Params_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("GET", pattern_Query_Account_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateContext(ctx, mux, req)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_Query_Account_0(rctx, inboundMarshaler, client, req, pathParams)
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_Query_Account_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("GET", pattern_Query_Accounts_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateContext(ctx, mux, req)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_Query_Accounts_0(rctx, inboundMarshaler, client, req, pathParams)
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_Query_Accounts_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("GET", pattern_Query_Balance_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateContext(ctx, mux, req)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_Query_Balance_0(rctx, inboundMarshaler, client, req, pathParams)
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_Query_Balance_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("GET", pattern_Query_Pool_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateContext(ctx, mux, req)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_Query_Pool_0(rctx, inboundMarshaler, client, req, pathParams)
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_Query_Pool_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("GET", pattern_Query_Orders_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateContext(ctx, mux, req)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_Query_Orders_0(rctx, inboundMarshaler, client, req, pathParams)
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_Query_Orders_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("GET", pattern_Query_History_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateContext(ctx, mux, req)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_Query_History_0(rctx, inboundMarshaler, client, req, pathParams)
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_Query_History_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
pattern_Query_Params_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"sonr", "dex", "v1", "params"}, "", runtime.AssumeColonVerbOpt(false)))
|
||||
|
||||
pattern_Query_Account_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 1, 0, 4, 1, 5, 5}, []string{"sonr", "dex", "v1", "account", "did", "connection_id"}, "", runtime.AssumeColonVerbOpt(false)))
|
||||
|
||||
pattern_Query_Accounts_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"sonr", "dex", "v1", "accounts", "did"}, "", runtime.AssumeColonVerbOpt(false)))
|
||||
|
||||
pattern_Query_Balance_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 1, 0, 4, 1, 5, 5}, []string{"sonr", "dex", "v1", "balance", "did", "connection_id"}, "", runtime.AssumeColonVerbOpt(false)))
|
||||
|
||||
pattern_Query_Pool_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 1, 0, 4, 1, 5, 5}, []string{"sonr", "dex", "v1", "pool", "connection_id", "pool_id"}, "", runtime.AssumeColonVerbOpt(false)))
|
||||
|
||||
pattern_Query_Orders_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 1, 0, 4, 1, 5, 5}, []string{"sonr", "dex", "v1", "orders", "did", "connection_id"}, "", runtime.AssumeColonVerbOpt(false)))
|
||||
|
||||
pattern_Query_History_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"sonr", "dex", "v1", "history", "did"}, "", runtime.AssumeColonVerbOpt(false)))
|
||||
)
|
||||
|
||||
var (
|
||||
forward_Query_Params_0 = runtime.ForwardResponseMessage
|
||||
|
||||
forward_Query_Account_0 = runtime.ForwardResponseMessage
|
||||
|
||||
forward_Query_Accounts_0 = runtime.ForwardResponseMessage
|
||||
|
||||
forward_Query_Balance_0 = runtime.ForwardResponseMessage
|
||||
|
||||
forward_Query_Pool_0 = runtime.ForwardResponseMessage
|
||||
|
||||
forward_Query_Orders_0 = runtime.ForwardResponseMessage
|
||||
|
||||
forward_Query_History_0 = runtime.ForwardResponseMessage
|
||||
)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,370 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/sonr-io/sonr/crypto/ucan"
|
||||
)
|
||||
|
||||
// UCAN Action Constants for DEX operations
|
||||
const (
|
||||
// Core Trading Actions
|
||||
UCANSwap = "swap" // Execute token swap
|
||||
UCANExecuteSwap = "execute-swap" // Execute a specific swap
|
||||
UCANLimitOrder = "limit-order" // Place limit order
|
||||
UCANMarketOrder = "market-order" // Place market order
|
||||
UCANCancelOrder = "cancel-order" // Cancel order
|
||||
UCANCancelAllOrders = "cancel-all-orders" // Cancel all orders
|
||||
|
||||
// Liquidity Actions
|
||||
UCANProvideLiquidity = "provide-liquidity" // Add liquidity to pool
|
||||
UCANRemoveLiquidity = "remove-liquidity" // Remove liquidity from pool
|
||||
UCANCreatePool = "create-pool" // Create new liquidity pool
|
||||
|
||||
// Portfolio Management Actions
|
||||
UCANRegisterAccount = "register-account" // Register trading account
|
||||
UCANUpdatePortfolio = "update-portfolio" // Update portfolio settings
|
||||
UCANWithdraw = "withdraw" // Withdraw funds
|
||||
UCANDeposit = "deposit" // Deposit funds
|
||||
|
||||
// Query Actions
|
||||
UCANQueryPool = "query-pool" // Query pool details
|
||||
UCANQueryOrders = "query-orders" // Query orders
|
||||
UCANQueryPortfolio = "query-portfolio" // Query portfolio
|
||||
|
||||
// Standard CRUD Actions (for compatibility)
|
||||
UCANCreate = "create" // Create resource
|
||||
UCANRead = "read" // Read resource
|
||||
UCANUpdate = "update" // Update resource
|
||||
UCANDelete = "delete" // Delete resource
|
||||
UCANAdmin = "admin" // Administrative actions
|
||||
UCANAll = "*" // Wildcard for all actions
|
||||
)
|
||||
|
||||
// DEXOperation represents the type of DEX operation being performed
|
||||
type DEXOperation string
|
||||
|
||||
const (
|
||||
DEXOpSwap DEXOperation = "swap"
|
||||
DEXOpExecuteSwap DEXOperation = "execute_swap"
|
||||
DEXOpLimitOrder DEXOperation = "limit_order"
|
||||
DEXOpMarketOrder DEXOperation = "market_order"
|
||||
DEXOpCancelOrder DEXOperation = "cancel_order"
|
||||
DEXOpCancelAllOrders DEXOperation = "cancel_all_orders"
|
||||
DEXOpProvideLiquidity DEXOperation = "provide_liquidity"
|
||||
DEXOpRemoveLiquidity DEXOperation = "remove_liquidity"
|
||||
DEXOpCreatePool DEXOperation = "create_pool"
|
||||
DEXOpRegisterAccount DEXOperation = "register_account"
|
||||
DEXOpUpdatePortfolio DEXOperation = "update_portfolio"
|
||||
DEXOpWithdraw DEXOperation = "withdraw"
|
||||
DEXOpDeposit DEXOperation = "deposit"
|
||||
DEXOpQueryPool DEXOperation = "query_pool"
|
||||
DEXOpQueryOrders DEXOperation = "query_orders"
|
||||
DEXOpQueryPortfolio DEXOperation = "query_portfolio"
|
||||
)
|
||||
|
||||
// String returns the string representation of the DEX operation
|
||||
func (op DEXOperation) String() string {
|
||||
return string(op)
|
||||
}
|
||||
|
||||
// UCANCapabilityMapper provides conversion between DEX operations and UCAN capabilities
|
||||
type UCANCapabilityMapper struct{}
|
||||
|
||||
// NewUCANCapabilityMapper creates a new capability mapper
|
||||
func NewUCANCapabilityMapper() *UCANCapabilityMapper {
|
||||
return &UCANCapabilityMapper{}
|
||||
}
|
||||
|
||||
// GetUCANCapabilitiesForOperation returns UCAN-specific capabilities for a DEX operation
|
||||
func (m *UCANCapabilityMapper) GetUCANCapabilitiesForOperation(operation DEXOperation) []string {
|
||||
switch operation {
|
||||
case DEXOpSwap:
|
||||
return []string{UCANSwap, UCANUpdate}
|
||||
case DEXOpExecuteSwap:
|
||||
return []string{UCANExecuteSwap, UCANUpdate}
|
||||
case DEXOpLimitOrder:
|
||||
return []string{UCANLimitOrder, UCANCreate}
|
||||
case DEXOpMarketOrder:
|
||||
return []string{UCANMarketOrder, UCANCreate}
|
||||
case DEXOpCancelOrder:
|
||||
return []string{UCANCancelOrder, UCANDelete}
|
||||
case DEXOpCancelAllOrders:
|
||||
return []string{UCANCancelAllOrders, UCANDelete, UCANAdmin}
|
||||
case DEXOpProvideLiquidity:
|
||||
return []string{UCANProvideLiquidity, UCANCreate}
|
||||
case DEXOpRemoveLiquidity:
|
||||
return []string{UCANRemoveLiquidity, UCANDelete}
|
||||
case DEXOpCreatePool:
|
||||
return []string{UCANCreatePool, UCANCreate, UCANAdmin}
|
||||
case DEXOpRegisterAccount:
|
||||
return []string{UCANRegisterAccount, UCANCreate}
|
||||
case DEXOpUpdatePortfolio:
|
||||
return []string{UCANUpdatePortfolio, UCANUpdate}
|
||||
case DEXOpWithdraw:
|
||||
return []string{UCANWithdraw, UCANUpdate}
|
||||
case DEXOpDeposit:
|
||||
return []string{UCANDeposit, UCANUpdate}
|
||||
case DEXOpQueryPool:
|
||||
return []string{UCANQueryPool, UCANRead}
|
||||
case DEXOpQueryOrders:
|
||||
return []string{UCANQueryOrders, UCANRead}
|
||||
case DEXOpQueryPortfolio:
|
||||
return []string{UCANQueryPortfolio, UCANRead}
|
||||
default:
|
||||
return []string{UCANRead} // Default to read permission
|
||||
}
|
||||
}
|
||||
|
||||
// CreateDEXResourceURI builds a DEX resource URI for UCAN validation
|
||||
func (m *UCANCapabilityMapper) CreateDEXResourceURI(resourceType, resourceID string) string {
|
||||
return fmt.Sprintf("dex:%s:%s", resourceType, resourceID)
|
||||
}
|
||||
|
||||
// CreatePoolResourceURI builds a pool resource URI for UCAN validation
|
||||
func (m *UCANCapabilityMapper) CreatePoolResourceURI(poolID string) string {
|
||||
return fmt.Sprintf("dex:pool:%s", poolID)
|
||||
}
|
||||
|
||||
// CreateOrderResourceURI builds an order resource URI for UCAN validation
|
||||
func (m *UCANCapabilityMapper) CreateOrderResourceURI(orderID string) string {
|
||||
return fmt.Sprintf("dex:order:%s", orderID)
|
||||
}
|
||||
|
||||
// CreateDEXAttenuation creates a UCAN attenuation for DEX operations
|
||||
func (m *UCANCapabilityMapper) CreateDEXAttenuation(
|
||||
actions []string,
|
||||
resourceType string,
|
||||
resourceID string,
|
||||
) ucan.Attenuation {
|
||||
resourceURI := m.CreateDEXResourceURI(resourceType, resourceID)
|
||||
|
||||
resource := &ucan.SimpleResource{
|
||||
Scheme: "dex",
|
||||
Value: fmt.Sprintf("%s:%s", resourceType, resourceID),
|
||||
URI: resourceURI,
|
||||
}
|
||||
|
||||
// Use MultiCapability for multiple actions
|
||||
var capability ucan.Capability
|
||||
if len(actions) == 1 {
|
||||
capability = &ucan.SimpleCapability{
|
||||
Action: actions[0],
|
||||
}
|
||||
} else {
|
||||
capability = &ucan.MultiCapability{
|
||||
Actions: actions,
|
||||
}
|
||||
}
|
||||
|
||||
return ucan.Attenuation{
|
||||
Capability: capability,
|
||||
Resource: resource,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateAmountLimitedAttenuation creates a UCAN attenuation with amount limits
|
||||
func (m *UCANCapabilityMapper) CreateAmountLimitedAttenuation(
|
||||
actions []string,
|
||||
poolID string,
|
||||
maxAmount string,
|
||||
) ucan.Attenuation {
|
||||
// Create base attenuation
|
||||
baseAttenuation := m.CreateDEXAttenuation(actions, "pool", poolID)
|
||||
|
||||
// For amount limits, we'll need to handle this at validation layer
|
||||
// since the standard capability types don't support custom constraints
|
||||
|
||||
return baseAttenuation
|
||||
}
|
||||
|
||||
// CreatePoolRestrictedAttenuation creates a UCAN attenuation restricted to specific pools
|
||||
func (m *UCANCapabilityMapper) CreatePoolRestrictedAttenuation(
|
||||
actions []string,
|
||||
allowedPools []string,
|
||||
) ucan.Attenuation {
|
||||
// Create resource for multiple pools
|
||||
resourceURI := "dex:pool:*"
|
||||
if len(allowedPools) == 1 {
|
||||
resourceURI = m.CreatePoolResourceURI(allowedPools[0])
|
||||
}
|
||||
|
||||
resource := &ucan.SimpleResource{
|
||||
Scheme: "dex",
|
||||
Value: "pool:*",
|
||||
URI: resourceURI,
|
||||
}
|
||||
|
||||
// Use MultiCapability for multiple actions
|
||||
var capability ucan.Capability
|
||||
if len(actions) == 1 {
|
||||
capability = &ucan.SimpleCapability{
|
||||
Action: actions[0],
|
||||
}
|
||||
} else {
|
||||
capability = &ucan.MultiCapability{
|
||||
Actions: actions,
|
||||
}
|
||||
}
|
||||
|
||||
return ucan.Attenuation{
|
||||
Capability: capability,
|
||||
Resource: resource,
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateUCANCapabilities validates that a UCAN capability grants the required DEX actions
|
||||
func (m *UCANCapabilityMapper) ValidateUCANCapabilities(
|
||||
capability ucan.Capability,
|
||||
requiredActions []string,
|
||||
) bool {
|
||||
return capability.Grants(requiredActions)
|
||||
}
|
||||
|
||||
// IsUCANAction checks if an action string is a valid UCAN action
|
||||
func IsUCANAction(action string) bool {
|
||||
validActions := []string{
|
||||
UCANSwap, UCANExecuteSwap, UCANLimitOrder, UCANMarketOrder,
|
||||
UCANCancelOrder, UCANCancelAllOrders,
|
||||
UCANProvideLiquidity, UCANRemoveLiquidity, UCANCreatePool,
|
||||
UCANRegisterAccount, UCANUpdatePortfolio, UCANWithdraw, UCANDeposit,
|
||||
UCANQueryPool, UCANQueryOrders, UCANQueryPortfolio,
|
||||
UCANCreate, UCANRead, UCANUpdate, UCANDelete, UCANAdmin, UCANAll,
|
||||
}
|
||||
|
||||
for _, validAction := range validActions {
|
||||
if action == validAction {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GetDEXCapabilityTemplate returns a preconfigured capability template for DEX
|
||||
func GetDEXCapabilityTemplate() *ucan.CapabilityTemplate {
|
||||
return ucan.StandardServiceTemplate()
|
||||
}
|
||||
|
||||
// UCANPermissionRegistry extends the basic permission registry with UCAN capabilities
|
||||
type UCANPermissionRegistry struct {
|
||||
operationCapabilities map[DEXOperation][]string
|
||||
mapper *UCANCapabilityMapper
|
||||
}
|
||||
|
||||
// NewUCANPermissionRegistry creates a new UCAN-aware permission registry
|
||||
func NewUCANPermissionRegistry() *UCANPermissionRegistry {
|
||||
registry := &UCANPermissionRegistry{
|
||||
operationCapabilities: make(map[DEXOperation][]string),
|
||||
mapper: NewUCANCapabilityMapper(),
|
||||
}
|
||||
|
||||
// Initialize default capabilities
|
||||
registry.initializeDefaultCapabilities()
|
||||
return registry
|
||||
}
|
||||
|
||||
// initializeDefaultCapabilities sets up default capability mappings
|
||||
func (r *UCANPermissionRegistry) initializeDefaultCapabilities() {
|
||||
operations := []DEXOperation{
|
||||
DEXOpSwap, DEXOpExecuteSwap, DEXOpLimitOrder, DEXOpMarketOrder,
|
||||
DEXOpCancelOrder, DEXOpCancelAllOrders,
|
||||
DEXOpProvideLiquidity, DEXOpRemoveLiquidity, DEXOpCreatePool,
|
||||
DEXOpRegisterAccount, DEXOpUpdatePortfolio, DEXOpWithdraw, DEXOpDeposit,
|
||||
DEXOpQueryPool, DEXOpQueryOrders, DEXOpQueryPortfolio,
|
||||
}
|
||||
|
||||
for _, op := range operations {
|
||||
r.operationCapabilities[op] = r.mapper.GetUCANCapabilitiesForOperation(op)
|
||||
}
|
||||
}
|
||||
|
||||
// GetRequiredUCANCapabilities returns UCAN-specific capabilities for a DEX operation
|
||||
func (r *UCANPermissionRegistry) GetRequiredUCANCapabilities(operation DEXOperation) ([]string, error) {
|
||||
capabilities, exists := r.operationCapabilities[operation]
|
||||
if !exists {
|
||||
capabilities = r.mapper.GetUCANCapabilitiesForOperation(operation)
|
||||
}
|
||||
|
||||
if len(capabilities) == 0 {
|
||||
return nil, fmt.Errorf("no UCAN capabilities defined for operation: %s", operation.String())
|
||||
}
|
||||
return capabilities, nil
|
||||
}
|
||||
|
||||
// CreateDEXAttenuation creates a UCAN attenuation for DEX operations
|
||||
func (r *UCANPermissionRegistry) CreateDEXAttenuation(
|
||||
actions []string,
|
||||
resourceType string,
|
||||
resourceID string,
|
||||
) ucan.Attenuation {
|
||||
return r.mapper.CreateDEXAttenuation(actions, resourceType, resourceID)
|
||||
}
|
||||
|
||||
// CreateAmountLimitedAttenuation creates an amount-limited attenuation
|
||||
func (r *UCANPermissionRegistry) CreateAmountLimitedAttenuation(
|
||||
actions []string,
|
||||
poolID string,
|
||||
maxAmount string,
|
||||
) ucan.Attenuation {
|
||||
return r.mapper.CreateAmountLimitedAttenuation(actions, poolID, maxAmount)
|
||||
}
|
||||
|
||||
// CreatePoolRestrictedAttenuation creates a pool-restricted attenuation
|
||||
func (r *UCANPermissionRegistry) CreatePoolRestrictedAttenuation(
|
||||
actions []string,
|
||||
allowedPools []string,
|
||||
) ucan.Attenuation {
|
||||
return r.mapper.CreatePoolRestrictedAttenuation(actions, allowedPools)
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
|
||||
// CreateGaslessDEXAttenuation creates a UCAN attenuation that supports gasless transactions
|
||||
func CreateGaslessDEXAttenuation(
|
||||
actions []string,
|
||||
resourceType string,
|
||||
resourceID string,
|
||||
gasLimit uint64,
|
||||
) ucan.Attenuation {
|
||||
mapper := NewUCANCapabilityMapper()
|
||||
baseAttenuation := mapper.CreateDEXAttenuation(actions, resourceType, resourceID)
|
||||
|
||||
// Wrap capability with gasless support
|
||||
gaslessCapability := &ucan.GaslessCapability{
|
||||
Capability: baseAttenuation.Capability,
|
||||
AllowGasless: true,
|
||||
GasLimit: gasLimit,
|
||||
}
|
||||
|
||||
return ucan.Attenuation{
|
||||
Capability: gaslessCapability,
|
||||
Resource: baseAttenuation.Resource,
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateAmountConstraint validates amount constraints for DEX operations
|
||||
func ValidateAmountConstraint(
|
||||
capability ucan.Capability,
|
||||
amount string,
|
||||
maxAmount string,
|
||||
) error {
|
||||
// Amount validation would be handled at a higher level
|
||||
// This is a placeholder for the actual implementation
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidatePoolConstraint validates pool constraints for DEX operations
|
||||
func ValidatePoolConstraint(
|
||||
capability ucan.Capability,
|
||||
poolID string,
|
||||
allowedPools []string,
|
||||
) error {
|
||||
// Check if pool is in allowed list
|
||||
for _, allowed := range allowedPools {
|
||||
if poolID == allowed {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("pool %s not in allowed list", poolID)
|
||||
}
|
||||
Reference in New Issue
Block a user