Skip to content
Merged
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
5 changes: 4 additions & 1 deletion @shared/api/helpers/soroban.ts
Original file line number Diff line number Diff line change
Expand Up @@ -585,7 +585,10 @@ export const getIsTokenSpec = async (contractId: string, serverUrl: string) => {
return isTokenSpec(spec);
};

export const isContractId = (contractId: string) => {
export const isContractId = (contractId?: string) => {
if (!contractId) {
return false;
}
try {
StrKey.decodeContract(contractId);
return true;
Expand Down
4 changes: 2 additions & 2 deletions @shared/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
"@stellar/js-xdr": "4.0.0",
"bignumber.js": "9.3.0",
"prettier": "3.5.3",
"stellar-sdk": "npm:@stellar/stellar-sdk@15.0.1",
"stellar-sdk-next": "npm:@stellar/stellar-sdk@15.0.1",
"stellar-sdk": "npm:@stellar/stellar-sdk@16.0.0-rc.1",
"stellar-sdk-next": "npm:@stellar/stellar-sdk@16.0.0-rc.1",
"typescript": "5.8.3",
"webextension-polyfill": "0.12.0"
},
Expand Down
4 changes: 2 additions & 2 deletions @shared/constants/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
"prettier": "../../.prettierrc.yaml",
"version": "1.0.0",
"dependencies": {
"stellar-sdk": "npm:@stellar/stellar-sdk@15.0.1",
"stellar-sdk-next": "npm:@stellar/stellar-sdk@15.0.1",
"stellar-sdk": "npm:@stellar/stellar-sdk@16.0.0-rc.1",
"stellar-sdk-next": "npm:@stellar/stellar-sdk@16.0.0-rc.1",
"typescript": "5.8.3"
},
"devDependencies": {
Expand Down
4 changes: 2 additions & 2 deletions @shared/helpers/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
"version": "1.0.0",
"dependencies": {
"bignumber.js": "9.3.0",
"stellar-sdk": "npm:@stellar/stellar-sdk@15.0.1",
"stellar-sdk-next": "npm:@stellar/stellar-sdk@15.0.1",
"stellar-sdk": "npm:@stellar/stellar-sdk@16.0.0-rc.1",
"stellar-sdk-next": "npm:@stellar/stellar-sdk@16.0.0-rc.1",
"typescript": "5.8.3"
}
}
5 changes: 1 addition & 4 deletions @shared/helpers/soroban/server.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import {
Transaction,
Memo,
MemoType,
Operation,
rpc as SorobanRpc,
scValToNative,
BASE_FEE,
Expand All @@ -11,7 +8,7 @@ import { NetworkDetails } from "@shared/constants/stellar";
import { getSdk } from "@shared/helpers/stellar";

export const simulateTx = async <ArgType>(
tx: Transaction<Memo<MemoType>, Operation[]>,
tx: Transaction,
server: SorobanRpc.Server,
): Promise<ArgType> => {
const simulatedTX = await server.simulateTransaction(tx);
Expand Down
7 changes: 7 additions & 0 deletions config/jest/setupTests.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ global.ResizeObserver = class ResizeObserver {
disconnect() {}
};

// stellar-sdk v16 hashes/signs via @noble/hashes v2, which strictly rejects
// anything that is not `instanceof Uint8Array`. `jsdom-global` swaps the global
// Uint8Array for jsdom's, so Node Buffers (which extend Node's Uint8Array) fail
// that check. Restore the Node Uint8Array (the constructor Buffer extends) so
// hashing accepts Buffers in tests.
global.Uint8Array = Object.getPrototypeOf(Buffer.prototype).constructor;

// make a JSDOM thing so we can fuck with mount
const jsdom = new JSDOM("<!doctype html><html><body></body></html>");
const { window } = jsdom;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ const SIGNED_AUTH_ENTRY =
const NON_SOROBAN_AUTH_ENTRY =
"AAAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAA==";

// A CAP-71 (protocol 27) ENVELOPE_TYPE_SOROBAN_AUTHORIZATION_WITH_ADDRESS
// preimage on TestNet, bound to account GAAQCAIB… (ed25519 0x01*32) — never the
// e2e test account. Used to exercise the bound-address mismatch block.
const V2_AUTH_ENTRY_WRONG_ADDRESS =
"AAAACs7gMC1ZhE0yvcqRXIID3USzP7t+3BkFHqN6vt8o7NRyAAAAAAAAACoAD0JAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAAAAAABAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMAAAAIdHJhbnNmZXIAAAAAAAAAAA==";

const MSG_TO_SIGN = "Hello, World!";
const SIGNED_MSG =
"dxdeMTXPabzkvpVyTFFvPyiQ1soAJVf55NLkzgQ1a5HihB0wGi78P6p4Qac3YJa9pOVD9YeKGeUPZVNCM/f8Cg==";
Expand Down Expand Up @@ -954,6 +960,45 @@ test("should show network mismatch warning when signing auth entry for wrong net
).toBeVisible();
});

test("should block signing an auth entry bound to a different account", async ({
page,
extensionId,
context,
}) => {
await loginToTestAccount({ page, extensionId, context, isIntegrationMode });
// Stay on TestNet so the embedded networkId matches (passes the network
// check) and we reach the bound-address mismatch check. The preimage is
// bound to GAAQCAIB… which is not the test account.
await allowDapp({ page });

const pageTwo = await page.context().newPage();
await pageTwo.waitForLoadState();

const popupPromise = page.context().waitForEvent("page");
await pageTwo.goto(
"https://play.freighter.app/#/extension/playground/signAuthEntry",
);
await pageTwo.getByRole("textbox").first().fill(V2_AUTH_ENTRY_WRONG_ADDRESS);
await pageTwo
.getByRole("textbox")
.nth(1)
.fill("Test SDF Network ; September 2015");
await pageTwo
.getByText("Sign Authorization Entry XDR")
.click({ force: true });

const popup = await popupPromise;

await expect(
popup.getByText("Freighter is set to a different account"),
).toBeVisible();
await expect(
popup.getByText(
"Signing this authorization is not possible at the moment.",
),
).toBeVisible();
});

test("should show invalid entry warning when auth entry XDR cannot be parsed", async ({
page,
extensionId,
Expand Down
4 changes: 2 additions & 2 deletions extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@
"sonner": "2.0.7",
"soroswap-router-sdk": "1.4.6",
"stellar-hd-wallet": "1.0.2",
"stellar-sdk": "npm:@stellar/stellar-sdk@15.0.1",
"stellar-sdk-next": "npm:@stellar/stellar-sdk@15.0.1",
"stellar-sdk": "npm:@stellar/stellar-sdk@16.0.0-rc.1",
"stellar-sdk-next": "npm:@stellar/stellar-sdk@16.0.0-rc.1",
"svg-url-loader": "8.0.0",
"tailwindcss": "4.1.18",
"tsconfig-paths-webpack-plugin": "4.2.0",
Expand Down
2 changes: 1 addition & 1 deletion extension/src/helpers/stellar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export const getTransactionInfo = (search: string) => {
};

export function isAsset(
value: Asset | { code: string; issuer: string },
value: Asset | { code: string; issuer?: string },
): value is Asset {
return (value as Asset).getIssuer !== undefined;
}
Expand Down
2 changes: 1 addition & 1 deletion extension/src/popup/components/AssetTile/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ interface AssetTileProps {
asset: {
code: string;
canonical: string;
issuer: string;
issuer?: string;
} | null;
assetIcon: string | null;
balance?: string;
Expand Down
38 changes: 32 additions & 6 deletions extension/src/popup/components/AuthEntry/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,29 @@ import { truncateString } from "helpers/stellar";

import "./styles.scss";

export interface AuthEntryDisplay {
invocation: xdr.SorobanAuthorizedInvocation;
/**
* The address whose authorization the entry's credentials represent.
* Present for address credentials (incl. CAP-71 ADDRESS_V2 /
* ADDRESS_WITH_DELEGATES); absent for source-account credentials.
*/
boundAddress?: string;
}

interface AuthEntriesProps {
invocations: xdr.SorobanAuthorizedInvocation[];
entries: AuthEntryDisplay[];
}

export const AuthEntries = ({ invocations }: AuthEntriesProps) => {
export const AuthEntries = ({ entries }: AuthEntriesProps) => {
const { t } = useTranslation();
const [expandedIndex, setExpandedIndex] = useState<number | null>(null);

const handleExpandDetail = (index: number) => {
setExpandedIndex((prev) => (prev === index ? null : index));
};

const renderAuthEntry = (invocation: xdr.SorobanAuthorizedInvocation) => {
const renderAuthEntry = ({ invocation, boundAddress }: AuthEntryDisplay) => {
const details = getInvocationDetails(invocation);

const renderDetailTitle = (detail: InvocationArgs) => {
Expand Down Expand Up @@ -146,6 +156,22 @@ export const AuthEntries = ({ invocations }: AuthEntriesProps) => {

return (
<>
{boundAddress && (
<div
className="AuthEntry__InfoBlock"
data-testid="AuthEntry__BoundAddress"
>
<KeyValueList
operationKey={t("Authorized address")}
operationValue={
<CopyValue
value={boundAddress}
displayValue={truncateString(boundAddress)}
/>
}
/>
</div>
)}
{details.map((detail, ind) => (
<div
className="AuthEntryContainer"
Expand Down Expand Up @@ -185,9 +211,9 @@ export const AuthEntries = ({ invocations }: AuthEntriesProps) => {
<Icon.Key01 />
<span>{t("Authorizations")}</span>
</div>
{invocations.map((invocation) => (
<React.Fragment key={invocation.toXDR("raw").toString()}>
{renderAuthEntry(invocation)}
{entries.map((entry) => (
<React.Fragment key={entry.invocation.toXDR("raw").toString()}>
{renderAuthEntry(entry)}
</React.Fragment>
))}
</div>
Expand Down
6 changes: 3 additions & 3 deletions extension/src/popup/components/__tests__/AuthEntry.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ describe("AuthEntry", () => {
},
}}
>
<AuthEntries invocations={[authorizedInvocation]} />
<AuthEntries entries={[{ invocation: authorizedInvocation }]} />
</Wrapper>,
);
await waitFor(() => screen.getAllByTestId("AuthEntryContainer"));
Expand Down Expand Up @@ -130,7 +130,7 @@ describe("AuthEntry", () => {
},
}}
>
<AuthEntries invocations={[authorizedInvocation]} />
<AuthEntries entries={[{ invocation: authorizedInvocation }]} />
</Wrapper>,
);
await waitFor(() => screen.getAllByTestId("AuthEntryContainer"));
Expand Down Expand Up @@ -195,7 +195,7 @@ describe("AuthEntry", () => {
},
}}
>
<AuthEntries invocations={[authorizedInvocation]} />
<AuthEntries entries={[{ invocation: authorizedInvocation }]} />
</Wrapper>,
);
await waitFor(() => screen.getAllByTestId("AuthEntryContainer"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export const SorobanTokenIcon = ({ noMargin }: { noMargin?: boolean }) => (
interface AssetIconProps {
assetIcons: AssetIcons;
code: string;
issuerKey: string;
issuerKey?: string;
retryAssetIconFetch?: (arg: { key: string; code: string }) => void;
isLPShare?: boolean;
isSorobanToken?: boolean;
Expand Down Expand Up @@ -157,7 +157,7 @@ export const AssetIcon = memo(
src={isXlm ? StellarLogo : imgSrc}
onError={() => {
if (retryAssetIconFetch) {
retryAssetIconFetch({ key: issuerKey, code });
retryAssetIconFetch({ key: issuerKey ?? "", code });
}
// we tried to load an image path but it failed, so show the broken image icon here
setHasError(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,9 @@ export const ChangeTrustInternal = ({
memo={{ value: memo, type: "text" }}
xdr={xdrDefined}
operationNames={operations.map(
(op) => OPERATION_TYPES[op.type] || op.type,
(op) =>
OPERATION_TYPES[op.type as keyof typeof OPERATION_TYPES] ||
op.type,
)}
/>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { useDispatch, useSelector, useStore } from "react-redux";
import BigNumber from "bignumber.js";
import { captureException } from "@sentry/browser";
import {
Account,
Asset,
BASE_FEE,
extractBaseAddress,
Expand Down Expand Up @@ -258,7 +257,7 @@ const getBuiltTx = async (
networkDetails.networkUrl,
networkDetails.networkPassphrase,
);
const sourceAccount: Account = await server.loadAccount(publicKey);
const sourceAccount = await server.loadAccount(publicKey);
try {
const operation = getOperation(
sourceAsset,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
nativeToScVal,
Operation,
Signer,
SignerKeyOptions,
StrKey,
xdr,
} from "stellar-sdk";
Expand Down Expand Up @@ -230,8 +229,8 @@ export const KeyValueLine = ({
operationKey={t("Asset Issuer")}
operationValue={
<CopyValue
value={line.issuer}
displayValue={truncateString(line.issuer)}
value={line.issuer ?? ""}
displayValue={truncateString(line.issuer ?? "")}
/>
}
/>{" "}
Expand Down Expand Up @@ -348,11 +347,7 @@ export const KeyValueClaimants = ({ claimants }: { claimants: Claimant[] }) => {
);
};

export const KeyValueSignerKeyOptions = ({
signer,
}: {
signer: SignerKeyOptions;
}) => {
export const KeyValueSignerKeyOptions = ({ signer }: { signer: Signer }) => {
const { t } = useTranslation();

if ("ed25519PublicKey" in signer) {
Expand All @@ -368,7 +363,7 @@ export const KeyValueSignerKeyOptions = ({
return (
<KeyValueList
operationKey={t("Signer Sha256 Hash")}
operationValue={signer.sha256Hash}
operationValue={formattedBuffer(signer.sha256Hash)}
/>
);
}
Expand All @@ -377,7 +372,7 @@ export const KeyValueSignerKeyOptions = ({
return (
<KeyValueList
operationKey={t("Pre Auth Transaction")}
operationValue={signer.preAuthTx}
operationValue={formattedBuffer(signer.preAuthTx)}
/>
);
}
Expand Down
Loading
Loading