Today we’ll explore the exciting world of meme tokens and automatic buying of the King of the Hill, armed with GoLang. So, grab a cup of smoothie and let’s get started!
King of the Hill, GoLang, and Pump.Fun API: An Introduction
Pump.Fun is a popular Solana-based launchpad platform for creating and trading meme tokens.
King of the Hill is the top gainer in the short term, the token that is most actively traded at the moment.
Pump.Fun API is 3rd party free API kindly provided by the https://api-pump.fun. You can check out the documentation here.
Building a API Wrapper
First, let’s write 2 auxiliary functions to call the API:
package main
import(
"bytes"
"encoding/json"
"errors"
"log"
"net/http"
"time"
)
var rest = &http.Client{Timeout: 10 * time.Second}
func _get(url string, apiKey string, target interface{}) error {
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return err
}
if apiKey != "" {
req.Header.Set("x-api-key", apiKey)
}
r, err := rest.Do(req)
if err != nil {
return err
}
defer r.Body.Close()
if r.StatusCode == http.StatusUnauthorized {
return errors.New(r.Status)
}
if r.StatusCode == http.StatusBadRequest {
b, _ := io.ReadAll(r.Body)
return errors.New(string(b))
}
return json.NewDecoder(r.Body).Decode(target)
}
func _post(url string, body interface{}, apiKey string, target interface{}) error {
jsonBody, _ := json.Marshal(body)
req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(jsonBody))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
if apiKey != "" {
req.Header.Set("x-api-key", apiKey)
}
r, err := rest.Do(req)
if err != nil {
return err
}
defer r.Body.Close()
if r.StatusCode == http.StatusUnauthorized {
return errors.New(r.Status)
}
if r.StatusCode == http.StatusBadRequest {
b, _ := io.ReadAll(r.Body)
return errors.New(string(b))
}
return json.NewDecoder(r.Body).Decode(target)
}
_get
function calls GET endpoints, passes the API key in the request header x-api-key and returns the converted JSON object_post
function calls POST endpoints, passes the API key in the request header x-api-key, passes converted JSON object as body and returns the JSON object.
Generating API Key
To interact with the API, we need an API key. We can write a function to call the key generation endpoint. But since we only need to generate the key once, we will use CURL.
curl --location --request POST 'https://rpc.api-pump.fun/createWallet' \
--header 'Content-Type: application/json'
As a result, we get the required API key:
{
"privateKey": "very-secret-private-key",
"publicKey": "not-very-secret-public-key",
"apiKey": "very-secret-api-key"
}
Building a Basic King of the Hill Watcher
Our second task is to build a simple endless loop that calls ‘King-of-the-hill’ endpoint every 60 second. Here’s the basic blueprint:
package main
import(
"bytes"
"encoding/json"
"errors"
"log"
"net/http"
"time"
)
type King struct {
Mint string
Name string
Symbol string
Description string
BondingCurve string `json:"bonding_curve"`
AssociatedBondingCurve string `json:"associated_bonding_curve"`
}
const RPC = "https://rpc.api-pump.fun"
const API_KEY = "<generated api key>"
var processed[] string
func main() {
for {
king := King{}
err := _get(RPC + "/king", API_KEY, &king)
if err == nil && !king.isProcessed() {
go king.process()
} else {
log.Println(err)
}
time.Sleep(60 * time.Second)
}
}
func(k King) isProcessed() bool {
for _, s := range processed {
if s == k.Mint {
return true
}
}
return false
}
func(k King) process() {
processed = append(processed, k.Mint)
log.Println(k) //spew.Dump(k)
}
As a result, every 60 seconds we request updated information about the king of the hill. To avoid duplicates and purchase a token only on the first event, we added an array of processed tokens
Now logs look like this (using spew.Dump):
17:15:30 (main.King) {
Mint: (string) (len=44) "2nh16MjCSsPZDtAdVA9HWgLqULT9MnPWqR2xrLWYpump",
Name: (string) (len=9) "Space Wif",
Symbol: (string) (len=4) "SWIF",
Description: (string) (len=13) "Dog Wif Space",
BondingCurve: (string) (len=44) "6crCg4j7wD9HP7oNESuXChJqezhQCpkxh2EB411bA4SA",
AssociatedBondingCurve: (string) (len=44) "2QWpYzUsnxh3FDjCoszuctiXf3KA35TecLs4U1s6NWH9"
}
17:22:30 (main.King) {
Mint: (string) (len=44) "44zXPs6e1qh9Bdye2LBw5BeGBX79SD26jgokghQCpump",
Name: (string) (len=7) "Meteor ",
Symbol: (string) (len=6) "Meteor",
Description: (string) (len=52) "about to strike the earth and destroy the space meta",
BondingCurve: (string) (len=44) "CmcojVLY8P7NqA8YitWUWukSWuzh5iViSUL6YkD5enYc",
AssociatedBondingCurve: (string) (len=44) "6fT3pLUqJ4qA9knkcy2pfnfY3b2DKaR9gsPV6YwjmNvY"
}
17:23:30 (main.King) {
Mint: (string) (len=43) "zXB51rdUxqQyzHHUaJ3UxBeBjLqfi7gNbhc3BMspump",
Name: (string) (len=12) "BaklavaOnSol",
Symbol: (string) (len=7) "BAKLAVA",
Description: (string) "",
BondingCurve: (string) (len=44) "3eUUW13ZTScAgq6UQNGwo2ScUCWoJrCHsxC6UaseCs3d",
AssociatedBondingCurve: (string) (len=44) "F8LoAxZKsWdqP4DchYJs68X9ThZiXsLggPdnuwKntyzm"
}
17:25:30 (main.King) {
Mint: (string) (len=44) "D2oy9CipYv8hPxH2YMRQyvgSEJmnN9d9zcCcKWG9pump",
Name: (string) (len=6) "heaven",
Symbol: (string) (len=6) "heaven",
Description: (string) (len=6) "heaven",
BondingCurve: (string) (len=44) "ADLQqSBau1AFKquKMujJYZSWFnkdPwMVtWMFGHdk7WxS",
AssociatedBondingCurve: (string) (len=44) "5VR9Fkhd6ZKqrseMuGg3A9SfeRBgKM9dextAUstXAqfo"
}
Initiating a token purchase
As a final step, let’s buy some amount of the found token:
package main
import(
"bytes"
"encoding/json"
"errors"
"log"
"net/http"
"time"
)
type TradeResponse struct {
Tx string
}
type Trade struct {
Mode string
Token string
Amount float64
AmountInSol bool
Slippage float64
PriorityFee float64
}
type King struct {
Mint string
Name string
Symbol string
Description string
BondingCurve string `json:"bonding_curve"`
AssociatedBondingCurve string `json:"associated_bonding_curve"`
}
const RPC = "https://rpc.api-pump.fun"
const API_KEY = "<generated api key>"
var rest = &http.Client{Timeout: 10 * time.Second}
var processed[] string
func main() {
for {
king := King{}
err := _get(RPC + "/king", API_KEY, &king)
if err == nil && !king.isProcessed() {
go king.process()
} else {
log.Println(err)
}
time.Sleep(60 * time.Second)
}
}
func(k King) isProcessed() bool {
for _, s := range processed {
if s == k.Mint {
return true
}
}
return false
}
func(k King) process() {
processed = append(processed, k.Mint)
tradeResponse := TradeResponse{}
tradeRequest := Trade{
Mode: "buy",
Token: k.Mint,
Amount: 10000000, //amount in lamports =0,01SOL
AmountInSol: true,
Slippage: 500, //slippage basis points (5%)
PriorityFee: 100000, //priority fee to speed up transaction confirmation ~0.005$
}
err := _post(RPC + "/trade", &tradeRequest, API_KEY, &tradeResponse)
if err != nil {
log.Println(err)
} else {
log.Println(tradeResponse)
}
}
func _get(url string, apiKey string, target interface{}) error {
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return err
}
if apiKey != "" {
req.Header.Set("x-api-key", apiKey)
}
r, err := rest.Do(req)
if err != nil {
return err
}
defer r.Body.Close()
if r.StatusCode == http.StatusUnauthorized {
return errors.New(r.Status)
}
if r.StatusCode == http.StatusBadRequest {
b, _ := io.ReadAll(r.Body)
return errors.New(string(b))
}
return json.NewDecoder(r.Body).Decode(target)
}
func _post(url string, body interface{}, apiKey string, target interface{}) error {
jsonBody, _ := json.Marshal(body)
req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(jsonBody))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
if apiKey != "" {
req.Header.Set("x-api-key", apiKey)
}
r, err := rest.Do(req)
if err != nil {
return err
}
defer r.Body.Close()
if r.StatusCode == http.StatusUnauthorized {
return errors.New(r.Status)
}
if r.StatusCode == http.StatusBadRequest {
b, _ := io.ReadAll(r.Body)S
return errors.New(string(b))
}
return json.NewDecoder(r.Body).Decode(target)
}
Here we trigger the token trade/swap operation with parameters:
- Mode: Swap direction — buy or sell.
- Token: Mint address
- Amount: Amount in SOL Lamports (10000000 lamports ~0,01SOL)
- Slippage: Swap slippage basis points (500bp ~5%)
- PriorityFee: priority fee to speed up transaction confirmation (100000 ~0.005$)
In the next part, we will implement a stop loss and take profit strategy based on the stream of Bonding Curve reserves changes.