feat!: chain_id, String amounts, Burn builder and chainInfo for the treasury chain - #3
Merged
Conversation
…reasury chain The node's breaking release moved the hash preimage to [from, nonce, chain_id, data] and the wire format to an 8-item list with chain_id at index 2, so every unsigned blob now carries chain_id and the faucet signs the new layout (pinned by a test that decodes its own output). Fares, amounts and balances move from GraphQL Int to String: Int is 32-bit and at 1 USD = 1,000,000 CLT it overflows at a ~$2,147 fare. Adds createUnsignedBurn and a chainInfo query (totalSupply as String - the one node field that can pass 2^53). The faucet refuses to boot on a non-testnet chain, and the auth challenge is now chain-bound so a captured testnet signature cannot authenticate against another network. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
faucet_amount_clt = 1000 was meaningful when CLT was a whole unit. At 1 USD = 1,000,000 CLT it is $0.001 - exactly one tx_fee - so a funded test account could send a single zero-value transaction and reach zero without ever affording a ride. Verified against a live stack: the drip credited 1000 while the same transaction's fee was also 1000. Rate limits (per-IP 30s, per-address 1h) bound the larger drip. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.
Companion to clutchprotocol/clutch-node#9. Merge together — this API cannot talk to the old node, and the old API cannot talk to the new node.
Signing format (the part that must be byte-exact)
The node moved its hash preimage to the 4-item RLP list
[from (no 0x), nonce, chain_id, data]and its wire format to the 8-item list[from, nonce, chain_id, r, s, v, hash, data], withchain_idat index 2.Two consequences here:
createUnsigned*blob now carrieschain_id→{from, nonce, chain_id, data}. Clients sign from this blob, so a missingchain_idmeans every signature is computed over the wrong preimage and the node rejects all of them.chain_idthird in both lists, matching the node exactly. Pinned by a new test that RLP-decodes the faucet's own output and asserts 8 items withchain_idat index 2 — the only guard against silently drifting from the node's format, since nothing else here would notice.Why
chain_idat all: a signature previously committed to a transaction's contents but not to its network, so onceMintexists a testnet mint could be replayed verbatim on mainnet and still verify.Fares become String, not Int
GraphQL's
Intis 32-bit. At the peg of 1 USD = 1,000,000 CLT,i32overflows at a fare of about $2,147 — an ordinary ride price. Every fare/amount/balance scalar is now a decimalString, parsed through one helper that rejects non-integer, negative, and above-i64::MAXvalues.Note the asymmetry, which is deliberate: the node still sends
fare/fare_paidas bare JSON numbers — onlytotal_supplymoved to a string there, because it is the one value that can pass 2^53. The conversion therefore happens at this GraphQL boundary, so SDK clients should expectStringfrom the hub regardless of what the node emits.New surface
createUnsignedBurn(amount, redemptionRef)— the redemption half of the treasury. A user burns CLT carrying the hash of an off-chain redemption intent, and the treasury's watcher matches the confirmed burn to that intent and pays out.chainInfo—chainId,isTestnet,txFee,totalSupply,mintAuthority. Fetched once at startup and held in schema data, since these are genesis constants; a node swap needs an API restart.Two security changes
faucet_enabledandis_testnetis false, the process panics at startup. A faucet surviving onto a real network would destroy the peg on day one, and this is the one place the codebase panics rather than returningResult— boot-time fatal misconfiguration.clutch-auth:{chain_id}:{publicKey}:{timestamp}. It previously carried no chain or hub identity, so a challenge signed on testnet authenticated the same key against any other Clutch hub inside the ±120s window — and money endpoints are about to be gated on these JWTs. Breaking, no fallback; the cross-language fixtures were regenerated.Verification
cargo test— 25/25 pass, no new warnings. Includes the new faucet wire-format test and the regenerated auth fixtures.🤖 Generated with Claude Code