Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
354 changes: 354 additions & 0 deletions docs/migration-guide/v15.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,354 @@
# Migrating to @stellar/stellar-sdk v15 (Protocol 26)

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)
- [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.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

| 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 = AssembledTransaction.fromXDR(
options, // { contractId: 'CABC...', ... }
xdrString, // could target a completely different contract
spec, // contract Spec instance
);
// No error — silently worked with wrong contract
```

**After (v15):**
```ts
// Throws if the XDR targets a different contract
const assembled = AssembledTransaction.fromXDR(
options, // { contractId: 'CABC...', ... }
xdrString, // MUST target 'CABC...'
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<ReturnType>(xdrBase64);
```

**`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 -rnE '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/migration-guide/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 -rnE '\.networkPassphrase[[:space:]]*=' 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';
```

**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.

---

## 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. Got <value>` 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 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. 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.
Loading