Examples

Golang

package main

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"time"

	"github.com/gagliardetto/solana-go"
)

var (
	Endpoints = map[string]string{
		"fra": "routerfra.systems-echo.com",
		"ams": "routerams.systems-echo.com",
		"ny":  "routerny.systems-echo.com",
	}
	Endpoint = Endpoints["fra"] // use the closest endpoint to you
	URL      = fmt.Sprintf("https://%s", Endpoint)
)

type Direction string

const (
	DirectionUnspecified Direction = "TRADE_DIRECTION_UNSPECIFIED"
	DirectionBuy         Direction = "TRADE_DIRECTION_BUY"
	DirectionSell        Direction = "TRADE_DIRECTION_SELL"
)

type Platform string

const (
	PlatformTypeUnspecified      Platform = "PLATFORM_UNSPECIFIED"
	PlatformTypeRaydium          Platform = "PLATFORM_RAYDIUM"
	PlatformTypeRaydiumCp        Platform = "PLATFORM_RAYDIUM_CP"
	PlatformTypeRaydiumClmm      Platform = "PLATFORM_RAYDIUM_CLMM"
	PlatformTypePumpfun          Platform = "PLATFORM_PUMPFUN"
	PlatformTypeMoonShot         Platform = "PLATFORM_MOONSHOT"
	PlatformTypeMeteoraDyn       Platform = "PLATFORM_METEORA_DYN"
	PlatformTypeMeteoraDlmm      Platform = "PLATFORM_METEORA_DLMM"
	PlatformTypePamm             Platform = "PLATFORM_PAMM"
	PlatformTypeRaydiumLaunchpad Platform = "PLATFORM_RAYDIUM_LAUNCHPAD"
	PlatformTypeMeteoraDbc       Platform = "PLATFORM_METEORA_DBC"
	PlatformTypeMeteoraDammV2    Platform = "PLATFORM_METEORA_DAMM_V2"
	PlatformTypeHeaven           Platform = "PLATFORM_HEAVEN"
)

type SwapSettings struct {
	Fee      float64 `json:"fee"`
	Tip      float64 `json:"tip"`
	Slippage float64 `json:"slippage"`
}

type TokenInfo struct {
	Address  string `json:"address"`
	Name     string `json:"name"`
	Symbol   string `json:"symbol"`
	Decimals uint32 `json:"decimals"`
	IsStable bool   `json:"isStable"`
}

type GenerateSwapRequest struct {
	User         string       `json:"user"`
	Input        string       `json:"input"`
	Direction    Direction    `json:"direction"`
	Amount       string       `json:"amount"`
	Settings     SwapSettings `json:"settings"`
	NonceAccount string       `json:"nonceAccount,omitempty"`
}

type GenerateSwapResponse struct {
	Transactions []*SwapTransaction `json:"transactions"`
}

type SwapTransaction struct {
	UnsignedTransaction string `json:"unsignedTransaction"`
}

type SubmitSwapRequest struct {
	SignedTransactions []string `json:"signedTransactions"`
}

type SubmitSwapResponse struct {
	Results []*SubmitSwapResult `json:"results"`
}

type SubmitSwapResult struct {
	TransactionID  string     `json:"transactionId"`
	Slot           string     `json:"slot"`
	PoolID         string     `json:"poolId"`
	Platform       Platform   `json:"platform"`
	TokenIn        *TokenInfo `json:"tokenIn"`
	TokenOut       *TokenInfo `json:"tokenOut"`
	TokenInAmount  string     `json:"tokenInAmount"`
	TokenOutAmount string     `json:"tokenOutAmount"`
	CreatedAt      string     `json:"createdAt"`
}

type Client struct {
	apiKey string
	client *http.Client
}

func NewClient(apiKey string) *Client {
	return &Client{
		apiKey: apiKey,
		client: &http.Client{},
	}
}

func (c *Client) Close() error {
	if c.client != nil {
		c.client.CloseIdleConnections()
	}
	return nil
}

