Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 24 additions & 10 deletions harnesses/token-quote-coverage/cmd/monitor/mobula.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,16 @@ const (
mobulaUSDCBase = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"
mobulaUSDCBSC = "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d"
mobulaUSDGRobinhood = "0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168"
// dummy wallet required by the quoting endpoint (no funds needed)
mobulaUSDCSolana = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
// dummy EVM wallet (no funds needed for quote)
mobulaWallet = "0x0000000000000000000000000000000000000001"
// dummy Solana wallet — Mobula routes the swap but tx build fails due to
// missing SOL for fees; we treat "Transaction build failed" as a coverage
// HIT since the route itself was found.
mobulaSolanaWallet = "GThUX1Atko4tqhN2NaiTazWSeFWMuiUvfFnyJyUghFMJ"
)

// MobulaProvider hits api.mobula.io/api/2/swap/quoting for EVM chains (Base + BNB).
// Solana is excluded: /api/2/swap/quoting does not support Solana.
// MobulaProvider hits api.mobula.io/api/2/swap/quoting for EVM chains and Solana.
type MobulaProvider struct {
apiKey string
client *http.Client
Expand All @@ -32,21 +36,23 @@ func (p *MobulaProvider) Slug() string { return "mobula" }

func (p *MobulaProvider) SupportsChain(chain string) bool {
switch chain {
case "base", "bsc", "robinhood":
case "base", "bsc", "robinhood", "solana":
return true
}
return false
}

func (p *MobulaProvider) Quote(ctx context.Context, token Token) (ok bool) {
var chainId, tokenIn string
var chainId, tokenIn, wallet string
switch token.Chain {
case "base":
chainId, tokenIn = "8453", mobulaUSDCBase
chainId, tokenIn, wallet = "8453", mobulaUSDCBase, mobulaWallet
case "bsc":
chainId, tokenIn = "56", mobulaUSDCBSC
chainId, tokenIn, wallet = "56", mobulaUSDCBSC, mobulaWallet
case "robinhood":
chainId, tokenIn = "4663", mobulaUSDGRobinhood
chainId, tokenIn, wallet = "4663", mobulaUSDGRobinhood, mobulaWallet
case "solana":
chainId, tokenIn, wallet = "solana", mobulaUSDCSolana, mobulaSolanaWallet
default:
RecordProbe(p.Slug(), token.Venue, token.Chain, false)
return false
Expand All @@ -55,9 +61,9 @@ func (p *MobulaProvider) Quote(ctx context.Context, token Token) (ok bool) {
q := url.Values{}
q.Set("tokenIn", tokenIn)
q.Set("tokenOut", token.Address)
q.Set("amount", "1")
q.Set("amount", "1000000")
q.Set("chainId", chainId)
q.Set("walletAddress", mobulaWallet)
q.Set("walletAddress", wallet)
endpoint := "https://api.mobula.io/api/2/swap/quoting?" + q.Encode()

req, err := http.NewRequestWithContext(ctx, "GET", endpoint, nil)
Expand Down Expand Up @@ -86,13 +92,21 @@ func (p *MobulaProvider) Quote(ctx context.Context, token Token) (ok bool) {
Data struct {
AmountOutTokens string `json:"amountOutTokens"`
} `json:"data"`
Error string `json:"error"`
}
if err := json.Unmarshal(body, &r); err != nil {
fmt.Printf("[mobula] %s/%s parse error: %v\n", token.Chain, token.Address, err)
RecordProbe(p.Slug(), token.Venue, token.Chain, false)
return false
}

// For Solana: Mobula finds the route but fails to build the tx because the
// dummy wallet has no SOL for fees. Route found = coverage HIT.
if token.Chain == "solana" && r.Error == "Transaction build failed" {
RecordProbe(p.Slug(), token.Venue, token.Chain, true)
return true
}

ok = r.Data.AmountOutTokens != "" && r.Data.AmountOutTokens != "0"
RecordProbe(p.Slug(), token.Venue, token.Chain, ok)
return ok
Expand Down
Loading