nexus-exchange (Python)
Official Python SDK for the Nexus Exchange API — a thin, typed wrapper over the public REST API.
⚠️ Experimental / in development. This is an early skeleton. The surface is small and may change without notice; only the endpoints in the table below are implemented. For the complete, ahead-of-this surface use the Rust SDK. This SDK exists so agents and bots can be written in Python or Rust depending on the libraries they need.
Install
pip install nexus-exchange # once published; for now, install from source:
pip install git+https://github.com/nexus-xyz/nexus-exchange-py
Requires Python 3.10+. Depends only on httpx.
Quick start
from nexus_exchange import Client
with Client() as client: # defaults to the public gateway
for market in client.fetch_markets():
print(market.market_id)
ticker = client.fetch_ticker("BTC-USDX-PERP")
print(ticker.last, ticker.mark_price)
No credentials are needed for market data. See examples/public_market_data.py.
What's supported
| Area | Status |
|---|---|
Markets — GET /markets, /markets/summary, /tickers |
✅ implemented |
Ticker — GET /markets/{id}/ticker |
✅ implemented |
Order book — GET /markets/{id}/orderbook |
✅ implemented |
Trades — GET /markets/{id}/trades |
✅ implemented |
OHLCV candles — GET /markets/{id}/candles |
✅ implemented |
Funding / mark price / status — GET /markets/{id}/{funding,mark-price,status} |
✅ implemented |
ADL events — GET /markets/{id}/adl-events, /account/{addr}/adl-history |
✅ implemented |
Health — GET /health |
✅ implemented |
| HMAC request signing (the plumbing for authed calls) | ✅ implemented |
Wallet-signed auth — sign_in (EIP-191) + register_agent (EIP-712) |
✅ implemented |
| CCXT-compatible adapter — public market data | ✅ implemented |
Error taxonomy (terminal vs transient, incl. the jurisdiction 403) |
✅ implemented |
Typed money — Decimal prices/sizes (full payload still on .raw / .info) |
✅ implemented |
Account reads — GET /account, /positions, /positions/closed, /fills, /withdrawals, /account/rate-limit |
✅ implemented |
Portfolio — GET /account/state (summary + positions, incl. withdrawable), /account/summary, /account/fees, /account/portfolio-history, /account/equity-history |
✅ implemented |
Trading — POST /orders, /orders/batch; GET /orders, /orders/{id}, /orders/history; DELETE /orders, /orders/{id} |
✅ implemented |
Funds — POST /account/deposit, /account/credit |
✅ implemented |
Bridge — GET /bridge/assets, /bridge/deposits(/{id}); POST/GET /bridge/deposit-addresses |
✅ implemented |
Keys / agents / WS token — /keys, /agents, POST /ws-tokens |
✅ implemented |
Admin tiers — GET/PUT/DELETE /admin/tiers |
✅ implemented |
Cursor pagination — cursor + X-Next-Cursor on all five paginated GETs |
✅ implemented |
| WebSocket streaming | ❌ not yet |
Rate-limit-aware retry (429 / Retry-After, token bucket) |
❌ not yet |
| OAuth auth | ❌ not yet |
The hand-maintained coverage source of truth is endpoints.txt.
Anything not listed there is not wrapped yet — contributions welcome.
Networks
Network is the network axis — which chain, and whose money:
Network |
Funds | Faucet | Notes |
|---|---|---|---|
Network.TESTNET |
Play (synthetic USDX) | Yes | Default. The safe target for integration work and CI. |
Network.MAINNET |
Real | No | Collateral is USDX bridged from Ethereum Mainnet. |
Network.LOCAL |
Play | Yes | A locally run indexer. Not a public network. |
Each member bundles its config — REST bases, both WebSocket bases, funds semantics and the EIP-712 signing domain:
from nexus_exchange import Client, Funds, Network
Network.TESTNET.ws_market_data_url # 'wss://api.testnet.nexus.xyz/stream'
Network.TESTNET.ws_authenticated_url # 'wss://api.testnet.nexus.xyz/ws'
Network.MAINNET.funds # Funds.REAL — branch on this, never on the host string
with Client(Network.TESTNET) as client:
...
funds is a tri-state, not a boolean: Funds.REAL, Funds.PLAY, or
Funds.UNKNOWN for a target whose funds were never declared. Guard on PLAY
positively so an undeclared target fails closed:
if client.network.funds is not Funds.PLAY: # correct — UNKNOWN is refused
refuse()
if client.network.funds is Funds.REAL: # WRONG — UNKNOWN slips through
refuse()
Whether a faucet exists is tracked separately (has_faucet): "not real money"
does not imply "can mint more of it".
The two WebSocket bases and published_rest_base are the spec's durable
per-network values, recorded here so they live in one place. The hosted ones do
not resolve yet (DNS is not configured), and this SDK ships no WebSocket client, so
treat them as published targets rather than something to connect to today. What
the client actually sends to is base_url / direct_base_url.
Three things worth knowing before you pick one:
- Mainnet has no default base URL yet. Its host (
api.nexus.xyz) is published but DNS is not live, soClient(Network.MAINNET)raises rather than guessing a real-funds target or quietly falling back to testnet. Passbase_url=…explicitly to target it. - Credentials never cross networks. Session tokens, HMAC keys and agent keys are minted per network and are invalid on any other. Switching network means a new client and new credentials — never carry a signature, nonce or agent registration across.
- The signing domain's
chain_idis not published statically. Read it from the edge's/metadatafor the network you are on.register_agentrefuses to sign without one rather than defaulting: a wrong domain either fails verification or produces a signature valid on a different network.
The retired stable / beta release channels were never networks. stable
became Network.TESTNET (same target); beta is now a custom target:
Client(
NetworkConfig.custom(
label="beta",
funds=Funds.UNKNOWN, # that deploy's funds are not ours to assert
base_url="https://beta.exchange.nexus.xyz/api/exchange",
# direct_base_url defaults to base_url, which is the right shape for a
# gateway deploy: the /api/v1 surface is mounted under the prefix, not at
# the host root. Only set it if you have measured that deploy serving the
# two surfaces apart.
)
)
Custom targets
For a deployment this SDK ships no hostname for, build the config yourself and
pass it wherever a Network goes. It carries the whole bundle, not just a
URL — that is what stops a client reporting play-funds guardrails while pointed
somewhere else:
from nexus_exchange import Client, Funds, NetworkConfig
config = NetworkConfig.custom(
label="dev", # required
funds=Funds.PLAY, # required — no default
base_url="https://exchange.example.com",
direct_base_url="https://exchange.example.com", # optional; defaults to base_url
has_faucet=False, # absent until declared
chain_id=None, # unknown ⇒ signing refuses
)
with Client(config) as client:
...
label and funds are required and have no defaults. There is no safe default
for funds: assuming play makes every guardrail lie on a real-funds deployment,
and assuming real makes a dev target unusable. Pass Funds.UNKNOWN when it truly
is unknown — the guards treat that as unsafe, which is the honest answer.
label is validated ([A-Za-z0-9._-], max 64, no . or ..) because it is a
key: it is what stored credentials are namespaced under across these SDKs, so
a label that can escape a directory or split a keyring entry would let one target
address another's credentials.
Both base URLs are validated for scheme and host, and refused if they carry
userinfo or a query or fragment. The request path is appended to the base, so
https://host?a=1 would be sent and signed as
https://host?a=1/api/v1/orders, and https://api.nexus.xyz@evil.com reads as
the published host to anyone skimming a config file while the requests — and the
API keys — go to evil.com. A path is accepted: a base under
/api/exchange is a real, working topology. The same checks apply to a raw
base_url / direct_base_url override, including the one mainnet requires.
A bare base_url with no network named is deprecated (#61) — build
the config instead. Both reach the same host; only the config says what is
behind it, so the bare form yields Funds.UNKNOWN and no faucet, and every
guard treats that as unsafe:
Client(base_url="https://exchange.example.com") # deprecated: UNKNOWN, no faucet
Client(NetworkConfig.custom(label="dev", funds=Funds.PLAY, base_url="https://exchange.example.com"))
It still works, unchanged, and does not warn at runtime — the marker each
SDK carries was chosen per ecosystem, and Python's is prose. So if
you do not read this section you get no signal at all, which is exactly why a
release that warns has to come before one that removes it: a real
DeprecationWarning, which Python shows by default when the caller is
__main__, i.e. in the local scripts and notebooks this form exists for.
Nothing is removed here, and nothing is removed before that runway has shipped.
What is deprecated is the selector — a URL that picks the target on its own.
direct_base_url is a modifier and stays, and so does a URL passed alongside a
named network, which keeps that network's semantics because the caller has
declared them:
Client(Network.LOCAL, base_url="http://127.0.0.1:8080") # stays play funds + faucet
Client(Network.MAINNET, base_url="https://api.nexus.xyz") # stays real funds
Custom configs are never added to the network map and are not addressable by
name — Network("dev") still raises.
Routing: direct /api/v1 service vs. legacy gateway
As the REST gateway is retired, backend services expose their own
REST API under an /api/v1 prefix. That prefix is a path, not a host: it
is mounted wherever the deployment serves the direct service, which on the
hosted deploy is under the …/api/exchange gateway prefix
(https://exchange.nexus.xyz/api/exchange/api/v1/…) and on a direct indexer
host is the bare origin. The client appends /api/v1 to direct_base_url, so
that field carries whichever base applies. The migrated market-data and
account/trading routes now target this direct service; the HMAC signature covers
the full path (e.g. /api/v1/orders), independent of the base. Routes with no
/api/v1 equivalent yet — GET /markets, /health, ADL history, GET /orders/{id}, deposits, keys/agents, WS tokens and admin tiers — stay on the
legacy gateway. This split is internal; method names and signatures are
unchanged. A custom base_url overrides both bases; pass direct_base_url
alongside it to target a deploy that serves the two surfaces apart.
Either topology is accepted. A gateway-prefixed direct_base_url used to be
rejected at construction, on the premise that /api/v1 is served only at the
host root. Production measurement says otherwise (rs#131):
…/api/exchange/api/v1/markets/summary answers 200 application/json while
…/api/v1/markets/summary answers 404 text/html, and junk segments under the
gateway prefix answer a JSON NOT_FOUND — so the gateway mounts /api/v1
specifically rather than routing permissively. A direct indexer host plausibly
serves it at the root too, so both are real and which applies is a property of
the URL, not something this client can assert. The rejection made the working
configuration unreachable on the deploy targeted by default, so it is gone
(#60).
If you are coming from another Nexus SDK
The field names differ, so line them up before copying a base URL across — the
two-URL split here is one field in the TypeScript client. Every field below
holds a deployment base with no /api/v1 — Python, TypeScript and Rust all
append that prefix themselves, on the direct routes only:
| Surface | Python | TypeScript | Base value (testnet) | Composed URL |
|---|---|---|---|---|
Direct /api/v1 service |
direct_base_url |
baseUrl |
https://exchange.nexus.xyz/api/exchange |
…/api/exchange/api/v1/orders |
Legacy /api/exchange gateway |
base_url |
not modelled | https://exchange.nexus.xyz/api/exchange |
…/api/exchange/ws/token |
On this deploy all of these hold the same string, because the direct surface is mounted under the gateway prefix — so copying a base across the three SDKs gives the right answer today, and Python's two fields stay separate only so a deploy that does serve the surfaces apart can still say so.
What does not survive the copy is a base that already carries /api/v1 —
including the value Network.TESTNET.direct_base_url composes to, and TypeScript
baseUrl's own pre-0.3 default. Every SDK appends the prefix itself, so such a
base sends /api/v1/api/v1/orders while signing the correct /api/v1/orders:
a routing failure whose signature looks fine. TypeScript rejects it at
construction; Python does not check, so strip the prefix before pasting a URL
into base_url or direct_base_url.
Authentication
Signed requests use the canonical HMAC-SHA256 scheme the exchange verifies:
<timestamp>\n<METHOD>\n<path>\n<query>\n<sha256hex(body)>
signed with the hex-decoded secret, sent as x-signature with x-api-key and
x-timestamp. Pass api_key / api_secret to Client. Note the default public
gateway proxies signed calls to the site account; to act as a specific account,
point base_url (or Network.LOCAL) at a direct gateway that verifies client
HMAC. Typed authed methods are not built yet — Client._request(..., signed=True)
is the low-level escape hatch in the meantime.
Wallet-signed auth
The HMAC scheme above signs requests with an API key. The two wallet-authorized
flows are different: an EVM wallet key authorizes a session or an agent
key, with the signature carried in the request body (these POSTs are
themselves unauthenticated). This mirrors the
Rust SDK's EthSigner and the
digests are cross-checked, byte-for-byte, against the server's known-answer
vectors.
EthSigner is a pure signer — the caller supplies the private key (a library
pattern; there is no key prompt or file handling). It needs the
eth-account dependency, which ships
with the SDK.
from nexus_exchange import Client, EthSigner
signer = EthSigner.from_hex("0x<wallet-private-key>") # you own the key
with Client() as client:
# EIP-191 personal_sign → POST /auth/login → session token.
session = client.sign_in(signer)
print(session.address, session.token) # token is a secret
# EIP-712 → POST /agents/register. expires_at_ms / nonce / chain_id are
# caller-supplied; expiry must fall in [now + 1d, now + 90d].
registration = signer.register_agent(
agent="0x<agent-address>",
expires_at_ms=1_782_000_000_000,
nonce=1,
chain_id=393,
label="my-bot",
)
registered = client.register_agent(registration)
print(registered.agent_address, registered.expires_at)
Bridge
Deposit funds across chains via the /bridge surface (USDC/USDX in Phase A).
Get a deposit address (idempotent per account + chain), send funds, then poll a
deposit until status is credited:
assets = client.fetch_bridge_assets()
addr = client.create_bridge_deposit_address(assets.chains[0].chain)
print(f"send USDC/USDX to {addr.address} on {addr.chain}")
deposits = client.fetch_bridge_deposits(limit=1, chain=addr.chain)
# deposits[0].status: "detected" | "confirming" | "credited" | "failed"
See examples/bridge_deposit.py.
Portfolio
One signed call returns the whole account state — summary aggregates plus every open position, from a single coherent read:
from nexus_exchange import PortfolioWindow
state = client.fetch_account_state()
print(state.summary.total_equity, state.summary.withdrawable) # None if unreported
for pos in state.positions:
# Enriched risk detail; None + a `*_error` reason when not derivable.
print(pos.market_id, pos.notional_value, pos.roe, pos.funding_paid)
print(pos.leverage, pos.leverage_error) # None, "margin_state_not_mirrored"
summary = client.fetch_account_summary() # the aggregates alone, no positions
print(summary.withdrawable)
fees = client.fetch_account_fees()
print(fees.maker_fee_bps, fees.taker_fee_bps) # maker may be negative (a rebate)
history = client.fetch_portfolio_history(PortfolioWindow.WEEK, limit=100)
for point in history.points: # oldest first
print(point.timestamp_ms, point.equity, point.pnl, point.volume)
withdrawable is engine-authoritative free margin floored at zero. The
endpoints serving it fail closed with 502 authoritative_margin_unavailable
(an ApiError) rather than returning a local estimate — that is transient, so
retry rather than substituting a self-computed figure.
Every money field is a Decimal, and decoding never invents one. A field the
spec marks optional decodes to None when unreported, never a defaulted 0
that would read as a real balance — while a reported "0" stays Decimal(0). A
field the spec marks required decodes strictly: if it is absent, null or
malformed, the call raises DecodeError (a NexusExchangeError, and a
ValueError) rather than handing back a plausible figure the server never sent.
That extends to lists — a malformed point or position raises instead of silently
dropping out of the series.
One 4xx is worth catching by itself. A jurisdiction control refuses
state-changing calls — and, on the sanctions list, reads too — with a 403 that
is permanent for your origin, so retrying it never helps:
from nexus_exchange import RestrictedJurisdictionError
try:
client.create_order(order)
except RestrictedJurisdictionError as err:
# US_RESTRICTED | GEO_UNRESOLVED | RESTRICTED_JURISDICTION — and the list is
# open, so treat anything unrecognized as permanent too. Never match on
# `err.message`; the spec marks its wording unstable.
print("refused:", err.block_reason)
It subclasses ApiError, so an existing except ApiError still catches it.
Pagination
The list endpoints return a page of results plus an opaque cursor for the next
page, carried in the X-Next-Cursor response header (spec v0.7.2). All five
paginated endpoints are wrapped, each with a flat fetch_* (first page), an
iter_* (every page) and a fetch_*_page (one page + its cursor):
| endpoint | methods | limit max |
|---|---|---|
GET /markets/{id}/trades |
fetch_trades / iter_trades / fetch_trades_page |
1000 |
GET /fills |
fetch_my_trades / iter_my_trades / fetch_my_trades_page |
1000 |
GET /orders/history |
fetch_order_history / iter_order_history / fetch_order_history_page |
500 |
GET /positions/closed |
fetch_closed_positions / iter_closed_positions / fetch_closed_positions_page |
200 |
GET /account/equity-history |
fetch_equity_history / iter_equity_history / fetch_equity_history_page |
720 (also the default) |
iter_* walks every page for you, lazily — one request per page, driven by the
cursor:
for fill in client.iter_my_trades(limit=500): # limit = page size, not a total
print(fill.id, fill.price, fill.size)
# Stop early and the requests stop with you.
for trade in client.iter_trades("BTC-USDX-PERP", limit=100, max_pages=5):
...
fetch_*_page is the manual form, for when the cursor has to outlive the
process (a resumable backfill):
page = client.fetch_my_trades_page(limit=500)
save_checkpoint(page.next_cursor) # None once page.is_last
page = client.fetch_my_trades_page(limit=500, cursor=load_checkpoint())
Cursors are opaque — never parse one. Termination rules:
- No
X-Next-Cursorheader ⇒ the last page. Not an error, and not a reason to retry. - An empty page that still carries a cursor is not the end — a sparse window keeps paging.
- A server that hands back the same cursor it was given cannot advance, so
the walk raises
PaginationErrorinstead of re-requesting one page forever. A silent stop would report a truncated history as complete. - Nothing else bounds how far back a walk goes; pass
max_pageswhen that matters.
limit sets the page size and is validated against that endpoint's spec
maximum before the request (TRADES_LIMIT_MAX, FILLS_LIMIT_MAX,
ORDER_HISTORY_LIMIT_MAX, CLOSED_POSITIONS_LIMIT_MAX,
EQUITY_HISTORY_LIMIT_MAX — see the table above). They are not
interchangeable: 500 is valid on /orders/history and out of range on
/positions/closed. On /account/equity-history the maximum is also the
server's default, so one page normally covers the whole ~1h window.
In particular the 366 bound belongs only to /account/portfolio-history
(PORTFOLIO_LIMIT_MAX), which has no cursor parameter at all — applying it to
a paginated endpoint would reject valid requests, and on /account/equity-history
it sits below the server's own default of 720.
CCXT compatibility
CCXT is the unified API the Python quant/retail
stack (freqtrade, hummingbot, bots) speaks. nexus_exchange.ccxt_adapter
exposes the exchange under CCXT's unified method names and return shapes, so
CCXT-shaped code can talk to Nexus with minimal changes.
This first increment covers describe() and public market data —
fetch_markets, fetch_ticker, fetch_tickers, fetch_order_book,
fetch_ohlcv, fetch_trades, plus load_markets. Private / trading methods
are a follow-up.
from nexus_exchange.ccxt_adapter import NexusExchange
with NexusExchange() as ex:
ex.load_markets()
ticker = ex.fetch_ticker("BTC-USDX-PERP") # unified ticker dict
book = ex.fetch_order_book("BTC-USDX-PERP", limit=10) # [price, amount] levels
candles = ex.fetch_ohlcv("BTC-USDX-PERP", "1m", limit=100) # [ts,o,h,l,c,v]
trades = ex.fetch_trades("BTC-USDX-PERP", limit=50)
The adapter returns plain CCXT-shaped dict/list structures and does not
import or subclass ccxt — it follows CCXT's conventions without taking the
dependency. See examples/ccxt_market_data.py.
API version
Currently targets Exchange API spec v0.8.1.
The pinned version lives in .api-version; the spec itself is
published by
nexus-xyz/nexus-exchange-api.
This repo does not vendor a copy. Two CI checks keep the pin honest, answering
different questions:
spec-drift— does the SDK still match the spec it pins? It fetches the pinned release and enforces, both ways, that every operation inendpoints.txtexists in that spec and that the operations the client code requests are exactly that list.drift— is the pin still the latest release? It compares.api-versionagainst the spec repo's newest tag.
The spec-autobump workflow opens the bump PR when a newer spec releases,
labelling it breaking or non-breaking from an
oasdiff classification; spec-drift runs on
that PR too, so a bump that would require SDK changes cannot land quietly. The
line above is bot-managed; the table below is maintained by hand when an SDK
release ships a new pin.
Every request advertises the pinned tag in an X-Nexus-Api-Version header (and
identifies itself with a User-Agent: nexus-exchange-py/<version>). Override the
advertised tag per client with Client(api_version="vX.Y.Z") if you need to
target a specific contract version.
| SDK version | API spec |
|---|---|
0.1.x |
v0.4.0 |
0.2.x |
v0.6.2 |
0.3.x |
v0.7.1 |
Development
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
pytest # tests — unit (mocked httpx) + an integration smoke over a
# real loopback socket; both run offline, no network
ruff check . # lint
mypy src # types
tests/test_integration_smoke.py stands up a real local HTTP server and drives
a real Client against it (fetch_markets / fetch_ticker / health_check),
mirroring the Rust SDK's wiremock tests — so the transport, URL building, and
JSON decoding are exercised end to end, not just the mock layer.
For an opt-in round-trip against a live gateway (read-only, unauthenticated; not run in CI), use the smoke script:
python scripts/smoke.py # testnet (default; play funds)
python scripts/smoke.py --network local
python scripts/smoke.py --base-url http://localhost:9090
License
Dual-licensed under MIT or Apache-2.0, at your option — same as the other Nexus Exchange SDKs.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file nexus_exchange-0.4.0.tar.gz.
File metadata
- Download URL: nexus_exchange-0.4.0.tar.gz
- Upload date:
- Size: 177.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6bb6b753a8f575d049563cf3bb55e94b3621c4374fd5385c5cc25e1876166d6d
|
|
| MD5 |
041fa9779dbb1066408abe91193ece71
|
|
| BLAKE2b-256 |
ba15c1839aa9de512277ed636f6489ea5be102d32fe28051f04179d932c9d9a1
|
Provenance
The following attestation bundles were made for nexus_exchange-0.4.0.tar.gz:
Publisher:
release.yml on nexus-xyz/nexus-exchange-py
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
nexus_exchange-0.4.0.tar.gz -
Subject digest:
6bb6b753a8f575d049563cf3bb55e94b3621c4374fd5385c5cc25e1876166d6d - Sigstore transparency entry: 2539161556
- Sigstore integration time:
-
Permalink:
nexus-xyz/nexus-exchange-py@82e841bdf93f2a8c102f96c17b6b7357aa8d715a -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/nexus-xyz
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@82e841bdf93f2a8c102f96c17b6b7357aa8d715a -
Trigger Event:
push
-
Statement type:
File details
Details for the file nexus_exchange-0.4.0-py3-none-any.whl.
File metadata
- Download URL: nexus_exchange-0.4.0-py3-none-any.whl
- Upload date:
- Size: 80.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b9f92d5356708558220331479d7951ed7454b5649dabfd96e98c6099b2d35a21
|
|
| MD5 |
823888f13d41b9cc8638e7521904b017
|
|
| BLAKE2b-256 |
0ee2455db0da301bd50e98ee51785981643982a30571546ee3a60e6168df6e3e
|
Provenance
The following attestation bundles were made for nexus_exchange-0.4.0-py3-none-any.whl:
Publisher:
release.yml on nexus-xyz/nexus-exchange-py
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
nexus_exchange-0.4.0-py3-none-any.whl -
Subject digest:
b9f92d5356708558220331479d7951ed7454b5649dabfd96e98c6099b2d35a21 - Sigstore transparency entry: 2539161658
- Sigstore integration time:
-
Permalink:
nexus-xyz/nexus-exchange-py@82e841bdf93f2a8c102f96c17b6b7357aa8d715a -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/nexus-xyz
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@82e841bdf93f2a8c102f96c17b6b7357aa8d715a -
Trigger Event:
push
-
Statement type: