Conversation
* Replace __USE_AXIOS__ dynamic require with static fetch default * Add babel and webpack aliasing to emit axios variant from shared source * Flip package.json exports: fetch default, /axios opt-in, drop /no-axios * Update vitest configs and test harness for TRANSPORT=axios variant * Update CI, README, and stale references for new fetch-default build matrix
* update eventsource to v4.1.0 and remove conditional import of eventsource * remove no-eventsource lib bundle as the eventsource dependency runs in the workerd runtime * remove the dom-monkeypatch as its now included in the tsconfig lib field * add additional tests for eventsource behavior
* add type: module in pkg.json and update js config files to esm style * build and export cjs and esm build varients, remove export of umd bundles * remove unused dependency (causing issue with hook files being cjs format)
* replace webpack + babel dependencies for rollup * migrate build tools to rollup
* move stellar-base src under src/base * add unit tests from stellar-base * port xdr and makefile for generating xdr * port manual type declarations for js only dependencies * inline js-xdr for node esm compatibility * disable bundle_size workflow
* migrate from classic yarn to pnpm * add pnpm setup action to git workflows
* remove randomBytes for universal crypto.getRandomValues * replace sha.js with noble/hashes and update to version 2.2.0 * update BigNumber to v11.0.0 * replace noble/curves with noble/ed25519 for reduced bundle size * move tsconfig project root * replace toml with smol-toml
* refactor: replace URI usage for native URL + URLSearchParam objects * remove usage of non-null assertion * allow expandUriTemplate to handle relative templated links
* test src code directly instead of the built lib
…p nyc (#1408) * Update husky config + remove nyc * Root .nvmrc + bump Node to v22 * pnpm minimumReleaseAge config * Cleanup
…ndly XdrString wrapper class
Expose enum schema nameByValue metadata through the public EnumSchema type and reuse it for generated enum fromValue/fromName helpers. This removes the per-enum byValue maps and generated fromName switch statements, leaving the schema as the single source of truth for enum member metadata.
Shaptic
left a comment
There was a problem hiding this comment.
LGTM! Left some comments on the migration guide but none of them are necessarily blocking for v0.
However, this should come alongside a SKILL.md or something that will do the porting for someone automatically. Doing this section-by-section yourself (or even w/ an LLM) would be annoying and we should try to streamline the adoption experience as much as possible.
| // Before | ||
| if (op.body().switch() === xdr.OperationType.payment()) { … } | ||
|
|
||
| // After | ||
| if (op.body.type === "payment") { … } |
There was a problem hiding this comment.
Is there still support for number-based comparisons? This covers the .name case on the type, but not the .value case. Those are faster and can be nicer to use in tight loops for a little extra performance. Not a show-stopper, but it's at least worth noting in a feature request.
| or `toXdr("hex")` returns a string. **No more `.toXDR().toString("base64")` | ||
| pattern** — that was a Buffer idiom and will now produce comma-separated | ||
| bytes instead of base64. |
There was a problem hiding this comment.
I am not a fan of this behavior changing silently; it's gonna break a lot of things more subtly since it's a runtime change.
There was a problem hiding this comment.
This should fail at compile time for users who are doing this. Uint8Array does not take an argument in its toString() function
There was a problem hiding this comment.
Yeaahhhh but it's optional in the Buffer case :/
There was a problem hiding this comment.
Yeah I agree but the fix is generally pretty easy as you move the "base64" parameter inside the toXdr call. It's a tad more annoying to get back utf-8 but its the cost of moving away from Buffer
| | Method | Description | | ||
| | -------------------------------------- | ----------- | | ||
| | `value.toXdrObject()` / `Class.fromXdrObject(wire)` | Bridges instance ↔ wire-shape object. Legacy types directly held their wire shape, so this distinction wasn't meaningful. | | ||
| | `value.toJson()` / `Class.fromJson(json)` | SEP-0051-compliant JSON serialization. See § 13. | |
There was a problem hiding this comment.
Suggestion: add the cross-reference anywhere SEP-51 is mentioned:
| | `value.toJson()` / `Class.fromJson(json)` | SEP-0051-compliant JSON serialization. See § 13. | | |
| | `value.toJson()` / `Class.fromJson(json)` | [SEP-51](https://stellar.org/protocol/sep-51)-compliant JSON serialization. See § 13. | |
| If you previously did `someMemo.value === "expected-string"`, switch to | ||
| `someMemo.value?.toString("utf8") === "expected-string"`. |
There was a problem hiding this comment.
This is lowkey obnoxious but I guess strictly more correct?
quietbits
left a comment
There was a problem hiding this comment.
Love the new approach, it reads much better. Just a few minor comments. Thanks for the update! 🙌
| - **`schema.kind` matches one of the kinds enumerated in | ||
| `values/to-json.ts`.** Adding a new primitive without updating the | ||
| walker will produce runtime errors on any consumer call to `toJson()`. | ||
| - **Schemas reachable from each other must form a DAG** *or* go through |
There was a problem hiding this comment.
It stands for directed acyclic graph
| ## 2. Union types: discriminated classes, not switch/value pairs | ||
|
|
||
| The biggest behavioral change. Every XDR `union` (and any type defined like | ||
| one — `Asset`, `SCVal`, `OperationBody`, `LedgerEntryData`, `TransactionEnvelope`, |
There was a problem hiding this comment.
Should SCVal be ScVal (lowercase "c")?
| SCValAddress, | ||
| SCAddressMuxedAccount, |
There was a problem hiding this comment.
Should these have lowercase "c"?
| SCValAddress, | |
| SCAddressMuxedAccount, | |
| ScValAddress, | |
| ScAddressMuxedAccount, |
| import type { SCValAddress } from "@stellar/stellar-sdk"; | ||
| const addr = (scv as SCValAddress).address; |
There was a problem hiding this comment.
Similar here?
| import type { SCValAddress } from "@stellar/stellar-sdk"; | |
| const addr = (scv as SCValAddress).address; | |
| import type { ScValAddress } from "@stellar/stellar-sdk"; | |
| const addr = (scv as ScValAddress).address; |
| } | ||
| switch (value) { | ||
| case xdr.ScSpecType.scSpecTypeVoid().value: | ||
| case xdr.ScSpecType.scSpecTypeOption().value: |
There was a problem hiding this comment.
We don't need scSpecTypeOption anymore?
There was a problem hiding this comment.
These functions are kinda a mess but tyType get checked at the beginning of the function for scSpecTypeOption and gets handled there
| if (isUnionVarient(responseXDR.result, "txSuccess")) { | ||
| results = responseXDR.result.results; | ||
| } else if (isUnionVarient(responseXDR.result, "txFailed")) { | ||
| results = responseXDR.result.results; | ||
| } |
There was a problem hiding this comment.
results in both cases is the same. Is there some kind of magic going on with type setting?
There was a problem hiding this comment.
No magic the XDR spec defines the field result: OperationResult[] for both success and failure
* ```xdr
* union switch (TransactionResultCode code)
* {
* case txFEE_BUMP_INNER_SUCCESS:
* case txFEE_BUMP_INNER_FAILED:
* InnerTransactionResultPair innerResultPair;
* case txSUCCESS:
* case txFAILED:
* OperationResult results<>;
There was a problem hiding this comment.
I refactored this to be less verbose
| const entry = await this.getLedgerEntry(trustlineLedgerKey); | ||
| return entry.val.trustLine(); | ||
| if (entry.val.type !== "trustline") { | ||
| throw new Error("unexpected ledger entry type"); |
There was a problem hiding this comment.
Should we add entry.val.type to the error message?
There was a problem hiding this comment.
| * @param {string} accountId The signer's public key. | |
| * @returns {boolean} Whether or not `accountId` was found to have signed the |
| @@ -0,0 +1,96 @@ | |||
| /* eslint-disable @typescript-eslint/no-use-before-define */ | |||
There was a problem hiding this comment.
Would it be possible to add a comment that this is generated file? It would be helpful when looking at the file without checking its path.
| readonly #chunks: number[] = []; | ||
|
|
||
| writeBytes(bytes: Uint8Array): void { | ||
| this.#chunks.push(...bytes); |
There was a problem hiding this comment.
writeBytes overflows the stack on large byte arrays
writeBytes(bytes: Uint8Array): void {
this.#chunks.push(...bytes);
}Writer.writeBytes() currently stores output in a number[] and appends each byte slice with this.#chunks.push(...bytes). This makes encoding riskier for larger XDR values: every write expands the whole Uint8Array into JS call arguments, and the final Uint8Array.from(this.#chunks) copies everything again. Across many fields/chunks, that can drift toward O(n²)-style allocation/copy overhead, and very large opaque/string/vector fields could also hit engine argument limits.
push(...bytes) spreads every byte as a separate function argument, so it's bounded by the engine's argument-count limit (~64k–125k depending on the JS engine). Every primitive writer funnels through writeBytes, so this caps the size of any opaque / varOpaque / string value we can encode.
In practice this breaks large ScBytes and contract Wasm uploads: an uploadContractWasm / InvokeHostFunction payload over ~125KB throws RangeError: Maximum call stack size exceeded at encode time. Wasm blobs routinely exceed that.
Suggested fix — accumulate into a list of chunks (or at minimum loop) instead of spreading:
readonly #chunks: Uint8Array[] = [];
#length = 0;
writeBytes(bytes: Uint8Array): void {
if (bytes.length === 0) return;
this.#chunks.push(bytes);
this.#length += bytes.length;
}
toUint8Array(): Uint8Array {
const out = new Uint8Array(this.#length);
let offset = 0;
for (const chunk of this.#chunks) {
out.set(chunk, offset);
offset += chunk.length;
}
return out;
}and concatenate in toUint8Array(). That also avoids the per-byte number[] overhead. If you'd rather keep the number[] accumulator, replace the spread with a for loop so it scales past the argument limit.
A regression test encoding a >256KB opaque value would lock this down.
There was a problem hiding this comment.
The fix already exists upstream — js-xdr#140 rewrote Writer to a growable Uint8Array (#ensureCapacity + #buffer.set) with a 200KB encode test. Since this copy is moving to js-xdr anyway, porting that verbatim keeps them in sync.
+1 on the >256KB regression test!
…source Replace the published @stellar/stellar-sdk@15.0.1 with the in-development class-XDR rewrite from stellar/js-stellar-sdk#1422 (commit c7eb18e), vendored as a prebuilt tarball and pinned via a pnpm override so the branch installs reproducibly (the SDK's own prepare/setup step can't run inside pnpm's git fetch sandbox). Migrate all SDK source to the new XDR surface: - union access via .type string discriminant + narrowing instead of .switch()/.value()/.arm() - enum singletons as property access (drop the call parens) - toXDR/fromXDR -> toXdr/fromXdr - ScVal arms via .value (wide ints expose .hi/.lo bigint; ScBytes.value is a Uint8Array) Add "node" to tsconfig types: the class-XDR build uses Uint8Array instead of Buffer and no longer transitively pulls @types/node, so process/Buffer globals must be declared explicitly.
| export function isPlainObject( | ||
| value: unknown, | ||
| ): value is Record<string, unknown> { | ||
| return typeof value === "object" && value !== null && !Array.isArray(value); |
There was a problem hiding this comment.
This is the same issue Copilot flagged on js-xdr#140. isPlainObject treats any non-array object as valid, including things like Date, Map, and Uint8Array. When one of those is passed into a struct or union, it slips past this check and only fails later with a confusing "missing field" error instead of a clear one here. Checking the prototype directly would catch it right away.
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
Why
The legacy
@stellar/js-xdrruntime had several rough edges thatconstrained the SDK from inside:
value.switch(),value.value(),value.arm()) with no TypeScript narrowing — everyunion access needed an
ascast.AssetType.assetTypeNative())even though the members are cached singletons under the hood. The
function-call ergonomics broke pattern-matching on enum identity and
made
switchstatements over enums verbose.new Int128(lo, hi))and exposed
slice()for accessing 32- or 64-bit chunks. The bigintvalue was only reachable via
.toBigInt().Int64/Uint64were object-boxed (Hyper/UnsignedHyperextending
LargeInt) rather than nativebigint.stellar-xdr-jsonwas a separate process.toXDR,fromXDR) where theSDK's broader convention is single-initial-cap (
toString, etc.).Additionally, this PR introduces several capabilities the legacy runtime
didn't have at all (not fixes, additions):
toJson/fromJsonon every XDR value, SEP-0051-compliant.toXdrObject/fromXdrObjectbridging methods (legacy types heldwire shape directly, so this distinction wasn't meaningful).
XdrStringwrapper for byte-faithfulstring<N>handling.What
A full in-tree replacement of the XDR layer (this will be moved to js-xdr but for testing purposes it lives here for now):
src/xdr/core/—Reader,Writer,BaseType<T>. Buffer I/Oprimitives.
src/xdr/types/— schema primitives (struct,union,enum,opaque,varOpaque,string,int32/int64/etc.,array,fixedArray,option,lazy). Charset-agnostic; no dependency onconsumer value classes. Designed to be liftable into a standalone XDR
runtime.
src/xdr/values/— consumer-facing bases.XdrValue(universalbase with
toXdr/fromXdr/toJson/fromJson),EnumValue,BytesValue,BigIntValue,XdrString. The SEP-0051 JSON walkerlives here (
to-json.ts).src/xdr/generated/— 447 generated classes produced bytools/xdrgen/generate.mjsreadingxdr/xdr.json. TSDoc on each classcarries the original
.xsource so hovers show the upstream XDRdeclaration.
src/xdr/dx/— hand-written DX overlays (Int128/Uint128/Int256/Uint256exposing a singlebigint) and primitive shims(
Int64/Uint64/Int32/Uint32returning native primitives via aProxyconstructtrap).Plus:
toXDR→toXdr,fromXDR→fromXdron every type.
toXDRObject/fromXDRObject/fromJSONlistedelsewhere are new methods with no legacy analogue.
LargeIntclasses insrc/base/numbers/—Int128,Uint128,Int256,Uint256now hold a singlebigint.XdrStringwrapper forstring<N>fields — bytes-faithful, withexplicit
.toString()/.toStringStrict()/.asStringOrBytes()/.bytes/.toJson()access patterns.value.toJson()andType.fromJson(json)onevery XDR class.
hand-written smoke tests (~15), real-traffic mainnet corpus (~15),
schema-driven exhaustive coverage (~2000), JSON walker round-trips
(~120).
docs/XDR_MIGRATION.md— caller-facing migration guide.docs/ARCHITECTURE.md— contributor-facing internals.Example changes
Union access —
.typeliteral narrowing replaces method-call accessors:Enum access — drop the parens; the singleton was already cached:
Wide ints — single bigint instead of parts arithmetic:
XDR strings —
XdrStringwrapper with explicit decoding:SEP-0051 JSON output (new — no legacy equivalent):
Method renames:
Bytes — explicit wrappers required:
Typedef-opaque aliases became distinct classes: