mirror of
https://github.com/sonr-io/sonr.git
synced 2026-08-02 17:31:39 +00:00
344 lines
13 KiB
Go
Executable File
344 lines
13 KiB
Go
Executable File
package keeper
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
sdkerrors "cosmossdk.io/errors"
|
|
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
|
|
}
|
|
|
|
// 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
|
|
func (ms msgServer) ExecuteSwap(
|
|
ctx context.Context,
|
|
msg *types.MsgExecuteSwap,
|
|
) (*types.MsgExecuteSwapResponse, error) {
|
|
sdkCtx := sdk.UnwrapSDKContext(ctx)
|
|
|
|
// 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
|
|
}
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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 IBC 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
|
|
}
|
|
|
|
// 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
|
|
}
|