DpExchange.Gemini (DpExchangeGemini v0.1.37)

Copy Markdown View Source

Gemini, behind the DpExchange facade.

⚠️ EXPERIMENTAL

This package has not run in production. While it is 0.x the API may change without a major version — pin all three segments. Maturity is declared per endpoint through capabilities/0; do not read this banner as your check.

This module is the entire public API of this package. Transport, signing, session handling and supervision are internal, and there is nothing here that returns them.

What is specific to Gemini, and what a caller can therefore rely on

Nine candle widths, and the venue's own documentation names three of the original seven wrong. The accepted set is 1m 5m 15m 30m 1h 6h 1d 1w 1M in canonical form — 1w and 1M (the venue's own 1mo) joined 2026-09-08, found live, after this package had shipped with only the original seven. Gemini's documentation lists values that its API rejects; this package sends what the venue accepts, measured. Asking for a width Gemini does not serve — 2h, 4h, 12h — is an error, never the nearest one.

The candle window is fixed and unbounded requests are refused, for seven of the nine widths. Gemini ignores start, end and limit entirely and returns a fixed window per width, from 1 day of one-minute bars to a year of daily ones (about 4.6 years of weekly, about 9.6 years of monthly). This package filters to your range, and refuses with {:error, {:range_unavailable, …}} when your range starts before the window can reach — rather than handing back a shorter period that reads as a complete answer. 1w and 1M are the two exceptions: neither has a fixed-seconds width the shared DpExchange.Core.Timeframe vocabulary will compute one for, so the pre-flight refusal does not fire for them — the real rows the venue returns are still filtered against your range afterward, so a range outside the true window still comes back empty rather than wrong, just without the named boundary. See DpExchange.Gemini.Rest's moduledoc.

Quote timestamps come from the venue's clock, or the call fails. Neither Gemini ticker publishes a quote timestamp — /v1/pubticker's only timestamp stamps its 24-hour volume window, about a minute stale. This package uses the venue's HTTP Date header and returns {:error, :missing_venue_timestamp} when it is absent. It never substitutes the local clock.

Gemini publishes no rate-limit headers. Measured 2026-08-28: a response carries date, x-request-id and x-envoy-upstream-service-time, and nothing else. It does publish its limits, in prose, which is better — and capabilities/0 carries all three GCRA parameters from the venue's own page rather than from a guess.

The demo environment is first-class here

Gemini runs a full exchange with test funds — its own words — where automated bots simulate order-book activity and new accounts are credited $100,000 USD, 1,000 BTC and 20,000 each of ETH, BCH, ZEC and LTC. Point this package at it with one option:

children = [{DpExchange.Gemini, environment: :sandbox}]

{:ok, quote} = DpExchange.Gemini.get_price("BTC-USD", environment: :sandbox)

Both REST and the WebSocket follow the setting. :production is the default, and deliberately so: a wrong default fails in only one direction. Meaning demo and getting production sends a real order to a real exchange; meaning production and getting demo gives obviously-wrong prices — the demo book is frequently crossed, and a frame captured 2026-08-28 carried a bid of 68169.88 against an ask of 64886.32.

Selecting it once for a whole process tree, rather than per call, goes through DpExchange.Core.Config — which resolves per process, so one async test can point at demo without redirecting every test running beside it.

live?/1 answers the question directly, resolving opts the same way every call here does: DpExchange.Gemini.live?(environment: :sandbox) is false.

You authenticate; this package signs

Account and trading are implemented, and credentials arrive as arguments — used to sign that one request and not kept:

{:ok, balances} = DpExchange.Gemini.get_balances(credentials, [])
{:ok, order} = DpExchange.Gemini.place_order(credentials, request, [])

