Documentation
¶
Overview ¶
Package source defines what a connector to a platform (exchange, broker, blockchain) must do, and keeps the registry of available connectors.
Index ¶
- Constants
- Variables
- func NewHTTP() *http.Client
- func Register(d Definition)
- func SplitLabel(line string) (value, label string)
- type Challenge
- type Choice
- type Config
- type Definition
- type Detected
- type Fetch
- type Field
- type Importer
- type Interactive
- type Mode
- type Offline
- type Session
- type Source
- type Statement
- type StatementFetcher
- type Tester
Constants ¶
const ModeField = "mode"
ModeField holds which of a definition's Modes a source uses.
Variables ¶
var ErrLogin = errors.New("sign-in needed")
ErrLogin means the connector must be signed in again by the user (a session expired): syncs stop until then, nothing is retried.
var ErrNoBalances = errors.New("balances unknown")
ErrNoBalances, from Balances, means the platform did not say what is held: the engine derives the balances from the history and says so.
Functions ¶
func NewHTTP ¶
NewHTTP is the client connectors share: a server that cannot be reached is given up after seconds, so a fallback gets its turn, while a slow answer still has a minute.
func Register ¶
func Register(d Definition)
Register adds a definition; connectors call it from init.
func SplitLabel ¶
SplitLabel separates a list value from the name the user gave it: the name follows a tab ("zpub…\tSavings").
Types ¶
type Challenge ¶ added in v0.2.0
type Challenge struct {
// Kind: "approve" (in the platform's app), "code" (type the code its
// app or SMS shows), "qr" (scan Data with the platform's app).
Kind string `json:"kind"`
Data string `json:"data,omitempty"`
Digits int `json:"digits,omitempty"`
Expires time.Time `json:"expires"`
}
Challenge is what the user has to do for a sign-in to go on.
type Config ¶
type Config struct {
Values map[string]string
HTTP *http.Client
// Log, when set, records what the connector does outside a sync (a
// sign-in), for the run log. Never secrets.
Log func(format string, args ...any)
// State is what the connector saved after its last run (cursors, ids
// already seen...). Opaque to the engine.
State []byte
}
Config is what a connector receives: its settings and an HTTP client.
type Definition ¶
type Definition struct {
Kind string `json:"kind"`
Name string `json:"name"`
Category string `json:"category"` // "exchange", "broker", "wallet"
Description string `json:"description"`
Fields []Field `json:"fields"`
// Setup is shown above the form: how to create a read-only key, etc.
Setup string `json:"setup,omitempty"`
// Statement, when set, says how to export the platform's own statement
// for the connector's Importer (and enables the import in the UI).
Statement string `json:"statement,omitempty"`
// Limits says what the platform's API cannot tell, shown where balance
// corrections are, so the user knows why they exist.
Limits string `json:"limits,omitempty"`
// Detect, for a Lines field, recognizes one value (a key, an address)
// so the UI can say what it is before saving.
Detect func(value string) (Detected, error) `json:"-"`
// Preview reads what one value of a list holds now, so the user sees
// before saving that it is the right account. cfg carries the source's
// other values (servers, keys).
Preview func(ctx context.Context, value string, cfg Config) ([]model.Balance, error) `json:"-"`
// Hidden: kept for sources already set up, not offered for new ones
// (another kind covers it).
Hidden bool `json:"hidden,omitempty"`
// NoTest: the form has no "Test connection" (checking would need the
// user, like a sign-in to approve).
NoTest bool `json:"noTest,omitempty"`
// Modes, when set, are the ways the source can be used (a statement
// file, a connection...); fields with a Mode belong to one of them.
Modes []Mode `json:"modes,omitempty"`
// SealState: the connector's state holds a secret (a session), so the
// engine stores it encrypted.
SealState bool `json:"-"`
// New builds a connector from its settings (secrets decrypted).
New func(cfg Config) (Source, error) `json:"-"`
}
Definition describes a kind of source.
func Get ¶
func Get(kind string) (Definition, error)
func List ¶
func List() []Definition
type Detected ¶
type Detected struct {
Kind string `json:"kind"` // the chain, for icons: "bitcoin", "evm"…
Chain string `json:"chain"`
Value string `json:"value"`
Choices []Choice `json:"choices,omitempty"`
}
Detected is what a value of a list is: Chain names it for people; Value is how to store it (it may gain a prefix that picks the chain). Choices are the other chains the same value can be read on.
type Fetch ¶
type Fetch struct {
Since time.Time
// Emit records one activity. NativeID must be stable across runs.
Emit func(model.Activity) error
// Progress reports what is being fetched, for the run log.
Progress func(format string, args ...any)
}
Fetch is one run's request. Since is the time of the last successful run (zero on the first one): connectors fetch from a bit before it, the engine deduplicates by native id.
type Field ¶
type Field struct {
Name string `json:"name"`
Label string `json:"label"`
Secret bool `json:"secret,omitempty"` // stored encrypted, never sent back to the UI
Optional bool `json:"optional,omitempty"` //
Help string `json:"help,omitempty"`
// Lines: one value per line (a list). A secret list comes back to the
// UI masked line by line; masked lines sent back keep their stored value.
Lines bool `json:"lines,omitempty"`
// Advanced: rarely needed, tucked away in the form.
Advanced bool `json:"advanced,omitempty"`
// Hidden: set by the UI itself (a choice made before the form), never
// shown as a field.
Hidden bool `json:"hidden,omitempty"`
// Mode: shown only when the source is used this way (see
// Definition.Modes); empty for every mode.
Mode string `json:"mode,omitempty"`
}
Field is one setting of a connector, rendered as a form field by the UI.
type Importer ¶
type Importer interface {
// Import parses the file. existing is the source's current history, so
// the connector can keep API activities that describe the same movement
// better (transaction hashes, ids, fiat amounts).
Import(name string, data []byte, existing []model.Activity) (*Statement, error)
}
Importer reads a statement exported from the platform (when the API does not tell the whole story). The statement becomes the reference for the period it covers: the engine replaces that period's history with what Import returns, and later syncs ignore API activities inside it.
type Interactive ¶ added in v0.2.0
type Interactive interface {
Session() Session
// Login starts a sign-in with one of Session().Methods.
Login(ctx context.Context, method string) (*Challenge, error)
// Poll checks on it: a nil challenge means signed in.
Poll(ctx context.Context) (*Challenge, error)
// Answer sends the code a "code" challenge asks for.
Answer(ctx context.Context, code string) (*Challenge, error)
}
Interactive is implemented by connectors that need the user to sign in now and then. The engine keeps the connector between calls while a sign-in is in progress; the session it ends with is part of State. A connector never retries a refused sign-in by itself.
type Mode ¶ added in v0.2.0
type Mode struct {
ID string `json:"id"`
Name string `json:"name"`
Help string `json:"help,omitempty"`
Beta bool `json:"beta,omitempty"`
}
Mode is one way of using a source, picked in its form (stored in the ModeField setting; the first mode when unset).
type Offline ¶ added in v0.2.0
type Offline interface {
Offline() bool
}
Offline is implemented by connectors that may read nothing from the platform (statements only): Offline reports whether this one does.
type Session ¶ added in v0.2.0
type Session struct {
// Needed: the source signs in interactively (a session it keeps).
Needed bool `json:"needed"`
// Until: when the current session ends (zero: none).
Until time.Time `json:"until,omitempty"`
// Methods: how a sign-in can be started ("app", "qr").
Methods []string `json:"methods,omitempty"`
}
Session is what an interactive connector says of its sign-in, without asking the platform.
type Source ¶
type Source interface {
// Activities emits the history since f.Since (everything when zero).
Activities(ctx context.Context, f Fetch) error
// Balances returns what the platform says is held now.
Balances(ctx context.Context) ([]model.Balance, error)
// State returns what to give back in Config.State next time.
State() []byte
}
Source is a configured connector.
type Statement ¶
type Statement struct {
From, To time.Time // period covered (inclusive)
// Accounts: the sub-accounts the file covers (nil: all of them);
// activities of other sub-accounts in the period stay.
Accounts []string
Activities []model.Activity // the whole history of that period
Notes []string // what the user should know (lines skipped...)
}
Statement is what an import produced.
type StatementFetcher ¶ added in v0.2.0
type StatementFetcher interface {
FetchStatement(ctx context.Context, f Fetch) (name string, data []byte, err error)
}
StatementFetcher is implemented by connectors whose sync downloads the platform's own statement (the file Importer reads): the engine imports it as if the user had, instead of calling Activities.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package binance reads a Binance account's history and balances with a read-only API key: deposits and withdrawals, spot trades (delisted pairs included), converts, card and bank purchases, dust conversions, distributions (airdrops, token swaps), Auto-Invest, Simple Earn rewards, ETH/SOL liquid staking, Binance Pay and P2P. Binance is treated as one account: moves between its internal wallets (spot, funding, earn) are not activities.
|
Package binance reads a Binance account's history and balances with a read-only API key: deposits and withdrawals, spot trades (delisted pairs included), converts, card and bank purchases, dust conversions, distributions (airdrops, token swaps), Auto-Invest, Simple Earn rewards, ETH/SOL liquid staking, Binance Pay and P2P. Binance is treated as one account: moves between its internal wallets (spot, funding, earn) are not activities. |
|
Package bitcoin reads a Bitcoin wallet from its extended public key (xpub, ypub or zpub): addresses are derived locally, history and balances come from an Esplora API (mempool.space by default, or your own instance).
|
Package bitcoin reads a Bitcoin wallet from its extended public key (xpub, ypub or zpub): addresses are derived locally, history and balances come from an Esplora API (mempool.space by default, or your own instance). |
|
Package bitpanda reads a Bitpanda account through the Public API (https://api.public.bitpanda.com/v1, docs.public.bitpanda.com) with a key limited to the Balances and Transaction scopes.
|
Package bitpanda reads a Bitpanda account through the Public API (https://api.public.bitpanda.com/v1, docs.public.bitpanda.com) with a key limited to the Balances and Transaction scopes. |
|
Package cardano reads a Cardano wallet from its stake address: every address of a Shelley wallet shares the stake credential, so the account's history, staking rewards and balance come from it, through a Koios API (api.koios.rest by default, free, an API key is optional).
|
Package cardano reads a Cardano wallet from its stake address: every address of a Shelley wallet shares the stake credential, so the account's history, staking rewards and balance come from it, through a Koios API (api.koios.rest by default, free, an API key is optional). |
|
Package coinbase reads a Coinbase account (the retail app and Advanced Trade) with a view-only CDP API key.
|
Package coinbase reads a Coinbase account (the retail app and Advanced Trade) with a view-only CDP API key. |
|
Package evm reads an Ethereum-compatible wallet (Ledger, MetaMask…) from its public address: native coin and ERC-20 history and balances, on one or more networks.
|
Package evm reads an Ethereum-compatible wallet (Ledger, MetaMask…) from its public address: native coin and ERC-20 history and balances, on one or more networks. |
|
Package kraken connects a Kraken account through the spot REST API with a read-only key ("Query Funds" and "Query Ledger Entries").
|
Package kraken connects a Kraken account through the spot REST API with a read-only key ("Query Funds" and "Query Ledger Entries"). |
|
Package okx connects an OKX account through the v5 REST API with a read-only key.
|
Package okx connects an OKX account through the v5 REST API with a read-only key. |
|
Package solana reads a Solana wallet (Phantom, Solflare, Ledger…) from its public address: SOL and SPL token history and balances.
|
Package solana reads a Solana wallet (Phantom, Solflare, Ledger…) from its public address: SOL and SPL token history and balances. |
|
Package traderepublic reads a Trade Republic account (compte-titres, PEA, crypto) in one of two ways:
|
Package traderepublic reads a Trade Republic account (compte-titres, PEA, crypto) in one of two ways: |
|
Package tron reads a Tron wallet (Ledger, TronLink…) from its public address: TRX and TRC-20 history and balances.
|
Package tron reads a Tron wallet (Ledger, TronLink…) from its public address: TRX and TRC-20 history and balances. |
|
Package wallet is one source for a whole self-custody wallet (a Ledger, a Trezor…): every account it holds, one key or address per line, each read on its own blockchain by the matching connector.
|
Package wallet is one source for a whole self-custody wallet (a Ledger, a Trezor…): every account it holds, one key or address per line, each read on its own blockchain by the matching connector. |
|
Package xrp reads an XRP Ledger account (Ledger, Xaman…) from its classic address: XRP and issued-currency history and balances.
|
Package xrp reads an XRP Ledger account (Ledger, Xaman…) from its classic address: XRP and issued-currency history and balances. |