Skip to content

Commit e05ac7a

Browse files
committed
update
1 parent 68ad62f commit e05ac7a

File tree

12 files changed

+394
-82
lines changed

12 files changed

+394
-82
lines changed

.changeset/fair-plants-pretend.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"thirdweb": patch
3+
---
4+
5+
Export util functions: formatNumber and shortenLargeNumber

packages/thirdweb/src/exports/react.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,7 @@ export {
221221
export {
222222
AccountBalance,
223223
type AccountBalanceProps,
224+
type AccountBalanceInfo,
224225
} from "../react/web/ui/prebuilt/Account/balance.js";
225226
export {
226227
AccountName,

packages/thirdweb/src/exports/utils.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,3 +204,6 @@ export type {
204204
AbiConstructor,
205205
AbiFallback,
206206
} from "abitype";
207+
208+
export { shortenLargeNumber } from "../utils/shortenLargeNumber.js";
209+
export { formatNumber } from "../utils/formatNumber.js";

packages/thirdweb/src/pay/convert/cryptoToFiat.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { getContract } from "../../contract/contract.js";
77
import { isAddress } from "../../utils/address.js";
88
import { getClientFetch } from "../../utils/fetch.js";
99
import { getPayConvertCryptoToFiatEndpoint } from "../utils/definitions.js";
10+
import type { SupportedFiatCurrency } from "./type.js";
1011

1112
/**
1213
* Props for the `convertCryptoToFiat` function
@@ -31,7 +32,7 @@ export type ConvertCryptoToFiatParams = {
3132
* The fiat symbol. e.g "USD"
3233
* Only USD is supported at the moment.
3334
*/
34-
to: "USD";
35+
to: SupportedFiatCurrency;
3536
};
3637

3738
/**

packages/thirdweb/src/pay/convert/fiatToCrypto.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { getContract } from "../../contract/contract.js";
77
import { isAddress } from "../../utils/address.js";
88
import { getClientFetch } from "../../utils/fetch.js";
99
import { getPayConvertFiatToCryptoEndpoint } from "../utils/definitions.js";
10+
import type { SupportedFiatCurrency } from "./type.js";
1011

1112
/**
1213
* Props for the `convertFiatToCrypto` function
@@ -18,7 +19,7 @@ export type ConvertFiatToCryptoParams = {
1819
* The fiat symbol. e.g: "USD"
1920
* Currently only USD is supported.
2021
*/
21-
from: "USD";
22+
from: SupportedFiatCurrency;
2223
/**
2324
* The total amount of fiat to convert
2425
* e.g: If you want to convert 2 cents to USD, enter `0.02`
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/**
2+
* @internal
3+
*/
4+
export const SUPPORTED_FIAT_CURRENCIES = ["USD"] as const;
5+
/**
6+
* @internal
7+
*/
8+
export type SupportedFiatCurrency = (typeof SUPPORTED_FIAT_CURRENCIES)[number];

packages/thirdweb/src/react/web/ui/ConnectWallet/Details.tsx

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import { getContract } from "../../../../contract/contract.js";
1717
import { getLastAuthProvider } from "../../../../react/core/utils/storage.js";
1818
import { shortenAddress } from "../../../../utils/address.js";
1919
import { isContractDeployed } from "../../../../utils/bytecode/is-contract-deployed.js";
20-
import { formatNumber } from "../../../../utils/formatNumber.js";
2120
import { webLocalStorage } from "../../../../utils/storage/webStorage.js";
2221
import { isEcosystemWallet } from "../../../../wallets/ecosystem/is-ecosystem-wallet.js";
2322
import type { Ecosystem } from "../../../../wallets/in-app/core/wallet/types.js";
@@ -86,7 +85,12 @@ import { fadeInAnimation } from "../design-system/animations.js";
8685
import { StyledButton } from "../design-system/elements.js";
8786
import { AccountAddress } from "../prebuilt/Account/address.js";
8887
import { AccountAvatar } from "../prebuilt/Account/avatar.js";
89-
import { AccountBalance } from "../prebuilt/Account/balance.js";
88+
import {
89+
AccountBalance,
90+
type AccountBalanceInfo,
91+
formatAccountFiatBalance,
92+
formatAccountTokenBalance,
93+
} from "../prebuilt/Account/balance.js";
9094
import { AccountBlobbie } from "../prebuilt/Account/blobbie.js";
9195
import { AccountName } from "../prebuilt/Account/name.js";
9296
import { AccountProvider } from "../prebuilt/Account/provider.js";
@@ -273,18 +277,28 @@ export const ConnectedWalletDetails: React.FC<{
273277
size="xs"
274278
color="secondaryText"
275279
weight={400}
280+
inline
281+
multiline={false}
276282
>
277283
<AccountBalance
278284
chain={walletChain}
279285
loadingComponent={<Skeleton height={fontSize.xs} width="70px" />}
280286
fallbackComponent={<Skeleton height={fontSize.xs} width="70px" />}
281-
formatFn={formatBalanceOnButton}
282287
tokenAddress={
283288
props.detailsButton?.displayBalanceToken?.[
284289
Number(walletChain?.id)
285290
]
286291
}
287292
/>
293+
<AccountBalance
294+
chain={walletChain}
295+
tokenAddress={
296+
props.detailsButton?.displayBalanceToken?.[
297+
Number(walletChain?.id)
298+
]
299+
}
300+
showInFiat="USD"
301+
/>
288302
</Text>
289303
</Container>
290304
</WalletInfoButton>
@@ -378,13 +392,26 @@ function DetailsModal(props: {
378392
{chainNameQuery.name || `Unknown chain #${walletChain?.id}`}
379393
<Text color="secondaryText" size="xs">
380394
<AccountBalance
381-
fallbackComponent={<Skeleton height="1em" width="100px" />}
382-
loadingComponent={<Skeleton height="1em" width="100px" />}
383-
formatFn={(num: number) => formatNumber(num, 9)}
395+
fallbackComponent={<Skeleton height="1em" width="70px" />}
396+
loadingComponent={<Skeleton height="1em" width="70px" />}
384397
chain={walletChain}
385398
tokenAddress={
386399
props.displayBalanceToken?.[Number(walletChain?.id)]
387400
}
401+
formatFn={(props: AccountBalanceInfo) =>
402+
formatAccountTokenBalance({ ...props, decimals: 9 })
403+
}
404+
/>{" "}
405+
<AccountBalance
406+
loadingComponent={<Skeleton height="1em" width="30px" />}
407+
chain={walletChain}
408+
tokenAddress={
409+
props.displayBalanceToken?.[Number(walletChain?.id)]
410+
}
411+
formatFn={(props: AccountBalanceInfo) =>
412+
formatAccountFiatBalance({ ...props, decimals: 3 })
413+
}
414+
showInFiat="USD"
388415
/>
389416
</Text>
390417
</Text>
@@ -1006,10 +1033,6 @@ function DetailsModal(props: {
10061033
);
10071034
}
10081035

1009-
function formatBalanceOnButton(num: number) {
1010-
return formatNumber(num, num < 1 ? 5 : 4);
1011-
}
1012-
10131036
const WalletInfoButton = /* @__PURE__ */ StyledButton((_) => {
10141037
const theme = useCustomTheme();
10151038
return {
Lines changed: 91 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,106 @@
11
import { describe, expect, it } from "vitest";
2-
import { ANVIL_CHAIN } from "~test/chains.js";
3-
import { render, screen, waitFor } from "~test/react-render.js";
2+
import { VITALIK_WALLET } from "~test/addresses.js";
43
import { TEST_CLIENT } from "~test/test-clients.js";
5-
import { TEST_ACCOUNT_A } from "~test/test-wallets.js";
6-
import { getWalletBalance } from "../../../../../wallets/utils/getWalletBalance.js";
7-
import { AccountBalance } from "./balance.js";
8-
import { AccountProvider } from "./provider.js";
4+
import { ethereum } from "../../../../../chains/chain-definitions/ethereum.js";
5+
import { NATIVE_TOKEN_ADDRESS } from "../../../../../constants/addresses.js";
6+
import {
7+
formatAccountFiatBalance,
8+
formatAccountTokenBalance,
9+
loadAccountBalance,
10+
} from "./balance.js";
911

1012
describe.runIf(process.env.TW_SECRET_KEY)("AccountBalance component", () => {
11-
it("format the balance properly", async () => {
12-
const roundTo1Decimal = (num: number): number => Math.round(num * 10) / 10;
13-
const balance = await getWalletBalance({
14-
chain: ANVIL_CHAIN,
13+
it("`loadAccountBalance` should fetch the native balance properly", async () => {
14+
const result = await loadAccountBalance({
1515
client: TEST_CLIENT,
16-
address: TEST_ACCOUNT_A.address,
16+
chain: ethereum,
17+
address: VITALIK_WALLET,
1718
});
1819

19-
render(
20-
<AccountProvider address={TEST_ACCOUNT_A.address} client={TEST_CLIENT}>
21-
<AccountBalance chain={ANVIL_CHAIN} formatFn={roundTo1Decimal} />
22-
</AccountProvider>,
23-
);
20+
expect(Number.isNaN(result.balance)).toBe(false);
21+
expect(result.symbol).toBe("ETH");
22+
});
2423

25-
waitFor(() =>
26-
expect(
27-
screen.getByText(roundTo1Decimal(Number(balance.displayValue)), {
28-
exact: true,
29-
selector: "span",
30-
}),
31-
).toBeInTheDocument(),
32-
);
24+
it("`loadAccountBalance` should fetch the token balance properly", async () => {
25+
const result = await loadAccountBalance({
26+
client: TEST_CLIENT,
27+
chain: ethereum,
28+
address: VITALIK_WALLET,
29+
// USDC
30+
tokenAddress: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
31+
});
32+
33+
expect(Number.isNaN(result.balance)).toBe(false);
34+
expect(result.symbol).toBe("USDC");
35+
});
36+
37+
it("`loadAccountBalance` should fetch the fiat balance properly", async () => {
38+
const result = await loadAccountBalance({
39+
client: TEST_CLIENT,
40+
chain: ethereum,
41+
address: VITALIK_WALLET,
42+
showInFiat: "USD",
43+
});
44+
45+
expect(Number.isNaN(result.balance)).toBe(false);
46+
expect(result.symbol).toBe("$");
47+
});
48+
49+
it("`loadAccountBalance` should throw if `chain` is not passed", async () => {
50+
await expect(() =>
51+
loadAccountBalance({ client: TEST_CLIENT, address: VITALIK_WALLET }),
52+
).rejects.toThrowError("chain is required");
3353
});
3454

35-
it("should fallback properly if failed to load", () => {
36-
render(
37-
<AccountProvider address={TEST_ACCOUNT_A.address} client={TEST_CLIENT}>
38-
<AccountBalance
39-
chain={undefined}
40-
fallbackComponent={<span>oops</span>}
41-
/>
42-
</AccountProvider>,
55+
it("`loadAccountBalance` should throw if `tokenAddress` is mistakenly passed as native token", async () => {
56+
await expect(() =>
57+
loadAccountBalance({
58+
client: TEST_CLIENT,
59+
address: VITALIK_WALLET,
60+
tokenAddress: NATIVE_TOKEN_ADDRESS,
61+
chain: ethereum,
62+
}),
63+
).rejects.toThrowError(
64+
`Invalid tokenAddress - cannot be ${NATIVE_TOKEN_ADDRESS}`,
4365
);
66+
});
67+
68+
it("`loadAccountBalance` should throw if `address` is not a valid evm address", async () => {
69+
await expect(() =>
70+
loadAccountBalance({
71+
client: TEST_CLIENT,
72+
address: "haha",
73+
chain: ethereum,
74+
}),
75+
).rejects.toThrowError("Invalid wallet address. Expected an EVM address");
76+
});
4477

45-
waitFor(() =>
46-
expect(
47-
screen.getByText("oops", {
48-
exact: true,
49-
selector: "span",
50-
}),
51-
).toBeInTheDocument(),
78+
it("`loadAccountBalance` should throw if `tokenAddress` is passed but is not a valid evm address", async () => {
79+
await expect(() =>
80+
loadAccountBalance({
81+
client: TEST_CLIENT,
82+
address: VITALIK_WALLET,
83+
tokenAddress: "haha",
84+
chain: ethereum,
85+
}),
86+
).rejects.toThrowError(
87+
"Invalid tokenAddress. Expected an EVM contract address",
5288
);
5389
});
90+
91+
it("`formatAccountTokenBalance` should display a rounded-up value + symbol", () => {
92+
expect(
93+
formatAccountTokenBalance({
94+
balance: 1.1999,
95+
symbol: "ETH",
96+
decimals: 1,
97+
}),
98+
).toBe("1.2 ETH");
99+
});
100+
101+
it("`formatAccountFiatBalance` should display fiat symbol followed by a rounded-up fiat value", () => {
102+
expect(
103+
formatAccountFiatBalance({ balance: 55.001, symbol: "$", decimals: 0 }),
104+
).toBe(" ($55)");
105+
});
54106
});

0 commit comments

Comments
 (0)