What stays yours is credential storage and the choice of scheme. Gemini offers an API key pair and a full OAuth 2.0 authorization-code flow — app registration, a permanent client type, redirecting users to approve scopes, PKCE, and refreshing a 24-hour token — and which one an application uses is a decision about its users and its deployment. Name it with auth_scheme: :api_key | :oauth; with it absent, whichever you actually supplied is used. Credentials carrying both are refused rather than resolved, because sending both header families is AmbiguousAuthentication at the venue.

This package never reads a credential from the environment or a vault.

Gemini serves no market orders, and this package will not invent one

capabilities/0 declares supported_order_types: [:limit, :stop_limit]. The venue's own reasoning: market orders "provide you with no price protection". Its documented workaround is an immediate-or-cancel order "coupled with an aggressive limit price" — and that price is not one a package may choose for you. How aggressive is a question only the caller can answer, and the answer is money.

So order_type: :market is {:error, {:unsupported_order_type, :market}}. Ask for the behaviour explicitly, with your own number:

%{order_type: :limit, time_in_force: :ioc, price: my_price, }

Your API key's nonce mode is also something only you know. Gemini provisions keys in either time-based or incremental mode, and the two need differently-shaped nonces — seconds versus a strictly increasing value, with no single value satisfying both. The default is :time_based, the venue's own recommendation; pass nonce_mode: :incremental if that is how your key was made. A mismatch fails loudly on the first request.

Supervision

Add it to your own tree. Nothing starts on load — a consumer that has not asked for Gemini must not find a socket open.

children = [{DpExchange.Gemini, []}]

{:ok, quote} = DpExchange.Gemini.get_price("BTC-USD", [])
:ok = DpExchange.Gemini.subscribe(["BTC-USD"], to: self())

Summary

Functions

Registers a bank account. opts[:country] selects the endpoint — US or CA.

Cancels open orders in bulk. opts[:scope] is required — :session or :account.

Cancels an unconfirmed clearing order.

Commits a quoted conversion by its quoteId.

Confirms a clearing order. This executes a trade.

Converts in one call — the venue's wrap endpoint.

coverage/1, split by which of this venue's two streamable kinds is arriving.

Creates a subaccount. opts[:name] is required.

Submits a broker-facilitated clearing order between two named counterparties.

Creates a bilateral clearing order. Not place_order/3 — nothing reaches the book.

What the venue would charge to withdraw. Requires opts[:address].

The funding amount report as a spreadsheet — the venue's bytes, unparsed.

The funding payment report as JSON.

The funding payment report as a spreadsheet — the venue's bytes, unparsed.

The perpetuals margin account — collateral, leverage, and the estimated liquidation price.

One clearing order's state.

Risk statistics for a perpetual — mark, index and open interest.

A fresh deposit address for asset on network.

Funding for a perpetual — settled, projected, and when the next one lands.

A foreign-exchange reference rate for pair at at.

The spot margin account summary — a different margin system from the perpetuals one.

Margin interest rates for every borrowable asset.

Every balance, each also valued in one notional currency.

Gemini publishes no per-method read.

Open positions on the perpetuals book.

The roles this API key carries.

Staked positions, one per asset.

Movements in and out of staked positions.

What each provider pays for staking each asset.

Rewards accrued over a window.

The account's own traded volume, one row per symbol per day.

Recent public trades — the tape.

Everything that moved on this account — wider than fills and wider than transfers.

Every subaccount in the group.

The addresses this account may withdraw to on opts[:network].

Broker clearing orders — a different row shape from list_clearing_orders/1.

Clearing orders this account is party to.

Clearing trades — the orders that completed.

What Gemini charged this account for holding assets.

Symbols currently carrying a promotional fee.

Funding payments this account actually paid or received.

The networks an asset moves over, or the assets a network carries.

The funding sources this account can move fiat through.

Whether the environment opts resolves to moves real money.

When the next funding calculation lands, on its own.

Not supported. Gemini places one order per request.

What a spot order would do to this account's margin. Places nothing.

Not supported. Gemini publishes no endpoint answering what an order would cost.

