DEV Community

Cover image for MPP TestKit - Go SDK
MPP TestKit
MPP TestKit

Posted on

MPP TestKit - Go SDK

The Go MPP SDK handles the full Solana HTTP 402 payment flow with zero external dependencies. Pure stdlib: ed25519 signing, base58, JSON-RPC, and compact-u16 transaction encoding. One import, one function call on each side.


Go and HTTP 402

Go is the language of infrastructure: APIs, microservices, proxies. If you're building something that receives a lot of requests and needs to monetise them without billing overhead, Go is a natural fit.

The Go MPP SDK (github.com/mpptestkit/mpp-test-sdk-go) adds Solana-backed HTTP 402 support to any net/http handler with a single middleware call. No wallet libraries. No Solana SDK. No cgo. Pure Go standard library from crypto to JSON-RPC.


Installation

go get github.com/mpptestkit/mpp-test-sdk-go
Enter fullscreen mode Exit fullscreen mode

Server

package main

import (
    "encoding/json"
    "fmt"
    "net/http"

    mpp "github.com/mpptestkit/mpp-test-sdk-go"
)

func main() {
    server, err := mpp.CreateTestServer(&mpp.TestServerConfig{
        Network: mpp.NetworkDevnet,
    })
    if err != nil {
        panic(err)
    }
    fmt.Println("Recipient:", server.RecipientAddress)

    mux := http.NewServeMux()
    mux.Handle("/api/data", server.Charge(mpp.ChargeOptions{Amount: "0.001"})(
        http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            w.Header().Set("Content-Type", "application/json")
            json.NewEncoder(w).Encode(map[string]string{"result": "here is your data"})
        }),
    ))

    http.ListenAndServe(":3001", mux)
}
Enter fullscreen mode Exit fullscreen mode

server.Charge(...) returns an http.Handler middleware that fits any net/http mux, gorilla/mux, chi, or echo router.


Client

package main

import (
    "context"
    "encoding/json"
    "fmt"

    mpp "github.com/mpptestkit/mpp-test-sdk-go"
)

func main() {
    ctx := context.Background()

    client, err := mpp.CreateTestClient(ctx, &mpp.TestClientConfig{
        Network: mpp.NetworkDevnet,
        OnStep: func(step mpp.PaymentStep) {
            fmt.Printf("[%s] %s\n", step.Type, step.Message)
        },
    })
    if err != nil {
        panic(err)
    }
    fmt.Println("Wallet:", client.Address)

    resp, err := client.Fetch(ctx, "http://localhost:3001/api/data", nil)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()

    var result map[string]string
    json.NewDecoder(resp.Body).Decode(&result)
    fmt.Println(result) // map[result:here is your data]
}
Enter fullscreen mode Exit fullscreen mode

What happens under the hood

The SDK builds a Solana legacy transaction entirely from stdlib primitives:

compact-u16(1)       // signature count
[64]byte             // ed25519 signature over message
Message:
  [3]byte            // header: required_sigs=1, ro_signed=0, ro_unsigned=1
  compact-u16(3)     // account key count
  [32*3]byte         // [from, to, system_program]
  [32]byte           // recent blockhash
  compact-u16(1)     // instruction count
  Instruction:
    u8(2)            // program_id_index = system program
    compact-u16(2)   // account indices [0=from, 1=to]
    compact-u16(12)  // data length
    [4]byte LE       // SystemInstruction::Transfer = 2
    [8]byte LE       // lamports (u64)
Enter fullscreen mode Exit fullscreen mode

No Solana Go SDK. No protobuf. Just crypto/ed25519, encoding/binary, and math/big for base58.


Lifecycle steps

client, err := mpp.CreateTestClient(ctx, &mpp.TestClientConfig{
    Network: mpp.NetworkDevnet,
    OnStep: func(step mpp.PaymentStep) {
        switch step.Type {
        case mpp.StepWalletCreated:
            fmt.Println("Wallet:", step.Data["address"])
        case mpp.StepFunded:
            fmt.Println("Funded via devnet airdrop")
        case mpp.StepPayment:
            fmt.Println("Paying", step.Data["amount"], "SOL")
        case mpp.StepSuccess:
            fmt.Println("Served, status:", step.Data["status"])
        }
    },
})
Enter fullscreen mode Exit fullscreen mode

Steps: wallet-created, funded, request, payment, retry, success, error.


Airdrop with exponential back-off

On devnet and testnet the SDK retries airdrop failures automatically with 1s → 2s → 4s back-off before returning MppFaucetError:

// Returns *MppFaucetError after 3 failed attempts
client, err := mpp.CreateTestClient(ctx, nil)
if err != nil {
    var faucetErr *mpp.MppFaucetError
    if errors.As(err, &faucetErr) {
        fmt.Println("Airdrop failed for:", faucetErr.Address)
    }
}
Enter fullscreen mode Exit fullscreen mode

Mainnet

client, err := mpp.CreateTestClient(ctx, &mpp.TestClientConfig{
    Network:   mpp.NetworkMainnet,
    SecretKey: myKeypairBytes, // 32-byte seed or 64-byte full private key
})
Enter fullscreen mode Exit fullscreen mode

Drop-in http.Get replacement

resp, err := mpp.MppFetch(ctx, "http://localhost:3001/api/data", nil)
Enter fullscreen mode Exit fullscreen mode

Lazily creates a shared devnet client. Call mpp.ResetMppFetch() to force a fresh wallet.


Error types

var (
    faucetErr  *mpp.MppFaucetError
    payErr     *mpp.MppPaymentError
    timeoutErr *mpp.MppTimeoutError
    netErr     *mpp.MppNetworkError
)

switch {
case errors.As(err, &faucetErr):  fmt.Println("Airdrop failed:", faucetErr.Address)
case errors.As(err, &payErr):     fmt.Println("Payment failed, status:", payErr.Status)
case errors.As(err, &timeoutErr): fmt.Println("Timed out after:", timeoutErr.TimeoutMs, "ms")
case errors.As(err, &netErr):     fmt.Println("Network error:", netErr.Network)
}
Enter fullscreen mode Exit fullscreen mode

Integration test example

func TestChargesPerRequest(t *testing.T) {
    ctx := context.Background()

    server, _ := mpp.CreateTestServer(&mpp.TestServerConfig{Network: mpp.NetworkDevnet})
    mux := http.NewServeMux()
    mux.Handle("/data", server.Charge(mpp.ChargeOptions{Amount: "0.001"})(
        http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            w.WriteHeader(http.StatusOK)
        }),
    ))
    ts := httptest.NewServer(mux)
    defer ts.Close()

    client, _ := mpp.CreateTestClient(ctx, &mpp.TestClientConfig{Network: mpp.NetworkDevnet})
    resp, err := client.Fetch(ctx, ts.URL+"/data", nil)
    if err != nil {
        t.Fatal(err)
    }
    if resp.StatusCode != http.StatusOK {
        t.Fatalf("want 200, got %d", resp.StatusCode)
    }
}
Enter fullscreen mode Exit fullscreen mode

Links

Top comments (0)