Skip to content

chore(release): 0.2.0 - #45

Open
isaacdoidge wants to merge 1 commit into
masterfrom
release/0.2.0
Open

isaacdoidge wants to merge 1 commit into
masterfrom
release/0.2.0

Conversation

@isaacdoidge

Copy link
Copy Markdown
Collaborator

What

Prepares the 0.2.0 release, as RELEASING.md describes:

Breaking changes in 0.2.0

Entry From
Submit helpers and faucet raise TransactionFailedError unless the transaction succeeds; wait_for_transaction=False keeps the old behaviour #19
Waits return only on a final status; an unknown hash times out with last_status=None instead of raising a 404 #19
max_gas_amount and expiration_ttl default to None (estimated max gas; expiry 5 s inside the node's maximum) #25
Feed cursors are None at the end of a feed (was "") #30
Primitive type tags carry no value; .value removed; write U64Tag() #20
str(ApiError) holds the node's message; ClientTimeoutError; pool wait limited by the timeout #35
view() and faucet() raise UnexpectedResponseError for a malformed 200 body #35
Path values validated before sending; base URL with ? or # refused #21
Multi-signatures sorted by key index; ValueError instead of assert #24
behave and types-six no longer runtime dependencies #22
asymmetric_crypto_wrapper.index_to_bitmap_value removed #24
str(PrivateKey) returns a redacted form #18

account_history's page type (#43) and the user-task priority refusal (#33) are not listed as breaking: neither API
exists in 0.1.1.

Merging

Merging this is followed by the v0.2.0 tag, which runs the publish workflow. Merge once the PyPI trusted publisher
and the pypi environment are configured, and update the date of the 0.2.0 entry to the publish day first.

Checks

make fmt-check, make lint, make typecheck, make test-unit (397 tests, OK, 26 node-backed skipped),
make test-spec (248 scenarios), make build: pass. The built wheel's metadata: Version: 0.2.0,
License-Expression: Apache-2.0, Requires-Dist httpx[http2], pynacl>=1.6.2, typing-extensions; py.typed
included.

🤖 Generated with Claude Code

- pyproject.toml: version 0.2.0; uv.lock updated to match (only the
  supra-sdk package entry changes).
- CHANGELOG.md: new, in the Keep a Changelog 1.1.0 format, with the
  entries for 0.2.0, 0.1.1 and 0.1.0. The 0.2.0 entry lists the changes
  since v0.1.1 as they stand at this commit; entries marked Breaking say
  what to change in code written for 0.1.1.
- README.md: the first sentence of the Compatibility section names the
  SDK version, 0.2.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment thread CHANGELOG.md
sent the request. Addresses and table handles must be account addresses (an `AccountAddress` or a hex string) and are
sent in canonical form; transaction and block hashes must be 64 hex digits with an optional `0x`; heights and epochs
must be non-negative integers or strings of decimal digits; other values, such as resource types, module names and
event types, must be non-empty strings. Each value is sent as one percent-encoded path segment, so a value containing

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This guarantee doesn't hold for . and ... encode_path_segment leaves dots unencoded (quote(value, safe=":,")), and httpx removes dot segments when it builds the request URL, so the value escapes its segment instead of raising:

await client.account_module("0x1", "..")   # GET https://host/prefix/rpc/v3/accounts/0x1
await client.events_by_type("..")          # GET https://host/prefix/rpc/v3

account_module then silently returns the account endpoint's body. I'd fix this in encode_path_segment before tagging, by raising ValueError for . and .., which is what this entry promises for invalid values. Narrowing the sentence would leave the bug in place.

Comment thread CHANGELOG.md
access token through `ApiClientConfig.access_token`. `ApiClient.base_url` always ends in `/`.
- **Breaking:** both `MultiSignature` classes (`ed25519` and `asymmetric_crypto_wrapper`) sort their signatures by key
index, the order in which the node pairs them with bitmap bits, so the order the caller supplies them in does not
matter and `.signatures` is sorted. The bytes of a MultiKey signature built from signatures out of index order change.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ed25519 MultiSignature changed the same way. 0.1.1 serialized ed25519.MultiSignature.signatures in the order the caller passed them, and 0.2.0 sorts them by index. So a MultiEd25519 signature built from out-of-order signatures also has different bytes, and its transaction a different hash. Suggest: "The bytes of a MultiEd25519 or MultiKey signature built from signatures out of index order change."

Comment thread CHANGELOG.md
error the input led to, such as `IndexError`).
- **Breaking:** `str()` of an `ApiError` keeps the form `{message: …, status_code: …}` and holds `ApiError.message`,
the node's message when the JSON error body has one, where 0.1.1 held the whole response body; the body is
`ApiError.body`. A request that times out raises `ClientTimeoutError` instead of the `httpx` timeout class;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

