Documentation
¶
Overview ¶
Package taphubserver is the Go SDK for the TapHub agency API.
It mirrors the @taphubhq/sdk-server TypeScript package in wire format, error taxonomy, and retry semantics. Server-only; do not import from client-side code that ships an HMAC secret to untrusted runtimes.
Index ¶
- Constants
- Variables
- func WrongRegionTarget(err error) (string, bool)
- type AgencyBidsInput
- type AgencyBidsResult
- type AuthModule
- type AuthResponse
- type AuthUser
- type Bid
- type BuilderCurrencyBalance
- type BuilderLeaderboard
- type BuilderLeaderboardInput
- type BuilderModule
- func (m *BuilderModule) GetAgencyBids(ctx context.Context, input AgencyBidsInput, opts ...CallOption) (AgencyBidsResult, error)
- func (m *BuilderModule) GetBuilderLeaderboard(ctx context.Context, input BuilderLeaderboardInput, opts ...CallOption) (*BuilderLeaderboard, error)
- func (m *BuilderModule) GetBuilderStats(ctx context.Context, input BuilderStatsInput, opts ...CallOption) (*BuilderStats, error)
- func (m *BuilderModule) GetBuilderUserBalances(ctx context.Context, input BuilderUserBalancesInput, opts ...CallOption) (*BuilderUserBalances, error)
- func (m *BuilderModule) GetBuilderUserClosedBids(ctx context.Context, input BuilderUserClosedBidsInput, opts ...CallOption) ([]Bid, error)
- func (m *BuilderModule) GetBuilderUserKpis(ctx context.Context, input BuilderUserKpisInput, opts ...CallOption) (*BuilderUserKpis, error)
- func (m *BuilderModule) GetBuilderUserOpenBids(ctx context.Context, input BuilderUserOpenBidsInput, opts ...CallOption) ([]Bid, error)
- func (m *BuilderModule) GetBuilderUserTransactions(ctx context.Context, input BuilderUserTransactionsInput, opts ...CallOption) ([]WalletTransaction, error)
- func (m *BuilderModule) GetBuilderUsersStats(ctx context.Context, input BuilderUsersStatsInput, opts ...CallOption) ([]RankEntry, error)
- func (m *BuilderModule) GetBuilderWalletStats(ctx context.Context, input BuilderWalletStatsInput, opts ...CallOption) (*BuilderWalletStats, error)
- func (m *BuilderModule) GetMyRank(ctx context.Context, input MyRankInput, opts ...CallOption) (*MyRank, error)
- func (m *BuilderModule) GetRetentionKpiCard(ctx context.Context, input RetentionKpiCardInput, opts ...CallOption) (*RetentionKpiCard, error)
- func (m *BuilderModule) GetRetentionTrend(ctx context.Context, input RetentionTrendInput, opts ...CallOption) (*RetentionTrend, error)
- type BuilderStats
- type BuilderStatsInput
- type BuilderUserBalances
- type BuilderUserBalancesInput
- type BuilderUserClosedBidsInput
- type BuilderUserKpis
- type BuilderUserKpisInput
- type BuilderUserOpenBidsInput
- type BuilderUserTransactionsInput
- type BuilderUsersStatsInput
- type BuilderWalletStats
- type BuilderWalletStatsInput
- type CallOption
- type Client
- type Config
- type Direction
- type Error
- type ErrorKind
- type LoginS2SInput
- type MyRank
- type MyRankInput
- type RankEntry
- type RankPeriod
- type RankSort
- type RankTimeRange
- type RetentionCohortRow
- type RetentionGranularity
- type RetentionKpiCard
- type RetentionKpiCardInput
- type RetentionPeriod
- type RetentionProductType
- type RetentionTrend
- type RetentionTrendInput
- type RetryConfig
- type RetryInfo
- type SortDir
- type TimeRange
- type UserCreateParams
- type UserFundingSummary
- type UserFundingSummaryParams
- type UserModule
- type Wallet
- type WalletModule
- func (m *WalletModule) Cancel(ctx context.Context, ref string, opts ...CallOption) (*WalletReservation, error)
- func (m *WalletModule) Commit(ctx context.Context, ref string, opts ...CallOption) (*WalletReservation, error)
- func (m *WalletModule) GetTxByRef(ctx context.Context, ref string, opts ...CallOption) (*WalletTransaction, error)
- func (m *WalletModule) GetUserFundingSummary(ctx context.Context, p UserFundingSummaryParams, opts ...CallOption) (*UserFundingSummary, error)
- func (m *WalletModule) GetUserWallet(ctx context.Context, agencyUID string, opts ...CallOption) (*Wallet, error)
- func (m *WalletModule) ListUserWallets(ctx context.Context, agencyUID string, opts ...CallOption) ([]Wallet, error)
- func (m *WalletModule) Reserve(ctx context.Context, p WalletReserveParams, opts ...CallOption) (*WalletReservation, error)
- func (m *WalletModule) Transfer(ctx context.Context, p WalletTransferParams, opts ...CallOption) (*WalletTransferResult, error)
- type WalletReservation
- type WalletReserveParams
- type WalletTransaction
- type WalletTransferParams
- type WalletTransferResult
Constants ¶
const ( // CodeAuthWrongRegion means the user is pinned to a DIFFERENT region than // the endpoint that was called. Meta["homeRegion"] names the right one: // retry the login ONCE there (never a loop — pinning is write-once, so a // second rejection means a real bug, not a race) and persist the corrected // region so the next login routes straight there. CodeAuthWrongRegion = "Auth_WrongRegion" // unreachable and the backend failed closed. Transient: classified // KindInternal so the SDK's own backoff retries it. CodeAuthRegionUnavailable = "Auth_RegionUnavailable" // sourced in that region — usually a seeding gap on the TapHub side. // Retrying cannot fix it; contact TapHub if it persists. CodeAuthAgencyConfigUnavailable = "Auth_AgencyConfigUnavailable" )
Multi-region error codes (extensions.code) from taphub-user-service's login path. See docs/sdk/multi-region-integration.md for the builder obligations each one implies.
Variables ¶
var ( ErrValidation = &Error{Kind: KindValidation} ErrAuth = &Error{Kind: KindAuth} ErrNotFound = &Error{Kind: KindNotFound} ErrConflict = &Error{Kind: KindConflict} ErrRateLimit = &Error{Kind: KindRateLimit} ErrInternal = &Error{Kind: KindInternal} ErrNetwork = &Error{Kind: KindNetwork} )
Sentinel values for use with errors.Is. The fields other than Kind are intentionally zero; comparing by Kind is the documented predicate.
Functions ¶
func WrongRegionTarget ¶ added in v0.11.0
WrongRegionTarget reports the region a login must be retried at after an Auth_WrongRegion rejection, implementing obligation 3 of the multi-region contract:
resp, err := client.Auth.LoginS2S(ctx, in)
if region, ok := taphubserver.WrongRegionTarget(err); ok {
resp, err = clientFor(region).Auth.LoginS2S(ctx, in) // ONCE, not a loop
}
Returns ok=false for a nil error, a non-*Error, any other code, or an Auth_WrongRegion whose meta carries no usable region — a caller that cannot learn the target must surface the original error rather than guess.
Types ¶
type AgencyBidsInput ¶ added in v0.11.0
type AgencyBidsInput struct {
GameID string
AgencyUID string
UserID string
Status string
Demo *bool // nil → no demo filter; true → demo bids only; false → real bids only
Range *TimeRange // optional created_at filter; both bounds required, From<=To (validated)
Limit int
Offset int
}
AgencyBidsInput drives GetAgencyBids. All fields are optional: a zero-value input asks grid-api for the authenticated agency's default bid list. GameID accepts grid-api's gameId filter; Status is the backend string filter (pending, won, lost, cancelled). AgencyUID filters to one builder user by the builder's own user id (reverse-mapped server-side); it is mutually exclusive with UserID. From/To filter by bid placement time (created_at) and must be supplied TOGETHER (both or neither), with From <= To — GetAgencyBids validates this before the call. Demo filters by the bid's demo flag: nil → no filter (mixed list), true → demo bids only, false → real bids only.
type AgencyBidsResult ¶ added in v0.11.0
AgencyBidsResult is one page of agency bids. Items is the current page (created_at DESC); Total is the full count matching the filter (independent of Limit/Offset); Limit/Offset are the applied (clamped) paging values.
type AuthModule ¶ added in v0.2.0
type AuthModule struct {
// contains filtered or unexported fields
}
AuthModule groups identity operations under Client.Auth.
func (*AuthModule) LoginS2S ¶ added in v0.2.0
func (m *AuthModule) LoginS2S( ctx context.Context, input LoginS2SInput, opts ...CallOption, ) (*AuthResponse, error)
LoginS2S is the server-to-server login. The caller authenticates via the agency HMAC secret (signed over the GraphQL request body) and receives an access token keyed by the supplied partnerUserId. apikey is threaded into the input variables so the backend resolver picks up the same value that signed the request — callers do not pass it directly.
func (*AuthModule) UserLogin ¶ added in v0.2.0
func (m *AuthModule) UserLogin( ctx context.Context, sessionToken string, opts ...CallOption, ) (*AuthResponse, error)
UserLogin exchanges a Privy session token (issued to a builder frontend) for an agency-scoped access token. Plain GraphQL: no HMAC, because the caller has nothing else to prove identity with at this point.
type AuthResponse ¶ added in v0.2.0
type AuthResponse struct {
AccessToken string `json:"accessToken"`
User AuthUser `json:"user"`
// HomeRegion is the region this user is PINNED to — where their user row
// and wallet physically live (multi-geo, docs/sdk/multi-region-integration.md).
// Empty when the backend returns null: the region-directory is unconfigured
// (feature off) or this is a demo/refresh path.
//
// A builder that runs against more than one region MUST persist this per
// user and route that user's subsequent S2S calls to the matching
// endpoint. Reading it without persisting it re-pins by luck on every
// login.
HomeRegion string `json:"homeRegion,omitempty"`
}
AuthResponse is the result of UserLogin / LoginS2S: a bearer access token and the user it represents. Callers attach AccessToken to subsequent downstream calls (out of SDK scope).
The struct is value-comparable; keep it that way (no pointer or map fields) so callers can assert on it with ==.
type AuthUser ¶ added in v0.2.0
type AuthUser struct {
ID string `json:"id"`
Name string `json:"name"`
Currency string `json:"currency"`
}
AuthUser is the lightweight user shape returned alongside an access token. Matches the TS SDK's AuthUser. The struct tolerates unknown fields so a backend addition does not break consumers.
type Bid ¶ added in v0.9.0
type Bid struct {
ID string
Ref string // wire tx_ref
AgencyID string
UserID string
PairID string
Currency string
Amount string
Coefficient string
Time1 int64
Time2 int64
Price1 string
Price2 string
Status string
Charged *bool
ChargedTxID *string
Win *bool
Rewarded *bool
Payout *string
RewardTxID *string
Cancelled *bool
CancelledAt *string
RefundRate *string
RefundAmount *string
Demo bool
Slippage int
CreatedAt string
UpdatedAt *string
Meta *string
RelativeLoc *string
// AgencyUID + UserName are populated ONLY by GetAgencyBids (agencyBids) — the builder's
// own user id and the user's LIVE display name. Nil for the per-user bid reads.
AgencyUID *string
UserName *string
}
Bid is a single bid as returned by GetBuilderUserOpenBids and GetBuilderUserClosedBids. Decimal fields are strings; Time1/Time2 are unix seconds; CreatedAt/UpdatedAt/CancelledAt are RFC3339. Closed bids populate outcome fields (Win, Rewarded, Payout, Cancelled, etc.); open bids leave them at their zero value.
type BuilderCurrencyBalance ¶ added in v0.11.0
type BuilderCurrencyBalance struct {
Currency string `json:"currency"`
// TotalNow is what the agency's users hold right now, INCLUDING funds
// reserved for in-flight bids. It is "what users hold", not "what they can
// spend this second": money held against an open bid is still theirs.
TotalNow string `json:"totalNow"`
// TotalAsOf is the same total as it stood at AsOfTs. Because both figures
// treat reserved funds identically, TotalNow - TotalAsOf reflects real money
// movement rather than how many bids happen to be open at call time.
TotalAsOf string `json:"totalAsOf"`
// WalletCount is how many wallets contribute to TotalNow.
WalletCount int `json:"walletCount"`
}
BuilderCurrencyBalance is one currency's slice of the agency's user balance. The Decimal fields are strings to preserve precision — parse them with a decimal library, never into float64.
type BuilderLeaderboard ¶ added in v0.5.0
type BuilderLeaderboard struct {
Period RankPeriod `json:"period"`
Range RankTimeRange `json:"range"`
Sort RankSort `json:"sort"`
SortDir SortDir `json:"sortDir"`
Total int `json:"total"`
Entries []RankEntry `json:"entries"`
}
BuilderLeaderboard is the result of GetBuilderLeaderboard. It is the public board: identical for every caller of the agency for a given (period, sort, sortDir, page), so it carries no per-user data. The caller's own row+rank is fetched separately via GetMyRank (bid-260606-leaderboard-split-my-rank).
type BuilderLeaderboardInput ¶ added in v0.5.0
type BuilderLeaderboardInput struct {
Period RankPeriod
Range *TimeRange // required iff Period == RankPeriodCustom
Sort RankSort // "" → server default (pnl)
SortDir SortDir // "" → server default (desc)
Limit *int // nil → server default (50)
Offset *int // nil → server default (0)
}
BuilderLeaderboardInput drives GetBuilderLeaderboard. Period is the only required field; every other field is optional and, when left zero/nil, falls back to grid-api's defaults (sort=pnl, sortDir=desc, limit=50, offset=0, max limit clamped to 500 server-side).
type BuilderModule ¶ added in v0.5.0
type BuilderModule struct {
// contains filtered or unexported fields
}
BuilderModule groups agency-scoped ("builder") operations under Client.Builder. These are server-to-server reads/writes that act on the agency identified by the configured BuilderCode — no end-user identity is involved. The leaderboard read targets grid-api (not user-service).
Added in v0.5.0.
func (*BuilderModule) GetAgencyBids ¶ added in v0.11.0
func (m *BuilderModule) GetAgencyBids( ctx context.Context, input AgencyBidsInput, opts ...CallOption, ) (AgencyBidsResult, error)
GetAgencyBids lists one page of full bid rows for the authenticated S2S builder agency from grid-api's agencyBids query, plus the total match count. The agency is resolved by grid-api from the HMAC-signed builder context, not from a caller-supplied GraphQL argument.
func (*BuilderModule) GetBuilderLeaderboard ¶ added in v0.5.0
func (m *BuilderModule) GetBuilderLeaderboard( ctx context.Context, input BuilderLeaderboardInput, opts ...CallOption, ) (*BuilderLeaderboard, error)
GetBuilderLeaderboard fetches the agency-scoped public leaderboard from grid-api's rank_leaderboardV2 query. It is an HMAC-signed server-to-server call: grid-api's builder-auth middleware classifies x-api-key + x-builder-code as S2S and verifies the HMAC signature when the agency has a secret. The agency is resolved from the configured BuilderCode.
The board is public and cacheable — it carries no per-user data. To resolve a specific user's own row + rank, use GetMyRank with WithUserToken(accessToken).
func (*BuilderModule) GetBuilderStats ¶ added in v0.9.0
func (m *BuilderModule) GetBuilderStats( ctx context.Context, input BuilderStatsInput, opts ...CallOption, ) (*BuilderStats, error)
GetBuilderStats fetches the agency's gameplay metrics (trading volume, revenue, active users) from grid-api's builderStats query over the given window. HMAC-signed S2S call; the agency is resolved from the configured BuilderCode. Sealed-hours-only — see BuilderStats.AsOf.
func (*BuilderModule) GetBuilderUserBalances ¶ added in v0.11.0
func (m *BuilderModule) GetBuilderUserBalances( ctx context.Context, input BuilderUserBalancesInput, opts ...CallOption, ) (*BuilderUserBalances, error)
GetBuilderUserBalances returns the total balance held by the calling agency's users, now and at a past UTC day boundary, broken down by currency.
Server-to-server only: the agency is derived from the configured BuilderCode and HMAC credentials, never from an argument, so a builder can only read its own users. Targets user-service (not grid-api).
An agency with no wallets returns an empty Balances slice — not an error and not a zero row. Render that as an empty state so a misconfigured integration stays distinguishable from a genuinely empty one.
Added in v0.11.0.
func (*BuilderModule) GetBuilderUserClosedBids ¶ added in v0.9.0
func (m *BuilderModule) GetBuilderUserClosedBids( ctx context.Context, input BuilderUserClosedBidsInput, opts ...CallOption, ) ([]Bid, error)
GetBuilderUserClosedBids lists one user's CLOSED bids (won + lost + cancelled — every bid that is NOT still open) by agencyUid, from grid-api's builderUserClosedBids query. It is the mirror of GetBuilderUserOpenBids: same HMAC-signed S2S call to grid-api, same agencyUid→userId reverse-map performed server-side; only the returned status set differs (the complement of open).
func (*BuilderModule) GetBuilderUserKpis ¶ added in v0.11.0
func (m *BuilderModule) GetBuilderUserKpis( ctx context.Context, input BuilderUserKpisInput, opts ...CallOption, ) (*BuilderUserKpis, error)
GetBuilderUserKpis fetches the S2S-only aggregate KPI summary for the configured builder from grid-api's builderUserKpis query. The agency is resolved from the configured BuilderCode and API key; it is never passed as an argument.
func (*BuilderModule) GetBuilderUserOpenBids ¶ added in v0.9.0
func (m *BuilderModule) GetBuilderUserOpenBids( ctx context.Context, input BuilderUserOpenBidsInput, opts ...CallOption, ) ([]Bid, error)
GetBuilderUserOpenBids lists one user's OPEN (pending) bids by agencyUid, from grid-api's builderUserOpenBids query. grid-api reverse-maps agencyUid→userId internally; this targets the grid-api endpoint (not user-service).
func (*BuilderModule) GetBuilderUserTransactions ¶ added in v0.9.0
func (m *BuilderModule) GetBuilderUserTransactions( ctx context.Context, input BuilderUserTransactionsInput, opts ...CallOption, ) ([]WalletTransaction, error)
GetBuilderUserTransactions lists one user's wallet transactions by agencyUid, from taphub-user-service's builderUserTransactions query.
func (*BuilderModule) GetBuilderUsersStats ¶ added in v0.7.0
func (m *BuilderModule) GetBuilderUsersStats( ctx context.Context, input BuilderUsersStatsInput, opts ...CallOption, ) ([]RankEntry, error)
GetBuilderUsersStats fetches per-user PnL / volume for a specific set of the builder's own users (by agency_uid) over a period — `rank_me` generalized to a batch. It is an HMAC-signed S2S call (same auth as GetBuilderLeaderboard); the agency is resolved from the configured BuilderCode, never passed as an argument.
The response contains one RankEntry per requested agency_uid, in input order. Each row's Rank is the user's competition rank in the FULL agency pool for the window (so it agrees with GetBuilderLeaderboard / GetMyRank). An agency_uid with no activity in the window comes back as a zero row that echoes only AgencyUID. Added in v0.7.0 (bid-260609).
func (*BuilderModule) GetBuilderWalletStats ¶ added in v0.9.0
func (m *BuilderModule) GetBuilderWalletStats( ctx context.Context, input BuilderWalletStatsInput, opts ...CallOption, ) (*BuilderWalletStats, error)
GetBuilderWalletStats fetches the agency's wallet/registration metrics (new users, deposit and withdraw volume) from taphub-user-service's builderWalletStats query over the given window. HMAC-signed S2S call; the agency is resolved from the configured BuilderCode. Note this targets the user-service endpoint (not grid-api) and its window uses unix-second bounds on the wire — the SDK converts the Range for you.
func (*BuilderModule) GetMyRank ¶ added in v0.6.0
func (m *BuilderModule) GetMyRank( ctx context.Context, input MyRankInput, opts ...CallOption, ) (*MyRank, error)
GetMyRank fetches the caller's own leaderboard row + rank from grid-api's rank_me query — the per-user companion to the public GetBuilderLeaderboard board, reading the same ranked window so the rank agrees with the board's ordering.
The caller's identity MUST be forwarded with WithUserToken(accessToken); grid-api resolves the user from that JWT (x-taphub-authorization). Without a user token there is no identity to rank: grid-api returns null and this method returns (nil, nil).
func (*BuilderModule) GetRetentionKpiCard ¶ added in v0.11.0
func (m *BuilderModule) GetRetentionKpiCard( ctx context.Context, input RetentionKpiCardInput, opts ...CallOption, ) (*RetentionKpiCard, error)
GetRetentionKpiCard fetches the agency's retention pair — the size of the cohort active in PreviousRange and the subset of that cohort that returned in CurrentRange — from grid-api's builderRetentionKpiCard query. HMAC-signed S2S call; the agency is resolved from the configured BuilderCode. "Active" = >=1 booked (settled OR cancelled) bid, the same user set behind builderAgencyStats.users, so the two dashboard cards agree for the same window.
func (*BuilderModule) GetRetentionTrend ¶ added in v0.11.0
func (m *BuilderModule) GetRetentionTrend( ctx context.Context, input RetentionTrendInput, opts ...CallOption, ) (*RetentionTrend, error)
GetRetentionTrend fetches the agency's cohort-retention matrix (day 14×7, week 12×8, month 12×12 — fixed server-side so every consumer draws the same chart) from grid-api's builderRetentionTrend query. HMAC-signed S2S call; the agency is resolved from the configured BuilderCode. See RetentionPeriod for the nil-vs-0 contract on cells.
type BuilderStats ¶ added in v0.9.0
type BuilderStats struct {
// Range is the resolved (hour-aligned) window the server applied.
Range RankTimeRange `json:"range"`
// AsOf is the latest sealed-hour boundary included — the metrics are "as of" this instant
// (the open hour is excluded), so the UI can show freshness.
AsOf time.Time `json:"asOf"`
TradingVolume string `json:"tradingVolume"` // Σ vol (total wagered, all pairs)
Revenue string `json:"revenue"` // −Σ pnl (house net; may be negative)
ActiveUsers int `json:"activeUsers"` // distinct non-bot users active in the window
}
BuilderStats is the result of GetBuilderStats — grid-api gameplay metrics for the agency, derived from the leaderboard rollups (sealed hours only). Decimal fields are strings to preserve precision; Revenue is the house net P&L (−Σ pnl) and MAY be negative when players net-won over the window.
type BuilderStatsInput ¶ added in v0.9.0
type BuilderStatsInput struct {
Period RankPeriod
Range *TimeRange
}
BuilderStatsInput selects the window for GetBuilderStats. Period is required; Range is required iff Period == RankPeriodCustom (hour-aligned UTC bounds), ignored otherwise.
type BuilderUserBalances ¶ added in v0.11.0
type BuilderUserBalances struct {
// NowTs is the server instant at which TotalNow was measured (unix seconds).
NowTs int64 `json:"nowTs"`
// AsOfTs is the resolved comparison instant: 00:00 UTC, DaysAgo days back
// (unix seconds). Read this rather than recomputing it — it derives from the
// server clock, so a locally computed cutoff will drift from it.
AsOfTs int64 `json:"asOfTs"`
Balances []BuilderCurrencyBalance `json:"balances"`
}
BuilderUserBalances is the result of GetBuilderUserBalances: what the calling agency's users hold now versus at a past UTC day boundary, per currency.
Balances are never summed across currencies — this service owns no exchange rate, so adding two currencies would produce a meaningless number. Convert with a rate you own if you need a single figure, and label it as converted.
Added in v0.11.0.
type BuilderUserBalancesInput ¶ added in v0.11.0
type BuilderUserBalancesInput struct {
// DaysAgo is how many whole UTC days back to compare against. nil → server
// default (7). Must be 1..90 when set.
//
// Days rather than a timestamp because balances are recorded once per day at
// 00:00 UTC — there is no sub-day precision the server could honour, and an
// instant argument would only invite callers to supply precision it discards.
DaysAgo *int
// Currency narrows the result to one currency. "" → every currency the
// agency's users hold.
Currency string
}
BuilderUserBalancesInput selects the comparison point for GetBuilderUserBalances. Both fields are optional: the zero value asks for "7 days ago, every currency".
type BuilderUserClosedBidsInput ¶ added in v0.9.0
BuilderUserClosedBidsInput drives GetBuilderUserClosedBids. Same shape as BuilderUserOpenBidsInput: AgencyUID is required; PairID, Limit and Offset are optional.
type BuilderUserKpis ¶ added in v0.11.0
type BuilderUserKpis struct {
Period RankPeriod `json:"period"`
Range RankTimeRange `json:"range"`
NumberOfUserTrade int `json:"numberOfUserTrade"`
NewUser int `json:"newUser"`
TotalTradingVolume string `json:"totalTradingVolume"`
UpdatedAt time.Time `json:"updatedAt"`
}
BuilderUserKpis is an agency-scoped aggregate KPI summary for the configured builder. Decimal fields are serialized as strings to preserve precision.
type BuilderUserKpisInput ¶ added in v0.11.0
type BuilderUserKpisInput struct {
Period RankPeriod
Range *TimeRange // required iff Period == RankPeriodCustom
}
BuilderUserKpisInput selects the window for GetBuilderUserKpis. Period is required by the SDK for explicitness; use RankPeriodAllTime for the backend's default window. Range is required iff Period == RankPeriodCustom.
type BuilderUserOpenBidsInput ¶ added in v0.9.0
BuilderUserOpenBidsInput drives GetBuilderUserOpenBids. AgencyUID is required; PairID, Limit and Offset are optional.
type BuilderUserTransactionsInput ¶ added in v0.9.0
type BuilderUserTransactionsInput struct {
AgencyUID string
Limit int // 0 → server default
Offset int
}
BuilderUserTransactionsInput drives GetBuilderUserTransactions. AgencyUID is required; Limit and Offset are optional (Limit is clamped server-side to ≤50).
type BuilderUsersStatsInput ¶ added in v0.7.0
type BuilderUsersStatsInput struct {
AgencyUIDs []string
Period RankPeriod
Range *TimeRange // required iff Period == RankPeriodCustom
Sort RankSort // "" → server default (pnl)
SortDir SortDir // "" → server default (desc)
}
BuilderUsersStatsInput drives GetBuilderUsersStats. AgencyUIDs (the builder's own user ids) and Period are required; Range is required iff Period == RankPeriodCustom. Sort/ SortDir only choose which board's `rank` is reported (the metrics are sort-independent); left zero, they fall back to grid-api's defaults (pnl/desc).
type BuilderWalletStats ¶ added in v0.9.0
type BuilderWalletStats struct {
FromTs int64 `json:"fromTs"`
ToTs int64 `json:"toTs"`
NewUsers int `json:"newUsers"`
DepositVolume string `json:"depositVolume"`
WithdrawVolume string `json:"withdrawVolume"`
}
BuilderWalletStats is the result of GetBuilderWalletStats — taphub-user-service wallet and registration metrics for the agency. Decimal fields are strings; WithdrawVolume is a positive magnitude. FromTs/ToTs are the resolved window as unix seconds.
type BuilderWalletStatsInput ¶ added in v0.9.0
type BuilderWalletStatsInput struct {
Period RankPeriod
Range *TimeRange
}
BuilderWalletStatsInput selects the window for GetBuilderWalletStats. Same selector as BuilderStatsInput — Period required, Range required iff Period == RankPeriodCustom. The SDK converts Range to the user-service's unix-second wire shape internally.
type CallOption ¶
type CallOption func(*callConfig)
CallOption customizes a single SDK call. Currently only retry behavior is configurable; the option pattern leaves room for future fields (e.g., custom headers) without breaking the call signatures.
func WithRetry ¶
func WithRetry(rc RetryConfig) CallOption
WithRetry overrides the client-level retry config for a single call.
func WithRetryDisabled ¶
func WithRetryDisabled() CallOption
WithRetryDisabled forces a single call to fail-fast regardless of the client's retry config.
func WithUserToken ¶ added in v0.5.0
func WithUserToken(jwt string) CallOption
WithUserToken forwards a TapHub user JWT on grid-api calls via the x-taphub-authorization header, so the call resolves the user's identity in addition to the agency (S2S). Used by Builder.GetMyRank to resolve the caller's own leaderboard rank (rank_me) — the public GetBuilderLeaderboard board carries no per-user data and does not need it. The token is the AccessToken returned by Auth.LoginS2S / Auth.UserLogin. Ignored by user-service operations.
Added in v0.5.0.
type Client ¶
type Client struct {
User *UserModule
Wallet *WalletModule
Auth *AuthModule
Builder *BuilderModule
// contains filtered or unexported fields
}
Client is the SDK entry point. After construction, Client is goroutine-safe and can be shared across the process.
func New ¶
New validates the config and returns a ready-to-use Client. The endpoint must use https:// or http://localhost; all five string fields must be non-empty.
func (*Client) Ping ¶
func (c *Client) Ping(ctx context.Context, opts ...CallOption) (json.RawMessage, error)
Ping issues a connectivity + auth check against /api/user/v1/agency/ping.
type Config ¶
type Config struct {
APIKey string
AgencyID string
AgencySecret string
BuilderCode string
Endpoint string
HTTPClient *http.Client
Retry *RetryConfig
}
Config carries the credentials and transport hooks required to construct a Client. AgencySecret is stored on the returned Client but is never surfaced via Config.String() or MarshalJSON. BuilderCode is an identifier the backend uses to resolve the canonical agency UUID via the `x-builder-code` header; it is NOT a secret and is rendered verbatim.
func (Config) MarshalJSON ¶
MarshalJSON emits the JSON representation with the secret redacted.
type Error ¶
type Error struct {
Kind ErrorKind
Code string
Status int
Message string
RetryAfter time.Duration
Cause error
Details json.RawMessage
// Meta carries errors[0].extensions.meta from the GraphQL response — the
// backend's channel for structured, machine-readable error data. Nil when
// the backend supplies none. Multi-region's Auth_WrongRegion uses it to name
// the region the caller must retry at; read it via WrongRegionTarget rather
// than indexing this map by hand.
Meta map[string]any
}
Error is the single error type returned by every SDK operation. Callers use errors.Is against the sentinel values to branch on Kind, and errors.As to recover the typed *Error for inspecting Code, Status, RetryAfter, etc.
The agency secret and the computed HMAC signature are never written into any field of Error.
type ErrorKind ¶
type ErrorKind int
ErrorKind classifies SDK errors. Callers should branch on Kind via errors.Is against the exported sentinel values rather than string-matching on Error().
type LoginS2SInput ¶ added in v0.2.0
type LoginS2SInput struct {
PartnerUserID string
DisplayName string
// ClientMeta is the stringified-JSON blob the builder's own frontend
// produced (tz, screen, lang, …). The backend stores it under
// login_histories.meta.client, and for multi-geo it is also the carrier for
// the FE's nearest-region probe result under the key "preferredRegion",
// which the backend validates against its deployed regions when pinning a
// first-join user.
//
// Forwarded VERBATIM: the SDK never parses or re-encodes it, because
// re-marshalling would rewrite an audit record the SDK does not own.
// Optional — nil omits the field entirely; a non-nil pointer to "" is sent
// as an empty blob.
ClientMeta *string
// Region is the caller's CURRENT region, used only as a first-join pinning
// candidate; for an already-pinned user the stored home region always wins.
// Optional — empty omits the field.
//
// A builder that geo-routes instead (sending the first login directly to
// the nearest cluster, which then pins by its own REGION) can leave this
// empty; that is what the reference builder does.
Region string
}
LoginS2SInput drives the server-to-server login. PartnerUserID is the builder's own identifier for the end-user (formerly agency_player_id); DisplayName is a human-readable label that the backend stores at first touch.
type MyRank ¶ added in v0.6.0
type MyRank struct {
Rank int `json:"rank"`
Entry *RankEntry `json:"entry,omitempty"`
Total int `json:"total"`
}
MyRank is the caller's own row + rank in the full pool (bid-260606-leaderboard-split-my-rank). Entry is nil when the caller has no activity in the window; Rank is then 0. Total is the ranked-pool size, so a client can render "#Rank of Total" without fetching the board.
type MyRankInput ¶ added in v0.6.0
type MyRankInput struct {
Period RankPeriod
Range *TimeRange // required iff Period == RankPeriodCustom
Sort RankSort // "" → server default (pnl)
SortDir SortDir // "" → server default (desc)
}
MyRankInput selects the window for GetMyRank — the same selector as the board minus pagination (which is meaningless for a single caller's rank).
type RankEntry ¶ added in v0.5.0
type RankEntry struct {
UserID string `json:"userId"`
// AgencyUID is the builder's own user id for the user. The server returns it to S2S
// (authenticated-builder) callers on every leaderboard read — GetBuilderUsersStats,
// GetBuilderLeaderboard, and GetMyRank — and returns null for anonymous / end-user
// (non-S2S) callers, since it is the builder's private (often PII) identifier. nil here
// means a non-S2S caller or an unresolved id. Added in v0.7.0; board + myRank selection
// added in v0.8.0 (bid-260609).
AgencyUID *string `json:"agencyUid,omitempty"`
LoginBy string `json:"loginBy"`
Username string `json:"username"`
Rank int `json:"rank"`
Pnl string `json:"pnl"`
Vol string `json:"vol"`
Payout string `json:"payout"`
Wins int `json:"wins"`
Bids int `json:"bids"`
IsNewUser bool `json:"isNewUser"`
}
RankEntry is one row of the leaderboard. The Decimal fields (Pnl, Vol, Payout) are serialized as strings to preserve precision.
type RankPeriod ¶ added in v0.5.0
type RankPeriod string
RankPeriod selects the leaderboard window. Mirrors grid-api's RankPeriod enum. today includes the open hour; custom requires an explicit hour-aligned Range.
const ( RankPeriodToday RankPeriod = "today" RankPeriodYesterday RankPeriod = "yesterday" RankPeriodLast7d RankPeriod = "last_7d" RankPeriodLast30d RankPeriod = "last_30d" RankPeriodAllTime RankPeriod = "all_time" RankPeriodCustom RankPeriod = "custom" )
type RankSort ¶ added in v0.5.0
type RankSort string
RankSort selects the ordering metric: pnl = net profit (payout - vol), vol = wagered volume.
type RankTimeRange ¶ added in v0.5.0
RankTimeRange is the resolved (hour-aligned) window grid-api actually applied — useful for rendering "for the window X–Y". grid-api returns these as ISO 8601 / RFC3339 strings; encoding/json decodes them into time.Time.
type RetentionCohortRow ¶ added in v0.11.0
type RetentionCohortRow struct {
CohortFrom time.Time `json:"cohortFrom"`
CohortTo time.Time `json:"cohortTo"`
CohortUsers int `json:"cohortUsers"`
Periods []RetentionPeriod `json:"periods"`
}
RetentionCohortRow is one cohort: every user active in [CohortFrom, CohortTo). Cohorts overlap by design (rolling retention — a user active in several periods belongs to several cohorts). CohortUsers is final: a cohort period is always fully in the past.
type RetentionGranularity ¶ added in v0.11.0
type RetentionGranularity string
RetentionGranularity is the calendar bucket both retention reads validate against. Mirrors grid-api's RetentionGranularity enum.
const ( RetentionGranularityDay RetentionGranularity = "day" RetentionGranularityWeek RetentionGranularity = "week" RetentionGranularityMonth RetentionGranularity = "month" )
type RetentionKpiCard ¶ added in v0.11.0
type RetentionKpiCard struct {
PreviousRangeUsers int `json:"previousRangeUsers"`
CurrentRangeUsers int `json:"currentRangeUsers"`
}
RetentionKpiCard is the result of GetRetentionKpiCard. CurrentRangeUsers is the INTERSECTION with the cohort — users counted in PreviousRangeUsers who were also active in CurrentRange — never an independent count of the current window, so it is always <= PreviousRangeUsers. Retention rate = CurrentRangeUsers / PreviousRangeUsers (caller-side; undefined on a 0 cohort).
type RetentionKpiCardInput ¶ added in v0.11.0
type RetentionKpiCardInput struct {
ProductType RetentionProductType
Granularity RetentionGranularity
PreviousRange TimeRange
CurrentRange TimeRange
}
RetentionKpiCardInput drives GetRetentionKpiCard. PreviousRange is the COHORT window, CurrentRange the return window; each must be exactly one calendar bucket of Granularity. The agency is always the configured BuilderCode — there is no agency field on purpose.
type RetentionPeriod ¶ added in v0.11.0
type RetentionPeriod struct {
PeriodNumber int `json:"periodNumber"`
TargetFrom time.Time `json:"targetFrom"`
TargetTo time.Time `json:"targetTo"`
RetainedUsers *int `json:"retainedUsers"`
}
RetentionPeriod is one matrix cell: how many of the cohort's users were active again in [TargetFrom, TargetTo). The null rule is load-bearing: RetainedUsers is nil ONLY when the target period has not started; a started-but-in-progress period carries a partial, still-rising number, and 0 means the period was read and nobody returned. Never coalesce nil to 0 — that would draw total churn where there is only future.
type RetentionProductType ¶ added in v0.11.0
type RetentionProductType string
RetentionProductType selects the product line a retention read applies to. Mirrors grid-api's ProductType enum. Only prediction_grid is served today; the other values are rejected by the server with Retention_ProductUnavailable rather than answered with a false 0.
const ( RetentionProductPredictionGrid RetentionProductType = "prediction_grid" RetentionProductPredictionSpot RetentionProductType = "prediction_spot" RetentionProductPredictionLeverage RetentionProductType = "prediction_leverage" )
type RetentionTrend ¶ added in v0.11.0
type RetentionTrend struct {
ProductType RetentionProductType `json:"productType"`
Granularity RetentionGranularity `json:"granularity"`
DataThrough time.Time `json:"dataThrough"`
MaxPeriod int `json:"maxPeriod"`
Rows []RetentionCohortRow `json:"rows"`
}
RetentionTrend is the cohort-retention matrix, newest cohort first, rows never omitted (an empty cohort is a 0-user row, so trend lines have no gaps). MaxPeriod is 7|8|12 by granularity and every row's Periods has exactly that many entries. DataThrough is the freshness of the served rollup — compare a cell's TargetFrom against it to identify the partial trailing diagonal client-side.
type RetentionTrendInput ¶ added in v0.11.0
type RetentionTrendInput struct {
ProductType RetentionProductType
Granularity RetentionGranularity
Anchor TimeRange
}
RetentionTrendInput drives GetRetentionTrend. Anchor names ONE calendar period of Granularity (normally the current one) — it anchors the grid and does NOT restrict which cohorts return: the server derives the whole matrix backwards from it (newest cohort = anchor − 1 period). A future anchor is rejected; a past anchor reviews a historical trend.
type RetryConfig ¶
type RetryConfig struct {
Disabled bool
MaxAttempts int
BaseDelay time.Duration
MaxDelay time.Duration
OnRetry func(RetryInfo)
}
RetryConfig controls the retry loop. A nil *RetryConfig on Config.Retry applies the defaults; an explicit Disabled=true short-circuits to single-shot.
type TimeRange ¶ added in v0.5.0
TimeRange is an hour-aligned UTC window. Required when BuilderLeaderboardInput.Period is RankPeriodCustom; ignored otherwise. The bounds are sent to grid-api as ISO 8601 / RFC3339 (yyyy-mm-ddThh:mm:ssZ).
type UserCreateParams ¶
UserCreateParams is the input to Create. Field names mirror the TS SDK; they are converted to snake_case on the wire.
type UserFundingSummary ¶ added in v0.11.0
type UserFundingSummary struct {
// echoed from the request, so a zero-valued summary is still self-describing
AgencyUID string
Currency string
// the window the server actually served; for an all-time call, To is the only record of
// where the books were closed
From time.Time
To time.Time
DepositCount int
WithdrawCount int
DepositAmount string // decimal string, positive
WithdrawAmount string // decimal string, positive magnitude
PendingDepositCount int
PendingWithdrawCount int
PendingDepositAmount string
PendingWithdrawAmount string
}
UserFundingSummary is the typed result of GetUserFundingSummary.
Amounts are decimal strings, never float64 — reconciliation exists to detect discrepancies too small to survive a floating-point round trip. Feed them to shopspring/decimal if you need arithmetic.
Both withdraw amounts are POSITIVE magnitudes even though withdrawals are stored negative backend-side, matching the sign convention of the builder wallet stats surface. So DepositAmount - WithdrawAmount is the net funding flow, and callers never need to know the storage convention.
Pending* carries the same four numbers for transactions still in flight. They are reported beside the finished ones rather than folded in, because that is where reconciliation mismatches live: a total short by 50 is a different problem depending on whether the 50 is pending or lost.
type UserFundingSummaryParams ¶ added in v0.11.0
type UserFundingSummaryParams struct {
AgencyUID string
Currency string
From *time.Time // nil = all-time
To *time.Time // nil = now (server-resolved)
}
UserFundingSummaryParams is the input to GetUserFundingSummary.
AgencyUID and Currency are both required. Currency is not optional by oversight: a user may hold several wallets, and summing across currencies yields a number that is arithmetically valid and semantically meaningless — the caller must state which book they are reconciling.
From and To are optional and half-open: From inclusive, To exclusive, so consecutive windows tile without double-counting a boundary transaction. Nil From means all-time; nil To means the instant the server serves the query. Pointers rather than zero-value sentinels because time.Time{} is a real instant (year 1), and treating it as "unset" would silently turn an uninitialized struct field into an all-time query.
type UserModule ¶
type UserModule struct {
// contains filtered or unexported fields
}
UserModule groups user-domain operations under Client.User.
func (*UserModule) Create ¶
func (m *UserModule) Create( ctx context.Context, p UserCreateParams, opts ...CallOption, ) (json.RawMessage, error)
Create provisions a user under the calling agency.
type Wallet ¶
Wallet is the domain type returned by GetUserWallet and ListUserWallets. Amount is a decimal string mirroring the backend decimal.Decimal.
type WalletModule ¶
type WalletModule struct {
// contains filtered or unexported fields
}
WalletModule groups wallet-domain operations under Client.Wallet.
func (*WalletModule) Cancel ¶ added in v0.3.0
func (m *WalletModule) Cancel( ctx context.Context, ref string, opts ...CallOption, ) (*WalletReservation, error)
Cancel releases a PENDING reservation: PENDING → FAILED. The wallet's Amount is restored (reservation funds are returned to the spendable balance) and ReservedAmount is decremented.
Idempotent on already-FAILED rows. Returns *Error with code ErrReservationAlreadyCommitted if the row is FINISHED, or code ErrReservationNotFound if the ref is unknown (or belongs to a different agency).
Backend endpoint: GraphQL mutation `agencyCancel` on /th-user-gql. The legacy REST endpoint POST /api/user/v1/agency/cancel is deprecated.
func (*WalletModule) Commit ¶ added in v0.3.0
func (m *WalletModule) Commit( ctx context.Context, ref string, opts ...CallOption, ) (*WalletReservation, error)
Commit finalizes a PENDING reservation referenced by ref: PENDING → FINISHED. The wallet's Amount is unchanged (it was already adjusted at reserve-time); only ReservedAmount is decremented.
Idempotent on already-FINISHED rows. Returns *Error with code ErrReservationAlreadyCancelled if the row is FAILED, or code ErrReservationNotFound if the ref is unknown (or belongs to a different agency).
Backend endpoint: GraphQL mutation `agencyCommit` on /th-user-gql. The legacy REST endpoint POST /api/user/v1/agency/commit is deprecated.
func (*WalletModule) GetTxByRef ¶
func (m *WalletModule) GetTxByRef( ctx context.Context, ref string, opts ...CallOption, ) (*WalletTransaction, error)
GetTxByRef looks up a wallet transaction by the idempotency ref passed to Transfer. Returns *Error with Kind=KindNotFound when the ref is unknown.
func (*WalletModule) GetUserFundingSummary ¶ added in v0.11.0
func (m *WalletModule) GetUserFundingSummary( ctx context.Context, p UserFundingSummaryParams, opts ...CallOption, ) (*UserFundingSummary, error)
GetUserFundingSummary returns the aggregated deposit/withdraw activity of one of the builder's own users (by agencyUid), in one currency, over an optional [From, To) window — the per-user counterpart of the agency-wide wallet stats.
It is an HMAC-signed S2S call to taphub-user-service. The agency comes from the client's configured builder credentials, never from an argument, so a builder can only read its own users. An unknown agencyUid returns an all-zero summary rather than an error; so does a uid belonging to another agency, deliberately — the two are indistinguishable, so this call cannot be used to probe whether a uid exists under some other builder.
Requires a backend carrying the builderUserFundingSummary query (OpenSpec change builder-260820-funding-reconciliation); older deployments reject it as an unknown field. Transfer-mode agencies only — seamless agencies keep transactions in a different table and are rejected with TransferModeNotSupported rather than answered with zeros.
LIMIT OF THIS DATA — read before drawing conclusions. These numbers cover only what TapHub recorded. A request that never reached TapHub (a network failure before the write) appears in none of them. Comparing balances alone therefore cannot distinguish "a request was lost" from "the ledger is correct": both look identical. Reconcile the COUNT of requests you issued against DepositCount / WithdrawCount, not merely the amounts.
func (*WalletModule) GetUserWallet ¶
func (m *WalletModule) GetUserWallet( ctx context.Context, agencyUID string, opts ...CallOption, ) (*Wallet, error)
GetUserWallet returns the agency's configured-currency wallet for the user.
func (*WalletModule) ListUserWallets ¶
func (m *WalletModule) ListUserWallets( ctx context.Context, agencyUID string, opts ...CallOption, ) ([]Wallet, error)
ListUserWallets returns every wallet the user owns under the agency.
As of builder-260627 this calls the GraphQL `builderUserWallets` query on the user-service (HMAC S2S) instead of the deprecated REST list-user-wallet endpoint. Same agency-scoped semantics — the agency is resolved from the configured BuilderCode, never passed as an argument — and the same Wallet payload (incl. IsEnabled). An unknown agencyUID returns an empty slice.
func (*WalletModule) Reserve ¶ added in v0.3.0
func (m *WalletModule) Reserve( ctx context.Context, p WalletReserveParams, opts ...CallOption, ) (*WalletReservation, error)
Reserve creates a PENDING wallet reservation against the agency wallet.
Funds are moved out of Wallet.Amount and into Wallet.ReservedAmount atomically. The reservation must be finalized via Commit or released via Cancel before TTLSeconds elapses (default 1800s, clamped by the backend to [60, 86400]); otherwise the backend sweeper auto-cancels.
Ref is the idempotency key. Replay with the same Ref + matching body returns the same row; replay with a different amount or currency returns a *Error with Kind=KindConflict and code ErrTxRefConflict.
Backend endpoint: GraphQL mutation `agencyReserve` on /th-user-gql. The legacy REST endpoint POST /api/user/v1/agency/reserve is deprecated since v0.4.0 — the backend logs a `[deprecated]` WARN per call.
func (*WalletModule) Transfer ¶
func (m *WalletModule) Transfer( ctx context.Context, p WalletTransferParams, opts ...CallOption, ) (*WalletTransferResult, error)
Transfer moves funds to or from a user's wallet. Ref is the idempotency key. The SDK always sends a positive Amount; for withdrawals it prepends `-` to the wire value, matching the backend convention where the sign of `amount` IS the direction (no separate `direction` field).
type WalletReservation ¶ added in v0.3.0
type WalletReservation struct {
ID string
WalletID string
UserID string
Amount string
BeforeAmount string
AfterAmount string
Ref string // from wire field tx_ref
Status string // PENDING | FINISHED | FAILED
TTLSeconds *int
ExpiresAt string // RFC3339; empty when backend omits
Timestamp string
}
WalletReservation is the typed return of Reserve, Commit, and Cancel. Mirrors the backend WalletReservationResVO and the documented AgencyWalletReservation GraphQL type.
type WalletReserveParams ¶ added in v0.3.0
type WalletReserveParams struct {
Ref string
AgencyUID string
Amount string // positive decimal string; SDK negates on the wire
Direction Direction
Currency string
TTLSeconds *int // optional. Backend clamps to [60, 86400], default 1800
}
WalletReserveParams is the input to Reserve. Mirrors WalletTransferParams plus an optional TTLSeconds override.
As of backend OpenSpec change user-260527-agency-wallet-reserve VULN-002, the backend rejects positive amounts (Direction=DirectionDeposit). The SDK fails fast at the validation layer to avoid an HTTP round-trip — only Direction=DirectionWithdraw is accepted. This restriction will be lifted when commit-of-deposit semantics are designed.
type WalletTransaction ¶
type WalletTransaction struct {
ID string
WalletID string
UserID string
Amount string
BeforeAmount string
AfterAmount string
Currency string
Ref string // from wire field tx_ref
Source string
Type string
// Timestamp is the RFC3339 placement time. Populated by GetBuilderUserTransactions
// (builder-260629); GetTxByRef leaves it empty (its wire shape omits it).
Timestamp string
}
WalletTransaction is the domain type returned by GetTxByRef and GetBuilderUserTransactions. Field names are Go-camelCase; the wire-side representation uses snake_case.
type WalletTransferParams ¶
type WalletTransferParams struct {
Ref string
AgencyUID string
Amount string
Direction Direction
Currency string
}
WalletTransferParams is the input to Transfer. Ref is mandatory for idempotency; the backend dedupes duplicate refs with 409 Conflict. Currency must match the agency configuration; the backend rejects mismatches unless skip_currency_validation is set (which the SDK never sends). Amount must always be a positive decimal string — Direction is the single source of truth for sign, and the SDK negates the wire value for withdrawals.
type WalletTransferResult ¶ added in v0.2.0
type WalletTransferResult struct {
ID string
WalletID string
UserID string
Amount string
BeforeAmount string
AfterAmount string
Ref string // from wire field tx_ref
Timestamp string
}
WalletTransferResult is the typed return of Transfer in v0.2.0+. Mirrors the TS SDK's WalletTransferResult and the backend WalletTransactionReqVO (which intentionally omits currency, source, type — those are implied by the request input or by the sign of amount).