Quotes a conversion — Gemini's Instant quote. See DpExchange.Gemini.Private.quote_conversion/4, including when it refuses to guess the direction.

Exchanges a refresh token for a new access token. Credential use, not consent.

Removes address from the allowlist for network.

Renames a subaccount.

Not supported. Gemini has no atomic replace; a caller cancels and re-places.

Asks the venue to allowlist address on network.

Revokes an access token. Only reachable with an OAuth token, not an API key.

Stakes amount of asset. This moves funds.

Moves funds between two accounts at this venue. Not withdraw/5.

Redeems amount of a staked asset. Returns before the redemption completes.

Endpoints the venue does not serve, as distinct from ones this package has not ported.

Moves funds. Withdraws amount of asset over network to address.

Functions

add_payment_method(details, opts \\ [])

Registers a bank account. opts[:country] selects the endpoint — US or CA.

See DpExchange.Gemini.Private.add_payment_method/3: the venue verifies out of band, so a successful response does not make the method usable.

cancel_all_orders(credentials, opts)

Cancels open orders in bulk. opts[:scope] is required — :session or :account.

See DpExchange.Gemini.Private.cancel_all_orders/2, including why there is no default.

cancel_clearing_order(clearing_id, opts \\ [])

@spec cancel_clearing_order(
  String.t(),
  keyword()
) :: {:ok, map()} | {:error, term()} | {:refused, term()}

Cancels an unconfirmed clearing order.

See DpExchange.Gemini.Private.cancel_clearing_order/3. A confirmed order is a trade, and a trade is not cancellable.

commit_conversion(id, opts \\ [])

Commits a quoted conversion by its quoteId.

confirm_clearing_order(clearing_id, request, opts \\ [])

@spec confirm_clearing_order(String.t(), map(), keyword()) ::
  {:ok, map()} | {:error, term()} | {:refused, term()}

Confirms a clearing order. This executes a trade.

See DpExchange.Gemini.Private.confirm_clearing_order/4. The venue re-asks for every term and this package fills none of them in — the confirming side says what it believes it is agreeing to, which is the whole point of the check.

convert(from, to, amount, opts \\ [])

Converts in one call — the venue's wrap endpoint.

Not a shorthand for quote_conversion/4 then commit_conversion/2: there is no rate held, and the caller learns the price from the result.

coverage_by_kind(opts \\ [])

@spec coverage_by_kind(keyword()) :: %{
  required(DpExchange.Core.Capabilities.data_kind()) => %{
    required(DpExchange.Core.Venue.symbol()) => DpExchange.Core.Venue.route()
  }
}

coverage/1, split by which of this venue's two streamable kinds is arriving.

Why this exists — the general failure mode

coverage/1 is truthful about whether anything is arriving for a symbol, but it answers with one boolean regardless of what is arriving. That collapse is invisible exactly when it matters most: a venue streaming several distinct kinds of data can have one kind healthy and another dark for the same symbol, and coverage/1 reports :stream either way, because it counts any payload against that symbol's one slot in the map. "One kind dark, another healthy" and "everything healthy" produce the same answer.

DpExchange.Core.Venue.coverage_by_kind/1 records the incident that forced this open: Coinbase's level2 (order book) channel delivered over 11,000 frames for 406 symbols while ticker (quotes) was dark for all but 5, and coverage/1 answered :stream for all 406 symbols — correctly, by its own definition, and uselessly for anyone trying to tell the two facts apart.

Why it applies here too, even though this venue's delivery is not Coinbase's

Gemini has no separate channel per kind — one @bookTicker stream carries both of this venue's declared kinds, :quotes and :top_of_book (see DpExchange.Gemini.Socket). But the two are still independent facts about a symbol: a bookTicker frame always produces a TopOfBook when it parses, and produces an accompanying Quote only when that same frame also carries a last-traded price. A symbol can quote continuously — its book updating on every level change — while never trading, so :top_of_book stays healthy for it and :quotes never appears at all. Under coverage/1 alone that symbol reads identically to one trading actively: :stream either way. Sharing one wire underneath both kinds does not prevent the collapse coverage_by_kind/1 exists to undo; it just changes which mechanism produces the two independent facts.

