Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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