Skip to content

Commit 9e538c8

Browse files
authored
feat(rpc): add getTransactionsForAddress client method (closes #343) (#450)
* feat(rpc): add getTransactionsForAddress client method Add GetTransactionsForAddress / GetTransactionsForAddressWithOpts, which return confirmed transactions involving an address. The method mirrors the existing GetSignaturesForAddress shape and supports the full config surface: transactionDetails (signatures/full), sortOrder, limit, paginationToken cursor, commitment, encoding, maxSupportedTransactionVersion, minContextSlot and server-side filters (slot/blockTime/signature ranges, status, token-account and token-transfer constraints). The response is typed as GetTransactionsForAddressResult, exposing the per-transaction rows and the opaque paginationToken for paging. Both the signatures and full detail levels are decoded; full transactions reuse the DataBytesOrJSON envelope so the requested encoding is honored. getTransactionsForAddress is a provider extension (e.g. Helius) rather than a core Solana RPC method; this is documented on the method. Closes #343 * fix(rpc): accept base64+zstd and type the filter enums in getTransactionsForAddress Address review feedback on #450: - add solana.EncodingBase64Zstd to the encoding allowlist; every comparable method (getBlock, getTransaction, ws/blockSubscribe) already accepts it, so rejecting it here failed valid requests client-side - give the Status, TokenAccounts and TokenTransfer.Direction filter fields typed string aliases (TransactionStatus, TokenAccountsFilter, TokenTransferDirection) with documented constants, matching the SortOrder/TransactionDetailsType pattern already used in this file - clarify in the godoc that the endpoint is a multi-vendor extension (Helius, Triton, FluxRPC), not provider-specific - tests: base64+zstd is accepted and forwarded in the request
1 parent 725147a commit 9e538c8

2 files changed

Lines changed: 509 additions & 0 deletions

File tree

rpc/getTransactionsForAddress.go

Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
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+
"github.com/gagliardetto/solana-go"
22+
)
23+
24+
// TransactionsForAddressSortOrder controls the order in which
25+
// getTransactionsForAddress returns results.
26+
type TransactionsForAddressSortOrder string
27+
28+
const (
29+
// TransactionsForAddressSortDesc returns the newest transactions first
30+
// (the default when no sort order is provided).
31+
TransactionsForAddressSortDesc TransactionsForAddressSortOrder = "desc"
32+
// TransactionsForAddressSortAsc returns the oldest transactions first.
33+
TransactionsForAddressSortAsc TransactionsForAddressSortOrder = "asc"
34+
)
35+
36+
type GetTransactionsForAddressOpts struct {
37+
// Level of transaction detail to return: "signatures" (default) or "full".
38+
TransactionDetails TransactionDetailsType `json:"transactionDetails,omitempty"`
39+
40+
// Order of the returned transactions: "desc" (newest first, the default)
41+
// or "asc" (oldest first).
42+
SortOrder TransactionsForAddressSortOrder `json:"sortOrder,omitempty"`
43+
44+
// (optional) Maximum number of transactions to return (between 1 and 1,000,
45+
// default: 1,000).
46+
Limit *int `json:"limit,omitempty"`
47+
48+
// (optional) Cursor returned as PaginationToken by a previous response,
49+
// in the "slot:position" format, used to fetch the next page.
50+
PaginationToken string `json:"paginationToken,omitempty"`
51+
52+
// (optional) Commitment; "processed" is not supported.
53+
// If not provided, the default is "finalized".
54+
Commitment CommitmentType `json:"commitment,omitempty"`
55+
56+
// (optional) Encoding for the returned transactions when TransactionDetails
57+
// is "full": "json", "jsonParsed", "base58" (slow), or "base64".
58+
Encoding solana.EncodingType `json:"encoding,omitempty"`
59+
60+
// (optional) The max transaction version to return in responses.
61+
// Set to 0 to return all (legacy and versioned) transactions; if a returned
62+
// block contains a transaction with a higher version, an error is returned.
63+
MaxSupportedTransactionVersion *uint64 `json:"maxSupportedTransactionVersion,omitempty"`
64+
65+
// (optional) The minimum slot that the request can be evaluated at.
66+
MinContextSlot *uint64 `json:"minContextSlot,omitempty"`
67+
68+
// (optional) Server-side filters narrowing the returned transactions.
69+
Filters *TransactionsForAddressFilters `json:"filters,omitempty"`
70+
}
71+
72+
// TransactionStatus filters returned transactions by execution status.
73+
type TransactionStatus string
74+
75+
const (
76+
// TransactionStatusSucceeded matches transactions that executed successfully.
77+
TransactionStatusSucceeded TransactionStatus = "succeeded"
78+
// TransactionStatusFailed matches transactions that failed on chain.
79+
TransactionStatusFailed TransactionStatus = "failed"
80+
// TransactionStatusAny matches transactions regardless of status (the default).
81+
TransactionStatusAny TransactionStatus = "any"
82+
)
83+
84+
// TokenAccountsFilter narrows results by the queried address's token-account
85+
// activity within each transaction.
86+
type TokenAccountsFilter string
87+
88+
const (
89+
// TokenAccountsNone matches transactions with no token-account activity.
90+
TokenAccountsNone TokenAccountsFilter = "none"
91+
// TokenAccountsBalanceChanged matches transactions where a token balance changed.
92+
TokenAccountsBalanceChanged TokenAccountsFilter = "balanceChanged"
93+
// TokenAccountsAll matches transactions with any token-account activity.
94+
TokenAccountsAll TokenAccountsFilter = "all"
95+
)
96+
97+
// TokenTransferDirection constrains a token-transfer filter to a direction
98+
// relative to the queried address.
99+
type TokenTransferDirection string
100+
101+
const (
102+
// TokenTransferIn matches transfers into the queried address.
103+
TokenTransferIn TokenTransferDirection = "in"
104+
// TokenTransferOut matches transfers out of the queried address.
105+
TokenTransferOut TokenTransferDirection = "out"
106+
// TokenTransferAny matches transfers in either direction (the default).
107+
TokenTransferAny TokenTransferDirection = "any"
108+
)
109+
110+
// TransactionsForAddressFilters narrows the result set of
111+
// getTransactionsForAddress on the server side.
112+
type TransactionsForAddressFilters struct {
113+
// Slot-number range comparisons.
114+
Slot *RangeFilterUint64 `json:"slot,omitempty"`
115+
116+
// Block-time (Unix timestamp) range comparisons.
117+
BlockTime *RangeFilterInt64 `json:"blockTime,omitempty"`
118+
119+
// Signature-string range comparisons.
120+
Signature *RangeFilterString `json:"signature,omitempty"`
121+
122+
// Execution status: "succeeded", "failed", or "any".
123+
Status TransactionStatus `json:"status,omitempty"`
124+
125+
// Token-account activity: "none", "balanceChanged", or "all".
126+
TokenAccounts TokenAccountsFilter `json:"tokenAccounts,omitempty"`
127+
128+
// Token-transfer constraints.
129+
TokenTransfer *TokenTransferFilter `json:"tokenTransfer,omitempty"`
130+
}
131+
132+
// RangeFilterUint64 expresses greater/less-than(-or-equal) bounds on a uint64.
133+
type RangeFilterUint64 struct {
134+
Gte *uint64 `json:"gte,omitempty"`
135+
Gt *uint64 `json:"gt,omitempty"`
136+
Lte *uint64 `json:"lte,omitempty"`
137+
Lt *uint64 `json:"lt,omitempty"`
138+
}
139+
140+
// RangeFilterInt64 expresses greater/less-than(-or-equal) and equality bounds
141+
// on an int64 (used for Unix timestamps).
142+
type RangeFilterInt64 struct {
143+
Gte *int64 `json:"gte,omitempty"`
144+
Gt *int64 `json:"gt,omitempty"`
145+
Lte *int64 `json:"lte,omitempty"`
146+
Lt *int64 `json:"lt,omitempty"`
147+
Eq *int64 `json:"eq,omitempty"`
148+
}
149+
150+
// RangeFilterString expresses greater/less-than(-or-equal) bounds on a string.
151+
type RangeFilterString struct {
152+
Gte string `json:"gte,omitempty"`
153+
Gt string `json:"gt,omitempty"`
154+
Lte string `json:"lte,omitempty"`
155+
Lt string `json:"lt,omitempty"`
156+
}
157+
158+
// TokenTransferFilter constrains results to transactions matching a token
159+
// transfer against a counterparty, mint, direction and/or amount.
160+
type TokenTransferFilter struct {
161+
// Counterparty address.
162+
With string `json:"with,omitempty"`
163+
// Transfer direction relative to the queried address: "in", "out", or "any".
164+
Direction TokenTransferDirection `json:"direction,omitempty"`
165+
// Token mint address.
166+
Mint string `json:"mint,omitempty"`
167+
// Amount range comparisons.
168+
Amount *RangeFilterUint64 `json:"amount,omitempty"`
169+
}
170+
171+
// GetTransactionsForAddressResult is the response of getTransactionsForAddress.
172+
type GetTransactionsForAddressResult struct {
173+
// The transactions matching the query, in the requested sort order.
174+
Data []*TransactionForAddress `json:"data"`
175+
176+
// Opaque cursor ("slot:position") for the next page, or nil when there are
177+
// no more results. Pass it back via GetTransactionsForAddressOpts.PaginationToken.
178+
PaginationToken *string `json:"paginationToken"`
179+
}
180+
181+
// TransactionForAddress is a single entry of a getTransactionsForAddress response.
182+
// Which fields are populated depends on the requested TransactionDetails level.
183+
type TransactionForAddress struct {
184+
// The slot that contains the block with the transaction.
185+
Slot uint64 `json:"slot"`
186+
187+
// The transaction's index within the block. This field is specific to
188+
// getTransactionsForAddress.
189+
TransactionIndex uint64 `json:"transactionIndex"`
190+
191+
// Estimated production time, as Unix timestamp (seconds since the Unix epoch)
192+
// of when the transaction was processed. Nil if not available.
193+
BlockTime *solana.UnixTimeSeconds `json:"blockTime"`
194+
195+
// Fields below are present when TransactionDetails is "signatures" (default):
196+
197+
// Transaction signature.
198+
Signature solana.Signature `json:"signature,omitempty"`
199+
200+
// Error if the transaction failed, nil if it succeeded.
201+
Err any `json:"err,omitempty"`
202+
203+
// Memo associated with the transaction, nil if none.
204+
Memo *string `json:"memo,omitempty"`
205+
206+
// The transaction's cluster confirmation status.
207+
ConfirmationStatus ConfirmationStatusType `json:"confirmationStatus,omitempty"`
208+
209+
// Fields below are present when TransactionDetails is "full":
210+
211+
// The decoded transaction, honoring the requested Encoding.
212+
Transaction *DataBytesOrJSON `json:"transaction,omitempty"`
213+
214+
// Transaction status metadata object.
215+
Meta *TransactionMeta `json:"meta,omitempty"`
216+
}
217+
218+
// GetTransactionsForAddress returns confirmed transactions that involve the
219+
// given address, newest first.
220+
//
221+
// NOTE: getTransactionsForAddress is not part of the core Solana JSON-RPC API;
222+
// it is a vendor extension offered by several RPC providers (e.g. Helius,
223+
// Triton, FluxRPC). Calls will fail against endpoints that do not implement it.
224+
func (cl *Client) GetTransactionsForAddress(
225+
ctx context.Context,
226+
account solana.PublicKey,
227+
) (out *GetTransactionsForAddressResult, err error) {
228+
return cl.GetTransactionsForAddressWithOpts(
229+
ctx,
230+
account,
231+
nil,
232+
)
233+
}
234+
235+
// GetTransactionsForAddressWithOpts returns confirmed transactions that involve
236+
// the given address, with control over detail level, ordering, paging and
237+
// server-side filters.
238+
//
239+
// NOTE: getTransactionsForAddress is not part of the core Solana JSON-RPC API;
240+
// it is a vendor extension offered by several RPC providers (e.g. Helius,
241+
// Triton, FluxRPC). Calls will fail against endpoints that do not implement it.
242+
func (cl *Client) GetTransactionsForAddressWithOpts(
243+
ctx context.Context,
244+
account solana.PublicKey,
245+
opts *GetTransactionsForAddressOpts,
246+
) (out *GetTransactionsForAddressResult, err error) {
247+
params := []any{account}
248+
if opts != nil {
249+
obj := M{}
250+
if opts.TransactionDetails != "" {
251+
obj["transactionDetails"] = opts.TransactionDetails
252+
}
253+
if opts.SortOrder != "" {
254+
obj["sortOrder"] = opts.SortOrder
255+
}
256+
if opts.Limit != nil {
257+
obj["limit"] = opts.Limit
258+
}
259+
if opts.PaginationToken != "" {
260+
obj["paginationToken"] = opts.PaginationToken
261+
}
262+
if opts.Commitment != "" {
263+
obj["commitment"] = opts.Commitment
264+
}
265+
if opts.Encoding != "" {
266+
if !solana.IsAnyOfEncodingType(
267+
opts.Encoding,
268+
// Valid encodings:
269+
solana.EncodingJSON,
270+
solana.EncodingJSONParsed,
271+
solana.EncodingBase58,
272+
solana.EncodingBase64,
273+
solana.EncodingBase64Zstd,
274+
) {
275+
return nil, fmt.Errorf("provided encoding is not supported: %s", opts.Encoding)
276+
}
277+
obj["encoding"] = opts.Encoding
278+
}
279+
if opts.MaxSupportedTransactionVersion != nil {
280+
obj["maxSupportedTransactionVersion"] = *opts.MaxSupportedTransactionVersion
281+
}
282+
if opts.MinContextSlot != nil {
283+
obj["minContextSlot"] = *opts.MinContextSlot
284+
}
285+
if opts.Filters != nil {
286+
obj["filters"] = opts.Filters
287+
}
288+
if len(obj) > 0 {
289+
params = append(params, obj)
290+
}
291+
}
292+
293+
err = cl.rpcClient.CallForInto(ctx, &out, "getTransactionsForAddress", params)
294+
return
295+
}

0 commit comments

Comments
 (0)