What this reports, and what it is not

Each DpExchange.Core.Types.Quote and DpExchange.Core.Types.TopOfBook this package has actually delivered, grouped by which struct it was — never by channel name, never by what was subscribed. :quotes and :top_of_book are the only keys, matching capabilities().streamable, and each is present even when nothing of that kind has arrived yet, mapped to an empty map — absence here is :not_covered, the same as it is in coverage/1.

Not a replacement for coverage/1: a caller asking "is anything at all arriving for this symbol" still gets a straight answer from that. Not a per-channel report: this venue's one WebSocket stream must never leak across this facade, by kind or otherwise.

See DpExchange.Core.Venue.coverage_by_kind/1 for the invariant Core's conformance suite checks whenever a venue exports this callback.

create_account(opts \\ [])

Creates a subaccount. opts[:name] is required.

See DpExchange.Gemini.Private.create_account/3. The venue answers with a kebab-cased shortname, and that is what every other endpoint's account parameter takes — not the name that was sent.

create_broker_clearing_order(request, opts \\ [])

@spec create_broker_clearing_order(
  map(),
  keyword()
) :: {:ok, map()} | {:error, term()} | {:refused, term()}

Submits a broker-facilitated clearing order between two named counterparties.

See DpExchange.Gemini.Private.create_broker_clearing_order/3. side is assigned to the source, and passing the counterparties the wrong way round produces a valid order in which each side trades the direction the other meant.

create_clearing_order(request, opts \\ [])

@spec create_clearing_order(
  map(),
  keyword()
) :: {:ok, map()} | {:error, term()} | {:refused, term()}

Creates a bilateral clearing order. Not place_order/3 — nothing reaches the book.

See DpExchange.Gemini.Private.create_clearing_order/3. is_confirmed on the response is the field that matters: false means the trade has not happened.

estimate_withdrawal_fee(asset, network, amount, opts \\ [])

What the venue would charge to withdraw. Requires opts[:address].

Moves no funds. See DpExchange.Gemini.Private.estimate_withdrawal_fee/5.

funding_amount_report(symbol, opts \\ [])

@spec funding_amount_report(
  String.t(),
  keyword()
) :: {:ok, binary()} | {:error, term()} | {:refused, term()}

The funding amount report as a spreadsheet — the venue's bytes, unparsed.

See DpExchange.Gemini.Private.funding_amount_report/3.

funding_payment_report(opts \\ [])

@spec funding_payment_report(keyword()) ::
  {:ok, [map()]} | {:error, term()} | {:refused, term()}

The funding payment report as JSON.

See DpExchange.Gemini.Private.funding_payment_report/2 — the query string is part of what is signed.

funding_payment_report_file(opts \\ [])

@spec funding_payment_report_file(keyword()) ::
  {:ok, binary()} | {:error, term()} | {:refused, term()}

The funding payment report as a spreadsheet — the venue's bytes, unparsed.

See DpExchange.Gemini.Private.funding_payment_report_file/2.

get_account_margin(opts \\ [])

@spec get_account_margin(keyword()) ::
  {:ok, map()} | {:error, term()} | {:refused, term()}

The perpetuals margin account — collateral, leverage, and the estimated liquidation price.

See DpExchange.Gemini.Private.get_account_margin/2. get_positions/1 publishes no liquidation price; this is where it lives.

get_clearing_order(clearing_id, opts \\ [])

@spec get_clearing_order(
  String.t(),
  keyword()
) :: {:ok, map()} | {:error, term()} | {:refused, term()}

One clearing order's state.

