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
8 changes: 7 additions & 1 deletion @shared/api/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import BigNumber from "bignumber.js";
import { Horizon } from "stellar-sdk";
import { Types } from "@stellar/wallet-sdk";

Expand Down Expand Up @@ -112,12 +113,17 @@ export type Balances = Types.BalanceMap | null;

export interface SorobanBalance {
contractId: string;
total: unknown; // BigNumber
total: BigNumber;
name: string;
symbol: string;
decimals: string;
}

export type AssetType =
| Types.AssetBalance
| Types.NativeBalance
| SorobanBalance;

export type TokenBalances = SorobanBalance[];

/* eslint-disable camelcase */
Expand Down
19 changes: 12 additions & 7 deletions extension/src/helpers/stellar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,18 @@ export const getAssetFromCanonical = (canonical: string) => {
return StellarSdk.Asset.native();
}
if (canonical.includes(":")) {
return new StellarSdk.Asset(
canonical.split(":")[0],
canonical.split(":")[1],
);
const [code, issuer] = canonical.split(":");

// Soroban issuer is a contractId, longer than classic issuer
if (issuer.length > 12) {
return {
code,
issuer,
};
}
return new StellarSdk.Asset(code, issuer);
}

throw new Error(`invalid asset canonical id: ${canonical}`);
};

Expand All @@ -75,9 +82,7 @@ export const getCanonicalFromAsset = (
if (assetCode === "XLM" && !assetIssuer) {
return "native";
}
if (!assetIssuer) {
return assetCode;
}

return `${assetCode}:${assetIssuer}`;
};

