From 9baa53da1211d0be79597fe18a285108fd2a6f55 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Wed, 17 Jun 2026 17:19:04 +0000
Subject: [PATCH 1/2] chore: bump app version to v1.19.27
---
android/app/build.gradle | 2 +-
ios/freighter-mobile.xcodeproj/project.pbxproj | 8 ++++----
ios/freighter-mobile/Info-Dev.plist | 2 +-
ios/freighter-mobile/Info.plist | 2 +-
package.json | 2 +-
5 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/android/app/build.gradle b/android/app/build.gradle
index d34731c4c..90c492b3c 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -141,7 +141,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1234567890
- versionName "1.19.26"
+ versionName "1.19.27"
}
buildTypes {
diff --git a/ios/freighter-mobile.xcodeproj/project.pbxproj b/ios/freighter-mobile.xcodeproj/project.pbxproj
index c3cf4b2c5..0526e587c 100644
--- a/ios/freighter-mobile.xcodeproj/project.pbxproj
+++ b/ios/freighter-mobile.xcodeproj/project.pbxproj
@@ -505,7 +505,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 1.19.26;
+ MARKETING_VERSION = 1.19.27;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -542,7 +542,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 1.19.26;
+ MARKETING_VERSION = 1.19.27;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -740,7 +740,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 1.19.26;
+ MARKETING_VERSION = 1.19.27;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -775,7 +775,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 1.19.26;
+ MARKETING_VERSION = 1.19.27;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
diff --git a/ios/freighter-mobile/Info-Dev.plist b/ios/freighter-mobile/Info-Dev.plist
index ead6b9c24..0c9aff850 100644
--- a/ios/freighter-mobile/Info-Dev.plist
+++ b/ios/freighter-mobile/Info-Dev.plist
@@ -17,7 +17,7 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 1.19.26
+ 1.19.27
CFBundleSignature
????
CFBundleURLTypes
diff --git a/ios/freighter-mobile/Info.plist b/ios/freighter-mobile/Info.plist
index 4507e3130..14489e219 100644
--- a/ios/freighter-mobile/Info.plist
+++ b/ios/freighter-mobile/Info.plist
@@ -17,7 +17,7 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 1.19.26
+ 1.19.27
CFBundleSignature
????
CFBundleURLTypes
diff --git a/package.json b/package.json
index 174a20c11..d4ab0bde8 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "freighter-mobile",
- "version": "1.19.26",
+ "version": "1.19.27",
"license": "Apache-2.0",
"scripts": {
"android": "yarn android-dev",
From a793306feee16c069218de75155eb2f637da6c1b Mon Sep 17 00:00:00 2001
From: Aristides Staffieri
Date: Wed, 17 Jun 2026 13:06:24 -0600
Subject: [PATCH 2/2] fix: resolve federation addresses under stellar-sdk v16
in React Native
Federation lookups broke after the stellar-sdk v16 upgrade with
"stellar.toml does not contain FEDERATION_SERVER field".
SDK v16 routes stellar.toml/federation requests (which set
maxContentLength/maxRedirects) through feaxios' bounded-fetch adapter,
which reads the body exclusively via `response.body.getReader()`. React
Native's fetch (whatwg-fetch) implements text()/arrayBuffer()/json() but
never exposes the streaming `Response.body` getter, so the adapter read
an empty buffer, every stellar.toml parsed to `{}`, and FEDERATION_SERVER
came back undefined.
Add a Response.body polyfill that backs the missing getter with the
arrayBuffer() RN does support, exposed as a single-chunk ReadableStream.
It is guarded (no-ops where Response.body already exists) and memoized per
response. Wired into bootstrap.js alongside the existing SDK polyfills, so
it loads before any SDK network call. Fixes federation and all other SDK
bounded-fetch paths, not just the symptom.
---
__tests__/polyfills/responseBody.test.ts | 132 +++++++++++++++++++++++
package.json | 1 +
src/bootstrap.js | 5 +
src/polyfills/responseBody.ts | 86 +++++++++++++++
yarn.lock | 1 +
5 files changed, 225 insertions(+)
create mode 100644 __tests__/polyfills/responseBody.test.ts
create mode 100644 src/polyfills/responseBody.ts
diff --git a/__tests__/polyfills/responseBody.test.ts b/__tests__/polyfills/responseBody.test.ts
new file mode 100644
index 000000000..10cdeb5f8
--- /dev/null
+++ b/__tests__/polyfills/responseBody.test.ts
@@ -0,0 +1,132 @@
+/**
+ * Response.body ReadableStream Polyfill Tests
+ *
+ * React Native's fetch (whatwg-fetch) implements Body.text()/arrayBuffer()/json()
+ * but does NOT expose the streaming `Response.body` getter. Stellar SDK v16's
+ * feaxios bounded-fetch adapter (used by StellarToml.Resolver and
+ * Federation.Server whenever maxContentLength/maxRedirects are set) reads the
+ * response exclusively via `response.body.getReader()`. With `body` undefined the
+ * adapter fell back to an empty buffer, so every stellar.toml parsed to `{}` and
+ * federation lookups failed with "stellar.toml does not contain FEDERATION_SERVER
+ * field".
+ *
+ * Jest (V8/Node) provides a native streaming `Response.body`, so these tests run
+ * the polyfill against plain prototype objects lacking `body` to reproduce the
+ * React Native gap.
+ */
+type ArrayBufferBody = { arrayBuffer(): Promise };
+
+type ReadableLike = {
+ getReader(): {
+ read(): Promise<{ done: boolean; value?: Uint8Array }>;
+ };
+};
+
+let createResponseBodyStream: (response: ArrayBufferBody) => ReadableLike;
+let installResponseBodyPolyfill: (target?: unknown) => void;
+
+beforeAll(() => {
+ // eslint-disable-next-line global-require, @typescript-eslint/no-var-requires
+ ({
+ createResponseBodyStream,
+ installResponseBodyPolyfill,
+ } = require("../../src/polyfills/responseBody"));
+});
+
+const encode = (text: string): ArrayBuffer => {
+ const { buffer, byteOffset, byteLength } = new TextEncoder().encode(text);
+ return buffer.slice(byteOffset, byteOffset + byteLength);
+};
+
+const readStream = async (stream: ReadableLike): Promise => {
+ const reader = stream.getReader();
+ const chunks: Uint8Array[] = [];
+ let total = 0;
+
+ for (;;) {
+ // eslint-disable-next-line no-await-in-loop
+ const { done, value } = await reader.read();
+ if (done) break;
+ if (value) {
+ chunks.push(value);
+ total += value.byteLength;
+ }
+ }
+
+ const merged = new Uint8Array(total);
+ let offset = 0;
+ chunks.forEach((chunk) => {
+ merged.set(chunk, offset);
+ offset += chunk.byteLength;
+ });
+
+ return new TextDecoder().decode(merged);
+};
+
+describe("Response.body polyfill", () => {
+ it("createResponseBodyStream streams the body that arrayBuffer() returns", async () => {
+ // A real stellar.toml snippet: the streamed bytes must round-trip intact so
+ // the SDK's smol-toml parse can recover FEDERATION_SERVER, the field whose
+ // absence broke federation resolution.
+ const toml = 'FEDERATION_SERVER = "https://stellar.org/federation"\n';
+ const response = { arrayBuffer: () => Promise.resolve(encode(toml)) };
+
+ const text = await readStream(createResponseBodyStream(response));
+
+ expect(text).toBe(toml);
+ });
+
+ it("createResponseBodyStream surfaces arrayBuffer() rejection as a stream error", async () => {
+ const failure = new Error("network down");
+ const response = { arrayBuffer: () => Promise.reject(failure) };
+
+ await expect(
+ readStream(createResponseBodyStream(response)),
+ ).rejects.toThrow("network down");
+ });
+
+ it("installs a working Response.body getter when one is missing", async () => {
+ const toml = 'FEDERATION_SERVER = "https://example.com/federation"\n';
+ const prototype: ArrayBufferBody = {
+ arrayBuffer: () => Promise.resolve(encode(toml)),
+ };
+ const target = { prototype } as unknown as typeof Response;
+
+ expect(Object.getOwnPropertyDescriptor(prototype, "body")).toBeUndefined();
+
+ installResponseBodyPolyfill(target);
+
+ const response = Object.create(prototype) as { body: ReadableLike | null };
+ expect(response.body).not.toBeNull();
+ expect(await readStream(response.body as ReadableLike)).toBe(toml);
+ });
+
+ it("memoizes the stream so the single-use body is not consumed twice", () => {
+ const prototype: ArrayBufferBody = {
+ arrayBuffer: () => Promise.resolve(encode('FEDERATION_SERVER = "x"')),
+ };
+ const target = { prototype } as unknown as typeof Response;
+
+ installResponseBodyPolyfill(target);
+
+ const response = Object.create(prototype) as { body: ReadableLike };
+ expect(response.body).toBe(response.body);
+ });
+
+ it("does not override a runtime that already provides Response.body", () => {
+ const nativeStream = Symbol("native-body");
+ const prototype: ArrayBufferBody = {
+ arrayBuffer: () => Promise.resolve(new ArrayBuffer(0)),
+ };
+ Object.defineProperty(prototype, "body", {
+ configurable: true,
+ get: () => nativeStream,
+ });
+ const target = { prototype } as unknown as typeof Response;
+
+ installResponseBodyPolyfill(target);
+
+ const response = Object.create(prototype) as { body: unknown };
+ expect(response.body).toBe(nativeStream);
+ });
+});
diff --git a/package.json b/package.json
index d4ab0bde8..7549cd91a 100644
--- a/package.json
+++ b/package.json
@@ -147,6 +147,7 @@
"url": "0.11.4",
"util": "0.10.3",
"vm-browserify": "0.0.4",
+ "web-streams-polyfill": "4.0.0-beta.3",
"zeego": "3.0.6",
"zustand": "5.0.6"
},
diff --git a/src/bootstrap.js b/src/bootstrap.js
index bc40e2554..cf6b86644 100644
--- a/src/bootstrap.js
+++ b/src/bootstrap.js
@@ -14,5 +14,10 @@ require("./polyfills/xhr");
// Stellar SDK's feaxios HTTP client needs for transaction submission.
require("./polyfills/abortSignal");
+// Response.body polyfill - React Native's fetch lacks the streaming body getter,
+// which the Stellar SDK's bounded-fetch adapter (stellar.toml/federation
+// resolution) reads from. Without it federation addresses fail to resolve.
+require("./polyfills/responseBody");
+
// Export nothing - this file is used only for side effects
module.exports = {};
diff --git a/src/polyfills/responseBody.ts b/src/polyfills/responseBody.ts
new file mode 100644
index 000000000..be8d31e5f
--- /dev/null
+++ b/src/polyfills/responseBody.ts
@@ -0,0 +1,86 @@
+/**
+ * Response.body ReadableStream Polyfill for React Native
+ *
+ * React Native's fetch (whatwg-fetch) implements the Body consumers
+ * `text()`/`arrayBuffer()`/`json()` but never exposes the streaming
+ * `Response.body` getter, so `response.body` is `undefined`.
+ *
+ * The Stellar SDK (v16+) routes HTTP through `feaxios`. Whenever a request sets
+ * `maxContentLength` or `maxRedirects` it takes the SDK's bounded-fetch adapter,
+ * which reads the response body *exclusively* via `response.body.getReader()`.
+ * `StellarToml.Resolver.resolve()` and `Federation.Server` both set those
+ * options, so on React Native the adapter saw `body === undefined`, fell back to
+ * an empty buffer, and every stellar.toml parsed to `{}`. Federation lookups then
+ * failed with "stellar.toml does not contain FEDERATION_SERVER field".
+ *
+ * This polyfill backs the missing getter with the `arrayBuffer()` that React
+ * Native does support, exposing a single-chunk `ReadableStream`. It is guarded so
+ * it no-ops on runtimes (Node/V8 in Jest, future RN) that already provide
+ * `Response.body`.
+ *
+ * `ReadableStream` is imported from `web-streams-polyfill` because Hermes does not
+ * provide a global one.
+ */
+import { ReadableStream } from "web-streams-polyfill";
+
+/** The subset of `Response` this polyfill relies on. */
+interface ArrayBufferBody {
+ arrayBuffer(): Promise;
+}
+
+/**
+ * Builds a one-shot `ReadableStream` that emits the response body as a single
+ * `Uint8Array` chunk, sourced from `arrayBuffer()`. A rejected `arrayBuffer()`
+ * surfaces as a stream error so callers see the original failure.
+ */
+export const createResponseBodyStream = (
+ response: ArrayBufferBody,
+): ReadableStream => {
+ const bodyPromise = response.arrayBuffer();
+
+ return new ReadableStream({
+ start(controller) {
+ bodyPromise
+ .then((buffer) => {
+ controller.enqueue(new Uint8Array(buffer));
+ controller.close();
+ })
+ .catch((error: unknown) => controller.error(error));
+ },
+ });
+};
+
+/**
+ * Defines a lazy `body` getter on the target's prototype when it is missing. The
+ * stream is memoized per response instance so repeated access returns the same
+ * stream and the single-use body is not consumed twice.
+ */
+export const installResponseBodyPolyfill = (
+ ResponseCtor: typeof Response | undefined = typeof Response === "undefined"
+ ? undefined
+ : Response,
+): void => {
+ if (!ResponseCtor) {
+ return;
+ }
+
+ if (Object.getOwnPropertyDescriptor(ResponseCtor.prototype, "body")) {
+ return;
+ }
+
+ Object.defineProperty(ResponseCtor.prototype, "body", {
+ configurable: true,
+ get(this: ArrayBufferBody): ReadableStream {
+ const stream = createResponseBodyStream(this);
+ Object.defineProperty(this, "body", {
+ configurable: true,
+ value: stream,
+ });
+ return stream;
+ },
+ });
+};
+
+installResponseBodyPolyfill();
+
+export {};
diff --git a/yarn.lock b/yarn.lock
index 82ee862f3..076c96f0a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -10031,6 +10031,7 @@ __metadata:
url: "npm:0.11.4"
util: "npm:0.10.3"
vm-browserify: "npm:0.0.4"
+ web-streams-polyfill: "npm:4.0.0-beta.3"
zeego: "npm:3.0.6"
zustand: "npm:5.0.6"
languageName: unknown