except httpx.TimeoutException still works. Code that catches one of the specific classes (httpx.ReadTimeout, httpx.ConnectTimeout, httpx.WriteTimeout, httpx.PoolTimeout) no longer catches anything, because ClientTimeoutError subclasses httpx.TimeoutException directly. Breaking entries are meant to say what to change, so it's worth adding: catch ClientTimeoutError (or httpx.TimeoutException) and read .phase to tell the phases apart.

Comment thread CHANGELOG.md
so f-strings, `print` and log output of a private key do not contain the key. `hex()` exports the key and is
unchanged. Code that used `str(private_key)` or an f-string to export the key calls `private_key.hex()`.
- `Account.store` writes the file with mode 0600, readable and writable by its owner only; in 0.1.1 the mode followed
the umask. It writes a temporary file in the same directory and renames it over the path, so an existing file is

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renaming a temp file over path also changes what happens with links. If path is a symlink (for example into a secrets mount), 0.1.1's open(path, "w") wrote through to the target. os.replace now replaces the symlink itself with a regular file and leaves the target unchanged. A hard link is split the same way. Also, the directory now has to be writable for mkstemp, where before only the file did. Worth a sentence here, since it changes where the key ends up.

Comment thread CHANGELOG.md
- **Breaking:** `faucet()` raises `UnexpectedResponseError` when a 200 body is neither `"TryLater"` nor a JSON object
whose `Accepted` is a non-empty string, including a body that is not JSON. For such bodies 0.1.1 returned `None`,
`""` or the value as it was with `wait_for_faucet=False`, raised `FaucetRequestNotAcceptedError` for a missing or
empty `Accepted` with `wait_for_faucet=True`, and raised `json.JSONDecodeError` for a body that is not JSON. Code

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This list misses one 0.1.1 outcome for these bodies. A 200 JSON body that isn't an object (for example "Busy", [], null or a number) raised AttributeError from res_data.get("Accepted"). That's the same failure the TryLater fix on line 239 describes. Code that caught AttributeError for these bodies needs the same pointer to UnexpectedResponseError.

Comment thread CHANGELOG.md
`AutomationRegistrationParams.build(...)`, which returns version 1 without a task type and version 2 with one.
`SupraClient.register_automation_task` takes `automation_task_type` and `automation_priority`; without a task type it
registers version 1 parameters, as 0.1.1 did. A priority is allowed only with `AutomationTaskType.SYSTEM`: with any
other task type or none, `build`, the `AutomationRegistrationParamsV2` constructor and `register_automation_task`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The AutomationRegistrationParamsV2 constructor doesn't raise ValueError for "none". With task_type=None and a priority, _check_task_type_and_priority raises TypeError("task_type must be an AutomationTaskType"). Only build and register_automation_task raise ValueError for a missing task type; the constructor raises ValueError only for USER. Either reword this, or make the constructor raise ValueError for None too.

Comment thread CHANGELOG.md
MultiKey `asymmetric_crypto_wrapper.MultiSignature` decode, so a `SignedTransaction` from a MultiEd25519 or MultiKey
account round-trips through BCS. A MultiKey supports Ed25519 keys only; another key type raises `ValueError` when
decoded. `MultiEd25519Authenticator.verify` verifies the signature; it raised `NotImplementedError`. The multi-key,
multi-signature and authenticator classes have `__eq__` and `__hash__`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This reads as covering every authenticator class, but only MultiEd25519Authenticator and MultiKeyAuthenticator gained both methods. Authenticator, AccountAuthenticator, Ed25519Authenticator, SingleSenderAuthenticator, MultiAgentAuthenticator and FeePayerAuthenticator define __eq__ without __hash__, so they're unhashable. SingleKeyAuthenticator has no __eq__ at all, so a single-key SignedTransaction decoded from its own bytes compares unequal to the original, while a multi-key one compares equal. Suggest naming the two classes.

Comment thread CHANGELOG.md

## [Unreleased]

## [0.2.0] - 2026-11-04

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This date is already a day behind, and per the description the publish day isn't known yet. The tag goes on the merge commit, so the date here at merge time is what ships in the tagged tree and gets copied into the GitHub release notes. It needs to be set in the last push before merging.

