Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aa9d858f1c | ||
|
|
d8540216f1 | ||
|
|
654f8795b4 | ||
|
|
279fb918f5 | ||
|
|
c5cff26faa | ||
|
|
78901457cf | ||
|
|
47f8cd6d8d |
@@ -67,7 +67,7 @@
|
||||
"input-otp": "^1.4.1",
|
||||
"ioredis": "^5.6.0",
|
||||
"ipaddr.js": "^2.2.0",
|
||||
"lucide-react": "0.482.0",
|
||||
"lucide-react": "0.483.0",
|
||||
"next": "15.2.2",
|
||||
"next-plausible": "^3.12.4",
|
||||
"next-themes": "^0.4.6",
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"jose": "6.0.10",
|
||||
"lucide-react": "0.482.0",
|
||||
"lucide-react": "0.483.0",
|
||||
"next": "15.2.2",
|
||||
"next-themes": "^0.4.6",
|
||||
"react": "19.0.0",
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"jose": "6.0.10",
|
||||
"lucide-react": "0.482.0",
|
||||
"lucide-react": "0.483.0",
|
||||
"next": "15.2.2",
|
||||
"next-themes": "^0.4.6",
|
||||
"nextjs-toploader": "^1.6.12",
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
"flexsearch": "^0.7.43",
|
||||
"github-slugger": "^2.0.0",
|
||||
"he": "^1.2.0",
|
||||
"lucide-react": "0.482.0",
|
||||
"lucide-react": "0.483.0",
|
||||
"next": "15.2.2",
|
||||
"nextjs-toploader": "^1.6.12",
|
||||
"node-html-markdown": "^1.3.0",
|
||||
|
||||
@@ -46,7 +46,7 @@ Log in to [the thirdweb dashboard](https://thirdweb.com/team). Navigate to the *
|
||||
|
||||
## Option 1: ConnectButton
|
||||
|
||||
Pay is available by default with our `ConnectButton` component. When users log in with Connect, they'll be able to onramp and purchase crypto directly from the logged in Connect interface. You can read more about `ConnectButton` [in this guide](/connect/sign-in/ConnectButton).
|
||||
Universal Bridge is available by default with our `ConnectButton` component. When users log in with Connect, they'll be able to onramp and purchase crypto directly from the logged in Connect interface. You can read more about `ConnectButton` [in this guide](/connect/sign-in/ConnectButton).
|
||||
|
||||
```tsx
|
||||
import { ThirdwebProvider, ConnectButton } from "thirdweb/react";
|
||||
|
||||
@@ -3,7 +3,7 @@ import { createMetadata, Callout } from "@doc";
|
||||
|
||||
export const metadata = createMetadata({
|
||||
image: {
|
||||
title: "thirdweb Pay - Test Mode",
|
||||
title: "thirdweb Universal Bridge - Test Mode",
|
||||
icon: "thirdweb",
|
||||
},
|
||||
title: "Enable Test Mode for Universal bridge — thirdweb docs",
|
||||
|
||||
@@ -75,6 +75,10 @@ export const sidebar: SideBar = {
|
||||
name: "Guest Mode",
|
||||
href: `${walletSlug}/sign-in-methods/guest`,
|
||||
},
|
||||
{
|
||||
name: "FAQ",
|
||||
href: `${walletSlug}/in-app-wallet/faq`,
|
||||
},
|
||||
{
|
||||
name: "Custom Authentication",
|
||||
links: [
|
||||
@@ -130,6 +134,10 @@ export const sidebar: SideBar = {
|
||||
name: "Register with WalletConnect",
|
||||
href: `${walletSlug}/ecosystem/register-walletconnect`,
|
||||
},
|
||||
{
|
||||
name: "FAQ",
|
||||
href: `${walletSlug}/ecosystem/faq`,
|
||||
},
|
||||
],
|
||||
},
|
||||
//Account abstraction
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import { createMetadata } from "@doc";
|
||||
|
||||
export const metadata = createMetadata({
|
||||
title: "Ecosystem Wallet FAQs",
|
||||
description: "Frequently asked questions about thirdweb's Ecosystem Wallet",
|
||||
image: {
|
||||
title: "FAQs for thirdweb Ecosystem Wallet",
|
||||
icon: "wallets",
|
||||
},
|
||||
});
|
||||
|
||||
# Ecosystem Wallet FAQs
|
||||
|
||||
### What is an Ecosystem Wallet?
|
||||
|
||||
An ecosystem wallet is a managed in-app wallet service that allows platforms to create a branded wallet and login system, manage their partners, and allow any number of partners to spin up in-app wallets. End users that create in-app wallets through your partner applications (either with email, phone number, passkey, or socials) will receive **one account and wallet address** that they can access across the entire ecosystem.
|
||||
|
||||
### How do Ecosystem Wallets work?
|
||||
|
||||
Ecosystem Wallets are a fully managed and containerized in-app wallet backend service that is white labeled to your application. By giving you access to this backend service, you can power any application with in-app wallets just like thirdweb does for over 70,000 developers.
|
||||
|
||||
### What is an Ecosystem Partner?
|
||||
|
||||
Ecosystem partners are games or applications that you have permissioned to spin up in-app wallets underneath your brand. They will have access to the same account (funds, assets, etc.) for any end users that 1) use your branded login system or 2) log in with your wallet.
|
||||
|
||||
You can manage ecosystem partners in the Permissions tab of your Ecosystem Wallet dashboard.
|
||||
|
||||
### Which login providers can display my Ecosystem Wallet?
|
||||
|
||||
thirdweb Connect, with WalletConnect and any wagmi- or viem-based login providers coming soon.
|
||||
|
||||
### How can Partners integrate my Ecosystem Wallet?
|
||||
|
||||
Partners can integrate your Ecosystem Wallet in three different ways:
|
||||
|
||||
**Login System**
|
||||
|
||||
Partners can install thirdweb SDK and use the Partner ID you created for them to display your branded sign in flow. All login options in this sign in flow will create an in-app wallet.
|
||||
|
||||
**Branded Wallet Connector**
|
||||
|
||||
Partners can add your wallet as a wallet connector option in their existing sign in flow on any domains they've whitelisted by calling the follow endpoint from their application.
|
||||
|
||||
**WalletConnect (Coming Soon)**
|
||||
|
||||
Partners using WalletConnect will automatically display your wallet as a WalletConnect option on any domains they've whitelisted.
|
||||
|
||||
### What is the difference between Anyone and Allowlist scopes for Ecosystem Wallet?
|
||||
|
||||
The **Anyone** scope allows any developer to integrate your ecosystem wallet, even if you have not given them permission. Any developer with thirdweb Connect, for example, would now display your wallet as a login option.
|
||||
|
||||
The **Allowlist** scope allows developers that you have explicitly added to your list of Partners to display your wallet as a login option.
|
||||
|
||||
You can manage scope in the Permissions tab of your Ecosystem Wallet dashboard.
|
||||
|
||||
### How does billing work for Ecosystem Wallets?
|
||||
|
||||
As the ecosystem admin, you will be billed $250 monthly for your Ecosystem Wallet. This allows any application or game in your ecosystem generate in-app wallets. Each month, we allow you and your ecosystem partners to generate 30,000 wallets for free. Once you've generated 30,000 wallets, you will be charged $0.02 per in-app wallet. At the end of each month, we will provide you with an invoice with a usage breakdown across all partners.
|
||||
@@ -57,63 +57,6 @@ Currently, users will rely on the built-in recovery of their auth method. Refer
|
||||
|
||||
Users can also link their account to a different auth method (e.g. email, phone, or social). All linked methods will be able to be used to access their accounts, providing an alternate recovery method in the case where one is lost.
|
||||
|
||||
# In App Wallet FAQs
|
||||
|
||||
### How do users connect to their in-app wallet in a third-party app?
|
||||
|
||||
Users can access it by doing a manual connection via wallet connect. They will then have to use the connect button or embed and select manage wallet -> connect an App.
|
||||
|
||||
### How is pricing calculated for in-app wallets?
|
||||
|
||||
In-app wallets are billed based on "monthly active wallets". An active wallet is defined as a wallet where a user logs in during the billing period. In-app wallets are completely free up to 1,000 monthly active wallets, and $0.02 per wallet after that.
|
||||
|
||||
# Ecosystem Wallet FAQs
|
||||
|
||||
### What is an Ecosystem Wallet?
|
||||
|
||||
An ecosystem wallet is a managed in-app wallet service that allows you to create a branded wallet and login system, manage your partners, and allow any number of partners to spin up in-app wallets. End users that create in-app wallets through your partner applications (either with email, phone number, passkey, or socials) will receive **one account and wallet address** that they can access across the entire ecosystem.
|
||||
|
||||
### How do Ecosystem Wallets work?
|
||||
|
||||
Ecosystem Wallets are a fully managed and containerized in-app wallet backend service that is white labeled to your application. By giving you access to this backend service, you can power any application with in-app wallets just like thirdweb does for over 70,000 developers.
|
||||
|
||||
### What is an Ecosystem Partner?
|
||||
|
||||
Ecosystem partners are games or applications that you have permissioned to spin up in-app wallets underneath your brand. They will have access to the same account (funds, assets, etc.) for any end users that 1) use your branded login system or 2) log in with your wallet.
|
||||
|
||||
You can manage ecosystem partners in the Permissions tab of your Ecosystem Wallet dashboard.
|
||||
|
||||
### Which login providers can display my Ecosystem Wallet?
|
||||
|
||||
thirdweb Connect, with WalletConnect and any wagmi- or viem-based login providers coming soon.
|
||||
|
||||
### How can Partners integrate my Ecosystem Wallet?
|
||||
|
||||
Partners can integrate your Ecosystem Wallet in three different ways:
|
||||
|
||||
**Login System**
|
||||
|
||||
Partners can install thirdweb SDK and use the Partner ID you created for them to display your branded sign in flow. All login options in this sign in flow will create an in-app wallet.
|
||||
|
||||
**Branded Wallet Connector**
|
||||
|
||||
Partners can add your wallet as a wallet connector option in their existing sign in flow on any domains they've whitelisted by calling the follow endpoint from their application.
|
||||
|
||||
**WalletConnect (Coming Soon)**
|
||||
|
||||
Partners using WalletConnect will automatically display your wallet as a WalletConnect option on any domains they've whitelisted.
|
||||
|
||||
### What is the difference between Anyone and Allowlist scopes for Ecosystem Wallet?
|
||||
|
||||
The **Anyone** scope allows any developer to integrate your ecosystem wallet, even if you have not given them permission. Any developer with thirdweb Connect, for example, would now display your wallet as a login option.
|
||||
|
||||
The **Allowlist** scope allows developers that you have explicitly added to your list of Partners to display your wallet as a login option.
|
||||
|
||||
You can manage scope in the Permissions tab of your Ecosystem Wallet dashboard.
|
||||
|
||||
### How does billing work for Ecosystem Wallets?
|
||||
|
||||
As the ecosystem admin, you will be billed $250 monthly for your Ecosystem Wallet. This allows any application or game in your ecosystem generate in-app wallets. Each month, we allow you and your ecosystem partners to generate 30,000 wallets for free. Once you’ve generated 30,000 wallets, you will be charged $0.02 per in-app wallet. At the end of each month, we will provide you with an invoice with a usage breakdown across all partners.
|
||||
|
||||
# Security FAQs
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Callout, OpenSourceCard, createMetadata, InstallTabs, SDKCard, Grid, ArticleIconCard } from "@doc";
|
||||
import { Callout, OpenSourceCard, createMetadata, InstallTabs, SDKCard, Grid, ArticleIconCard, Tabs, TabsList, TabsTrigger, TabsContent } from "@doc";
|
||||
import {
|
||||
ReactIcon,
|
||||
TypeScriptIcon,
|
||||
@@ -10,26 +10,36 @@ import { ExternalLink } from "lucide-react";
|
||||
|
||||
export const metadata = createMetadata({
|
||||
image: {
|
||||
title: "Get started with thirdweb Wallets",
|
||||
title: "Get started with Connect",
|
||||
icon: "wallets",
|
||||
},
|
||||
title: "Get started with thirdweb Wallet",
|
||||
description: "Get started with thirdweb Wallet",
|
||||
title: "Get started with Connect",
|
||||
description: "Get started with Connect",
|
||||
});
|
||||
|
||||
# Get Started
|
||||
# Get Started with Connect
|
||||
|
||||
thirdweb offers multiple ways to add wallets to your application. Choose the method that best suits your needs:
|
||||
Connect offers multiple ways to integrate wallet functionality in your application:
|
||||
|
||||
1. **External Wallets** - Connect to existing wallets like MetaMask, Coinbase Wallet, etc.
|
||||
2. **In-App Wallets** - Create wallets with social/email login.
|
||||
3. **Ecosystem Wallets** - Create wallets with social/email login that is shareable across various aplications.
|
||||
3. **Ecosystem Wallets** - Create wallets with social/email login that is shareable across various applications.
|
||||
|
||||
These methods can be used independently or together in the same application to provide the best experience for your users.
|
||||
|
||||
## Get your Client ID
|
||||
|
||||
To use wallet features in your applications, you will need a client ID. Generate one for free at [your thirdweb dashboard](https://thirdweb.com/create-api-key).
|
||||
|
||||
<Callout variant="info">
|
||||
Keep your client ID safe. You can generate different client IDs for development and production environments.
|
||||
</Callout>
|
||||
|
||||
Your client ID authenticates your application with thirdweb's services and provides access to infrastructure like RPC endpoints, IPFS gateways, and wallet services.
|
||||
|
||||
## Live Playground
|
||||
|
||||
Check out the playground that lets you see all the various capabilities of Connect.
|
||||
Before diving into the implementation, check out our playground to see all the various capabilities of Connect:
|
||||
|
||||
<ArticleIconCard
|
||||
title="Try the demo"
|
||||
@@ -38,13 +48,684 @@ Check out the playground that lets you see all the various capabilities of Conne
|
||||
href="https://playground.thirdweb.com/connect/sign-in/button"
|
||||
/>
|
||||
|
||||
## Get your client ID
|
||||
## Choose Your Framework/Language
|
||||
|
||||
To use in-app wallets in your applications, you will need a client ID. You can get one for free on [your thirdweb dashboard](https://thirdweb.com/create-api-key).
|
||||
<Tabs defaultValue="typescript">
|
||||
<TabsList>
|
||||
<TabsTrigger value="typescript">TypeScript</TabsTrigger>
|
||||
<TabsTrigger value="react">React</TabsTrigger>
|
||||
<TabsTrigger value="reactnative">React Native</TabsTrigger>
|
||||
<TabsTrigger value="unity">Unity</TabsTrigger>
|
||||
<TabsTrigger value="dotnet">.NET</TabsTrigger>
|
||||
<TabsTrigger value="unreal">Unreal Engine</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
## Pick your language
|
||||
<TabsContent value="typescript">
|
||||
### Installation
|
||||
|
||||
Start with Connect in your preferred language.
|
||||
Install the thirdweb SDK in your TypeScript project:
|
||||
|
||||
```bash
|
||||
npm install thirdweb
|
||||
# or
|
||||
yarn add thirdweb
|
||||
# or
|
||||
pnpm add thirdweb
|
||||
```
|
||||
|
||||
### Set up the Client
|
||||
|
||||
First, create a client instance to connect with thirdweb services:
|
||||
|
||||
```typescript
|
||||
import { createThirdwebClient } from "thirdweb";
|
||||
|
||||
// For client-side applications:
|
||||
const client = createThirdwebClient({
|
||||
clientId: "YOUR_CLIENT_ID", // Get from your thirdweb dashboard
|
||||
});
|
||||
|
||||
// For server-side scripts (Never expose secret keys in client code):
|
||||
// const client = createThirdwebClient({
|
||||
// secretKey: process.env.THIRDWEB_SECRET_KEY,
|
||||
// });
|
||||
```
|
||||
|
||||
### Connect External Wallets
|
||||
|
||||
Enable users to connect their existing wallets:
|
||||
|
||||
```typescript
|
||||
import { createThirdwebClient, walletConnect } from "thirdweb";
|
||||
|
||||
// Create the client (do this once and reuse it)
|
||||
const client = createThirdwebClient({
|
||||
clientId: "YOUR_CLIENT_ID",
|
||||
});
|
||||
|
||||
// Connect WalletConnect
|
||||
const wallet = walletConnect({
|
||||
projectId: "YOUR_WALLETCONNECT_PROJECT_ID", // Get from WalletConnect dashboard
|
||||
});
|
||||
|
||||
// Connect the wallet
|
||||
const walletInstance = await wallet.connect();
|
||||
const address = await walletInstance.getAddress();
|
||||
console.log("Connected to:", address);
|
||||
|
||||
// Read wallet balance
|
||||
import { getWalletBalance, ethereum } from "thirdweb";
|
||||
|
||||
const balance = await getWalletBalance({
|
||||
client,
|
||||
chain: ethereum,
|
||||
address: walletInstance.getAddress(),
|
||||
});
|
||||
console.log(`Balance: ${balance.displayValue} ${balance.symbol}`);
|
||||
```
|
||||
|
||||
### Setup In-App Wallet
|
||||
|
||||
Create wallets with email or social login:
|
||||
|
||||
```typescript
|
||||
import { createThirdwebClient, embeddedWallet } from "thirdweb";
|
||||
|
||||
// Create the client (do this once and reuse it)
|
||||
const client = createThirdwebClient({
|
||||
clientId: "YOUR_CLIENT_ID",
|
||||
});
|
||||
|
||||
// Initialize embedded wallet
|
||||
const wallet = embeddedWallet({
|
||||
client,
|
||||
});
|
||||
|
||||
// Connect with email
|
||||
const emailWallet = await wallet.connect({
|
||||
strategy: "email",
|
||||
email: "[email protected]",
|
||||
});
|
||||
|
||||
// Connect with social login
|
||||
const googleWallet = await wallet.connect({
|
||||
strategy: "google",
|
||||
});
|
||||
```
|
||||
|
||||
### Setup Ecosystem Wallet
|
||||
|
||||
Create shareable wallets across multiple applications:
|
||||
|
||||
```typescript
|
||||
import { createThirdwebClient, ecosystemWallet } from "thirdweb";
|
||||
|
||||
// Create the client (do this once and reuse it)
|
||||
const client = createThirdwebClient({
|
||||
clientId: "YOUR_CLIENT_ID",
|
||||
});
|
||||
|
||||
// Initialize ecosystem wallet
|
||||
const wallet = ecosystemWallet({
|
||||
client,
|
||||
ecosystemId: "YOUR_ECOSYSTEM_ID", // Get from your ecosystem dashboard
|
||||
});
|
||||
|
||||
// Connect with email
|
||||
const emailWallet = await wallet.connect({
|
||||
strategy: "email",
|
||||
email: "[email protected]",
|
||||
});
|
||||
```
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="react">
|
||||
### Installation
|
||||
|
||||
Install the thirdweb SDK in your React project:
|
||||
|
||||
```bash
|
||||
npm install thirdweb
|
||||
# or
|
||||
yarn add thirdweb
|
||||
# or
|
||||
pnpm add thirdweb
|
||||
```
|
||||
|
||||
### Create a Client
|
||||
|
||||
First, create a client file (e.g., `thirdwebClient.ts`) for reuse throughout your app:
|
||||
|
||||
```typescript
|
||||
// thirdwebClient.ts
|
||||
import { createThirdwebClient } from "thirdweb";
|
||||
|
||||
export const client = createThirdwebClient({
|
||||
clientId: process.env.NEXT_PUBLIC_THIRDWEB_CLIENT_ID,
|
||||
});
|
||||
```
|
||||
|
||||
### Setup the Provider
|
||||
|
||||
Wrap your application with the ThirdwebProvider:
|
||||
|
||||
```tsx
|
||||
// app.tsx / _app.tsx
|
||||
import { ThirdwebProvider } from "thirdweb/react";
|
||||
import { client } from "./thirdwebClient";
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<ThirdwebProvider>
|
||||
<YourApp />
|
||||
</ThirdwebProvider>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Connect Button Component
|
||||
|
||||
Use the pre-built ConnectButton for a complete wallet connection UI:
|
||||
|
||||
```tsx
|
||||
import { ConnectButton } from "thirdweb/react";
|
||||
import { client } from "./thirdwebClient";
|
||||
|
||||
function YourApp() {
|
||||
return (
|
||||
<div>
|
||||
<h1>My dApp</h1>
|
||||
<ConnectButton client={client} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
The ConnectButton automatically handles 500+ wallets, including MetaMask, WalletConnect, Coinbase Wallet, and all supported in-app wallets.
|
||||
|
||||
### Connection Hooks
|
||||
|
||||
For more customized implementations, use the connection hooks:
|
||||
|
||||
```tsx
|
||||
import { useConnect, useDisconnect, useActiveAccount, useWalletBalance } from "thirdweb/react";
|
||||
import { client } from "./thirdwebClient";
|
||||
import { ethereum } from "thirdweb/chains";
|
||||
|
||||
function CustomWalletUI() {
|
||||
const { connect } = useConnect();
|
||||
const { disconnect } = useDisconnect();
|
||||
const activeAccount = useActiveAccount();
|
||||
|
||||
// Get wallet balance (only when connected)
|
||||
const { data: balance, isLoading } = useWalletBalance({
|
||||
client,
|
||||
chain: ethereum,
|
||||
address: activeAccount?.address,
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
{activeAccount ? (
|
||||
<div>
|
||||
<p>Connected: {activeAccount.address}</p>
|
||||
{isLoading ? (
|
||||
<p>Loading balance...</p>
|
||||
) : (
|
||||
<p>Balance: {balance?.displayValue} {balance?.symbol}</p>
|
||||
)}
|
||||
<button onClick={() => disconnect()}>Disconnect</button>
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
onClick={() => connect({ connector: "metamask" })}
|
||||
>
|
||||
Connect MetaMask
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Using In-App Wallets
|
||||
|
||||
Enable email or social login with the `useEmbeddedWallet` hook:
|
||||
|
||||
```tsx
|
||||
import { useEmbeddedWallet } from "thirdweb/react";
|
||||
import { useState } from "react";
|
||||
import { client } from "./thirdwebClient";
|
||||
|
||||
function EmailLoginButton() {
|
||||
const embeddedWallet = useEmbeddedWallet({
|
||||
client,
|
||||
});
|
||||
const [email, setEmail] = useState("");
|
||||
|
||||
const handleLogin = async () => {
|
||||
try {
|
||||
await embeddedWallet.connect({
|
||||
strategy: "email",
|
||||
email,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to login:", error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<input
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
placeholder="Enter your email"
|
||||
/>
|
||||
<button onClick={handleLogin}>Login with Email</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="reactnative">
|
||||
### Installation
|
||||
|
||||
Install the thirdweb SDK in your React Native project:
|
||||
|
||||
```bash
|
||||
npm install thirdweb
|
||||
# or
|
||||
yarn add thirdweb
|
||||
# or
|
||||
pnpm add thirdweb
|
||||
```
|
||||
|
||||
<Callout variant="warning">
|
||||
You cannot use Expo Go with thirdweb because native modules are required.
|
||||
Use an Expo development build (`npx expo prebuild`) or React Native CLI app.
|
||||
</Callout>
|
||||
|
||||
### Create a Client
|
||||
|
||||
Create a client file for reuse throughout your app:
|
||||
|
||||
```typescript
|
||||
// thirdwebClient.ts
|
||||
import { createThirdwebClient } from "thirdweb";
|
||||
|
||||
export const client = createThirdwebClient({
|
||||
clientId: "YOUR_CLIENT_ID", // Configure in your app's env or constants
|
||||
});
|
||||
```
|
||||
|
||||
<Callout variant="info">
|
||||
When creating your client ID on the thirdweb dashboard, allowlist your mobile app's bundle ID (e.g., com.yourcompany.app) for security.
|
||||
</Callout>
|
||||
|
||||
### Setup the Provider
|
||||
|
||||
Wrap your application with the ThirdwebProvider:
|
||||
|
||||
```tsx
|
||||
import { ThirdwebProvider } from "thirdweb/react";
|
||||
import { client } from "./thirdwebClient";
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<ThirdwebProvider>
|
||||
<YourApp />
|
||||
</ThirdwebProvider>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Connect Button Component
|
||||
|
||||
Use the pre-built ConnectButton for a complete wallet connection UI:
|
||||
|
||||
```tsx
|
||||
import { ConnectButton } from "thirdweb/react";
|
||||
import { client } from "./thirdwebClient";
|
||||
import { View, Text } from "react-native";
|
||||
|
||||
function YourApp() {
|
||||
return (
|
||||
<View>
|
||||
<Text>My Mobile dApp</Text>
|
||||
<ConnectButton client={client} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Using In-App Wallets
|
||||
|
||||
Enable email or social login for mobile:
|
||||
|
||||
```tsx
|
||||
import { useEmbeddedWallet } from "thirdweb/react";
|
||||
import { useState } from "react";
|
||||
import { View, TextInput, Button, Alert } from "react-native";
|
||||
import { client } from "./thirdwebClient";
|
||||
|
||||
function EmailLoginButton() {
|
||||
const embeddedWallet = useEmbeddedWallet({
|
||||
client,
|
||||
});
|
||||
const [email, setEmail] = useState("");
|
||||
|
||||
const handleLogin = async () => {
|
||||
try {
|
||||
await embeddedWallet.connect({
|
||||
strategy: "email",
|
||||
email,
|
||||
});
|
||||
} catch (error) {
|
||||
Alert.alert("Login Failed", error.message);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<View>
|
||||
<TextInput
|
||||
value={email}
|
||||
onChangeText={setEmail}
|
||||
placeholder="Enter your email"
|
||||
keyboardType="email-address"
|
||||
/>
|
||||
<Button title="Login with Email" onPress={handleLogin} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
```
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="unity">
|
||||
### Installation
|
||||
|
||||
1. Download the latest [thirdweb Unity SDK](https://github.com/thirdweb-dev/unity-sdk/releases) (.unitypackage file)
|
||||
2. Import the package into your Unity project via Assets > Import Package > Custom Package
|
||||
|
||||
### Configure Client ID
|
||||
|
||||
After importing the SDK:
|
||||
|
||||
1. Go to Project Settings > Thirdweb
|
||||
2. Enter your Client ID from the thirdweb dashboard
|
||||
3. Allowlist your game's Bundle ID on the thirdweb dashboard for security
|
||||
|
||||
### Initialize the SDK
|
||||
|
||||
Create a new script to manage wallet connections:
|
||||
|
||||
```csharp
|
||||
using Thirdweb;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
public class WalletManager : MonoBehaviour
|
||||
{
|
||||
private ThirdwebSDK sdk;
|
||||
public Text walletAddressText;
|
||||
public Button connectButton;
|
||||
|
||||
void Start()
|
||||
{
|
||||
// Client ID is set in Project Settings > Thirdweb
|
||||
sdk = new ThirdwebSDK("ethereum"); // Or any supported chain
|
||||
connectButton.onClick.AddListener(ConnectWallet);
|
||||
}
|
||||
|
||||
public async void ConnectWallet()
|
||||
{
|
||||
try {
|
||||
// Connect with an external wallet like Coinbase Wallet
|
||||
string address = await sdk.wallet.Connect(new WalletConnection() {
|
||||
provider = WalletProvider.CoinbaseWallet,
|
||||
chainId = 1 // Ethereum Mainnet
|
||||
});
|
||||
|
||||
walletAddressText.text = "Connected: " + address;
|
||||
}
|
||||
catch (System.Exception e) {
|
||||
Debug.LogError("Error connecting wallet: " + e.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Using the Connect Wallet Prefab
|
||||
|
||||
For a quicker implementation, use the provided prefab:
|
||||
|
||||
1. Add the `ThirdwebManager` prefab to your scene
|
||||
2. Configure your Client ID in the inspector
|
||||
3. Add the `ConnectWallet` prefab to your UI Canvas
|
||||
4. Connect the prefab to your ThirdwebManager
|
||||
|
||||
### Implementing In-App Wallets
|
||||
|
||||
Enable email login in Unity:
|
||||
|
||||
```csharp
|
||||
public async void ConnectWithEmail(string email)
|
||||
{
|
||||
try {
|
||||
string address = await sdk.wallet.Connect(new WalletConnection() {
|
||||
provider = WalletProvider.EmbeddedWallet,
|
||||
email = email,
|
||||
chainId = 1 // Ethereum Mainnet
|
||||
});
|
||||
|
||||
walletAddressText.text = "Connected: " + address;
|
||||
|
||||
// Read wallet balance
|
||||
var balance = await sdk.wallet.GetBalance();
|
||||
Debug.Log($"Balance: {balance.DisplayValue} {balance.Symbol}");
|
||||
}
|
||||
catch (System.Exception e) {
|
||||
Debug.LogError("Error connecting wallet: " + e.Message);
|
||||
}
|
||||
}
|
||||
```
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="dotnet">
|
||||
### Installation
|
||||
|
||||
Install the thirdweb .NET SDK using NuGet:
|
||||
|
||||
```bash
|
||||
dotnet add package Thirdweb
|
||||
```
|
||||
|
||||
### Initialize the SDK
|
||||
|
||||
Create a client instance to connect with thirdweb services:
|
||||
|
||||
```csharp
|
||||
using Thirdweb;
|
||||
|
||||
// For client-side applications:
|
||||
var sdk = new ThirdwebSDK("ethereum", new ThirdwebSDK.Options
|
||||
{
|
||||
ClientId = "YOUR_CLIENT_ID" // From thirdweb dashboard
|
||||
});
|
||||
|
||||
// For server-side applications:
|
||||
// var sdk = new ThirdwebSDK("ethereum", new ThirdwebSDK.Options
|
||||
// {
|
||||
// SecretKey = Environment.GetEnvironmentVariable("THIRDWEB_SECRET_KEY")
|
||||
// });
|
||||
```
|
||||
|
||||
### Connect External Wallets
|
||||
|
||||
For .NET applications that need to interact with external wallets:
|
||||
|
||||
```csharp
|
||||
// For server-side applications or wallet management
|
||||
var privateKey = Environment.GetEnvironmentVariable("WALLET_PRIVATE_KEY"); // Never hardcode
|
||||
var wallet = new PrivateKeyWallet(privateKey);
|
||||
|
||||
await sdk.SetWallet(wallet);
|
||||
var address = await sdk.Wallet.GetAddress();
|
||||
Console.WriteLine($"Connected wallet address: {address}");
|
||||
|
||||
// Read wallet balance
|
||||
var balance = await sdk.Wallet.GetBalance();
|
||||
Console.WriteLine($"Balance: {balance.DisplayValue} {balance.Symbol}");
|
||||
```
|
||||
|
||||
### Using In-App Wallets
|
||||
|
||||
Create wallets with email authentication:
|
||||
|
||||
```csharp
|
||||
// Create an embedded wallet with email
|
||||
var walletOptions = new EmbeddedWalletOptions
|
||||
{
|
||||
Email = "[email protected]",
|
||||
AuthProvider = AuthProvider.Email
|
||||
};
|
||||
|
||||
var wallet = new EmbeddedWallet(walletOptions);
|
||||
await sdk.SetWallet(wallet);
|
||||
|
||||
// Authenticate and get the wallet address
|
||||
await wallet.Authenticate();
|
||||
var address = await sdk.Wallet.GetAddress();
|
||||
Console.WriteLine($"Connected with embedded wallet: {address}");
|
||||
```
|
||||
|
||||
### Reading Contract Data
|
||||
|
||||
Interact with smart contracts:
|
||||
|
||||
```csharp
|
||||
// Get a contract instance
|
||||
var contract = await ThirdwebContract.Create(
|
||||
client: sdk.Client,
|
||||
address: "0x...",
|
||||
chain: Chain.Ethereum
|
||||
);
|
||||
|
||||
// Read a value from the contract
|
||||
var name = await contract.Read<string>("name");
|
||||
Console.WriteLine($"Contract name: {name}");
|
||||
```
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="unreal">
|
||||
### Installation
|
||||
|
||||
1. Download the thirdweb Unreal Engine plugin from the [Unreal Engine Marketplace](https://www.unrealengine.com/marketplace/en-US/product/thirdweb)
|
||||
2. Add the plugin to your Unreal project
|
||||
3. Enable the plugin in your project settings
|
||||
|
||||
### Configure Client ID
|
||||
|
||||
1. Go to Edit > Project Settings > Thirdweb
|
||||
2. Enter your Client ID from the thirdweb dashboard
|
||||
3. Enter your Bundle ID (must match what was allowlisted on the thirdweb dashboard)
|
||||
|
||||
### Initialize the SDK (C++)
|
||||
|
||||
Create a new class to manage wallet connections:
|
||||
|
||||
```cpp
|
||||
#include "ThirdwebManager.h"
|
||||
#include "WalletConnection.h"
|
||||
|
||||
// In your game class or controller
|
||||
void AMyGameController::InitializeThirdweb()
|
||||
{
|
||||
// Get the Thirdweb subsystem
|
||||
UThirdwebSubsystem* ThirdwebSubsystem = GEngine->GetEngineSubsystem<UThirdwebSubsystem>();
|
||||
|
||||
// Initialize with your client ID (configured in project settings)
|
||||
ThirdwebSubsystem->Initialize("ethereum");
|
||||
}
|
||||
|
||||
void AMyGameController::ConnectWallet()
|
||||
{
|
||||
UThirdwebSubsystem* ThirdwebSubsystem = GEngine->GetEngineSubsystem<UThirdwebSubsystem>();
|
||||
|
||||
// Configure wallet connection
|
||||
FWalletConnection WalletOptions;
|
||||
WalletOptions.Provider = EWalletProvider::EmbeddedWallet;
|
||||
WalletOptions.Email = "[email protected]";
|
||||
WalletOptions.ChainId = 1; // Ethereum Mainnet
|
||||
|
||||
// Connect wallet asynchronously
|
||||
ThirdwebSubsystem->ConnectWallet(WalletOptions, FOnWalletConnected::CreateUObject(this, &AMyGameController::OnWalletConnected));
|
||||
}
|
||||
|
||||
void AMyGameController::OnWalletConnected(const FString& Address, const FString& Error)
|
||||
{
|
||||
if (Error.IsEmpty())
|
||||
{
|
||||
UE_LOG(LogTemp, Display, TEXT("Wallet connected: %s"), *Address);
|
||||
|
||||
// Get balance
|
||||
UThirdwebSubsystem* ThirdwebSubsystem = GEngine->GetEngineSubsystem<UThirdwebSubsystem>();
|
||||
ThirdwebSubsystem->GetWalletBalance(FOnWalletBalanceReceived::CreateUObject(this, &AMyGameController::OnBalanceReceived));
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogTemp, Error, TEXT("Failed to connect wallet: %s"), *Error);
|
||||
}
|
||||
}
|
||||
|
||||
void AMyGameController::OnBalanceReceived(const FString& Balance, const FString& Symbol)
|
||||
{
|
||||
UE_LOG(LogTemp, Display, TEXT("Balance: %s %s"), *Balance, *Symbol);
|
||||
}
|
||||
```
|
||||
|
||||
### Using Blueprints
|
||||
|
||||
Alternatively, use Blueprint nodes provided by the Thirdweb plugin:
|
||||
|
||||
1. Create a new Blueprint class
|
||||
2. Add a component for user interface (like widget blueprint)
|
||||
3. Use the Thirdweb nodes found in the node palette
|
||||
4. Connect nodes for wallet login, balance checking, etc.
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
Optimize your wallet implementation:
|
||||
|
||||
1. **Create Client Once**
|
||||
- Initialize the thirdweb client once and reuse it
|
||||
- Avoid recreating the client for each operation
|
||||
|
||||
2. **Caching**
|
||||
- Use built-in React query capabilities
|
||||
- Avoid polling or constant refetching
|
||||
- Implement proper state management
|
||||
|
||||
3. **Lazy Loading**
|
||||
- The SDK loads wallet connectors on demand
|
||||
- Modularity reduces bundle size
|
||||
- Only import what you need
|
||||
|
||||
## Next Steps
|
||||
|
||||
After setting up wallet functionality, explore these advanced topics:
|
||||
|
||||
- [Enable Account Abstraction](/connect/account-abstraction/overview)
|
||||
- [Implement Sign In with Ethereum](/connect/auth)
|
||||
- [Configure Gas Sponsorship](/connect/account-abstraction/sponsorship-rules)
|
||||
- [Explore Payment Options](/connect/pay/overview)
|
||||
|
||||
## Explore Full SDK Documentation
|
||||
|
||||
For comprehensive guides on implementing the full thirdweb SDK, explore our language-specific documentation:
|
||||
|
||||
<Grid>
|
||||
<SDKCard
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { createMetadata } from "@doc";
|
||||
|
||||
export const metadata = createMetadata({
|
||||
title: "In-App Wallet FAQs",
|
||||
description: "Frequently asked questions about thirdweb's In-App Wallet",
|
||||
image: {
|
||||
title: "FAQs for thirdweb In-App Wallet",
|
||||
icon: "wallets",
|
||||
},
|
||||
});
|
||||
|
||||
# In-App Wallet FAQs
|
||||
|
||||
### How do users connect to their in-app wallet in a third-party app?
|
||||
|
||||
Users can access it by doing a manual connection via wallet connect. They will then have to use the connect button or embed and select manage wallet -> connect an App.
|
||||
|
||||
### How is pricing calculated for in-app wallets?
|
||||
|
||||
In-app wallets are billed based on "monthly active wallets". An active wallet is defined as a wallet where a user logs in during the billing period. In-app wallets are completely free up to 1,000 monthly active wallets, and $0.02 per wallet after that.
|
||||
|
||||
### What security measures should users take when connecting their in-app wallet?
|
||||
|
||||
Users should always verify they are connecting to legitimate websites by checking the URL carefully. Never connect to unverified or suspicious websites. The connection process should only be initiated through the official WalletConnect interface or trusted dApp integrations.
|
||||
|
||||
### Can developers integrate both in-app wallets and external wallets in the same application?
|
||||
|
||||
Yes, developers can build a unified experience that supports both in-app wallets and external wallets (like MetaMask) using the same code base. The SDK provides a wallet signer that handles how all wallet types interact with signatures and smart contracts consistently, though the initial connection flow will differ by wallet type.
|
||||
|
||||
### What blockchain networks are supported for in-app wallet connections?
|
||||
|
||||
In-app wallets are fully EVM (Ethereum Virtual Machine) compatible and support all EVM chains. This includes major networks like Ethereum Mainnet, Polygon, Optimism, Base, and other EVM-compatible blockchains.
|
||||
@@ -1,17 +1,17 @@
|
||||
import { Details, createMetadata } from "@doc";
|
||||
|
||||
export const metadata = createMetadata({
|
||||
title: "Thirdweb Pay Quickstart | thirdweb .NET SDK",
|
||||
description: "End to end examples for using the Thirdweb Pay SDK.",
|
||||
title: "Thirdweb Universal Bridge Quickstart | thirdweb .NET SDK",
|
||||
description: "End to end examples for using the Thirdweb Universal Bridge SDK.",
|
||||
});
|
||||
|
||||
# Quickstart
|
||||
|
||||
Thirdweb Pay is a powerful service that allows you to easily integrate fiat onramps and cross-chain crypto purchases.
|
||||
Thirdweb Universal Bridge is a powerful service that allows you to easily integrate fiat onramps and cross-chain crypto purchases.
|
||||
|
||||
## Cross-Chain Swaps
|
||||
|
||||
This example demonstrates how to perform a cross-chain swap using the Thirdweb Pay SDK.
|
||||
This example demonstrates how to perform a cross-chain swap using the Thirdweb Universal Bridge SDK.
|
||||
|
||||
```csharp
|
||||
using Thirdweb.Pay;
|
||||
@@ -45,7 +45,7 @@ while (currentSwapStatus is not SwapStatus.COMPLETED and not SwapStatus.FAILED)
|
||||
|
||||
## Fiat Onramps
|
||||
|
||||
This example demonstrates how to use the Thirdweb Pay SDK to initiate a fiat onramp.
|
||||
This example demonstrates how to use the Thirdweb Universal Bridge SDK to initiate a fiat onramp.
|
||||
|
||||
```csharp
|
||||
// Get a Buy with Fiat quote
|
||||
@@ -80,4 +80,4 @@ while (currentOnRampStatus is not OnRampStatus.ON_RAMP_TRANSFER_COMPLETED and no
|
||||
|
||||
## Learn More
|
||||
|
||||
Please refer to our official [Thirdweb Pay](/connect/pay/overview) documentation for more general information.
|
||||
Please refer to our official [Thirdweb Universal Bridge](/connect/pay/overview) documentation for more general information.
|
||||
|
||||
@@ -27,3 +27,4 @@ making it a powerful tool for building dynamic blockchain applications.
|
||||
| Fetch all transactions emitted by a single wallet across multiple chains. | Use Transactions Blueprint to filter transactions by wallet address across different chain_ids to provide a historical view of cross-chain transactions. |
|
||||
| Fetch all tokens owned by a specific wallet address. | Use Events Blueprint to find transfer events where the wallet address is the recipient, providing a list of token balances. |
|
||||
| Detect wallet staking activity on platforms such as AAVE or other DeFi protocols. | Use Events Blueprint to filter for staking-related events such as deposits, and monitor which wallets are actively staking. |
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.0.4",
|
||||
"lucide-react": "0.482.0",
|
||||
"lucide-react": "0.483.0",
|
||||
"next": "15.2.2",
|
||||
"next-themes": "^0.4.6",
|
||||
"react": "19.0.0",
|
||||
|
||||
@@ -1,5 +1,197 @@
|
||||
# thirdweb
|
||||
|
||||
## 5.93.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#6464](https://github.com/thirdweb-dev/js/pull/6464) [`654f879`](https://github.com/thirdweb-dev/js/commit/654f8795b45f6a14f5e4808e71f2d6143ef46171) Thanks [@gregfromstl](https://github.com/gregfromstl)! - Adds a new `Bridge` module to the thirdweb SDK to access the Universal Bridge.
|
||||
|
||||
## Features
|
||||
|
||||
### Buy & Sell Operations
|
||||
|
||||
The Bridge module makes it easy to buy and sell tokens across chains:
|
||||
|
||||
- `Bridge.Buy` - For specifying the destination amount you want to receive
|
||||
- `Bridge.Sell` - For specifying the origin amount you want to send
|
||||
|
||||
Each operation provides two functions:
|
||||
|
||||
1. `quote` - Get an estimate without connecting a wallet
|
||||
2. `prepare` - Get a finalized quote with transaction data
|
||||
|
||||
#### Buy Example
|
||||
|
||||
```typescript
|
||||
import { Bridge, toWei, NATIVE_TOKEN_ADDRESS } from "thirdweb";
|
||||
|
||||
// First, get a quote to see approximately how much you'll pay
|
||||
const buyQuote = await Bridge.Buy.quote({
|
||||
originChainId: 1, // Ethereum
|
||||
originTokenAddress: NATIVE_TOKEN_ADDRESS,
|
||||
destinationChainId: 10, // Optimism
|
||||
destinationTokenAddress: NATIVE_TOKEN_ADDRESS,
|
||||
buyAmountWei: toWei("0.01"), // I want to receive 0.01 ETH on Optimism
|
||||
client: thirdwebClient,
|
||||
});
|
||||
|
||||
console.log(
|
||||
`To get ${buyQuote.destinationAmount} wei on destination chain, you need to pay ${buyQuote.originAmount} wei`,
|
||||
);
|
||||
|
||||
// When ready to execute, prepare the transaction
|
||||
const preparedBuy = await Bridge.Buy.prepare({
|
||||
originChainId: 1,
|
||||
originTokenAddress: NATIVE_TOKEN_ADDRESS,
|
||||
destinationChainId: 10,
|
||||
destinationTokenAddress: NATIVE_TOKEN_ADDRESS,
|
||||
buyAmountWei: toWei("0.01"),
|
||||
sender: "0x...", // Your wallet address
|
||||
receiver: "0x...", // Recipient address (can be the same as sender)
|
||||
client: thirdwebClient,
|
||||
});
|
||||
|
||||
// The prepared quote contains the transactions you need to execute
|
||||
console.log(`Transactions to execute: ${preparedBuy.transactions.length}`);
|
||||
```
|
||||
|
||||
#### Sell Example
|
||||
|
||||
```typescript
|
||||
import { Bridge, toWei } from "thirdweb";
|
||||
|
||||
// First, get a quote to see approximately how much you'll receive
|
||||
const sellQuote = await Bridge.Sell.quote({
|
||||
originChainId: 1, // Ethereum
|
||||
originTokenAddress: NATIVE_TOKEN_ADDRESS,
|
||||
destinationChainId: 10, // Optimism
|
||||
destinationTokenAddress: NATIVE_TOKEN_ADDRESS,
|
||||
sellAmountWei: toWei("0.01"), // I want to sell 0.01 ETH from Ethereum
|
||||
client: thirdwebClient,
|
||||
});
|
||||
|
||||
console.log(
|
||||
`If you send ${sellQuote.originAmount} wei, you'll receive approximately ${sellQuote.destinationAmount} wei`,
|
||||
);
|
||||
|
||||
// When ready to execute, prepare the transaction
|
||||
const preparedSell = await Bridge.Sell.prepare({
|
||||
originChainId: 1,
|
||||
originTokenAddress: NATIVE_TOKEN_ADDRESS,
|
||||
destinationChainId: 10,
|
||||
destinationTokenAddress: NATIVE_TOKEN_ADDRESS,
|
||||
sellAmountWei: toWei("0.01"),
|
||||
sender: "0x...", // Your wallet address
|
||||
receiver: "0x...", // Recipient address (can be the same as sender)
|
||||
client: thirdwebClient,
|
||||
});
|
||||
|
||||
// Execute the transactions in sequence
|
||||
for (const tx of preparedSell.transactions) {
|
||||
// Send the transaction using your wallet
|
||||
// Wait for it to be mined
|
||||
}
|
||||
```
|
||||
|
||||
### Bridge Routes
|
||||
|
||||
You can discover available bridge routes using the `routes` function:
|
||||
|
||||
```typescript
|
||||
import { Bridge, NATIVE_TOKEN_ADDRESS } from "thirdweb";
|
||||
|
||||
// Get all available routes
|
||||
const allRoutes = await Bridge.routes({
|
||||
client: thirdwebClient,
|
||||
});
|
||||
|
||||
// Filter routes for a specific token or chain
|
||||
const filteredRoutes = await Bridge.routes({
|
||||
originChainId: 1, // From Ethereum
|
||||
originTokenAddress: NATIVE_TOKEN_ADDRESS,
|
||||
destinationChainId: 10, // To Optimism
|
||||
client: thirdwebClient,
|
||||
});
|
||||
|
||||
// Paginate through routes
|
||||
const paginatedRoutes = await Bridge.routes({
|
||||
limit: 10,
|
||||
offset: 0,
|
||||
client: thirdwebClient,
|
||||
});
|
||||
```
|
||||
|
||||
### Bridge Transaction Status
|
||||
|
||||
After executing bridge transactions, you can check their status:
|
||||
|
||||
```typescript
|
||||
import { Bridge } from "thirdweb";
|
||||
|
||||
// Check the status of a bridge transaction
|
||||
const bridgeStatus = await Bridge.status({
|
||||
transactionHash:
|
||||
"0xe199ef82a0b6215221536e18ec512813c1aa10b4f5ed0d4dfdfcd703578da56d",
|
||||
chainId: 8453, // The chain ID where the transaction was initiated
|
||||
client: thirdwebClient,
|
||||
});
|
||||
|
||||
// The status will be one of: "COMPLETED", "PENDING", "FAILED", or "NOT_FOUND"
|
||||
if (bridgeStatus.status === "completed") {
|
||||
console.log(`
|
||||
Bridge completed!
|
||||
Sent: ${bridgeStatus.originAmount} wei on chain ${bridgeStatus.originChainId}
|
||||
Received: ${bridgeStatus.destinationAmount} wei on chain ${bridgeStatus.destinationChainId}
|
||||
`);
|
||||
} else if (bridgeStatus.status === "pending") {
|
||||
console.log("Bridge transaction is still pending...");
|
||||
} else {
|
||||
console.log("Bridge transaction failed");
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
The Bridge module provides consistent error handling with descriptive error messages:
|
||||
|
||||
```typescript
|
||||
try {
|
||||
await Bridge.Buy.quote({
|
||||
// ...params
|
||||
});
|
||||
} catch (error) {
|
||||
// Errors will have the format: "ErrorCode | Error message details"
|
||||
console.error(error.message); // e.g. "AmountTooHigh | The provided amount is too high for the requested route."
|
||||
}
|
||||
```
|
||||
|
||||
## Types
|
||||
|
||||
The Bridge module exports the following TypeScript types:
|
||||
|
||||
- `Route` - Describes a bridge route between chains and tokens
|
||||
- `Status` - Represents the status of a bridge transaction
|
||||
- `Quote` - Contains quote information for a bridge transaction
|
||||
- `PreparedQuote` - Extends Quote with transaction data
|
||||
|
||||
## Integration
|
||||
|
||||
The Bridge module is accessible as a top-level export:
|
||||
|
||||
```typescript
|
||||
import { Bridge } from "thirdweb";
|
||||
```
|
||||
|
||||
Use `Bridge.Buy`, `Bridge.Sell`, `Bridge.routes`, and `Bridge.status` to access the corresponding functionality.
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#6503](https://github.com/thirdweb-dev/js/pull/6503) [`47f8cd6`](https://github.com/thirdweb-dev/js/commit/47f8cd6d8d855bd01c4512098d7d4f0fbb44677e) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - UI cleanup for multistep swaps in PayEmbed
|
||||
|
||||
- [#6506](https://github.com/thirdweb-dev/js/pull/6506) [`d854021`](https://github.com/thirdweb-dev/js/commit/d8540216f1da8be67aee55f5583f9a019caf18c1) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Pass along chainId to internal 1193 provider when connecting
|
||||
|
||||
- [#6505](https://github.com/thirdweb-dev/js/pull/6505) [`7890145`](https://github.com/thirdweb-dev/js/commit/78901457cfb282f33cc7fb78895ea9c225e455bd) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Fix requireApproval option not enforced for wallet connection
|
||||
|
||||
## 5.92.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "thirdweb",
|
||||
"version": "5.92.3",
|
||||
"version": "5.93.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/thirdweb-dev/js.git#main"
|
||||
@@ -128,6 +128,11 @@
|
||||
"import": "./dist/esm/exports/ai.js",
|
||||
"default": "./dist/cjs/exports/ai.js"
|
||||
},
|
||||
"./bridge": {
|
||||
"types": "./dist/types/exports/bridge.d.ts",
|
||||
"import": "./dist/esm/exports/bridge.js",
|
||||
"default": "./dist/cjs/exports/bridge.js"
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"typesVersions": {
|
||||
@@ -188,6 +193,9 @@
|
||||
],
|
||||
"ai": [
|
||||
"./dist/types/exports/ai.d.ts"
|
||||
],
|
||||
"bridge": [
|
||||
"./dist/types/exports/bridge.d.ts"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
@@ -15,7 +15,9 @@ import type { EIP1193Provider } from "./types.js";
|
||||
* Options for creating an EIP-1193 provider adapter.
|
||||
*/
|
||||
export type FromEip1193AdapterOptions = {
|
||||
provider: EIP1193Provider | (() => Promise<EIP1193Provider>);
|
||||
provider:
|
||||
| EIP1193Provider
|
||||
| ((params?: { chainId?: number }) => Promise<EIP1193Provider>);
|
||||
walletId?: WalletId;
|
||||
};
|
||||
|
||||
@@ -63,13 +65,11 @@ export function fromProvider(options: FromEip1193AdapterOptions): Wallet {
|
||||
let account: Account | undefined = undefined;
|
||||
let chain: Chain | undefined = undefined;
|
||||
let provider: EIP1193Provider | undefined = undefined;
|
||||
const getProvider = async () => {
|
||||
if (!provider) {
|
||||
provider =
|
||||
typeof options.provider === "function"
|
||||
? await options.provider()
|
||||
: options.provider;
|
||||
}
|
||||
const getProvider = async (params?: { chainId?: number }) => {
|
||||
provider =
|
||||
typeof options.provider === "function"
|
||||
? await options.provider(params)
|
||||
: options.provider;
|
||||
return provider;
|
||||
};
|
||||
|
||||
@@ -118,7 +118,7 @@ export function fromProvider(options: FromEip1193AdapterOptions): Wallet {
|
||||
const [connectedAccount, connectedChain, doDisconnect, doSwitchChain] =
|
||||
await connectEip1193Wallet({
|
||||
id,
|
||||
provider: await getProvider(),
|
||||
provider: await getProvider({ chainId: connectOptions.chain?.id }),
|
||||
client: connectOptions.client,
|
||||
chain: connectOptions.chain,
|
||||
emitter,
|
||||
@@ -141,7 +141,7 @@ export function fromProvider(options: FromEip1193AdapterOptions): Wallet {
|
||||
const [connectedAccount, connectedChain, doDisconnect, doSwitchChain] =
|
||||
await autoConnectEip1193Wallet({
|
||||
id,
|
||||
provider: await getProvider(),
|
||||
provider: await getProvider({ chainId: connectOptions.chain?.id }),
|
||||
emitter,
|
||||
chain: connectOptions.chain,
|
||||
client: connectOptions.client,
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import { toWei } from "src/utils/units.js";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { TEST_CLIENT } from "~test/test-clients.js";
|
||||
import * as Buy from "./Buy.js";
|
||||
|
||||
describe("Bridge.Buy.quote", () => {
|
||||
it("should get a valid quote", async () => {
|
||||
const quote = await Buy.quote({
|
||||
originChainId: 1,
|
||||
originTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
|
||||
destinationChainId: 10,
|
||||
destinationTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
|
||||
buyAmountWei: toWei("0.01"),
|
||||
client: TEST_CLIENT,
|
||||
});
|
||||
|
||||
expect(quote).toBeDefined();
|
||||
expect(quote.destinationAmount).toEqual(toWei("0.01"));
|
||||
expect(quote.intent).toBeDefined();
|
||||
});
|
||||
|
||||
it("should surface any errors", async () => {
|
||||
await expect(
|
||||
Buy.quote({
|
||||
originChainId: 1,
|
||||
originTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
|
||||
destinationChainId: 10,
|
||||
destinationTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
|
||||
buyAmountWei: toWei("1000000000"),
|
||||
client: TEST_CLIENT,
|
||||
}),
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`[Error: AMOUNT_TOO_HIGH | The provided amount is too high for the requested route.]`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Bridge.Buy.prepare", () => {
|
||||
it("should get a valid prepared quote", async () => {
|
||||
const quote = await Buy.prepare({
|
||||
originChainId: 1,
|
||||
originTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
|
||||
destinationChainId: 10,
|
||||
destinationTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
|
||||
buyAmountWei: toWei("0.01"),
|
||||
sender: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
|
||||
receiver: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
|
||||
client: TEST_CLIENT,
|
||||
});
|
||||
|
||||
expect(quote).toBeDefined();
|
||||
expect(quote.destinationAmount).toEqual(toWei("0.01"));
|
||||
expect(quote.transactions).toBeDefined();
|
||||
expect(quote.transactions.length).toBeGreaterThan(0);
|
||||
expect(quote.intent).toBeDefined();
|
||||
});
|
||||
|
||||
it("should surface any errors", async () => {
|
||||
await expect(
|
||||
Buy.prepare({
|
||||
originChainId: 1,
|
||||
originTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
|
||||
destinationChainId: 10,
|
||||
destinationTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
|
||||
buyAmountWei: toWei("1000000000"),
|
||||
sender: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
|
||||
receiver: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
|
||||
client: TEST_CLIENT,
|
||||
}),
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`[Error: AMOUNT_TOO_HIGH | The provided amount is too high for the requested route.]`,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,267 @@
|
||||
import type { Address as ox__Address } from "ox";
|
||||
import type { ThirdwebClient } from "../client/client.js";
|
||||
import { getClientFetch } from "../utils/fetch.js";
|
||||
import { UNIVERSAL_BRIDGE_URL } from "./constants.js";
|
||||
import type { PreparedQuote, Quote } from "./types/Quote.js";
|
||||
|
||||
/**
|
||||
* Retrieves a Universal Bridge quote for the provided buy intent. The quote will specify the necessary `originAmount` to receive the desired `destinationAmount`, which is specified with the `buyAmountWei` option.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { Bridge, NATIVE_TOKEN_ADDRESS } from "thirdweb";
|
||||
*
|
||||
* const quote = await Bridge.Buy.quote({
|
||||
* originChainId: 1,
|
||||
* originTokenAddress: NATIVE_TOKEN_ADDRESS,
|
||||
* destinationChainId: 10,
|
||||
* destinationTokenAddress: NATIVE_TOKEN_ADDRESS,
|
||||
* buyAmountWei: toWei("0.01"),
|
||||
* client: thirdwebClient,
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* This will return a quote that might look like:
|
||||
* ```typescript
|
||||
* {
|
||||
* originAmount: 10000026098875381n,
|
||||
* destinationAmount: 1000000000000000000n,
|
||||
* blockNumber: 22026509n,
|
||||
* timestamp: 1741730936680,
|
||||
* estimatedExecutionTimeMs: 1000
|
||||
* intent: {
|
||||
* originChainId: 1,
|
||||
* originTokenAddress: NATIVE_TOKEN_ADDRESS,
|
||||
* destinationChainId: 10,
|
||||
* destinationTokenAddress: NATIVE_TOKEN_ADDRESS,
|
||||
* buyAmountWei: 1000000000000000000n
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* The quote is an **estimate** for how much you would expect to pay for a specific buy. This quote is not guaranteed and you should use `Buy.prepare` to get a finalized quote with transaction data ready for execution.
|
||||
* So why use `quote`? The quote function is sometimes slightly faster than `prepare`, and can be used before the user connects their wallet.
|
||||
*
|
||||
* You can access this functions input and output types with `Buy.quote.Options` and `Buy.quote.Result`, respectively.
|
||||
*
|
||||
* @param options - The options for the quote.
|
||||
* @param options.originChainId - The chain ID of the origin token.
|
||||
* @param options.originTokenAddress - The address of the origin token.
|
||||
* @param options.destinationChainId - The chain ID of the destination token.
|
||||
* @param options.destinationTokenAddress - The address of the destination token.
|
||||
* @param options.buyAmountWei - The amount of the origin token to buy.
|
||||
* @param options.client - Your thirdweb client.
|
||||
*
|
||||
* @returns A promise that resolves to a non-finalized quote for the requested buy.
|
||||
*
|
||||
* @throws Will throw an error if there is an issue fetching the quote.
|
||||
* @bridge
|
||||
* @beta
|
||||
*/
|
||||
export async function quote(options: quote.Options): Promise<quote.Result> {
|
||||
const {
|
||||
originChainId,
|
||||
originTokenAddress,
|
||||
destinationChainId,
|
||||
destinationTokenAddress,
|
||||
buyAmountWei,
|
||||
client,
|
||||
} = options;
|
||||
|
||||
const clientFetch = getClientFetch(client);
|
||||
const url = new URL(`${UNIVERSAL_BRIDGE_URL}/buy/quote`);
|
||||
url.searchParams.set("originChainId", originChainId.toString());
|
||||
url.searchParams.set("originTokenAddress", originTokenAddress);
|
||||
url.searchParams.set("destinationChainId", destinationChainId.toString());
|
||||
url.searchParams.set("destinationTokenAddress", destinationTokenAddress);
|
||||
url.searchParams.set("buyAmountWei", buyAmountWei.toString());
|
||||
|
||||
const response = await clientFetch(url.toString());
|
||||
if (!response.ok) {
|
||||
const errorJson = await response.json();
|
||||
throw new Error(`${errorJson.code} | ${errorJson.message}`);
|
||||
}
|
||||
|
||||
const { data }: { data: Quote } = await response.json();
|
||||
return {
|
||||
originAmount: BigInt(data.originAmount),
|
||||
destinationAmount: BigInt(data.destinationAmount),
|
||||
blockNumber: data.blockNumber ? BigInt(data.blockNumber) : undefined,
|
||||
timestamp: data.timestamp,
|
||||
estimatedExecutionTimeMs: data.estimatedExecutionTimeMs,
|
||||
intent: {
|
||||
originChainId,
|
||||
originTokenAddress,
|
||||
destinationChainId,
|
||||
destinationTokenAddress,
|
||||
buyAmountWei,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export declare namespace quote {
|
||||
type Options = {
|
||||
originChainId: number;
|
||||
originTokenAddress: ox__Address.Address;
|
||||
destinationChainId: number;
|
||||
destinationTokenAddress: ox__Address.Address;
|
||||
buyAmountWei: bigint;
|
||||
client: ThirdwebClient;
|
||||
};
|
||||
|
||||
type Result = Quote & {
|
||||
intent: {
|
||||
originChainId: number;
|
||||
originTokenAddress: ox__Address.Address;
|
||||
destinationChainId: number;
|
||||
destinationTokenAddress: ox__Address.Address;
|
||||
buyAmountWei: bigint;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a **finalized** Universal Bridge quote for the provided buy request with transaction data. This function will return everything `quote` does, with the addition of a series of prepared transactions and the associated expiration timestamp.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { Bridge, NATIVE_TOKEN_ADDRESS } from "thirdweb";
|
||||
*
|
||||
* const quote = await Bridge.Buy.prepare({
|
||||
* originChainId: 1,
|
||||
* originTokenAddress: NATIVE_TOKEN_ADDRESS,
|
||||
* destinationChainId: 10,
|
||||
* destinationTokenAddress: NATIVE_TOKEN_ADDRESS,
|
||||
* buyAmountWei: toWei("0.01"),
|
||||
* client: thirdwebClient,
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* This will return a quote that might look like:
|
||||
* ```typescript
|
||||
* {
|
||||
* originAmount: 10000026098875381n,
|
||||
* destinationAmount: 1000000000000000000n,
|
||||
* blockNumber: 22026509n,
|
||||
* timestamp: 1741730936680,
|
||||
* estimatedExecutionTimeMs: 1000
|
||||
* transactions: [
|
||||
* {
|
||||
* to: NATIVE_TOKEN_ADDRESS,
|
||||
* value: 10000026098875381n,
|
||||
* data: "0x",
|
||||
* chainId: 10,
|
||||
* type: "eip1559"
|
||||
* }
|
||||
* ],
|
||||
* expiration: 1741730936680,
|
||||
* intent: {
|
||||
* originChainId: 1,
|
||||
* originTokenAddress: NATIVE_TOKEN_ADDRESS,
|
||||
* destinationChainId: 10,
|
||||
* destinationTokenAddress: NATIVE_TOKEN_ADDRESS,
|
||||
* buyAmountWei: 1000000000000000000n
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* ## Sending the transactions
|
||||
* The `transactions` array is a series of [ox](https://oxlib.sh) EIP-1559 transactions that must be executed one after the other in order to fulfill the complete route. There are a few things to keep in mind when executing these transactions:
|
||||
* - Approvals and other preparation transactions are not included in the transactions array.
|
||||
* - All transactions are assumed to be executed by the `sender` address, regardless of which chain they are on. The final transaction will use the `receiver` as the recipient address.
|
||||
* - If an `expiration` timestamp is provided, all transactions must be executed before that time to guarantee successful execution at the specified price.
|
||||
*
|
||||
* NOTE: To get the status of each transaction, use `Bridge.status` rather than checking for transaction inclusion. This function will ensure full bridge completion on the destination chain.
|
||||
*
|
||||
* You can access this functions input and output types with `Buy.prepare.Options` and `Buy.prepare.Result`, respectively.
|
||||
*
|
||||
* @param options - The options for the quote.
|
||||
* @param options.originChainId - The chain ID of the origin token.
|
||||
* @param options.originTokenAddress - The address of the origin token.
|
||||
* @param options.destinationChainId - The chain ID of the destination token.
|
||||
* @param options.destinationTokenAddress - The address of the destination token.
|
||||
* @param options.buyAmountWei - The amount of the origin token to buy.
|
||||
* @param options.sender - The address of the sender.
|
||||
* @param options.receiver - The address of the recipient.
|
||||
* @param options.client - Your thirdweb client.
|
||||
*
|
||||
* @returns A promise that resolves to a non-finalized quote for the requested buy.
|
||||
*
|
||||
* @throws Will throw an error if there is an issue fetching the quote.
|
||||
* @bridge
|
||||
* @beta
|
||||
*/
|
||||
export async function prepare(
|
||||
options: prepare.Options,
|
||||
): Promise<prepare.Result> {
|
||||
const {
|
||||
originChainId,
|
||||
originTokenAddress,
|
||||
destinationChainId,
|
||||
destinationTokenAddress,
|
||||
buyAmountWei,
|
||||
sender,
|
||||
receiver,
|
||||
client,
|
||||
} = options;
|
||||
|
||||
const clientFetch = getClientFetch(client);
|
||||
const url = new URL(`${UNIVERSAL_BRIDGE_URL}/buy/prepare`);
|
||||
url.searchParams.set("originChainId", originChainId.toString());
|
||||
url.searchParams.set("originTokenAddress", originTokenAddress);
|
||||
url.searchParams.set("destinationChainId", destinationChainId.toString());
|
||||
url.searchParams.set("destinationTokenAddress", destinationTokenAddress);
|
||||
url.searchParams.set("buyAmountWei", buyAmountWei.toString());
|
||||
url.searchParams.set("sender", sender);
|
||||
url.searchParams.set("receiver", receiver);
|
||||
|
||||
const response = await clientFetch(url.toString());
|
||||
if (!response.ok) {
|
||||
const errorJson = await response.json();
|
||||
throw new Error(`${errorJson.code} | ${errorJson.message}`);
|
||||
}
|
||||
|
||||
const { data }: { data: PreparedQuote } = await response.json();
|
||||
return {
|
||||
originAmount: BigInt(data.originAmount),
|
||||
destinationAmount: BigInt(data.destinationAmount),
|
||||
blockNumber: data.blockNumber ? BigInt(data.blockNumber) : undefined,
|
||||
timestamp: data.timestamp,
|
||||
estimatedExecutionTimeMs: data.estimatedExecutionTimeMs,
|
||||
transactions: data.transactions.map((transaction) => ({
|
||||
...transaction,
|
||||
value: transaction.value ? BigInt(transaction.value) : undefined,
|
||||
})),
|
||||
expiration: data.expiration,
|
||||
intent: {
|
||||
originChainId,
|
||||
originTokenAddress,
|
||||
destinationChainId,
|
||||
destinationTokenAddress,
|
||||
buyAmountWei,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export declare namespace prepare {
|
||||
type Options = {
|
||||
originChainId: number;
|
||||
originTokenAddress: ox__Address.Address;
|
||||
destinationChainId: number;
|
||||
destinationTokenAddress: ox__Address.Address;
|
||||
buyAmountWei: bigint;
|
||||
sender: ox__Address.Address;
|
||||
receiver: ox__Address.Address;
|
||||
client: ThirdwebClient;
|
||||
};
|
||||
|
||||
type Result = PreparedQuote & {
|
||||
intent: {
|
||||
originChainId: number;
|
||||
originTokenAddress: ox__Address.Address;
|
||||
destinationChainId: number;
|
||||
destinationTokenAddress: ox__Address.Address;
|
||||
buyAmountWei: bigint;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
import { http, passthrough } from "msw";
|
||||
import { setupServer } from "msw/node";
|
||||
import { afterAll, afterEach, beforeAll, describe, expect, it } from "vitest";
|
||||
import { TEST_CLIENT } from "~test/test-clients.js";
|
||||
import { routes } from "./Routes.js";
|
||||
|
||||
const server = setupServer(
|
||||
http.get("https://bridge.thirdweb.com/v1/routes", () => {
|
||||
passthrough();
|
||||
}),
|
||||
);
|
||||
|
||||
describe("Bridge.routes", () => {
|
||||
beforeAll(() => server.listen());
|
||||
afterEach(() => server.resetHandlers());
|
||||
afterAll(() => server.close());
|
||||
|
||||
it("should get a valid list of routes", async () => {
|
||||
const allRoutes = await routes({
|
||||
client: TEST_CLIENT,
|
||||
});
|
||||
|
||||
expect(allRoutes).toBeDefined();
|
||||
expect(Array.isArray(allRoutes)).toBe(true);
|
||||
});
|
||||
|
||||
it("should filter routes by origin chain", async () => {
|
||||
const filteredRoutes = await routes({
|
||||
client: TEST_CLIENT,
|
||||
originChainId: 1,
|
||||
});
|
||||
|
||||
expect(filteredRoutes).toBeDefined();
|
||||
expect(Array.isArray(filteredRoutes)).toBe(true);
|
||||
expect(
|
||||
filteredRoutes.every((route) => route.originToken.chainId === 1),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("should filter routes by destination chain", async () => {
|
||||
const filteredRoutes = await routes({
|
||||
client: TEST_CLIENT,
|
||||
destinationChainId: 1,
|
||||
});
|
||||
|
||||
expect(filteredRoutes).toBeDefined();
|
||||
expect(Array.isArray(filteredRoutes)).toBe(true);
|
||||
expect(
|
||||
filteredRoutes.every((route) => route.destinationToken.chainId === 1),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("should filter routes by origin token", async () => {
|
||||
const filteredRoutes = await routes({
|
||||
client: TEST_CLIENT,
|
||||
originTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
|
||||
});
|
||||
|
||||
expect(filteredRoutes).toBeDefined();
|
||||
expect(Array.isArray(filteredRoutes)).toBe(true);
|
||||
expect(
|
||||
filteredRoutes.every(
|
||||
(route) =>
|
||||
route.originToken.address ===
|
||||
"0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("should filter routes by destination token", async () => {
|
||||
const filteredRoutes = await routes({
|
||||
client: TEST_CLIENT,
|
||||
destinationTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
|
||||
});
|
||||
|
||||
expect(filteredRoutes).toBeDefined();
|
||||
expect(Array.isArray(filteredRoutes)).toBe(true);
|
||||
expect(
|
||||
filteredRoutes.every(
|
||||
(route) =>
|
||||
route.destinationToken.address ===
|
||||
"0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("should combine filters", async () => {
|
||||
const filteredRoutes = await routes({
|
||||
client: TEST_CLIENT,
|
||||
originChainId: 1,
|
||||
destinationChainId: 10,
|
||||
originTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
|
||||
destinationTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
|
||||
});
|
||||
|
||||
expect(filteredRoutes).toBeDefined();
|
||||
expect(Array.isArray(filteredRoutes)).toBe(true);
|
||||
expect(filteredRoutes.length).toBeGreaterThan(0);
|
||||
expect(
|
||||
filteredRoutes.every(
|
||||
(route) =>
|
||||
route.originToken.chainId === 1 &&
|
||||
route.destinationToken.chainId === 10 &&
|
||||
route.originToken.address ===
|
||||
"0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" &&
|
||||
route.destinationToken.address ===
|
||||
"0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("should respect limit and offset", async () => {
|
||||
const page1Routes = await routes({
|
||||
client: TEST_CLIENT,
|
||||
limit: 1,
|
||||
offset: 1,
|
||||
});
|
||||
|
||||
expect(page1Routes).toBeDefined();
|
||||
expect(Array.isArray(page1Routes)).toBe(true);
|
||||
expect(page1Routes.length).toBe(1);
|
||||
|
||||
const page2Routes = await routes({
|
||||
client: TEST_CLIENT,
|
||||
limit: 1,
|
||||
offset: 2,
|
||||
});
|
||||
|
||||
expect(page2Routes).toBeDefined();
|
||||
expect(Array.isArray(page2Routes)).toBe(true);
|
||||
expect(page2Routes.length).toBe(1);
|
||||
|
||||
expect(JSON.stringify(page1Routes)).not.toEqual(
|
||||
JSON.stringify(page2Routes),
|
||||
);
|
||||
});
|
||||
|
||||
it("should surface any errors", async () => {
|
||||
server.use(
|
||||
http.get("https://bridge.thirdweb.com/v1/routes", () => {
|
||||
return Response.json(
|
||||
{
|
||||
code: "InvalidRoutesRequest",
|
||||
message: "The provided request is invalid.",
|
||||
},
|
||||
{ status: 400 },
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
await expect(
|
||||
routes({
|
||||
client: TEST_CLIENT,
|
||||
limit: 1000,
|
||||
offset: 1000,
|
||||
}),
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`[Error: InvalidRoutesRequest | The provided request is invalid.]`,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,160 @@
|
||||
import type { Address as ox__Address, Hex as ox__Hex } from "ox";
|
||||
import type { ThirdwebClient } from "../client/client.js";
|
||||
import { getClientFetch } from "../utils/fetch.js";
|
||||
import { UNIVERSAL_BRIDGE_URL } from "./constants.js";
|
||||
import type { Route } from "./types/Route.js";
|
||||
|
||||
/**
|
||||
* Retrieves supported Universal Bridge routes based on the provided filters.
|
||||
*
|
||||
* When multiple filters are specified, a route must satisfy all filters to be included (it acts as an AND operator).
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { Bridge } from "thirdweb";
|
||||
*
|
||||
* const routes = await Bridge.routes({
|
||||
* client: thirdwebClient,
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* Returned routes might look something like:
|
||||
* ```typescript
|
||||
* [
|
||||
* {
|
||||
* destinationToken: {
|
||||
* address: "0x12c88a3C30A7AaBC1dd7f2c08a97145F5DCcD830",
|
||||
* chainId: 1,
|
||||
* decimals: 18,
|
||||
* iconUri: "https://assets.coingecko.com/coins/images/37207/standard/G.jpg",
|
||||
* name: "G7",
|
||||
* symbol: "G7",
|
||||
* },
|
||||
* originToken: {
|
||||
* address: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
|
||||
* chainId: 480,
|
||||
* decimals: 18,
|
||||
* iconUri: "https://assets.relay.link/icons/1/light.png",
|
||||
* name: "Ether",
|
||||
* symbol: "ETH",
|
||||
* }
|
||||
* },
|
||||
* {
|
||||
* destinationToken: {
|
||||
* address: "0x4d224452801ACEd8B2F0aebE155379bb5D594381",
|
||||
* chainId: 1,
|
||||
* decimals: 18,
|
||||
* iconUri: "https://coin-images.coingecko.com/coins/images/24383/large/apecoin.jpg?1696523566",
|
||||
* name: "ApeCoin",
|
||||
* symbol: "APE",
|
||||
* },
|
||||
* originToken: {
|
||||
* address: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
|
||||
* chainId: 480,
|
||||
* decimals: 18,
|
||||
* iconUri: "https://assets.relay.link/icons/1/light.png",
|
||||
* name: "Ether",
|
||||
* symbol: "ETH",
|
||||
* }
|
||||
* }
|
||||
* ]
|
||||
* ```
|
||||
*
|
||||
* You can filter for specific chains or tokens:
|
||||
* ```typescript
|
||||
* import { Bridge } from "thirdweb";
|
||||
*
|
||||
* // Get all routes starting from mainnet ETH
|
||||
* const routes = await Bridge.routes({
|
||||
* originChainId: 1,
|
||||
* originTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
|
||||
* client: thirdwebClient,
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* The returned routes will be limited based on the API. You can paginate through the results using the `limit` and `offset` parameters:
|
||||
* ```typescript
|
||||
* import { Bridge } from "thirdweb";
|
||||
*
|
||||
* // Get the first 10 routes starting from mainnet ETH
|
||||
* const routes = await Bridge.routes({
|
||||
* originChainId: 1,
|
||||
* originTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
|
||||
* limit: 10,
|
||||
* offset: 0,
|
||||
* client: thirdwebClient,
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @param options - The options for the quote.
|
||||
* @param options.client - Your thirdweb client.
|
||||
* @param options.originChainId - Filter by a specific origin chain ID.
|
||||
* @param options.originTokenAddress - Filter by a specific origin token address.
|
||||
* @param options.destinationChainId - Filter by a specific destination chain ID.
|
||||
* @param options.destinationTokenAddress - Filter by a specific destination token address.
|
||||
* @param options.transactionHash - Filter by a specific transaction hash.
|
||||
* @param options.limit - Limit the number of routes returned.
|
||||
* @param options.offset - Offset the number of routes returned.
|
||||
*
|
||||
* @returns A promise that resolves to an array of routes.
|
||||
*
|
||||
* @throws Will throw an error if there is an issue fetching the routes.
|
||||
* @bridge
|
||||
* @beta
|
||||
*/
|
||||
export async function routes(options: routes.Options): Promise<routes.Result> {
|
||||
const {
|
||||
client,
|
||||
originChainId,
|
||||
originTokenAddress,
|
||||
destinationChainId,
|
||||
destinationTokenAddress,
|
||||
limit,
|
||||
offset,
|
||||
} = options;
|
||||
|
||||
const clientFetch = getClientFetch(client);
|
||||
const url = new URL(`${UNIVERSAL_BRIDGE_URL}/routes`);
|
||||
if (originChainId) {
|
||||
url.searchParams.set("originChainId", originChainId.toString());
|
||||
}
|
||||
if (originTokenAddress) {
|
||||
url.searchParams.set("originTokenAddress", originTokenAddress);
|
||||
}
|
||||
if (destinationChainId) {
|
||||
url.searchParams.set("destinationChainId", destinationChainId.toString());
|
||||
}
|
||||
if (destinationTokenAddress) {
|
||||
url.searchParams.set("destinationTokenAddress", destinationTokenAddress);
|
||||
}
|
||||
if (limit) {
|
||||
url.searchParams.set("limit", limit.toString());
|
||||
}
|
||||
if (offset) {
|
||||
url.searchParams.set("offset", offset.toString());
|
||||
}
|
||||
|
||||
const response = await clientFetch(url.toString());
|
||||
if (!response.ok) {
|
||||
const errorJson = await response.json();
|
||||
throw new Error(`${errorJson.code} | ${errorJson.message}`);
|
||||
}
|
||||
|
||||
const { data }: { data: Route[] } = await response.json();
|
||||
return data;
|
||||
}
|
||||
|
||||
export declare namespace routes {
|
||||
type Options = {
|
||||
client: ThirdwebClient;
|
||||
originChainId?: number;
|
||||
originTokenAddress?: ox__Address.Address;
|
||||
destinationChainId?: number;
|
||||
destinationTokenAddress?: ox__Address.Address;
|
||||
transactionHash?: ox__Hex.Hex;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
};
|
||||
|
||||
type Result = Route[];
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import { toWei } from "src/utils/units.js";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { TEST_CLIENT } from "~test/test-clients.js";
|
||||
import * as Sell from "./Sell.js";
|
||||
|
||||
describe("Bridge.Sell.quote", () => {
|
||||
it("should get a valid quote", async () => {
|
||||
const quote = await Sell.quote({
|
||||
originChainId: 1,
|
||||
originTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
|
||||
destinationChainId: 10,
|
||||
destinationTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
|
||||
sellAmountWei: toWei("0.01"),
|
||||
client: TEST_CLIENT,
|
||||
});
|
||||
|
||||
expect(quote).toBeDefined();
|
||||
expect(quote.originAmount).toEqual(toWei("0.01"));
|
||||
expect(quote.intent).toBeDefined();
|
||||
});
|
||||
|
||||
it("should surface any errors", async () => {
|
||||
await expect(
|
||||
Sell.quote({
|
||||
originChainId: 1,
|
||||
originTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
|
||||
destinationChainId: 10,
|
||||
destinationTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
|
||||
sellAmountWei: toWei("1000000000"),
|
||||
client: TEST_CLIENT,
|
||||
}),
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`[Error: AMOUNT_TOO_HIGH | The provided amount is too high for the requested route.]`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Bridge.Sell.prepare", () => {
|
||||
it("should get a valid prepared quote", async () => {
|
||||
const quote = await Sell.prepare({
|
||||
originChainId: 1,
|
||||
originTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
|
||||
destinationChainId: 10,
|
||||
destinationTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
|
||||
sellAmountWei: toWei("0.01"),
|
||||
sender: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
|
||||
receiver: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
|
||||
client: TEST_CLIENT,
|
||||
});
|
||||
|
||||
expect(quote).toBeDefined();
|
||||
expect(quote.originAmount).toEqual(toWei("0.01"));
|
||||
expect(quote.transactions).toBeDefined();
|
||||
expect(quote.transactions.length).toBeGreaterThan(0);
|
||||
expect(quote.intent).toBeDefined();
|
||||
});
|
||||
|
||||
it("should surface any errors", async () => {
|
||||
await expect(
|
||||
Sell.prepare({
|
||||
originChainId: 1,
|
||||
originTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
|
||||
destinationChainId: 10,
|
||||
destinationTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
|
||||
sellAmountWei: toWei("1000000000"),
|
||||
sender: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
|
||||
receiver: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
|
||||
client: TEST_CLIENT,
|
||||
}),
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`[Error: AMOUNT_TOO_HIGH | The provided amount is too high for the requested route.]`,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,267 @@
|
||||
import type { Address as ox__Address } from "ox";
|
||||
import type { ThirdwebClient } from "../client/client.js";
|
||||
import { getClientFetch } from "../utils/fetch.js";
|
||||
import { UNIVERSAL_BRIDGE_URL } from "./constants.js";
|
||||
import type { PreparedQuote, Quote } from "./types/Quote.js";
|
||||
|
||||
/**
|
||||
* Retrieves a Universal Bridge quote for the provided sell intent. The quote will specify the expected `destinationAmount` that will be received in exchange for the specified `originAmount`, which is specified with the `sellAmountWei` option.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { Bridge, NATIVE_TOKEN_ADDRESS } from "thirdweb";
|
||||
*
|
||||
* const quote = await Bridge.Sell.quote({
|
||||
* originChainId: 1,
|
||||
* originTokenAddress: NATIVE_TOKEN_ADDRESS,
|
||||
* destinationChainId: 10,
|
||||
* destinationTokenAddress: NATIVE_TOKEN_ADDRESS,
|
||||
* sellAmountWei: toWei("0.01"),
|
||||
* client: thirdwebClient,
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* This will return a quote that might look like:
|
||||
* ```typescript
|
||||
* {
|
||||
* originAmount: 1000000000000000000n,
|
||||
* destinationAmount: 9999979011973735n,
|
||||
* blockNumber: 22026509n,
|
||||
* timestamp: 1741730936680,
|
||||
* estimatedExecutionTimeMs: 1000
|
||||
* intent: {
|
||||
* originChainId: 1,
|
||||
* originTokenAddress: NATIVE_TOKEN_ADDRESS,
|
||||
* destinationChainId: 10,
|
||||
* destinationTokenAddress: NATIVE_TOKEN_ADDRESS,
|
||||
* sellAmountWei: 1000000000000000000n
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* The quote is an **estimate** for how much you would expect to receive for a specific sell. This quote is not guaranteed and you should use `Sell.prepare` to get a finalized quote with transaction data ready for execution.
|
||||
* So why use `quote`? The quote function is sometimes slightly faster than `prepare`, and can be used before the user connects their wallet.
|
||||
*
|
||||
* You can access this functions input and output types with `Sell.quote.Options` and `Sell.quote.Result`, respectively.
|
||||
*
|
||||
* @param options - The options for the quote.
|
||||
* @param options.originChainId - The chain ID of the origin token.
|
||||
* @param options.originTokenAddress - The address of the origin token.
|
||||
* @param options.destinationChainId - The chain ID of the destination token.
|
||||
* @param options.destinationTokenAddress - The address of the destination token.
|
||||
* @param options.sellAmountWei - The amount of the origin token to sell.
|
||||
* @param options.client - Your thirdweb client.
|
||||
*
|
||||
* @returns A promise that resolves to a non-finalized quote for the requested sell.
|
||||
*
|
||||
* @throws Will throw an error if there is an issue fetching the quote.
|
||||
* @bridge
|
||||
* @beta
|
||||
*/
|
||||
export async function quote(options: quote.Options): Promise<quote.Result> {
|
||||
const {
|
||||
originChainId,
|
||||
originTokenAddress,
|
||||
destinationChainId,
|
||||
destinationTokenAddress,
|
||||
sellAmountWei,
|
||||
client,
|
||||
} = options;
|
||||
|
||||
const clientFetch = getClientFetch(client);
|
||||
const url = new URL(`${UNIVERSAL_BRIDGE_URL}/sell/quote`);
|
||||
url.searchParams.set("originChainId", originChainId.toString());
|
||||
url.searchParams.set("originTokenAddress", originTokenAddress);
|
||||
url.searchParams.set("destinationChainId", destinationChainId.toString());
|
||||
url.searchParams.set("destinationTokenAddress", destinationTokenAddress);
|
||||
url.searchParams.set("sellAmountWei", sellAmountWei.toString());
|
||||
|
||||
const response = await clientFetch(url.toString());
|
||||
if (!response.ok) {
|
||||
const errorJson = await response.json();
|
||||
throw new Error(`${errorJson.code} | ${errorJson.message}`);
|
||||
}
|
||||
|
||||
const { data }: { data: Quote } = await response.json();
|
||||
return {
|
||||
originAmount: BigInt(data.originAmount),
|
||||
destinationAmount: BigInt(data.destinationAmount),
|
||||
blockNumber: data.blockNumber ? BigInt(data.blockNumber) : undefined,
|
||||
timestamp: data.timestamp,
|
||||
estimatedExecutionTimeMs: data.estimatedExecutionTimeMs,
|
||||
intent: {
|
||||
originChainId,
|
||||
originTokenAddress,
|
||||
destinationChainId,
|
||||
destinationTokenAddress,
|
||||
sellAmountWei,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export declare namespace quote {
|
||||
type Options = {
|
||||
originChainId: number;
|
||||
originTokenAddress: ox__Address.Address;
|
||||
destinationChainId: number;
|
||||
destinationTokenAddress: ox__Address.Address;
|
||||
sellAmountWei: bigint;
|
||||
client: ThirdwebClient;
|
||||
};
|
||||
|
||||
type Result = Quote & {
|
||||
intent: {
|
||||
originChainId: number;
|
||||
originTokenAddress: ox__Address.Address;
|
||||
destinationChainId: number;
|
||||
destinationTokenAddress: ox__Address.Address;
|
||||
sellAmountWei: bigint;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a **finalized** Universal Bridge quote for the provided sell request with transaction data. This function will return everything `quote` does, with the addition of a series of prepared transactions and the associated expiration timestamp.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { Bridge, NATIVE_TOKEN_ADDRESS } from "thirdweb";
|
||||
*
|
||||
* const quote = await Bridge.Sell.prepare({
|
||||
* originChainId: 1,
|
||||
* originTokenAddress: NATIVE_TOKEN_ADDRESS,
|
||||
* destinationChainId: 10,
|
||||
* destinationTokenAddress: NATIVE_TOKEN_ADDRESS,
|
||||
* sellAmountWei: toWei("0.01"),
|
||||
* client: thirdwebClient,
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* This will return a quote that might look like:
|
||||
* ```typescript
|
||||
* {
|
||||
* originAmount: 1000000000000000000n,
|
||||
* destinationAmount: 9980000000000000000n,
|
||||
* blockNumber: 22026509n,
|
||||
* timestamp: 1741730936680,
|
||||
* estimatedExecutionTimeMs: 1000
|
||||
* transactions: [
|
||||
* {
|
||||
* to: NATIVE_TOKEN_ADDRESS,
|
||||
* value: 9980000000000000000n,
|
||||
* data: "0x",
|
||||
* chainId: 10,
|
||||
* type: "eip1559"
|
||||
* }
|
||||
* ],
|
||||
* expiration: 1741730936680,
|
||||
* intent: {
|
||||
* originChainId: 1,
|
||||
* originTokenAddress: NATIVE_TOKEN_ADDRESS,
|
||||
* destinationChainId: 10,
|
||||
* destinationTokenAddress: NATIVE_TOKEN_ADDRESS,
|
||||
* sellAmountWei: 1000000000000000000n
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* ## Sending the transactions
|
||||
* The `transactions` array is a series of [ox](https://oxlib.sh) EIP-1559 transactions that must be executed one after the other in order to fulfill the complete route. There are a few things to keep in mind when executing these transactions:
|
||||
* - Approvals and other preparation transactions are not included in the transactions array.
|
||||
* - All transactions are assumed to be executed by the `sender` address, regardless of which chain they are on. The final transaction will use the `receiver` as the recipient address.
|
||||
* - If an `expiration` timestamp is provided, all transactions must be executed before that time to guarantee successful execution at the specified price.
|
||||
*
|
||||
* NOTE: To get the status of each transaction, use `Bridge.status` rather than checking for transaction inclusion. This function will ensure full bridge completion on the destination chain.
|
||||
*
|
||||
* You can access this functions input and output types with `Sell.prepare.Options` and `Sell.prepare.Result`, respectively.
|
||||
*
|
||||
* @param options - The options for the quote.
|
||||
* @param options.originChainId - The chain ID of the origin token.
|
||||
* @param options.originTokenAddress - The address of the origin token.
|
||||
* @param options.destinationChainId - The chain ID of the destination token.
|
||||
* @param options.destinationTokenAddress - The address of the destination token.
|
||||
* @param options.sellAmountWei - The amount of the origin token to sell.
|
||||
* @param options.sender - The address of the sender.
|
||||
* @param options.receiver - The address of the recipient.
|
||||
* @param options.client - Your thirdweb client.
|
||||
*
|
||||
* @returns A promise that resolves to a non-finalized quote for the requested buy.
|
||||
*
|
||||
* @throws Will throw an error if there is an issue fetching the quote.
|
||||
* @bridge
|
||||
* @beta
|
||||
*/
|
||||
export async function prepare(
|
||||
options: prepare.Options,
|
||||
): Promise<prepare.Result> {
|
||||
const {
|
||||
originChainId,
|
||||
originTokenAddress,
|
||||
destinationChainId,
|
||||
destinationTokenAddress,
|
||||
sellAmountWei,
|
||||
sender,
|
||||
receiver,
|
||||
client,
|
||||
} = options;
|
||||
|
||||
const clientFetch = getClientFetch(client);
|
||||
const url = new URL(`${UNIVERSAL_BRIDGE_URL}/sell/prepare`);
|
||||
url.searchParams.set("originChainId", originChainId.toString());
|
||||
url.searchParams.set("originTokenAddress", originTokenAddress);
|
||||
url.searchParams.set("destinationChainId", destinationChainId.toString());
|
||||
url.searchParams.set("destinationTokenAddress", destinationTokenAddress);
|
||||
url.searchParams.set("sellAmountWei", sellAmountWei.toString());
|
||||
url.searchParams.set("sender", sender);
|
||||
url.searchParams.set("receiver", receiver);
|
||||
|
||||
const response = await clientFetch(url.toString());
|
||||
if (!response.ok) {
|
||||
const errorJson = await response.json();
|
||||
throw new Error(`${errorJson.code} | ${errorJson.message}`);
|
||||
}
|
||||
|
||||
const { data }: { data: PreparedQuote } = await response.json();
|
||||
return {
|
||||
originAmount: BigInt(data.originAmount),
|
||||
destinationAmount: BigInt(data.destinationAmount),
|
||||
blockNumber: data.blockNumber ? BigInt(data.blockNumber) : undefined,
|
||||
timestamp: data.timestamp,
|
||||
estimatedExecutionTimeMs: data.estimatedExecutionTimeMs,
|
||||
transactions: data.transactions.map((transaction) => ({
|
||||
...transaction,
|
||||
value: transaction.value ? BigInt(transaction.value) : undefined,
|
||||
})),
|
||||
expiration: data.expiration,
|
||||
intent: {
|
||||
originChainId,
|
||||
originTokenAddress,
|
||||
destinationChainId,
|
||||
destinationTokenAddress,
|
||||
sellAmountWei,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export declare namespace prepare {
|
||||
type Options = {
|
||||
originChainId: number;
|
||||
originTokenAddress: ox__Address.Address;
|
||||
destinationChainId: number;
|
||||
destinationTokenAddress: ox__Address.Address;
|
||||
sellAmountWei: bigint;
|
||||
sender: ox__Address.Address;
|
||||
receiver: ox__Address.Address;
|
||||
client: ThirdwebClient;
|
||||
};
|
||||
|
||||
type Result = PreparedQuote & {
|
||||
intent: {
|
||||
originChainId: number;
|
||||
originTokenAddress: ox__Address.Address;
|
||||
destinationChainId: number;
|
||||
destinationTokenAddress: ox__Address.Address;
|
||||
sellAmountWei: bigint;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { TEST_CLIENT } from "~test/test-clients.js";
|
||||
import { status } from "./Status.js";
|
||||
|
||||
describe("Bridge.status", () => {
|
||||
it("should handle successful status", async () => {
|
||||
const result = await status({
|
||||
transactionHash:
|
||||
"0xe199ef82a0b6215221536e18ec512813c1aa10b4f5ed0d4dfdfcd703578da56d",
|
||||
chainId: 8453,
|
||||
client: TEST_CLIENT,
|
||||
});
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result.status).toBe("COMPLETED");
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
{
|
||||
"destinationAmount": 188625148000000n,
|
||||
"destinationChainId": 2741,
|
||||
"destinationTokenAddress": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
|
||||
"originAmount": 200000000000000n,
|
||||
"originChainId": 8453,
|
||||
"originTokenAddress": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
|
||||
"status": "COMPLETED",
|
||||
"transactions": [
|
||||
{
|
||||
"chainId": 8453,
|
||||
"transactionHash": "0xe199ef82a0b6215221536e18ec512813c1aa10b4f5ed0d4dfdfcd703578da56d",
|
||||
},
|
||||
{
|
||||
"chainId": 2741,
|
||||
"transactionHash": "0xa70a82f42330f54be95a542e1fcfe6ed2dd9f07fb8c82ae67afb4342319f7433",
|
||||
},
|
||||
],
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,163 @@
|
||||
import type { Hex as ox__Hex } from "ox";
|
||||
import type { ThirdwebClient } from "../client/client.js";
|
||||
import { getClientFetch } from "../utils/fetch.js";
|
||||
import { UNIVERSAL_BRIDGE_URL } from "./constants.js";
|
||||
import type { Status } from "./types/Status.js";
|
||||
|
||||
/**
|
||||
* Retrieves a Universal Bridge quote for the provided sell intent. The quote will specify the expected `destinationAmount` that will be received in exchange for the specified `originAmount`, which is specified with the `sellAmountWei` option.
|
||||
*
|
||||
+ * The returned status will include both the origin and destination transactions and any finalized amounts for the route.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { Bridge } from "thirdweb";
|
||||
*
|
||||
* const status = await Bridge.status({
|
||||
* transactionHash: "0xe199ef82a0b6215221536e18ec512813c1aa10b4f5ed0d4dfdfcd703578da56d",
|
||||
* chainId: 8453,
|
||||
* client: thirdwebClient,
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* If the transaction is complete, a response might look like:
|
||||
* ```typescript
|
||||
* {
|
||||
* status: 'COMPLETED',
|
||||
* originAmount: 200000000000000n,
|
||||
* destinationAmount: 188625148000000n,
|
||||
* originChainId: 8453,
|
||||
* destinationChainId: 2741,
|
||||
* originTokenAddress: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE',
|
||||
* destinationTokenAddress: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE',
|
||||
* transactions: [
|
||||
* {
|
||||
* chainId: 8453,
|
||||
* transactionHash: '0xe199ef82a0b6215221536e18ec512813c1aa10b4f5ed0d4dfdfcd703578da56d'
|
||||
* },
|
||||
* {
|
||||
* chainId: 2741,
|
||||
* transactionHash: '0xa70a82f42330f54be95a542e1fcfe6ed2dd9f07fb8c82ae67afb4342319f7433'
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* If the origin transaction hasn't been mined yet, a response might look like:
|
||||
* ```typescript
|
||||
* {
|
||||
* status: "NOT_FOUND",
|
||||
* }
|
||||
* ```
|
||||
* This is to allow you to poll for the status without catching an error. Be sure your transaction hash and chain are correct though, as this could also represent a legitimate 404 if the transaction doesn't exist.
|
||||
*
|
||||
* If the transaction is still pending, a response might look like:
|
||||
* ```typescript
|
||||
* {
|
||||
* status: "PENDING",
|
||||
* originAmount: 1000000000000000000n,
|
||||
* originChainId: 466,
|
||||
* destinationChainId: 1,
|
||||
* originTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
|
||||
* destinationTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
|
||||
* transactions: [
|
||||
* {
|
||||
* transactionHash: "0xe199ef82a0b6215221536e18ec512813c1aa10b4f5ed0d4dfdfcd703578da56d",
|
||||
* chainId: 466,
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* If the transaction failed, a response might look like:
|
||||
* ```typescript
|
||||
* {
|
||||
* status: "FAILED",
|
||||
* transactions: [
|
||||
* {
|
||||
* transactionHash: "0xe199ef82a0b6215221536e18ec512813c1aa10b4f5ed0d4dfdfcd703578da56d",
|
||||
* chainId: 466,
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* This status is for a **single origin transaction only**. If your route involves multiple transactions, you'll need to get the status for each of them individually.
|
||||
*
|
||||
* If sending multiple dependent sequential transactions, wait until `status` returns `COMPLETED` before sending the next transaction.
|
||||
*
|
||||
* You can access this function's input and output types with `status.Options` and `status.Result`, respectively.
|
||||
*
|
||||
* @param options - The options for the quote.
|
||||
* @param options.transactionHash - The hash of the origin transaction to get the bridge status for.
|
||||
* @param options.chainId - The chain ID of the origin token.
|
||||
* @param options.client - Your thirdweb client.
|
||||
*
|
||||
* @returns A promise that resolves to a status object for the transaction.
|
||||
*
|
||||
* @throws Will throw an error if there is an issue fetching the status.
|
||||
* @bridge
|
||||
* @beta
|
||||
*/
|
||||
export async function status(options: status.Options): Promise<status.Result> {
|
||||
const { transactionHash, chainId, client } = options;
|
||||
|
||||
const clientFetch = getClientFetch(client);
|
||||
const url = new URL(`${UNIVERSAL_BRIDGE_URL}/status`);
|
||||
url.searchParams.set("transactionHash", transactionHash);
|
||||
url.searchParams.set("chainId", chainId.toString());
|
||||
|
||||
const response = await clientFetch(url.toString());
|
||||
if (!response.ok) {
|
||||
const errorJson = await response.json();
|
||||
throw new Error(`${errorJson.code}: ${errorJson.message}`);
|
||||
}
|
||||
|
||||
const { data }: { data: Status } = await response.json();
|
||||
if (data.status === "FAILED") {
|
||||
return {
|
||||
status: "FAILED",
|
||||
transactions: data.transactions,
|
||||
};
|
||||
}
|
||||
|
||||
if (data.status === "PENDING") {
|
||||
return {
|
||||
status: "PENDING",
|
||||
originAmount: BigInt(data.originAmount),
|
||||
originChainId: data.originChainId,
|
||||
destinationChainId: data.destinationChainId,
|
||||
originTokenAddress: data.originTokenAddress,
|
||||
destinationTokenAddress: data.destinationTokenAddress,
|
||||
transactions: data.transactions,
|
||||
};
|
||||
}
|
||||
|
||||
if (data.status === "NOT_FOUND") {
|
||||
return {
|
||||
status: "NOT_FOUND",
|
||||
transactions: [],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: "COMPLETED",
|
||||
originAmount: BigInt(data.originAmount),
|
||||
destinationAmount: BigInt(data.destinationAmount),
|
||||
originChainId: data.originChainId,
|
||||
destinationChainId: data.destinationChainId,
|
||||
originTokenAddress: data.originTokenAddress,
|
||||
destinationTokenAddress: data.destinationTokenAddress,
|
||||
transactions: data.transactions,
|
||||
};
|
||||
}
|
||||
|
||||
export declare namespace status {
|
||||
type Options = {
|
||||
transactionHash: ox__Hex.Hex;
|
||||
chainId: number;
|
||||
client: ThirdwebClient;
|
||||
};
|
||||
|
||||
type Result = Status;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export const UNIVERSAL_BRIDGE_URL = "https://bridge.thirdweb.com/v1";
|
||||
@@ -0,0 +1,8 @@
|
||||
export * as Buy from "./Buy.js";
|
||||
export * as Sell from "./Sell.js";
|
||||
export { status } from "./Status.js";
|
||||
export { routes } from "./Routes.js";
|
||||
|
||||
export type { Status } from "./types/Status.js";
|
||||
export type { Route } from "./types/Route.js";
|
||||
export type { Quote, PreparedQuote } from "./types/Quote.js";
|
||||
@@ -0,0 +1,42 @@
|
||||
import type { TransactionEnvelopeEip1559 as ox__TransactionEnvelopeEip1559 } from "ox";
|
||||
|
||||
export type Quote = {
|
||||
/**
|
||||
* The input amount (in wei) including fees to be paid.
|
||||
*/
|
||||
originAmount: bigint;
|
||||
/**
|
||||
* The output amount (in wei) to be received.
|
||||
*/
|
||||
destinationAmount: bigint;
|
||||
/**
|
||||
* The blocknumber this quote was generated at.
|
||||
*/
|
||||
blockNumber?: bigint;
|
||||
/**
|
||||
* The timestamp this quote was generated at.
|
||||
*/
|
||||
timestamp: number;
|
||||
/**
|
||||
* The estimated execution time in milliseconds.
|
||||
*/
|
||||
estimatedExecutionTimeMs?: number | undefined;
|
||||
};
|
||||
|
||||
export type PreparedQuote = Quote & {
|
||||
/**
|
||||
* The expiration timestamp for the quote. All transactions must be executed before this timestamp to guarantee successful execution at the specified price.
|
||||
*/
|
||||
expiration?: number | undefined;
|
||||
/**
|
||||
* A series of [ox](https://oxlib.sh) EIP-1559 transactions that must be executed in sequential order to fulfill the complete route.
|
||||
*/
|
||||
transactions: Array<
|
||||
ox__TransactionEnvelopeEip1559.TransactionEnvelopeEip1559<
|
||||
false,
|
||||
bigint,
|
||||
number,
|
||||
"eip1559"
|
||||
>
|
||||
>;
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
import type { Address as ox__Address } from "ox";
|
||||
|
||||
export type Route = {
|
||||
originToken: {
|
||||
chainId: number;
|
||||
address: ox__Address.Address;
|
||||
decimals: number;
|
||||
symbol: string;
|
||||
name: string;
|
||||
iconUri?: string;
|
||||
};
|
||||
destinationToken: {
|
||||
chainId: number;
|
||||
address: string;
|
||||
decimals: number;
|
||||
symbol: string;
|
||||
name: string;
|
||||
iconUri?: string;
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,39 @@
|
||||
import type { Address as ox__Address, Hex as ox__Hex } from "ox";
|
||||
|
||||
export type Status =
|
||||
| {
|
||||
status: "COMPLETED";
|
||||
originAmount: bigint;
|
||||
destinationAmount: bigint;
|
||||
originChainId: number;
|
||||
destinationChainId: number;
|
||||
originTokenAddress: ox__Address.Address;
|
||||
destinationTokenAddress: ox__Address.Address;
|
||||
transactions: Array<{
|
||||
chainId: number;
|
||||
transactionHash: ox__Hex.Hex;
|
||||
}>;
|
||||
}
|
||||
| {
|
||||
status: "PENDING";
|
||||
originAmount: bigint;
|
||||
originChainId: number;
|
||||
destinationChainId: number;
|
||||
originTokenAddress: ox__Address.Address;
|
||||
destinationTokenAddress: ox__Address.Address;
|
||||
transactions: Array<{
|
||||
chainId: number;
|
||||
transactionHash: ox__Hex.Hex;
|
||||
}>;
|
||||
}
|
||||
| {
|
||||
status: "FAILED";
|
||||
transactions: Array<{
|
||||
chainId: number;
|
||||
transactionHash: ox__Hex.Hex;
|
||||
}>;
|
||||
}
|
||||
| {
|
||||
status: "NOT_FOUND";
|
||||
transactions: [];
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from "../bridge/index.js";
|
||||
@@ -71,6 +71,11 @@ export {
|
||||
type ThirdwebContract,
|
||||
} from "../contract/contract.js";
|
||||
|
||||
/**
|
||||
* UNIVERSAL BRIDGE
|
||||
*/
|
||||
export * as Bridge from "../bridge/index.js";
|
||||
|
||||
/**
|
||||
* WALLETS
|
||||
*/
|
||||
|
||||
@@ -183,6 +183,7 @@ const WalletSelectorInner: React.FC<WalletSelectorProps> = (props) => {
|
||||
props.selectWallet(localWalletConfig);
|
||||
}}
|
||||
data-test="continue-as-guest-button"
|
||||
disabled={props.meta.requireApproval && !approvedTOS}
|
||||
>
|
||||
{props.connectLocale.continueAsGuest}
|
||||
</Button>
|
||||
@@ -227,6 +228,7 @@ const WalletSelectorInner: React.FC<WalletSelectorProps> = (props) => {
|
||||
<WalletTypeRowButton
|
||||
client={props.client}
|
||||
icon={OutlineWalletIcon}
|
||||
disabled={props.meta.requireApproval && !approvedTOS}
|
||||
onClick={() => {
|
||||
setIsWalletGroupExpanded(true);
|
||||
}}
|
||||
|
||||
@@ -90,9 +90,6 @@ export function OnRampScreen(props: {
|
||||
isAutoMode,
|
||||
});
|
||||
const firstStepChainId = state.steps[0]?.step.token.chainId;
|
||||
const currentStepIndex = state.steps.findIndex(
|
||||
(step) => step.status === "pending" || step.status === "actionRequired",
|
||||
);
|
||||
return (
|
||||
<Container p="lg">
|
||||
<ModalHeader title={props.title} onBack={props.onBack} />
|
||||
@@ -131,9 +128,7 @@ export function OnRampScreen(props: {
|
||||
index={index}
|
||||
/>
|
||||
</StepContainer>
|
||||
{index < state.steps.length - 1 && (
|
||||
<StepConnectorArrow active={index === currentStepIndex} />
|
||||
)}
|
||||
{index < state.steps.length - 1 && <StepConnectorArrow />}
|
||||
</Container>
|
||||
))}
|
||||
</Container>
|
||||
|
||||
+14
-35
@@ -1,9 +1,8 @@
|
||||
import { ChevronDownIcon } from "@radix-ui/react-icons";
|
||||
import { useCustomTheme } from "../../../../../../core/design-system/CustomThemeProvider.js";
|
||||
import { Container } from "../../../../components/basic.js";
|
||||
|
||||
export function StepConnectorArrow(props: {
|
||||
active: boolean;
|
||||
}) {
|
||||
export function StepConnectorArrow() {
|
||||
const theme = useCustomTheme();
|
||||
return (
|
||||
<Container
|
||||
@@ -11,45 +10,25 @@ export function StepConnectorArrow(props: {
|
||||
center="both"
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "12px",
|
||||
position: "relative",
|
||||
marginTop: "-1px",
|
||||
marginTop: "-10px",
|
||||
marginBottom: "-10px",
|
||||
zIndex: 1000,
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
role="presentation"
|
||||
width="32"
|
||||
height="16"
|
||||
viewBox="0 0 32 16"
|
||||
fill="none"
|
||||
<Container
|
||||
flex="row"
|
||||
center="both"
|
||||
style={{
|
||||
display: "block",
|
||||
borderRadius: "100%",
|
||||
width: "30px",
|
||||
height: "30px",
|
||||
backgroundColor: theme.colors.modalBg,
|
||||
border: `1px solid ${theme.colors.borderColor}`,
|
||||
}}
|
||||
>
|
||||
<path
|
||||
d="M1 0L16 15L31 0"
|
||||
fill={theme.colors.tertiaryBg}
|
||||
stroke={
|
||||
props.active ? theme.colors.accentText : theme.colors.borderColor
|
||||
}
|
||||
strokeWidth="1"
|
||||
strokeLinecap="square"
|
||||
strokeLinejoin="miter"
|
||||
/>
|
||||
<path
|
||||
d="M8 0L16 7.5L24 0"
|
||||
fill="none"
|
||||
stroke={
|
||||
props.active
|
||||
? theme.colors.accentText
|
||||
: theme.colors.secondaryIconColor
|
||||
}
|
||||
strokeWidth="1"
|
||||
strokeLinecap="square"
|
||||
strokeLinejoin="miter"
|
||||
/>
|
||||
</svg>
|
||||
<ChevronDownIcon width={16} height={16} />
|
||||
</Container>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ export function SwapSummary(props: {
|
||||
/>
|
||||
</Container>
|
||||
{/* Connector Icon */}
|
||||
<StepConnectorArrow active={false} />
|
||||
<StepConnectorArrow />
|
||||
{/* Buy */}
|
||||
<Container
|
||||
flex="column"
|
||||
|
||||
@@ -57,6 +57,10 @@
|
||||
"tagName": "@buyCrypto",
|
||||
"syntaxKind": "block"
|
||||
},
|
||||
{
|
||||
"tagName": "@bridge",
|
||||
"syntaxKind": "block"
|
||||
},
|
||||
{
|
||||
"tagName": "@storage",
|
||||
"syntaxKind": "block"
|
||||
@@ -112,6 +116,7 @@
|
||||
"@walletConnection": true,
|
||||
"@walletUtils": true,
|
||||
"@buyCrypto": true,
|
||||
"@bridge": true,
|
||||
"@storage": true,
|
||||
"@auth": true,
|
||||
"@utils": true,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# @thirdweb-dev/wagmi-adapter
|
||||
|
||||
## 0.2.35
|
||||
|
||||
## 0.2.34
|
||||
|
||||
## 0.2.33
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/wagmi-adapter",
|
||||
"version": "0.2.34",
|
||||
"version": "0.2.35",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/thirdweb-dev/js.git#main"
|
||||
|
||||
Generated
+69
-61
@@ -197,8 +197,8 @@ importers:
|
||||
specifier: ^2.2.0
|
||||
version: 2.2.0
|
||||
lucide-react:
|
||||
specifier: 0.482.0
|
||||
version: 0.482.0([email protected])
|
||||
specifier: 0.483.0
|
||||
version: 0.483.0([email protected])
|
||||
next:
|
||||
specifier: 15.2.2
|
||||
version: 15.2.2(@babel/[email protected])(@opentelemetry/[email protected])(@playwright/[email protected])([email protected]([email protected]))([email protected])
|
||||
@@ -450,8 +450,8 @@ importers:
|
||||
specifier: 6.0.10
|
||||
version: 6.0.10
|
||||
lucide-react:
|
||||
specifier: 0.482.0
|
||||
version: 0.482.0([email protected])
|
||||
specifier: 0.483.0
|
||||
version: 0.483.0([email protected])
|
||||
next:
|
||||
specifier: 15.2.2
|
||||
version: 15.2.2(@babel/[email protected])(@opentelemetry/[email protected])(@playwright/[email protected])([email protected]([email protected]))([email protected])
|
||||
@@ -577,8 +577,8 @@ importers:
|
||||
specifier: 6.0.10
|
||||
version: 6.0.10
|
||||
lucide-react:
|
||||
specifier: 0.482.0
|
||||
version: 0.482.0([email protected])
|
||||
specifier: 0.483.0
|
||||
version: 0.483.0([email protected])
|
||||
next:
|
||||
specifier: 15.2.2
|
||||
version: 15.2.2(@babel/[email protected])(@opentelemetry/[email protected])(@playwright/[email protected])([email protected]([email protected]))([email protected])
|
||||
@@ -713,8 +713,8 @@ importers:
|
||||
specifier: ^1.2.0
|
||||
version: 1.2.0
|
||||
lucide-react:
|
||||
specifier: 0.482.0
|
||||
version: 0.482.0([email protected])
|
||||
specifier: 0.483.0
|
||||
version: 0.483.0([email protected])
|
||||
next:
|
||||
specifier: 15.2.2
|
||||
version: 15.2.2(@babel/[email protected])(@opentelemetry/[email protected])(@playwright/[email protected])([email protected]([email protected]))([email protected])
|
||||
@@ -867,8 +867,8 @@ importers:
|
||||
specifier: ^1.0.4
|
||||
version: 1.0.4(@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])
|
||||
lucide-react:
|
||||
specifier: 0.482.0
|
||||
version: 0.482.0([email protected])
|
||||
specifier: 0.483.0
|
||||
version: 0.483.0([email protected])
|
||||
next:
|
||||
specifier: 15.2.2
|
||||
version: 15.2.2(@babel/[email protected])(@opentelemetry/[email protected])(@playwright/[email protected])([email protected]([email protected]))([email protected])
|
||||
@@ -5652,9 +5652,9 @@ packages:
|
||||
resolution: {integrity: sha512-E1ImOIAD1tBZFRdjeM4/pzTiTApC0AOBGwyAMS4fwIodCWArzJ3DWdoh8cKxeFM2fElkxBh2Aqts1BPC373rHA==}
|
||||
engines: {node: '>=5.10'}
|
||||
|
||||
'@solana/[email protected]3':
|
||||
resolution: {integrity: sha512-apqMuYwFp1jFi55NxDfvXUX2x1T0Zh07MxhZ/nCCTGys5raSfYUh82zen2BLv8BSDj/JxZ2P/s7jrQZGrX8uAw==}
|
||||
engines: {node: '>=16'}
|
||||
'@solana/[email protected]4':
|
||||
resolution: {integrity: sha512-f3kwHF/2Lx3YgcO37B45MM46YLFy4QkdLemZ+N/0SwLAnSfhq3+Vb9bC5vuoupMJ/onos09TIDeIxRdg/+51kw==}
|
||||
engines: {node: '>=20'}
|
||||
peerDependencies:
|
||||
'@solana/web3.js': ^1.77.3
|
||||
|
||||
@@ -6086,8 +6086,8 @@ packages:
|
||||
react: '>=16.8'
|
||||
react-dom: '>=16.8'
|
||||
|
||||
'@tanstack/[email protected].3':
|
||||
resolution: {integrity: sha512-khJmiDJCkklsDTvXxTZHfEa7H161e94eDKxKyXqg9/3LstIbRg4JWBxPD2/e3LKtklC5dxkoYzNllCMVR904FA==}
|
||||
'@tanstack/[email protected].4':
|
||||
resolution: {integrity: sha512-jPWC3BXvVLHsMX67NEHpJaZ+/FySoNxFfBEiF4GBc1+/nVwdRm+UcSCYnKP3pXQr0eEsDpXi/PQZhNfJNopH0g==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
@@ -6096,8 +6096,8 @@ packages:
|
||||
resolution: {integrity: sha512-uvXk/U4cBiFMxt+p9/G7yUWI/UbHYbyghLCjlpWZ3mLeIZiUBSKcUnw9UnKkdRz7Z/N4UBuFLWQdJCjUe7HjvA==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
'@tanstack/[email protected].3':
|
||||
resolution: {integrity: sha512-9kfCeSG6zUx1I1iF4RKZrquNog3Eho1T6+LyJEDYpHjNNdDlRhXyqzTod5u6LCEBSeG0f2txkNjAq0tFbCJ4bA==}
|
||||
'@tanstack/[email protected].4':
|
||||
resolution: {integrity: sha512-fNGO9fjjSLns87tlcto106enQQLycCKR4DPNpgq3djP5IdcPFdPAmaKjsgzIeRhH7hWrELgW12hYnRthS5kLUw==}
|
||||
|
||||
'@testing-library/[email protected]':
|
||||
resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==}
|
||||
@@ -10674,6 +10674,7 @@ packages:
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==}
|
||||
deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead.
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
|
||||
@@ -10743,8 +10744,8 @@ packages:
|
||||
resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
[email protected]2.0:
|
||||
resolution: {integrity: sha512-XM8PzHzSrg8ATmmO+fzf+JyYlVVdQnJjuyLDj2p4V2zEtcKeBNAqAoJIGFv1x2HSBa7kT8gpYUxwdQ0g7nypfw==}
|
||||
[email protected]3.0:
|
||||
resolution: {integrity: sha512-WldsY17Qb/T3VZdMnVQ9C3DDIP7h1ViDTHVdVGnLZcvHNg30zH/MTQ04RTORjexoGmpsXroiQXZ4QyR0kBy0FA==}
|
||||
peerDependencies:
|
||||
react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
|
||||
@@ -11238,8 +11239,8 @@ packages:
|
||||
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
[email protected]3.0:
|
||||
resolution: {integrity: sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg==}
|
||||
[email protected]4.0:
|
||||
resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
[email protected]:
|
||||
@@ -11436,6 +11437,11 @@ packages:
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-pfRR4ZcNTSm2ZFHaztuvbICf+hyiG6ecA06SfAxoPmuHjvMu0KUIae7Y8GyVkbBqeEIidsmXeYooWIX9+qjfRQ==}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
|
||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||
hasBin: true
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-SppoicMGpZvbF1l3z4x7No3OlIjP7QJvC9XR7AhZr1kL133KHnKPztkKDc+Ir4aJ/1VhTySrtKhrsycmrMQfvg==}
|
||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||
@@ -13744,8 +13750,8 @@ packages:
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==}
|
||||
|
||||
[email protected]5:
|
||||
resolution: {integrity: sha512-PpOTEztW87Ua2xbmLa7yssjNyUF9vE7wdldRfn1I2E6RTkqknkBYpj771OxM/xrvRGinLy2oysa7GOd7NcZZIA==}
|
||||
[email protected]6:
|
||||
resolution: {integrity: sha512-KpWB6ORAWGmbWM10cDJfEV6sXc/uVkkkQV3SLwTNQ/E/PqWgNHIoMSLh1Lnk2FkB9+JHK7uuMq1i+9ArxDD7iQ==}
|
||||
engines: {node: '>= 16'}
|
||||
peerDependencies:
|
||||
react: '>= 16.8.0'
|
||||
@@ -18426,7 +18432,7 @@ snapshots:
|
||||
'@floating-ui/react': 0.26.28([email protected]([email protected]))([email protected])
|
||||
'@react-aria/focus': 3.20.1([email protected]([email protected]))([email protected])
|
||||
'@react-aria/interactions': 3.24.1([email protected]([email protected]))([email protected])
|
||||
'@tanstack/react-virtual': 3.13.3([email protected]([email protected]))([email protected])
|
||||
'@tanstack/react-virtual': 3.13.4([email protected]([email protected]))([email protected])
|
||||
react: 19.0.0
|
||||
react-dom: 19.0.0([email protected])
|
||||
|
||||
@@ -19714,9 +19720,9 @@ snapshots:
|
||||
'@metamask/eth-sig-util': 6.0.2
|
||||
'@privy-io/js-sdk-core': 0.45.1([email protected])([email protected])([email protected])([email protected]([email protected])([email protected])([email protected])([email protected]))
|
||||
'@simplewebauthn/browser': 9.0.1
|
||||
'@solana/wallet-adapter-base': 0.9.23(@solana/[email protected]([email protected])([email protected])([email protected]))
|
||||
'@solana/wallet-adapter-base': 0.9.24(@solana/[email protected]([email protected])([email protected])([email protected]))
|
||||
'@solana/wallet-standard-wallet-adapter-base': 1.1.4(@solana/[email protected]([email protected])([email protected])([email protected]))([email protected])
|
||||
'@solana/wallet-standard-wallet-adapter-react': 1.1.4(@solana/[email protected]3(@solana/[email protected]([email protected])([email protected])([email protected])))(@solana/[email protected]([email protected])([email protected])([email protected]))([email protected])([email protected])
|
||||
'@solana/wallet-standard-wallet-adapter-react': 1.1.4(@solana/[email protected]4(@solana/[email protected]([email protected])([email protected])([email protected])))(@solana/[email protected]([email protected])([email protected])([email protected]))([email protected])([email protected])
|
||||
'@wallet-standard/app': 1.1.0
|
||||
'@walletconnect/ethereum-provider': 2.19.1(@types/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected])([email protected])([email protected])([email protected])
|
||||
'@walletconnect/modal': 2.7.0(@types/[email protected])([email protected])
|
||||
@@ -19737,7 +19743,7 @@ snapshots:
|
||||
react-device-detect: 2.2.3([email protected]([email protected]))([email protected])
|
||||
react-dom: 19.0.0([email protected])
|
||||
secure-password-utilities: 0.2.1
|
||||
styled-components: 6.1.15([email protected]([email protected]))([email protected])
|
||||
styled-components: 6.1.16([email protected]([email protected]))([email protected])
|
||||
stylis: 4.3.6
|
||||
tinycolor2: 1.6.0
|
||||
uuid: 9.0.1
|
||||
@@ -21723,7 +21729,7 @@ snapshots:
|
||||
dependencies:
|
||||
buffer: 6.0.3
|
||||
|
||||
'@solana/[email protected]3(@solana/[email protected]([email protected])([email protected])([email protected]))':
|
||||
'@solana/[email protected]4(@solana/[email protected]([email protected])([email protected])([email protected]))':
|
||||
dependencies:
|
||||
'@solana/wallet-standard-features': 1.3.0
|
||||
'@solana/web3.js': 1.98.0([email protected])([email protected])([email protected])
|
||||
@@ -21748,7 +21754,7 @@ snapshots:
|
||||
|
||||
'@solana/[email protected](@solana/[email protected]([email protected])([email protected])([email protected]))([email protected])':
|
||||
dependencies:
|
||||
'@solana/wallet-adapter-base': 0.9.23(@solana/[email protected]([email protected])([email protected])([email protected]))
|
||||
'@solana/wallet-adapter-base': 0.9.24(@solana/[email protected]([email protected])([email protected])([email protected]))
|
||||
'@solana/wallet-standard-chains': 1.1.1
|
||||
'@solana/wallet-standard-features': 1.3.0
|
||||
'@solana/wallet-standard-util': 1.1.2
|
||||
@@ -21759,9 +21765,9 @@ snapshots:
|
||||
'@wallet-standard/wallet': 1.1.0
|
||||
bs58: 6.0.0
|
||||
|
||||
'@solana/[email protected](@solana/[email protected]3(@solana/[email protected]([email protected])([email protected])([email protected])))(@solana/[email protected]([email protected])([email protected])([email protected]))([email protected])([email protected])':
|
||||
'@solana/[email protected](@solana/[email protected]4(@solana/[email protected]([email protected])([email protected])([email protected])))(@solana/[email protected]([email protected])([email protected])([email protected]))([email protected])([email protected])':
|
||||
dependencies:
|
||||
'@solana/wallet-adapter-base': 0.9.23(@solana/[email protected]([email protected])([email protected])([email protected]))
|
||||
'@solana/wallet-adapter-base': 0.9.24(@solana/[email protected]([email protected])([email protected])([email protected]))
|
||||
'@solana/wallet-standard-wallet-adapter-base': 1.1.4(@solana/[email protected]([email protected])([email protected])([email protected]))([email protected])
|
||||
'@wallet-standard/app': 1.1.0
|
||||
'@wallet-standard/base': 1.1.0
|
||||
@@ -22627,15 +22633,15 @@ snapshots:
|
||||
react: 19.0.0
|
||||
react-dom: 19.0.0([email protected])
|
||||
|
||||
'@tanstack/[email protected].3([email protected]([email protected]))([email protected])':
|
||||
'@tanstack/[email protected].4([email protected]([email protected]))([email protected])':
|
||||
dependencies:
|
||||
'@tanstack/virtual-core': 3.13.3
|
||||
'@tanstack/virtual-core': 3.13.4
|
||||
react: 19.0.0
|
||||
react-dom: 19.0.0([email protected])
|
||||
|
||||
'@tanstack/[email protected]': {}
|
||||
|
||||
'@tanstack/[email protected].3': {}
|
||||
'@tanstack/[email protected].4': {}
|
||||
|
||||
'@testing-library/[email protected]':
|
||||
dependencies:
|
||||
@@ -23317,7 +23323,7 @@ snapshots:
|
||||
sirv: 3.0.1
|
||||
tinyglobby: 0.2.12
|
||||
tinyrainbow: 2.0.0
|
||||
vitest: 3.0.8(@types/[email protected])(@types/[email protected])(@vitest/[email protected])(happy-dom@17.1.8)([email protected])([email protected])([email protected](@types/[email protected])([email protected]))([email protected])([email protected])([email protected])
|
||||
vitest: 3.0.8(@types/[email protected])(@types/[email protected])(@vitest/[email protected])(happy-dom@17.4.4)([email protected])([email protected])([email protected](@types/[email protected])([email protected]))([email protected])([email protected])([email protected])
|
||||
|
||||
'@vitest/[email protected]':
|
||||
dependencies:
|
||||
@@ -25463,7 +25469,7 @@ snapshots:
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
mime-db: 1.53.0
|
||||
mime-db: 1.54.0
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
@@ -26379,8 +26385,8 @@ snapshots:
|
||||
'@typescript-eslint/parser': 7.14.1([email protected]([email protected]))([email protected])
|
||||
eslint: 9.22.0([email protected])
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-import-resolver-typescript: 3.8.3([email protected](@typescript-eslint/[email protected]([email protected]([email protected]))([email protected]))([email protected]([email protected])))([email protected]([email protected]))
|
||||
eslint-plugin-import: 2.31.0(@typescript-eslint/[email protected]([email protected]([email protected]))([email protected]))([email protected]([email protected](@typescript-eslint/[email protected]([email protected]([email protected]))([email protected]))([email protected]([email protected])))([email protected]([email protected])))([email protected]([email protected]))
|
||||
eslint-import-resolver-typescript: 3.8.3([email protected])([email protected]([email protected]))
|
||||
eslint-plugin-import: 2.31.0(@typescript-eslint/[email protected]([email protected]([email protected]))([email protected]))([email protected])([email protected]([email protected]))
|
||||
eslint-plugin-jsx-a11y: 6.10.2([email protected]([email protected]))
|
||||
eslint-plugin-react: 7.37.4([email protected]([email protected]))
|
||||
eslint-plugin-react-hooks: 5.1.0([email protected]([email protected]))
|
||||
@@ -26399,21 +26405,6 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
[email protected]([email protected](@typescript-eslint/[email protected]([email protected]([email protected]))([email protected]))([email protected]([email protected])))([email protected]([email protected])):
|
||||
dependencies:
|
||||
'@nolyfill/is-core-module': 1.0.39
|
||||
debug: 4.4.0([email protected])
|
||||
enhanced-resolve: 5.18.1
|
||||
eslint: 9.22.0([email protected])
|
||||
get-tsconfig: 4.10.0
|
||||
is-bun-module: 1.3.0
|
||||
stable-hash: 0.0.4
|
||||
tinyglobby: 0.2.12
|
||||
optionalDependencies:
|
||||
eslint-plugin-import: 2.31.0(@typescript-eslint/[email protected]([email protected]([email protected]))([email protected]))([email protected]([email protected](@typescript-eslint/[email protected]([email protected]([email protected]))([email protected]))([email protected]([email protected])))([email protected]([email protected])))([email protected]([email protected]))
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
[email protected]([email protected])([email protected]):
|
||||
dependencies:
|
||||
'@nolyfill/is-core-module': 1.0.39
|
||||
@@ -26429,6 +26420,21 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
[email protected]([email protected])([email protected]([email protected])):
|
||||
dependencies:
|
||||
'@nolyfill/is-core-module': 1.0.39
|
||||
debug: 4.4.0([email protected])
|
||||
enhanced-resolve: 5.18.1
|
||||
eslint: 9.22.0([email protected])
|
||||
get-tsconfig: 4.10.0
|
||||
is-bun-module: 1.3.0
|
||||
stable-hash: 0.0.4
|
||||
tinyglobby: 0.2.12
|
||||
optionalDependencies:
|
||||
eslint-plugin-import: 2.31.0(@typescript-eslint/[email protected]([email protected]([email protected]))([email protected]))([email protected])([email protected]([email protected]))
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
[email protected]([email protected]):
|
||||
dependencies:
|
||||
acorn: 8.14.1
|
||||
@@ -26461,14 +26467,14 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
[email protected](@typescript-eslint/[email protected]([email protected]([email protected]))([email protected]))([email protected])([email protected]([email protected](@typescript-eslint/[email protected]([email protected]([email protected]))([email protected]))([email protected]([email protected])))([email protected]([email protected])))([email protected]([email protected])):
|
||||
[email protected](@typescript-eslint/[email protected]([email protected]([email protected]))([email protected]))([email protected])([email protected]([email protected])([email protected]([email protected])))([email protected]([email protected])):
|
||||
dependencies:
|
||||
debug: 3.2.7
|
||||
optionalDependencies:
|
||||
'@typescript-eslint/parser': 7.14.1([email protected]([email protected]))([email protected])
|
||||
eslint: 9.22.0([email protected])
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-import-resolver-typescript: 3.8.3([email protected](@typescript-eslint/[email protected]([email protected]([email protected]))([email protected]))([email protected]([email protected])))([email protected]([email protected]))
|
||||
eslint-import-resolver-typescript: 3.8.3([email protected])([email protected]([email protected]))
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -26501,7 +26507,7 @@ snapshots:
|
||||
- eslint-import-resolver-webpack
|
||||
- supports-color
|
||||
|
||||
[email protected](@typescript-eslint/[email protected]([email protected]([email protected]))([email protected]))([email protected]([email protected](@typescript-eslint/[email protected]([email protected]([email protected]))([email protected]))([email protected]([email protected])))([email protected]([email protected])))([email protected]([email protected])):
|
||||
[email protected](@typescript-eslint/[email protected]([email protected]([email protected]))([email protected]))([email protected])([email protected]([email protected])):
|
||||
dependencies:
|
||||
'@rtsao/scc': 1.1.0
|
||||
array-includes: 3.1.8
|
||||
@@ -26512,7 +26518,7 @@ snapshots:
|
||||
doctrine: 2.1.0
|
||||
eslint: 9.22.0([email protected])
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-module-utils: 2.12.0(@typescript-eslint/[email protected]([email protected]([email protected]))([email protected]))([email protected])([email protected]([email protected](@typescript-eslint/[email protected]([email protected]([email protected]))([email protected]))([email protected]([email protected])))([email protected]([email protected])))([email protected]([email protected]))
|
||||
eslint-module-utils: 2.12.0(@typescript-eslint/[email protected]([email protected]([email protected]))([email protected]))([email protected])([email protected]([email protected])([email protected]([email protected])))([email protected]([email protected]))
|
||||
hasown: 2.0.2
|
||||
is-core-module: 2.16.1
|
||||
is-glob: 4.0.3
|
||||
@@ -29004,7 +29010,7 @@ snapshots:
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]2.0([email protected]):
|
||||
[email protected]3.0([email protected]):
|
||||
dependencies:
|
||||
react: 19.0.0
|
||||
|
||||
@@ -30148,7 +30154,7 @@ snapshots:
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]3.0: {}
|
||||
[email protected]4.0: {}
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
@@ -30342,6 +30348,8 @@ snapshots:
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]: {}
|
||||
@@ -31243,7 +31251,7 @@ snapshots:
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
nanoid: 3.3.9
|
||||
nanoid: 3.3.11
|
||||
picocolors: 1.1.1
|
||||
source-map-js: 1.2.1
|
||||
|
||||
@@ -33005,7 +33013,7 @@ snapshots:
|
||||
dependencies:
|
||||
inline-style-parser: 0.2.4
|
||||
|
||||
[email protected]5([email protected]([email protected]))([email protected]):
|
||||
[email protected]6([email protected]([email protected]))([email protected]):
|
||||
dependencies:
|
||||
'@emotion/is-prop-valid': 1.2.2
|
||||
'@emotion/unitless': 0.8.1
|
||||
|
||||
Reference in New Issue
Block a user