179 lines
4.2 KiB
Go
179 lines
4.2 KiB
Go
package bridge
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
stargateAPIBase = "https://stargate.finance/api/v1"
|
|
stargateTimeout = 10 * time.Second
|
|
)
|
|
|
|
// chainIDToStargateKey maps chain ID to Stargate chain key
|
|
var stargateChainKeys = map[int]string{
|
|
1: "ethereum",
|
|
10: "optimism",
|
|
137: "polygon",
|
|
42161: "arbitrum",
|
|
8453: "base",
|
|
56: "bnb",
|
|
43114: "avalanche",
|
|
25: "cronos",
|
|
100: "gnosis",
|
|
324: "zksync",
|
|
59144: "linea",
|
|
534352: "scroll",
|
|
}
|
|
|
|
// Stargate-supported chain IDs
|
|
var stargateSupportedChains = map[int]bool{
|
|
1: true,
|
|
10: true,
|
|
137: true,
|
|
42161: true,
|
|
8453: true,
|
|
56: true,
|
|
43114: true,
|
|
25: true,
|
|
100: true,
|
|
324: true,
|
|
59144: true,
|
|
534352: true,
|
|
}
|
|
|
|
type stargateQuoteResponse struct {
|
|
Quotes []struct {
|
|
Bridge string `json:"bridge"`
|
|
SrcAmount string `json:"srcAmount"`
|
|
DstAmount string `json:"dstAmount"`
|
|
DstAmountMin string `json:"dstAmountMin"`
|
|
Error string `json:"error"`
|
|
Duration *struct {
|
|
Estimated int `json:"estimated"`
|
|
} `json:"duration"`
|
|
} `json:"quotes"`
|
|
}
|
|
|
|
// StargateProvider implements Provider for Stargate (LayerZero)
|
|
type StargateProvider struct {
|
|
apiBase string
|
|
client *http.Client
|
|
}
|
|
|
|
// NewStargateProvider creates a new Stargate bridge provider
|
|
func NewStargateProvider() *StargateProvider {
|
|
return &StargateProvider{
|
|
apiBase: stargateAPIBase,
|
|
client: &http.Client{
|
|
Timeout: stargateTimeout,
|
|
},
|
|
}
|
|
}
|
|
|
|
// Name returns the provider name
|
|
func (p *StargateProvider) Name() string {
|
|
return "Stargate"
|
|
}
|
|
|
|
// SupportsRoute returns true if Stargate supports the fromChain->toChain route
|
|
func (p *StargateProvider) SupportsRoute(fromChain, toChain int) bool {
|
|
return stargateSupportedChains[fromChain] && stargateSupportedChains[toChain]
|
|
}
|
|
|
|
// GetQuote fetches a bridge quote from the Stargate API
|
|
func (p *StargateProvider) GetQuote(ctx context.Context, req *BridgeRequest) (*BridgeQuote, error) {
|
|
srcKey, ok := stargateChainKeys[req.FromChain]
|
|
if !ok {
|
|
return nil, fmt.Errorf("Stargate: unsupported fromChain %d", req.FromChain)
|
|
}
|
|
dstKey, ok := stargateChainKeys[req.ToChain]
|
|
if !ok {
|
|
return nil, fmt.Errorf("Stargate: unsupported toChain %d", req.ToChain)
|
|
}
|
|
|
|
if req.Recipient == "" {
|
|
req.Recipient = "0x0000000000000000000000000000000000000000"
|
|
}
|
|
|
|
params := url.Values{}
|
|
params.Set("srcToken", req.FromToken)
|
|
params.Set("dstToken", req.ToToken)
|
|
params.Set("srcChainKey", srcKey)
|
|
params.Set("dstChainKey", dstKey)
|
|
params.Set("srcAddress", req.Recipient)
|
|
params.Set("dstAddress", req.Recipient)
|
|
params.Set("srcAmount", req.Amount)
|
|
params.Set("dstAmountMin", "0")
|
|
|
|
apiURL := fmt.Sprintf("%s/quotes?%s", p.apiBase, params.Encode())
|
|
|
|
httpReq, err := http.NewRequestWithContext(ctx, http.MethodGet, apiURL, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resp, err := p.client.Do(httpReq)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, fmt.Errorf("Stargate API error %d: %s", resp.StatusCode, string(body))
|
|
}
|
|
|
|
var stargateResp stargateQuoteResponse
|
|
if err := json.Unmarshal(body, &stargateResp); err != nil {
|
|
return nil, fmt.Errorf("failed to parse Stargate response: %w", err)
|
|
}
|
|
|
|
var bestIdx = -1
|
|
for i := range stargateResp.Quotes {
|
|
q := &stargateResp.Quotes[i]
|
|
if q.Error != "" {
|
|
continue
|
|
}
|
|
if bestIdx < 0 || q.DstAmount > stargateResp.Quotes[bestIdx].DstAmount {
|
|
bestIdx = i
|
|
}
|
|
}
|
|
|
|
if bestIdx < 0 {
|
|
return nil, fmt.Errorf("Stargate: no valid quotes")
|
|
}
|
|
bestQuote := &stargateResp.Quotes[bestIdx]
|
|
|
|
estTime := "1-5 min"
|
|
if bestQuote.Duration != nil && bestQuote.Duration.Estimated > 0 {
|
|
estTime = fmt.Sprintf("%d sec", bestQuote.Duration.Estimated)
|
|
}
|
|
|
|
return &BridgeQuote{
|
|
Provider: "Stargate",
|
|
FromChain: req.FromChain,
|
|
ToChain: req.ToChain,
|
|
FromAmount: req.Amount,
|
|
ToAmount: bestQuote.DstAmount,
|
|
Fee: "0",
|
|
EstimatedTime: estTime,
|
|
Route: []BridgeStep{{
|
|
Provider: bestQuote.Bridge,
|
|
From: strconv.Itoa(req.FromChain),
|
|
To: strconv.Itoa(req.ToChain),
|
|
Type: "bridge",
|
|
}},
|
|
}, nil
|
|
}
|