From bc5c3c0a5e8c1df9594f4d03ddcd3223e363637f Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Fri, 3 Apr 2026 21:51:53 +0000
Subject: [PATCH 1/2] chore: bump app version to v1.14.25
---
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 b302df00..e170cb4d 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.13.25"
+ versionName "1.14.25"
}
buildTypes {
diff --git a/ios/freighter-mobile.xcodeproj/project.pbxproj b/ios/freighter-mobile.xcodeproj/project.pbxproj
index 851a422d..40f4092d 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.13.25;
+ MARKETING_VERSION = 1.14.25;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -542,7 +542,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 1.13.25;
+ MARKETING_VERSION = 1.14.25;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -740,7 +740,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 1.13.25;
+ MARKETING_VERSION = 1.14.25;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -775,7 +775,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 1.13.25;
+ MARKETING_VERSION = 1.14.25;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
diff --git a/ios/freighter-mobile/Info-Dev.plist b/ios/freighter-mobile/Info-Dev.plist
index 76cbcfca..1daf2ce8 100644
--- a/ios/freighter-mobile/Info-Dev.plist
+++ b/ios/freighter-mobile/Info-Dev.plist
@@ -17,7 +17,7 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 1.13.25
+ 1.14.25
CFBundleSignature
????
CFBundleURLTypes
diff --git a/ios/freighter-mobile/Info.plist b/ios/freighter-mobile/Info.plist
index 3b4fbdfe..8023e0e4 100644
--- a/ios/freighter-mobile/Info.plist
+++ b/ios/freighter-mobile/Info.plist
@@ -17,7 +17,7 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 1.13.25
+ 1.14.25
CFBundleSignature
????
CFBundleURLTypes
diff --git a/package.json b/package.json
index 26de648b..3c682686 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "freighter-mobile",
- "version": "1.13.25",
+ "version": "1.14.25",
"license": "Apache-2.0",
"scripts": {
"android": "yarn android-dev",
From 94cfe7848fc12d494f631e430106b64544046382 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?C=C3=A1ssio=20Marcos=20Goulart?=
<3228151+CassioMG@users.noreply.github.com>
Date: Fri, 3 Apr 2026 17:28:18 -0700
Subject: [PATCH 2/2] Fix crash when adding contract tokens from stellar.expert
search (#799) (#801)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* Fix crash when adding contract tokens from stellar.expert search (#799)
stellar.expert now returns Soroban contract tokens alongside classic
assets. The `asset` field for these is a raw contract ID instead of the
"CODE-ISSUER-TYPE" format, which caused `new Asset()` to throw when
the 56-char contract ID was used as an asset code.
Handle contract tokens in `formatTokensFromSearchResults` by detecting
contract IDs and reading metadata from the new top-level fields
(`code`, `decimals`, `token_name`) with `tomlInfo` as fallback.
Co-Authored-By: Claude Opus 4.6 (1M context)
* Cap CI Xcode version at 26.3 to fix iOS build
Xcode 26.4 ships a stricter Clang that breaks the `fmt` CocoaPod with
consteval errors. Cap the auto-selected Xcode at 26.3 until the Pod is
updated. The cap is a single variable (MAX_XCODE_VERSION) at the top of
the script for easy bumping.
Co-Authored-By: Claude Opus 4.6 (1M context)
* Add tests for contract token parsing from stellar.expert
Test that search results containing Soroban contract tokens (where
`asset` is a raw contract ID) are correctly parsed using the new
top-level fields, and that missing metadata falls back gracefully.
Co-Authored-By: Claude Opus 4.6 (1M context)
* Prevent crash when swapping custom/Soroban tokens
Custom tokens cannot be swapped via Horizon's classic DEX path finder.
Detect them early in findSwapPath and show a user-friendly error instead
of letting getTokenForPayment throw an unhandled exception.
Co-Authored-By: Claude Opus 4.6 (1M context)
* Fix infinite useEffect loop in SwapAmountScreen
Remove activeError from the dependency array of the pathError useEffect.
activeError was both set by and a dependency of this effect, creating an
infinite loop: setActiveError creates a new object → triggers the effect
→ setActiveError again → forever. The effect only needs to react to
pathError, sourceAmount, and destinationTokenId changes.
Co-Authored-By: Claude Opus 4.6 (1M context)
* Fix tokenContractId selection to prefer explicit contract field
Use explicit isContractId checks instead of || fallback to avoid picking
a non-empty classic issuer (G...) when tokenCode holds the contract ID.
Co-Authored-By: Claude Opus 4.6 (1M context)
* add comment
---------
Co-authored-by: Claude Opus 4.6 (1M context)
---
__tests__/hooks/useTokenLookup.test.tsx | 110 ++++++++++++++++++
scripts/setup-xcode-latest-stable | 28 +++--
.../AddTokenBottomSheetContent.tsx | 22 +++-
.../SwapScreen/screens/SwapAmountScreen.tsx | 2 +-
src/config/types.ts | 9 +-
src/ducks/swap.ts | 18 +++
src/hooks/useTokenLookup.ts | 75 +++++++++---
src/i18n/locales/en/translations.json | 3 +-
src/i18n/locales/pt/translations.json | 3 +-
9 files changed, 239 insertions(+), 31 deletions(-)
diff --git a/__tests__/hooks/useTokenLookup.test.tsx b/__tests__/hooks/useTokenLookup.test.tsx
index b09bc17f..1529517a 100644
--- a/__tests__/hooks/useTokenLookup.test.tsx
+++ b/__tests__/hooks/useTokenLookup.test.tsx
@@ -109,6 +109,46 @@ jest.mock("services/stellarExpert", () => ({
},
});
}
+ if (searchTerm === "dejaaa") {
+ return Promise.resolve({
+ _embedded: {
+ records: [
+ {
+ asset: "CC64WBDGS6QQP22QTTIACYIXT3WF7BBQEYOQPLTP7GTKYY7PZ74QYGSL",
+ domain: "centrifuge.io",
+ code: "deJAAA",
+ token_name: "deJAAA",
+ decimals: 18,
+ tomlInfo: {
+ code: "deJAAA",
+ issuer:
+ "CBSZNRNQIFHKHBHPZEHJFWGTJMPLYN4NJYGTLEJBHUVQTTOQ2WSQ6OMV",
+ image:
+ "https://stellar.myfilebase.com/ipfs/QmXu7RUtm2zhGDhk1ScDSWH587xccMm8ewprrFr4rYX6F3",
+ name: "deJAAA",
+ decimals: 18,
+ },
+ },
+ {
+ asset:
+ "DEJAAA-GASIX3XBHMFJGHOMC2FEELMO2E6JYE5LL2V2QBW3GX23GJI2EHWM4R5E-2",
+ },
+ ],
+ },
+ });
+ }
+ if (searchTerm === "contract-only") {
+ return Promise.resolve({
+ _embedded: {
+ records: [
+ {
+ asset: "CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC",
+ domain: "example.com",
+ },
+ ],
+ },
+ });
+ }
if (searchTerm === "error") {
return Promise.reject(new Error("Search failed"));
}
@@ -294,6 +334,76 @@ describe("useTokenLookup", () => {
expect(tokens).not.toContain("FOO");
});
+ it("should parse contract tokens from stellar.expert search results", async () => {
+ const { result } = renderHook(() =>
+ useTokenLookup({
+ network: mockNetwork,
+ publicKey: mockPublicKey,
+ balanceItems: mockBalanceItems,
+ }),
+ );
+
+ await act(async () => {
+ result.current.handleSearch("dejaaa");
+ await flushPromises();
+ });
+
+ expect(result.current.status).toBe(HookStatus.SUCCESS);
+ expect(result.current.searchResults).toHaveLength(2);
+
+ // First result: contract token — should use top-level `code`, not the raw contract ID
+ const contractToken = result.current.searchResults[0];
+ expect(contractToken.tokenCode).toBe("deJAAA");
+ expect(contractToken.issuer).toBe(
+ "CC64WBDGS6QQP22QTTIACYIXT3WF7BBQEYOQPLTP7GTKYY7PZ74QYGSL",
+ );
+ expect(contractToken.domain).toBe("centrifuge.io");
+ expect(contractToken.tokenType).toBe(TokenTypeWithCustomToken.CUSTOM_TOKEN);
+ expect(contractToken.decimals).toBe(18);
+ expect(contractToken.name).toBe("deJAAA");
+ expect(contractToken.iconUrl).toBe(
+ "https://stellar.myfilebase.com/ipfs/QmXu7RUtm2zhGDhk1ScDSWH587xccMm8ewprrFr4rYX6F3",
+ );
+
+ // Second result: classic asset — should be parsed with split("-")
+ const classicToken = result.current.searchResults[1];
+ expect(classicToken.tokenCode).toBe("DEJAAA");
+ expect(classicToken.issuer).toBe(
+ "GASIX3XBHMFJGHOMC2FEELMO2E6JYE5LL2V2QBW3GX23GJI2EHWM4R5E",
+ );
+ });
+
+ it("should fall back to contract ID as tokenCode when metadata is missing", async () => {
+ const { result } = renderHook(() =>
+ useTokenLookup({
+ network: mockNetwork,
+ publicKey: mockPublicKey,
+ balanceItems: mockBalanceItems,
+ }),
+ );
+
+ await act(async () => {
+ result.current.handleSearch("contract-only");
+ await flushPromises();
+ });
+
+ expect(result.current.status).toBe(HookStatus.SUCCESS);
+ expect(result.current.searchResults).toHaveLength(1);
+
+ const token = result.current.searchResults[0];
+ // No `code` or `tomlInfo.code` — falls back to contract ID
+ expect(token.tokenCode).toBe(
+ "CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC",
+ );
+ expect(token.issuer).toBe(
+ "CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC",
+ );
+ expect(token.domain).toBe("example.com");
+ expect(token.tokenType).toBe(TokenTypeWithCustomToken.CUSTOM_TOKEN);
+ expect(token.decimals).toBeUndefined();
+ expect(token.iconUrl).toBeUndefined();
+ });
+
it("should handle contract ID search", async () => {
const { result } = renderHook(() =>
useTokenLookup({
diff --git a/scripts/setup-xcode-latest-stable b/scripts/setup-xcode-latest-stable
index 6ac268fc..3cf64f0e 100644
--- a/scripts/setup-xcode-latest-stable
+++ b/scripts/setup-xcode-latest-stable
@@ -4,6 +4,10 @@
set -e
+# Maximum allowed Xcode major.minor version.
+# Bump this once all Pods (especially `fmt`) compile cleanly on the next Xcode.
+MAX_XCODE_VERSION="26.3"
+
# Find all beta/RC Xcode versions to identify base versions to exclude
BETA_XCODE_PATHS=$(ls -1d /Applications/Xcode*.app 2>/dev/null | grep -E "(Release_Candidate|RC|beta|Beta)" || true)
@@ -44,15 +48,23 @@ for XCODE_PATH in $XCODE_PATHS; do
# Extract major.minor version for comparison
BASE_VERSION=$(echo "$VERSION" | sed -E 's/^([0-9]+\.[0-9]+).*/\1/')
- # Check if this base version should be excluded
+ # Check if this version exceeds the maximum allowed version
SHOULD_EXCLUDE=false
- for EXCLUDED_BASE in $EXCLUDED_BASE_VERSIONS; do
- if [ "$BASE_VERSION" = "$EXCLUDED_BASE" ]; then
- echo "Excluding $XCODE_PATH (Version: $VERSION, Base: $BASE_VERSION matches excluded base version)"
- SHOULD_EXCLUDE=true
- break
- fi
- done
+ if [ -n "$MAX_XCODE_VERSION" ] && [ "$(printf '%s\n%s' "$MAX_XCODE_VERSION" "$BASE_VERSION" | sort -V | head -n1)" != "$BASE_VERSION" ]; then
+ echo "Excluding $XCODE_PATH (Version: $VERSION exceeds max allowed $MAX_XCODE_VERSION)"
+ SHOULD_EXCLUDE=true
+ fi
+
+ # Check if this base version should be excluded (beta/RC)
+ if [ "$SHOULD_EXCLUDE" = "false" ]; then
+ for EXCLUDED_BASE in $EXCLUDED_BASE_VERSIONS; do
+ if [ "$BASE_VERSION" = "$EXCLUDED_BASE" ]; then
+ echo "Excluding $XCODE_PATH (Version: $VERSION, Base: $BASE_VERSION matches excluded base version)"
+ SHOULD_EXCLUDE=true
+ break
+ fi
+ done
+ fi
if [ "$SHOULD_EXCLUDE" = "false" ]; then
printf "%s\t%s\n" "$VERSION" "$XCODE_PATH" >> "$TEMP_FILE"
diff --git a/src/components/screens/AddTokenScreen/AddTokenBottomSheetContent.tsx b/src/components/screens/AddTokenScreen/AddTokenBottomSheetContent.tsx
index abb4c269..8f3bdcfd 100644
--- a/src/components/screens/AddTokenScreen/AddTokenBottomSheetContent.tsx
+++ b/src/components/screens/AddTokenScreen/AddTokenBottomSheetContent.tsx
@@ -157,11 +157,23 @@ const AddTokenBottomSheetContent: React.FC = ({
const listItems = useMemo(() => {
if (!token) return [];
- // If issuer is already a contract ID, use it directly
- // Otherwise, create an Asset and get its contract ID
- const tokenContractId = isContractId(token.issuer)
- ? token.issuer
- : new Asset(token.tokenCode, token.issuer).contractId(networkPassphrase);
+ // For contract tokens (Soroban), the contract ID is typically stored in
+ // `issuer`. As a safety fallback, stellar.expert can return contract
+ // tokens where `asset` is the raw contract ID, so `tokenCode` may hold
+ // the contract ID instead. Select the contract-bearing field explicitly:
+ // a non-empty classic issuer must not override a contract ID found in
+ // `tokenCode`.
+ const getTokenContractId = () => {
+ if (isContractId(token.issuer)) return token.issuer;
+ if (isContractId(token.tokenCode)) return token.tokenCode;
+
+ // without the above guard, `new Asset()` would
+ // throw because contract IDs exceed the 12-char asset code limit
+ return new Asset(token.tokenCode, token.issuer).contractId(
+ networkPassphrase,
+ );
+ };
+ const tokenContractId = getTokenContractId();
const items = [
{
diff --git a/src/components/screens/SwapScreen/screens/SwapAmountScreen.tsx b/src/components/screens/SwapScreen/screens/SwapAmountScreen.tsx
index 2d824d57..0dc87de0 100644
--- a/src/components/screens/SwapScreen/screens/SwapAmountScreen.tsx
+++ b/src/components/screens/SwapScreen/screens/SwapAmountScreen.tsx
@@ -280,7 +280,7 @@ const SwapAmountScreen: React.FC = ({
duration: 0,
});
}
- }, [pathError, sourceAmount, destinationTokenId, activeError]);
+ }, [pathError, sourceAmount, destinationTokenId]);
const handleSettingsPress = useCallback(() => {
transactionSettingsBottomSheetModalRef.current?.present();
diff --git a/src/config/types.ts b/src/config/types.ts
index 4bf07dfa..65c384c6 100644
--- a/src/config/types.ts
+++ b/src/config/types.ts
@@ -271,11 +271,16 @@ export interface SearchTokenResponse {
};
score: number;
paging_token: number;
- tomlInfo: {
+ tomlInfo?: {
code: string;
- image: string;
+ image?: string;
issuer: string;
+ decimals?: number;
+ name?: string;
};
+ code?: string;
+ token_name?: string;
+ decimals?: number;
}[];
};
}
diff --git a/src/ducks/swap.ts b/src/ducks/swap.ts
index 6a4c79ee..c590fca8 100644
--- a/src/ducks/swap.ts
+++ b/src/ducks/swap.ts
@@ -9,6 +9,7 @@ import { logger } from "config/logger";
import { PricedBalance } from "config/types";
import { useDebugStore } from "ducks/debug";
import { formatBigNumberForDisplay } from "helpers/formatAmount";
+import { isContractId } from "helpers/soroban";
import { t } from "i18next";
import { getTokenForPayment } from "services/transactionService";
import { create } from "zustand";
@@ -184,6 +185,23 @@ export const useSwapStore = create((set) => ({
throw new Error(t("debug.debugMessages.swapPathFailure"));
}
+ // Custom/Soroban tokens cannot be swapped via Horizon's classic DEX
+ const isSourceCustom =
+ "contractId" in sourceBalance ||
+ ("id" in sourceBalance && isContractId(sourceBalance.id ?? ""));
+ const isDestCustom =
+ "contractId" in destinationBalance ||
+ ("id" in destinationBalance &&
+ isContractId(destinationBalance.id ?? ""));
+
+ if (isSourceCustom || isDestCustom) {
+ set({
+ isLoadingPath: false,
+ pathError: t("swapScreen.errors.customTokenSwapNotSupported"),
+ });
+ return;
+ }
+
// For now, we only support classic path payments
// TODO: Add Soroswap support for Testnet in future iteration
const pathResult = await findClassicSwapPath({
diff --git a/src/hooks/useTokenLookup.ts b/src/hooks/useTokenLookup.ts
index 1a6c6ef5..2ef97434 100644
--- a/src/hooks/useTokenLookup.ts
+++ b/src/hooks/useTokenLookup.ts
@@ -135,7 +135,21 @@ export const useTokenLookup = ({
return !!matchingBalance;
};
- // Format tokens from different sources while preserving original order
+ // Format tokens from different sources while preserving original order.
+ //
+ // Three paths depending on the result origin:
+ //
+ // 1. freighter-backend (contract ID search via handleContractLookup)
+ // → Already a FormattedSearchTokenRecord, just enrich with icon and trustline info.
+ //
+ // 2. stellar.expert — Soroban contract token
+ // → `asset` is a raw contract ID (e.g. "CC64WB...QYGSL").
+ // Token metadata comes from top-level fields (`code`, `decimals`, `token_name`)
+ // with `tomlInfo` as fallback.
+ //
+ // 3. stellar.expert — Classic asset
+ // → `asset` is "CODE-ISSUER-TYPE" (e.g. "USDC-GA5ZSE...R5E-1").
+ // Split by "-" to extract code and issuer.
const formatTokensFromSearchResults = (
rawSearchResults:
| SearchTokenResponse["_embedded"]["records"]
@@ -144,8 +158,8 @@ export const useTokenLookup = ({
icons: Record = {},
): FormattedSearchTokenRecord[] =>
rawSearchResults.map((result) => {
+ // Path 1: Already formatted by freighter-backend (contract ID search)
if ("tokenCode" in result) {
- // came from freighter-backend
const tokenIdentifier = getTokenIdentifier({
type: TokenTypeWithCustomToken.CUSTOM_TOKEN,
code: result.tokenCode,
@@ -165,7 +179,43 @@ export const useTokenLookup = ({
};
}
- // came from stellar.expert
+ // Path 2: stellar.expert — Soroban contract token
+ // Contract tokens have no "-" in the asset field (it's just the contract ID).
+ // Metadata (symbol, decimals, name) comes from top-level fields with
+ // tomlInfo as fallback.
+ const isContract = isContractId(result.asset);
+
+ if (isContract) {
+ const contractId = result.asset;
+ const tokenCode = result.code ?? result.tomlInfo?.code ?? contractId;
+ const tokenIdentifier = getTokenIdentifier({
+ type: TokenTypeWithCustomToken.CUSTOM_TOKEN,
+ code: tokenCode,
+ issuer: {
+ key: contractId,
+ },
+ });
+ const iconUrl =
+ icons[tokenIdentifier]?.imageUrl ?? result.tomlInfo?.image;
+
+ return {
+ tokenCode,
+ domain: result.domain ?? "",
+ hasTrustline: hasExistingTrustline(
+ userBalances,
+ tokenCode,
+ contractId,
+ ),
+ iconUrl,
+ issuer: contractId,
+ isNative: false,
+ tokenType: TokenTypeWithCustomToken.CUSTOM_TOKEN,
+ decimals: result.decimals ?? result.tomlInfo?.decimals,
+ name: result.token_name ?? result.tomlInfo?.name,
+ };
+ }
+
+ // Path 3: stellar.expert — Classic asset (asset format: "CODE-ISSUER-TYPE")
const [tokenCode, issuer] = result.asset.split("-");
const tokenIdentifier = getTokenIdentifier({
type: TokenTypeWithCustomToken.CUSTOM_TOKEN,
@@ -243,31 +293,30 @@ export const useTokenLookup = ({
icons = resJson?.reduce(
(prev, curr) => {
+ if (!curr.tomlInfo) return prev;
+
const tokenIdentifier = getTokenIdentifier({
type: TokenTypeWithCustomToken.CREDIT_ALPHANUM4,
- code: curr.tomlInfo?.code,
+ code: curr.tomlInfo.code,
issuer: {
- key: curr.tomlInfo?.issuer,
+ key: curr.tomlInfo.issuer,
},
});
// Apply USDC special case logic inline
- let imageUrl = curr.tomlInfo?.image;
+ let imageUrl = curr.tomlInfo.image;
if (
network === NETWORKS.PUBLIC &&
- curr.tomlInfo?.code === USDC_CODE &&
- curr.tomlInfo?.issuer === CIRCLE_USDC_ISSUER
+ curr.tomlInfo.code === USDC_CODE &&
+ curr.tomlInfo.issuer === CIRCLE_USDC_ISSUER
) {
imageUrl = logos.usdc as unknown as string;
}
- const icon = {
- imageUrl,
- network,
- };
+ if (!imageUrl) return prev;
// eslint-disable-next-line no-param-reassign
- prev[tokenIdentifier] = icon;
+ prev[tokenIdentifier] = { imageUrl, network };
return prev;
},
{} as Record,
diff --git a/src/i18n/locales/en/translations.json b/src/i18n/locales/en/translations.json
index 1cc6e8fa..8f050613 100644
--- a/src/i18n/locales/en/translations.json
+++ b/src/i18n/locales/en/translations.json
@@ -671,7 +671,8 @@
"insufficientBalance": "Insufficient balance. Maximum spendable: {{amount}} {{symbol}}",
"insufficientXlmForFees": "Insufficient XLM for transaction fees. You need at least {{fee}} XLM to pay the transaction fee.",
"noPathFound": "No path found between these tokens",
- "pathFindFailed": "Couldn't fetch swap path. Please try again."
+ "pathFindFailed": "Couldn't fetch swap path. Please try again.",
+ "customTokenSwapNotSupported": "Swapping custom tokens is not supported yet"
},
"proceedAnyway": "Proceed anyway"
},
diff --git a/src/i18n/locales/pt/translations.json b/src/i18n/locales/pt/translations.json
index 6ae79f3b..6ae372a8 100644
--- a/src/i18n/locales/pt/translations.json
+++ b/src/i18n/locales/pt/translations.json
@@ -635,7 +635,8 @@
"insufficientBalance": "Saldo insuficiente. Máximo disponível: {{amount}} {{symbol}}",
"insufficientXlmForFees": "XLM insuficiente para taxas de transação. Você precisa de pelo menos {{fee}} XLM para pagar a taxa de transação.",
"noPathFound": "Nenhum caminho encontrado entre estes tokens",
- "pathFindFailed": "Não foi possível buscar o caminho de troca. Tente novamente."
+ "pathFindFailed": "Não foi possível buscar o caminho de troca. Tente novamente.",
+ "customTokenSwapNotSupported": "Troca de tokens customizados ainda não é suportada"
},
"proceedAnyway": "Prosseguir mesmo assim"
},