From b078796e673c77721ff0ed8e73295d8e9840c445 Mon Sep 17 00:00:00 2001 From: oceans404 Date: Mon, 30 Mar 2026 15:22:48 -0700 Subject: [PATCH 1/3] add v15 migration guide --- docs/upgrade/v15.md | 347 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 347 insertions(+) create mode 100644 docs/upgrade/v15.md diff --git a/docs/upgrade/v15.md b/docs/upgrade/v15.md new file mode 100644 index 000000000..62b7a29d4 --- /dev/null +++ b/docs/upgrade/v15.md @@ -0,0 +1,347 @@ +# Migrating to @stellar/stellar-sdk v15 (Protocol 26) + +This guide walks you through upgrading from `@stellar/stellar-sdk` v14.x to v15.0.0. It covers SDK-specific changes **and** all inherited breaking changes from `@stellar/stellar-base` v15, so you only need this one document. + +For the full list of changes, see: +- [SDK CHANGELOG](../../CHANGELOG.md) +- [stellar-base v15 CHANGELOG](https://github.com/stellar/js-stellar-base/blob/master/CHANGELOG.md) + +--- + +## Table of Contents + +- [Who Needs to Migrate](#who-needs-to-migrate) +- [Prerequisites](#prerequisites) +- [Step 1: Update Dependencies](#step-1-update-dependencies) +- [Step 2: SDK-Specific Breaking Changes](#step-2-sdk-specific-breaking-changes) + - [2a. `AssembledTransaction.fromXDR()` / `fromJSON()` Validate Contract ID (Critical)](#2a-assembledtransactionfromxdr--fromjson-validate-contract-id) + - [2b. Generated Binding Identifiers Are Now Sanitized (Behavioral)](#2b-generated-binding-identifiers-are-now-sanitized) +- [Step 3: Inherited Breaking Changes from stellar-base v15](#step-3-inherited-breaking-changes-from-stellar-base-v15) + - [3a. Immutable `networkPassphrase` (Critical)](#3a-immutable-networkpassphrase) + - [3b. XDR Integer Overflow Now Throws (Critical)](#3b-xdr-integer-overflow-now-throws) + - [3c. Hermes Typed-Array Polyfill Removed (Critical — React Native only)](#3c-hermes-typed-array-polyfill-removed) +- [Step 4: Behavioral Fixes That May Affect You](#step-4-behavioral-fixes-that-may-affect-you) + - [4a. `Memo.id` Rejects Invalid Values (Critical)](#4a-memoid-rejects-invalid-values) + - [4b. `Soroban.parseTokenAmount` Rejects Excess Decimals (Critical)](#4b-sorobanparsetokenamount-rejects-excess-decimals) + - [4c. `Keypair.verify` Returns `false` Instead of Throwing (Behavioral)](#4c-keypairverify-returns-false-instead-of-throwing) + - [4d. Other Behavioral Fixes (Low Impact)](#4d-other-behavioral-fixes) +- [Step 5: Update TypeScript Exhaustive Switches](#step-5-update-typescript-exhaustive-switches) +- [Step 6: Verify Your Upgrade](#step-6-verify-your-upgrade) +- [FAQ / Troubleshooting](#faq--troubleshooting) + +--- + +## Who Needs to Migrate + +You need this guide if you: +- Depend on `@stellar/stellar-sdk` and are bumping from any v14.x to v15.0.0 +- Use `AssembledTransaction` to interact with Soroban smart contracts +- Generate TypeScript bindings from contract specs +- Build, sign, or submit transactions via the SDK + +--- + +## Prerequisites + +| Requirement | Minimum Version | +|---|---| +| Node.js | >= 20 (unchanged from v14) | +| npm / yarn / pnpm | Any recent version | + +--- + +## Step 1: Update Dependencies + +```bash +npm install @stellar/stellar-sdk@^15.0.0 +``` + +This automatically pulls in `@stellar/stellar-base@^15.0.0` and `@stellar/js-xdr@^4.0.0`. + +If you also depend on `@stellar/stellar-base` directly: +```bash +npm install @stellar/stellar-base@^15.0.0 @stellar/stellar-sdk@^15.0.0 +``` + +Verify: +```bash +npm ls @stellar/stellar-sdk @stellar/stellar-base @stellar/js-xdr +# Should show: sdk 15.x, base 15.x, js-xdr 4.x +``` + +--- + +## Step 2: SDK-Specific Breaking Changes + +### 2a. `AssembledTransaction.fromXDR()` / `fromJSON()` Validate Contract ID + +**Severity: Critical** — code will throw at runtime + +These methods now validate that the deserialized transaction targets the contract your `Client` is configured for. They also reject multi-operation transactions and non-`invokeHostFunction` operations. + +**Why:** Without validation, a malicious or misrouted XDR envelope could be deserialized and signed for the wrong contract — a security vulnerability. + +**Before (v14):** +```ts +// Accepted any transaction envelope, no contract ID check +const assembled = await AssembledTransaction.fromXDR( + options, // { contractId: 'CABC...', ... } + xdrString, // could target a completely different contract + rpcServer, +); +// No error — silently worked with wrong contract +``` + +**After (v15):** +```ts +// Throws if the XDR targets a different contract +const assembled = await AssembledTransaction.fromXDR( + options, // { contractId: 'CABC...', ... } + xdrString, // MUST target 'CABC...' + rpcServer, +); +// Error: "Transaction envelope targets contract CXYZ..., +// but this Client is configured for CABC..." +``` + +**`fromJSON()` additionally validates the method name:** +```ts +// Error: "Transaction envelope calls method 'transfer', +// but the provided method is 'mint'." +``` + +**Migration:** +- Ensure the `contractId` in your options matches the contract in the XDR +- If you intentionally handle multiple contracts, use separate `Client` instances: + ```ts + const clientA = new Contract.Client({ contractId: 'CABC...', ... }); + const clientB = new Contract.Client({ contractId: 'CXYZ...', ... }); + ``` + +**Find affected code:** +```bash +grep -rn 'fromXDR\|fromJSON' src/ --include='*.ts' --include='*.js' +``` + +--- + +### 2b. Generated Binding Identifiers Are Now Sanitized + +**Severity: Behavioral** — regenerated bindings may have different names + +`sanitizeIdentifier()` now replaces all characters outside `[a-zA-Z0-9_$]` with `_`. A new `escapeStringLiteral()` escapes quotes, newlines, and Unicode separators in string contexts. + +**Why:** Malicious contract specs with special characters in names could inject arbitrary code into generated TypeScript bindings. + +**Before (v14):** +```ts +// Contract spec with special chars -> generated as-is (potential code injection) +// spec name: "transfer;drop()" -> generated identifier: transfer;drop() +``` + +**After (v15):** +```ts +// Same spec name -> sanitized identifier: transfer_drop__ +``` + +**Who is affected:** Only if you regenerate bindings from contract specs that contain non-alphanumeric characters in identifiers. Standard contracts with clean names are unaffected. + +**Migration:** +1. Regenerate your bindings: `npx @stellar/stellar-sdk generate ...` +2. Update any imports or references to match the new sanitized names +3. Run `tsc --noEmit` to find any broken references + +--- + +## Step 3: Inherited Breaking Changes from stellar-base v15 + +These come from the `@stellar/stellar-base` v15.0.0 dependency bump. They affect all SDK users. For full details with extended code examples, see the [stellar-base migration guide](https://github.com/stellar/js-stellar-base/blob/master/docs/upgrade/v15.md). + +### 3a. Immutable `networkPassphrase` + +**Severity: Critical** — code will throw at runtime + +The `networkPassphrase` property on `Transaction` and `FeeBumpTransaction` is now read-only. + +**Before (v14):** +```js +const tx = TransactionBuilder.fromXDR(xdrString, Networks.TESTNET); +tx.networkPassphrase = Networks.PUBLIC; // silently worked +``` + +**After (v15):** +```js +// Pass the correct passphrase at parse time +const tx = TransactionBuilder.fromXDR(xdrString, Networks.PUBLIC); +``` + +**Find affected code:** +```bash +grep -rn '\.networkPassphrase\s*=' src/ +``` + +--- + +### 3b. XDR Integer Overflow Now Throws + +**Severity: Critical** — code will throw at runtime + +Sized XDR integer types (`Uint32`, `Int32`, `Uint64`, etc.) now throw on overflow/underflow instead of silently clamping. + +**Before (v14):** +```js +new xdr.Uint32(5000000000); // silently clamped to 4294967295 +``` + +**After (v15):** +```js +new xdr.Uint32(5000000000); // throws RangeError +``` + +Validate inputs before constructing XDR integers. Valid ranges: + +| Type | Min | Max | +|---|---|---| +| `Uint32` | `0` | `4,294,967,295` (2^32 - 1) | +| `Int32` | `-2,147,483,648` | `2,147,483,647` | +| `Uint64` / `UnsignedHyper` | `0` | `2^64 - 1` | +| `Int64` / `Hyper` | `-2^63` | `2^63 - 1` | + +--- + +### 3c. Hermes Typed-Array Polyfill Removed + +**Severity: Critical** — React Native + Hermes only + +```bash +npm install @exodus/patch-broken-hermes-typed-arrays +``` +```js +// Add BEFORE any Stellar imports in your app entry point +import '@exodus/patch-broken-hermes-typed-arrays'; +``` + +Node.js and browser environments are unaffected. + +--- + +## Step 4: Behavioral Fixes That May Affect You + +These are bug fixes that change behavior. They won't break most code, but if you relied on the old (incorrect) behavior, you'll see differences. + +### 4a. `Memo.id` Rejects Invalid Values + +**Severity: Critical** — values that previously "worked" now throw + +```js +// These all throw now: +Memo.id('-1'); // negative +Memo.id('1.5'); // decimal +Memo.id('18446744073709551616'); // > 2^64-1 + +// Valid: +Memo.id('42'); +Memo.id('18446744073709551615'); // 2^64-1 (max) +``` + +### 4b. `Soroban.parseTokenAmount` Rejects Excess Decimals + +**Severity: Critical** — values that previously "worked" now throw + +```js +// Throws: 'Too many decimal places in "1.999999": expected at most 2, got 6' +Soroban.parseTokenAmount('1.999999', 2); + +// Truncate first: +Soroban.parseTokenAmount('1.99', 2); // ok +``` + +### 4c. `Keypair.verify` Returns `false` Instead of Throwing + +**Severity: Behavioral** — different control flow, no crashes + +```js +// Before: malformed signatures threw exceptions +// After: returns false for both invalid and malformed signatures + +if (!keypair.verify(data, sig)) { + // handles all failure cases +} +``` + +### 4d. Other Behavioral Fixes (Low Impact) + +No migration needed for these — they correct bugs: + +| Fix | What Changed | +|---|---| +| `TransactionBuilder.cloneFrom` | `extraSigners` re-encoded as StrKey; `unscaledFee` floored | +| `TransactionBuilder` timebounds | `Date` objects floored to integer UNIX timestamps | +| `Auth.bytesToInt64` | Upper-32-bit bytes processed correctly | +| `ScInt` constructor | String inputs converted to `BigInt` first | +| `SignerKey.decodeSignerKey` | Reads exact payload length from 4-byte prefix | +| `Operation._toXDRPrice` | `{ n: 0, d: 1 }` handled correctly | + +--- + +## Step 5: Update TypeScript Exhaustive Switches + +Protocol 26 adds new XDR enum variants. If you have exhaustive `switch` statements on these types, add the new cases: + +```ts +// TransactionResultCode +case xdr.TransactionResultCode.txFrozenKeyAccessed(): + break; + +// ClaimClaimableBalanceResultCode +case xdr.ClaimClaimableBalanceResultCode.claimClaimableBalanceTrustlineFrozen(): + break; + +// LiquidityPoolDepositResultCode +case xdr.LiquidityPoolDepositResultCode.liquidityPoolDepositTrustlineFrozen(): + break; + +// LiquidityPoolWithdrawResultCode +case xdr.LiquidityPoolWithdrawResultCode.liquidityPoolWithdrawTrustlineFrozen(): + break; + +// ContractCostType — 16 new bn254* variants (IDs 70-85) +``` + +--- + +## Step 6: Verify Your Upgrade + +1. **Check dependency tree:** + ```bash + npm ls @stellar/stellar-sdk @stellar/stellar-base @stellar/js-xdr + ``` +2. **Run your test suite** — watch for `RangeError` (XDR integers), `Error('Transaction is immutable')`, and contract ID validation errors +3. **TypeScript:** `tsc --noEmit` — fix exhaustive switch errors +4. **Regenerate bindings** if you use the CLI binding generator +5. **Submit a test transaction on Testnet** to confirm end-to-end + +--- + +## FAQ / Troubleshooting + +**Q: I get `Transaction envelope targets contract X, but this Client is configured for Y`** +A: Your `fromXDR()` / `fromJSON()` call is deserializing a transaction for a different contract than the `Client` expects. Use the correct `contractId` or create a separate `Client` instance. See [Step 2a](#2a-assembledtransactionfromxdr--fromjson-validate-contract-id). + +**Q: I get `Error: Transaction is immutable`** +A: You're assigning to `tx.networkPassphrase`. Pass the correct passphrase at construction/parse time. See [Step 3a](#3a-immutable-networkpassphrase). + +**Q: I get `RangeError` from XDR integer construction** +A: Values exceeding the type's range are no longer clamped. Validate your inputs. See [Step 3b](#3b-xdr-integer-overflow-now-throws). + +**Q: My generated bindings have different identifier names** +A: Special characters are now sanitized to `_`. Regenerate and update your imports. See [Step 2b](#2b-generated-binding-identifiers-are-now-sanitized). + +**Q: I get `Expects a uint64 as a string` from `Memo.id`** +A: Negative, decimal, and overflow values are now rejected. See [Step 4a](#4a-memoid-rejects-invalid-values). + +**Q: My React Native app crashes on `subarray is not a function`** +A: Install `@exodus/patch-broken-hermes-typed-arrays`. See [Step 3c](#3c-hermes-typed-array-polyfill-removed). + +**Q: Do I need to update if I'm not on a Protocol 26 network yet?** +A: Yes. The breaking changes (immutable passphrase, integer overflow, Hermes polyfill, contract ID validation, binding sanitization) apply regardless of network protocol version. From d39f97a1754c85ab085e408df23ae0a84314f2b4 Mon Sep 17 00:00:00 2001 From: oceans404 Date: Mon, 30 Mar 2026 16:01:14 -0700 Subject: [PATCH 2/3] fix: correct fromXDR signature and grep portability in v15 migration guide --- docs/upgrade/v15.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/docs/upgrade/v15.md b/docs/upgrade/v15.md index 62b7a29d4..d6547f676 100644 --- a/docs/upgrade/v15.md +++ b/docs/upgrade/v15.md @@ -84,10 +84,10 @@ These methods now validate that the deserialized transaction targets the contrac **Before (v14):** ```ts // Accepted any transaction envelope, no contract ID check -const assembled = await AssembledTransaction.fromXDR( +const assembled = AssembledTransaction.fromXDR( options, // { contractId: 'CABC...', ... } xdrString, // could target a completely different contract - rpcServer, + spec, // contract Spec instance ); // No error — silently worked with wrong contract ``` @@ -95,13 +95,16 @@ const assembled = await AssembledTransaction.fromXDR( **After (v15):** ```ts // Throws if the XDR targets a different contract -const assembled = await AssembledTransaction.fromXDR( +const assembled = AssembledTransaction.fromXDR( options, // { contractId: 'CABC...', ... } xdrString, // MUST target 'CABC...' - rpcServer, + spec, // contract Spec instance ); // Error: "Transaction envelope targets contract CXYZ..., // but this Client is configured for CABC..." + +// Or use the Client helper, which passes the spec automatically: +const assembled = client.txFromXDR(xdrBase64); ``` **`fromJSON()` additionally validates the method name:** @@ -120,7 +123,7 @@ const assembled = await AssembledTransaction.fromXDR( **Find affected code:** ```bash -grep -rn 'fromXDR\|fromJSON' src/ --include='*.ts' --include='*.js' +grep -rnE 'fromXDR|fromJSON' src/ --include='*.ts' --include='*.js' ``` --- @@ -177,7 +180,7 @@ const tx = TransactionBuilder.fromXDR(xdrString, Networks.PUBLIC); **Find affected code:** ```bash -grep -rn '\.networkPassphrase\s*=' src/ +grep -rnE '\.networkPassphrase[[:space:]]*=' src/ ``` --- @@ -337,7 +340,7 @@ A: Values exceeding the type's range are no longer clamped. Validate your inputs **Q: My generated bindings have different identifier names** A: Special characters are now sanitized to `_`. Regenerate and update your imports. See [Step 2b](#2b-generated-binding-identifiers-are-now-sanitized). -**Q: I get `Expects a uint64 as a string` from `Memo.id`** +**Q: I get `Expects a uint64 as a string. Got ` from `Memo.id`** A: Negative, decimal, and overflow values are now rejected. See [Step 4a](#4a-memoid-rejects-invalid-values). **Q: My React Native app crashes on `subarray is not a function`** From 73eff4ba9b5fbebbe167ee8d28f5a9d08f21a874 Mon Sep 17 00:00:00 2001 From: oceans404 Date: Thu, 2 Apr 2026 08:52:06 -0700 Subject: [PATCH 3/3] fix: address PR feedback on v15 migration guide - Use v15.x instead of v15.0.0 to cover future minor releases - Add recommendation that all consumers upgrade - Move guide from docs/upgrade/ to docs/migration-guide/ - Add migration guide link to CHANGELOG.md v15.0.0 section - Fix Hermes polyfill FAQ: clarify silent wrong results, not a crash - Add P26 deployment dates (Testnet Apr 16, Mainnet May 6) with link - Update cross-reference link to base migration guide --- CHANGELOG.md | 2 ++ docs/{upgrade => migration-guide}/v15.md | 16 ++++++++++------ 2 files changed, 12 insertions(+), 6 deletions(-) rename docs/{upgrade => migration-guide}/v15.md (89%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a1ec4579..54a6ad8f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ A breaking change will get clearly marked in this log. ## [v15.0.0](https://github.com/stellar/js-stellar-sdk/compare/v14.6.1...v15.0.0) +**[Migration Guide](docs/migration-guide/v15.md)** — step-by-step upgrade instructions with code examples and severity ratings. + ### Breaking Changes * XDR has been upgraded to support **Protocol 26**, please refer to the [`@stellar/stellar-base`](https://github.com/stellar/js-stellar-base/releases/tag/v15.0.0) release notes for details and other breaking changes. diff --git a/docs/upgrade/v15.md b/docs/migration-guide/v15.md similarity index 89% rename from docs/upgrade/v15.md rename to docs/migration-guide/v15.md index d6547f676..8da9c45fc 100644 --- a/docs/upgrade/v15.md +++ b/docs/migration-guide/v15.md @@ -1,6 +1,6 @@ # Migrating to @stellar/stellar-sdk v15 (Protocol 26) -This guide walks you through upgrading from `@stellar/stellar-sdk` v14.x to v15.0.0. It covers SDK-specific changes **and** all inherited breaking changes from `@stellar/stellar-base` v15, so you only need this one document. +This guide walks you through upgrading from `@stellar/stellar-sdk` v14.x to v15.x. It covers SDK-specific changes **and** all inherited breaking changes from `@stellar/stellar-base` v15, so you only need this one document. For the full list of changes, see: - [SDK CHANGELOG](../../CHANGELOG.md) @@ -34,11 +34,13 @@ For the full list of changes, see: ## Who Needs to Migrate You need this guide if you: -- Depend on `@stellar/stellar-sdk` and are bumping from any v14.x to v15.0.0 +- Depend on `@stellar/stellar-sdk` and are bumping from any v14.x to v15.x - Use `AssembledTransaction` to interact with Soroban smart contracts - Generate TypeScript bindings from contract specs - Build, sign, or submit transactions via the SDK +Even if none of the above apply directly, **we recommend all consumers upgrade** to ensure everything works as expected with the latest fixes and Protocol 26 support. + --- ## Prerequisites @@ -158,7 +160,7 @@ grep -rnE 'fromXDR|fromJSON' src/ --include='*.ts' --include='*.js' ## Step 3: Inherited Breaking Changes from stellar-base v15 -These come from the `@stellar/stellar-base` v15.0.0 dependency bump. They affect all SDK users. For full details with extended code examples, see the [stellar-base migration guide](https://github.com/stellar/js-stellar-base/blob/master/docs/upgrade/v15.md). +These come from the `@stellar/stellar-base` v15.0.0 dependency bump. They affect all SDK users. For full details with extended code examples, see the [stellar-base migration guide](https://github.com/stellar/js-stellar-base/blob/master/docs/migration-guide/v15.md). ### 3a. Immutable `networkPassphrase` @@ -224,6 +226,8 @@ npm install @exodus/patch-broken-hermes-typed-arrays import '@exodus/patch-broken-hermes-typed-arrays'; ``` +**Important:** Without the polyfill, you will **not** get an error. Hermes's broken `TypedArray.subarray` silently returns incorrect data, so operations like signing, XDR encoding, and hashing will produce wrong results without any indication that something is wrong. This makes the issue difficult to diagnose — your code runs without errors but behaves incorrectly. + Node.js and browser environments are unaffected. --- @@ -343,8 +347,8 @@ A: Special characters are now sanitized to `_`. Regenerate and update your impor **Q: I get `Expects a uint64 as a string. Got ` from `Memo.id`** A: Negative, decimal, and overflow values are now rejected. See [Step 4a](#4a-memoid-rejects-invalid-values). -**Q: My React Native app crashes on `subarray is not a function`** -A: Install `@exodus/patch-broken-hermes-typed-arrays`. See [Step 3c](#3c-hermes-typed-array-polyfill-removed). +**Q: My React Native app on Hermes returns incorrect or corrupted data (e.g., wrong signatures, garbled XDR)** +A: Without the polyfill, Hermes's broken `TypedArray.subarray` silently returns wrong results rather than throwing an error. Install `@exodus/patch-broken-hermes-typed-arrays` and import it before any Stellar imports. See [Step 3c](#3c-hermes-typed-array-polyfill-removed). **Q: Do I need to update if I'm not on a Protocol 26 network yet?** -A: Yes. The breaking changes (immutable passphrase, integer overflow, Hermes polyfill, contract ID validation, binding sanitization) apply regardless of network protocol version. +A: Yes. The breaking changes (immutable passphrase, integer overflow, Hermes polyfill, contract ID validation, binding sanitization) apply regardless of network protocol version. Protocol 26 is scheduled for Testnet on **April 16, 2026** and the Mainnet upgrade vote on **May 6, 2026**. See the [Protocol 26 Upgrade Guide](https://stellar.org/blog/foundation-news/stellar-yardstick-protocol-26-upgrade-guide) for the full timeline.