mirror of
https://github.com/sonr-io/sonr.git
synced 2026-08-02 17:31:39 +00:00
261 lines
7.9 KiB
Go
261 lines
7.9 KiB
Go
package usdcswapdid
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"fmt"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
"github.com/sonr-io/sonr/test/e2e/utils"
|
||
|
|
dextypes "github.com/sonr-io/sonr/x/dex/types"
|
||
|
|
didtypes "github.com/sonr-io/sonr/x/did/types"
|
||
|
|
)
|
||
|
|
|
||
|
|
// CreateTestDID creates a DID document for testing
|
||
|
|
// Returns the created DID document or error
|
||
|
|
// Note: For E2E tests, DIDs should be created using CLI commands before running tests
|
||
|
|
// This function checks if the DID exists and returns it, or returns instructions to create it
|
||
|
|
func CreateTestDID(ctx context.Context, cfg *utils.TestConfig, account, didID string) (*didtypes.DIDDocument, error) {
|
||
|
|
// Check if DID already exists
|
||
|
|
existing, err := QueryDID(ctx, cfg, didID)
|
||
|
|
if err == nil && existing != nil {
|
||
|
|
return existing, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// DID doesn't exist - return error with instructions
|
||
|
|
return nil, fmt.Errorf("DID %s not found. Create it using CLI:\n"+
|
||
|
|
" snrd tx did create-did %s --from %s --chain-id %s --yes",
|
||
|
|
didID, didID, account, cfg.ChainID)
|
||
|
|
}
|
||
|
|
|
||
|
|
// QueryDID queries a DID document by ID
|
||
|
|
func QueryDID(ctx context.Context, cfg *utils.TestConfig, didID string) (*didtypes.DIDDocument, error) {
|
||
|
|
// Build query URL
|
||
|
|
url := fmt.Sprintf("%s/sonr/did/v1/did/%s", cfg.BaseURL, didID)
|
||
|
|
|
||
|
|
// Response structure
|
||
|
|
var response struct {
|
||
|
|
DidDocument *didtypes.DIDDocument `json:"did_document"`
|
||
|
|
}
|
||
|
|
|
||
|
|
// Make HTTP request
|
||
|
|
if err := cfg.Client.DoRequest(ctx, url, &response); err != nil {
|
||
|
|
return nil, fmt.Errorf("failed to query DID: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
return response.DidDocument, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// QueryDEXAccount queries a DEX account by DID and connection ID
|
||
|
|
func QueryDEXAccount(ctx context.Context, cfg *utils.TestConfig, didID, connectionID string) (*dextypes.InterchainDEXAccount, error) {
|
||
|
|
// Build query URL
|
||
|
|
url := fmt.Sprintf("%s/sonr/dex/v1/account/%s/%s", cfg.BaseURL, didID, connectionID)
|
||
|
|
|
||
|
|
// Response structure
|
||
|
|
var response struct {
|
||
|
|
Account *dextypes.InterchainDEXAccount `json:"account"`
|
||
|
|
}
|
||
|
|
|
||
|
|
// Make HTTP request
|
||
|
|
if err := cfg.Client.DoRequest(ctx, url, &response); err != nil {
|
||
|
|
return nil, fmt.Errorf("failed to query DEX account: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
return response.Account, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// QueryDEXHistory queries transaction history for a DID
|
||
|
|
func QueryDEXHistory(ctx context.Context, cfg *utils.TestConfig, didID string) ([]*dextypes.DEXActivity, error) {
|
||
|
|
// Build query URL
|
||
|
|
url := fmt.Sprintf("%s/sonr/dex/v1/history/%s", cfg.BaseURL, didID)
|
||
|
|
|
||
|
|
// Response structure
|
||
|
|
var response struct {
|
||
|
|
History []*dextypes.DEXActivity `json:"history"`
|
||
|
|
}
|
||
|
|
|
||
|
|
// Make HTTP request
|
||
|
|
if err := cfg.Client.DoRequest(ctx, url, &response); err != nil {
|
||
|
|
return nil, fmt.Errorf("failed to query DEX history: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
return response.History, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// QueryAllDEXAccounts queries all DEX accounts
|
||
|
|
func QueryAllDEXAccounts(ctx context.Context, cfg *utils.TestConfig) ([]*dextypes.InterchainDEXAccount, error) {
|
||
|
|
// Build query URL
|
||
|
|
url := fmt.Sprintf("%s/sonr/dex/v1/accounts", cfg.BaseURL)
|
||
|
|
|
||
|
|
// Response structure
|
||
|
|
var response struct {
|
||
|
|
Accounts []*dextypes.InterchainDEXAccount `json:"accounts"`
|
||
|
|
}
|
||
|
|
|
||
|
|
// Make HTTP request
|
||
|
|
if err := cfg.Client.DoRequest(ctx, url, &response); err != nil {
|
||
|
|
return nil, fmt.Errorf("failed to query all DEX accounts: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
return response.Accounts, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// WaitForDEXAccountActivation waits for a DEX account to become active
|
||
|
|
// Returns true if account is active, false if timeout
|
||
|
|
func WaitForDEXAccountActivation(ctx context.Context, cfg *utils.TestConfig, didID, connectionID string, timeout time.Duration) (bool, error) {
|
||
|
|
deadline := time.Now().Add(timeout)
|
||
|
|
|
||
|
|
for time.Now().Before(deadline) {
|
||
|
|
account, err := QueryDEXAccount(ctx, cfg, didID, connectionID)
|
||
|
|
if err != nil {
|
||
|
|
// Account not found yet, continue waiting
|
||
|
|
time.Sleep(cfg.BlockTime)
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
|
||
|
|
if account.Status == dextypes.ACCOUNT_STATUS_ACTIVE {
|
||
|
|
return true, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check for failed status
|
||
|
|
if account.Status == dextypes.ACCOUNT_STATUS_FAILED {
|
||
|
|
return false, fmt.Errorf("DEX account activation failed")
|
||
|
|
}
|
||
|
|
|
||
|
|
time.Sleep(cfg.BlockTime)
|
||
|
|
}
|
||
|
|
|
||
|
|
return false, fmt.Errorf("timeout waiting for account activation")
|
||
|
|
}
|
||
|
|
|
||
|
|
// VerifyICAAccountAddress verifies that an ICA account has been created
|
||
|
|
func VerifyICAAccountAddress(ctx context.Context, cfg *utils.TestConfig, didID, connectionID string) (string, error) {
|
||
|
|
account, err := QueryDEXAccount(ctx, cfg, didID, connectionID)
|
||
|
|
if err != nil {
|
||
|
|
return "", fmt.Errorf("failed to query DEX account: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
if account.AccountAddress == "" {
|
||
|
|
return "", fmt.Errorf("ICA account address not yet assigned")
|
||
|
|
}
|
||
|
|
|
||
|
|
return account.AccountAddress, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// ValidateSwapParameters validates swap parameters before execution
|
||
|
|
func ValidateSwapParameters(sourceDenom, targetDenom string, amount, minOut int64) error {
|
||
|
|
if sourceDenom == "" {
|
||
|
|
return fmt.Errorf("source denom cannot be empty")
|
||
|
|
}
|
||
|
|
|
||
|
|
if targetDenom == "" {
|
||
|
|
return fmt.Errorf("target denom cannot be empty")
|
||
|
|
}
|
||
|
|
|
||
|
|
if sourceDenom == targetDenom {
|
||
|
|
return fmt.Errorf("source and target denoms must be different")
|
||
|
|
}
|
||
|
|
|
||
|
|
if amount <= 0 {
|
||
|
|
return fmt.Errorf("swap amount must be positive")
|
||
|
|
}
|
||
|
|
|
||
|
|
if minOut < 0 {
|
||
|
|
return fmt.Errorf("min output cannot be negative")
|
||
|
|
}
|
||
|
|
|
||
|
|
if minOut > amount {
|
||
|
|
return fmt.Errorf("min output cannot exceed input amount (unless exchange rate > 1)")
|
||
|
|
}
|
||
|
|
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// CalculateMinimumOutput calculates minimum output based on slippage tolerance
|
||
|
|
// slippagePct should be between 0 and 100 (e.g., 5 for 5% slippage)
|
||
|
|
func CalculateMinimumOutput(inputAmount int64, slippagePct float64) int64 {
|
||
|
|
if slippagePct < 0 {
|
||
|
|
slippagePct = 0
|
||
|
|
}
|
||
|
|
if slippagePct > 100 {
|
||
|
|
slippagePct = 100
|
||
|
|
}
|
||
|
|
|
||
|
|
multiplier := (100 - slippagePct) / 100
|
||
|
|
return int64(float64(inputAmount) * multiplier)
|
||
|
|
}
|
||
|
|
|
||
|
|
// FormatUSDCAmount formats a USDC amount for display
|
||
|
|
// USDC has 6 decimals, so 1000000 = 1.000000 USDC
|
||
|
|
func FormatUSDCAmount(amount int64) string {
|
||
|
|
whole := amount / 1_000_000
|
||
|
|
fractional := amount % 1_000_000
|
||
|
|
return fmt.Sprintf("%d.%06d USDC", whole, fractional)
|
||
|
|
}
|
||
|
|
|
||
|
|
// FormatSNRAmount formats a SNR amount for display
|
||
|
|
// SNR has 6 decimals (usnr), so 1000000 = 1.000000 SNR
|
||
|
|
func FormatSNRAmount(amount int64) string {
|
||
|
|
whole := amount / 1_000_000
|
||
|
|
fractional := amount % 1_000_000
|
||
|
|
return fmt.Sprintf("%d.%06d SNR", whole, fractional)
|
||
|
|
}
|
||
|
|
|
||
|
|
// ParseIBCDenom parses an IBC denom to extract the base denom
|
||
|
|
// IBC denoms have format: ibc/HASH
|
||
|
|
func ParseIBCDenom(ibcDenom string) (hash string, isIBC bool) {
|
||
|
|
if len(ibcDenom) > 4 && ibcDenom[:4] == "ibc/" {
|
||
|
|
return ibcDenom[4:], true
|
||
|
|
}
|
||
|
|
return ibcDenom, false
|
||
|
|
}
|
||
|
|
|
||
|
|
// BuildNobleIBCDenom builds the IBC denom for Noble USDC on Sonr chain
|
||
|
|
// This requires knowing the IBC channel from Noble to Sonr
|
||
|
|
func BuildNobleIBCDenom(channelID string) string {
|
||
|
|
// In actual implementation, this would use IBC denom trace hashing
|
||
|
|
// For now, return a placeholder
|
||
|
|
return fmt.Sprintf("ibc/noble_usdc_via_%s", channelID)
|
||
|
|
}
|
||
|
|
|
||
|
|
// ExtractSwapEventData extracts relevant data from a swap event
|
||
|
|
type SwapEventData struct {
|
||
|
|
DID string
|
||
|
|
ConnectionID string
|
||
|
|
SourceDenom string
|
||
|
|
TargetDenom string
|
||
|
|
Amount string
|
||
|
|
MinAmountOut string
|
||
|
|
Sequence string
|
||
|
|
SwapType string
|
||
|
|
ICAAddress string
|
||
|
|
}
|
||
|
|
|
||
|
|
// ParseSwapEvent parses a swap event from transaction logs
|
||
|
|
func ParseSwapEvent(events []interface{}) (*SwapEventData, error) {
|
||
|
|
// This would parse the actual event structure from the transaction
|
||
|
|
// For now, return a placeholder
|
||
|
|
return &SwapEventData{}, fmt.Errorf("event parsing not yet implemented")
|
||
|
|
}
|
||
|
|
|
||
|
|
// VerifyNobleConnection verifies that a Noble IBC connection exists
|
||
|
|
func VerifyNobleConnection(ctx context.Context, cfg *utils.TestConfig, connectionID string) error {
|
||
|
|
// Build query URL for connection
|
||
|
|
url := fmt.Sprintf("%s/ibc/core/connection/v1/connections/%s", cfg.BaseURL, connectionID)
|
||
|
|
|
||
|
|
var response struct {
|
||
|
|
Connection struct {
|
||
|
|
State string `json:"state"`
|
||
|
|
} `json:"connection"`
|
||
|
|
}
|
||
|
|
|
||
|
|
if err := cfg.Client.DoRequest(ctx, url, &response); err != nil {
|
||
|
|
return fmt.Errorf("failed to query connection: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
if response.Connection.State != "STATE_OPEN" {
|
||
|
|
return fmt.Errorf("connection is not open: %s", response.Connection.State)
|
||
|
|
}
|
||
|
|
|
||
|
|
return nil
|
||
|
|
}
|