wormhole-explorer/tx-tracker/chains/api_solana.go

119 lines
3.1 KiB
Go

package chains
import (
"context"
"encoding/hex"
"fmt"
"time"
"github.com/mr-tron/base58"
)
type solanaTransactionSignature struct {
Signature string `json:"signature"`
}
type solanaGetTransactionResponse struct {
BlockTime int64 `json:"blockTime"`
Meta struct {
InnerInstructions []struct {
Instructions []struct {
ParsedInstruction struct {
Type_ string `json:"type"`
Info struct {
Account string `json:"account"`
Amount string `json:"amount"`
Authority string `json:"authority"`
Destination string `json:"destination"`
Source string `json:"source"`
} `json:"info"`
} `json:"parsed"`
} `json:"instructions"`
} `json:"innerInstructions"`
Err []interface{} `json:"err"`
} `json:"meta"`
Transaction struct {
Message struct {
AccountKeys []struct {
Pubkey string `json:"pubkey"`
Signer bool `json:"signer"`
} `json:"accountKeys"`
} `json:"message"`
Signatures []string `json:"signatures"`
} `json:"transaction"`
}
func fetchSolanaTx(
ctx context.Context,
rateLimiter *time.Ticker,
baseUrl string,
txHash string,
) (*TxDetail, error) {
// Initialize RPC client
client, err := rpcDialContext(ctx, baseUrl)
if err != nil {
return nil, fmt.Errorf("failed to initialize RPC client: %w", err)
}
defer client.Close()
// Decode txHash bytes
// TODO: remove this when the fly fixes all txHash for Solana
h, err := hex.DecodeString(txHash)
if err != nil {
h, err = base58.Decode(txHash)
if err != nil {
return nil, fmt.Errorf("failed to decode from hex txHash=%s: %w", txHash, err)
}
}
// Get transaction signatures for the given account
var sigs []solanaTransactionSignature
{
err = client.CallContext(ctx, rateLimiter, &sigs, "getSignaturesForAddress", base58.Encode(h))
if err != nil {
return nil, fmt.Errorf("failed to get signatures for account: %w (%+v)", err, err)
}
if len(sigs) == 0 {
return nil, ErrTransactionNotFound
}
if len(sigs) > 1 {
return nil, fmt.Errorf("expected exactly one signature, but found %d", len(sigs))
}
}
// Fetch the portal token bridge transaction
var response solanaGetTransactionResponse
{
err = client.CallContext(ctx, rateLimiter, &response, "getTransaction", sigs[0].Signature, "jsonParsed")
if err != nil {
return nil, fmt.Errorf("failed to get tx by signature: %w", err)
}
if len(response.Meta.InnerInstructions) == 0 {
return nil, fmt.Errorf("response.Meta.InnerInstructions is empty")
}
if len(response.Meta.InnerInstructions[0].Instructions) == 0 {
return nil, fmt.Errorf("response.Meta.InnerInstructions[0].Instructions is empty")
}
}
// populate the response object
txDetail := TxDetail{
Timestamp: time.Unix(response.BlockTime, 0).UTC(),
NativeTxHash: sigs[0].Signature,
}
// set sender/receiver
for i := range response.Transaction.Message.AccountKeys {
if response.Transaction.Message.AccountKeys[i].Signer {
txDetail.From = response.Transaction.Message.AccountKeys[i].Pubkey
}
}
if txDetail.From == "" {
return nil, fmt.Errorf("failed to find source account")
}
return &txDetail, nil
}