feat(dex): add support for noble usdc swaps

##

feat(dex): add support for noble usdc swaps
This commit is contained in:
Prad Nukala
2025-10-26 16:22:07 -04:00
parent 85304fd8b4
commit 3c682390a6
5 changed files with 309 additions and 41 deletions
+145 -21
View File
@@ -2,7 +2,10 @@ 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"
)
@@ -52,23 +55,15 @@ func (ms msgServer) RegisterDEXAccount(
}, 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.
// 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
@@ -77,13 +72,119 @@ func (ms msgServer) ExecuteSwap(
}
}
// 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
// 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
@@ -200,7 +301,7 @@ func (ms msgServer) CreateLimitOrder(
// 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
// 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
@@ -217,3 +318,26 @@ func (ms msgServer) CancelOrder(
// 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
}