github.com/gagliardetto/solana-go@v1.11.0/rpc/getTransaction.go (about)

     1  // Copyright 2021 github.com/gagliardetto
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package rpc
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  
    21  	bin "github.com/gagliardetto/binary"
    22  	"github.com/gagliardetto/solana-go"
    23  )
    24  
    25  type GetTransactionOpts struct {
    26  	Encoding solana.EncodingType `json:"encoding,omitempty"`
    27  
    28  	// Desired commitment. "processed" is not supported. If parameter not provided, the default is "finalized".
    29  	Commitment CommitmentType `json:"commitment,omitempty"`
    30  
    31  	// Max transaction version to return in responses.
    32  	// If the requested block contains a transaction with a higher version, an error will be returned.
    33  	MaxSupportedTransactionVersion *uint64
    34  }
    35  
    36  // GetTransaction returns transaction details for a confirmed transaction.
    37  //
    38  // NEW: This method is only available in solana-core v1.7 or newer.
    39  // Please use `getConfirmedTransaction` for solana-core v1.6
    40  func (cl *Client) GetTransaction(
    41  	ctx context.Context,
    42  	txSig solana.Signature, // transaction signature
    43  	opts *GetTransactionOpts,
    44  ) (out *GetTransactionResult, err error) {
    45  	params := []interface{}{txSig}
    46  	if opts != nil {
    47  		obj := M{}
    48  		if opts.Encoding != "" {
    49  			if !solana.IsAnyOfEncodingType(
    50  				opts.Encoding,
    51  				// Valid encodings:
    52  				// solana.EncodingJSON, // TODO
    53  				// solana.EncodingJSONParsed, // TODO
    54  				solana.EncodingBase58,
    55  				solana.EncodingBase64,
    56  				solana.EncodingBase64Zstd,
    57  			) {
    58  				return nil, fmt.Errorf("provided encoding is not supported: %s", opts.Encoding)
    59  			}
    60  			obj["encoding"] = opts.Encoding
    61  		}
    62  		if opts.Commitment != "" {
    63  			obj["commitment"] = opts.Commitment
    64  		}
    65  		if opts.MaxSupportedTransactionVersion != nil {
    66  			obj["maxSupportedTransactionVersion"] = *opts.MaxSupportedTransactionVersion
    67  		}
    68  		if len(obj) > 0 {
    69  			params = append(params, obj)
    70  		}
    71  	}
    72  	err = cl.rpcClient.CallForInto(ctx, &out, "getTransaction", params)
    73  	if err != nil {
    74  		return nil, err
    75  	}
    76  	if out == nil {
    77  		return nil, ErrNotFound
    78  	}
    79  	return
    80  }
    81  
    82  type GetTransactionResult struct {
    83  	// The slot this transaction was processed in.
    84  	Slot uint64 `json:"slot"`
    85  
    86  	// Estimated production time, as Unix timestamp (seconds since the Unix epoch)
    87  	// of when the transaction was processed.
    88  	// Nil if not available.
    89  	BlockTime *solana.UnixTimeSeconds `json:"blockTime" bin:"optional"`
    90  
    91  	Transaction *TransactionResultEnvelope `json:"transaction" bin:"optional"`
    92  	Meta        *TransactionMeta           `json:"meta,omitempty" bin:"optional"`
    93  	Version     TransactionVersion         `json:"version"`
    94  }
    95  
    96  // TransactionResultEnvelope will contain a *solana.Transaction if the requested encoding is `solana.EncodingJSON`
    97  // (which is also the default when the encoding is not specified),
    98  // or a `solana.Data` in case of EncodingBase58, EncodingBase64.
    99  type TransactionResultEnvelope struct {
   100  	asDecodedBinary     solana.Data
   101  	asParsedTransaction *solana.Transaction
   102  }
   103  
   104  func (wrap TransactionResultEnvelope) MarshalJSON() ([]byte, error) {
   105  	if wrap.asParsedTransaction != nil {
   106  		return json.Marshal(wrap.asParsedTransaction)
   107  	}
   108  	return json.Marshal(wrap.asDecodedBinary)
   109  }
   110  
   111  func (wrap *TransactionResultEnvelope) UnmarshalJSON(data []byte) error {
   112  	if len(data) == 0 || (len(data) == 4 && string(data) == "null") {
   113  		// TODO: is this an error?
   114  		return nil
   115  	}
   116  
   117  	firstChar := data[0]
   118  
   119  	switch firstChar {
   120  	// Check if first character is `[`, standing for a JSON array.
   121  	case '[':
   122  		// It's base64 (or similar)
   123  		{
   124  			err := wrap.asDecodedBinary.UnmarshalJSON(data)
   125  			if err != nil {
   126  				return err
   127  			}
   128  		}
   129  	case '{':
   130  		// It's JSON, most likely.
   131  		{
   132  			return json.Unmarshal(data, &wrap.asParsedTransaction)
   133  		}
   134  	default:
   135  		return fmt.Errorf("Unknown kind: %v", data)
   136  	}
   137  
   138  	return nil
   139  }
   140  
   141  // GetBinary returns the decoded bytes if the encoding is
   142  // "base58", "base64".
   143  func (dt *TransactionResultEnvelope) GetBinary() []byte {
   144  	return dt.asDecodedBinary.Content
   145  }
   146  
   147  func (dt *TransactionResultEnvelope) GetData() solana.Data {
   148  	return dt.asDecodedBinary
   149  }
   150  
   151  // GetRawJSON returns a *solana.Transaction when the data
   152  // encoding is EncodingJSON.
   153  func (dt *TransactionResultEnvelope) GetTransaction() (*solana.Transaction, error) {
   154  	if dt.asDecodedBinary.Content != nil {
   155  		tx := new(solana.Transaction)
   156  		err := tx.UnmarshalWithDecoder(bin.NewBinDecoder(dt.asDecodedBinary.Content))
   157  		if err != nil {
   158  			return nil, err
   159  		}
   160  		return tx, nil
   161  	}
   162  	return dt.asParsedTransaction, nil
   163  }
   164  
   165  func (obj TransactionResultEnvelope) MarshalWithEncoder(encoder *bin.Encoder) (err error) {
   166  	return encoder.Encode(obj.asDecodedBinary)
   167  }
   168  
   169  func (obj *TransactionResultEnvelope) UnmarshalWithDecoder(decoder *bin.Decoder) (err error) {
   170  	return decoder.Decode(&obj.asDecodedBinary)
   171  }
   172  
   173  func (obj GetTransactionResult) MarshalWithEncoder(encoder *bin.Encoder) (err error) {
   174  	err = encoder.WriteUint64(obj.Slot, bin.LE)
   175  	if err != nil {
   176  		return err
   177  	}
   178  	{
   179  		if obj.BlockTime == nil {
   180  			err = encoder.WriteBool(false)
   181  			if err != nil {
   182  				return err
   183  			}
   184  		} else {
   185  			err = encoder.WriteBool(true)
   186  			if err != nil {
   187  				return err
   188  			}
   189  			err = encoder.WriteInt64(int64(*obj.BlockTime), bin.LE)
   190  			if err != nil {
   191  				return err
   192  			}
   193  		}
   194  	}
   195  	{
   196  		if obj.Transaction == nil {
   197  			err = encoder.WriteBool(false)
   198  			if err != nil {
   199  				return err
   200  			}
   201  		} else {
   202  			err = encoder.WriteBool(true)
   203  			if err != nil {
   204  				return err
   205  			}
   206  			err = obj.Transaction.MarshalWithEncoder(encoder)
   207  			if err != nil {
   208  				return err
   209  			}
   210  		}
   211  	}
   212  	{
   213  		if obj.Meta == nil {
   214  			err = encoder.WriteBool(false)
   215  			if err != nil {
   216  				return err
   217  			}
   218  		} else {
   219  			err = encoder.WriteBool(true)
   220  			if err != nil {
   221  				return err
   222  			}
   223  			// NOTE: storing as JSON bytes:
   224  			buf, err := json.Marshal(obj.Meta)
   225  			if err != nil {
   226  				return err
   227  			}
   228  			err = encoder.WriteBytes(buf, true)
   229  			if err != nil {
   230  				return err
   231  			}
   232  		}
   233  	}
   234  	return nil
   235  }
   236  
   237  func (obj *GetTransactionResult) UnmarshalWithDecoder(decoder *bin.Decoder) (err error) {
   238  	// Deserialize `Slot`:
   239  	obj.Slot, err = decoder.ReadUint64(bin.LE)
   240  	if err != nil {
   241  		return err
   242  	}
   243  	// Deserialize `BlockTime` (optional):
   244  	{
   245  		ok, err := decoder.ReadBool()
   246  		if err != nil {
   247  			return err
   248  		}
   249  		if ok {
   250  			err = decoder.Decode(&obj.BlockTime)
   251  			if err != nil {
   252  				return err
   253  			}
   254  		}
   255  	}
   256  	{
   257  		ok, err := decoder.ReadBool()
   258  		if err != nil {
   259  			return err
   260  		}
   261  		if ok {
   262  			obj.Transaction = new(TransactionResultEnvelope)
   263  			err = obj.Transaction.UnmarshalWithDecoder(decoder)
   264  			if err != nil {
   265  				return err
   266  			}
   267  		}
   268  	}
   269  	{
   270  		ok, err := decoder.ReadBool()
   271  		if err != nil {
   272  			return err
   273  		}
   274  		if ok {
   275  			// NOTE: storing as JSON bytes:
   276  			buf, err := decoder.ReadByteSlice()
   277  			if err != nil {
   278  				return err
   279  			}
   280  			err = json.Unmarshal(buf, &obj.Meta)
   281  			if err != nil {
   282  				return err
   283  			}
   284  		}
   285  	}
   286  	return nil
   287  }