func (c *Client) GenerateSwap(ctx context.Context, in *GenerateSwapRequest) (*GenerateSwapResponse, error) {
	var out GenerateSwapResponse
	err := c.call(ctx, http.MethodPost, "/api/v1/router/swap/generate", in, &out)
	if err != nil {
		return nil, err
	}
	return &out, nil
}

func (c *Client) SubmitSwap(ctx context.Context, in *SubmitSwapRequest) (*SubmitSwapResponse, error) {
	var out SubmitSwapResponse
	err := c.call(ctx, http.MethodPost, "/api/v1/router/swap/submit", in, &out)
	if err != nil {
		return nil, err
	}
	return &out, nil
}

func (c *Client) call(
	ctx context.Context,
	method, path string,
	in, out interface{},
) error {
	body, err := json.Marshal(in)
	if err != nil {
		return err
	}

	req, err := http.NewRequestWithContext(
		ctx,
		method,
		fmt.Sprintf("%s%s", URL, path),
		bytes.NewReader(body),
	)
	if err != nil {
		return err
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Accept", "application/json")
	req.Header.Set("Echo-Api-Key", c.apiKey)

	resp, err := c.client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return err
	}

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("%d: %s", resp.StatusCode, string(respBody))
	}

	if err := json.Unmarshal(respBody, out); err != nil {
		return err
	}

	return nil
}

func main() {
	apiKey := "YOUR_API_KEY"
	user := solana.MustPrivateKeyFromBase58("YOUR_PRIVATE_KEY")
	input := "YOUR INPUT" // can be token address or pool address
	amount := "0.01"      // or "50%" for balance-based"
	direction := DirectionBuy
	settings := SwapSettings{
		Fee:      0.0001,
		Tip:      0.001,
		Slippage: 10,
	}
	nonceAccount := "YOUR_NONCE_ACCOUNT" // optional,  BUT highly recommended to set

	client := NewClient(apiKey)
	defer client.Close()

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	generateSwapReq := &GenerateSwapRequest{
		User:         user.PublicKey().String(),
		Input:        input,
		Direction:    direction,
		Amount:       amount,
		Settings:     settings,
		NonceAccount: nonceAccount,
	}

	genResp, err := client.GenerateSwap(ctx, generateSwapReq)
	if err != nil {
		fmt.Printf("Error generating swap: %v\n", err)
		return
	}
	fmt.Printf("Got %d transactions\n", len(genResp.Transactions))

	signedTransactions := make([]string, len(genResp.Transactions))
	for i := range genResp.Transactions {
		r := genResp.Transactions[i]
		var tx solana.Transaction
		if err := tx.UnmarshalBase64(r.UnsignedTransaction); err != nil {
			fmt.Printf("Error unmarshaling transaction: %v\n", err)
			return
		}
		if _, err := tx.PartialSign(func(key solana.PublicKey) *solana.PrivateKey {
			if user.PublicKey().Equals(key) {
				return &user
			}
			return nil
		}); err != nil {
			fmt.Printf("Error signing transaction: %v\n", err)
			return
		}
		signedTx, err := tx.ToBase64()
		if err != nil {
			fmt.Printf("Error marshaling signed transaction: %v\n", err)
			return
		}
		signedTransactions[i] = signedTx
	}
	fmt.Printf("Signed %d transactions\n", len(signedTransactions))

	// since we don't use nonce account in this example, submit only 1st tx
	// if u use nonce account -> submit all transactions
	txToSubmit := signedTransactions[:1]
	submitSwapReq := &SubmitSwapRequest{
		SignedTransactions: txToSubmit,
	}

	submitResp, err := client.SubmitSwap(ctx, submitSwapReq)
	if err != nil {
		fmt.Printf("Error submitting swap: %v\n", err)
		return
	}
	fmt.Printf("Submitted %d transactions\n", len(submitResp.Results))

	// by default, router will wait for 1 confirmation only
	// so for now you can expect only 1 result
	for i, r := range submitResp.Results {
		fmt.Printf("Result %d: %+v\n", i, r)
	}
}

Last updated