Repository navigation
Conversation
jorbuedo
force-pushed
the
upstream-pr
branch
2 times, most recently
from
October 11, 2026 15:23
e545d3d to
c780f1b
Compare
A wallet needs three things this SDK did not have: the bytes a transaction arrived as, so a signature covers what was displayed; reads beyond the ten a transaction builder makes; and the standards layers around a transaction that every wallet otherwise reimplements. In `packages/evolution`: - **Byte-exactness.** A decoded body, witness set, datum and redeemer replay the bytes they arrived as, scoped to what the schema cannot carry: the `IntersectMBO#6.258` input-set tag and integer header widths. `mode: "canonical"` still outranks the record. A body key the schema does not model is refused at decode rather than dropped, which changed the transaction id silently. - **The hex boundary is branded and validated.** Sixteen modules export a `Hex` schema built the way `PaymentAddress` already is, so a transaction hash and a datum hash stop being interchangeable once they leave the typed domain. - **The provider port grows a history tier**, every member optional: what has happened at these addresses, which addresses a stake account has used, the bytes of a transaction, how deep it is, and the batches `getTxsAt` can serve. - **Fees, coin selection, CDDL conformance, COSE, native scripts**: exact Conway fee arithmetic, random-improve as CSL computes it, pool owners as a tagged set, a structurally-keyed COSE header map, and `NativeScripts.fromJSON`. - **Defects fixed**: `type-check` checked an empty program; `getHeader` never found a label that was present; `toJSON` truncated a uint64 slot; a Koios credential went on the wire as `{"0":170,…}`; `writeConstr` refused every constructor tag under a non-minimal option set; `getByUnit` answered `0n` for an asset `fromUnit` had just built. Ten packages: `review` (span-preserving review before signing), `plan` (wallet-side planning), `cips`, `cip30`, `cip113`, `cip139`, `canonical`, `dijkstra`, `hardware-cardano`, `backup-keys`.⚠️ One breaking change: `TransactionOutput` was a second, parallel output model that the builders and `TransactionBody` rejected. It now re-exports `TxOut`, which is the model they use. Evidence: 2,731 tests passed / 84 skipped across 11 packages; `turbo lint` 14/14; `turbo type-check` 15/15; `changeset status` clean.
This branch has not been deployed
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.
This is the wallet-side work we have been running on top of
evolutionfor several months, rebased ontomainas of4e748e6aand offered in one piece. It is large, so the sections below are ordered by how much they are likely to interest you, and every claim in it is something the suite checks.Happy to split any section into its own PR, re-shape an API, or drop a part outright — this is the whole of what we have, not a take-it-or-leave-it.
Defects in current
main, each with a testtype-checkchecks an empty programpackages/evolution/package.jsontsc --noEmitresolvestsconfig.json, which is"include": []plus a project reference.--noEmitdoes not build references, so the script passes on any tree. It reported clean with fourteen real errors staged. Now-p tsconfig.src.jsonHeaderMapcompares labels by object identitysrc/cose/Header.tslabelFromInt(n)builds a fresh Label, sogetHeadernever found a label that was present andsetHeaderappended — producing a COSE header map with a duplicate key, both values encodedNativeScripts.toJSONtruncates a uint64 slotsrc/NativeScripts.tsNumber(bigint)onslot:InvalidBefore(2^53)andInvalidBefore(2^53 + 1)both produced{ type: "after", slot: 9007199254740992 }, and[Hash.symbol]routed through it, so the two hashed equal. Reachable on read — a script decoded from the chain may carry any uint64src/sdk/provider/internal/KoiosQuery.tsCredential.hashdirectly, soJSON.stringifywrote{"0":170,"1":170,…}where Koios expects hex. The query could not have worked for any real credential; the tests that covered it put a string in the fixturewriteConstrrefuses every constructor tag when minimal encoding is offsrc/Data.tscustom, non-minimalthrew on every constructor index of 7 and up. Caught by your ownData.encoder.test.tsonce the tree encoder could write wide tagspackages/cip113/src/Cip113.tsd87980where the metadata had been. Ours cannot overwrite a datum, but it could move such an input and emit no datum, which loses the metadata just as permanently. Now refused, naming the inputgetByUnitanswers0nfor an assetfromUnitjust builtsrc/Assets.tsfromUnitacceptspolicyId.assetName;getByUnitsliced the dotted text at 56. Zero reads as "not here", so the disagreement reached a balance silentlysrc/TransactionBody.tsByte-exactness, which is the reason for most of the rest
A wallet signs bytes and a user approves a screen; if those are not the same bytes, the signature covers something else. Two arrival details cannot be recovered from a decoded value: whether an input set came
#6.258-tagged or bare (both valid, both accepted), and the width of every integer header.So a decoded body, witness set, datum and redeemer now replay the bytes they arrived as, at every node — a list assembled from decoded children writes each child as it arrived while the list itself takes the current layout.
mode: "canonical"still outranks the record, because canonicalising is a request for a new representation. A value built in-process has no record and is encoded by your layout exactly as before, whichData.encoder.test.tsandInlineDatum.TxCodecOptions.test.tsboth pin.This sits on top of #616/#617/#618 rather than beside them: the Plutus-data writer is yours, and the arrival replay is a check inside it.
fromEncodedonTransaction,TransactionBody,TransactionWitnessSetandAuxiliaryDataassembles from parts already encoded, which is what a builder accumulating fields as bytes needs.The hex boundary, branded the way you brand the rest
BytesFromHexisSchema.Uint8ArrayFromHexplus a length filter, soTransactionHash.toHex()returned a barestring— and from there a transaction hash, a datum hash and a key were interchangeable. Sixteen modules now export aHexschema built the wayPaymentAddress,RewardAddressandDnsNamealready are (Schema.String+ pattern +Schema.brand), andtoHexreturns it.The extended key pair is why this is worth more than tidiness: a 64-byte extended public key and a 96-byte extended private key are both "hex", and confusing them is unrecoverable in one direction.
The provider port, and a history tier
The port stopped at the ten reads a transaction builder makes. A wallet cannot work without more, so nine members join it — plus
getTip,getTxConfirmationsandsplitAddressesForTxsAt.Every addition is optional, including
getTip, because this interface is implemented outside this repository and a required member retires those implementations at a minor version. A caller that needs the tip checks for it and reports the absence.getTxConfirmationscounts the containing block as the first confirmation, so a freshly included transaction answers1and an unseen onenullrather than0.splitAddressesForTxsAtpublishes the batching a bounded backend already applies:getTxsAtrefuses anoffsetacross a split body, which leaves a paging caller needing the budget — and a budget restated beside a caller drifts. Absent means unbounded.The Koios tier was driven against preprod rather than a stubbed
fetch, which is what found the credential-hash defect above, the 5120-byte body limit a wallet's signable set passes at ~90 credentials, and chain parameters that were read from the wrong endpoint. A request carrying a credential is refused on a plainhttpendpoint, and a refusal report carries the body, because429means two different things and only the body says which.Smaller additions in
evolutiontxscriptfeecomputes), a singlefloorof the tiered reference-script total (astierRefScriptFeedoes), bounded by the real 204,800-byte cap.randomImproveis a faithful port of CSL'scip2_random_improve_by, asset-aware, and it throws rather than falling back to a predictable PRNG when WebCrypto is absent.pool_ownersas the#6.258set the chain writes, theUnitIntervalandVotercodec pairs their siblings had,CostModelreachable from the entry point,MinAda.forOutput,NonnegativeInterval.fromDecimal.multiassetandmint, applied by the encoder that owns each map and not as a global option — Plutus map keys keep the order they were given. Narrower than fix: encode mint, multiasset and withdrawal keys in CIP-21 canonical order #584, which was closed.CBORBuilder, an append-as-you-go serializer that encodes throughCBOR's own encoder rather than being a second one, plus wide tag support and the splicing helpers.Ten packages
reviewunknownKeysandinputsSetTagged— the two facts aSchema.Structcannot carryplanExUnitsas a swappable service (caller-supplied, presets, provider-evaluated)cipscip30signTx;getAccountPublicKeyis consent-gatedcip113cip139cip-0144.mdcanonicaldijkstrahardware-cardanobackup-keysEach has a README,
@since/@categoryon its public symbols, and a changeset:changeset statusbumps eleven packages minor (the ten above plus
evolution), withaiken-uplc,devnetandscalus-uplcas dependency patches.One breaking change
TransactionOutputcarried a second, parallel output model — aShelleyTransactionOutput/BabbageTransactionOutputunion withaddress: AddressEras,amount: Value— whileTxOutcarried the oneTransactionBody, the builders and the sdk use. Both were public under names that gave no hint which was which, and they rejected each other: an output built with the first could not be placed in a body, and the error readExpected TransactionOutput, actual [toString threw]about a value that was aTransactionOutput, the other one.TransactionOutputnow re-exportsTxOut. One case inInlineDatum.TxCodecOptions.test.tscovered the removed model specifically; with one model it is theTxOutcase written twice, so it is dropped. If you would rather keep both models, say so and we will restore the module and keep our side onTxOut.TxOutalso gains anencodingFormatfield: Conway accepts both the Shelley array and the Babbage map, they are different bytes, and a builder that cannot say which it means inherits whichever the legacy rule picks — which changes with whether an inline datum happens to be present.Validation
ci.ymlruns, on this branch:turbo lint14/14,turbo type-check15/15,turbo check:dts16/16,turbo build15/15 (packages),turbo docgen1/1,changeset statusclean.
docs:build(the Next.js site) fails here — identically on unmodifiedmain, so it isthis machine's
node_modules, not the change.packages/evolution/docsis regenerated (191 modules updated, 6 added, 1 removed), since thischanges the public surface and that output is committed in your tree.
evolutionalone: 2,131 passed / 75 skipped). The skips arecip113harnesses that need a local blueprint clone andevolution-devnet, which needs Docker.packages/evolution's tests are not type-checked.type-checknames onlytsconfig.src.json, andtsc -b tsconfig.test.jsonaborts onTS5101(deprecatedbaseUrl) before reporting. Silencing that shows 147 diagnostics across 24 files on currentmain— none of them ours, we fixed the seven that were. Worth its own PR, and we are happy to send it.Two things we had to decide, and would rather you decided
TSchema.tsis excluded fromdocgen.json.docgendocuments a name and cannot followexport { ArrayCombinator as Array }, so it reportsMissing Array documentation. The alias existsbecause a module-scope binding called
Arraycaptures the one Babel's rest-parameter prologueemits —
for (var n = arguments.length, t = new Array(n), …)— and inUnion(...members)thatbuilt a schema object instead of an array:
members.map is not a functionunder Metro in Node,TypeError: Function is not a constructoron Hermes, and on React Native it took the whole appdown. It is invisible to
node,bunandvitest, which run the native prologue. The module stillhas rest-parameter functions, so restoring the const restores the crash. Excluding it costs one
module's generated page; if you would rather have the page, the fix belongs in docgen's alias
handling.
Two
@exampleblocks needed imports.docgentype-checks examples standalone, andNonnegativeInterval.fromDecimalandRequestBudget.configurenamed their module withoutimporting it. Fixed in place — worth knowing that an example without an import fails the docgen
gate rather than being skipped.
Suggested reading order
packages/evolution/package.json— the one-line type-check fix.src/cose/Header.ts,src/NativeScripts.ts,src/Assets.ts— the small defects.src/TransactionBody.ts,src/Transaction.ts,src/Data.ts— byte-exactness.src/sdk/provider/Provider.ts— the port, and whether the optional-member convention is what you want.packages/review, thenpackages/plan— the two packages everything else rests on.