2023-07-04 11:25:08 -07:00
|
|
|
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"`
|
|
|
|
}
|
|
|
|
|
2023-12-01 09:15:19 -08:00
|
|
|
type getTransactionConfig struct {
|
|
|
|
Encoding string `json:"encoding"`
|
|
|
|
MaxSupportedTransactionVersion int `json:"maxSupportedTransactionVersion"`
|
|
|
|
}
|
|
|
|
|
2023-07-04 11:25:08 -07:00
|
|
|
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
|
|
|
|
{
|
2023-12-01 09:15:19 -08:00
|
|
|
err = client.CallContext(ctx, rateLimiter, &response, "getTransaction", sigs[0].Signature,
|
|
|
|
getTransactionConfig{
|
|
|
|
Encoding: "jsonParsed",
|
|
|
|
MaxSupportedTransactionVersion: 0,
|
|
|
|
})
|
2023-07-04 11:25:08 -07:00
|
|
|
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{
|
|
|
|
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
|
|
|
|
}
|