Skip to content

Examples

In the snippets below tx is a *adapter.SolanaTransaction (see Fetching transactions), parser is dexparser.NewDexParser() and shredParser is dexparser.NewShredParser(). Complete, runnable programs are in Getting Started; every snippet here is compiled by the test suite.

Parse all data

result := parser.ParseAll(tx, nil)
if !result.State {
    log.Fatal(result.Msg)
}
fmt.Println("status:", result.TxStatus)
fmt.Println("trades:", len(result.Trades))
fmt.Println("liquidities:", len(result.Liquidities))
fmt.Println("meme events:", len(result.MemeEvents))
fmt.Println("transfers:", len(result.Transfers))
for _, warning := range result.Warnings {
    fmt.Println("warning:", warning)
}

Trades

for _, trade := range parser.ParseTrades(tx, nil) {
    fmt.Println("type:", trade.Type, "amm:", trade.AMM, "route:", trade.Route, "bot:", trade.Bot)
    fmt.Println("user:", trade.User, "pool:", trade.Pool, "idx:", trade.Idx)
    // AmountRaw is exact; Amount is a float64 for display
    fmt.Println("in: ", trade.InputToken.AmountRaw, trade.InputToken.Mint, trade.InputToken.Decimals)
    fmt.Println("out:", trade.OutputToken.AmountRaw, trade.OutputToken.Mint, trade.OutputToken.Decimals)
    for _, fee := range trade.Fees {
        fmt.Println("fee:", fee.Type, fee.Dex, fee.AmountRaw, fee.Mint, fee.Recipient)
    }
}

Aggregated trade

A multi-hop or routed swap has one trade per hop; AggregateTrade goes from the first input to the last output. A nil config computes it; with a config, set ParseType.AggregateTrade. For a Titan or OKX DEX Router V2 route it is the aggregator's route total, with the aggregator fee in Fee.

result := parser.ParseAll(tx, &types.ParseConfig{ParseType: types.ParseTradesOnly()})
if agg := result.AggregateTrade; agg != nil {
    fmt.Println(agg.Type, agg.InputToken.AmountRaw, agg.InputToken.Mint, "->",
        agg.OutputToken.AmountRaw, agg.OutputToken.Mint, "via", agg.AMMs)
    fmt.Println("arbitrage:", result.IsArbitrage())
}

Liquidity events

for _, event := range parser.ParseLiquidity(tx, nil) {
    fmt.Println(event.Type, event.AMM, "pool:", event.PoolId)
    fmt.Println("token0:", event.Token0AmountRaw, event.Token0Mint)
    fmt.Println("token1:", event.Token1AmountRaw, event.Token1Mint)
    fmt.Println("lp:", event.LpAmountRaw, event.PoolLpMint)
}

Meme events

result := parser.ParseAll(tx, nil)
for _, event := range result.MemeEvents {
    fmt.Println(event.Protocol, event.Type, "mint:", event.BaseMint, "quote:", event.QuoteMint)
    fmt.Println("user:", event.User, "pool:", event.Pool, "idx:", event.Idx)
}

Transfers

Transfers is filled when the transaction has no trades and no liquidity events. Program actions such as Jupiter DCA deposits, limit orders and Pump.fun fee payouts set Type (OpenDca, settleLimitOrder, claimCashback, …).

for _, transfer := range parser.ParseTransfers(tx, nil) {
    fmt.Println(transfer.Type, transfer.Info.TokenAmount.Amount, transfer.Info.Mint,
        transfer.Info.Source, "->", transfer.Info.Destination, "fee:", transfer.IsFee)
}

Filter by program

config := &types.ParseConfig{
    ProgramIds: []string{
        constants.DEX_PROGRAMS.PUMP_FUN.ID,
        constants.DEX_PROGRAMS.RAYDIUM_V4.ID,
    },
}
result := parser.ParseAll(tx, config)
if !result.State {
    fmt.Println(result.Msg) // "No matching program ids"
}

Ignore programs

config := &types.ParseConfig{
    IgnoreProgramIds: []string{constants.DEX_PROGRAMS.PHOENIX.ID},
}
result := parser.ParseAll(tx, config)
fmt.Println(len(result.Trades))

Failed transactions

result := parser.ParseAll(tx, nil)
if result.TxStatus == types.TransactionStatusFailed {
    fmt.Println(result.Msg, "fee:", result.Fee.Amount) // "transaction failed"
}

// Decode the reverted instructions anyway
config := types.DefaultParseConfig()
config.IncludeFailedTxs = true
result = parser.ParseAll(tx, &config)
fmt.Println(len(result.Trades))

Batch parsing

txs := []*adapter.SolanaTransaction{tx}
results := parser.ParseBatch(txs, nil, 8) // 8 workers; results keep the input order
for i, result := range results {
    fmt.Println(i, result.Signature, len(result.Trades))
}

ShredParser

result := shredParser.ParseAll(tx, nil)
for _, ins := range result.ParsedInstructions {
    switch {
    case ins.Trade != nil:
        fmt.Println(ins.Idx, ins.ProgramName, ins.Action, ins.Trade.Type,
            ins.Trade.InputToken.AmountRaw, ins.InputAmountKind,
            ins.Trade.OutputToken.AmountRaw, ins.OutputAmountKind)
    case ins.MemeEvent != nil:
        fmt.Println(ins.Idx, ins.ProgramName, ins.Action, ins.MemeEvent.Type, ins.MemeEvent.BaseMint)
    case ins.Transfer != nil:
        fmt.Println(ins.Idx, ins.ProgramName, ins.Action, ins.Transfer.Info.TokenAmount.Amount)
    }
}

See ShredParser for what these amounts mean.

Raydium V4 logs

The Raydium AMM v4 program writes a ray_log line per swap, deposit and withdraw:

for _, line := range tx.Meta.LogMessages {
    payload, ok := strings.CutPrefix(line, "Program log: ray_log: ")
    if !ok {
        continue
    }
    data, err := base64.StdEncoding.DecodeString(payload)
    if err != nil {
        continue
    }
    if swap := raydium.ParseRaydiumSwapLog(raydium.DecodeRaydiumLog(data)); swap != nil {
        fmt.Println(swap.Type, swap.Mode, swap.InputAmount, swap.OutputAmount, swap.SlippageProtection)
    }
}

Constants

fmt.Println(constants.GetProgramName(constants.DEX_PROGRAMS.ORCA.ID)) // Orca
fmt.Println(constants.GetProgramName("11111111111111111111111111111111")) // Unknown: not a DEX program
fmt.Println(constants.IsDexProgram(constants.DEX_PROGRAMS.PUMP_SWAP.ID))  // true
fmt.Println(constants.GetBotNames())
fmt.Println(constants.GetTipProvider("96gYZGLnJYVFmbjzopPSU6QiEV5fGqZNyN9nmNhvrZU5")) // Jito