();
const store = useStore();
- const { asset, amount, transactionFee, memo, isCollectible } = useSelector(
- transactionDataSelector,
- );
+ const { asset, amount, transactionFee, memo, memoType, isCollectible } =
+ useSelector(transactionDataSelector);
const { scanTx } = useScanTx();
const [state, dispatch] = useReducer(
@@ -442,6 +442,7 @@ function useSimulateTxData({
store.getState() as AppState,
);
const currentMemo = currentTransactionData.memo || memo;
+ const currentMemoType = currentTransactionData.memoType || memoType;
const currentAmount = currentTransactionData.amount || amount;
const currentAsset = currentTransactionData.asset || asset;
const currentTransactionFee = getCurrentTransactionFee({
@@ -613,6 +614,7 @@ function useSimulateTxData({
transactionTimeout,
networkDetails,
memoToUse,
+ currentMemoType,
);
const xdr = transaction.build().toXDR();
payload.transactionXdr = xdr;
diff --git a/extension/src/popup/components/send/SendAmount/index.tsx b/extension/src/popup/components/send/SendAmount/index.tsx
index 209dfcc2e2..a1b45399da 100644
--- a/extension/src/popup/components/send/SendAmount/index.tsx
+++ b/extension/src/popup/components/send/SendAmount/index.tsx
@@ -36,7 +36,7 @@ import {
saveAmount,
saveAsset,
saveIsToken,
- saveMemo,
+ saveMemoAndType,
saveTransactionFee,
saveManualTransactionFee,
saveTransactionTimeout,
@@ -941,6 +941,7 @@ export const SendAmount = ({
{
setIsEditingMemo(false);
// Reopen review sheet if user came from review flow
@@ -950,7 +951,7 @@ export const SendAmount = ({
setMemoEditingContext(null);
}}
onSubmit={async ({ memo }: { memo: string }) => {
- dispatch(saveMemo(memo));
+ dispatch(saveMemoAndType({ memo, memoType: "" }));
setIsEditingMemo(false);
// Regenerate transaction XDR with new memo (now reads memo from Redux state inside fetchData)
await fetchSimulationData();
diff --git a/extension/src/popup/components/send/SendTo/hooks/useSendToData.tsx b/extension/src/popup/components/send/SendTo/hooks/useSendToData.tsx
index 1ef6db7ccc..71fd1e12b0 100644
--- a/extension/src/popup/components/send/SendTo/hooks/useSendToData.tsx
+++ b/extension/src/popup/components/send/SendTo/hooks/useSendToData.tsx
@@ -1,9 +1,10 @@
import { useReducer } from "react";
-import { Federation } from "stellar-sdk";
+import { Federation, StrKey } from "stellar-sdk";
import { FormikErrors } from "formik";
import debounce from "lodash/debounce";
-import * as Sentry from "@sentry/browser";
+import { captureException } from "@sentry/browser";
import i18n from "popup/helpers/localizationConfig";
+import { FederationMemoType } from "popup/helpers/federationMemo";
import { initialState, isError, reducer } from "helpers/request";
import { AccountBalances, useGetBalances } from "helpers/hooks/useGetBalances";
@@ -25,6 +26,8 @@ interface ResolvedSendToData {
destinationBalances?: AccountBalances;
validatedAddress: string;
fedAddress: string;
+ federationMemo: string;
+ federationMemoType: FederationMemoType | "";
applicationState: APPLICATION_STATE;
publicKey: string;
networkDetails: NetworkDetails;
@@ -34,21 +37,39 @@ type SendToData = NeedsReRoute | ResolvedSendToData;
export const getAddressFromInput = async (userInput: string) => {
if (isFederationAddress(userInput)) {
+ let fedResp;
try {
- const fedResp = await Federation.Server.resolve(userInput);
- return {
- validatedAddress: fedResp.account_id,
- fedAddress: userInput,
- };
+ fedResp = await Federation.Server.resolve(userInput);
} catch (error) {
- Sentry.captureException(`Failed to fetch toml for ${userInput}`);
+ captureException(error);
throw new Error(i18n.t("Failed to resolve federated address"));
}
+
+ if (!StrKey.isValidEd25519PublicKey(fedResp.account_id)) {
+ throw new Error(i18n.t("Federation server returned an invalid address"));
+ }
+
+ const rawMemoType = fedResp.memo_type ?? "";
+ const memoType = (Object.values(FederationMemoType) as string[]).includes(
+ rawMemoType,
+ )
+ ? (rawMemoType as FederationMemoType)
+ : ("" as const);
+ const memo = fedResp.memo != null ? String(fedResp.memo) : "";
+
+ return {
+ validatedAddress: fedResp.account_id,
+ fedAddress: userInput,
+ federationMemo: memo,
+ federationMemoType: memoType,
+ };
}
return {
validatedAddress: userInput,
fedAddress: "",
+ federationMemo: "",
+ federationMemoType: "" as const,
};
};
@@ -72,8 +93,12 @@ function useSendToData() {
_isMainnet: boolean,
) => {
try {
- const { validatedAddress, fedAddress } =
- await getAddressFromInput(userInput);
+ const {
+ validatedAddress,
+ fedAddress,
+ federationMemo,
+ federationMemoType,
+ } = await getAddressFromInput(userInput);
const { recentAddresses } = await loadRecentAddresses({
activePublicKey: publicKey,
@@ -84,6 +109,8 @@ function useSendToData() {
recentAddresses,
validatedAddress,
fedAddress,
+ federationMemo,
+ federationMemoType,
applicationState,
publicKey,
networkDetails,
@@ -142,6 +169,8 @@ function useSendToData() {
recentAddresses: [],
validatedAddress: "",
fedAddress: "",
+ federationMemo: "",
+ federationMemoType: "",
applicationState,
publicKey,
networkDetails,
@@ -168,6 +197,8 @@ function useSendToData() {
recentAddresses,
validatedAddress: "",
fedAddress: "",
+ federationMemo: "",
+ federationMemoType: "",
applicationState,
publicKey,
networkDetails,
diff --git a/extension/src/popup/components/send/SendTo/index.tsx b/extension/src/popup/components/send/SendTo/index.tsx
index 00222fc578..194cd95244 100644
--- a/extension/src/popup/components/send/SendTo/index.tsx
+++ b/extension/src/popup/components/send/SendTo/index.tsx
@@ -32,8 +32,10 @@ import {
saveDestination,
saveDestinationAsset,
saveFederationAddress,
+ saveMemoAndType,
transactionDataSelector,
} from "popup/ducks/transactionSubmission";
+import type { FederationMemoType } from "popup/helpers/federationMemo";
import { RequestState } from "constants/request";
import { useSendToData, getAddressFromInput } from "./hooks/useSendToData";
@@ -105,10 +107,22 @@ export const SendTo = ({
const handleContinue = (
validatedDestination: string,
validatedFedAdress?: string,
+ federationMemo?: string,
+ federationMemoType?: FederationMemoType | "",
) => {
dispatch(saveDestination(validatedDestination));
dispatch(saveDestinationAsset(""));
dispatch(saveFederationAddress(validatedFedAdress || ""));
+ if (validatedFedAdress && federationMemo !== undefined) {
+ dispatch(
+ saveMemoAndType({
+ memo: federationMemo,
+ memoType: federationMemoType || "",
+ }),
+ );
+ } else {
+ dispatch(saveMemoAndType({ memo: "", memoType: "" }));
+ }
goToNext();
};
@@ -122,6 +136,8 @@ export const SendTo = ({
handleContinue(
sendDataState.data.validatedAddress,
sendDataState.data.fedAddress,
+ sendDataState.data.federationMemo,
+ sendDataState.data.federationMemoType,
);
}
},
@@ -246,6 +262,8 @@ export const SendTo = ({
handleContinue(
addressFromInput.validatedAddress,
addressFromInput.fedAddress,
+ addressFromInput.federationMemo,
+ addressFromInput.federationMemoType,
);
}}
className="SendTo__subheading-identicon"
diff --git a/extension/src/popup/components/swap/SwapAmount/__tests__/SwapAmount.errorState.test.tsx b/extension/src/popup/components/swap/SwapAmount/__tests__/SwapAmount.errorState.test.tsx
index a23d6ec0d5..c128e5bd8d 100644
--- a/extension/src/popup/components/swap/SwapAmount/__tests__/SwapAmount.errorState.test.tsx
+++ b/extension/src/popup/components/swap/SwapAmount/__tests__/SwapAmount.errorState.test.tsx
@@ -29,16 +29,14 @@ describe("SwapAmount RequestState.ERROR", () => {
});
it("renders the fetch-fail notification without throwing on RequestState.ERROR", () => {
- jest
- .spyOn(UseGetSwapAmountData, "useGetSwapAmountData")
- .mockReturnValue({
- state: {
- state: RequestState.ERROR,
- data: null,
- error: new Error("boom"),
- },
- fetchData: jest.fn().mockResolvedValue(undefined),
- } as any);
+ jest.spyOn(UseGetSwapAmountData, "useGetSwapAmountData").mockReturnValue({
+ state: {
+ state: RequestState.ERROR,
+ data: null,
+ error: new Error("boom"),
+ },
+ fetchData: jest.fn().mockResolvedValue(undefined),
+ } as any);
render(
@@ -53,8 +51,6 @@ describe("SwapAmount RequestState.ERROR", () => {
,
);
- expect(
- screen.getByTestId("swap-amount-fetch-fail"),
- ).toBeInTheDocument();
+ expect(screen.getByTestId("swap-amount-fetch-fail")).toBeInTheDocument();
});
});
diff --git a/extension/src/popup/ducks/transactionSubmission.ts b/extension/src/popup/ducks/transactionSubmission.ts
index 6d3254b930..78fcb98b01 100644
--- a/extension/src/popup/ducks/transactionSubmission.ts
+++ b/extension/src/popup/ducks/transactionSubmission.ts
@@ -37,6 +37,7 @@ import {
getSoroswapTokens as getSoroswapTokensService,
} from "popup/helpers/sorobanSwap";
import { hardwareSign, hardwareSignAuth } from "popup/helpers/hardwareConnect";
+import type { FederationMemoType } from "popup/helpers/federationMemo";
import { AppState } from "popup/App";
import { publicKeySelector } from "./accountServices";
@@ -425,6 +426,7 @@ interface TransactionData {
transactionFee: string;
transactionTimeout: number;
memo?: string;
+ memoType?: FederationMemoType | "";
destinationAsset: string;
destinationDecimals?: number;
destinationAmount: string;
@@ -495,6 +497,7 @@ export const initialState: InitialState = {
transactionFee: "",
transactionTimeout: 180,
memo: "",
+ memoType: "",
destinationAsset: "",
destinationAmount: "",
destinationIcon: "",
@@ -563,8 +566,17 @@ const transactionSubmissionSlice = createSlice({
saveTransactionTimeout: (state, action) => {
state.transactionData.transactionTimeout = action.payload;
},
- saveMemo: (state, action) => {
- state.transactionData.memo = action.payload;
+ saveMemoAndType: (
+ state,
+ action: {
+ payload: {
+ memo: string;
+ memoType?: FederationMemoType | "";
+ };
+ },
+ ) => {
+ state.transactionData.memo = action.payload.memo;
+ state.transactionData.memoType = action.payload.memoType ?? "";
},
saveDestinationAsset: (state, action) => {
state.transactionData.destinationAsset = action.payload;
@@ -756,7 +768,7 @@ export const {
saveTransactionFee,
saveManualTransactionFee,
saveTransactionTimeout,
- saveMemo,
+ saveMemoAndType,
saveDestinationAsset,
saveDestinationIcon,
saveIsSoroswap,
diff --git a/extension/src/popup/helpers/__tests__/federationMemo.test.ts b/extension/src/popup/helpers/__tests__/federationMemo.test.ts
new file mode 100644
index 0000000000..3821e98800
--- /dev/null
+++ b/extension/src/popup/helpers/__tests__/federationMemo.test.ts
@@ -0,0 +1,200 @@
+import { Memo } from "stellar-sdk";
+import {
+ validateFederationMemo,
+ buildMemoFromFederation,
+ FederationMemoType,
+} from "../federationMemo";
+
+jest.mock("@sentry/browser", () => ({ captureException: jest.fn() }));
+
+// --- validateFederationMemo ---
+
+describe("validateFederationMemo", () => {
+ describe("empty memo", () => {
+ it("accepts empty string for any memo type", () => {
+ expect(() =>
+ validateFederationMemo("", FederationMemoType.Text),
+ ).not.toThrow();
+ expect(() =>
+ validateFederationMemo("", FederationMemoType.Id),
+ ).not.toThrow();
+ expect(() =>
+ validateFederationMemo("", FederationMemoType.Hash),
+ ).not.toThrow();
+ expect(() => validateFederationMemo("", "unknown")).not.toThrow();
+ expect(() => validateFederationMemo("", "")).not.toThrow();
+ });
+ });
+
+ describe("text memo", () => {
+ it("accepts a short ASCII string", () => {
+ expect(() =>
+ validateFederationMemo("hello", FederationMemoType.Text),
+ ).not.toThrow();
+ });
+
+ it("accepts a string at exactly the 28-byte boundary", () => {
+ expect(() =>
+ validateFederationMemo("a".repeat(28), FederationMemoType.Text),
+ ).not.toThrow();
+ });
+
+ it("rejects a string exceeding 28 bytes", () => {
+ expect(() =>
+ validateFederationMemo("a".repeat(29), FederationMemoType.Text),
+ ).toThrow("exceeds 28 bytes");
+ });
+
+ it("measures byte length, not character length (multibyte chars)", () => {
+ // Each '€' is 3 bytes in UTF-8; 10 × 3 = 30 bytes > 28
+ expect(() =>
+ validateFederationMemo("€".repeat(10), FederationMemoType.Text),
+ ).toThrow("exceeds 28 bytes");
+ // 9 × '€' = 27 bytes — should pass
+ expect(() =>
+ validateFederationMemo("€".repeat(9), FederationMemoType.Text),
+ ).not.toThrow();
+ });
+ });
+
+ describe("id memo", () => {
+ it("accepts a valid non-negative integer string", () => {
+ expect(() =>
+ validateFederationMemo("0", FederationMemoType.Id),
+ ).not.toThrow();
+ expect(() =>
+ validateFederationMemo("12345", FederationMemoType.Id),
+ ).not.toThrow();
+ });
+
+ it("accepts the maximum uint64 value", () => {
+ expect(() =>
+ validateFederationMemo("18446744073709551615", FederationMemoType.Id),
+ ).not.toThrow();
+ });
+
+ it("rejects non-integer strings", () => {
+ expect(() =>
+ validateFederationMemo("not-a-number", FederationMemoType.Id),
+ ).toThrow("non-negative integer");
+ expect(() =>
+ validateFederationMemo("1.5", FederationMemoType.Id),
+ ).toThrow("non-negative integer");
+ expect(() => validateFederationMemo("-1", FederationMemoType.Id)).toThrow(
+ "non-negative integer",
+ );
+ });
+
+ it("rejects values exceeding uint64 max", () => {
+ expect(() =>
+ validateFederationMemo("18446744073709551616", FederationMemoType.Id),
+ ).toThrow("exceeds maximum uint64 value");
+ });
+ });
+
+ describe("hash memo", () => {
+ // 32 zero-bytes encoded as base64 (43 chars + 1 padding '=')
+ const VALID_B64 = "A".repeat(43) + "=";
+
+ it("accepts a valid base64-encoded 32-byte hash", () => {
+ expect(() =>
+ validateFederationMemo(VALID_B64, FederationMemoType.Hash),
+ ).not.toThrow();
+ });
+
+ it("rejects a string that decodes to fewer than 32 bytes", () => {
+ expect(() =>
+ validateFederationMemo("AAAA", FederationMemoType.Hash),
+ ).toThrow("base64-encoded 32-byte value");
+ });
+
+ it("rejects a string that decodes to more than 32 bytes", () => {
+ // 33 zero-bytes in base64 = 44 chars + padding
+ const tooLong = "A".repeat(44) + "==";
+ expect(() =>
+ validateFederationMemo(tooLong, FederationMemoType.Hash),
+ ).toThrow("base64-encoded 32-byte value");
+ });
+
+ it("rejects a non-base64 string of the wrong length", () => {
+ expect(() =>
+ validateFederationMemo(
+ "not-valid-base64-string!!!",
+ FederationMemoType.Hash,
+ ),
+ ).toThrow("base64-encoded 32-byte value");
+ });
+ });
+
+ describe("unknown memo type", () => {
+ it("does not throw for an unknown type (pass-through)", () => {
+ expect(() =>
+ validateFederationMemo("anything", "totally_unknown"),
+ ).not.toThrow();
+ });
+
+ it("does not throw for an empty type string", () => {
+ expect(() => validateFederationMemo("anything", "")).not.toThrow();
+ });
+ });
+});
+
+// --- buildMemoFromFederation ---
+
+describe("buildMemoFromFederation", () => {
+ describe("text memo", () => {
+ it("returns Memo.text for type 'text'", () => {
+ const memo = buildMemoFromFederation(
+ "payment-ref",
+ FederationMemoType.Text,
+ );
+ expect(memo).toEqual(Memo.text("payment-ref"));
+ });
+
+ it("defaults to Memo.text for an unknown type", () => {
+ const memo = buildMemoFromFederation("fallback", "weird_type");
+ expect(memo).toEqual(Memo.text("fallback"));
+ });
+
+ it("throws when the text value exceeds 28 bytes", () => {
+ expect(() =>
+ buildMemoFromFederation("a".repeat(29), FederationMemoType.Text),
+ ).toThrow("Failed to resolve federated address");
+ });
+ });
+
+ describe("id memo", () => {
+ it("returns Memo.id for type 'id'", () => {
+ const memo = buildMemoFromFederation("12345", FederationMemoType.Id);
+ expect(memo).toEqual(Memo.id("12345"));
+ });
+
+ it("throws for a non-integer id", () => {
+ expect(() =>
+ buildMemoFromFederation("abc", FederationMemoType.Id),
+ ).toThrow("Failed to resolve federated address");
+ });
+
+ it("throws for an id exceeding uint64 max", () => {
+ expect(() =>
+ buildMemoFromFederation("18446744073709551616", FederationMemoType.Id),
+ ).toThrow("Failed to resolve federated address");
+ });
+ });
+
+ describe("hash memo", () => {
+ // 32 zero-bytes encoded as base64 per SEP-0002
+ const VALID_B64 = "A".repeat(43) + "=";
+
+ it("returns Memo.hash for type 'hash'", () => {
+ const memo = buildMemoFromFederation(VALID_B64, FederationMemoType.Hash);
+ expect(memo).toEqual(Memo.hash(Buffer.from(VALID_B64, "base64")));
+ });
+
+ it("throws for an invalid hash value", () => {
+ expect(() =>
+ buildMemoFromFederation("not-valid", FederationMemoType.Hash),
+ ).toThrow("Failed to resolve federated address");
+ });
+ });
+});
diff --git a/extension/src/popup/helpers/federationMemo.ts b/extension/src/popup/helpers/federationMemo.ts
new file mode 100644
index 0000000000..d8a05e5a5b
--- /dev/null
+++ b/extension/src/popup/helpers/federationMemo.ts
@@ -0,0 +1,100 @@
+import { Memo } from "stellar-sdk";
+import * as Sentry from "@sentry/browser";
+import i18n from "popup/helpers/localizationConfig";
+
+/**
+ * Memo types defined by SEP-0002 federation protocol.
+ * https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0002.md
+ */
+export enum FederationMemoType {
+ Text = "text",
+ Id = "id",
+ Hash = "hash",
+}
+
+const FEDERATION_MEMO_TEXT_MAX_BYTES = 28;
+
+// Max value of a 64-bit unsigned integer
+const MAX_UINT64 = BigInt("18446744073709551615");
+
+/**
+ * Validates a federation memo value against the constraints for its SEP-0002
+ * memo type. Throws a descriptive `Error` if the value is invalid. These
+ * errors are intended for Sentry logging only — do not surface them to users.
+ *
+ * Spec: https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0002.md
+ */
+export const validateFederationMemo = (
+ memo: string,
+ memoType: FederationMemoType | string,
+): void => {
+ // An empty memo is always valid regardless of type
+ if (!memo) {
+ return;
+ }
+
+ switch (memoType) {
+ case FederationMemoType.Text: {
+ const byteLength = new TextEncoder().encode(memo).length;
+ if (byteLength > FEDERATION_MEMO_TEXT_MAX_BYTES) {
+ throw new Error(
+ `Federation memo text exceeds ${FEDERATION_MEMO_TEXT_MAX_BYTES} bytes`,
+ );
+ }
+ break;
+ }
+
+ case FederationMemoType.Id: {
+ if (!/^\d+$/.test(memo)) {
+ throw new Error("Federation memo id must be a non-negative integer");
+ }
+ if (BigInt(memo) > MAX_UINT64) {
+ throw new Error("Federation memo id exceeds maximum uint64 value");
+ }
+ break;
+ }
+
+ case FederationMemoType.Hash: {
+ // SEP-0002 specifies hash memos as base64-encoded 32-byte values
+ if (Buffer.from(memo, "base64").length !== 32) {
+ throw new Error(
+ "Federation memo hash must be a base64-encoded 32-byte value",
+ );
+ }
+ break;
+ }
+
+ default:
+ // Unknown memo type — pass through and let stellar-sdk validate
+ break;
+ }
+};
+
+/**
+ * Builds a Stellar `Memo` object from a federation server memo value and type,
+ * running SEP-0002 validation before construction.
+ *
+ * Validation errors are logged to Sentry; a generic user-facing error is thrown
+ * so implementation details are never surfaced to users.
+ */
+export const buildMemoFromFederation = (
+ memo: string,
+ memoType: FederationMemoType | string,
+): Memo => {
+ try {
+ validateFederationMemo(memo, memoType);
+
+ switch (memoType) {
+ case FederationMemoType.Id:
+ return Memo.id(memo);
+ case FederationMemoType.Hash:
+ return Memo.hash(Buffer.from(memo, "base64"));
+ default:
+ return Memo.text(memo);
+ }
+ } catch (err) {
+ // capture sentry specific message, but throw a generic error for users
+ Sentry.captureException(err);
+ throw new Error(i18n.t("Failed to resolve federated address"));
+ }
+};
diff --git a/extension/src/popup/helpers/useValidateMemo.ts b/extension/src/popup/helpers/useValidateMemo.ts
index 8ee7946de1..682e924d2e 100644
--- a/extension/src/popup/helpers/useValidateMemo.ts
+++ b/extension/src/popup/helpers/useValidateMemo.ts
@@ -1,33 +1,46 @@
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { Memo } from "stellar-sdk";
+import { FederationMemoType } from "popup/helpers/federationMemo";
const MAX_MEMO_BYTES = 28;
+const MAX_UINT64 = BigInt("18446744073709551615");
-/**
- * Calculates the byte length of a string
- * @param str The string to measure
- * @returns The length in bytes
- */
const getByteLength = (str: string): number =>
new TextEncoder().encode(str).length;
-/**
- * Hook to validate a transaction memo
- * Returns error message if invalid
- */
-export const useValidateMemo = (memo: string) => {
+export const useValidateMemo = (memo: string, memoType?: string) => {
const { t } = useTranslation();
const [error, setError] = useState(null);
useEffect(() => {
- // Memo is optional, so empty is valid
if (!memo) {
setError(null);
return;
}
- // Check byte length first (Stellar has a 28-byte limit for text memos)
+ if (memoType === FederationMemoType.Hash) {
+ setError(
+ Buffer.from(memo, "base64").length === 32
+ ? null
+ : t("Memo hash must be a base64-encoded 32-byte value"),
+ );
+ return;
+ }
+
+ if (memoType === FederationMemoType.Id) {
+ if (!/^\d+$/.test(memo)) {
+ setError(t("Memo ID must be a non-negative integer"));
+ return;
+ }
+ setError(
+ BigInt(memo) > MAX_UINT64
+ ? t("Memo ID exceeds maximum uint64 value")
+ : null,
+ );
+ return;
+ }
+
if (getByteLength(memo) > MAX_MEMO_BYTES) {
setError(
t("Memo is too long. Maximum {{max}} bytes allowed", {
@@ -38,14 +51,12 @@ export const useValidateMemo = (memo: string) => {
}
try {
- // Then try creating a Stellar memo to validate
Memo.text(memo);
-
setError(null);
} catch (err) {
setError(t("Invalid memo format"));
}
- }, [memo, t]);
+ }, [memo, memoType, t]);
return { error };
};
diff --git a/extension/src/popup/locales/en/translation.json b/extension/src/popup/locales/en/translation.json
index d1ac9715da..438b995cb7 100644
--- a/extension/src/popup/locales/en/translation.json
+++ b/extension/src/popup/locales/en/translation.json
@@ -210,12 +210,16 @@
"Failed to fetch your account data.": "Failed to fetch your account data.",
"Failed to fetch your transaction details": "Failed to fetch your transaction details",
"Failed to fetch your wallets.": "Failed to fetch your wallets.",
+ "Failed to load assets.": "Failed to load assets.",
+ "Failed to load send data.": "Failed to load send data.",
+ "Failed to load swap data.": "Failed to load swap data.",
"Failed to resolve federated address": "Failed to resolve federated address.",
"failed to sign transaction": "failed to sign transaction",
"failed to simulate token transfer": "failed to simulate token transfer",
"Failed to simulate transaction": "Failed to simulate transaction",
"failed to submit transaction": "failed to submit transaction",
"Failed!": "Failed!",
+ "Federation server returned an invalid address": "Federation server returned an invalid address",
"Fee": "Fee",
"Fee breakdown": "Fee breakdown",
"Fee is required": "Fee is required",
@@ -352,6 +356,9 @@
"Medium": "Medium",
"Medium Threshold": "Medium Threshold",
"Memo": "Memo",
+ "Memo hash must be a base64-encoded 32-byte value": "Memo hash must be a base64-encoded 32-byte value",
+ "Memo ID exceeds maximum uint64 value": "Memo ID exceeds maximum uint64 value",
+ "Memo ID must be a non-negative integer": "Memo ID must be a non-negative integer",
"Memo is required": "Memo is required",
"Memo is too long. Maximum {{max}} bytes allowed": "Memo is too long. Maximum {{max}} bytes allowed",
"Memo required": "Memo required",
@@ -737,13 +744,16 @@
"Your account balances could not be fetched at this time.": "Your account balances could not be fetched at this time.",
"Your account data could not be fetched at this time.": "Your account data could not be fetched at this time.",
"Your assets": "Your assets",
+ "Your assets could not be fetched at this time.": "Your assets could not be fetched at this time.",
"Your available XLM balance is not enough to pay for the transaction fee.": "Your available XLM balance is not enough to pay for the transaction fee.",
"Your gateway to the Stellar ecosystem. Browse and connect to decentralized applications built on Stellar.": "Your gateway to the Stellar ecosystem. Browse and connect to decentralized applications built on Stellar.",
"Your recovery phrase": "Your recovery phrase",
"Your Recovery Phrase": "Your Recovery Phrase",
"Your recovery phrase gives you access to your account and is the only way to access it in a new browser.": "Your recovery phrase gives you access to your account and is the only way to access it in a new browser.",
"Your recovery phrase gives you full access to your wallets and funds": "Your recovery phrase gives you full access to your wallets and funds",
+ "Your send data could not be fetched at this time.": "Your send data could not be fetched at this time.",
"Your Stellar secret key": "Your Stellar secret key",
+ "Your swap data could not be fetched at this time.": "Your swap data could not be fetched at this time.",
"Your Tokens": "Your Tokens",
"Your wallets could not be fetched at this time.": "Your wallets could not be fetched at this time."
}
diff --git a/extension/src/popup/locales/pt/translation.json b/extension/src/popup/locales/pt/translation.json
index 98965a9b1f..b37b186114 100644
--- a/extension/src/popup/locales/pt/translation.json
+++ b/extension/src/popup/locales/pt/translation.json
@@ -210,12 +210,16 @@
"Failed to fetch your account data.": "Falha ao buscar os dados da sua conta.",
"Failed to fetch your transaction details": "Falha ao buscar os detalhes da sua transação",
"Failed to fetch your wallets.": "Falha ao buscar suas carteiras.",
+ "Failed to load assets.": "Failed to load assets.",
+ "Failed to load send data.": "Failed to load send data.",
+ "Failed to load swap data.": "Failed to load swap data.",
"Failed to resolve federated address": "Falha ao resolver endereço federado.",
"failed to sign transaction": "falha ao assinar transação",
"failed to simulate token transfer": "falha ao simular transferência de token",
"Failed to simulate transaction": "Falha ao simular transação",
"failed to submit transaction": "falha ao enviar transação",
"Failed!": "Falhou!",
+ "Federation server returned an invalid address": "O servidor de federação retornou um endereço inválido",
"Fee": "Taxa",
"Fee breakdown": "Detalhes da taxa",
"Fee is required": "A taxa é obrigatória",
@@ -352,6 +356,9 @@
"Medium": "Média",
"Medium Threshold": "Limite Médio",
"Memo": "Memo",
+ "Memo hash must be a base64-encoded 32-byte value": "O hash do memo deve ser um valor de 32 bytes codificado em base64",
+ "Memo ID exceeds maximum uint64 value": "O ID do memo excede o valor máximo uint64",
+ "Memo ID must be a non-negative integer": "O ID do memo deve ser um número inteiro não negativo",
"Memo is disabled for this transaction": "Memo está desabilitado para esta transação",
"Memo is not supported for this operation": "Memo não é suportado para esta operação",
"Memo is required": "Memo é obrigatório",
@@ -737,13 +744,16 @@
"Your account balances could not be fetched at this time.": "Os saldos da sua conta não puderam ser buscados neste momento.",
"Your account data could not be fetched at this time.": "Os dados da sua conta não puderam ser buscados neste momento.",
"Your assets": "Seus ativos",
+ "Your assets could not be fetched at this time.": "Your assets could not be fetched at this time.",
"Your available XLM balance is not enough to pay for the transaction fee.": "Seu saldo XLM disponível não é suficiente para pagar a taxa de transação.",
"Your gateway to the Stellar ecosystem. Browse and connect to decentralized applications built on Stellar.": "Sua porta de entrada para o ecossistema Stellar. Navegue e conecte-se a aplicações descentralizadas construídas na Stellar.",
"Your recovery phrase": "Sua frase de recuperação",
"Your Recovery Phrase": "Sua Frase de Recuperação",
"Your recovery phrase gives you access to your account and is the only way to access it in a new browser.": "Sua frase de recuperação lhe dá acesso à sua conta e é a única maneira de acessá-la em um novo navegador.",
"Your recovery phrase gives you full access to your wallets and funds": "Sua frase de recuperação lhe dá acesso total às suas carteiras e fundos",
+ "Your send data could not be fetched at this time.": "Your send data could not be fetched at this time.",
"Your Stellar secret key": "Sua Stellar secret key",
+ "Your swap data could not be fetched at this time.": "Your swap data could not be fetched at this time.",
"Your Tokens": "Seus Tokens",
"Your wallets could not be fetched at this time.": "Suas carteiras não puderam ser buscadas neste momento."
}