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

249 lines
6.3 KiB
Go
Raw Normal View History

2025-10-03 14:45:52 -04:00
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
}