Comment thread CHANGELOG.md
`TransactionWaitTimeoutReachedError` with `last_status=None`. 0.1.1 raised `ApiError` with status 404 at once. Code
that caught that `ApiError` catches `TransactionWaitTimeoutReachedError` and checks `last_status is None`. Other 404
responses, such as one for a wrong base URL, still raise at once, as `ApiNotFoundError`. The first poll is sent
without a sleep before it, no poll starts after the deadline, each poll and sleep is bounded by the time remaining,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't quite true when transaction_wait_time_in_seconds is 0 or negative. _wait_until_final always makes the first poll, and with remaining <= 0 it calls asyncio.wait_for(fetch(...), timeout=None). That poll starts after the deadline and is bounded only by the HTTP timeout, plus retries if a RetryPolicy is set. Either qualify the sentence ("after the first poll") or bound the first poll too.

Comment thread CHANGELOG.md
units), and uses the gas used times 1.5, rounded up, at least 1,500 units and at most what the balance pays for.
When the simulation reports the sequence number as ahead of the account's (a transaction sent behind uncommitted
ones), the account's sequence number is read and the simulation is repeated with it; the transaction keeps its own
number. A sender given only as an address gets 20,000 units, capped by the balance. When the estimate cannot be

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 20,000-unit fallback also applies when the sender is an Account but fee_payer is only an address (create_raw_transaction(account, payload, fee_payer=address)). to_signed stays None there, so nothing is simulated. That also makes line 152 ("makes a balance read and a simulation request") wrong for both address cases, which only read the balance.

Comment thread CHANGELOG.md
timeout.
- **`ApiClientConfig.timeout`** and **`ApiClientConfig.retry`**, keyword-only, also on `SupraClientConfig`. `timeout`
(default 60) limits each phase of a request, in seconds: connecting, waiting for a pooled connection, writing and
reading. A value that is not a finite number above 0 raises `ValueError`. `retry` takes a

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ApiClientConfig(timeout=0) doesn't raise. The check runs in ApiClient.__init__, so the ValueError comes when the ApiClient or SupraClient is built. Worth saying so, because anyone validating config up front will expect the dataclass itself to reject the value.

Comment thread CHANGELOG.md
- **`TransactionConfig.gas_unit_price`** defaults to `None`, which uses the node's `min_configured_gas_price` from
`GET /rpc/v3/transactions/estimate_gas_price` instead of 100.

## [0.1.0] - 2025-09-19

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The two older dates come from different sources. PyPI has 0.1.0 uploaded on 2025-10-27 and 0.1.1 on 2026-02-10. The 0.1.1 entry uses the PyPI date, but 0.1.0 uses the tag and GitHub release date (2025-09-19). Since 0.2.0 will use the publish day, 0.1.0 should probably be 2025-10-27.

Comment thread README.md
## Compatibility

The SDK is tested against Supra node `supra_node_v11.7.2` (Mainnet and Testnet) and `supra_node_v12.0.0.rc7` (Devnet).
0.2.0 is tested against Supra node `supra_node_v11.7.2` (Mainnet and Testnet) and `supra_node_v12.0.0.rc7` (Devnet).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the version in this sentence, master's README will say "0.2.0 is tested against…" for everything merged after the release. RELEASING.md's steps (bump pyproject.toml, add the CHANGELOG entry) don't include updating it, so it will go stale. The 0.2.0 CHANGELOG entry already records the tested node versions for this release, so I'd keep the README version-neutral, or else add a README step to RELEASING.md.

Suggested change
0.2.0 is tested against Supra node `supra_node_v11.7.2` (Mainnet and Testnet) and `supra_node_v12.0.0.rc7` (Devnet).
The SDK is tested against Supra node `supra_node_v11.7.2` (Mainnet and Testnet) and `supra_node_v12.0.0.rc7` (Devnet).

Comment thread CHANGELOG.md
- **Breaking:** `behave` and `types-six` are no longer runtime dependencies. The wheel requires `httpx[http2]`,
`PyNaCl` and `typing-extensions`; a project that used `behave` or `types-six` through `supra-sdk` declares them
itself. The `PyNaCl` lower bound is 1.6.2 (was 1.5.0).
- `block_by_height` takes `with_finalized_transactions`, the node's parameter name, as its third parameter. The keyword

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The file says it follows Keep a Changelog 1.1.0, which has a Deprecated section for this. This release deprecates two things: the with_finalized_transaction keyword here, and passing a value to the primitive tag constructors (line 166). Both are listed only under Changed. A ### Deprecated section between Changed and Removed would make them easy to find before they're removed. Also, the sections currently run Fixed then Removed; the spec's order is Added, Changed, Deprecated, Removed, Fixed, Security.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants