Compare commits

..
Author SHA1 Message Date
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
c37af4f191 Version Packages (#8389)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-11-10 14:31:30 -08:00
Joaquim VergesandGitHub f964b8aaff [SDK] Loosen peer dependency requirements for React Native (#8390) 2025-11-10 10:47:57 -08:00
YashandGitHub 424e6b8f9b Stylus modules (#8383) 2025-11-10 10:33:56 -08:00
MananTank 763d3de5a8 [MNY-303] Various UI/UX Improvements in Bridge components (#8388)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on various UI/UX improvements across multiple components in the `thirdweb` package, particularly enhancing visual consistency and user interaction within the Bridge and Token Selection components.

### Detailed summary
- Adjusted font weights in several components from `600` to `500`.
- Updated `Spacer` component usage for consistent spacing.
- Enhanced `TokenSelection` UI with tabbed navigation for "All Tokens" and "Your Tokens".
- Added fallback images for icons in `TokenAndChain` and `ChainButton`.
- Refactored `CopyIcon` to use a `Button` instead of a `div` for better accessibility.
- Improved loading states and skeletons in `TokenSelectionScreen`.
- Simplified conditional rendering for transaction details in `PaymentReceipt`.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

* **New Features**
  * Token selection: separate "All Tokens" and "Your Tokens" tabs with per-tab paging and "Load More"
  * Payment receipts: per-transaction explorer links and copy-to-clipboard controls
  * Enhanced success/details views showing expanded per-transaction status entries

* **UI/UX Improvements**
  * Refined typography weights across Bridge screens for clearer hierarchy
  * Adjusted icon sizing and added visual fallbacks for missing chain/token icons
  * Improved spacing, alignment, scrolling and containment in Bridge layouts
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-10 18:11:43 +00:00
Joaquim VergesandGitHub f6f138ccbc [Portal] Move x402 from payments to top-level section (#8386) 2025-11-09 18:09:15 -08:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
f5d5e73e57 Version Packages (#8385)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-11-09 15:17:10 -08:00
Joaquim VergesandGitHub 673e51b11c [SDK] Replace Celo Alfajores with Celo Sepolia Testnet (#8384) 2025-11-09 14:46:12 -08:00
MananTank 282edf7ccb [PRO-73] Portal: Update hero images (#8381)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on making stylistic updates to the `Hero` component in the `page.tsx` file, including adjustments to padding and text size in the layout.

### Detailed summary
- Changed padding in the `section` from `py-14` to `pt-14 pb-6`.
- Modified paragraph class from `text-lg` to `text-base` for smaller text size on smaller screens while keeping `lg:text-lg`.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

* **Style**
  * Adjusted Hero section vertical spacing for improved layout balance.
  * Enhanced introductory text with responsive font sizing and refined spacing across different screen sizes.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-07 20:40:50 +00:00
MananTank a87e91b99c [MNY-305] Dashboard: Checksum addresses in tx page (#8380)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on enhancing address handling in the `GeneericTxDetails` and `TokenInfo` components by introducing the `getAddress` function from `thirdweb`, ensuring that addresses are consistently formatted.

### Detailed summary
- Added `getAddress` import in `page.tsx` and `bridge-status.tsx`.
- Replaced direct usage of `transaction.from` and `transaction.to` with `fromAddress` and `toAddress` in `GeneericTxDetails`.
- Updated `textToCopy` and `textToShow` in `CopyTextButton` components to use `fromAddress` and `toAddress`.
- Updated `TokenInfo` to use `getAddress` for `props.token.address` and `props.walletAddress`.
- Changed `textToCopy` and `textToShow` in `TokenInfo` to use `tokenAddress`.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

* **Bug Fixes**
  * Improved address normalization in transaction and bridge status displays to ensure consistent formatting across the dashboard, preventing potential issues with address validation and comparison.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-07 20:28:47 +00:00
MananTank 0e630c4550 [MNY-304] Move engine tx summary request to client side to fix page crash (#8379)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on refactoring the transaction analytics feature by updating data fetching methods, improving UI components, and enhancing error handling for transaction summaries and charts.

### Detailed summary
- Removed outdated files: `utils.ts` and `filter.tsx`.
- Updated import paths for `TransactionSummaryData`.
- Changed UI text in `tx-chart-ui.tsx` for clarity.
- Enhanced error handling in data fetching functions.
- Introduced `getTransactionsChartData` and `getTransactionAnalyticsSummary` for improved data management.
- Refactored `TransactionsAnalyticsPageContent` to streamline data flow and UI rendering.
- Added new components for date and interval selection in transaction charts.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

* **Refactor**
  * Analytics now load client-side with a spinner and a streamlined layout; analytics sections only show when transactions exist.

* **New Features**
  * Direct date-range and interval controls in the analytics view.
  * Unified analytics summary and charts presented above the transactions table.
  * Chart header updated to "Transactions" with revised description.

* **Chores**
  * Removed URL-synced filter behavior; filters are local.
  * Empty-chart messaging changed to "No transactions found."
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-07 20:13:34 +00:00
Joaquim VergesandGitHub 11c7bf0955 [Playground] Remove hardcoded limit from wrapFetchWithPayment (#8376) 2025-11-07 02:38:54 -08:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
c9e430d092 Version Packages (#8375)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-11-07 17:09:03 +07:00
FirekeeperandGitHub 1f440d0c97 Add Epic Games as new auth provider (#8343) 2025-11-07 15:59:08 +07:00
Yash094 3a0aac4393 Change TransactionWidget fiat decimals to two (#8366)
<!--

## [SDK] Fix: Format TransactionWidget fiat amounts to 2 decimals

## Notes for the reviewer

This PR updates the `formatMoney` function to display fiat amounts with a maximum of 2 decimal places instead of 5. This standardizes currency formatting for improved readability within the TransactionWidget.

## How to test

- Verify fiat amounts in the TransactionWidget (e.g., payment screens, token selection) are displayed with a maximum of 2 decimal places.
- Unit tests passed.

-->

---
[Slack Thread](https://thirdwebdev.slack.com/archives/C09DS2CKGP2/p1762414792059159?thread_ts=1762414792.059159&cid=C09DS2CKGP2)

<a href="https://cursor.com/background-agent?bcId=bc-65f07aee-1cee-4915-9656-8e4d9904ae3c"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/open-in-cursor-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/open-in-cursor-light.svg"><img alt="Open in Cursor" src="https://cursor.com/open-in-cursor.svg"></picture></a>&nbsp;<a href="https://cursor.com/agents?id=bc-65f07aee-1cee-4915-9656-8e4d9904ae3c"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/open-in-web-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/open-in-web-light.svg"><img alt="Open in Web" src="https://cursor.com/open-in-web.svg"></picture></a>

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on modifying the formatting of token balances in the `formatTokenBalance.ts` file to limit the maximum number of fraction digits displayed.

### Detailed summary
- Changed `maximumFractionDigits` from `5` to `2` in the currency formatting options.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

* **Bug Fixes**
  * Updated currency formatting to display a maximum of 2 decimal places instead of 5, improving readability and consistency of financial amounts throughout the app while maintaining appropriate minimum precision.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-07 07:36:09 +00:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
7b13015053 Version Packages (#8374)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-11-06 16:29:15 -08:00
Joaquim VergesandGitHub 7f081618e1 [SDK] Expose useInvalidateBalances() (#8373) 2025-11-06 16:21:38 -08:00
99 changed files with 1797 additions and 950 deletions
+2 -1
View File
@@ -21,7 +21,8 @@ export type AuthOption =
| "steam"
| "apple"
| "coinbase"
| "line";
| "line"
| "epic";
export type Ecosystem = {
name: string;
@@ -225,6 +225,10 @@ export const InstallModuleForm = (props: InstallModuleFormProps) => {
moduleInfo: {
bytecodeUri: selectedModule.metadata.bytecodeUri,
},
isStylus:
(selectedModule.metadata.compilers?.stylus &&
selectedModule.metadata.compilers?.stylus?.length > 0) ||
false,
});
},
queryKey: [
@@ -432,6 +436,7 @@ async function isModuleCompatible(options: {
moduleInfo: {
bytecodeUri: string;
};
isStylus: boolean;
client: ThirdwebClient;
}) {
// 1. get module's bytecode
@@ -444,15 +449,17 @@ async function isModuleCompatible(options: {
// 2. check compatibility with core and installed modules
try {
const isCompatible = await checkModulesCompatibility({
chain: options.contractInfo.chain,
client: options.client,
coreBytecode: options.contractInfo.bytecode,
moduleBytecodes: [
moduleBytecode,
...options.contractInfo.installedModuleBytecodes,
],
});
const isCompatible =
options.isStylus ||
(await checkModulesCompatibility({
chain: options.contractInfo.chain,
client: options.client,
coreBytecode: options.contractInfo.bytecode,
moduleBytecodes: [
moduleBytecode,
...options.contractInfo.installedModuleBytecodes,
],
}));
return isCompatible;
} catch (e) {
@@ -10,7 +10,11 @@ import {
CircleXIcon,
} from "lucide-react";
import Link from "next/link";
import { NATIVE_TOKEN_ADDRESS, type ThirdwebClient } from "thirdweb";
import {
getAddress,
NATIVE_TOKEN_ADDRESS,
type ThirdwebClient,
} from "thirdweb";
import type { Status, Token } from "thirdweb/bridge";
import { status } from "thirdweb/bridge";
import { toTokens } from "thirdweb/utils";
@@ -123,6 +127,8 @@ function TokenInfo(props: {
const isNativeToken =
props.token.address.toLowerCase() === NATIVE_TOKEN_ADDRESS.toLowerCase();
const tokenAddress = getAddress(props.token.address);
return (
<div className="flex-1 pt-10 pb-9 px-6 lg:px-8">
<div className="flex justify-between items-center">
@@ -206,7 +212,7 @@ function TokenInfo(props: {
<div className="space-y-1">
<p className="text-sm text-foreground ">{props.addressLabel}</p>
<WalletAddress
address={props.walletAddress}
address={getAddress(props.walletAddress)}
client={props.client}
className="py-0.5 h-auto text-sm [&>*]:!font-sans tabular-nums [&>*]:font-normal text-muted-foreground hover:text-foreground"
iconClassName="size-3"
@@ -222,8 +228,8 @@ function TokenInfo(props: {
? `/${chainQuery.data?.slug || props.token.chainId}`
: `/${chainQuery.data?.slug || props.token.chainId}/${props.token.address}`
}
textToShow={props.token.address}
textToCopy={props.token.address}
textToShow={tokenAddress}
textToCopy={tokenAddress}
copyTooltip="Copy Token Address"
className="-translate-x-1"
/>
@@ -8,7 +8,7 @@ import {
Clock4Icon,
InfoIcon,
} from "lucide-react";
import { toTokens } from "thirdweb";
import { getAddress, toTokens } from "thirdweb";
import { status } from "thirdweb/bridge";
import type { ChainMetadata } from "thirdweb/chains";
import {
@@ -153,6 +153,9 @@ function GeneericTxDetails(props: {
const timestamp = getDatefromTimestamp(block.timestamp);
const fromAddress = getAddress(transaction.from);
const toAddress = transaction.to ? getAddress(transaction.to) : undefined;
return (
<div className="border rounded-xl p-4 lg:p-6 bg-card">
{/* section 1 */}
@@ -216,8 +219,8 @@ function GeneericTxDetails(props: {
<section className="border-b py-6 space-y-5 lg:space-y-3">
<GridItem label="From" tooltip="The sending party of the transaction.">
<CopyTextButton
textToCopy={transaction.from}
textToShow={transaction.from}
textToCopy={fromAddress}
textToShow={fromAddress}
tooltip="Copy from address"
variant="ghost"
copyIconPosition="right"
@@ -225,14 +228,14 @@ function GeneericTxDetails(props: {
/>
</GridItem>
{transaction.to && (
{toAddress && (
<GridItem
label="Interacted with (To)"
tooltip="The receiving party of the transaction (could be a contract address)."
>
<CopyTextButton
textToCopy={transaction.to}
textToShow={transaction.to}
textToCopy={toAddress}
textToShow={toAddress}
tooltip="Copy to address"
variant="ghost"
copyIconPosition="right"
@@ -183,13 +183,64 @@ const STYLUS = {
],
description:
"Airdrop your NFTs or tokens to a large number of recipients. Built with Arbitrum Stylus.",
displayName: "Arbitrum Stylus Contracts",
displayName: "Airdrop - Arbitrum Stylus",
id: "stylus",
name: "Stylus",
showInExplore: true,
isBeta: true,
} satisfies ExploreCategory;
const MODULAR_CONTRACTS_STYLUS = {
contracts: [
// erc721 token
[
"thirdweb.eth/ERC721CoreInitializable",
[
"0x6453a486d52e0eb6e79ec4491038e2522a926936/StylusMintableERC721",
"deployer.thirdweb.eth/BatchMetadataERC721",
"0x6453a486d52e0eb6e79ec4491038e2522a926936/StylusTransferableERC721",
],
{
description: "ERC721 NFTs that only owners can mint.",
title: "Modular NFT Collection",
},
],
// erc1155 token
[
"thirdweb.eth/ERC1155CoreInitializable",
[
"0x6453a486d52e0eb6e79ec4491038e2522a926936/StylusMintableERC1155",
"deployer.thirdweb.eth/BatchMetadataERC1155",
"0x6453a486d52e0eb6e79ec4491038e2522a926936/StylusTransferableERC1155",
"deployer.thirdweb.eth/SequentialTokenIdERC1155",
],
{
description: "ERC1155 NFTs that only owners can mint.",
title: "Modular Edition",
},
],
// erc20 token
[
"thirdweb.eth/ERC20CoreInitializable",
[
"0x6453a486d52e0eb6e79ec4491038e2522a926936/StylusMintableERC20",
"0x6453a486d52e0eb6e79ec4491038e2522a926936/StylusTransferableERC20",
],
{
description: "ERC20 Tokens that only owners can mint.",
title: "Modular Token",
},
],
],
description:
"Collection of highly customizable and upgradeable smart contracts built with the modular contracts framework.",
displayName: "Modular Contracts - Arbitrum Stylus",
id: "modular-contracts",
name: "modular",
showInExplore: true,
isBeta: true,
} satisfies ExploreCategory;
const CATEGORIES: Record<string, ExploreCategory> = {
[POPULAR.id]: POPULAR,
[NFTS.id]: NFTS,
@@ -203,6 +254,7 @@ const CATEGORIES: Record<string, ExploreCategory> = {
[COMMERCE.id]: COMMERCE,
[STAKING.id]: STAKING,
[GOVERNANCE.id]: GOVERNANCE,
[MODULAR_CONTRACTS_STYLUS.id]: MODULAR_CONTRACTS_STYLUS,
};
export function getCategory(id: string) {
@@ -58,6 +58,7 @@ const authOptions = [
"apple",
"coinbase",
"line",
"epic",
] as const satisfies AuthOption[];
type AuthOptionsFormData = {
@@ -1,45 +1,95 @@
import { ResponsiveSearchParamsProvider } from "responsive-rsc";
"use client";
import { useQuery } from "@tanstack/react-query";
import { Spinner } from "@workspace/ui/components/spinner";
import type { ThirdwebClient } from "thirdweb";
import type { Project } from "@/api/project/projects";
import { UnifiedTransactionsTable } from "../components/transactions-table.client";
import { getTransactionAnalyticsSummary } from "../lib/analytics-summary.client";
import type { Wallet } from "../server-wallets/wallet-table/types";
import { TransactionAnalyticsFilter } from "./filter";
import { TransactionsChartCard } from "./tx-chart/tx-chart";
import type { SolanaWallet } from "../solana-wallets/wallet-table/types";
import { EngineChecklist } from "./ftux.client";
import { TransactionAnalyticsSummary } from "./summary";
import { TransactionsAnalytics } from "./tx-chart/tx-chart";
export function TransactionsAnalyticsPageContent(props: {
searchParams: {
from?: string | undefined | string[];
to?: string | undefined | string[];
interval?: string | undefined | string[];
};
project: Project;
showAnalytics: boolean;
wallets?: Wallet[];
wallets: Wallet[];
teamSlug: string;
client: ThirdwebClient;
authToken: string;
teamId: string;
isManagedVault: boolean;
testTxWithWallet: string | undefined;
testSolanaTxWithWallet: string | undefined;
solanaWallets: SolanaWallet[];
}) {
return (
<ResponsiveSearchParamsProvider value={props.searchParams}>
<div className="flex grow flex-col gap-6">
{props.showAnalytics && (
<>
<div className="flex justify-end">
<TransactionAnalyticsFilter />
</div>
<TransactionsChartCard
project={props.project}
searchParams={props.searchParams}
teamSlug={props.teamSlug}
wallets={props.wallets ?? []}
/>
</>
)}
<UnifiedTransactionsTable
client={props.client}
project={props.project}
teamSlug={props.teamSlug}
/>
const engineTxSummaryQuery = useQuery({
queryKey: [
"engine-tx-analytics-summary",
props.teamId,
props.project.publishableKey,
props.authToken,
],
queryFn: async () => {
const data = await getTransactionAnalyticsSummary({
clientId: props.project.publishableKey,
teamId: props.teamId,
authToken: props.authToken,
});
return data;
},
refetchOnWindowFocus: false,
refetchOnMount: false,
});
if (engineTxSummaryQuery.isPending) {
return (
<div className="flex h-[642px] grow items-center justify-center bg-card rounded-xl border">
<Spinner className="size-10" />
</div>
</ResponsiveSearchParamsProvider>
);
}
const hasTransactions = engineTxSummaryQuery.data
? engineTxSummaryQuery.data.totalCount > 0
: false;
return (
<div className="flex grow flex-col gap-10">
<EngineChecklist
isManagedVault={props.isManagedVault}
client={props.client}
hasTransactions={hasTransactions}
project={props.project}
teamSlug={props.teamSlug}
testTxWithWallet={props.testTxWithWallet}
testSolanaTxWithWallet={props.testSolanaTxWithWallet}
wallets={props.wallets}
solanaWallets={props.solanaWallets}
/>
{props.showAnalytics && hasTransactions && (
<div className="flex flex-col gap-6">
<TransactionAnalyticsSummary
clientId={props.project.publishableKey}
teamId={props.project.teamId}
initialData={engineTxSummaryQuery.data}
/>
<TransactionsAnalytics
project={props.project}
authToken={props.authToken}
teamSlug={props.teamSlug}
wallets={props.wallets ?? []}
/>
</div>
)}
<UnifiedTransactionsTable
client={props.client}
project={props.project}
teamSlug={props.teamSlug}
/>
</div>
);
}
@@ -1,51 +0,0 @@
"use client";
import {
useResponsiveSearchParams,
useSetResponsiveSearchParams,
} from "responsive-rsc";
import { DateRangeSelector } from "@/components/analytics/date-range-selector";
import { IntervalSelector } from "@/components/analytics/interval-selector";
import { normalizeTimeISOString } from "@/lib/time";
import { getTxAnalyticsFiltersFromSearchParams } from "../lib/utils";
export function TransactionAnalyticsFilter() {
const responsiveSearchParams = useResponsiveSearchParams();
const setResponsiveSearchParams = useSetResponsiveSearchParams();
const { range, interval } = getTxAnalyticsFiltersFromSearchParams({
from: responsiveSearchParams.from,
interval: responsiveSearchParams.interval,
to: responsiveSearchParams.to,
});
return (
<div className="no-scrollbar flex items-center gap-3 max-sm:overflow-auto">
<DateRangeSelector
popoverAlign="end"
range={range}
setRange={(newRange) => {
setResponsiveSearchParams((v) => {
return {
...v,
from: normalizeTimeISOString(newRange.from),
to: normalizeTimeISOString(newRange.to),
};
});
}}
/>
<IntervalSelector
intervalType={interval}
setIntervalType={(newInterval) => {
setResponsiveSearchParams((v) => {
return {
...v,
interval: newInterval,
};
});
}}
/>
</div>
);
}
@@ -1,7 +1,7 @@
import { ActivityIcon, CoinsIcon } from "lucide-react";
import { toEther } from "thirdweb/utils";
import { StatCard } from "@/components/analytics/stat"; // Assuming correct path
import type { TransactionSummaryData } from "../lib/analytics";
import type { TransactionSummaryData } from "../lib/analytics-summary.client";
// Renders the UI based on fetched data or pending state
function TransactionAnalyticsSummaryUI(props: {
@@ -0,0 +1,75 @@
"use client";
import { NEXT_PUBLIC_ENGINE_CLOUD_URL } from "@/constants/public-envs";
import type { TransactionStats } from "@/types/analytics";
export async function getTransactionsChartData({
teamId,
clientId,
from,
to,
interval,
authToken,
}: {
teamId: string;
clientId: string;
from: string;
to: string;
interval: "day" | "week";
authToken: string;
}): Promise<TransactionStats[]> {
const filters = {
endDate: to,
resolution: interval,
startDate: from,
};
const response = await fetch(
`${NEXT_PUBLIC_ENGINE_CLOUD_URL}/v1/transactions/analytics`,
{
body: JSON.stringify(filters),
headers: {
Authorization: `Bearer ${authToken}`,
"Content-Type": "application/json",
"x-client-id": clientId,
"x-team-id": teamId,
},
method: "POST",
},
);
if (!response.ok) {
if (response.status === 401 || response.status === 400) {
return [];
}
// TODO - need to handle this error state, like we do with the connect charts
throw new Error(
`Error fetching transactions chart data: ${response.status} ${
response.statusText
} - ${await response.text().catch(() => "Unknown error")}`,
);
}
type TransactionsChartResponse = {
result: {
analytics: Array<{
timeBucket: string;
chainId: string;
count: number;
}>;
metadata: {
resolution: string;
startDate: string;
endDate: string;
};
};
};
const data = (await response.json()) as TransactionsChartResponse;
return data.result.analytics.map((stat) => ({
chainId: Number(stat.chainId),
count: stat.count,
date: stat.timeBucket,
}));
}
@@ -108,10 +108,10 @@ export function TransactionsChartCardUI(props: {
customHeader={
<div className="relative px-6 pt-6">
<h3 className="mb-0.5 font-semibold text-xl tracking-tight">
Daily Transactions
Transactions
</h3>
<p className="text-muted-foreground text-sm">
Amount of daily transactions by chain.
transactions broken down by chain
</p>
<div className="top-6 right-6 mb-8 grid grid-cols-2 items-center gap-2 md:absolute md:mb-0 md:flex">
@@ -194,13 +194,7 @@ function EmptyChartContent(props: {
</div>
</>
) : (
<p className="flex items-center gap-2 rounded-full border bg-background px-3.5 py-1.5 text-sm">
<span className="!pointer-events-auto relative flex size-2">
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-sky-400 opacity-75" />
<span className="relative inline-flex size-2 rounded-full bg-primary" />
</span>
Waiting for transactions...
</p>
<p className="text-sm text-muted-foreground">No transactions found</p>
)}
</div>
);
@@ -1,73 +1,68 @@
import { ResponsiveSuspense } from "responsive-rsc";
"use client";
import { useQuery } from "@tanstack/react-query";
import { useState } from "react";
import type { Project } from "@/api/project/projects";
import { getTransactionsChart } from "../../lib/analytics";
import { getTxAnalyticsFiltersFromSearchParams } from "../../lib/utils";
import {
DateRangeSelector,
getLastNDaysRange,
} from "@/components/analytics/date-range-selector";
import { IntervalSelector } from "@/components/analytics/interval-selector";
import { normalizeTimeISOString } from "@/lib/time";
import type { Wallet } from "../../server-wallets/wallet-table/types";
import { getTransactionsChartData } from "./data";
import { TransactionsChartCardUI } from "./tx-chart-ui";
async function AsyncTransactionsChartCard(props: {
from: string;
to: string;
interval: "day" | "week";
export function TransactionsAnalytics(props: {
project: Project;
wallets: Wallet[];
authToken: string;
teamSlug: string;
}) {
const data = await getTransactionsChart({
const [range, setRange] = useState(() => getLastNDaysRange("last-30"));
const [interval, setInterval] = useState<"day" | "week">("day");
const params = {
clientId: props.project.publishableKey,
from: props.from,
interval: props.interval,
from: normalizeTimeISOString(range.from),
interval: interval,
teamId: props.project.teamId,
to: props.to,
to: normalizeTimeISOString(range.to),
authToken: props.authToken,
};
const engineTxAnalytics = useQuery({
queryKey: ["engine-tx-analytics", params],
queryFn: async () => {
const data = await getTransactionsChartData(params);
return data;
},
refetchOnWindowFocus: false,
refetchOnMount: false,
});
return (
<TransactionsChartCardUI
isPending={false}
project={props.project}
teamSlug={props.teamSlug}
userOpStats={data}
wallets={props.wallets}
/>
);
}
<div className="flex flex-col gap-6">
<div className="flex justify-end">
<div className="no-scrollbar flex items-center gap-3 max-sm:overflow-auto">
<DateRangeSelector
popoverAlign="end"
range={range}
setRange={setRange}
/>
export function TransactionsChartCard(props: {
searchParams: {
from?: string | undefined | string[];
to?: string | undefined | string[];
interval?: string | undefined | string[];
};
project: Project;
wallets: Wallet[];
teamSlug: string;
}) {
const { range, interval } = getTxAnalyticsFiltersFromSearchParams(
props.searchParams,
);
return (
<ResponsiveSuspense
// TODO - change this if this component does not end up using these params
fallback={
<TransactionsChartCardUI
isPending={true}
project={props.project}
teamSlug={props.teamSlug}
userOpStats={[]}
wallets={[]}
/>
}
searchParamsUsed={["from", "to", "interval"]}
>
<AsyncTransactionsChartCard
from={range.from.toISOString()}
interval={interval}
<IntervalSelector
intervalType={interval}
setIntervalType={setInterval}
/>
</div>
</div>
<TransactionsChartCardUI
isPending={engineTxAnalytics.isPending}
project={props.project}
teamSlug={props.teamSlug}
to={range.to.toISOString()}
userOpStats={engineTxAnalytics.data ?? []}
wallets={props.wallets}
/>
</ResponsiveSuspense>
</div>
);
}
@@ -0,0 +1,75 @@
"use client";
import { NEXT_PUBLIC_ENGINE_CLOUD_URL } from "@/constants/public-envs";
export type TransactionSummaryData = {
totalCount: number;
totalGasCostWei: string;
totalGasUnitsUsed: string; // Keep fetched data structure
};
type AnalyticsSummaryApiResponse = {
result: {
summary: {
totalCount: number;
totalGasCostWei: string;
totalGasUnitsUsed: string;
};
metadata: {
startDate?: string;
endDate?: string;
};
};
};
// Fetches data from the /analytics-summary endpoint
export async function getTransactionAnalyticsSummary(props: {
teamId: string;
clientId: string;
authToken: string;
}): Promise<TransactionSummaryData> {
const body = {};
const defaultData: TransactionSummaryData = {
totalCount: 0,
totalGasCostWei: "0",
totalGasUnitsUsed: "0",
};
try {
const response = await fetch(
`${NEXT_PUBLIC_ENGINE_CLOUD_URL}/v1/transactions/analytics-summary`,
{
body: JSON.stringify(body),
headers: {
Authorization: `Bearer ${props.authToken}`,
"Content-Type": "application/json",
"x-client-id": props.clientId,
"x-team-id": props.teamId,
},
method: "POST",
},
);
if (!response.ok) {
if (response.status === 401 || response.status === 400) {
console.error("Unauthorized fetching transaction summary");
return defaultData;
}
const errorText = await response.text();
throw new Error(
`Error fetching transaction summary: ${response.status} ${response.statusText} - ${errorText}`,
);
}
const data = (await response.json()) as AnalyticsSummaryApiResponse;
return {
totalCount: data.result.summary.totalCount ?? 0,
totalGasCostWei: data.result.summary.totalGasCostWei ?? "0",
totalGasUnitsUsed: data.result.summary.totalGasUnitsUsed ?? "0",
};
} catch (error) {
console.error("Failed to fetch transaction summary:", error);
return defaultData;
}
}
@@ -1,157 +1,10 @@
import { getAuthToken } from "@/api/auth-token";
import { NEXT_PUBLIC_ENGINE_CLOUD_URL } from "@/constants/public-envs";
import type { TransactionStats } from "@/types/analytics";
import type {
Transaction,
TransactionsResponse,
} from "../analytics/tx-table/types";
// Define the structure of the data we expect back from our fetch function
export type TransactionSummaryData = {
totalCount: number;
totalGasCostWei: string;
totalGasUnitsUsed: string; // Keep fetched data structure
};
// Define the structure of the API response
type AnalyticsSummaryApiResponse = {
result: {
summary: {
totalCount: number;
totalGasCostWei: string;
totalGasUnitsUsed: string;
};
metadata: {
startDate?: string;
endDate?: string;
};
};
};
// Fetches data from the /analytics-summary endpoint
export async function getTransactionAnalyticsSummary(props: {
teamId: string;
clientId: string;
}): Promise<TransactionSummaryData> {
const authToken = await getAuthToken();
const body = {};
const defaultData: TransactionSummaryData = {
totalCount: 0,
totalGasCostWei: "0",
totalGasUnitsUsed: "0",
};
try {
const response = await fetch(
`${NEXT_PUBLIC_ENGINE_CLOUD_URL}/v1/transactions/analytics-summary`,
{
body: JSON.stringify(body),
headers: {
Authorization: `Bearer ${authToken}`,
"Content-Type": "application/json",
"x-client-id": props.clientId,
"x-team-id": props.teamId,
},
method: "POST",
},
);
if (!response.ok) {
if (response.status === 401 || response.status === 400) {
console.error("Unauthorized fetching transaction summary");
return defaultData;
}
const errorText = await response.text();
throw new Error(
`Error fetching transaction summary: ${response.status} ${response.statusText} - ${errorText}`,
);
}
const data = (await response.json()) as AnalyticsSummaryApiResponse;
return {
totalCount: data.result.summary.totalCount ?? 0,
totalGasCostWei: data.result.summary.totalGasCostWei ?? "0",
totalGasUnitsUsed: data.result.summary.totalGasUnitsUsed ?? "0",
};
} catch (error) {
console.error("Failed to fetch transaction summary:", error);
return defaultData;
}
}
export async function getTransactionsChart({
teamId,
clientId,
from,
to,
interval,
}: {
teamId: string;
clientId: string;
from: string;
to: string;
interval: "day" | "week";
}): Promise<TransactionStats[]> {
const authToken = await getAuthToken();
const filters = {
endDate: to,
resolution: interval,
startDate: from,
};
const response = await fetch(
`${NEXT_PUBLIC_ENGINE_CLOUD_URL}/v1/transactions/analytics`,
{
body: JSON.stringify(filters),
headers: {
Authorization: `Bearer ${authToken}`,
"Content-Type": "application/json",
"x-client-id": clientId,
"x-team-id": teamId,
},
method: "POST",
},
);
if (!response.ok) {
if (response.status === 401 || response.status === 400) {
return [];
}
// TODO - need to handle this error state, like we do with the connect charts
throw new Error(
`Error fetching transactions chart data: ${response.status} ${
response.statusText
} - ${await response.text().catch(() => "Unknown error")}`,
);
}
type TransactionsChartResponse = {
result: {
analytics: Array<{
timeBucket: string;
chainId: string;
count: number;
}>;
metadata: {
resolution: string;
startDate: string;
endDate: string;
};
};
};
const data = (await response.json()) as TransactionsChartResponse;
return data.result.analytics.map((stat) => ({
chainId: Number(stat.chainId),
count: stat.count,
date: stat.timeBucket,
}));
}
export async function getSingleTransaction({
teamId,
clientId,
@@ -1,14 +0,0 @@
import { getFiltersFromSearchParams } from "@/lib/time";
export function getTxAnalyticsFiltersFromSearchParams(params: {
from?: string | undefined | string[];
to?: string | undefined | string[];
interval?: string | undefined | string[];
}) {
return getFiltersFromSearchParams({
defaultRange: "last-30",
from: params.from,
interval: params.interval,
to: params.to,
});
}
@@ -7,10 +7,7 @@ import { ProjectPage } from "@/components/blocks/project-page/project-page";
import { NEXT_PUBLIC_THIRDWEB_VAULT_URL } from "@/constants/public-envs";
import { getClientThirdwebClient } from "@/constants/thirdweb-client.client";
import { TransactionsAnalyticsPageContent } from "./analytics/analytics-page";
import { EngineChecklist } from "./analytics/ftux.client";
import { TransactionAnalyticsSummary } from "./analytics/summary";
import { ServerWalletsTable } from "./components/server-wallets-table.client";
import { getTransactionAnalyticsSummary } from "./lib/analytics";
import type { Wallet } from "./server-wallets/wallet-table/types";
import { listSolanaAccounts } from "./solana-wallets/lib/vault.client";
import type { SolanaWallet } from "./solana-wallets/wallet-table/types";
@@ -110,12 +107,6 @@ export default async function TransactionsAnalyticsPage(props: {
const isSolanaPermissionError =
solanaAccounts.error?.message.includes("AUTH_INSUFFICIENT_SCOPE") ?? false;
const initialData = await getTransactionAnalyticsSummary({
clientId: project.publishableKey,
teamId: project.teamId,
}).catch(() => undefined);
const hasTransactions = initialData ? initialData.totalCount > 0 : false;
const client = getClientThirdwebClient({
jwt: authToken,
teamId: project.teamId,
@@ -152,41 +143,30 @@ export default async function TransactionsAnalyticsPage(props: {
}}
>
<div className="flex flex-col gap-10">
<EngineChecklist
isManagedVault={isManagedVault}
client={client}
hasTransactions={hasTransactions}
project={project}
teamSlug={params.team_slug}
testTxWithWallet={searchParams.testTxWithWallet as string | undefined}
testSolanaTxWithWallet={
searchParams.testSolanaTxWithWallet as string | undefined
}
wallets={wallets ?? []}
solanaWallets={solanaAccounts.data.items}
/>
{hasTransactions &&
!searchParams.testTxWithWallet &&
!searchParams.testSolanaTxWithWallet && (
<TransactionAnalyticsSummary
clientId={project.publishableKey}
initialData={initialData}
teamId={project.teamId}
/>
)}
{/* transactions */}
<TransactionsAnalyticsPageContent
client={client}
authToken={authToken}
project={project}
searchParams={searchParams}
showAnalytics={
hasTransactions &&
!searchParams.testTxWithWallet &&
!searchParams.testSolanaTxWithWallet
}
teamSlug={params.team_slug}
wallets={wallets}
wallets={wallets ?? []}
solanaWallets={solanaAccounts.data.items}
teamId={project.teamId}
isManagedVault={isManagedVault}
testTxWithWallet={
typeof searchParams.testTxWithWallet === "string"
? searchParams.testTxWithWallet
: undefined
}
testSolanaTxWithWallet={
typeof searchParams.testSolanaTxWithWallet === "string"
? searchParams.testSolanaTxWithWallet
: undefined
}
/>
{/* Server Wallets (EVM + Solana) */}
@@ -87,6 +87,7 @@ const authMethodsToPickFrom: InAppWalletAuth[] = [
"line",
"x",
"tiktok",
"epic",
"coinbase",
"farcaster",
"telegram",
@@ -43,7 +43,6 @@ export function X402RightSection(props: { options: X402PlaygroundOptions }) {
fetch,
THIRDWEB_CLIENT,
activeWallet,
BigInt(1 * 10 ** 18),
);
const searchParams = new URLSearchParams();
searchParams.set("chainId", props.options.chain.id.toString());
@@ -20,6 +20,7 @@ const allInAppWalletLoginMethods: InAppWalletAuth[] = [
"line",
"x",
"tiktok",
"epic",
"facebook",
"apple",
"coinbase",
+8
View File
@@ -742,6 +742,14 @@ const paymentRedirects = {
"/payments/swap": "/bridge/swap",
"/payments/tokens": "/bridge/tokens",
"/payments/routes": "/bridge/routes",
"/payments/x402": "/x402",
"/payments/x402/:path*": "/x402/:path*",
"/payments/fund": "/bridge/fund",
"/payments/products": "/bridge/products",
"/payments/transactions": "/bridge/transactions",
"/payments/send": "/bridge/send",
"/payments/webhooks": "/bridge/webhooks",
"/payments/custom-data": "/bridge/custom-data",
};
const contractRedirects = {
+2 -2
View File
@@ -40,8 +40,8 @@ const links = [
name: "Contracts",
},
{
href: "/payments",
name: "Payments",
href: "/x402",
name: "x402",
},
{
href: "/bridge",
Binary file not shown.

Before

Width:  |  Height:  |  Size: 169 KiB

After

Width:  |  Height:  |  Size: 312 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 169 KiB

After

Width:  |  Height:  |  Size: 310 KiB

@@ -188,8 +188,8 @@ Any purchase data you provide to a payment will be included on all webhooks, sta
To connect with other auth strategies, use external wallets, or sponsor gas for users, check out the following guides:
- [Send a Payment](/payments/send)
- [Webhooks](/payments/webhooks)
- [Send a Payment](/bridge/send)
- [Webhooks](/bridge/webhooks)
## Explore Full API References
@@ -74,8 +74,8 @@ The BuyWidget handles the complete payment flow, supporting both crypto and fiat
## Going Further
- [Custom Payment Data](/payments/custom-data) - Attach metadata to payments
- [Webhooks](/payments/webhooks) - Get notified when payments complete
- [Custom Payment Data](/bridge/custom-data) - Attach metadata to payments
- [Webhooks](/bridge/webhooks) - Get notified when payments complete
## API Reference
+1 -1
View File
@@ -97,7 +97,7 @@ const preparedQuote = await Bridge.Buy.prepare({
The prepared quote will contain details about the payment, including the transactions needed to execute it.
You can execute the included transactions using the wallet of your choice. To learn how to send it using thirdweb Wallets, checkout the [Send a Payment](/payments/send) guide.
You can execute the included transactions using the wallet of your choice. To learn how to send it using thirdweb Wallets, checkout the [Send a Payment](/bridge/send) guide.
</TabsContent>
@@ -77,8 +77,8 @@ The CheckoutWidget handles the complete payment flow, supporting both crypto and
## Going Further
- [Custom Payment Data](/payments/custom-data) - Attach metadata to payments
- [Webhooks](/payments/webhooks) - Get notified when payments complete
- [Custom Payment Data](/bridge/custom-data) - Attach metadata to payments
- [Webhooks](/bridge/webhooks) - Get notified when payments complete
## API Reference
+2 -2
View File
@@ -52,14 +52,14 @@ client,
});
```
You can execute this quote exactly like a buy. See the [Send a Payment](/payments/send) guide for more details on this process.
You can execute this quote exactly like a buy. See the [Send a Payment](/bridge/send) guide for more details on this process.
## Going further
To connect with other auth strategies, use external wallets, or sponsor gas for users, check out the following guides:
- [Send a Payment](/payments/send)
- [Send a Payment](/bridge/send)
## Explore Full API References
@@ -262,8 +262,8 @@ Once this code completes, your payment has been fully executed. Payments normall
## Going further
- [Sell a Product](/payments/products)
- [Token Prices](/payments/tokens)
- [Sell a Product](/bridge/products)
- [Token Prices](/bridge/tokens)
## Explore API References
+30 -6
View File
@@ -16,21 +16,45 @@ export const sidebar: SideBar = {
{
isCollapsible: false,
links: [
{
href: `${bridgeSlug}/sell`,
name: "Sell Tokens",
},
{
href: `${bridgeSlug}/swap`,
name: "Swap Tokens",
},
{
href: `${bridgeSlug}/bridge-widget-script`,
name: "BridgeWidget Script",
},
{
href: `${bridgeSlug}/fund`,
name: "Fund Wallets",
},
{
href: `${bridgeSlug}/products`,
name: "Sell a Product",
},
{
href: `${bridgeSlug}/transactions`,
name: "Pay for Transactions",
},
{
href: `${bridgeSlug}/send`,
name: "Send a Payment",
},
{
href: `${bridgeSlug}/sell`,
name: "Sell Tokens",
},
{
href: `${bridgeSlug}/tokens`,
name: "Get Token Prices",
},
{
href: `${bridgeSlug}/bridge-widget-script`,
name: "BridgeWidget Script",
href: `${bridgeSlug}/webhooks`,
name: "Webhooks",
},
{
href: `${bridgeSlug}/custom-data`,
name: "Custom Data",
},
],
name: "Guides",
+2 -2
View File
@@ -317,9 +317,9 @@ async function swapEthToMatic(amount: string, userAccount: Account) {
## Going Further
- [Send a Payment](/payments/send) - Learn about peer-to-peer transfers
- [Send a Payment](/bridge/send) - Learn about peer-to-peer transfers
- [Token Prices](/bridge/tokens) - Access real-time token pricing
- [Webhooks](/payments/webhooks) - Get notified when swaps complete
- [Webhooks](/bridge/webhooks) - Get notified when swaps complete
## API Reference
+12 -2
View File
@@ -59,7 +59,17 @@ This returns token information with current price:
symbol: "USDC",
name: "USD Coin",
iconUri: "https://assets.coingecko.com/coins/images/6319/large/USD_Coin_icon.png",
priceUsd: 1.00
prices: {
USD: 1.00,
EUR: 0.86,
GBP: 0.74,
JPY: 148.85,
KRW: 1393.14,
CNY: 7.18,
INR: 86.20,
NOK: 10.18,
SEK: 9.68,
}
}
]
```
@@ -118,7 +128,7 @@ const nextTokens = await Bridge.tokens({
To connect with other auth strategies, use external wallets, or sponsor gas for users, check out the following guides:
- [Send a Payment](/payments/send)
- [Send a Payment](/bridge/send)
## Explore Full API References
@@ -86,8 +86,8 @@ The TransactionWidget handles the complete payment flow, supporting both crypto
## Going Further
- [Custom Payment Data](/payments/custom-data) - Attach metadata to payments
- [Webhooks](/payments/webhooks) - Get notified when payments complete
- [Custom Payment Data](/bridge/custom-data) - Attach metadata to payments
- [Webhooks](/bridge/webhooks) - Get notified when payments complete
## API Reference
@@ -221,8 +221,8 @@ async function verifyWebhook(
To connect with other auth strategies, use external wallets, or sponsor gas for users, check out the following guides:
- [Send a Payment](/payments/send)
- [Get Token Prices](/payments/tokens)
- [Send a Payment](/bridge/send)
- [Get Token Prices](/bridge/tokens)
## Explore Full API References
@@ -0,0 +1,160 @@
import { Step, Steps, createMetadata } from "@doc";
export const metadata = createMetadata({
title: "Stylus Minting Modules | thirdweb Documentation",
description:
"Customizable rust modules written for thirdweb's modular contracts",
image: {
title: "Stylus Minting Modules",
icon: "contracts",
},
});
# Stylus Minting Modules
You can now install rust based modules into a solidity based [modular contract](https://github.com/thirdweb-dev/modular-contracts/blob/dev/design-document.md).
This plug-n-play model also allows users to write their own custom minting logic in rust, taking advantage of Stylus' interoperability and gas savings. These modules work with existing router contracts and modules written in solidity.
## Deploy through dashboard
Deploying a Modular Contract and installing modules is easy through the thirdweb dashboard and ideal when you don't want to modify any code on the contract.
<Steps>
<Step title="Select Modular Contract for Stylus">
Navigate to the Modular Contracts section on Explore and select any token contract for your project.
</Step>
<Step title="Deploy Contract">
Select Arbitrum Sepolia or any other Stylus-supported network, then select Deploy. Once you have deployed one of these contracts, the modules come pre-installed, both solidity and rust ones.
</Step>
<Step title="Edit Modules">
If you wish to install a custom module, navigate to modules tab on your contract page. Enter the publisher address and select module from the dropdown, and click install.
You can uninstall a module from the same page.
Here's a list of prebuilt Rust / Stylus specific modules provided by thirdweb:
- [Mintable ERC20](https://thirdweb.com/0x6453a486d52e0EB6E79Ec4491038E2522a926936/StylusMintableERC20)
- [Transferable ERC20](https://thirdweb.com/0x6453a486d52e0EB6E79Ec4491038E2522a926936/StylusTransferableERC20)
- [Mintable ERC721](https://thirdweb.com/0x6453a486d52e0EB6E79Ec4491038E2522a926936/StylusMintableERC721)
- [Transferable ERC721](https://thirdweb.com/0x6453a486d52e0EB6E79Ec4491038E2522a926936/StylusTransferableERC721)
- [Mintable ERC1155](https://thirdweb.com/0x6453a486d52e0EB6E79Ec4491038E2522a926936/StylusMintableERC1155)
- [Transferable ERC1155](https://thirdweb.com/0x6453a486d52e0EB6E79Ec4491038E2522a926936/StylusTransferableERC1155)
You can customize these modules via thirdweb CLI as described below.
</Step>
</Steps>
## Build with CLI
If you want to modify the contract code or publish a custom module, you can use the thirdweb CLI.
<Steps>
<Step title="Create a new Stylus module">
In your CLI, run the following command to create a new directory with an template module. Select the module template from the list shown.
```bash
npx thirdweb create-stylus
```
</Step>
<Step title="Modify logic">
In the `src/lib.rs` file you can modify the contract logic such as adding fees, gating logic, analytics events, and more.
</Step>
<Step title="Build & Test">
To build your project, run the following command:
```bash
cargo stylus build
```
</Step>
<Step title="Deploy or Publish Your Module">
You can publish your module and install it to your modular contract. Publishing stores your module metadata in thirdweb’s on-chain registry so anyone (including you) can use that exact version later with a few clicks.
To publish your module, ensure you have your thirdweb secret key from your created project, then run the following command:
```bash
npx thirdweb publish-stylus -k YOUR_TW_SECRET_KEY
```
Once published, the module is now available to install via thirdweb dashboard from the modules tab of your contract.
</Step>
</Steps>
## Interacting with the Contract
Using the thirdweb SDKs, you can interact with your Stylus Modular contracts / modules to mint tokens, transfer ownership, and more.
The following describes how to interact with a ERC721 modular contract using thirdweb SDK. (Swap for ERC20 or ERC1155 variants as needed.)
### Upload metadata
```ts
import { BatchMetadataERC721 } from "thirdweb/modules";
const transaction = BatchMetadataERC721.uploadMetadata({
contract,
metadatas: [
{ name: "My NFT", description: "This is my NFT" },
],
});
await sendTransaction({
transaction,
account,
});
```
### Mint with role
```typescript
import { MintableERC721 } from "thirdweb/modules";
const transaction = MintableERC721.mintWithRole({
contract,
to: "0x...", // Address to mint tokens to
nfts: [
{
name: "My NFT",
description: "This is my NFT",
image: "ipfs://...",
},
],
});
// Send the transaction
await sendTransaction({ transaction, account });
```
### Set transfer rules
```ts
import { sendTransaction } from "thirdweb";
import { TransferableERC20 } from "thirdweb/modules";
const transaction = TransferableERC20.setTransferable({
contract,
enableTransfer: ...,
overrides: {
...
}
});
// Send the transaction
await sendTransaction({ transaction, account });
```
### Resources
- [Modular Contracts Design Doc](https://github.com/thirdweb-dev/modular-contracts/blob/dev/design-document.md)
@@ -0,0 +1,240 @@
import { Step, Steps, createMetadata } from "@doc";
export const metadata = createMetadata({
title: "Stylus Zk Mint Contract | thirdweb Documentation",
description:
"Stylus Zk Minting contract and app template",
image: {
title: "Stylus Zk Mint",
icon: "contracts",
},
});
# ZK Minting with Stylus
You can now create and deploy ZK Proof based token contracts with Stylus. The templates provide you with Stylus contracts, ZK circuit files, and a ready-to-deploy Next.js app + API for proof generation and minting.
Follow the steps below to learn how to build a privacy-preserving ERC721 token system using Zero-Knowledge proofs on Arbitrum Stylus. Users can mint tokens by proving they own a minimum amount of ETH without revealing their exact balance. However, you can customize the circuits and the contracts to add your own checks for minting.
## What you'll build
- ZK Circuit: Proves token ownership without revealing exact balances
- Stylus Contract: Rust-based ERC721 contract that verifies ZK proofs
- Frontend: Next.js app for generating proofs and minting tokens
- Oracle System: Secure balance verification mechanism
## Prerequisites
- [Create a project on your thirdweb account](https://thirdweb.com/dashboard)
- Install thirdweb CLI by running `npm install -g thirdweb`
- Install Rust tool chain by running `curl https://sh.rustup.rs -sSf | sh` or visit rust-lang.org
- Install solc by running `npm install -g solc` or visit soliditylang.org
- Install Node version 18 or higher
<Steps>
<Step title="Project Setup">
In your CLI, run the following command to create a new directory with an template module. Select the module template from the list shown.
```bash
npx thirdweb create-stylus
```
Select **"Stylus ZK ERC721"** from the dropdown menu. This will:
- Clone the repository to your machine
- Set up the project structure
- Install basic dependencies
</Step>
<Step title="Install Dependencies">
Install dependencies for all components:
```bash
# Install root dependencies
pnpm install
# Install circuit dependencies
cd circuits
pnpm install
cd ..
# Install frontend dependencies
cd app
pnpm install
cd ..
```
</Step>
<Step title="Generate Cryptographic Keys">
Run the setup script to generate oracle keys and build the ZK circuit:
```bash
chmod +x setup.sh
./setup.sh
```
This script will:
- Generate a random oracle secret key
- Inject the secret into the ZK circuit
- Compile the circuit with circom
- Generate proving and verification keys
- Create the trusted setup for Groth16
**⚠️ Important**: The oracle secret is critical for security. Keep it private!
</Step>
<Step title="Deploy the Contract">
### Using thirdweb CLI
```bash
cd contracts
npx thirdweb@latest deploy-stylus -k <THIRDWEB_SECRET_KEY>
```
### Using Stylus CLI (Alternative)
```bash
cd contracts
cargo stylus deploy --endpoint arbitrum-sepolia
```
Copy the deployed contract address - you'll need it for the frontend.
</Step>
<Step title="Configure the Frontend">
Update the contract address in your frontend:
```bash
cd app
# Edit pages/index.tsx or lib/config.ts
# Update ZK_MINT_CONTRACT_ADDRESS with your deployed address
```
Create environment file:
```bash
# app/.env.local
RPC_URL=https://sepolia-rollup.arbitrum.io/rpc
ORACLE_SECRET_KEY=<your_oracle_secret_from_setup>
```
</Step>
<Step title="Run the Application">
```bash
cd app
pnpm dev
# Visit http://localhost:3000
```
</Step>
<Step title="Test the System">
1. **Connect Wallet**: Connect to Arbitrum Sepolia testnet
2. **Generate Proof**: Click "Generate ZK Proof" - this proves you have sufficient balance
3. **Mint Tokens**: Use the proof to mint ERC721 tokens
</Step>
</Steps>
## Customizing the ZK Logic
The above described logic for ZK verification of user balance is provided as a template. You can modify the logic and write your own custom ZK verification code and circuits as described below.
### Understanding the Circuit
The core circuit (`circuits/token_ownership.circom`) has these components:
```txt
template TokenOwnership(oracle_secret) {
// Private inputs (hidden from public)
signal input actual_balance; // Real balance from oracle
signal input salt; // Randomness for uniqueness
// Public inputs (visible on-chain)
signal input min_required_balance; // Minimum threshold
signal input token_contract_hash; // Which token to check
signal input user_address_hash; // User's address hash
signal input timestamp; // When oracle signed data
signal input oracle_commitment; // Oracle's commitment
// Output
signal output nullifier; // Prevents replay attacks
}
```
### Customization Examples
#### 1. Change Balance Threshold Logic
Replace the balance check with custom logic:
```txt
// Original: Simple greater-than check
component gte = GreaterEqThan(64);
gte.in[0] <== actual_balance;
gte.in[1] <== min_required_balance;
gte.out === 1;
// Custom: Logarithmic scaling
component log_check = LogarithmicCheck();
log_check.balance <== actual_balance;
log_check.threshold <== min_required_balance;
log_check.out === 1;
```
#### 2. Add Multiple Token Support
Extend the circuit to verify multiple token balances:
```txt
template MultiTokenOwnership(oracle_secret, num_tokens) {
signal input actual_balances[num_tokens];
signal input min_required_balances[num_tokens];
signal input token_addresses[num_tokens];
// Verify each token separately
component checks[num_tokens];
for (var i = 0; i < num_tokens; i++) {
checks[i] = GreaterEqThan(64);
checks[i].in[0] <== actual_balances[i];
checks[i].in[1] <== min_required_balances[i];
checks[i].out === 1;
}
}
```
#### 3. Add Time-Based Constraints
Add expiration logic to proofs:
```txt
// Add time validation
component time_check = LessThan(64);
time_check.in[0] <== timestamp;
time_check.in[1] <== max_timestamp; // New public input
time_check.out === 1;
```
### Rebuilding After Changes
After modifying the circuit:
```bash
cd circuits
pnpm run build
# Re-inject verification keys
cd ..
node scripts/inject-vk.js
# Recompile contract
cd contracts
cargo build
```
### Resources
- [thirdweb Arbitrum Documentation](https://portal.thirdweb.com/contracts/arbitrum-stylus/stylus-contract)
- [Arbitrum Stylus Documentation](https://docs.arbitrum.io/stylus/concepts/how-it-works)
@@ -56,6 +56,14 @@ export const sidebar: SideBar = {
href: `${slug}/arbitrum-stylus/airdrop-contract`,
name: "Stylus Airdrop Contract",
},
{
href: `${slug}/arbitrum-stylus/zk-mint`,
name: "Stylus Zk Mint",
},
{
href: `${slug}/arbitrum-stylus/minting-modules`,
name: "Stylus Minting Modules",
},
],
name: "Arbitrum Stylus",
},
+6 -6
View File
@@ -41,14 +41,14 @@ export default function Page() {
function Hero() {
return (
<section className="grid gap-4 py-14 lg:grid-cols-[1fr_420px] lg:py-0">
<section className="grid gap-4 pt-14 pb-6 lg:grid-cols-[1fr_420px] lg:py-0">
{/* Left */}
<div className="flex flex-col justify-center">
<div>
<h1 className="mb-3 font-bold text-4xl tracking-tighter lg:text-6xl">
thirdweb Documentation
</h1>
<p className="mb-8 max-w-lg text-lg text-muted-foreground leading-normal text-pretty">
<p className="mb-6 max-w-lg text-base lg:text-lg text-muted-foreground leading-normal text-pretty">
Platform for building the next generation of internet products
</p>
<div className="flex gap-3">
@@ -178,10 +178,10 @@ function LearningResourcesSection() {
title="Wallets"
/>
<ArticleCardIndex
description="Create onchain payments and sell products"
href="/payments"
icon={PaymentsIcon}
title="Payments"
description="Instant payments for APIs, websites, and agents"
href="/x402"
icon={ZapIcon}
title="x402"
/>
<ArticleCardIndex
description="Bridge, swap, and onramp tokens on any chain"
-27
View File
@@ -61,33 +61,6 @@ export const sidebar: SideBar = {
name: "Guides",
},
{ separator: true },
{
isCollapsible: false,
links: [
{
href: `${paymentsSlug}/x402`,
name: "Get Started",
},
{
href: `${paymentsSlug}/x402/client`,
name: "Client Side",
},
{
href: `${paymentsSlug}/x402/server`,
name: "Server Side",
},
{
href: `${paymentsSlug}/x402/agents`,
name: "Agents",
},
{
href: `${paymentsSlug}/x402/facilitator`,
name: "Facilitator",
},
],
name: "x402",
},
{ separator: true },
{
isCollapsible: false,
links: [
@@ -75,7 +75,7 @@ The client library wraps the native `fetch` API and handles:
</TabsContent>
<TabsContent value="http">
## Prepare Payment
## Fetch with Payment
Fetch any x402-compatible API and automatically handle payment flows when APIs return a `402 Payment Required` response using the authenticated wallet.
@@ -27,7 +27,9 @@ You can view all transactions processed by your facilitator in your project dash
## Chain and token support
Our facilitator supports payments on any EVM chain, as long as the payment token supports either:
The thirdweb x402 client/server stack supports payments on [170+ EVM chains](https://thirdweb.com/chainlist?service=account-abstraction).
The payment token must support either:
- ERC-2612 permit (most ERC20 tokens)
- ERC-3009 sign with authorization (USDC on all chains)
+21
View File
@@ -0,0 +1,21 @@
import { createMetadata } from "@doc";
import { DocLayout } from "@/components/Layouts/DocLayout";
import { sidebar } from "./sidebar";
export default async function Layout(props: { children: React.ReactNode }) {
return (
<DocLayout editPageButton={true} sideBar={sidebar}>
{props.children}
</DocLayout>
);
}
export const metadata = createMetadata({
description:
"Instant payments for your APIs, websites, and autonomous agents. x402 is an open-source protocol that turns the HTTP 402 status code into a fully-featured, on-chain payment layer.",
image: {
icon: "payments",
title: "x402 Payments Docs",
},
title: "x402 Payments Documentation",
});
@@ -12,9 +12,8 @@ export const metadata = createMetadata({
# x402 Payments
x402 is an open-source protocol that turns the dormant HTTP 402 Payment Required status code into a fully-featured, on-chain payment layer for APIs, websites, and autonomous agents.
[x402](https://x402.org) is an open-source protocol that turns the dormant HTTP 402 Payment Required status code into a fully-featured, on-chain payment layer for APIs, websites, and autonomous agents.
Learn more about the protocol at [x402.org](https://x402.org).
<ArticleIconCard
title="x402 Playground"
@@ -23,6 +22,15 @@ Learn more about the protocol at [x402.org](https://x402.org).
href="https://playground.thirdweb.com/payments/x402"
/>
## Chain and token support
The thirdweb x402 client/server stack supports payments on [170+ EVM chains](https://thirdweb.com/chainlist?service=account-abstraction).
The payment token must support either:
- ERC-2612 permit (most ERC20 tokens)
- ERC-3009 sign with authorization (USDC on all chains)
## Client Side
`wrapFetchWithPayment` wraps the native fetch API to automatically handle `402 Payment Required` responses from any x402-compatible API:
@@ -42,7 +50,7 @@ const fetchWithPay = wrapFetchWithPayment(fetch, client, wallet);
const response = await fetchWithPay('https://api.example.com/paid-endpoint');
```
You can also use the thirdweb API to fetch any x402 compatible endpoint and pay for it with the authenticated wallet. See the [client side docs](/payments/x402/client) for more details.
You can also use the thirdweb API to fetch any x402 compatible endpoint and pay for it with the authenticated wallet. See the [client side docs](/x402/client) for more details.
## Server Side
@@ -88,7 +96,7 @@ export async function GET(request: Request) {
}
```
You can also create middlewares to handle payment for multiple endpoints, see the [server side docs](/payments/x402/server) for more details. The [facilitator](/payments/x402/facilitator) handles settling the payment onchain using your own server wallet.
You can also create middlewares to handle payment for multiple endpoints, see the [server side docs](/x402/server) for more details. The [facilitator](/x402/facilitator) handles settling the payment onchain using your own server wallet.
## Going Further
@@ -97,18 +105,18 @@ You can also create middlewares to handle payment for multiple endpoints, see th
title="Client Side"
description="Learn how to interact with x402-compatible APIs from your client applications"
icon={TypeScriptIcon}
href="/payments/x402/client"
href="/x402/client"
/>
<ArticleIconCard
title="Server Side"
description="Accept x402 payments in your APIs with middleware or dedicated endpoints"
icon={EngineIcon}
href="/payments/x402/server"
href="/x402/server"
/>
<ArticleIconCard
title="Facilitator API"
description="Configure the facilitator service to verify and settle payments"
icon={EngineIcon}
href="/payments/x402/facilitator"
href="/x402/facilitator"
/>
</div>
@@ -421,23 +421,31 @@ Protect multiple endpoints with a shared middleware:
</TabsContent>
</Tabs>
## Price Configuration
## Price and Token Configuration
You can specify prices in multiple ways:
### USD String
This will default to using USDC on the specified network.
```typescript
price: "$0.10" // 10 cents in USD
network: polygon, // or any other EVM chain
price: "$0.10" // 10 cents in USDC
```
### ERC20 Token
You can use any ERC20 token that supports the ERC-2612 permit or ERC-3009 sign with authorization.
Simply pass the amount in base units and the token address.
```typescript
network: arbitrum,
price: {
amount: "100000", // Amount in base units (100000 = 0.1 USDC with 6 decimals)
amount: "1000000000000000", // Amount in base units (0.001 tokens with 18 decimals)
asset: {
address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // Token address
address: "0xf01E52B0BAC3E147f6CAf956a64586865A0aA928", // Token address
}
}
```

Before

Width:  |  Height:  |  Size: 376 KiB

After

Width:  |  Height:  |  Size: 376 KiB

+77
View File
@@ -0,0 +1,77 @@
import { ExternalLinkIcon, ZapIcon } from "lucide-react";
import type { SideBar } from "@/components/Layouts/DocLayout";
import { EngineIcon, ReactIcon, TypeScriptIcon, UnityIcon } from "@/icons";
import { UnrealEngineIcon } from "../../icons/sdks/UnrealEngineIcon";
const x402Slug = "/x402";
export const sidebar: SideBar = {
links: [
{
href: x402Slug,
name: "Get Started",
icon: <ZapIcon />,
},
{
href: "https://playground.thirdweb.com/payments/x402",
icon: <ExternalLinkIcon />,
name: "Playground",
},
{ separator: true },
{
isCollapsible: false,
links: [
{
href: `${x402Slug}/client`,
name: "Client Side",
},
{
href: `${x402Slug}/server`,
name: "Server Side",
},
{
href: `${x402Slug}/agents`,
name: "Agents",
},
{
href: `${x402Slug}/facilitator`,
name: "Facilitator",
},
],
name: "Guides",
},
{ separator: true },
{
isCollapsible: false,
links: [
{
href: `https://payments.thirdweb.com/reference`,
icon: <EngineIcon />,
name: "REST API",
},
{
href: "/references/typescript/v5",
icon: <TypeScriptIcon />,
name: "TypeScript",
},
{
href: "/references/typescript/v5",
icon: <ReactIcon />,
name: "React",
},
{
href: "/unity",
icon: <UnityIcon />,
name: "Unity",
},
{
href: "/unreal-engine",
icon: <UnrealEngineIcon />,
name: "Unreal Engine",
},
],
name: "API References",
},
],
name: "x402",
};
@@ -15,6 +15,7 @@ const authOptions: InAppWalletAuth[] = [
"facebook",
"x",
"tiktok",
"epic",
"discord",
"telegram",
"twitch",
@@ -29,6 +29,9 @@ type AuthMethod =
| "telegram"
| "farcaster"
| "github"
| "line"
| "tiktok"
| "epic"
| "passkey"
| "wallet"
| "guest"
@@ -55,6 +58,9 @@ const authMethods: { id: AuthMethod; label: string; description: string }[] = [
{ id: "telegram", label: "Telegram", description: "Telegram OAuth" },
{ id: "farcaster", label: "Farcaster", description: "Farcaster OAuth" },
{ id: "github", label: "GitHub", description: "GitHub OAuth" },
{ id: "line", label: "Line", description: "Line OAuth" },
{ id: "tiktok", label: "TikTok", description: "TikTok OAuth" },
{ id: "epic", label: "Epic Games", description: "Epic Games OAuth" },
{ id: "passkey", label: "Passkey", description: "WebAuthn passkey" },
{
id: "wallet",
@@ -244,6 +250,9 @@ const account = await wallet.connect({
case "telegram":
case "farcaster":
case "github":
case "line":
case "tiktok":
case "epic":
return (
baseSetup +
`// ${authMethod.charAt(0).toUpperCase() + authMethod.slice(1)} OAuth
@@ -561,6 +570,9 @@ function PrebuiltUIExample() {
case "telegram":
case "farcaster":
case "github":
case "line":
case "tiktok":
case "epic":
return (
baseSetup +
` await wallet.connect({\n client,\n strategy: "${authMethod}",\n });\n` +
@@ -639,7 +651,10 @@ var address = await wallet.LoginWithOtp("123456");`
case "x":
case "telegram":
case "farcaster":
case "github": {
case "github":
case "line":
case "tiktok":
case "epic": {
const providerMap: Record<string, string> = {
google: "Google",
apple: "Apple",
@@ -649,6 +664,9 @@ var address = await wallet.LoginWithOtp("123456");`
telegram: "Telegram",
farcaster: "Farcaster",
github: "Github",
line: "Line",
tiktok: "TikTok",
epic: "Epic",
};
return (
baseSetup +
@@ -746,7 +764,10 @@ var wallet = await ThirdwebManager.Instance.ConnectWallet(options);`
case "x":
case "telegram":
case "farcaster":
case "github": {
case "github":
case "line":
case "tiktok":
case "epic": {
const providerMap: Record<string, string> = {
google: "Google",
apple: "Apple",
@@ -756,6 +777,9 @@ var wallet = await ThirdwebManager.Instance.ConnectWallet(options);`
telegram: "Telegram",
farcaster: "Farcaster",
github: "Github",
line: "Line",
tiktok: "TikTok",
epic: "Epic",
};
return (
baseSetup +
+32
View File
@@ -1,5 +1,37 @@
# wagmi-inapp
## 0.0.30
### Patch Changes
- Updated dependencies [[`f964b8a`](https://github.com/thirdweb-dev/js/commit/f964b8aaff22d4078ff4c4c5e09c6ebb5fcb182c), [`424e6b8`](https://github.com/thirdweb-dev/js/commit/424e6b8f9b071d3629ff0291ae6875fa4e4c882e), [`763d3de`](https://github.com/thirdweb-dev/js/commit/763d3de5a8db19a8022b2eda420f13a6787de100)]:
- thirdweb@5.111.10
- @thirdweb-dev/wagmi-adapter@0.2.185
## 0.0.29
### Patch Changes
- Updated dependencies [[`673e51b`](https://github.com/thirdweb-dev/js/commit/673e51b11cf2d06f00ab92bd4cbb45c51d95ec1f)]:
- thirdweb@5.111.9
- @thirdweb-dev/wagmi-adapter@0.2.184
## 0.0.28
### Patch Changes
- Updated dependencies [[`1f440d0`](https://github.com/thirdweb-dev/js/commit/1f440d0c97adf14d26d55666257b57541b2ef01a)]:
- thirdweb@5.111.8
- @thirdweb-dev/wagmi-adapter@0.2.183
## 0.0.27
### Patch Changes
- Updated dependencies [[`7f08161`](https://github.com/thirdweb-dev/js/commit/7f081618e16e7d5754a09780421e35b244186ce8), [`7f08161`](https://github.com/thirdweb-dev/js/commit/7f081618e16e7d5754a09780421e35b244186ce8)]:
- thirdweb@5.111.7
- @thirdweb-dev/wagmi-adapter@0.2.182
## 0.0.26
### Patch Changes
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "wagmi-inapp",
"private": true,
"version": "0.0.26",
"version": "0.0.30",
"type": "module",
"scripts": {
"dev": "vite",
+28
View File
@@ -1,5 +1,33 @@
# @thirdweb-dev/nebula
## 0.2.88
### Patch Changes
- Updated dependencies [[`f964b8a`](https://github.com/thirdweb-dev/js/commit/f964b8aaff22d4078ff4c4c5e09c6ebb5fcb182c), [`424e6b8`](https://github.com/thirdweb-dev/js/commit/424e6b8f9b071d3629ff0291ae6875fa4e4c882e), [`763d3de`](https://github.com/thirdweb-dev/js/commit/763d3de5a8db19a8022b2eda420f13a6787de100)]:
- thirdweb@5.111.10
## 0.2.87
### Patch Changes
- Updated dependencies [[`673e51b`](https://github.com/thirdweb-dev/js/commit/673e51b11cf2d06f00ab92bd4cbb45c51d95ec1f)]:
- thirdweb@5.111.9
## 0.2.86
### Patch Changes
- Updated dependencies [[`1f440d0`](https://github.com/thirdweb-dev/js/commit/1f440d0c97adf14d26d55666257b57541b2ef01a)]:
- thirdweb@5.111.8
## 0.2.85
### Patch Changes
- Updated dependencies [[`7f08161`](https://github.com/thirdweb-dev/js/commit/7f081618e16e7d5754a09780421e35b244186ce8), [`7f08161`](https://github.com/thirdweb-dev/js/commit/7f081618e16e7d5754a09780421e35b244186ce8)]:
- thirdweb@5.111.7
## 0.2.84
### Patch Changes
+1 -1
View File
@@ -57,5 +57,5 @@
"type": "module",
"types": "./dist/types/exports/thirdweb.d.ts",
"typings": "./dist/types/exports/thirdweb.d.ts",
"version": "0.2.84"
"version": "0.2.88"
}
@@ -1,5 +1,11 @@
# @thirdweb-dev/react-native-adapter
## 1.5.5
### Patch Changes
- [#8390](https://github.com/thirdweb-dev/js/pull/8390) [`f964b8a`](https://github.com/thirdweb-dev/js/commit/f964b8aaff22d4078ff4c4c5e09c6ebb5fcb182c) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Loosen peer dependency requirements for RN
## 1.5.4
### Patch Changes
+5 -5
View File
@@ -34,12 +34,12 @@
"peerDependencies": {
"@biomejs/biome": "2.0.6",
"@coinbase/wallet-mobile-sdk": "^1",
"@react-native-async-storage/async-storage": "^1 || ^2",
"@react-native-async-storage/async-storage": "*",
"@react-native-community/netinfo": "^11",
"@walletconnect/react-native-compat": "^2",
"expo-application": "^5 || ^6",
"expo-linking": "^6 || ^7",
"expo-web-browser": "^13 || ^14",
"expo-application": "*",
"expo-linking": "*",
"expo-web-browser": "*",
"react-native": ">=0.70",
"react-native-aes-gcm-crypto": "^0.2",
"react-native-get-random-values": "^1",
@@ -69,5 +69,5 @@
"type": "module",
"types": "./dist/types/exports/thirdweb.d.ts",
"typings": "./dist/types/exports/thirdweb.d.ts",
"version": "1.5.4"
"version": "1.5.5"
}
+30
View File
@@ -1,5 +1,35 @@
# thirdweb
## 5.111.10
### Patch Changes
- [#8390](https://github.com/thirdweb-dev/js/pull/8390) [`f964b8a`](https://github.com/thirdweb-dev/js/commit/f964b8aaff22d4078ff4c4c5e09c6ebb5fcb182c) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Loosen peer dependency requirements for RN
- [#8383](https://github.com/thirdweb-dev/js/pull/8383) [`424e6b8`](https://github.com/thirdweb-dev/js/commit/424e6b8f9b071d3629ff0291ae6875fa4e4c882e) Thanks [@kumaryash90](https://github.com/kumaryash90)! - deploy and install stylus modules
- [#8388](https://github.com/thirdweb-dev/js/pull/8388) [`763d3de`](https://github.com/thirdweb-dev/js/commit/763d3de5a8db19a8022b2eda420f13a6787de100) Thanks [@MananTank](https://github.com/MananTank)! - - Various UI/UX Improvements in Bridge components
## 5.111.9
### Patch Changes
- [#8384](https://github.com/thirdweb-dev/js/pull/8384) [`673e51b`](https://github.com/thirdweb-dev/js/commit/673e51b11cf2d06f00ab92bd4cbb45c51d95ec1f) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Replace celo alfajores with celoSepoliaTestnet
## 5.111.8
### Patch Changes
- [#8343](https://github.com/thirdweb-dev/js/pull/8343) [`1f440d0`](https://github.com/thirdweb-dev/js/commit/1f440d0c97adf14d26d55666257b57541b2ef01a) Thanks [@0xFirekeeper](https://github.com/0xFirekeeper)! - Added Epic Games Authentication
## 5.111.7
### Patch Changes
- [#8373](https://github.com/thirdweb-dev/js/pull/8373) [`7f08161`](https://github.com/thirdweb-dev/js/commit/7f081618e16e7d5754a09780421e35b244186ce8) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Respect from address when simulating with Engine.serverWallet
- [#8373](https://github.com/thirdweb-dev/js/pull/8373) [`7f08161`](https://github.com/thirdweb-dev/js/commit/7f081618e16e7d5754a09780421e35b244186ce8) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Expose useInvalidateBalances() react hook
## 5.111.6
### Patch Changes
+6 -6
View File
@@ -73,8 +73,8 @@
"dotenv-mono": "^1.3.14",
"ethers5": "npm:ethers@5",
"ethers6": "npm:ethers@6",
"expo-linking": "7.0.5",
"expo-web-browser": "14.0.2",
"expo-linking": "8.0.8",
"expo-web-browser": "15.0.9",
"happy-dom": "17.4.4",
"knip": "5.60.2",
"msw": "2.7.5",
@@ -256,10 +256,10 @@
"@aws-sdk/credential-providers": "^3",
"@coinbase/wallet-mobile-sdk": "^1",
"@mobile-wallet-protocol/client": "^1",
"@react-native-async-storage/async-storage": "^1 || ^2",
"@react-native-async-storage/async-storage": "*",
"ethers": "^5 || ^6",
"expo-linking": "^6 || ^7",
"expo-web-browser": "^13 || ^14",
"expo-linking": "*",
"expo-web-browser": "*",
"react": "^18 || ^19",
"react-native": "*",
"react-native-aes-gcm-crypto": "^0.2",
@@ -430,5 +430,5 @@
}
},
"typings": "./dist/types/exports/thirdweb.d.ts",
"version": "5.111.6"
"version": "5.111.10"
}
@@ -1,17 +0,0 @@
import { defineChain } from "../utils.js";
/**
* @chain
*/
export const celoAlfajoresTestnet = /* @__PURE__ */ defineChain({
blockExplorers: [
{
name: "Alfajoresscan",
url: "https://alfajores.celoscan.io",
},
],
id: 44787,
name: "Celo Alfajores Testnet",
nativeCurrency: { decimals: 18, name: "CELO", symbol: "CELO" },
testnet: true,
});
@@ -0,0 +1,17 @@
import { defineChain } from "../utils.js";
/**
* @chain
*/
export const celoSepoliaTestnet = /* @__PURE__ */ defineChain({
blockExplorers: [
{
name: "Celo Sepolia Explorer",
url: "https://celo-sepolia.blockscout.com/",
},
],
id: 11142220,
name: "Celo Sepolia Testnet",
nativeCurrency: { decimals: 18, name: "CELO", symbol: "CELO-S" },
testnet: true,
});
@@ -60,6 +60,12 @@ export async function createStylusProject() {
{ title: "Airdrop ERC1155", value: "airdrop1155" },
{ title: "ZK ERC721", value: "zk-erc721" },
{ title: "ZK ERC20", value: "zk-erc20" },
{ title: "Mint module - ERC20", value: "mintable20" },
{ title: "Transfer module - ERC20", value: "transferable20" },
{ title: "Mint module - ERC721", value: "mintable721" },
{ title: "Transfer module - ERC721", value: "transferable721" },
{ title: "Mint module - ERC1155", value: "mintable1155" },
{ title: "Transfer module - ERC1155", value: "transferable1155" },
],
message: "Select a template:",
name: "projectType",
@@ -133,6 +139,49 @@ export async function createStylusProject() {
newProject = spawnSync("git", ["clone", repoUrl, projectName], {
stdio: "inherit",
});
} else if (projectType === "mintable20") {
const repoUrl = "[email protected]:thirdweb-example/stylus-mintable-erc20.git";
spinner.start(`Creating new ERC20 Mintable module: ${projectName}...`);
newProject = spawnSync("git", ["clone", repoUrl, projectName], {
stdio: "inherit",
});
} else if (projectType === "transferable20") {
const repoUrl =
"[email protected]:thirdweb-example/stylus-transferable-erc20.git";
spinner.start(`Creating new ERC20 Transferable module: ${projectName}...`);
newProject = spawnSync("git", ["clone", repoUrl, projectName], {
stdio: "inherit",
});
} else if (projectType === "mintable721") {
const repoUrl =
"[email protected]:thirdweb-example/stylus-mintable-erc721.git";
spinner.start(`Creating new ERC721 Mintable module: ${projectName}...`);
newProject = spawnSync("git", ["clone", repoUrl, projectName], {
stdio: "inherit",
});
} else if (projectType === "transferable721") {
const repoUrl =
"[email protected]:thirdweb-example/stylus-transferable-erc721.git";
spinner.start(`Creating new ERC721 Transferable module: ${projectName}...`);
newProject = spawnSync("git", ["clone", repoUrl, projectName], {
stdio: "inherit",
});
} else if (projectType === "mintable1155") {
const repoUrl =
"[email protected]:thirdweb-example/stylus-mintable-erc1155.git";
spinner.start(`Creating new ERC1155 Mintable module: ${projectName}...`);
newProject = spawnSync("git", ["clone", repoUrl, projectName], {
stdio: "inherit",
});
} else if (projectType === "transferable1155") {
const repoUrl =
"[email protected]:thirdweb-example/stylus-transferable-erc1155.git";
spinner.start(
`Creating new ERC1155 Transferable module: ${projectName}...`,
);
newProject = spawnSync("git", ["clone", repoUrl, projectName], {
stdio: "inherit",
});
}
if (!newProject || newProject.status !== 0) {
@@ -9,48 +9,44 @@ import { getContract } from "../contract.js";
import { deployCloneFactory } from "./utils/bootstrap.js";
describe.runIf(process.env.TW_SECRET_KEY)("deploy dynamic", () => {
// TODO: Fix this test
it.skip.sequential(
"should deploy dynamic contract with extensions",
async () => {
await deployCloneFactory({
account: TEST_ACCOUNT_A,
chain: ANVIL_CHAIN,
client: TEST_CLIENT,
});
it.sequential("should deploy dynamic contract with extensions", async () => {
await deployCloneFactory({
account: TEST_ACCOUNT_A,
chain: ANVIL_CHAIN,
client: TEST_CLIENT,
});
const deployed = await deployPublishedContract({
account: TEST_ACCOUNT_A,
chain: ANVIL_CHAIN,
client: TEST_CLIENT,
contractId: "EvolvingNFT",
contractParams: {
contractURI: "",
defaultAdmin: TEST_ACCOUNT_A.address,
name: "Evolving nft",
royaltyBps: 0n,
royaltyRecipient: TEST_ACCOUNT_A.address,
saleRecipient: TEST_ACCOUNT_A.address,
symbol: "ENFT",
trustedForwarders: [],
},
});
const deployed = await deployPublishedContract({
account: TEST_ACCOUNT_A,
chain: ANVIL_CHAIN,
client: TEST_CLIENT,
contractId: "EvolvingNFT",
contractParams: {
contractURI: "",
defaultAdmin: TEST_ACCOUNT_A.address,
name: "Evolving nft",
royaltyBps: 0n,
royaltyRecipient: TEST_ACCOUNT_A.address,
saleRecipient: TEST_ACCOUNT_A.address,
symbol: "ENFT",
trustedForwarders: [],
},
});
expect(deployed).toBeDefined();
expect(deployed).toBeDefined();
const contract = getContract({
address: deployed,
chain: ANVIL_CHAIN,
client: TEST_CLIENT,
});
const contract = getContract({
address: deployed,
chain: ANVIL_CHAIN,
client: TEST_CLIENT,
});
const extensions = await readContract({
contract,
method: resolveMethod("getAllExtensions"),
params: [],
});
const extensions = await readContract({
contract,
method: resolveMethod("getAllExtensions"),
params: [],
});
expect(extensions.length).toEqual(3);
},
);
expect(extensions.length).toEqual(3);
});
});
@@ -332,6 +332,7 @@ export function serverWallet(options: ServerWalletOptions): ServerWallet {
if (args.simulate) {
serializedTransaction = await toSerializableTransaction({
transaction: args.transaction,
from: address,
});
} else {
const [to, data, value] = await Promise.all([
+1 -1
View File
@@ -24,7 +24,7 @@ export { blastSepolia } from "../chains/chain-definitions/blast-sepolia.js";
export { bsc } from "../chains/chain-definitions/bsc.js";
export { bscTestnet } from "../chains/chain-definitions/bsc-testnet.js";
export { celo } from "../chains/chain-definitions/celo.js";
export { celoAlfajoresTestnet } from "../chains/chain-definitions/celo-alfajores-testnet.js";
export { celoSepoliaTestnet } from "../chains/chain-definitions/celo-sepolia-testnet.js";
export { coreMainnet } from "../chains/chain-definitions/core-mainnet.js";
export { coreTestnet } from "../chains/chain-definitions/core-testnet.js";
export { cronos } from "../chains/chain-definitions/cronos.js";
@@ -22,6 +22,7 @@ export { useContractEvents } from "../react/core/hooks/contract/useContractEvent
// contract
export { useReadContract } from "../react/core/hooks/contract/useReadContract.js";
export { useWaitForReceipt } from "../react/core/hooks/contract/useWaitForReceipt.js";
export { useInvalidateBalances } from "../react/core/hooks/others/useInvalidateBalances.js";
export { useInvalidateContractQuery } from "../react/core/hooks/others/useInvalidateQueries.js";
export { useWalletBalance } from "../react/core/hooks/others/useWalletBalance.js";
export {
+1
View File
@@ -29,6 +29,7 @@ export { useReadContract } from "../react/core/hooks/contract/useReadContract.js
export { useWaitForReceipt } from "../react/core/hooks/contract/useWaitForReceipt.js";
// chain hooks
export { useChainMetadata } from "../react/core/hooks/others/useChainQuery.js";
export { useInvalidateBalances } from "../react/core/hooks/others/useInvalidateBalances.js";
export { useInvalidateContractQuery } from "../react/core/hooks/others/useInvalidateQueries.js";
export { useWalletBalance } from "../react/core/hooks/others/useWalletBalance.js";
export {
@@ -11,54 +11,50 @@ import { sendTransaction } from "../../../transaction/actions/send-transaction.j
import { installPublishedExtension } from "./installPublishedExtension.js";
describe.runIf(process.env.TW_SECRET_KEY)("install extension", () => {
// TODO: Fix this test
it.skip.sequential(
"should install extension to a dynamic contract",
async () => {
await deployCloneFactory({
account: TEST_ACCOUNT_A,
chain: ANVIL_CHAIN,
client: TEST_CLIENT,
});
it.sequential("should install extension to a dynamic contract", async () => {
await deployCloneFactory({
account: TEST_ACCOUNT_A,
chain: ANVIL_CHAIN,
client: TEST_CLIENT,
});
const deployed = await deployPublishedContract({
account: TEST_ACCOUNT_A,
chain: ANVIL_CHAIN,
client: TEST_CLIENT,
contractId: "EvolvingNFT",
contractParams: {
contractURI: "",
defaultAdmin: TEST_ACCOUNT_A.address,
name: "Evolving nft",
royaltyBps: 0n,
royaltyRecipient: TEST_ACCOUNT_A.address,
saleRecipient: TEST_ACCOUNT_A.address,
symbol: "ENFT",
trustedForwarders: [],
},
});
const deployed = await deployPublishedContract({
account: TEST_ACCOUNT_A,
chain: ANVIL_CHAIN,
client: TEST_CLIENT,
contractId: "EvolvingNFT",
contractParams: {
contractURI: "",
defaultAdmin: TEST_ACCOUNT_A.address,
name: "Evolving nft",
royaltyBps: 0n,
royaltyRecipient: TEST_ACCOUNT_A.address,
saleRecipient: TEST_ACCOUNT_A.address,
symbol: "ENFT",
trustedForwarders: [],
},
});
const contract = getContract({
address: deployed,
chain: ANVIL_CHAIN,
client: TEST_CLIENT,
});
const contract = getContract({
address: deployed,
chain: ANVIL_CHAIN,
client: TEST_CLIENT,
});
const transaction = installPublishedExtension({
account: TEST_ACCOUNT_A,
contract,
extensionName: "DirectListingsLogic",
});
const transaction = installPublishedExtension({
account: TEST_ACCOUNT_A,
contract,
extensionName: "DirectListingsLogic",
});
await sendTransaction({ account: TEST_ACCOUNT_A, transaction });
await sendTransaction({ account: TEST_ACCOUNT_A, transaction });
const extensions = await readContract({
contract,
method: resolveMethod("getAllExtensions"),
params: [],
});
const extensions = await readContract({
contract,
method: resolveMethod("getAllExtensions"),
params: [],
});
expect(extensions.length).toEqual(4);
},
);
expect(extensions.length).toEqual(4);
});
});
@@ -11,8 +11,7 @@ import { deployPublishedContract } from "../../prebuilts/deploy-published.js";
import { uninstallExtension } from "./uninstallExtension.js";
describe.runIf(process.env.TW_SECRET_KEY)("uninstall extension", () => {
// TODO: Fix this test
it.skip.sequential(
it.sequential(
"should uninstall extension from a dynamic contract",
async () => {
await deployCloneFactory({
@@ -1,6 +1,7 @@
import type { ThirdwebContract } from "../../../contract/contract.js";
import { getOrDeployInfraForPublishedContract } from "../../../contract/deployment/utils/bootstrap.js";
import type { Account } from "../../../wallets/interfaces/wallet.js";
import { deployPublishedContract } from "../../prebuilts/deploy-published.js";
import { installModule } from "../__generated__/IModularCore/write/installModule.js";
/**
@@ -49,17 +50,35 @@ export function installPublishedModule(options: InstallPublishedModuleOptions) {
return installModule({
asyncParams: async () => {
const deployedModule = await getOrDeployInfraForPublishedContract({
account,
chain: contract.chain,
client: contract.client,
constructorParams,
contractId: moduleName,
publisher,
});
let implementationAddress: string;
if (moduleName.toLowerCase().includes("stylus")) {
// TODO: switch to deterministic / create2 when available
implementationAddress = await deployPublishedContract({
account,
chain: contract.chain,
client: contract.client,
contractParams: constructorParams,
contractId: moduleName,
publisher,
});
} else {
const deployedModule = await getOrDeployInfraForPublishedContract({
account,
chain: contract.chain,
client: contract.client,
constructorParams,
contractId: moduleName,
publisher,
});
implementationAddress = deployedModule.implementationContract
.address as string;
}
return {
data: moduleData || "0x",
moduleContract: deployedModule.implementationContract.address as string,
moduleContract: implementationAddress,
};
},
contract,
@@ -215,7 +215,7 @@ export async function deployContractfromDeployMetadata(
if (
deployMetadata.routerType === "dynamic" &&
deployMetadata.defaultExtensions &&
!isZkSyncChain(chain)
!(await isZkSyncChain(chain))
) {
for (const e of deployMetadata.defaultExtensions) {
await getOrDeployInfraForPublishedContract({
@@ -0,0 +1,18 @@
import { useQueryClient } from "@tanstack/react-query";
import { invalidateWalletBalance } from "../../providers/invalidateWalletBalance.js";
/**
* Invalidate the balances for a given chainId. If no chainId is provided, invalidate all balances.
* @example
* ```ts
* const invalidateBalances = useInvalidateBalances();
* invalidateBalances();
* ```
*/
export function useInvalidateBalances() {
const queryClient = useQueryClient();
return ({ chainId }: { chainId?: number }) => {
invalidateWalletBalance(queryClient, chainId);
};
}
@@ -55,6 +55,10 @@ describe("walletIcon", () => {
expect(getSocialIcon("tiktok")).toBe(socialIcons.tiktok);
});
it("should return epic icon", () => {
expect(getSocialIcon("epic")).toBe(socialIcons.epic);
});
it("should return farcaster icon", () => {
expect(getSocialIcon("farcaster")).toBe(socialIcons.farcaster);
});
File diff suppressed because one or more lines are too long
@@ -11,6 +11,7 @@ import {
APPLE_ICON,
DISCORD_ICON,
EMAIL_ICON,
EPIC_ICON,
FACEBOOK_ICON,
FARCASTER_ICON,
GITHUB_ICON,
@@ -106,6 +107,8 @@ export function getAuthProviderImage(authProvider: string | null): string {
return X_ICON;
case "tiktok":
return TIKTOK_ICON;
case "epic":
return EPIC_ICON;
case "farcaster":
return FARCASTER_ICON;
case "telegram":
@@ -32,6 +32,7 @@ import {
APPLE_ICON,
COINBASE_ICON,
DISCORD_ICON,
EPIC_ICON,
FACEBOOK_ICON,
FARCASTER_ICON,
GITHUB_ICON,
@@ -69,6 +70,7 @@ const socialIcons = {
twitch: TWITCH_ICON,
x: X_ICON,
tiktok: TIKTOK_ICON,
epic: EPIC_ICON,
};
type InAppWalletFormUIProps = {
File diff suppressed because one or more lines are too long
@@ -219,7 +219,7 @@ export function TransactionPayment({
}}
>
{/* USD Value */}
<Text color="primaryText" size="xl" weight={600}>
<Text color="primaryText" size="xl" weight={500}>
{costToDisplay}
</Text>
@@ -82,7 +82,7 @@ export function TokenAndChain({
textOverflow: "ellipsis",
whiteSpace: "nowrap",
}}
weight={600}
weight={500}
trackingTight
>
{token.name}
@@ -68,18 +68,9 @@ export function TokenBalanceRow({
overflow: "hidden",
}}
>
<FiatValue
currency={currency}
chain={chain}
client={client}
<Text
color="primaryText"
size="sm"
token={token}
tokenAmount={amount}
/>
<Text
color="secondaryText"
size="xs"
style={{
overflow: "hidden",
textOverflow: "ellipsis",
@@ -91,6 +82,16 @@ export function TokenBalanceRow({
minimumFractionDigits: 0,
})} ${token.symbol}`}
</Text>
<FiatValue
currency={currency}
chain={chain}
client={client}
color="secondaryText"
size="sm"
token={token}
tokenAmount={amount}
/>
</Container>
</Container>
</StyledButton>
@@ -100,13 +100,23 @@ export function SelectedTokenButton(props: {
}}
>
<Img
src={props.chain?.icon}
src={props.chain?.icon || ""}
client={props.client}
width={iconSize.sm}
height={iconSize.sm}
style={{
borderRadius: radius.full,
}}
fallback={
<Container
style={{
background: `linear-gradient(45deg, white, ${theme.colors.accentText})`,
borderRadius: radius.full,
width: `${iconSize.sm}px`,
height: `${iconSize.sm}px`,
}}
/>
}
/>
</Container>
)}
@@ -63,7 +63,7 @@ export function PaymentOverview(props: {
<WalletRow
address={sender}
client={props.client}
iconSize="md"
iconSize="lg"
textSize="sm"
/>
</Container>
@@ -91,13 +91,14 @@ export function PaymentOverview(props: {
py="sm"
style={{ justifyContent: "space-between" }}
>
{/* left */}
<Container center="y" flex="row" gap="sm">
{getFiatCurrencyIcon({
currency: props.paymentMethod.currency,
size: "lg",
})}
<Container center="y" flex="column" gap="3xs">
<Text color="primaryText" size="sm" style={{ fontWeight: 600 }}>
<Text color="primaryText" size="sm" weight={500}>
{props.paymentMethod.currency}
</Text>
<Text color="secondaryText" size="xs">
@@ -106,7 +107,8 @@ export function PaymentOverview(props: {
</Text>
</Container>
</Container>
<Text color="primaryText" size="sm" style={{ fontWeight: 600 }}>
{/* right */}
<Text color="primaryText" size="sm">
{props.fromAmount}
</Text>
</Container>
@@ -136,11 +138,12 @@ export function PaymentOverview(props: {
<WalletRow
address={props.receiver}
client={props.client}
iconSize="md"
iconSize="lg"
textSize="sm"
/>
</Container>
)}
{props.modeInfo.mode === "direct_payment" && (
<Container
center="y"
@@ -150,7 +153,7 @@ export function PaymentOverview(props: {
style={{ justifyContent: "space-between" }}
>
<Container center="y" flex="column" gap="3xs" style={{ flex: 1 }}>
<Text color="primaryText" size="sm" style={{ fontWeight: 600 }}>
<Text color="primaryText" size="sm" weight={500}>
{props.metadata.title || "Payment"}
</Text>
{props.metadata.description && (
@@ -173,7 +176,6 @@ export function PaymentOverview(props: {
size="sm"
token={props.toToken}
tokenAmount={props.modeInfo.paymentInfo.amount}
weight={600}
/>
<Text color="secondaryText" size="xs">
{props.modeInfo.paymentInfo.amount} {props.toToken.symbol}
@@ -285,7 +287,7 @@ const TransactionOverViewCompact = (props: {
style={{ justifyContent: "space-between" }}
>
<Container center="y" flex="column" gap="3xs" style={{ flex: 1 }}>
<Text color="primaryText" size="sm" style={{ fontWeight: 600 }}>
<Text color="primaryText" size="sm" weight={500}>
{props.metadata.title || "Transaction"}
</Text>
{props.metadata.description && (
@@ -89,7 +89,7 @@ function PaymentMethodTokenRow({
<Text
color="primaryText"
size="sm"
style={{ fontWeight: 600, textWrap: "nowrap" }}
style={{ fontWeight: 500, textWrap: "nowrap" }}
>
{formatCurrencyAmount(
currency || "USD",
@@ -1,7 +1,5 @@
"use client";
import { CopyIcon } from "@radix-ui/react-icons";
import { useQuery } from "@tanstack/react-query";
import { useCallback } from "react";
import type { Token } from "../../../../../bridge/types/Token.js";
import type { ChainMetadata } from "../../../../../chains/types.js";
import {
@@ -10,22 +8,18 @@ import {
getChainMetadata,
} from "../../../../../chains/utils.js";
import { shortenHex } from "../../../../../utils/address.js";
import { formatExplorerTxUrl } from "../../../../../utils/url.js";
import type { WindowAdapter } from "../../../../core/adapters/WindowAdapter.js";
import { useCustomTheme } from "../../../../core/design-system/CustomThemeProvider.js";
import {
iconSize,
radius,
spacing,
} from "../../../../core/design-system/index.js";
import { radius, spacing } from "../../../../core/design-system/index.js";
import type { BridgePrepareResult } from "../../../../core/hooks/useBridgePrepare.js";
import type { CompletedStatusResult } from "../../../../core/hooks/useStepExecutor.js";
import { formatTokenAmount } from "../../ConnectWallet/screens/formatTokenBalance.js";
import { Container, ModalHeader } from "../../components/basic.js";
import { Container, Line, ModalHeader } from "../../components/basic.js";
import { shorterChainName } from "../../components/ChainName.js";
import { CopyIcon } from "../../components/CopyIcon.js";
import { Skeleton } from "../../components/Skeleton.js";
import { Spacer } from "../../components/Spacer.js";
import { Text } from "../../components/text.js";
import { Link, Text } from "../../components/text.js";
interface TransactionInfo {
type: "paymentId" | "transactionHash";
@@ -128,7 +122,6 @@ interface CompletedStepDetailCardProps {
status: CompletedStatusResult;
preparedQuote: BridgePrepareResult;
windowAdapter: WindowAdapter;
onCopyToClipboard: (text: string) => Promise<void>;
}
/**
@@ -137,29 +130,12 @@ interface CompletedStepDetailCardProps {
function CompletedStepDetailCard({
status,
preparedQuote,
windowAdapter,
onCopyToClipboard,
}: CompletedStepDetailCardProps) {
const theme = useCustomTheme();
const { data: txInfo, isLoading } = useTransactionInfo(status, preparedQuote);
const { data: txInfo, isPending } = useTransactionInfo(status, preparedQuote);
if (isLoading) {
return (
<Container
flex="column"
gap="sm"
style={{
backgroundColor: theme.colors.tertiaryBg,
border: `1px solid ${theme.colors.borderColor}`,
borderRadius: radius.sm,
padding: spacing.md,
}}
>
<Skeleton height="30px" />
<Skeleton height="30px" />
<Skeleton height="30px" />
</Container>
);
if (isPending) {
return <Skeleton height="200px" style={{ borderRadius: radius.lg }} />;
}
if (!txInfo) {
@@ -174,7 +150,7 @@ function CompletedStepDetailCard({
style={{
backgroundColor: theme.colors.tertiaryBg,
border: `1px solid ${theme.colors.borderColor}`,
borderRadius: radius.sm,
borderRadius: radius.lg,
padding: spacing.md,
}}
>
@@ -182,27 +158,37 @@ function CompletedStepDetailCard({
<Container
flex="row"
gap="sm"
py="4xs"
style={{
alignItems: "center",
justifyContent: "space-between",
}}
>
<Text color="primaryText" size="sm">
<Text color="primaryText" size="sm" weight={500}>
{txInfo.label}
</Text>
<Container
style={{
backgroundColor: theme.colors.success,
borderRadius: radius.sm,
backgroundColor: theme.colors.modalBg,
borderRadius: radius.full,
border: `1px solid ${theme.colors.borderColor}`,
padding: `${spacing["3xs"]} ${spacing.xs}`,
}}
>
<Text size="xs" style={{ color: theme.colors.primaryButtonText }}>
<Text
style={{
color: theme.colors.success,
fontSize: 10,
letterSpacing: "0.025em",
}}
>
COMPLETED
</Text>
</Container>
</Container>
<Line />
{/* Amount Paid */}
{txInfo.amountPaid && (
<Container
@@ -219,22 +205,6 @@ function CompletedStepDetailCard({
</Container>
)}
{/* Origin Chain */}
{txInfo.originChain && (
<Container
center="y"
flex="row"
style={{ justifyContent: "space-between" }}
>
<Text color="secondaryText" size="sm">
Origin Chain
</Text>
<Text color="primaryText" size="sm">
{shorterChainName(txInfo.originChain.name)}
</Text>
</Container>
)}
{/* Amount Received */}
{txInfo.amountReceived && (
<Container
@@ -251,14 +221,30 @@ function CompletedStepDetailCard({
</Container>
)}
{/* Chain */}
{/* Origin Chain */}
{txInfo.originChain && (
<Container
center="y"
flex="row"
style={{ justifyContent: "space-between" }}
>
<Text color="secondaryText" size="sm">
Origin Chain
</Text>
<Text color="primaryText" size="sm">
{shorterChainName(txInfo.originChain.name)}
</Text>
</Container>
)}
{/* Destination Chain */}
<Container
center="y"
flex="row"
style={{ justifyContent: "space-between" }}
>
<Text color="secondaryText" size="sm">
Chain
Destination Chain
</Text>
<Text color="primaryText" size="sm">
{shorterChainName(txInfo.chain.name)}
@@ -266,58 +252,59 @@ function CompletedStepDetailCard({
</Container>
{/* Transaction Info */}
<Container
center="y"
flex="row"
style={{ justifyContent: "space-between" }}
>
<Text color="secondaryText" size="sm">
{txInfo.type === "paymentId" ? "Payment ID" : "Transaction Hash"}
</Text>
<Container flex="row" gap="sm" style={{ alignItems: "center" }}>
<Text
color="accentText"
onClick={
txInfo.type === "paymentId"
? () => onCopyToClipboard(txInfo.id)
: () => {
const explorer = txInfo.chain.explorers?.[0];
if (explorer) {
windowAdapter.open(
formatExplorerTxUrl(explorer.url, txInfo.id),
);
}
}
}
size="sm"
style={{
cursor: "pointer",
fontFamily: "monospace",
}}
>
{shortenHex(txInfo.id)}
{txInfo.type === "paymentId" && (
<Container
center="y"
flex="row"
style={{ justifyContent: "space-between" }}
>
<Text color="secondaryText" size="sm">
Payment ID
</Text>
<Container flex="row" gap="3xs" center="y">
<CopyIcon text={txInfo.id} iconSize={12} tip="Copy Payment ID" />
{txInfo.type === "paymentId" ? (
<button
onClick={() => onCopyToClipboard(txInfo.id)}
style={{
background: "none",
border: "none",
cursor: "pointer",
padding: 0,
}}
type="button"
>
<CopyIcon
color={theme.colors.primaryText}
height={iconSize.sm}
width={iconSize.sm}
/>
</button>
) : null}
<Text color="primaryText" weight={400} size="sm">
{shortenHex(txInfo.id)}
</Text>
</Container>
</Container>
</Container>
)}
{status.transactions.map((tx) => {
const explorerLink = `https://thirdweb.com/${tx.chainId}/tx/${tx.transactionHash}`;
return (
<Container
key={tx.transactionHash}
center="y"
flex="row"
style={{ justifyContent: "space-between" }}
>
<Text color="secondaryText" size="sm">
Transaction Hash
</Text>
<Container flex="row" gap="3xs" center="y">
<CopyIcon
text={tx.transactionHash}
iconSize={12}
tip="Copy Transaction Hash"
/>
<Link
href={explorerLink}
target="_blank"
rel="noreferrer"
color="primaryText"
hoverColor="accentText"
weight={400}
size="sm"
>
{shortenHex(tx.transactionHash)}
</Link>
</Container>
</Container>
);
})}
</Container>
);
}
@@ -350,34 +337,23 @@ export function PaymentReceipt({
onBack,
windowAdapter,
}: PaymentReceitProps) {
// Copy to clipboard
const copyToClipboard = useCallback(async (text: string) => {
try {
await navigator.clipboard.writeText(text);
// Could add a toast notification here
} catch (error) {
console.warn("Failed to copy to clipboard:", error);
}
}, []);
return (
<Container
flex="column"
fullHeight
p="md"
style={{ maxHeight: "500px", minHeight: "250px", overflowY: "auto" }}
>
<Container flex="column" fullHeight px="md">
<Spacer y="md+" />
<ModalHeader onBack={onBack} title="Payment Receipt" />
<Spacer y="md+" />
<Spacer y="lg" />
<Container flex="column" gap="lg">
<Container
flex="column"
gap="lg"
scrollY
style={{ maxHeight: "500px", minHeight: "400px" }}
>
{/* Status Results */}
<Container flex="column" gap="md">
<Container flex="column" gap="sm">
{completedStatuses.map((status, index) => (
<CompletedStepDetailCard
key={`${status.type}-${index}`}
onCopyToClipboard={copyToClipboard}
preparedQuote={preparedQuote}
status={status}
windowAdapter={windowAdapter}
@@ -385,6 +361,7 @@ export function PaymentReceipt({
))}
</Container>
</Container>
<Spacer y="md+" />
</Container>
);
}
@@ -9,7 +9,7 @@ import { useCustomTheme } from "../../../../core/design-system/CustomThemeProvid
import { iconSize, spacing } from "../../../../core/design-system/index.js";
import type { BridgePrepareResult } from "../../../../core/hooks/useBridgePrepare.js";
import type { CompletedStatusResult } from "../../../../core/hooks/useStepExecutor.js";
import { Container, ModalHeader } from "../../components/basic.js";
import { Container } from "../../components/basic.js";
import { Button } from "../../components/buttons.js";
import { Spacer } from "../../components/Spacer.js";
import { Text } from "../../components/text.js";
@@ -91,9 +91,7 @@ export function SuccessScreen({
return (
<Container flex="column" fullHeight px="md" pb="md" pt="md+">
<ModalHeader title="Payment Complete" />
<Spacer y="xxl" />
<Spacer y="3xl" />
<Container center="x" flex="column" gap="md">
{/* Success Icon with Animation */}
@@ -130,7 +128,7 @@ export function SuccessScreen({
marginBottom: spacing.xxs,
}}
>
Payment Successful!
Payment Successful
</Text>
<Text center color="secondaryText" size="sm">
@@ -143,7 +141,7 @@ export function SuccessScreen({
</div>
</Container>
<Spacer y="xxl" />
<Spacer y="3xl" />
{/* Action Buttons */}
<Container flex="column" gap="sm" style={{ width: "100%" }}>
@@ -1,6 +1,7 @@
import { useMemo, useState } from "react";
import type { Chain as BridgeChain } from "../../../../../bridge/index.js";
import type { ThirdwebClient } from "../../../../../client/client.js";
import { useCustomTheme } from "../../../../core/design-system/CustomThemeProvider.js";
import {
fontSize,
iconSize,
@@ -170,6 +171,8 @@ function ChainButton(props: {
isSelected: boolean;
isMobile: boolean;
}) {
const theme = useCustomTheme();
const iconSizeValue = props.isMobile ? iconSize.lg : iconSize.md;
return (
<Button
variant={props.isSelected ? "secondary" : "ghost-solid"}
@@ -185,13 +188,25 @@ function ChainButton(props: {
gap="sm"
>
<Img
src={props.chain.icon}
src={props.chain.icon || ""}
client={props.client}
width={props.isMobile ? iconSize.lg : iconSize.md}
height={props.isMobile ? iconSize.lg : iconSize.md}
width={iconSizeValue}
height={iconSizeValue}
style={{
borderRadius: radius.full,
}}
fallback={
<Container color="secondaryText">
<Container
style={{
background: `linear-gradient(45deg, white, ${theme.colors.accentText})`,
borderRadius: radius.full,
width: `${iconSizeValue}px`,
height: `${iconSizeValue}px`,
}}
/>
</Container>
}
/>
{cleanedChainName(props.chain.name)}
</Button>
@@ -1,4 +1,4 @@
import { useMemo, useState } from "react";
import { useCallback, useMemo, useState } from "react";
import type { Token } from "../../../../../bridge/index.js";
import type { BridgeChain } from "../../../../../bridge/types/Chain.js";
import type { ThirdwebClient } from "../../../../../client/client.js";
@@ -10,8 +10,6 @@ import {
radius,
spacing,
} from "../../../../core/design-system/index.js";
import { CoinsIcon } from "../../ConnectWallet/icons/CoinsIcon.js";
import { WalletDotIcon } from "../../ConnectWallet/icons/WalletDotIcon.js";
import { Container, noScrollBar } from "../../components/basic.js";
import { Button } from "../../components/buttons.js";
import { Img } from "../../components/Img.js";
@@ -52,14 +50,18 @@ function findChain(chains: BridgeChain[], activeChainId: number | undefined) {
return chains.find((chain) => chain.chainId === activeChainId);
}
const ALL_TOKENS_LIMIT = 100;
const OWNED_TOKENS_LIMIT = 50;
/**
* @internal
*/
export function SelectToken(props: SelectTokenUIProps) {
const chainQuery = useBridgeChains(props.client);
const [search, setSearch] = useState("");
const [search, _setSearch] = useState("");
const debouncedSearch = useDebouncedValue(search, 500);
const [limit, setLimit] = useState(1000);
const [allTokensLimit, setAllTokensLimit] = useState(ALL_TOKENS_LIMIT);
const [ownedTokensLimit, setOwnedTokensLimit] = useState(OWNED_TOKENS_LIMIT);
const [_selectedChain, setSelectedChain] = useState<BridgeChain | undefined>(
undefined,
@@ -77,7 +79,7 @@ export function SelectToken(props: SelectTokenUIProps) {
client: props.client,
chainId: selectedChain?.chainId,
search: debouncedSearch,
limit,
limit: allTokensLimit,
offset: 0,
});
@@ -85,7 +87,7 @@ export function SelectToken(props: SelectTokenUIProps) {
const ownedTokensQuery = useTokenBalances({
clientId: props.client.clientId,
chainId: selectedChain?.chainId,
limit,
limit: ownedTokensLimit,
page: 1,
walletAddress: props.activeWalletInfo?.activeAccount.address,
});
@@ -102,43 +104,62 @@ export function SelectToken(props: SelectTokenUIProps) {
});
}, [ownedTokensQuery.data?.tokens, debouncedSearch]);
const isFetching = tokensQuery.isFetching || ownedTokensQuery.isFetching;
const setSearch = useCallback((search: string) => {
_setSearch(search);
setAllTokensLimit(ALL_TOKENS_LIMIT);
setOwnedTokensLimit(OWNED_TOKENS_LIMIT);
}, []);
return (
<SelectTokenUI
{...props}
ownedTokens={filteredOwnedTokens || []}
allTokens={tokensQuery.data || []}
isFetching={isFetching}
ownedTokens={{
data: filteredOwnedTokens || [],
isFetching: ownedTokensQuery.isFetching,
showMore: ownedTokensQuery.data?.pagination.hasMore
? () => {
setOwnedTokensLimit(ownedTokensLimit + OWNED_TOKENS_LIMIT);
}
: undefined,
}}
allTokens={{
data: tokensQuery.data || [],
isFetching: tokensQuery.isFetching,
showMore:
tokensQuery.data?.length === allTokensLimit
? () => {
setAllTokensLimit(allTokensLimit + ALL_TOKENS_LIMIT);
}
: undefined,
}}
selectedChain={selectedChain}
setSelectedChain={setSelectedChain}
search={search}
setSearch={setSearch}
selectedToken={props.selectedToken}
setSelectedToken={props.setSelectedToken}
showMore={
tokensQuery.data?.length === limit
? () => {
setLimit(limit * 2);
}
: undefined
}
/>
);
}
function SelectTokenUI(
props: SelectTokenUIProps & {
ownedTokens: TokenBalance[];
allTokens: Token[];
isFetching: boolean;
allTokens: {
data: Token[];
isFetching: boolean;
showMore: (() => void) | undefined;
};
ownedTokens: {
data: TokenBalance[];
isFetching: boolean;
showMore: (() => void) | undefined;
};
selectedChain: BridgeChain | undefined;
setSelectedChain: (chain: BridgeChain) => void;
search: string;
setSearch: (search: string) => void;
selectedToken: TokenSelection | undefined;
setSelectedToken: (token: TokenSelection) => void;
showMore: (() => void) | undefined;
},
) {
const isMobile = useIsMobile();
@@ -148,7 +169,7 @@ function SelectTokenUI(
// show tokens with icons first
const sortedOwnedTokens = useMemo(() => {
return props.ownedTokens.sort((a, b) => {
return props.ownedTokens.data.sort((a, b) => {
if (a.icon_uri && !b.icon_uri) {
return -1;
}
@@ -157,23 +178,11 @@ function SelectTokenUI(
}
return 0;
});
}, [props.ownedTokens]);
const otherTokens = useMemo(() => {
const ownedTokenSet = new Set(
sortedOwnedTokens.map((t) =>
`${t.token_address}-${t.chain_id}`.toLowerCase(),
),
);
return props.allTokens.filter(
(token) =>
!ownedTokenSet.has(`${token.address}-${token.chainId}`.toLowerCase()),
);
}, [props.allTokens, sortedOwnedTokens]);
}, [props.ownedTokens.data]);
// show tokens with icons first
const sortedOtherTokens = useMemo(() => {
return otherTokens.sort((a, b) => {
const sortedAllTokens = useMemo(() => {
return props.allTokens.data.sort((a, b) => {
if (a.iconUri && !b.iconUri) {
return -1;
}
@@ -182,7 +191,7 @@ function SelectTokenUI(
}
return 0;
});
}, [otherTokens]);
}, [props.allTokens.data]);
// desktop
if (!isMobile) {
@@ -214,15 +223,20 @@ function SelectTokenUI(
}}
isMobile={false}
selectedToken={props.selectedToken}
isFetching={props.isFetching}
ownedTokens={props.ownedTokens}
otherTokens={sortedOtherTokens}
showMore={props.showMore}
ownedTokens={{
...props.ownedTokens,
data: sortedOwnedTokens,
}}
allTokens={{
...props.allTokens,
data: sortedAllTokens,
}}
selectedChain={props.selectedChain}
onSelectChain={() => setScreen("select-chain")}
client={props.client}
search={props.search}
setSearch={props.setSearch}
isWalletConnected={props.activeWalletInfo !== undefined}
/>
</Container>
</Container>
@@ -232,15 +246,20 @@ function SelectTokenUI(
if (screen === "select-token") {
return (
<TokenSelectionScreen
isWalletConnected={props.activeWalletInfo !== undefined}
onSelectToken={(token) => {
props.setSelectedToken(token);
props.onClose();
}}
selectedToken={props.selectedToken}
isFetching={props.isFetching}
ownedTokens={props.ownedTokens}
otherTokens={sortedOtherTokens}
showMore={props.showMore}
ownedTokens={{
...props.ownedTokens,
data: sortedOwnedTokens,
}}
allTokens={{
...props.allTokens,
data: sortedAllTokens,
}}
selectedChain={props.selectedChain}
isMobile={true}
onSelectChain={() => setScreen("select-chain")}
@@ -428,17 +447,36 @@ function TokenSelectionScreen(props: {
client: ThirdwebClient;
search: string;
setSearch: (search: string) => void;
isFetching: boolean;
ownedTokens: TokenBalance[];
otherTokens: Token[];
showMore: (() => void) | undefined;
selectedToken: TokenSelection | undefined;
onSelectToken: (token: TokenSelection) => void;
isWalletConnected: boolean;
allTokens: {
data: Token[];
isFetching: boolean;
showMore: (() => void) | undefined;
};
ownedTokens: {
data: TokenBalance[];
isFetching: boolean;
showMore: (() => void) | undefined;
};
}) {
const noTokensFound =
!props.isFetching &&
props.otherTokens.length === 0 &&
props.ownedTokens.length === 0;
const noAllTokensFound =
!props.allTokens.isFetching && props.allTokens.data.length === 0;
const noOwnedTokensFound =
!props.ownedTokens.isFetching && props.ownedTokens.data.length === 0;
const [activeTab, setActiveTab] = useState<"owned" | "all">("all");
const showSkeletons =
(props.allTokens.isFetching && activeTab === "all") ||
(props.ownedTokens.isFetching && activeTab === "owned");
const showNoTokensFound =
(activeTab === "all" && noAllTokensFound) ||
(activeTab === "owned" && noOwnedTokensFound);
const theme = useCustomTheme();
return (
<Container fullHeight flex="column">
@@ -448,16 +486,14 @@ function TokenSelectionScreen(props: {
</Text>
<Spacer y="3xs" />
<Text
size="xs"
size="sm"
color="secondaryText"
multiline
style={{
textWrap: "pretty",
maxWidth: "70%",
}}
>
Select a token from the list or search for a token by symbol or
address
Select a token from the list or use the search
</Text>
</Container>
@@ -467,7 +503,7 @@ function TokenSelectionScreen(props: {
display: "flex",
alignItems: "center",
justifyContent: "center",
minHeight: "400px",
minHeight: "300px",
}}
>
<Spinner color="secondaryText" size="xl" />
@@ -498,7 +534,28 @@ function TokenSelectionScreen(props: {
/>
</Container>
<Spacer y="xs" />
<Spacer y="sm" />
{/* tabs */}
{props.isWalletConnected && (
<Container
flex="row"
gap="xs"
px="md"
style={{ borderBottom: `1px solid ${theme.colors.borderColor}` }}
>
<TabButton
isSelected={activeTab === "all"}
onSelect={() => setActiveTab("all")}
label="All Tokens"
/>
<TabButton
isSelected={activeTab === "owned"}
onSelect={() => setActiveTab("owned")}
label="Your Tokens"
/>
</Container>
)}
<Container
pb="md"
@@ -507,43 +564,24 @@ function TokenSelectionScreen(props: {
gap="xxs"
flex="column"
style={{
minHeight: "400px",
minHeight: "300px",
maxHeight: props.isMobile ? "450px" : "none",
overflowY: "auto",
scrollbarWidth: "none",
paddingBottom: spacing.md,
}}
>
{props.isFetching &&
{props.isWalletConnected && <Spacer y="xxs" />}
{showSkeletons &&
new Array(20).fill(0).map((_, i) => (
// biome-ignore lint/suspicious/noArrayIndexKey: ok
<TokenButtonSkeleton key={i} />
))}
{!props.isFetching && props.ownedTokens.length > 0 && (
<Container
px="xs"
py="xs"
flex="row"
gap="xs"
center="y"
color="secondaryText"
>
<WalletDotIcon size={iconSize.xs} />
<Text
size="sm"
color="secondaryText"
style={{
overflow: "unset",
}}
>
Your Tokens
</Text>
</Container>
)}
{!props.isFetching &&
props.ownedTokens.map((token) => (
{activeTab === "owned" &&
!props.ownedTokens.isFetching &&
props.ownedTokens.data.map((token) => (
<TokenButton
key={token.token_address}
token={token}
@@ -558,33 +596,9 @@ function TokenSelectionScreen(props: {
/>
))}
{!props.isFetching && props.ownedTokens.length > 0 && (
<Container
px="xs"
py="xs"
flex="row"
gap="xs"
center="y"
color="secondaryText"
style={{
marginTop: spacing.sm,
}}
>
<CoinsIcon size={iconSize.xs} />
<Text
size="sm"
color="secondaryText"
style={{
overflow: "unset",
}}
>
Other Tokens
</Text>
</Container>
)}
{!props.isFetching &&
props.otherTokens.map((token) => (
{activeTab === "all" &&
!props.allTokens.isFetching &&
props.allTokens.data.map((token) => (
<TokenButton
key={token.address}
token={token}
@@ -599,19 +613,31 @@ function TokenSelectionScreen(props: {
/>
))}
{props.showMore && (
{activeTab === "all" && props.allTokens.showMore && (
<Button
variant="secondary"
fullWidth
onClick={() => {
props.showMore?.();
props.allTokens.showMore?.();
}}
>
Load More
</Button>
)}
{noTokensFound && (
{activeTab === "owned" && props.ownedTokens.showMore && (
<Button
variant="secondary"
fullWidth
onClick={() => {
props.ownedTokens.showMore?.();
}}
>
Load More
</Button>
)}
{showNoTokensFound && (
<div
style={{
flex: 1,
@@ -620,7 +646,7 @@ function TokenSelectionScreen(props: {
justifyContent: "center",
}}
>
<Text size="md" color="secondaryText">
<Text size="sm" color="secondaryText">
No Tokens Found
</Text>
</div>
@@ -643,3 +669,45 @@ const LeftContainer = /* @__PURE__ */ StyledDiv((_) => {
position: "relative",
};
});
function TabButton(props: {
isSelected: boolean;
onSelect: () => void;
label: string;
}) {
const theme = useCustomTheme();
return (
<div
style={{
paddingBottom: "4px",
position: "relative",
}}
>
<Button
variant="ghost-solid"
onClick={props.onSelect}
style={{
fontSize: fontSize.sm,
padding: `10px ${spacing.xs}`,
color: props.isSelected
? theme.colors.primaryText
: theme.colors.secondaryText,
}}
>
{props.label}
</Button>
{props.isSelected && (
<div
style={{
position: "absolute",
left: 0,
right: 0,
bottom: "-1.5px",
borderBottom: `2px solid ${theme.colors.primaryText}`,
}}
></div>
)}
</div>
);
}
@@ -46,17 +46,19 @@ export function WalletRow(props: {
const addressOrENS = address
? ensNameQuery.data || shortenAddress(address)
: "";
const iconSizeValue = iconSize[props.iconSize || "md"];
return (
<Container flex="row" style={{ justifyContent: "space-between" }}>
<Container center="y" color="secondaryText" flex="row" gap="md">
<Container center="y" color="secondaryText" flex="row" gap="sm">
{wallet ? (
<WalletImage
client={props.client}
id={wallet.id}
size={iconSize[props.iconSize || "md"]}
size={iconSizeValue}
/>
) : (
<OutlineWalletIcon size={iconSize[props.iconSize || "md"]} />
<OutlineWalletIcon size={iconSizeValue} />
)}
<Container flex="column" gap="3xs">
{props.label ? (
@@ -49,7 +49,7 @@ function formatMoney(
return new Intl.NumberFormat(locale, {
style: "currency",
currency: currencyCode,
maximumFractionDigits: 5,
maximumFractionDigits: 2,
minimumFractionDigits: 0,
}).format(value);
}
@@ -1,7 +1,9 @@
"use client";
import { CheckIcon, CopyIcon as CopyIconSVG } from "@radix-ui/react-icons";
import { radius } from "../../../core/design-system/index.js";
import { useClipboard } from "../hooks/useCopyClipboard.js";
import { Container } from "./basic.js";
import { Button } from "./buttons.js";
import { ToolTip } from "./Tooltip.js";
/**
@@ -19,25 +21,22 @@ export const CopyIcon: React.FC<{
const showCheckIcon = props.hasCopied || hasCopied;
return (
// biome-ignore lint/a11y/noStaticElementInteractions: TODO
<div
<Button
onClick={onCopy}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
onCopy();
}
}}
variant="ghost-solid"
style={{
alignItems: "center",
display: "flex",
padding: 2,
justifyContent: "center",
borderRadius: radius.sm,
}}
>
<ToolTip align={props.align} side={props.side} tip={props.tip}>
<div>
<Container
center="both"
color={showCheckIcon ? "success" : undefined}
color={showCheckIcon ? "success" : "secondaryText"}
flex="row"
>
{showCheckIcon ? (
@@ -56,6 +55,6 @@ export const CopyIcon: React.FC<{
</Container>
</div>
</ToolTip>
</div>
</Button>
);
};
@@ -8,6 +8,7 @@ export const Spacer: React.FC<{ y: keyof typeof spacing }> = ({ y }) => {
<div
style={{
height: spacing[y],
flexShrink: 0,
}}
/>
);
@@ -119,6 +119,7 @@ export const ConnectWalletSocialOptions = (
twitch: "Twitch",
x: "X",
tiktok: "TikTok",
epic: "Epic",
};
const { data: ecosystemAuthOptions, isLoading } = useQuery({
@@ -44,6 +44,7 @@ function getOauthLoginPath(
case "line":
case "x":
case "tiktok":
case "epic":
case "guest":
case "coinbase":
case "discord":
@@ -89,6 +89,7 @@ export function AllInAppWalletAuthMethods() {
"discord",
"x",
"tiktok",
"epic",
"coinbase",
"farcaster",
"telegram",
@@ -103,6 +103,7 @@ export type AuthProvider =
| "Coinbase"
| "X"
| "TikTok"
| "Epic"
| "Line"
| "Twitch"
| "Steam"
@@ -200,6 +200,7 @@ export class InAppNativeConnector implements InAppConnector {
case "line":
case "x":
case "tiktok":
case "epic":
case "apple": {
const { socialAuth } = await import("./auth/native-auth.js");
const ExpoLinking = require("expo-linking");
@@ -338,6 +338,7 @@ export class InAppWebConnector implements InAppConnector {
case "line":
case "x":
case "tiktok":
case "epic":
case "steam":
case "coinbase":
case "discord": {
@@ -412,6 +413,7 @@ export class InAppWebConnector implements InAppConnector {
case "line":
case "x":
case "tiktok":
case "epic":
case "guest":
case "coinbase":
case "twitch":
+1
View File
@@ -27,6 +27,7 @@ export const socialAuthOptions = [
"line",
"x",
"tiktok",
"epic",
"coinbase",
"farcaster",
"telegram",
+8
View File
@@ -1,5 +1,13 @@
# @thirdweb-dev/wagmi-adapter
## 0.2.185
## 0.2.184
## 0.2.183
## 0.2.182
## 0.2.181
## 0.2.180
+1 -1
View File
@@ -56,5 +56,5 @@
"type": "module",
"types": "./dist/types/exports/thirdweb.d.ts",
"typings": "./dist/types/exports/thirdweb.d.ts",
"version": "0.2.181"
"version": "0.2.185"
}
+71 -100
View File
@@ -1317,9 +1317,9 @@ importers:
version: 1.1.2([email protected](@babel/[email protected])([email protected])([email protected])([email protected](@babel/[email protected])(@babel/[email protected](@babel/[email protected]))(@types/[email protected])([email protected])([email protected])([email protected]))([email protected])([email protected]))([email protected](@babel/[email protected])(@babel/[email protected](@babel/[email protected]))(@types/[email protected])([email protected])([email protected])([email protected]))([email protected])
'@mobile-wallet-protocol/client':
specifier: 1.0.0
version: 1.0.0(owhh2bkjdc6c7756g5hiphjqv4)
version: 1.0.0(bg6kixppm6cc5zeazzvnc2tera)
'@react-native-async-storage/async-storage':
specifier: ^1 || ^2
specifier: '*'
version: 2.2.0([email protected](@babel/[email protected])(@babel/[email protected](@babel/[email protected]))(@types/[email protected])([email protected])([email protected])([email protected]))
'@react-native-community/netinfo':
specifier: ^11
@@ -1328,14 +1328,14 @@ importers:
specifier: ^2
version: 2.17.3(rbspwdd7ndsri6hcpfkczthinq)
expo-application:
specifier: ^5 || ^6
specifier: '*'
version: 6.0.1([email protected](@babel/[email protected])([email protected])([email protected])([email protected](@babel/[email protected])(@babel/[email protected](@babel/[email protected]))(@types/[email protected])([email protected])([email protected])([email protected]))([email protected])([email protected]))
expo-linking:
specifier: ^6 || ^7
version: 7.0.5([email protected](@babel/[email protected])([email protected])([email protected])([email protected](@babel/[email protected])(@babel/[email protected](@babel/[email protected]))(@types/[email protected])([email protected])([email protected])([email protected]))([email protected])([email protected]))([email protected](@babel/[email protected])(@babel/[email protected](@babel/[email protected]))(@types/[email protected])([email protected])([email protected])([email protected]))([email protected])
specifier: '*'
version: 8.0.8([email protected](@babel/[email protected])([email protected])([email protected])([email protected](@babel/[email protected])(@babel/[email protected](@babel/[email protected]))(@types/[email protected])([email protected])([email protected])([email protected]))([email protected])([email protected]))([email protected](@babel/[email protected])(@babel/[email protected](@babel/[email protected]))(@types/[email protected])([email protected])([email protected])([email protected]))([email protected])
expo-web-browser:
specifier: ^13 || ^14
version: 14.0.2([email protected](@babel/[email protected])([email protected])([email protected])([email protected](@babel/[email protected])(@babel/[email protected](@babel/[email protected]))(@types/[email protected])([email protected])([email protected])([email protected]))([email protected])([email protected]))([email protected](@babel/[email protected])(@babel/[email protected](@babel/[email protected]))(@types/[email protected])([email protected])([email protected])([email protected]))
specifier: '*'
version: 15.0.9([email protected](@babel/[email protected])([email protected])([email protected])([email protected](@babel/[email protected])(@babel/[email protected](@babel/[email protected]))(@types/[email protected])([email protected])([email protected])([email protected]))([email protected])([email protected]))([email protected](@babel/[email protected])(@babel/[email protected](@babel/[email protected]))(@types/[email protected])([email protected])([email protected])([email protected]))
react-native:
specifier: '>=0.70'
version: 0.78.1(@babel/[email protected])(@babel/[email protected](@babel/[email protected]))(@types/[email protected])([email protected])([email protected])([email protected])
@@ -1512,7 +1512,7 @@ importers:
version: 1.1.2([email protected](@babel/[email protected])([email protected])([email protected])([email protected](@babel/[email protected])(@babel/[email protected](@babel/[email protected]))(@types/[email protected])([email protected])([email protected])([email protected]))([email protected])([email protected]))([email protected](@babel/[email protected])(@babel/[email protected](@babel/[email protected]))(@types/[email protected])([email protected])([email protected])([email protected]))([email protected])
'@mobile-wallet-protocol/client':
specifier: 1.0.0
version: 1.0.0(owhh2bkjdc6c7756g5hiphjqv4)
version: 1.0.0(bg6kixppm6cc5zeazzvnc2tera)
'@react-native-async-storage/async-storage':
specifier: 2.2.0
version: 2.2.0([email protected](@babel/[email protected])(@babel/[email protected](@babel/[email protected]))(@types/[email protected])([email protected])([email protected])([email protected]))
@@ -1580,11 +1580,11 @@ importers:
specifier: npm:ethers@6
version: [email protected]([email protected])([email protected])
expo-linking:
specifier: 7.0.5
version: 7.0.5([email protected](@babel/[email protected])([email protected])([email protected])([email protected](@babel/[email protected])(@babel/[email protected](@babel/[email protected]))(@types/[email protected])([email protected])([email protected])([email protected]))([email protected])([email protected]))([email protected](@babel/[email protected])(@babel/[email protected](@babel/[email protected]))(@types/[email protected])([email protected])([email protected])([email protected]))([email protected])
specifier: 8.0.8
version: 8.0.8([email protected](@babel/[email protected])([email protected])([email protected])([email protected](@babel/[email protected])(@babel/[email protected](@babel/[email protected]))(@types/[email protected])([email protected])([email protected])([email protected]))([email protected])([email protected]))([email protected](@babel/[email protected])(@babel/[email protected](@babel/[email protected]))(@types/[email protected])([email protected])([email protected])([email protected]))([email protected])
expo-web-browser:
specifier: 14.0.2
version: 14.0.2([email protected](@babel/[email protected])([email protected])([email protected])([email protected](@babel/[email protected])(@babel/[email protected](@babel/[email protected]))(@types/[email protected])([email protected])([email protected])([email protected]))([email protected])([email protected]))([email protected](@babel/[email protected])(@babel/[email protected](@babel/[email protected]))(@types/[email protected])([email protected])([email protected])([email protected]))
specifier: 15.0.9
version: 15.0.9([email protected](@babel/[email protected])([email protected])([email protected])([email protected](@babel/[email protected])(@babel/[email protected](@babel/[email protected]))(@types/[email protected])([email protected])([email protected])([email protected]))([email protected])([email protected]))([email protected](@babel/[email protected])(@babel/[email protected](@babel/[email protected]))(@types/[email protected])([email protected])([email protected])([email protected]))
happy-dom:
specifier: 17.4.4
version: 17.4.4
@@ -3473,30 +3473,30 @@ packages:
'@expo/[email protected]':
resolution: {integrity: sha512-IMYCxBOcnuFStuK0Ay+FzEIBKrwW8OVUMc65+v0+i7YFIIe8aL342l7T4F8lR4oCfhXn7d6M5QPgXvjtc/gAcw==}
'@expo/config-plugins@9.0.17':
resolution: {integrity: sha512-m24F1COquwOm7PBl5wRbkT9P9DviCXe0D7S7nQsolfbhdCWuvMkfXeoWmgjtdhy7sDlOyIgBrAdnB6MfsWKqIg==}
'@expo/[email protected]':
resolution: {integrity: sha512-AMDeuDLHXXqd8W+0zSjIt7f37vUd/BP8p43k68NHpyAvQO+z8mbQZm3cNQVAMySeayK2XoPigAFB1JF2NFajaA==}
'@expo/config-plugins@54.0.2':
resolution: {integrity: sha512-jD4qxFcURQUVsUFGMcbo63a/AnviK8WUGard+yrdQE3ZrB/aurn68SlApjirQQLEizhjI5Ar2ufqflOBlNpyPg==}
'@expo/[email protected]':
resolution: {integrity: sha512-kqZ0w44E+HEGBjy+Lpyn0BVL5UANg/tmNixxaRMLS6nf37YsDrLk2VMAmeKMMk5CKG0NmOdVv3ngeUjRQMsy9g==}
'@expo/config@10.0.11':
resolution: {integrity: sha512-nociJ4zr/NmbVfMNe9j/+zRlt7wz/siISu7PjdWE4WE+elEGxWWxsGzltdJG0llzrM+khx8qUiFK5aiVcdMBww==}
'@expo/config-types@54.0.8':
resolution: {integrity: sha512-lyIn/x/Yz0SgHL7IGWtgTLg6TJWC9vL7489++0hzCHZ4iGjVcfZmPTUfiragZ3HycFFj899qN0jlhl49IHa94A==}
'@expo/[email protected]':
resolution: {integrity: sha512-TnGb4u/zUZetpav9sx/3fWK71oCPaOjZHoVED9NaEncktAd0Eonhq5NUghiJmkUGt3gGSjRAEBXiBbbY9/B1LA==}
'@expo/[email protected]':
resolution: {integrity: sha512-lJMof5Nqakq1DxGYlghYB/ogSBjmv4Fxn1ovyDmcjlRsQdFCXgu06gEUogkhPtc9wBt9WlTTfqENln5HHyLW6w==}
'@expo/[email protected]':
resolution: {integrity: sha512-Uilcv3xGELD5t/b0eM4cxBFEKQRIivB3v7i+VhWLV/gL98aw810unLKKJbGAxAIhY6Ipyz8ChWibFsKFXYwstA==}
'@expo/[email protected]':
resolution: {integrity: sha512-TgbCgvSk0Kq0e2fLoqHwEBL4M0ztFjnBEz0YCDm5boc1nvkV1VMuIMteVdeBwnTh8Z0oPJTwHCD49vhMEt1I6A==}
'@expo/[email protected]':
resolution: {integrity: sha512-qSTEnwvuYJ3umapO9XJtrb1fAqiPlmUUg78N0IZXXGwQRt+bkp0OBls+Y5Mxw/Owj8waAM0Z3huKKskRADR5ow==}
'@expo/[email protected]':
resolution: {integrity: sha512-BNETbLEohk3HQ2LxwwezpG8pq+h7Fs7/vAMP3eAtFT1BCpprLYoBBFZH7gW4aqGfqOcVP4Lc91j014verrYNGg==}
'@expo/[email protected]':
resolution: {integrity: sha512-MYfPYBTMfrrNr07DALuLhG6EaLVNVrY/PXjEzsjWdWE4ZFn0yqI0IdHNkJG7t1gePT8iztHc7qnsx+oo/rDo6w==}
hasBin: true
@@ -3504,8 +3504,8 @@ packages:
'@expo/[email protected]':
resolution: {integrity: sha512-GKnMqC79+mo/1AFrmAcUcGfbsXXTRqOMNS1umebuevl3aaw+ztsYEFEiuNhHZW7PQ3Xs3URNT513ZxKhznDscw==}
'@expo/json-file@9.0.2':
resolution: {integrity: sha512-yAznIUrybOIWp3Uax7yRflB0xsEpvIwIEqIjao9SGi2Gaa+N0OamWfe0fnXBSWF+2zzF4VvqwT4W5zwelchfgw==}
'@expo/json-file@10.0.7':
resolution: {integrity: sha512-z2OTC0XNO6riZu98EjdNHC05l51ySeTto6GP7oSQrCvQgG9ARBwD1YvMQaVZ9wU7p/4LzSf1O7tckL3B45fPpw==}
'@expo/[email protected]':
resolution: {integrity: sha512-prWBhLUlmcQtvN6Y7BpW2k9zXGd3ySa3R6rAguMJkp1z22nunLN64KYTUWfijFlprFoxm9r2VNnGkcbndAlgKA==}
@@ -3520,12 +3520,12 @@ packages:
'@expo/[email protected]':
resolution: {integrity: sha512-gcdICLuL+nHKZagPIDC5tX8UoDDB8vNA5/+SaQEqz8D+T2C4KrEJc2Vi1gPAlDnKif834QS6YluHWyxjk0yZlQ==}
'@expo/[email protected]':
resolution: {integrity: sha512-ZZGvTO6vEWq02UAPs3LIdja+HRO18+LRI5QuDl6Hs3Ps7KX7xU6Y6kjahWKY37Rx2YjNpX07dGpBFzzC+vKa2g==}
'@expo/[email protected]':
resolution: {integrity: sha512-9RYVU1iGyCJ7vWfg3e7c/NVyMFs8wbl+dMWZphtFtsqyN9zppGREU3ctlD3i8KUE0sCUTVnLjCWr+VeUIDep2g==}
'@expo/[email protected]':
resolution: {integrity: sha512-dGxqHPvCZKeRKDU1sJZMmuyVtcASuSYh1LPFVaM1DuffqPL36n6FMEL0iUqq2Tx3xhWk8wCnWl34IKplUjJDdA==}
'@expo/[email protected]':
resolution: {integrity: sha512-0DsxhhixRbCCvmYskBTq8czsU0YOBsntYURhWPNpkl0IPVpeP9haE5W4OwtHGzXEbmHdzaoDwNmVcWjS/mqbDw==}
@@ -8359,11 +8359,6 @@ packages:
'@webassemblyjs/[email protected]':
resolution: {integrity: sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==}
'@xmldom/[email protected]':
resolution: {integrity: sha512-lm2GW5PkosIzccsaZIz7tp8cPADSIlIHWDFTR1N0SzfinhhYgeIQjFMz4rYzanCScr3DqQLeomUDArp6MWKm+g==}
engines: {node: '>=10.0.0'}
deprecated: this version is no longer supported, please update to at least 0.8.*
'@xmldom/[email protected]':
resolution: {integrity: sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==}
engines: {node: '>=10.0.0'}
@@ -10561,14 +10556,14 @@ packages:
react: '*'
react-native: '*'
expo-constants@17.0.8:
resolution: {integrity: sha512-XfWRyQAf1yUNgWZ1TnE8pFBMqGmFP5Gb+SFSgszxDdOoheB/NI5D4p7q86kI2fvGyfTrxAe+D+74nZkfsGvUlg==}
expo-constants@17.1.7:
resolution: {integrity: sha512-byBjGsJ6T6FrLlhOBxw4EaiMXrZEn/MlUYIj/JAd+FS7ll5X/S4qVRbIimSJtdW47hXMq0zxPfJX6njtA56hHA==}
peerDependencies:
expo: '*'
react-native: '*'
expo-constants@17.1.7:
resolution: {integrity: sha512-byBjGsJ6T6FrLlhOBxw4EaiMXrZEn/MlUYIj/JAd+FS7ll5X/S4qVRbIimSJtdW47hXMq0zxPfJX6njtA56hHA==}
expo-constants@18.0.10:
resolution: {integrity: sha512-Rhtv+X974k0Cahmvx6p7ER5+pNhBC0XbP1lRviL2J1Xl4sT2FBaIuIxF/0I0CbhOsySf0ksqc5caFweAy9Ewiw==}
peerDependencies:
expo: '*'
react-native: '*'
@@ -10591,8 +10586,8 @@ packages:
expo: '*'
react: '*'
expo-linking@7.0.5:
resolution: {integrity: sha512-3KptlJtcYDPWohk0MfJU75MJFh2ybavbtcSd84zEPfw9s1q3hjimw3sXnH03ZxP54kiEWldvKmmnGcVffBDB1g==}
expo-linking@8.0.8:
resolution: {integrity: sha512-MyeMcbFDKhXh4sDD1EHwd0uxFQNAc6VCrwBkNvvvufUsTYFq3glTA9Y8a+x78CPpjNqwNAamu74yIaIz7IEJyg==}
peerDependencies:
react: '*'
react-native: '*'
@@ -10604,8 +10599,8 @@ packages:
[email protected]:
resolution: {integrity: sha512-RCb0wniYCJkxwpXrkiBA/WiNGxzYsEpL0sB50gTnS/zEfX3DImS2npc4lfZ3hPZo1UF9YC6OSI9Do+iacV0NUg==}
expo-web-browser@14.0.2:
resolution: {integrity: sha512-Hncv2yojhTpHbP6SGWARBFdl7P6wBHc1O8IKaNsH0a/IEakq887o1eRhLxZ5IwztPQyRDhpqHdgJ+BjWolOnwA==}
expo-web-browser@15.0.9:
resolution: {integrity: sha512-Dj8kNFO+oXsxqCDNlUT/GhOrJnm10kAElH++3RplLydogFm5jTzXYWDEeNIDmV+F+BzGYs+sIhxiBf7RyaxXZg==}
peerDependencies:
expo: '*'
react-native: '*'
@@ -11026,10 +11021,6 @@ packages:
resolution: {integrity: sha512-E1b1lFFLvLgak2whF2xDBcOy6NLVGZBqqjJjsIhvopKfWWEi64pLVTWWehV8KlLerZkfNTA95sTe2OdJKm1OzQ==}
engines: {node: '>= 14'}
[email protected]:
resolution: {integrity: sha512-7yetJWqbS9sbn0vIfliPsFgoXMKn/YMF+Wuiog97x+urnSRRRZ7xB+uVkwGKzRgq9CDFfMQnE9ruL5DHv9c6Xg==}
engines: {node: '>=6'}
[email protected]:
resolution: {integrity: sha512-VilgtJj/ALgGY77fiLam5iD336eSWi96Q15JSAG1zi8NRBysm3LXKdGnHb4m5cuyxvOLQQKWpBZAT6ni4FI2iQ==}
engines: {node: '>=6'}
@@ -16378,9 +16369,6 @@ packages:
[email protected]:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
[email protected]:
resolution: {integrity: sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==}
[email protected]:
resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==}
engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
@@ -16481,10 +16469,6 @@ packages:
resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==}
engines: {node: '>=4.0'}
[email protected]:
resolution: {integrity: sha512-ts+B2rSe4fIckR6iquDjsKbQFK2NlUk6iG5nf14mDEyldgoc2nEKZ3jZWMPTxGQwVgToSjt6VGIho1H8/fNFTg==}
engines: {node: '>=8.0'}
[email protected]:
resolution: {integrity: sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==}
engines: {node: '>=8.0'}
@@ -19715,15 +19699,15 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@expo/config-plugins@9.0.17':
'@expo/config-plugins@54.0.2':
dependencies:
'@expo/config-types': 52.0.5
'@expo/json-file': 9.0.2
'@expo/plist': 0.2.2
'@expo/config-types': 54.0.8
'@expo/json-file': 10.0.7
'@expo/plist': 0.4.7
'@expo/sdk-runtime-versions': 1.0.0
chalk: 4.1.2
debug: 4.4.1
getenv: 1.0.0
getenv: 2.0.0
glob: 10.4.5
resolve-from: 5.0.0
semver: 7.7.2
@@ -19734,18 +19718,18 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@expo/[email protected]': {}
'@expo/[email protected]': {}
'@expo/config@10.0.11':
'@expo/config[email protected]': {}
'@expo/[email protected]':
dependencies:
'@babel/code-frame': 7.10.4
'@expo/config-plugins': 9.0.17
'@expo/config-types': 52.0.5
'@expo/config-plugins': 10.1.2
'@expo/config-types': 53.0.5
'@expo/json-file': 9.1.5
deepmerge: 4.3.1
getenv: 1.0.0
getenv: 2.0.0
glob: 10.4.5
require-from-string: 2.0.2
resolve-from: 5.0.0
@@ -19756,12 +19740,12 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@expo/config@11.0.13':
'@expo/config@12.0.10':
dependencies:
'@babel/code-frame': 7.10.4
'@expo/config-plugins': 10.1.2
'@expo/config-types': 53.0.5
'@expo/json-file': 9.1.5
'@expo/config-plugins': 54.0.2
'@expo/config-types': 54.0.8
'@expo/json-file': 10.0.7
deepmerge: 4.3.1
getenv: 2.0.0
glob: 10.4.5
@@ -19782,17 +19766,17 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@expo/env@0.4.2':
'@expo/env@1.0.7':
dependencies:
chalk: 4.1.2
debug: 4.4.1
dotenv: 16.4.7
dotenv-expand: 11.0.7
getenv: 1.0.0
getenv: 2.0.0
transitivePeerDependencies:
- supports-color
'@expo/env@1.0.7':
'@expo/env@2.0.7':
dependencies:
chalk: 4.1.2
debug: 4.4.1
@@ -19831,11 +19815,10 @@ snapshots:
temp-dir: 2.0.0
unique-string: 2.0.0
'@expo/json-file@9.0.2':
'@expo/json-file@10.0.7':
dependencies:
'@babel/code-frame': 7.10.4
json5: 2.2.3
write-file-atomic: 2.4.3
'@expo/[email protected]':
dependencies:
@@ -19880,18 +19863,18 @@ snapshots:
ora: 3.4.0
resolve-workspace-root: 2.0.0
'@expo/[email protected]':
dependencies:
'@xmldom/xmldom': 0.7.13
base64-js: 1.5.1
xmlbuilder: 14.0.0
'@expo/[email protected]':
dependencies:
'@xmldom/xmldom': 0.8.10
base64-js: 1.5.1
xmlbuilder: 15.1.1
'@expo/[email protected]':
dependencies:
'@xmldom/xmldom': 0.8.10
base64-js: 1.5.1
xmlbuilder: 15.1.1
'@expo/[email protected]':
dependencies:
'@expo/config': 11.0.13
@@ -20785,7 +20768,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@mobile-wallet-protocol/[email protected](owhh2bkjdc6c7756g5hiphjqv4)':
'@mobile-wallet-protocol/[email protected](bg6kixppm6cc5zeazzvnc2tera)':
dependencies:
'@noble/ciphers': 0.5.3
'@noble/curves': 1.8.2
@@ -20793,7 +20776,7 @@ snapshots:
'@react-native-async-storage/async-storage': 2.2.0([email protected](@babel/[email protected])(@babel/[email protected](@babel/[email protected]))(@types/[email protected])([email protected])([email protected])([email protected]))
eventemitter3: 5.0.1
expo: 53.0.17(@babel/[email protected])([email protected])([email protected])([email protected](@babel/[email protected])(@babel/[email protected](@babel/[email protected]))(@types/[email protected])([email protected])([email protected])([email protected]))([email protected])([email protected])
expo-web-browser: 14.0.2([email protected](@babel/[email protected])([email protected])([email protected])([email protected](@babel/[email protected])(@babel/[email protected](@babel/[email protected]))(@types/[email protected])([email protected])([email protected])([email protected]))([email protected])([email protected]))([email protected](@babel/[email protected])(@babel/[email protected](@babel/[email protected]))(@types/[email protected])([email protected])([email protected])([email protected]))
expo-web-browser: 15.0.9([email protected](@babel/[email protected])([email protected])([email protected])([email protected](@babel/[email protected])(@babel/[email protected](@babel/[email protected]))(@types/[email protected])([email protected])([email protected])([email protected]))([email protected])([email protected]))([email protected](@babel/[email protected])(@babel/[email protected](@babel/[email protected]))(@types/[email protected])([email protected])([email protected])([email protected]))
fflate: 0.8.2
react: 19.1.0
react-native: 0.78.1(@babel/[email protected])(@babel/[email protected](@babel/[email protected]))(@types/[email protected])([email protected])([email protected])([email protected])
@@ -30650,8 +30633,6 @@ snapshots:
'@webassemblyjs/ast': 1.14.1
'@xtuc/long': 4.2.2
'@xmldom/[email protected]': {}
'@xmldom/[email protected]': {}
'@xtuc/[email protected]': {}
@@ -33312,19 +33293,19 @@ snapshots:
transitivePeerDependencies:
- supports-color
expo-constants@17.0.8([email protected](@babel/[email protected])([email protected])([email protected])([email protected](@babel/[email protected])(@babel/[email protected](@babel/[email protected]))(@types/[email protected])([email protected])([email protected])([email protected]))([email protected])([email protected]))([email protected](@babel/[email protected])(@babel/[email protected](@babel/[email protected]))(@types/[email protected])([email protected])([email protected])([email protected])):
expo-constants@17.1.7([email protected](@babel/[email protected])([email protected])([email protected])([email protected](@babel/[email protected])(@babel/[email protected](@babel/[email protected]))(@types/[email protected])([email protected])([email protected])([email protected]))([email protected])([email protected]))([email protected](@babel/[email protected])(@babel/[email protected](@babel/[email protected]))(@types/[email protected])([email protected])([email protected])([email protected])):
dependencies:
'@expo/config': 10.0.11
'@expo/env': 0.4.2
'@expo/config': 11.0.13
'@expo/env': 1.0.7
expo: 53.0.17(@babel/[email protected])([email protected])([email protected])([email protected](@babel/[email protected])(@babel/[email protected](@babel/[email protected]))(@types/[email protected])([email protected])([email protected])([email protected]))([email protected])([email protected])
react-native: 0.78.1(@babel/[email protected])(@babel/[email protected](@babel/[email protected]))(@types/[email protected])([email protected])([email protected])([email protected])
transitivePeerDependencies:
- supports-color
expo-constants@17.1.7([email protected](@babel/[email protected])([email protected])([email protected])([email protected](@babel/[email protected])(@babel/[email protected](@babel/[email protected]))(@types/[email protected])([email protected])([email protected])([email protected]))([email protected])([email protected]))([email protected](@babel/[email protected])(@babel/[email protected](@babel/[email protected]))(@types/[email protected])([email protected])([email protected])([email protected])):
expo-constants@18.0.10([email protected](@babel/[email protected])([email protected])([email protected])([email protected](@babel/[email protected])(@babel/[email protected](@babel/[email protected]))(@types/[email protected])([email protected])([email protected])([email protected]))([email protected])([email protected]))([email protected](@babel/[email protected])(@babel/[email protected](@babel/[email protected]))(@types/[email protected])([email protected])([email protected])([email protected])):
dependencies:
'@expo/config': 11.0.13
'@expo/env': 1.0.7
'@expo/config': 12.0.10
'@expo/env': 2.0.7
expo: 53.0.17(@babel/[email protected])([email protected])([email protected])([email protected](@babel/[email protected])(@babel/[email protected](@babel/[email protected]))(@types/[email protected])([email protected])([email protected])([email protected]))([email protected])([email protected])
react-native: 0.78.1(@babel/[email protected])(@babel/[email protected](@babel/[email protected]))(@types/[email protected])([email protected])([email protected])([email protected])
transitivePeerDependencies:
@@ -33346,9 +33327,9 @@ snapshots:
expo: 53.0.17(@babel/[email protected])([email protected])([email protected])([email protected](@babel/[email protected])(@babel/[email protected](@babel/[email protected]))(@types/[email protected])([email protected])([email protected])([email protected]))([email protected])([email protected])
react: 19.1.0
expo-linking@7.0.5([email protected](@babel/[email protected])([email protected])([email protected])([email protected](@babel/[email protected])(@babel/[email protected](@babel/[email protected]))(@types/[email protected])([email protected])([email protected])([email protected]))([email protected])([email protected]))([email protected](@babel/[email protected])(@babel/[email protected](@babel/[email protected]))(@types/[email protected])([email protected])([email protected])([email protected]))([email protected]):
expo-linking@8.0.8([email protected](@babel/[email protected])([email protected])([email protected])([email protected](@babel/[email protected])(@babel/[email protected](@babel/[email protected]))(@types/[email protected])([email protected])([email protected])([email protected]))([email protected])([email protected]))([email protected](@babel/[email protected])(@babel/[email protected](@babel/[email protected]))(@types/[email protected])([email protected])([email protected])([email protected]))([email protected]):
dependencies:
expo-constants: 17.0.8([email protected](@babel/[email protected])([email protected])([email protected])([email protected](@babel/[email protected])(@babel/[email protected](@babel/[email protected]))(@types/[email protected])([email protected])([email protected])([email protected]))([email protected])([email protected]))([email protected](@babel/[email protected])(@babel/[email protected](@babel/[email protected]))(@types/[email protected])([email protected])([email protected])([email protected]))
expo-constants: 18.0.10([email protected](@babel/[email protected])([email protected])([email protected])([email protected](@babel/[email protected])(@babel/[email protected](@babel/[email protected]))(@types/[email protected])([email protected])([email protected])([email protected]))([email protected])([email protected]))([email protected](@babel/[email protected])(@babel/[email protected](@babel/[email protected]))(@types/[email protected])([email protected])([email protected])([email protected]))
invariant: 2.2.4
react: 19.1.0
react-native: 0.78.1(@babel/[email protected])(@babel/[email protected](@babel/[email protected]))(@types/[email protected])([email protected])([email protected])([email protected])
@@ -33370,7 +33351,7 @@ snapshots:
dependencies:
invariant: 2.2.4
expo-web-browser@14.0.2([email protected](@babel/[email protected])([email protected])([email protected])([email protected](@babel/[email protected])(@babel/[email protected](@babel/[email protected]))(@types/[email protected])([email protected])([email protected])([email protected]))([email protected])([email protected]))([email protected](@babel/[email protected])(@babel/[email protected](@babel/[email protected]))(@types/[email protected])([email protected])([email protected])([email protected])):
expo-web-browser@15.0.9([email protected](@babel/[email protected])([email protected])([email protected])([email protected](@babel/[email protected])(@babel/[email protected](@babel/[email protected]))(@types/[email protected])([email protected])([email protected])([email protected]))([email protected])([email protected]))([email protected](@babel/[email protected])(@babel/[email protected](@babel/[email protected]))(@types/[email protected])([email protected])([email protected])([email protected])):
dependencies:
expo: 53.0.17(@babel/[email protected])([email protected])([email protected])([email protected](@babel/[email protected])(@babel/[email protected](@babel/[email protected]))(@types/[email protected])([email protected])([email protected])([email protected]))([email protected])([email protected])
react-native: 0.78.1(@babel/[email protected])(@babel/[email protected](@babel/[email protected]))(@types/[email protected])([email protected])([email protected])([email protected])
@@ -33831,8 +33812,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
[email protected]: {}
[email protected]: {}
[email protected]:
@@ -41102,12 +41081,6 @@ snapshots:
[email protected]: {}
[email protected]:
dependencies:
graceful-fs: 4.2.11
imurmurhash: 0.1.4
signal-exit: 3.0.7
[email protected]:
dependencies:
imurmurhash: 0.1.4
@@ -41276,8 +41249,6 @@ snapshots:
[email protected]: {}
[email protected]: {}
[email protected]: {}
[email protected]: {}