Skip to content

Commit b078796

Browse files
committed
add v15 migration guide
1 parent 5daf44c commit b078796

File tree

1 file changed

+347
-0
lines changed

1 file changed

+347
-0
lines changed

docs/upgrade/v15.md

Lines changed: 347 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,347 @@
1+
# Migrating to @stellar/stellar-sdk v15 (Protocol 26)
2+
3+
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.
4+
5+
For the full list of changes, see:
6+
- [SDK CHANGELOG](../../CHANGELOG.md)
7+
- [stellar-base v15 CHANGELOG](https://github.com/stellar/js-stellar-base/blob/master/CHANGELOG.md)
8+
9+
---
10+
11+
## Table of Contents
12+
13+
- [Who Needs to Migrate](#who-needs-to-migrate)
14+
- [Prerequisites](#prerequisites)
15+
- [Step 1: Update Dependencies](#step-1-update-dependencies)
16+
- [Step 2: SDK-Specific Breaking Changes](#step-2-sdk-specific-breaking-changes)
17+
- [2a. `AssembledTransaction.fromXDR()` / `fromJSON()` Validate Contract ID (Critical)](#2a-assembledtransactionfromxdr--fromjson-validate-contract-id)
18+
- [2b. Generated Binding Identifiers Are Now Sanitized (Behavioral)](#2b-generated-binding-identifiers-are-now-sanitized)
19+
- [Step 3: Inherited Breaking Changes from stellar-base v15](#step-3-inherited-breaking-changes-from-stellar-base-v15)
20+
- [3a. Immutable `networkPassphrase` (Critical)](#3a-immutable-networkpassphrase)
21+
- [3b. XDR Integer Overflow Now Throws (Critical)](#3b-xdr-integer-overflow-now-throws)
22+
- [3c. Hermes Typed-Array Polyfill Removed (Critical — React Native only)](#3c-hermes-typed-array-polyfill-removed)
23+
- [Step 4: Behavioral Fixes That May Affect You](#step-4-behavioral-fixes-that-may-affect-you)
24+
- [4a. `Memo.id` Rejects Invalid Values (Critical)](#4a-memoid-rejects-invalid-values)
25+
- [4b. `Soroban.parseTokenAmount` Rejects Excess Decimals (Critical)](#4b-sorobanparsetokenamount-rejects-excess-decimals)
26+
- [4c. `Keypair.verify` Returns `false` Instead of Throwing (Behavioral)](#4c-keypairverify-returns-false-instead-of-throwing)
27+
- [4d. Other Behavioral Fixes (Low Impact)](#4d-other-behavioral-fixes)
28+
- [Step 5: Update TypeScript Exhaustive Switches](#step-5-update-typescript-exhaustive-switches)
29+
- [Step 6: Verify Your Upgrade](#step-6-verify-your-upgrade)
30+
- [FAQ / Troubleshooting](#faq--troubleshooting)
31+
32+
---
33+
34+
## Who Needs to Migrate
35+
36+
You need this guide if you:
37+
- Depend on `@stellar/stellar-sdk` and are bumping from any v14.x to v15.0.0
38+
- Use `AssembledTransaction` to interact with Soroban smart contracts
39+
- Generate TypeScript bindings from contract specs
40+
- Build, sign, or submit transactions via the SDK
41+
42+
---
43+
44+
## Prerequisites
45+
46+
| Requirement | Minimum Version |
47+
|---|---|
48+
| Node.js | >= 20 (unchanged from v14) |
49+
| npm / yarn / pnpm | Any recent version |
50+
51+
---
52+
53+
## Step 1: Update Dependencies
54+
55+
```bash
56+
npm install @stellar/stellar-sdk@^15.0.0
57+
```
58+
59+
This automatically pulls in `@stellar/stellar-base@^15.0.0` and `@stellar/js-xdr@^4.0.0`.
60+
61+
If you also depend on `@stellar/stellar-base` directly:
62+
```bash
63+
npm install @stellar/stellar-base@^15.0.0 @stellar/stellar-sdk@^15.0.0
64+
```
65+
66+
Verify:
67+
```bash
68+
npm ls @stellar/stellar-sdk @stellar/stellar-base @stellar/js-xdr
69+
# Should show: sdk 15.x, base 15.x, js-xdr 4.x
70+
```
71+
72+
---
73+
74+
## Step 2: SDK-Specific Breaking Changes
75+
76+
### 2a. `AssembledTransaction.fromXDR()` / `fromJSON()` Validate Contract ID
77+
78+
**Severity: Critical** — code will throw at runtime
79+
80+
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.
81+
82+
**Why:** Without validation, a malicious or misrouted XDR envelope could be deserialized and signed for the wrong contract — a security vulnerability.
83+
84+
**Before (v14):**
85+
```ts
86+
// Accepted any transaction envelope, no contract ID check
87+
const assembled = await AssembledTransaction.fromXDR(
88+
options, // { contractId: 'CABC...', ... }
89+
xdrString, // could target a completely different contract
90+
rpcServer,
91+
);
92+
// No error — silently worked with wrong contract
93+
```
94+
95+
**After (v15):**
96+
```ts
97+
// Throws if the XDR targets a different contract
98+
const assembled = await AssembledTransaction.fromXDR(
99+
options, // { contractId: 'CABC...', ... }
100+
xdrString, // MUST target 'CABC...'
101+
rpcServer,
102+
);
103+
// Error: "Transaction envelope targets contract CXYZ...,
104+
// but this Client is configured for CABC..."
105+
```
106+
107+
**`fromJSON()` additionally validates the method name:**
108+
```ts
109+
// Error: "Transaction envelope calls method 'transfer',
110+
// but the provided method is 'mint'."
111+
```
112+
113+
**Migration:**
114+
- Ensure the `contractId` in your options matches the contract in the XDR
115+
- If you intentionally handle multiple contracts, use separate `Client` instances:
116+
```ts
117+
const clientA = new Contract.Client({ contractId: 'CABC...', ... });
118+
const clientB = new Contract.Client({ contractId: 'CXYZ...', ... });
119+
```
120+
121+
**Find affected code:**
122+
```bash
123+
grep -rn 'fromXDR\|fromJSON' src/ --include='*.ts' --include='*.js'
124+
```
125+
126+
---
127+
128+
### 2b. Generated Binding Identifiers Are Now Sanitized
129+
130+
**Severity: Behavioral** — regenerated bindings may have different names
131+
132+
`sanitizeIdentifier()` now replaces all characters outside `[a-zA-Z0-9_$]` with `_`. A new `escapeStringLiteral()` escapes quotes, newlines, and Unicode separators in string contexts.
133+
134+
**Why:** Malicious contract specs with special characters in names could inject arbitrary code into generated TypeScript bindings.
135+
136+
**Before (v14):**
137+
```ts
138+
// Contract spec with special chars -> generated as-is (potential code injection)
139+
// spec name: "transfer;drop()" -> generated identifier: transfer;drop()
140+
```
141+
142+
**After (v15):**
143+
```ts
144+
// Same spec name -> sanitized identifier: transfer_drop__
145+
```
146+
147+
**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.
148+
149+
**Migration:**
150+
1. Regenerate your bindings: `npx @stellar/stellar-sdk generate ...`
151+
2. Update any imports or references to match the new sanitized names
152+
3. Run `tsc --noEmit` to find any broken references
153+
154+
---
155+
156+
## Step 3: Inherited Breaking Changes from stellar-base v15
157+
158+
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).
159+
160+
### 3a. Immutable `networkPassphrase`
161+
162+
**Severity: Critical** — code will throw at runtime
163+
164+
The `networkPassphrase` property on `Transaction` and `FeeBumpTransaction` is now read-only.
165+
166+
**Before (v14):**
167+
```js
168+
const tx = TransactionBuilder.fromXDR(xdrString, Networks.TESTNET);
169+
tx.networkPassphrase = Networks.PUBLIC; // silently worked
170+
```
171+
172+
**After (v15):**
173+
```js
174+
// Pass the correct passphrase at parse time
175+
const tx = TransactionBuilder.fromXDR(xdrString, Networks.PUBLIC);
176+
```
177+
178+
**Find affected code:**
179+
```bash
180+
grep -rn '\.networkPassphrase\s*=' src/
181+
```
182+
183+
---
184+
185+
### 3b. XDR Integer Overflow Now Throws
186+
187+
**Severity: Critical** — code will throw at runtime
188+
189+
Sized XDR integer types (`Uint32`, `Int32`, `Uint64`, etc.) now throw on overflow/underflow instead of silently clamping.
190+
191+
**Before (v14):**
192+
```js
193+
new xdr.Uint32(5000000000); // silently clamped to 4294967295
194+
```
195+
196+
**After (v15):**
197+
```js
198+
new xdr.Uint32(5000000000); // throws RangeError
199+
```
200+
201+
Validate inputs before constructing XDR integers. Valid ranges:
202+
203+
| Type | Min | Max |
204+
|---|---|---|
205+
| `Uint32` | `0` | `4,294,967,295` (2^32 - 1) |
206+
| `Int32` | `-2,147,483,648` | `2,147,483,647` |
207+
| `Uint64` / `UnsignedHyper` | `0` | `2^64 - 1` |
208+
| `Int64` / `Hyper` | `-2^63` | `2^63 - 1` |
209+
210+
---
211+
212+
### 3c. Hermes Typed-Array Polyfill Removed
213+
214+
**Severity: Critical** — React Native + Hermes only
215+
216+
```bash
217+
npm install @exodus/patch-broken-hermes-typed-arrays
218+
```
219+
```js
220+
// Add BEFORE any Stellar imports in your app entry point
221+
import '@exodus/patch-broken-hermes-typed-arrays';
222+
```
223+
224+
Node.js and browser environments are unaffected.
225+
226+
---
227+
228+
## Step 4: Behavioral Fixes That May Affect You
229+
230+
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.
231+
232+
### 4a. `Memo.id` Rejects Invalid Values
233+
234+
**Severity: Critical** — values that previously "worked" now throw
235+
236+
```js
237+
// These all throw now:
238+
Memo.id('-1'); // negative
239+
Memo.id('1.5'); // decimal
240+
Memo.id('18446744073709551616'); // > 2^64-1
241+
242+
// Valid:
243+
Memo.id('42');
244+
Memo.id('18446744073709551615'); // 2^64-1 (max)
245+
```
246+
247+
### 4b. `Soroban.parseTokenAmount` Rejects Excess Decimals
248+
249+
**Severity: Critical** — values that previously "worked" now throw
250+
251+
```js
252+
// Throws: 'Too many decimal places in "1.999999": expected at most 2, got 6'
253+
Soroban.parseTokenAmount('1.999999', 2);
254+
255+
// Truncate first:
256+
Soroban.parseTokenAmount('1.99', 2); // ok
257+
```
258+
259+
### 4c. `Keypair.verify` Returns `false` Instead of Throwing
260+
261+
**Severity: Behavioral** — different control flow, no crashes
262+
263+
```js
264+
// Before: malformed signatures threw exceptions
265+
// After: returns false for both invalid and malformed signatures
266+
267+
if (!keypair.verify(data, sig)) {
268+
// handles all failure cases
269+
}
270+
```
271+
272+
### 4d. Other Behavioral Fixes (Low Impact)
273+
274+
No migration needed for these — they correct bugs:
275+
276+
| Fix | What Changed |
277+
|---|---|
278+
| `TransactionBuilder.cloneFrom` | `extraSigners` re-encoded as StrKey; `unscaledFee` floored |
279+
| `TransactionBuilder` timebounds | `Date` objects floored to integer UNIX timestamps |
280+
| `Auth.bytesToInt64` | Upper-32-bit bytes processed correctly |
281+
| `ScInt` constructor | String inputs converted to `BigInt` first |
282+
| `SignerKey.decodeSignerKey` | Reads exact payload length from 4-byte prefix |
283+
| `Operation._toXDRPrice` | `{ n: 0, d: 1 }` handled correctly |
284+
285+
---
286+
287+
## Step 5: Update TypeScript Exhaustive Switches
288+
289+
Protocol 26 adds new XDR enum variants. If you have exhaustive `switch` statements on these types, add the new cases:
290+
291+
```ts
292+
// TransactionResultCode
293+
case xdr.TransactionResultCode.txFrozenKeyAccessed():
294+
break;
295+
296+
// ClaimClaimableBalanceResultCode
297+
case xdr.ClaimClaimableBalanceResultCode.claimClaimableBalanceTrustlineFrozen():
298+
break;
299+
300+
// LiquidityPoolDepositResultCode
301+
case xdr.LiquidityPoolDepositResultCode.liquidityPoolDepositTrustlineFrozen():
302+
break;
303+
304+
// LiquidityPoolWithdrawResultCode
305+
case xdr.LiquidityPoolWithdrawResultCode.liquidityPoolWithdrawTrustlineFrozen():
306+
break;
307+
308+
// ContractCostType — 16 new bn254* variants (IDs 70-85)
309+
```
310+
311+
---
312+
313+
## Step 6: Verify Your Upgrade
314+
315+
1. **Check dependency tree:**
316+
```bash
317+
npm ls @stellar/stellar-sdk @stellar/stellar-base @stellar/js-xdr
318+
```
319+
2. **Run your test suite** — watch for `RangeError` (XDR integers), `Error('Transaction is immutable')`, and contract ID validation errors
320+
3. **TypeScript:** `tsc --noEmit` — fix exhaustive switch errors
321+
4. **Regenerate bindings** if you use the CLI binding generator
322+
5. **Submit a test transaction on Testnet** to confirm end-to-end
323+
324+
---
325+
326+
## FAQ / Troubleshooting
327+
328+
**Q: I get `Transaction envelope targets contract X, but this Client is configured for Y`**
329+
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).
330+
331+
**Q: I get `Error: Transaction is immutable`**
332+
A: You're assigning to `tx.networkPassphrase`. Pass the correct passphrase at construction/parse time. See [Step 3a](#3a-immutable-networkpassphrase).
333+
334+
**Q: I get `RangeError` from XDR integer construction**
335+
A: Values exceeding the type's range are no longer clamped. Validate your inputs. See [Step 3b](#3b-xdr-integer-overflow-now-throws).
336+
337+
**Q: My generated bindings have different identifier names**
338+
A: Special characters are now sanitized to `_`. Regenerate and update your imports. See [Step 2b](#2b-generated-binding-identifiers-are-now-sanitized).
339+
340+
**Q: I get `Expects a uint64 as a string` from `Memo.id`**
341+
A: Negative, decimal, and overflow values are now rejected. See [Step 4a](#4a-memoid-rejects-invalid-values).
342+
343+
**Q: My React Native app crashes on `subarray is not a function`**
344+
A: Install `@exodus/patch-broken-hermes-typed-arrays`. See [Step 3c](#3c-hermes-typed-array-polyfill-removed).
345+
346+
**Q: Do I need to update if I'm not on a Protocol 26 network yet?**
347+
A: Yes. The breaking changes (immutable passphrase, integer overflow, Hermes polyfill, contract ID validation, binding sanitization) apply regardless of network protocol version.

0 commit comments

Comments
 (0)