Expand Down
15 changes: 7 additions & 8 deletions extension/src/popup/components/account/AccountAssets/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -189,16 +189,19 @@ export const AccountAssets = ({
code = getLPShareCode(rb.reserves);
amountUnit = "shares";
} else if (rb.contractId) {
issuer = rb.contractId;
issuer = {
key: rb.contractId,
};
code = rb.symbol;
amountUnit = rb.symbol;
} else {
issuer = rb.token.issuer;
code = rb.token.code;
amountUnit = rb.token.code;
}

const isLP = issuer === "lp";
const isSorobanToken = rb.contractId;
const isSorobanToken = !!rb.contractId;
const canonicalAsset = getCanonicalFromAsset(code, issuer?.key);

const assetDomain = assetDomains[canonicalAsset];
Expand All @@ -207,16 +210,12 @@ export const AccountAssets = ({
return (
<div
className={`AccountAssets__asset ${
setSelectedAsset && !isLP && !isSorobanToken
setSelectedAsset && !isLP
? "AccountAssets__asset--has-detail"
: ""
}`}
key={canonicalAsset}
onClick={
isLP || isSorobanToken
? () => null
: () => handleClick(canonicalAsset)
}
onClick={isLP ? () => null : () => handleClick(canonicalAsset)}
>
<div className="AccountAssets__copy-left">
<AssetIcon
Expand Down
107 changes: 62 additions & 45 deletions extension/src/popup/components/account/AssetDetail/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ import { IconButton, Icon } from "@stellar/design-system";
import SimpleBar from "simplebar-react";
import "simplebar-react/dist/simplebar.min.css";

import { AccountBalancesInterface, HorizonOperation } from "@shared/api/types";
import { HorizonOperation, AssetType } from "@shared/api/types";
import { NetworkDetails } from "@shared/constants/stellar";
import {
getAvailableBalance,
getIsPayment,
getIsSwap,
getStellarExpertUrl,
getRawBalance,
getIssuerFromBalance,
} from "popup/helpers/account";
import { useAssetDomain } from "popup/helpers/useAssetDomain";
import { navigateTo } from "popup/helpers/navigate";
Expand Down Expand Up @@ -47,10 +49,11 @@ import "./styles.scss";

interface AssetDetailProps {
assetOperations: Array<HorizonOperation>;
accountBalances: AccountBalancesInterface;
accountBalances: Array<AssetType>;
networkDetails: NetworkDetails;
publicKey: string;
selectedAsset: string;
subentryCount: number;
setSelectedAsset: (selectedAsset: string) => void;
}

Expand All @@ -61,33 +64,29 @@ export const AssetDetail = ({
publicKey,
selectedAsset,
setSelectedAsset,
subentryCount,
}: AssetDetailProps) => {
const dispatch: AppDispatch = useDispatch();
const isNative = selectedAsset === "native";
const assetCode = getAssetFromCanonical(selectedAsset).code;

const canonical = getAssetFromCanonical(selectedAsset);
const isSorobanAsset = !!canonical.issuer && canonical.issuer.length > 12;
const isOwnedScamAsset = useIsOwnedScamAsset(
assetCode,
getAssetFromCanonical(selectedAsset).issuer,
canonical.code,
canonical.issuer,
);

const balanceKey = Object.keys(accountBalances?.balances || {}).find((k) =>
k.includes(selectedAsset),
);
const balance =
accountBalances?.balances && balanceKey
? accountBalances.balances[balanceKey]
: null;
const assetIssuer =
balance && "issuer" in balance?.token
? balance.token.issuer.key.toString()
: "";
const balanceTotal = balance?.total
? `${new BigNumber(balance?.total).toString()} ${assetCode}`
: `0 ${assetCode}`;
const balance = getRawBalance(accountBalances, selectedAsset) || null;
const assetIssuer = balance ? getIssuerFromBalance(balance) : "";
const balanceTotal =
balance && balance?.total
? `${new BigNumber(balance?.total).toString()} ${canonical.code}`
: `0 ${canonical.code}`;

const balanceAvailable = getAvailableBalance({
accountBalances,
selectedAsset,
subentryCount,
});

const stellarExpertUrl = getStellarExpertUrl(networkDetails);
Expand All @@ -109,11 +108,11 @@ export const AssetDetail = ({
assetIssuer,
});

if (!assetOperations) {
if (!assetOperations && !isSorobanAsset) {
return null;
}

if (assetIssuer && !assetDomain) {
if (assetIssuer && !assetDomain && !isSorobanAsset) {
// if we have an asset issuer, wait until we have the asset domain before continuing
return null;
}
Expand All @@ -124,13 +123,16 @@ export const AssetDetail = ({
<div className="AssetDetail">
<div className="AssetDetail__wrapper">
<SubviewHeader
title={assetCode}
title={canonical.code}
customBackAction={() => setSelectedAsset("")}
/>
{balance && "name" in balance && (
<span className="AssetDetail__token-name">{balance.name}</span>
)}
{isNative ? (
<div className="AssetDetail__available">
<span className="AssetDetail__available__copy">
{balanceAvailable} {assetCode} {t("available")}
{balanceAvailable} {canonical.code} {t("available")}
</span>
<span
className="AssetDetail__available__icon"
Expand All @@ -144,32 +146,44 @@ export const AssetDetail = ({
<div className="AssetDetail__total__copy">{balanceTotal}</div>
<div className="AssetDetail__total__network">
<AssetNetworkInfo
assetCode={assetCode}
assetCode={canonical.code}
assetIssuer={assetIssuer}
assetType={balance?.token.type || ""}
assetType={
(balance && "token" in balance && balance?.token.type) || ""
}
assetDomain={assetDomain}
contractId={
balance && "contractId" in balance
? balance.contractId
: undefined
}
/>
</div>
</div>
<div className="AssetDetail__actions">
{balance?.total && new BigNumber(balance?.total).toNumber() > 0 ? (
<>
<PillButton
onClick={() => {
dispatch(saveAsset(selectedAsset));
navigateTo(ROUTES.sendPayment);
}}
>
{t("SEND")}
</PillButton>
<PillButton
onClick={() => {
dispatch(saveAsset(selectedAsset));
navigateTo(ROUTES.swap);
}}
>
{t("SWAP")}
</PillButton>
{/* Hide send until send work is ready for Soroban tokens */}
{!isSorobanAsset && (
<PillButton
onClick={() => {
dispatch(saveAsset(selectedAsset));
navigateTo(ROUTES.sendPayment);
}}
>
{t("SEND")}
</PillButton>
)}
{!isSorobanAsset && (
<PillButton
onClick={() => {
dispatch(saveAsset(selectedAsset));
navigateTo(ROUTES.swap);
}}
>
{t("SWAP")}
</PillButton>
)}
</>
) : (
<PillButton
Expand Down Expand Up @@ -236,7 +250,7 @@ export const AssetDetail = ({
<div className="AssetDetail__info-modal__total-box">
<div className="AssetDetail__info-modal__asset-code">
<img src={StellarLogo} alt="Network icon" />{" "}
<div>{assetCode}</div>
<div>{canonical.code}</div>
</div>
<div>{balanceTotal}</div>
</div>
Expand All @@ -247,19 +261,22 @@ export const AssetDetail = ({
</div>
<div className="AssetDetail__info-modal__balance-row">
<div>{t("Reserved Balance*")}</div>
{balance?.available && balance?.total ? (
{balance &&
"available" in balance &&
balance?.available &&
balance?.total ? (
<div>
{new BigNumber(balanceAvailable)
.minus(new BigNumber(balance?.total))
.toString()}{" "}
{assetCode}
{canonical.code}
</div>
) : null}
</div>
<div className="AssetDetail__info-modal__total-available-row">
<div>{t("Total Available")}</div>
<div>
{balanceAvailable} {assetCode}
{balanceAvailable} {canonical.code}
</div>
</div>
</div>
Expand Down
11 changes: 11 additions & 0 deletions extension/src/popup/components/account/AssetDetail/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,17 @@
}
}

&__token-name {
align-items: center;
color: rgba(255, 255, 255, 0.6);
display: flex;
justify-content: center;
font-size: 0.875rem;
gap: 0.375rem;
line-height: 1.375rem;
text-align: center;
}

&__scam-warning {
margin-bottom: 2rem;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { settingsNetworkDetailsSelector } from "popup/ducks/settings";
import { transactionSubmissionSelector } from "popup/ducks/transactionSubmission";
import { ScamAssetIcon } from "popup/components/account/ScamAssetIcon";
import StellarLogo from "popup/assets/stellar-logo.png";
import { displaySorobanId } from "popup/helpers/account";

import "./styles.scss";

Expand All @@ -15,13 +16,15 @@ interface AssetNetworkInfoProps {
assetCode: string;
assetType: string;
assetDomain: string;
contractId?: string;
}

export const AssetNetworkInfo = ({
assetIssuer,
assetCode,
assetType,
assetDomain,
contractId,
}: AssetNetworkInfoProps) => {
const networkDetails = useSelector(settingsNetworkDetailsSelector);
const { blockedDomains } = useSelector(transactionSubmissionSelector);
Expand Down Expand Up @@ -64,7 +67,11 @@ export const AssetNetworkInfo = ({
<div className="AssetNetworkInfo__network">
<>
{decideNetworkIcon()}
<span>{assetDomain || "Stellar Lumens"}</span>
{contractId ? (
<span>{displaySorobanId(contractId, 32)}</span>
) : (
<span>{assetDomain || "Stellar Lumens"}</span>
)}
</>
</div>
);
Expand Down
3 changes: 1 addition & 2 deletions extension/src/popup/ducks/soroban.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,14 @@ export const getTokenBalances = createAsyncThunk<
},
params,
);
const total = new BigNumber(balance);
const total = new BigNumber(balance) as any; // ?? why can't the BigNumber type work here

results.push({
contractId: tokenId,
total,
...rest,
});
} catch (e) {
console.error(e);
console.error(`Token "${tokenId}" missing data on RPC server`);
}
}
Expand Down
Loading