Build wallet transactions via the HPL blockchain-api (Phase 0) - #560
Open
madninja wants to merge 64 commits into
Open
Build wallet transactions via the HPL blockchain-api (Phase 0)#560madninja wants to merge 64 commits into
madninja wants to merge 64 commits into
Conversation
Introduce `helium_lib::blockchain_api`, a reqwest client for the HPL blockchain-api REST surface (`/api/v1`). The service builds Solana transactions server-side and returns them unsigned and base64-encoded; this client posts an action request (wallet pubkey as an explicit field), receives the transactions, and exposes submit + status polling so a caller can sign locally and broadcast via `POST /transactions`. Covers the Phase 0 action endpoints (token transfer/multi-transfer, dc mint/delegate, hotspot claim-rewards/update-rewards-destination/ update-info) plus the shared response envelope, submit/status types, and base64<->bincode VersionedTransaction helpers. Wire types mirror the published `@helium/blockchain-api` Zod schemas. Decode/encode preserve typed error sources via the existing DecodeError/EncodeError. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add `Opts::blockchain_api()` (resolves the base URL from HELIUM_BLOCKCHAIN_API_URL, else the mainnet default; devnet requires the env var) and `CommitOpts::commit_via_api()`, which decodes the unsigned transactions returned by an action endpoint, shows them for review, refreshes the blockhash, signs locally with the existing signer, then either submits the batch and polls to a terminal status (--commit) or simulates locally without broadcasting (dry-run). Hoist `context_err` to module scope so both the local and API paths reuse the preflight-log enrichment, and add a `CommitResponse::Batch` variant for multi-transaction batches. Priority-fee flags do not apply on the API path; fee/compute-budget/LUT are chosen server-side. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the minimal pre-sign print with `review_lines`/ `display_action_for_review`: for each transaction the blockchain-api returns, show the server's declared intent (its `description`), the fee payer and instruction count derived from the decoded transaction, and the estimated SOL fee for the batch. Printed to stderr so it does not corrupt the JSON result on stdout. Ledger sources additionally show the device blind-sign hash. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Route `transfer one` (and any single-payee `transfer multi`) through the blockchain-api's tokens/transfer endpoint, then sign and submit via CommitOpts::commit_via_api. Squads proposals still build locally (Phase 2), and multi-recipient payments stay on the local builder for now: they can mix tokens within one atomic transaction, which the single-mint multi-transfer endpoint cannot express. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Simplify `transfer` to HNT: drop the token-type argument from `transfer one` and the "token" key from the multi-payment file. Because every payment is now HNT (a single mint), route `transfer multi` through the blockchain-api multi-transfer endpoint alongside single-recipient transfers. Behavior change: MOBILE and IOT are no longer transferable via the CLI, and a multi-payment file containing a "token" key is now rejected (deny_unknown_fields) rather than honored. Squads proposals still build locally (Phase 2). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Not every action endpoint returns the `{transactionData, estimatedSolFee}`
envelope: the data-credit endpoints return the TransactionData bare, with
no fee estimate. Add an `ApiTransactions` trait implemented for both
`ActionResponse` (wrapped, with fee) and `TransactionData` (bare, no fee)
so a caller can sign and submit either shape uniformly, and return
`TransactionData` from `dc_mint`/`dc_delegate`.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Route the non-Squads paths of `dc mint` and `dc delegate` through the blockchain-api data-credit endpoints. Make `CommitOpts::commit_via_api` generic over the `ApiTransactions` trait so it accepts both the wrapped and bare response envelopes, and show the estimated fee in the review summary only when the endpoint provides one. Squads proposals still build locally (Phase 2). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Route `assets rewards recipient update` (and its `hotspots rewards recipient update` wrapper) through the blockchain-api update-rewards-destination endpoint. The token selects the lazy distributor (via ClaimableToken::lazy_distributor_key) and the entity key is sent as the hotspot pubkey for server-side asset resolution. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Split `hotspots update` into `iot` and `mobile` subcommands mapping to the blockchain-api update-info endpoint. IoT asserts location/gain/elevation; Mobile asserts location plus WiFi deployment info (antenna, elevation, azimuth, mechanical/electrical downtilt, serial). Only the fields passed are changed; the server keeps current on-chain values for the rest. Add a typed `DeploymentInfo` (WiFi only) to the lib request types; CBRS is defunct and not supported. Drops the `--onboarding` flag (the server selects the onboarding endpoint) and the local build path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reflect the HNT-only transfer (drop the token argument and the multi-payment "token" field) and the `hotspots update iot`/`update mobile` subcommands. Also drops the stale "max" amount and "memo" multi-payment fields, which the code does not accept. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Route `swap` through the blockchain-api: GET /swap/quote for the quote, then POST /swap/instructions (quote passed back verbatim) for the unsigned transaction, signed and submitted via commit_via_api. The quote's in/out amounts, mints, slippage, and price impact are merged into the output as before. Add `SwapQuote` (round-trips verbatim via a flattened catch-all so routePlan et al. survive the quote->instructions handoff) and `SwapInstructionsRequest` to the lib, plus a query-param GET helper. Verified the API quote matches a direct Jupiter quote for HNT->USDC within quote-timing noise. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
madninja
force-pushed
the
blockchain-api-client
branch
from
July 7, 2026 16:19
14dc9b5 to
019eeb2
Compare
Add request types and client methods for the new endpoints: token burn, memo, dc burn (bare TransactionData), hotspot burn, and single-hotspot claim (entity key as a path parameter). Mirrors the published contracts. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Wire the direct (non-Squads) paths of `burn`, `memo`, `dc burn`, `assets burn`/`hotspots burn`, and `hotspots rewards claim`/`assets claim one` to the new blockchain-api endpoints. - dc burn: direct burn goes via the API; the delegated (router+subdao) path still builds locally, since the API endpoint covers only direct. - Squads proposal paths still build locally (Phase 2). Behavior change: per-hotspot claim now claims the full pending amount; the optional `amount` (partial-claim) argument is dropped, matching the API endpoint. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds request types and Client methods for the Squads v4 proposal lifecycle endpoints (approve/reject/cancel/execute, propose config change) and an optional multisig/memo on the token transfer request for the transfer endpoint's propose mode. All lifecycle endpoints return the bare TransactionData.
Routes the v4 paths of the squads command tree (approve/reject/cancel, execute, member add/remove, threshold) and single-recipient transfer --squads proposals through the new blockchain-api endpoints, keeping the local resolve/permission-check reads. v3 votes/executes and the atomic approve --execute bundle stay local (the API is v4-only and can't emit an atomic approve+execute); multi-recipient transfer proposals also stay local (multi-transfer has no propose mode).
Adds the optional multisig/memo fields to the token burn, dc burn, dc delegate, and hotspot burn requests, and a new TransferHotspotRequest + transfer_hotspot client method, matching the blockchain-api's propose mode. DC mint intentionally has no multisig field (minting needs a fresh Pyth price in-tx, which a deferred proposal can't provide).
Routes the --squads branches of token burn, dc burn, dc delegate, and asset/hotspot burn + transfer through the new propose-mode endpoints (resolving the target to a multisig PDA locally, then letting the API build the vault proposal), replacing the local submit_proposal_with wrap. dc mint --squads and multi-recipient transfer --squads stay local (no API propose path). Adds a shared resolve_multisig helper.
Removes the delegated-burn path (`dc burn --router --subdao`) and the helium-lib `burn_delegated`/`burn_delegated_message` builders. The blockchain-api covers only direct DC burn, so this local-construction path had no migration target; `dc burn` is now direct (or a Squads vault proposal) only. Breaking: removes the public helium-lib `dc::burn_delegated` / `burn_delegated_message` functions.
- Squads v3 vote/execute now error (v4 only); v3 multisigs stay inspectable via `squads inspect` / `list`. - `dc mint --squads` removed: minting needs a fresh Pyth price in-tx, which a deferred proposal can't provide. - multi-recipient `transfer --squads` removed: multi-transfer has no propose endpoint (single-recipient still proposes via the API). - `squads approve --execute` removed: the API can't emit an atomic approve+execute in one transaction. Single-recipient transfer --squads now resolves the target to its multisig PDA (matching the other action commands). Deletes the now-unused local submit_proposal_with / wrap_as_proposal / squads_vault helpers.
The wallet builds all Squads v4 proposals/votes/execs through the blockchain-api, and v3 is inspect-only, so the local construction builders have no callers. Removes them from helium-lib, keeping every read/inspect/resolve path intact: - v4: propose_ixs(_with_luts), proposal_approve/reject/cancel_ix, vault/config_transaction_create_ix, proposal_create_ix, vault/config_transaction_execute_ix, execute_ix, get_time_lock, proposal_vote_ix, compile_transaction_message(_with_luts), ConfigActionInput, and the ix discriminator constants. - v3: approve/reject/cancel/execute_transaction_ix + build_execute_accounts. Kept: PDAs, list_open_proposals, get_proposal_info, all decoders, resolve/inspect, MemberPermissions, and the shared fetch_account. Breaking: removes these public helium-lib squads construction functions.
…e API - assets/hotspots transfer: the direct (non-squads) path now builds via the tokens/hotspots transfer endpoint, matching the --squads path. - rewards recipient init: builds via update-rewards-destination (which creates the recipient account if absent), setting the destination to the caller's wallet.
The wallet builds every migrated action through the blockchain-api, so the corresponding local construction builders have no callers. Removes them, keeping reads/PDAs/decoders and the still-live builders (hotspot::dataonly issue/onboard, queue, schedule, message/transaction infra). Deleted whole modules (only held construction): dc, memo, boosting, ed25519_instruction. Stripped construction from token, reward, asset, hotspot, hotspot::dataonly, priority_fee, transaction. Breaking: removes these public helium-lib functions.
Add request types and Client methods for the blockchain-api claim-automation surface: setup, fund, close, requeue, add-wallet, add-entity, remove-entity, and the operator floor top-up. Each posts to the automation REST path and returns the standard unsigned-transaction envelope for local signing. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…-api The claim cron is now built server-side: `schedule init` takes a raw crontab and funds by claim-cycle count (`--duration`), with `fund`, `wallet`, `one`, `remove`, `requeue`, and `close` as granular API-backed actions. `queue wallet` becomes a one-time Tuktuk claim. The `info` subcommands stay local reads over the kept PDA derivations. The raw `--fund` amount and the third-party target argument are dropped. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claim-cron transactions are built by the blockchain-api, so drop the local instruction and transaction builders from `schedule` and `queue`. Keep the PDA derivations and the cron-account read the CLI's `info` commands rely on. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`hotspots add` now builds its issue and onboard transactions via the API instead of locally: the wallet decodes the add-gateway token, calls the issue and onboard endpoints, and signs the returned transactions. The issue tx comes back co-signed by the ECC verifier, so commit_via_api gains an ApiSigning mode — PreserveCosigned fills only the owner's signature slot, keeping the server's message, blockhash, and co-signature intact; FreshBlockhash is the default for the single-signer transactions every other command uses. The offline add-gateway token generation stays local. Also drop the `hotspots updates` history command. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
With every command building through the blockchain-api, the local construction machinery is dead. Delete data-only onboarding, versioned-message assembly, priority-fee estimation, and the Jupiter and maker-onboarding HTTP clients; drop TransactionOpts and the CLI's --min/--max-priority-fee flags. Priority fees, compute budget, and lookup tables are now chosen server-side only. helium-lib keeps reads, PDA derivations, the blockchain-api client, and the sign/submit path for the unsigned transactions the API returns. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Send the required `commitment` query param on the batch-status GET so confirmation polling doesn't 400. Truncate over-long error bodies by character rather than byte, so a multi-byte boundary can't panic on an error path. Carry the batch id in the poll-timeout error so a caller can check whether the batch landed after the deadline. Drop an unused private helper and a dead constant. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Without `--commit`, interactive runs now simulate the built transactions and prompt before submitting instead of only dry-running; `--commit` submits without asking, and a non-interactive run without `--commit` stays a safe no-op so scripts never hang or submit unexpectedly. Signing happens only once a submit is decided, so a Ledger is never touched for a dry run or a declined action, and a fresh blockhash is fetched only when actually submitting. Simulation failures now include the program logs. Sign-in-place bounds-checks the signature slot so a malformed server response errors cleanly instead of panicking, and a non-mainnet `--url` without an explicit blockchain-api URL now warns instead of silently using mainnet. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The review line printed the server-supplied description plus fee payer and instruction count. Add the set of programs each transaction actually invokes, decoded from the transaction rather than taken from the server, so an unexpected program is visible even when signing blind (only `transfer` verifies transaction content semantically). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Restoring the token argument made it required, which broke the prior `transfer one <address> <amount>` form that sent HNT. Default the token to hnt so that form keeps working; an explicit token still overrides. Matches the `transfer multi --token` default. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
||
| async fn parse<R: DeserializeOwned>(resp: reqwest::Response) -> Result<R, BlockchainApiError> { | ||
| let status = resp.status().as_u16(); | ||
| if (200..300).contains(&status) { |
Contributor
There was a problem hiding this comment.
StatusCode::is_success() does this same check.
There's also Response::error_for_status() that automatically puts error status codes into the right side of a Result. That would change how you're formatting the returned errs here.
Member
Author
There was a problem hiding this comment.
Switched to status.is_success() in fb6e295, thanks. Kept the explicit body read rather than error_for_status() since we want the response body in the error (it carries the API's failure detail, which error_for_status drops).
Bring the branch up to date with master (5 commits) so it's mergeable and CI can run. Conflict resolutions: - error.rs, transaction.rs, reward.rs: master modified the confirmation subsystem (ConfirmationError/SignatureStatus) and reward-claim construction that this branch removed when moving transaction building and confirmation to the blockchain-api. Kept the removals; master's changes were to code that no longer exists here. - cmd/mod.rs: kept both this branch's DEFAULT_BLOCKCHAIN_API_URL and master's resolve_endpoints helper (a location collision, not a logic conflict); the client() method already uses resolve_endpoints. Also fixes a needless borrow in the create_wallet example that master moved into the build, so clippy -D warnings passes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Address review feedback: replace the manual `(200..300).contains` check with `reqwest::StatusCode::is_success`. Keep reading the response body into the error rather than using `error_for_status`, which would discard the body that carries the API's failure detail. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
With transaction construction moved to the blockchain-api, these deps are no longer referenced: spl-memo and solana-system-interface (0 uses), and solana-transaction-utils (only an unreachable error variant, removed here). helium-lib still depends on solana-sdk/client, anchor, and the HPL program crates for decoding, signing, PDA derivation, and on-chain reads. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
clippy's useless_borrows_in_formatting (in `-D clippy::all`, the hygiene CI check) flags `&self.base_url` in the cert request format!. Pass it by value and inline the path variable. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The setup endpoint requires `schedule`; the client sent `cronSchedule`, so `claim schedule init` was rejected with HTTP 400 before reaching the server's handler. The field also accepts a `daily`/`weekly`/`monthly` preset alongside a raw clockwork crontab, which the CLI help now says. The wallet is the URL's path parameter on every automation endpoint, so it no longer goes in the body as well. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The transfer sections still described the HNT-only interim state: the token argument, the `--token` batch flag and the supported-token list are all present in the command, so the README documented an interface the CLI does not have. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two repos outside this workspace build Solana transactions with helium-lib and cannot move to the blockchain-api. alembic bundles a hotspot location reset and an asset transfer into a single transaction, sharing one merkle proof so the pair stays inside the 1232-byte packet limit. The API builds one action per request, so two calls would produce two transactions, two proofs, and a window where a hotspot is reset but still owned. helium-solana-api serves a transaction shape its own consumers depend on. The instruction, message and signing layers therefore return, gated so the wallet CLI neither compiles them nor takes the three deps they need. Only what those consumers call comes back: whole features nothing uses stay deleted, including delegated DC burn, DC delegate and burn, and the onboarding-server data-only paths. `tests/txn_surface.rs` names every path the two repos import, so removing one fails to compile here instead of surfacing as a broken `cargo update` there. CI gains an `--all-features` test run and `--all-targets` on clippy, without which neither the feature-gated code nor that test is built at all. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The compute-budget program was allowlisted by the transfer guard and never inspected, so a transaction carrying exactly the requested transfer could also carry an unbounded `SetComputeUnitPrice` and take the wallet's whole SOL balance as priority fee. The guard's own docstring claimed "moves the requested token and nothing else" holds, which it did not; the fee shown for review is the server's `estimatedSolFee` and would not reveal it either. `commit_via_api` now derives the worst-case prioritization fee from the compute-budget instructions and refuses above the ceiling the local builder clamps to, so every command inherits the check rather than only `transfer`. The review line reports the derived bound alongside the server's estimate. `priority_fee`'s two bounds move out from behind the `txn` feature so the cap has one definition shared with the local builder instead of a copy that drifts. Also from review: `txn` pulled `spl-memo` and `solana-transaction-utils`, which no restored code references; the surface test asserted only that the default LUT list was non-empty, which a devnet default passed; and it named `IssueToken` and `HotspotInfoUpdate` without naming the builder methods both consumers call. The release build now compiles the feature on both runners. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Six guards from review, and a simpler commit flow. `--commit` is now the single decision point: every run simulates, and the flag adds submission. The interactive prompt is gone. It fired only on a terminal, so the same command behaved differently piped, and it read as a third state beside the flag and the dry run. It was not carrying its weight as a control either: the label shown for review is the server's own text, and a Ledger shows a blind-sign hash. What the transaction does is now checked in code. `assets rewards recipient update` decodes the returned transaction and asserts the destination and the authorizing owner, reading their account positions from the shipped IDL. A substituted destination is the costliest thing this API can return, since it keeps paying after the compromise is found and needs a second on-chain action to undo, and the review line cannot separate it from a correct update because both invoke the same program. The co-signed signing path refuses a transaction anchored to a durable nonce. That path preserves the server's blockhash so an existing co-signature stays valid, which is also what a nonce needs, and a nonce-anchored transaction never expires. A committed batch is re-checked against the wallet's own RPC, so a reported success is no longer the submitting service's word alone. The transfer guard now holds the associated-token program to CreateIdempotent; plain Create funds an account from the signer at rent cost for any owner, which the amount comparison cannot see. A `--squads` transfer is held to moving nothing at the top level, where it previously skipped the guard entirely under a comment claiming funds could not be redirected without it failing first. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`issue_token` and `issue_token_to_add_tx` sign and decode a protobuf add-gateway envelope. They touch base64 and helium-crypto and build no Solana transaction, so they sat behind the transaction-construction feature only because they share a file with the onboarding builders. Gating the items inside `hotspot::dataonly` rather than the module lets a consumer that mints tokens and takes its transactions from the blockchain-api reach them without pulling in local construction at all. `IssueToken` is served as a response body by helium-solana-api, so its field names are a cross-repo contract; the surface test now asserts the serialized shape rather than only that the type resolves. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ction A caller that signs a transaction it did not build has to decide what the transaction may contain, and every such caller was arriving at the same primitives: resolve a program or account and fail closed when it is loaded from a lookup table, price the compute-budget instructions to bound what the transaction can cost in SOL, and establish that one wallet is the only required signer. `helium_lib::verify` holds those, ungated, since a caller that no longer builds transactions still has to inspect them. Policy stays with the caller: which programs are expected, which SPL-token instruction tags, and which accounts must hold which pubkeys are all questions only the caller can answer. The wallet's own copies go, along with the compute-unit limit and the fee arithmetic it carried. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The transfer guards each carried their own resolution of a program or account from an instruction, so what a lookup-table-loaded index means for verifiability was stated seven times across the wallet. It is stated once, in `helium_lib::verify`, and the guards read it from there. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Establishing that a transaction moves exactly the requested amounts to the requested recipients, out of one wallet's account and on that wallet's authority, is the check any caller signing a server-built transfer needs. It lived in the wallet, where a second consumer could not reach it. `verify::assert_spl_transfers` takes the expected recipients and raw amounts and compares totals, so a recipient named twice sums rather than depending on instruction order. The associated-token program is held to CreateIdempotent, since a plain create funds an account from the signer at rent cost for any owner, which the totals do not see. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…s asked Three commands signed whatever the service built for them. Each now decodes the transactions first and refuses one that names a different destination: - an asset transfer must hand the asset to the requested wallet, on this wallet's authority, and a Squads proposal must carry no top-level transfer - a data-credits mint must credit the requested payee and burn this wallet's HNT - initializing a rewards recipient sets the same standing redirect the update path sets, and is held to the same check `verify::find_methods` resolves an Anchor method by discriminator against the program's shipped IDL, so the account indices these read are the ones the IDL declares. `assert_programs_within` goes: both callers that wanted a program allowlist need per-program branching it cannot express.
A quote is handed back verbatim to build the route, and its `other_amount_threshold` becomes the on-chain minimum output, so it is the only thing holding execution to a price: a quote reporting a healthy `price_impact_pct` can still carry a threshold of 1. The mints, mode, input amount, slippage and that floor are checked before the quote is spent. The built route is held to invoking the aggregator, the associated-token and compute-budget programs, and the SPL-token tags that wrap and unwrap SOL. A signature alone authorizes every transfer, delegation and authority change the wallet owns. Both checks live in helium-lib, so this and the swap service that runs unattended hold a route to one rule rather than two.
An unexpected-instruction refusal named the program but not the call, so a service that refuses one unattended reports which program it distrusted without saying what it saw. The leading discriminator byte travels with the error.
The router key is an instruction argument rather than an account, so a substituted one resolves to a different escrow: the DC leaves this wallet and becomes spendable by someone else's router, and nothing here reverses it. `NamedInstruction::args` decodes a body against the program's IDL, so the delegation is checked against what was asked for rather than against an account standing in for it. A proposal is held to carrying no delegation of its own, as the vault's DC moves inside a Squads instruction this cannot read.
Five commands signed whatever the service built for them. Each now decodes the transactions first: - a DC burn destroys the requested amount, from this wallet - a token burn destroys the requested amount, out of this wallet's account - an asset burn destroys one asset this wallet owns - an info update is for a hotspot this wallet owns, asserts the location that was asked for, and sets no field that was not - adding a hotspot issues it to this wallet and onboards it to this wallet at the asserted location Location is compared exactly, through `hotspot::cell_for`, so the guard derives the cell the update would. Gain and elevation are checked only for presence: their unit conversion is the service's, and a second copy of its rounding would refuse honest updates the day it changes. Which asset a burn destroys is not checked either, as the leaf is named by a merkle proof. A proposal moves the vault's funds inside a Squads instruction that cannot be read here, so each command holds one to carrying no such action itself.
Each check answers whether a built transaction carries the action that was requested, once, naming the accounts and values the caller named. That is a question about a program's layout, and the IDLs those positions come from ship in this crate -- so a caller no longer has to know an account index to hold a transaction to its own intent. The CLI was the only consumer while the checks lived in its command modules. The transaction service serves hotspot transfer, data-only issue and onboard to callers who then sign them, which is the same question one hop further out, and the swap service already shares the two swap checks.
Both services decoded a builder's response, refused anything but a single transaction, bounded the fee and checked the sole signer -- the same four steps, written twice, against two error types. `sole_signable` states them once: the per-action checks say what a transaction must do, this says what shape it has to arrive in.
The type describes an update a caller wants and builds nothing: its accessors read the fields back and `set_geo` names an h3 cell. A service that asks the blockchain-api for the update still needs to say what the update is, so the intent belongs outside the construction gate that `direct_update_instruction` stays behind.
The gate gated one optional dependency, `solana-system-interface`, which every build already pulls in transitively: the crate graph is 926 packages with the feature and 926 without it. What it bought was a compile-time fact that a consumer builds no transactions locally, and the wallet holds that by calling none of it. Against that, every consumer carried the flag, fifty-five `cfg` attributes carried the split, and the surface test carried two halves. The construction functions are public like the rest.
Signing is what rejects a transaction whose fee payer or co-signer set does not match the wallet, and it runs only under `--commit`. A dry run therefore reported success on a transaction that would fail the moment a key was asked for -- the case the run exists to find. The check is the same one the swap service and the transaction service apply, and it sits before the early return: after it, only the path that already enforces the rule would reach it.
The line a caller reads before authorizing a swap renders each program through `KnownProgram::name`, and the aggregator was not in that enum -- so the one program deciding what the swap does appeared as raw base58 beside a named compute_budget. Its address now has a single definition, which `verify` re-exports.
The endpoint's `feePayer` defaults to `maker`, which relays the onboarding server: the maker co-signs and covers the DC, so the wallet is neither the fee payer nor the only required signature. This client sent no such field and took that default, and then signs alone -- so every update it built carried a signature it could not supply. The field is required rather than defaulted, so a caller states who pays instead of inheriting a wire default that does not suit it.
`set_geo` derives its cell through `cell_for`, and a decommission resets a hotspot to 0,0 before transferring it away. Nothing named the resulting cell, so a change to the derivation would move where those hotspots land with every test still passing.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Moves Solana transaction construction out of
helium-liband onto the HPLblockchain-api. Each migrated command posts an action request (wallet pubkeyas an explicit field), receives an unsigned transaction, signs it locally with
the existing keypair/Ledger signer, then submits via
POST /transactionsandpolls for confirmation.
Net effect is about 3,800 lines removed: the local instruction builders,
priority-fee logic, lookup-table selection and Squads proposal construction all
move server-side.
Added
helium_lib::blockchain_api-- a reqwest client for the/api/v1surface:action requests, the response envelopes, submit + status polling, and
base64/bincode
VersionedTransactionhelpers. Wire types mirror the published@helium/blockchain-apiZod schemas.CommitOpts::commit_via_api-- decode, review-display, simulate, signlocally, then submit and poll.
stderr, alongside the existing Ledger blind-sign hash.
token, associated-token and compute-budget programs (or only the system
program, for SOL), must be authorized by this wallet, and must move funds from
this wallet's account. It fails closed on any unexpected program, any
non-transfer token instruction, and on any program that arrives via lookup
table and so cannot be checked.
Migrated commands
transfer(one + multi),dc mint,dc delegate,dc burn,burn,memo,swap,hotspots update,hotspots transfer,hotspots burn,hotspots rewards(claim, recipient update, and the claim-automation schedule/queuetree), data-only hotspot issue + onboard, and the Squads v4 proposal lifecycle
(approve, reject, cancel, execute, config change).
Behavior changes
--commitsemantics. Without it, an interactive run simulates and asks forconfirmation before submitting; a non-interactive run (piped/CI) simulates and
stops, broadcasting nothing. Signing happens only once a submit is decided, so
a Ledger is never touched for a dry run or a declined action.
hotspots updateis nowupdate iot/update mobile. Mobile gains WiFideployment flags; IoT gains azimuth. The
--onboardingflag is gone -- theserver selects the onboarding endpoint.
chosen by the API.
--min-priority-fee,--max-priority-feeand--skip-preflightare removed.Removed
dc burncovers thedirect burn only.
surface covers remain.
Validation
Checked against the deployed API (
my-helium.web.helium.io), which publishes itsOpenAPI spec at
/api/v1/spec.json:names.
transactionData+estimatedSolFeewrapper vs. the bareTransactionDatathedata-credit, Squads and swap routes return.
Squads permissions and action tags, simulation commitment, batch status.
serializes to. Action endpoints only build unsigned transactions, so this
broadcasts nothing.
That sweep caught one defect, fixed here: the automation setup body sent
cronSchedulewhere the server requiresschedule, which madeclaim schedule initfail with HTTP 400. A test now pins the wire names for the automationbodies, and it was mutation-checked -- reintroducing
cronSchedule, or lettingthe path-parameter wallet leak back into a body, each turns the suite red.