See DpExchange.Gemini.Private.get_clearing_order/3. Read is_confirmed, not status.

get_contract_stats(symbol, opts \\ [])

Risk statistics for a perpetual — mark, index and open interest.

Public. See DpExchange.Gemini.Rest.get_contract_stats/2: mark and index are separate prices with separate meanings, and neither is what the contract last traded at.

get_deposit_address(asset, network, opts \\ [])

A fresh deposit address for asset on network.

See DpExchange.Gemini.Private.get_deposit_address/4 — in particular why memo_required comes back nil rather than false.

get_funding(symbol, opts \\ [])

Funding for a perpetual — settled, projected, and when the next one lands.

Public. See DpExchange.Gemini.Rest.get_funding/2: the settled amount and the venue's estimate are different facts and stay in different fields.

get_fx_rate(pair, at, opts \\ [])

A foreign-exchange reference rate for pair at at.

See DpExchange.Gemini.Rest.get_fx_rate/3 — including why this is not a rate the venue trades at, and why the source is carried separately from the provider.

get_margin_account(opts \\ [])

@spec get_margin_account(keyword()) ::
  {:ok, map()} | {:error, term()} | {:refused, term()}

The spot margin account summary — a different margin system from the perpetuals one.

See DpExchange.Gemini.Private.get_margin_account/2.

get_margin_rates(opts \\ [])

@spec get_margin_rates(keyword()) ::
  {:ok, [map()]} | {:error, term()} | {:refused, term()}

Margin interest rates for every borrowable asset.

See DpExchange.Gemini.Private.get_margin_rates/2. Three rates per currency, and taking the hourly one for the annual is an error of four orders of magnitude that still looks like a rate.

get_notional_balances(credentials, currency, opts)

Every balance, each also valued in one notional currency.

See DpExchange.Gemini.Private.get_notional_balances/3. The quantity is the ledger; the notional figure beside it is Gemini's valuation of that quantity, and reconciling a position uses get_balances/2 instead.

get_payment_method(credentials, id, opts \\ [])

Gemini publishes no per-method read.

/v1/payments/methods returns the whole set and there is no path taking a method identifier. Filtering the listing here would answer with a snapshot while looking like a read, which is exactly the difference get_payment_method/3 exists to draw.

get_positions(opts \\ [])

Open positions on the perpetuals book.

See DpExchange.Gemini.Private.get_positions/2. Gemini sends a negative quantity for a short and this returns a positive size with an explicit :side — a sign convention is a fact about one venue's JSON, not about the market.

get_roles(opts \\ [])

The roles this API key carries.

See DpExchange.Gemini.Private.get_roles/2. Three booleans rather than one role, because Fund Manager and Trader combine and Auditor does not combine with anything.

get_staking_balances(opts \\ [])

Staked positions, one per asset.

See DpExchange.Gemini.Private.get_staking_balances/2. Three amounts, kept apart: the whole position can be redeemable and none of it tradable.

get_staking_history(opts \\ [])

Movements in and out of staked positions.

See DpExchange.Gemini.Private.get_staking_history/2. A redemption's progress travels with it; nil is "not reported", not "complete".

get_staking_rates(opts \\ [])

What each provider pays for staking each asset.

Public. See DpExchange.Gemini.Rest.get_staking_rates/1 — percentages only, both named, and :apy_pct never derived from :rate_pct.

get_staking_rewards(opts \\ [])

Rewards accrued over a window.

See DpExchange.Gemini.Private.get_staking_rewards/2. The window travels with the number, because the same number is a good day or a poor quarter without it.

get_trade_volume(credentials, opts)

The account's own traded volume, one row per symbol per day.

get_trades(symbol, opts \\ [])

Recent public trades — the tape.

See DpExchange.Gemini.Rest.get_trades/2, including why type is the taker's side and why broken trades are excluded unless asked for.

get_transactions(credentials, opts)

Everything that moved on this account — wider than fills and wider than transfers.

See DpExchange.Gemini.Private.get_transactions/2; summing it is not a balance.

list_accounts(opts \\ [])

@spec list_accounts(keyword()) ::
  {:ok, [map()]} | {:error, term()} | {:refused, term()}

Every subaccount in the group.

Venue-specific. See DpExchange.Gemini.Private.list_accounts/2 — the venue caps this at 500 and does not paginate.

list_approved_addresses(opts \\ [])

The addresses this account may withdraw to on opts[:network].

See DpExchange.Gemini.Private.list_approved_addresses/2 — an address on the list can still be time-locked.

list_clearing_brokers(opts \\ [])

@spec list_clearing_brokers(keyword()) ::
  {:ok, [map()]} | {:error, term()} | {:refused, term()}

Broker clearing orders — a different row shape from list_clearing_orders/1.

See DpExchange.Gemini.Private.list_clearing_brokers/2.

list_clearing_orders(opts \\ [])

@spec list_clearing_orders(keyword()) ::
  {:ok, [map()]} | {:error, term()} | {:refused, term()}

Clearing orders this account is party to.

See DpExchange.Gemini.Private.list_clearing_orders/2. Expiration and submission are different windows.

list_clearing_trades(opts \\ [])

@spec list_clearing_trades(keyword()) ::
  {:ok, [map()]} | {:error, term()} | {:refused, term()}

Clearing trades — the orders that completed.

See DpExchange.Gemini.Private.list_clearing_trades/2. opts[:since_nanos] is nanoseconds, unlike every other timestamp on this venue.

list_custody_fees(credentials, opts)

What Gemini charged this account for holding assets.

See DpExchange.Gemini.Private.list_custody_fees/2. An empty list is "nothing charged in this window", never "no such fee".

list_fee_promos(opts \\ [])

Symbols currently carrying a promotional fee.

See DpExchange.Gemini.Rest.list_fee_promos/1 — not get_fees/2, which is the schedule applying to a credential.

list_funding_payments(opts \\ [])

@spec list_funding_payments(keyword()) ::
  {:ok, [map()]} | {:error, term()} | {:refused, term()}

Funding payments this account actually paid or received.

See DpExchange.Gemini.Private.list_funding_payments/2. Not get_funding/2, which is the contract's rate rather than this account's ledger.

list_networks(asset, opts \\ [])

The networks an asset moves over, or the assets a network carries.

See DpExchange.Gemini.Private.list_networks/2 — in particular why the network→assets direction is scoped to the credential and an empty answer does not describe the network.

list_payment_methods(credentials, opts)

The funding sources this account can move fiat through.

live?(opts \\ [])

@spec live?(keyword()) :: boolean()

Whether the environment opts resolves to moves real money.

Resolves opts[:environment] through the same precedence as every call this package makes — an explicit option, then DpExchange.Core.Config, then :production — and answers with DpExchange.Gemini.Environment.live?/1. Meant as a check a caller makes of itself before a money-moving call such as place_order/3 or withdraw/5, in the same spirit as capabilities/0's own declaration: the default is production and that is the direction where a wrong guess costs money, so a caller that wants to be certain asks rather than assumes.

if DpExchange.Gemini.live?(opts) do
  # confirm with the human before placing this order
end

next_funding_timestamp(symbol, opts \\ [])

@spec next_funding_timestamp(
  String.t(),
  keyword()
) :: {:ok, DateTime.t()} | {:error, term()} | {:refused, term()}

When the next funding calculation lands, on its own.

See DpExchange.Gemini.Rest.next_funding_timestamp/2. The venue answers with a bare integer here; get_funding/2 carries the same value beside the amounts.

place_orders(credentials, requests, opts)

Not supported. Gemini places one order per request.

Its multi-order surface is cancel_all_orders/2 and the session-scoped cancels, which destroy rather than create. A caller placing several here calls place_order/3 several times and reconciles the outcomes itself.

preview_margin_order(request, opts \\ [])

@spec preview_margin_order(
  map(),
  keyword()
) :: {:ok, map()} | {:error, term()} | {:refused, term()}

What a spot order would do to this account's margin. Places nothing.

See DpExchange.Gemini.Private.preview_margin_order/3, including which of amount and total_spend the venue requires for each order shape.

preview_order(credentials, request, opts \\ [])

Not supported. Gemini publishes no endpoint answering what an order would cost.

It does publish POST /v1/margin/order/preview — a margin impact preview, returning pre- and post-order risk statistics for a hypothetical spot order. That is a different question, and answering the cost question with margin statistics is the nearby substitute this family refuses. It is reachable on its own terms, as preview_margin_order/2.

Declared through supports_order_preview: false, so a consumer routes around it rather than discovering the refusal at call time.

quote_conversion(from, to, amount, opts \\ [])

Quotes a conversion — Gemini's Instant quote. See DpExchange.Gemini.Private.quote_conversion/4, including when it refuses to guess the direction.

refresh_access_token(client_id, refresh_token, opts \\ [])

@spec refresh_access_token(String.t(), String.t(), keyword()) ::
  {:ok, map()} | {:error, term()} | {:refused, term()}

Exchanges a refresh token for a new access token. Credential use, not consent.

Venue-specific. See DpExchange.Gemini.Private.refresh_access_token/3 — a different host from every other endpoint, and the response carries a new refresh token that replaces the one sent.

remove_approved_address(network, address, opts \\ [])

Removes address from the allowlist for network.

rename_account(id, name, opts \\ [])

Renames a subaccount.

id is the subaccount's shortname and name is its new display name. To change the shortname itself — which changes how the account is addressed — pass opts[:shortname]; see DpExchange.Gemini.Private.rename_account/2.

replace_order(credentials, id, request, opts \\ [])

Not supported. Gemini has no atomic replace; a caller cancels and re-places.

That is not equivalent — it opens a window in which no order is live — which is why supports_order_replace: false is a claim about risk rather than convenience.

request_approved_address(network, address, label, opts \\ [])

Asks the venue to allowlist address on network.

A successful response is not permission to withdraw — see DpExchange.Gemini.Private.request_approved_address/5.

revoke_access_token(opts \\ [])

@spec revoke_access_token(keyword()) ::
  {:ok, map()} | {:error, term()} | {:refused, term()}

Revokes an access token. Only reachable with an OAuth token, not an API key.

Venue-specific. See DpExchange.Gemini.Private.revoke_access_token/2.

stake(asset, amount, opts \\ [])

Stakes amount of asset. This moves funds.

See DpExchange.Gemini.Private.stake/4. opts[:provider_id] is required — the same asset stakes with several providers at different rates.

transfer_internal(asset, amount, transfer_opts, opts \\ [])

Moves funds between two accounts at this venue. Not withdraw/5.

opts[:from] and opts[:to] are required.

unstake(asset, amount, opts \\ [])

Redeems amount of a staked asset. Returns before the redemption completes.

See DpExchange.Gemini.Private.unstake/4. :amount_remaining is non-zero for as long as the asset is unbonding.

venue_does_not_serve()

@spec venue_does_not_serve() :: [{atom(), arity()}]

Endpoints the venue does not serve, as distinct from ones this package has not ported.

Both answer {:error, :not_supported}, and a caller acts the same way on either — but they mean different things to anyone deciding what to build next, so they are told apart here rather than flattened into one list.

Every entry is recorded with its source and the date consulted in docs/reference/gemini/negative-claims.md.

withdraw(asset, network, amount, address, opts \\ [])

Moves funds. Withdraws amount of asset over network to address.

See DpExchange.Gemini.Private.withdraw/6 — in particular the idempotency key this always sends, and the memo requirement this package cannot check for you.