Files
sonr/x/dex/keeper/msg_server.go
T

344 lines
13 KiB
Go
Raw Normal View History

2025-10-03 14:45:52 -04:00
package keeper
import (
"context"
2025-10-26 16:22:07 -04:00
"fmt"
"time"
2025-10-03 14:45:52 -04:00
2025-10-26 16:22:07 -04:00
sdkerrors "cosmossdk.io/errors"
2025-10-03 14:45:52 -04:00
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
}
2025-10-26 16:22:07 -04:00
// ExecuteSwap implements cross-chain swap execution via ICA
// This method handles token swaps on remote chains through Interchain Accounts
// with special support for Noble USDC integration
2025-10-03 14:45:52 -04:00
func (ms msgServer) ExecuteSwap(
ctx context.Context,
msg *types.MsgExecuteSwap,
) (*types.MsgExecuteSwapResponse, error) {
2025-10-26 16:22:07 -04:00
sdkCtx := sdk.UnwrapSDKContext(ctx)
2025-10-03 14:45:52 -04:00
// 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
}
}
2025-10-26 16:22:07 -04:00
// 1. Validate DID exists and is active
_, err := ms.didKeeper.GetDIDDocument(sdkCtx, msg.Did)
if err != nil {
return nil, sdkerrors.Wrapf(err, "DID %s not found", msg.Did)
}
// Note: Active status check removed as DIDDocument may not have Active field
// If needed, add additional validation based on actual DID structure
// 2. Validate connection exists and is open
if err := ms.ValidateConnection(sdkCtx, msg.ConnectionId); err != nil {
return nil, sdkerrors.Wrapf(types.ErrInvalidConnection, "connection validation failed: %v", err)
}
// 3. Get or ensure ICA account exists
account, err := ms.GetDEXAccount(sdkCtx, msg.Did, msg.ConnectionId)
if err != nil {
// Account doesn't exist, return error - user must register first
return nil, sdkerrors.Wrapf(types.ErrAccountNotFound, "DEX account not found for DID %s on connection %s. Please register first.", msg.Did, msg.ConnectionId)
}
// Verify account is active
if account.Status != types.ACCOUNT_STATUS_ACTIVE {
return nil, sdkerrors.Wrapf(types.ErrAccountNotActive, "DEX account is not active (status: %s)", account.Status.String())
}
// 4. Validate swap parameters
tokenIn := sdk.NewCoin(msg.SourceDenom, msg.Amount)
if err := ms.ValidateSwapParameters(tokenIn, msg.TargetDenom, msg.MinAmountOut); err != nil {
return nil, sdkerrors.Wrapf(types.ErrInvalidSwapParams, "swap parameter validation failed: %v", err)
}
// 5. Build swap message for the target chain
// For Noble USDC swaps, we use IBC transfer
// For other DEX chains (like Osmosis), we build chain-specific swap messages
var swapMsgs []sdk.Msg
var swapType string
// Check if this is a Noble USDC swap
if types.IsNobleChain(account.HostChainId) || msg.SourceDenom == types.NobleUSDCDenom || msg.TargetDenom == types.NobleUSDCDenom {
// Build Noble-specific swap message (IBC transfer for USDC)
swapMsg, err := ms.BuildNobleSwapMsg(sdkCtx, account.AccountAddress, tokenIn, msg.TargetDenom, msg.MinAmountOut)
if err != nil {
return nil, sdkerrors.Wrapf(types.ErrSwapFailed, "failed to build Noble swap message: %v", err)
}
swapMsgs = []sdk.Msg{swapMsg}
swapType = "noble_usdc_swap"
} else {
// Build generic DEX swap message (e.g., Osmosis)
swapMsg := ms.BuildOsmosisSwapMsg(account.AccountAddress, 1, tokenIn, msg.TargetDenom, msg.MinAmountOut)
swapMsgs = []sdk.Msg{swapMsg}
swapType = "osmosis_swap"
}
// 6. Calculate timeout from message or use default
timeoutDuration := msg.Timeout.Sub(sdkCtx.BlockTime())
if timeoutDuration <= 0 {
timeoutDuration = 30 * time.Second // Default 30 second timeout
}
// 7. Send the swap transaction via ICA
sequence, err := ms.SendDEXTransaction(
sdkCtx,
msg.Did,
msg.ConnectionId,
swapMsgs,
fmt.Sprintf("swap_%s_%s_for_%s", swapType, msg.SourceDenom, msg.TargetDenom),
timeoutDuration,
)
if err != nil {
return nil, sdkerrors.Wrapf(types.ErrSwapFailed, "failed to send swap transaction via ICA: %v", err)
}
// 8. Track transaction in DWN if available
if ms.dwnKeeper != nil {
// Create swap activity record
activity := types.DEXActivity{
Type: "swap",
Did: msg.Did,
ConnectionId: msg.ConnectionId,
BlockHeight: sdkCtx.BlockHeight(),
Timestamp: sdkCtx.BlockTime(),
Status: "pending",
Amount: sdk.NewCoins(tokenIn),
}
// Store in DWN for user history (non-blocking)
if err := ms.storeActivityInDWN(sdkCtx, msg.Did, &activity); err != nil {
// Log but don't fail the transaction
ms.Logger(sdkCtx).Error("failed to store swap activity in DWN", "error", err, "did", msg.Did)
}
}
// 9. Emit swap event for indexing
sdkCtx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeSwapExecuted,
sdk.NewAttribute("did", msg.Did),
sdk.NewAttribute("connection_id", msg.ConnectionId),
sdk.NewAttribute("source_denom", msg.SourceDenom),
sdk.NewAttribute("target_denom", msg.TargetDenom),
sdk.NewAttribute("amount", msg.Amount.String()),
sdk.NewAttribute("min_amount_out", msg.MinAmountOut.String()),
sdk.NewAttribute("sequence", fmt.Sprintf("%d", sequence)),
sdk.NewAttribute("swap_type", swapType),
sdk.NewAttribute("ica_address", account.AccountAddress),
),
)
return &types.MsgExecuteSwapResponse{
TxHash: "", // Will be populated by ICA callback
AmountReceived: "", // Will be populated by ICA callback
Sequence: sequence,
}, nil
2025-10-03 14:45:52 -04:00
}
// 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
2025-10-26 16:22:07 -04:00
// 8. Send IBC packet through IBC channel and await acknowledgment
2025-10-03 14:45:52 -04:00
// 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
}
2025-10-26 16:22:07 -04:00
// storeActivityInDWN stores a DEX activity record in the DWN module
func (ms msgServer) storeActivityInDWN(ctx sdk.Context, did string, activity *types.DEXActivity) error {
// For now, this is a stub - actual implementation would interface with DWN keeper
// to store activity records in the user's decentralized web node
if ms.dwnKeeper == nil {
return nil
}
// TODO: Implement actual DWN storage logic
// This would involve:
// 1. Serializing the activity to JSON
// 2. Creating a DWN record with proper schema
// 3. Storing it in the user's vault
ms.Logger(ctx).Info("DEX activity tracked",
"did", did,
"type", activity.Type,
"status", activity.Status,
)
return nil
}