gagliardetto / solana-go

Go SDK library and RPC client for the Solana Blockchain
Apache License 2.0
896 stars 260 forks source link

how to get token owner of a token account #206

Open BillInUK opened 6 months ago

BillInUK commented 6 months ago

I use the following code to get owner of token account, but returned me a program id 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'。

    rpcClient := rpc.New(RpcMainNet)
    tokenMintPubKey := solana.MPK(TokenMainNetAddress)
    tokenAccountPubKey := solana.MustPublicKeyFromBase58("3XRNhFEXkV7itJEJMDBN8iRHBgwhgk83XB5K3s3re3Ew")

    tokenAccountInfo, err := rpcClient.GetAccountInfo(context.Background(), tokenAccountPubKey)
    if err != nil {
        fmt.Printf("get account info error %v\n", err)
    }
    fmt.Printf("get token account owner %v\n", tokenAccountInfo.Value)

how can i get the token owner of this token account? like below: image

theghostmac commented 6 months ago

Here is how you do it @BillInUK:

package main

import (
    "context"
    "fmt"
    "github.com/gagliardetto/solana-go"
    "github.com/gagliardetto/solana-go/rpc"
)

func main() {
    rpcClient := rpc.New(rpc.MainNetBeta_RPC)
    tokenAccountPubKey := solana.MustPublicKeyFromBase58("3XRNhFEXkV7itJEJMDBN8iRHBgwhgk83XB5K3s3re3Ew")

    // Fetch account info
    accountInfo, err := rpcClient.GetAccountInfo(context.Background(), tokenAccountPubKey)
    if err != nil {
        fmt.Printf("Error retrieving account info: %v\n", err)
        return
    }

    owner := accountInfo.Value.Owner

    // Output the owner of the token account
    fmt.Printf("Token account owner: %v\n", owner.String())
    // OUTPUT: Token account owner: TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA

    // Ensure that data is available
    if accountInfo.Value == nil || accountInfo.Value.Data.GetBinary() == nil {
        fmt.Println("Account data is not available")
        return
    }

    fmt.Println("Trying the second method...")

    data := accountInfo.Value.Data.GetBinary()

    // Check if data length is sufficient to contain Mint and Owner public keys
    if len(data) < 64 {
        fmt.Println("Account data is too short to contain required information")
        return
    }

    // Owner public key starts at byte 32 and spans 32 bytes
    ownerPubKey := solana.PublicKeyFromBytes(data[32:64])

    // Output the owner of the token account
    fmt.Printf("Token account owner from metadata: %v\n", ownerPubKey)
    // OUTPUT: Token account owner: GmKsGRytiVoeMZGmBVCWPcUzJGHVqcvzhP5K9cstdr3E
}
BillInUK commented 6 months ago

thanks for your help, i will try it !!!

BillInUK commented 6 months ago

I implemented this functionality by defining my own structure to parse the results of GetAccountInfoWithOpts. The code is as follows:

const (
    SPLTokenProgramId = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
)

type SPLTokenAccountInfo struct {
    IsNative    bool             `json:"isNative"`
    Mint        solana.PublicKey `json:"mint"`
    Owner       solana.PublicKey `json:"owner"`
    State       string           `json:"state"`
    TokenAmount struct {
        Amount      string  `json:"amount"`
        Decimals    int     `json:"decimals"`
        UIAmount    float64 `json:"uiAmount"`
        UIAmountStr string  `json:"uiAmountString"`
    } `json:"tokenAmount"`
}

type SPLTokenAccountData struct {
    TokenAccountInfo SPLTokenAccountInfo `json:"info"`
    Type             string              `json:"type"`
}

type ParsedData struct {
    Parsed  SPLTokenAccountData `json:"parsed"`
    Program string              `json:"program"`
    Space   int                 `json:"space"`
}
func TestGetTokenAccountOwner(t *testing.T) {
    client := rpc.New(DevRpc)

    pubKey := solana.MustPublicKeyFromBase58("EgyMZLY7DrHpYgmQHCrQ8fXJCv7ekfBepxNNizMST9Dh")
    out, err := client.GetAccountInfoWithOpts(
        context.TODO(),
        pubKey,
        &rpc.GetAccountInfoOpts{
            Encoding: solana.EncodingJSONParsed,
        },
    )
    if err != nil {
        panic(err)
    }
    var parsedData ParsedData
    dataBytes, _ := out.Value.Data.MarshalJSON()
    fmt.Println(string(dataBytes))

    err = json.Unmarshal(dataBytes, &parsedData)
    if err != nil {
        fmt.Println("Error parsing JSON:", err)
        return
    }
    // Access parsed data
    fmt.Println("Type:", parsedData.Parsed.Type)
    fmt.Println("Program:", parsedData.Program)
    fmt.Println("Space:", parsedData.Space)
    fmt.Println("IsNative:", parsedData.Parsed.TokenAccountInfo.IsNative)
    fmt.Println("Mint:", parsedData.Parsed.TokenAccountInfo.Mint)
    fmt.Println("Owner:", parsedData.Parsed.TokenAccountInfo.Owner)
    fmt.Println("State:", parsedData.Parsed.TokenAccountInfo.State)
    fmt.Println("Amount:", parsedData.Parsed.TokenAccountInfo.TokenAmount.Amount)
    fmt.Println("Decimals:", parsedData.Parsed.TokenAccountInfo.TokenAmount.Decimals)
    fmt.Println("UIAmount:", parsedData.Parsed.TokenAccountInfo.TokenAmount.UIAmount)
    fmt.Println("UIAmountString:", parsedData.Parsed.TokenAccountInfo.TokenAmount.UIAmountStr)
}
BillInUK commented 6 months ago

Overall, the code you provided is simpler, avoiding unnecessary struct definitions and JSON parsing.