Compare commits

...
Author SHA1 Message Date
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
bc0295487b Version Packages (#8339)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-10-30 07:07:28 +07:00
FirekeeperandGitHub 90b779107d Add Solana wallet balance endpoint and UI integration (#8338) 2025-10-30 06:56:00 +07:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
afe187de95 Version Packages (#8336)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-10-29 16:10:10 -07:00
759b93beff [SDK] Fix: move @storybook/react package to be a dev dependency (#8306)
Co-authored-by: Jonas Daniels <[email protected]>
2025-10-29 15:58:10 -07:00
Jonas DanielsandGitHub 8e357b3cb3 setup @thirdweb-dev/nexus package (#8332) 2025-10-29 15:55:12 -07:00
kumaryash90 6c318f83d6 Update implementations (#8334)
<!--

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on updating the `MarketplaceV3` contract addresses and the `ZKSYNC_WETH` token addresses in the `implementations.ts` file.

### Detailed summary
- Updated `MarketplaceV3` address for network `232` from `0x9742f5ac11958cFAd151eBF0Fc31302fA409036E` to `0xB7387cF425eC8Da1b04b8E34De86245cb32bCA6D`.
- Updated `MarketplaceV3` address for network `37111` from `0x56Abb6a3f25DCcdaDa106191053b1CC54C196DEE` to `0xF8fd1016420c2B7832714241d36Efe55D41df126`.
- Updated `ZKSYNC_WETH` address for network `232` from `0xE5ecd226b3032910CEaa43ba92EE8232f8237553` to `0x6bDc36E20D267Ff0dd6097799f82e78907105e2F`.
- Updated `ZKSYNC_WETH` address for network `37111` from `0xaA91D645D7a6C1aeaa5988e0547267B77d33fe16` to `0xeee5a340Cdc9c179Db25dea45AcfD5FE8d4d3eB8`.

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

<!-- end pr-codex -->

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

* **Chores**
  * Updated contract addresses for zkSync networks: MarketplaceV3 and WETH addresses replaced for chain 232 and chain 37111.
  * Added a changeset marking a patch release (release metadata).
  * No public API or exported signatures changed.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-29 21:14:05 +00:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
f790c9fae9 Version Packages (#8328)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-10-29 02:14:23 -07:00
Joaquim VergesandGitHub bcbb3f540a [X402] Simplify FacilitatorNetworkSchema to accept any string (#8331) 2025-10-29 02:11:13 -07:00
Joaquim VergesandGitHub 931c730a08 [Paywall] Support string price format when tokenAddress is not provided (#8330) 2025-10-28 12:09:59 -07:00
YashandGitHub e0c83121c2 add arc testnet to chains package (#8325) 2025-10-28 12:09:02 -07:00
MananTank a82a31e5ad Dashboard: Use monotone curve type for AreaChart to avoid clipping (#8329)
<!--

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview

This PR focuses on updating the `type` property for charts from `natural` to `monotone` in various components to improve the visual representation of the data.

### Detailed summary

- In `empty-chart-state.tsx`, changed `type` from `natural` to `monotone`.
- In `area-chart.tsx`, changed `type` from `natural` to `monotone`.
- In `ErrorRate.tsx`, changed `type` from `natural` to `monotone`.

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

<!-- end pr-codex -->

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

## Summary by CodeRabbit

* **Style**
  * Updated chart line rendering across multiple dashboard components for improved visual consistency. Area and bar charts now use refined curve interpolation for smoother data visualization.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-28 18:14:30 +00:00
MananTank 3d235e218a Dashboard: Update faucet amount for arc testnet (#8326)
<!--

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview

This PR adds a new entry for the `Arc testnet` in the `faucet.ts` file, enhancing the functionality to support an additional test network alongside the existing `Sophon testnet`.

### Detailed summary

- Added a comment for `Arc testnet`.
- Introduced a new entry `5042002: 1` for the `Arc testnet`.

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

<!-- end pr-codex -->

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

## Summary by CodeRabbit

- **New Features**
  - Introduced faucet support for the Arc testnet, allowing users connected to this network to claim test funds.
  - The claim amount on Arc is set to 1, ensuring consistent and predictable claims on that network.
  - Existing networks and the default claim behavior remain unchanged, preserving current user flows while expanding testnet coverage.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-28 17:53:36 +00:00
MananTank c08fb1356e SDK: Fix Safe wallet connection issues with WalletConnect (#8327)
<!--

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

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

## Notes for the reviewer

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

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview

This PR addresses issues with the Safe wallet connection when using `WalletConnect`, ensuring better handling of account retrieval.

### Detailed summary

- Updated the comment for `account` retrieval to clarify that it grabs the address from mainnet if available.
- Modified the `firstAccountOn` function to return the first account from the session if no matching account is found.

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

<!-- end pr-codex -->

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

## Summary by CodeRabbit

* **Bug Fixes**
  * Fixed Safe wallet connection issues with WalletConnect

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-28 17:19:37 +00:00
50 changed files with 3378 additions and 1475 deletions
+9 -2
View File
@@ -188,9 +188,16 @@ jobs:
- name: Build Packages
run: pnpm build
- name: Report bundle size
- name: Report bundle size (thirdweb)
uses: andresz1/size-limit-action@94bc357df29c36c8f8d50ea497c3e225c3c95d1d # v1.8.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
package_manager: pnpm
directory: packages/thirdweb
directory: packages/thirdweb
- name: Report bundle size (nexus)
uses: andresz1/size-limit-action@94bc357df29c36c8f8d50ea497c3e225c3c95d1d # v1.8.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
package_manager: pnpm
directory: packages/nexus
@@ -95,7 +95,7 @@ function SkeletonBarChart(props: {
fill={`url(#${fillAreaSkeletonId})`}
radius={8}
stroke="hsl(var(--muted-foreground))"
type="natural"
type="monotone"
/>
</AreaChart>
)}
@@ -172,7 +172,7 @@ export function ThirdwebAreaChart<TConfig extends ChartConfig>(
key={key}
stackId={props.variant !== "stacked" ? undefined : "a"}
stroke={`var(--color-${key})`}
type="natural"
type="monotone"
/>
),
)}
+2
View File
@@ -5,6 +5,8 @@ const customClaimAmounts: Record<number, number> = {
631571: 0.1,
// Sophon testnet
531050104: 5,
// Arc testnet
5042002: 1,
};
const defaultClaimAmount = 0.01;
@@ -59,7 +59,6 @@ export function ErrorRate({ datapoints }: { datapoints: ResultItem[] }) {
key={tag}
stackId="a"
stroke={`var(--color-${tag})`}
type="natural"
/>,
);
}
@@ -40,6 +40,13 @@ import {
PaginationNext,
PaginationPrevious,
} from "@/components/ui/pagination";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Skeleton } from "@/components/ui/skeleton";
import { Switch } from "@/components/ui/switch";
import {
@@ -57,6 +64,7 @@ import { useV5DashboardChain } from "@/hooks/chains/v5-adapter";
import { WalletProductIcon } from "@/icons/WalletProductIcon";
import { useDashboardRouter } from "@/lib/DashboardRouter";
import { cn } from "@/lib/utils";
import { fetchSolanaBalance } from "../lib/getSolanaBalance";
import { updateDefaultProjectWallet } from "../lib/vault.client";
import { CreateServerWallet } from "../server-wallets/components/create-server-wallet.client";
import type { Wallet as EVMWallet } from "../server-wallets/wallet-table/types";
@@ -79,6 +87,7 @@ interface ServerWalletsTableProps {
teamSlug: string;
client: ThirdwebClient;
solanaPermissionError?: boolean;
authToken: string;
}
export function ServerWalletsTable(props: ServerWalletsTableProps) {
@@ -95,10 +104,14 @@ export function ServerWalletsTable(props: ServerWalletsTableProps) {
solanaTotalPages,
client,
solanaPermissionError,
authToken,
} = props;
const [activeChain, setActiveChain] = useState<WalletChain>("evm");
const [selectedChainId, setSelectedChainId] = useState<number>(1);
const [selectedSolanaChain, setSelectedSolanaChain] = useState<
"solana:mainnet" | "solana:devnet"
>("solana:mainnet");
const [showSmartAccount, setShowSmartAccount] = useState(false);
const queryClient = useQueryClient();
@@ -145,11 +158,33 @@ export function ServerWalletsTable(props: ServerWalletsTableProps) {
</>
)}
{activeChain === "solana" && (
<CreateSolanaWallet
project={project}
teamSlug={teamSlug}
disabled={solanaPermissionError}
/>
<>
<CreateSolanaWallet
project={project}
teamSlug={teamSlug}
disabled={solanaPermissionError}
/>
<Select
value={selectedSolanaChain}
onValueChange={(value) =>
setSelectedSolanaChain(
value as "solana:mainnet" | "solana:devnet",
)
}
>
<SelectTrigger className="w-fit min-w-[180px] rounded-full bg-background hover:bg-accent/50">
<SelectValue placeholder="Select network" />
</SelectTrigger>
<SelectContent className="rounded-xl">
<SelectItem value="solana:mainnet" className="rounded-lg">
Solana Mainnet
</SelectItem>
<SelectItem value="solana:devnet" className="rounded-lg">
Solana Devnet
</SelectItem>
</SelectContent>
</Select>
</>
)}
</div>
@@ -278,6 +313,8 @@ export function ServerWalletsTable(props: ServerWalletsTableProps) {
project={project}
teamSlug={teamSlug}
client={client}
authToken={authToken}
chainId={selectedSolanaChain}
/>
))}
</TableBody>
@@ -507,11 +544,15 @@ function SolanaWalletRow({
project,
teamSlug,
client,
authToken,
chainId,
}: {
wallet: SolanaWallet;
project: Project;
teamSlug: string;
client: ThirdwebClient;
authToken: string;
chainId: "solana:mainnet" | "solana:devnet";
}) {
const engineService = project.services.find(
(s) => s.name === "engineCloud",
@@ -547,7 +588,12 @@ function SolanaWalletRow({
</TableCell>
<TableCell>
<SolanaWalletBalance publicKey={wallet.publicKey} />
<SolanaWalletBalance
publicKey={wallet.publicKey}
authToken={authToken}
clientId={project.publishableKey}
chainId={chainId}
/>
</TableCell>
<TableCell>
@@ -739,16 +785,27 @@ function WalletBalance({
);
}
function SolanaWalletBalance({ publicKey }: { publicKey: string }) {
function SolanaWalletBalance({
publicKey,
authToken,
clientId,
chainId,
}: {
publicKey: string;
authToken: string;
clientId: string;
chainId: "solana:mainnet" | "solana:devnet";
}) {
const balance = useQuery({
queryFn: async () => {
// TODO: Implement actual Solana balance fetching
return {
displayValue: "0",
symbol: "SOL",
};
return await fetchSolanaBalance({
publicKey,
authToken,
clientId,
chainId,
});
},
queryKey: ["solanaWalletBalance", publicKey],
queryKey: ["solanaWalletBalance", publicKey, chainId],
});
if (balance.isFetching) {
@@ -0,0 +1,60 @@
import { configure, getSolanaWalletBalance } from "@thirdweb-dev/api";
import { THIRDWEB_API_HOST } from "@/constants/urls";
// Configure the API client to use the correct base URL
configure({
override: {
baseUrl: THIRDWEB_API_HOST,
},
});
export async function fetchSolanaBalance({
publicKey,
authToken,
clientId,
chainId = "solana:mainnet",
}: {
publicKey: string;
authToken: string;
clientId: string;
chainId?: "solana:mainnet" | "solana:devnet";
}): Promise<{
displayValue: string;
symbol: string;
value: string;
decimals: number;
} | null> {
try {
const response = await getSolanaWalletBalance({
path: {
address: publicKey,
},
query: {
chainId,
},
headers: {
Authorization: `Bearer ${authToken}`,
"Content-Type": "application/json",
"x-client-id": clientId,
},
});
if (response.error || !response.data) {
console.error(
"Error fetching Solana balance:",
response.error || "No data returned",
);
return null;
}
return {
displayValue: response.data.result.displayValue,
symbol: "SOL",
value: response.data.result.value,
decimals: response.data.result.decimals,
};
} catch (error) {
console.error("Error fetching Solana balance:", error);
return null;
}
}
@@ -215,6 +215,7 @@ export default async function TransactionsAnalyticsPage(props: {
solanaWallets={solanaAccounts.data.items}
teamSlug={params.team_slug}
solanaPermissionError={isSolanaPermissionError}
authToken={authToken}
/>
)}
</div>
@@ -70,8 +70,8 @@ export function CreateSolanaWallet(props: {
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button
variant="default"
className="gap-2 rounded-full bg-foreground hover:bg-foreground/90"
variant="outline"
className="gap-1.5 rounded-full bg-background text-foreground"
disabled={props.disabled}
>
<PlusIcon className="size-4" />
@@ -202,6 +202,7 @@ export default async function Page(props: {
solanaWallets={solanaAccounts.data.items}
teamSlug={params.team_slug}
solanaPermissionError={isSolanaPermissionError || false}
authToken={authToken}
/>
)}
</div>
@@ -41,7 +41,7 @@ export async function GET(request: NextRequest) {
const amount = queryParams.get("amount") || "0.01";
const payTo = queryParams.get("payTo") ?? undefined;
const tokenAddress = queryParams.get("tokenAddress") || token.address;
const tokenAddress = queryParams.get("tokenAddress");
const decimals = queryParams.get("decimals") || token.decimals.toString();
const waitUntil =
(queryParams.get("waitUntil") as "simulated" | "submitted" | "confirmed") ||
@@ -53,13 +53,15 @@ export async function GET(request: NextRequest) {
paymentData,
network: defineChain(Number(chainId)),
payTo,
price: {
amount: toUnits(amount, parseInt(decimals)).toString(),
asset: {
address: tokenAddress as `0x${string}`,
decimals: decimals ? parseInt(decimals) : token.decimals,
},
},
price: tokenAddress
? {
amount: toUnits(amount, parseInt(decimals)).toString(),
asset: {
address: tokenAddress as `0x${string}`,
decimals: decimals ? parseInt(decimals) : token.decimals,
},
}
: amount,
routeConfig: {
description: "Access to paid content",
},
+16
View File
@@ -1,5 +1,21 @@
# wagmi-inapp
## 0.0.19
### Patch Changes
- Updated dependencies [[`6c318f8`](https://github.com/thirdweb-dev/js/commit/6c318f83d63aa8acce46da666e43638e410f799c), [`759b93b`](https://github.com/thirdweb-dev/js/commit/759b93beff7ab10a3a2fa3b9dd25ca628efb9267), [`8e357b3`](https://github.com/thirdweb-dev/js/commit/8e357b3cb39dcc7a8595a83224098a9c44585389)]:
- thirdweb@5.110.6
- @thirdweb-dev/wagmi-adapter@0.2.174
## 0.0.18
### Patch Changes
- Updated dependencies [[`c08fb13`](https://github.com/thirdweb-dev/js/commit/c08fb1356e77be9eeb0569905fcb2ab76a516dc3), [`bcbb3f5`](https://github.com/thirdweb-dev/js/commit/bcbb3f540a8eab2b02f9261b779bd2aad3464598), [`e0c8312`](https://github.com/thirdweb-dev/js/commit/e0c83121c201eb2dbdfa6a6b1bbb637f5ad2a21d)]:
- thirdweb@5.110.5
- @thirdweb-dev/wagmi-adapter@0.2.173
## 0.0.17
### Patch Changes
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "wagmi-inapp",
"private": true,
"version": "0.0.17",
"version": "0.0.19",
"type": "module",
"scripts": {
"dev": "vite",
+6
View File
@@ -1,5 +1,11 @@
# @thirdweb-dev/api
## 0.1.2
### Patch Changes
- [#8338](https://github.com/thirdweb-dev/js/pull/8338) [`90b7791`](https://github.com/thirdweb-dev/js/commit/90b779107d3cb726adeca6a3f885129c12599c6a) Thanks [@0xFirekeeper](https://github.com/0xFirekeeper)! - added solana token balances endpoint
## 0.1.1
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/api",
"version": "0.1.1",
"version": "0.1.2",
"repository": {
"type": "git",
"url": "git+https://github.com/thirdweb-dev/js.git#main"
+28
View File
@@ -66,6 +66,9 @@ import type {
GetSolanaTransactionData,
GetSolanaTransactionErrors,
GetSolanaTransactionResponses,
GetSolanaWalletBalanceData,
GetSolanaWalletBalanceErrors,
GetSolanaWalletBalanceResponses,
GetTokenOwnersData,
GetTokenOwnersErrors,
GetTokenOwnersResponses,
@@ -1590,6 +1593,31 @@ export const createSolanaWallet = <ThrowOnError extends boolean = false>(
});
};
/**
* Get Solana Wallet Balance
* Get the SOL or SPL token balance for a Solana wallet on a specific Solana network.
*
* **Authentication**: Pass `x-client-id` for frontend usage from allowlisted origins or `x-secret-key` for backend usage.
*/
export const getSolanaWalletBalance = <ThrowOnError extends boolean = false>(
options: Options<GetSolanaWalletBalanceData, ThrowOnError>,
) => {
return (options.client ?? _heyApiClient).get<
GetSolanaWalletBalanceResponses,
GetSolanaWalletBalanceErrors,
ThrowOnError
>({
security: [
{
name: "x-client-id",
type: "apiKey",
},
],
url: "/v1/solana/wallets/{address}/balance",
...options,
});
};
/**
* Sign Solana Message
* Sign an arbitrary message with a Solana wallet. Supports both text and hexadecimal message formats with automatic format detection.
+92 -83
View File
@@ -2135,6 +2135,10 @@ export type GetWalletTransactionsData = {
address: string;
};
query: {
/**
* Chain ID(s) to request transaction data for. You can specify multiple chain IDs by repeating the parameter, up to a maximum of 50. Example: ?chainId=1&chainId=137
*/
chainId: Array<number>;
/**
* Filter by block timestamp (Unix timestamp) greater than or equal to this value
*/
@@ -2171,10 +2175,6 @@ export type GetWalletTransactionsData = {
* Sort order: 'asc' for ascending, 'desc' for descending
*/
sortOrder?: "asc" | "desc";
/**
* Chain ID(s) to request transaction data for. You can specify multiple chain IDs by repeating the parameter, up to a maximum of 50. Example: ?chainId=1&chainId=137
*/
chainId: Array<number>;
};
url: "/v1/wallets/{address}/transactions";
};
@@ -3234,11 +3234,11 @@ export type WriteContractErrors = {
/**
* Payment ID
*/
id: string;
id?: string;
/**
* Bridge quote for completing the payment
*/
quote: {
quote?: {
/**
* Block number when quote was generated
*/
@@ -4314,11 +4314,11 @@ export type SendTransactionsErrors = {
/**
* Payment ID
*/
id: string;
id?: string;
/**
* Bridge quote for completing the payment
*/
quote: {
quote?: {
/**
* Block number when quote was generated
*/
@@ -4725,11 +4725,11 @@ export type PaymentsPurchaseErrors = {
/**
* Payment ID
*/
id: string;
id?: string;
/**
* Bridge quote for completing the payment
*/
quote: {
quote?: {
/**
* Block number when quote was generated
*/
@@ -4914,17 +4914,7 @@ export type VerifyX402PaymentData = {
paymentPayload: {
x402Version: number;
scheme: "exact";
network:
| "base-sepolia"
| "base"
| "avalanche-fuji"
| "avalanche"
| "iotex"
| "solana-devnet"
| "solana"
| "sei"
| "sei-testnet"
| string;
network: string;
payload:
| {
signature: string;
@@ -4943,17 +4933,7 @@ export type VerifyX402PaymentData = {
};
paymentRequirements: {
scheme: "exact";
network:
| "base-sepolia"
| "base"
| "avalanche-fuji"
| "avalanche"
| "iotex"
| "solana-devnet"
| "solana"
| "sei"
| "sei-testnet"
| string;
network: string;
maxAmountRequired: string;
resource: string;
description: string;
@@ -5015,7 +4995,6 @@ export type VerifyX402PaymentResponses = {
| "invalid_exact_svm_payload_transaction_instruction_not_spl_token_transfer_checked"
| "invalid_exact_svm_payload_transaction_instruction_not_token_2022_transfer_checked"
| "invalid_exact_svm_payload_transaction_not_a_transfer_instruction"
| "invalid_exact_svm_payload_transaction_cannot_derive_receiver_ata"
| "invalid_exact_svm_payload_transaction_receiver_ata_not_found"
| "invalid_exact_svm_payload_transaction_sender_ata_not_found"
| "invalid_exact_svm_payload_transaction_simulation_failed"
@@ -5049,17 +5028,7 @@ export type SettleX402PaymentData = {
paymentPayload: {
x402Version: number;
scheme: "exact";
network:
| "base-sepolia"
| "base"
| "avalanche-fuji"
| "avalanche"
| "iotex"
| "solana-devnet"
| "solana"
| "sei"
| "sei-testnet"
| string;
network: string;
payload:
| {
signature: string;
@@ -5078,17 +5047,7 @@ export type SettleX402PaymentData = {
};
paymentRequirements: {
scheme: "exact";
network:
| "base-sepolia"
| "base"
| "avalanche-fuji"
| "avalanche"
| "iotex"
| "solana-devnet"
| "solana"
| "sei"
| "sei-testnet"
| string;
network: string;
maxAmountRequired: string;
resource: string;
description: string;
@@ -5154,7 +5113,6 @@ export type SettleX402PaymentResponses = {
| "invalid_exact_svm_payload_transaction_instruction_not_spl_token_transfer_checked"
| "invalid_exact_svm_payload_transaction_instruction_not_token_2022_transfer_checked"
| "invalid_exact_svm_payload_transaction_not_a_transfer_instruction"
| "invalid_exact_svm_payload_transaction_cannot_derive_receiver_ata"
| "invalid_exact_svm_payload_transaction_receiver_ata_not_found"
| "invalid_exact_svm_payload_transaction_sender_ata_not_found"
| "invalid_exact_svm_payload_transaction_simulation_failed"
@@ -5183,7 +5141,10 @@ export type SettleX402PaymentResponses = {
| "solana-devnet"
| "solana"
| "sei"
| "sei-testnet";
| "sei-testnet"
| "polygon"
| "polygon-amoy"
| "peaq";
errorMessage?: string;
};
};
@@ -5226,17 +5187,7 @@ export type SupportedX402PaymentsResponses = {
kinds: Array<{
x402Version: 1;
scheme: "exact";
network:
| "base-sepolia"
| "base"
| "avalanche-fuji"
| "avalanche"
| "iotex"
| "solana-devnet"
| "solana"
| "sei"
| "sei-testnet"
| string;
network: string;
extra?: {
defaultAsset?: {
/**
@@ -5340,11 +5291,11 @@ export type FetchWithPaymentErrors = {
/**
* Payment ID
*/
id: string;
id?: string;
/**
* Bridge quote for completing the payment
*/
quote: {
quote?: {
/**
* Block number when quote was generated
*/
@@ -5552,17 +5503,7 @@ export type ListPayableServicesResponses = {
x402Version: number;
accepts: Array<{
scheme: "exact";
network:
| "base-sepolia"
| "base"
| "avalanche-fuji"
| "avalanche"
| "iotex"
| "solana-devnet"
| "solana"
| "sei"
| "sei-testnet"
| string;
network: string;
maxAmountRequired: string;
resource: string;
description: string;
@@ -6298,11 +6239,11 @@ export type BridgeSwapErrors = {
/**
* Payment ID
*/
id: string;
id?: string;
/**
* Bridge quote for completing the payment
*/
quote: {
quote?: {
/**
* Block number when quote was generated
*/
@@ -6640,6 +6581,74 @@ export type CreateSolanaWalletResponses = {
export type CreateSolanaWalletResponse =
CreateSolanaWalletResponses[keyof CreateSolanaWalletResponses];
export type GetSolanaWalletBalanceData = {
body?: never;
path: {
/**
* Public key of the Solana wallet.
*/
address: string;
};
query: {
/**
* Solana network to query. Choose either solana:mainnet or solana:devnet.
*/
chainId: "solana:mainnet" | "solana:devnet";
/**
* SPL token mint address. Omit to retrieve native SOL balance.
*/
tokenAddress?: string;
};
url: "/v1/solana/wallets/{address}/balance";
};
export type GetSolanaWalletBalanceErrors = {
/**
* Invalid request parameters. This occurs when the wallet address or token mint is invalid, or the request mixes a token mint with multiple networks.
*/
400: unknown;
/**
* Authentication required. Include `x-client-id` or `x-secret-key` headers.
*/
401: unknown;
/**
* Internal server error. This may occur due to RPC connectivity issues or unexpected failures.
*/
500: unknown;
};
export type GetSolanaWalletBalanceResponses = {
/**
* Wallet balance retrieved successfully for the requested Solana network.
*/
200: {
/**
* Balance data for the requested Solana network.
*/
result: {
/**
* Requested Solana network.
*/
chainId: "solana:mainnet" | "solana:devnet";
/**
* Number of decimals used by the token.
*/
decimals: number;
/**
* Human-readable balance formatted using token decimals.
*/
displayValue: string;
/**
* Raw balance value expressed in base units (lamports).
*/
value: string;
};
};
};
export type GetSolanaWalletBalanceResponse =
GetSolanaWalletBalanceResponses[keyof GetSolanaWalletBalanceResponses];
export type SignSolanaMessageData = {
/**
* Request payload for signing an arbitrary Solana message.
+14
View File
@@ -1,5 +1,19 @@
# @thirdweb-dev/nebula
## 0.2.77
### Patch Changes
- Updated dependencies [[`6c318f8`](https://github.com/thirdweb-dev/js/commit/6c318f83d63aa8acce46da666e43638e410f799c), [`759b93b`](https://github.com/thirdweb-dev/js/commit/759b93beff7ab10a3a2fa3b9dd25ca628efb9267), [`8e357b3`](https://github.com/thirdweb-dev/js/commit/8e357b3cb39dcc7a8595a83224098a9c44585389)]:
- thirdweb@5.110.6
## 0.2.76
### Patch Changes
- Updated dependencies [[`c08fb13`](https://github.com/thirdweb-dev/js/commit/c08fb1356e77be9eeb0569905fcb2ab76a516dc3), [`bcbb3f5`](https://github.com/thirdweb-dev/js/commit/bcbb3f540a8eab2b02f9261b779bd2aad3464598), [`e0c8312`](https://github.com/thirdweb-dev/js/commit/e0c83121c201eb2dbdfa6a6b1bbb637f5ad2a21d)]:
- thirdweb@5.110.5
## 0.2.75
### Patch Changes
+1 -1
View File
@@ -57,5 +57,5 @@
"type": "module",
"types": "./dist/types/exports/thirdweb.d.ts",
"typings": "./dist/types/exports/thirdweb.d.ts",
"version": "0.2.75"
"version": "0.2.77"
}
+13
View File
@@ -0,0 +1,13 @@
[
{
"import": "*",
"limit": "110 kB",
"name": "@thirdweb-dev/nexus (esm)",
"path": "./dist/esm/exports/nexus.js"
},
{
"limit": "350 kB",
"name": "@thirdweb-dev/nexus (cjs)",
"path": "./dist/cjs/exports/nexus.js"
}
]
+7
View File
@@ -0,0 +1,7 @@
# @thirdweb-dev/nexus
## 0.1.0
### Minor Changes
- [#8332](https://github.com/thirdweb-dev/js/pull/8332) [`8e357b3`](https://github.com/thirdweb-dev/js/commit/8e357b3cb39dcc7a8595a83224098a9c44585389) Thanks [@jnsdls](https://github.com/jnsdls)! - initial release
+16
View File
@@ -0,0 +1,16 @@
{
"$schema": "https://biomejs.dev/schemas/2.0.6/schema.json",
"extends": "//",
"overrides": [
{
"assist": {
"actions": {
"source": {
"useSortedKeys": "off"
}
}
},
"includes": ["package.json"]
}
]
}
+12
View File
@@ -0,0 +1,12 @@
{
"$schema": "https://unpkg.com/knip@5/schema.json",
"entry": ["src/exports/**"],
"ignore": ["src/**/__generated__/**", "**/*.bench.ts"],
"ignoreBinaries": ["printf"],
"ignoreDependencies": ["tslib"],
"project": ["src/**/*.{ts,tsx}"],
"rules": {
"enumMembers": "off",
"optionalPeerDependencies": "off"
}
}
+79
View File
@@ -0,0 +1,79 @@
{
"author": "thirdweb eng <[email protected]>",
"browser": {
"crypto": false
},
"bugs": {
"url": "https://github.com/thirdweb-dev/js/issues"
},
"dependencies": {
"x402": "0.7.0",
"zod": "3.25.75"
},
"devDependencies": {
"@biomejs/biome": "2.0.6",
"@size-limit/preset-small-lib": "11.2.0",
"knip": "5.60.2",
"rimraf": "6.0.1",
"size-limit": "11.2.0",
"typescript": "5.8.3"
},
"engines": {
"node": ">=22"
},
"exports": {
".": {
"types": "./dist/types/exports/nexus.d.ts",
"import": "./dist/esm/exports/nexus.js",
"default": "./dist/cjs/exports/nexus.js"
}
},
"files": [
"dist/*",
"src/*",
"!**/*.tsbuildinfo",
"!**/*.test.ts",
"!**/*.test.tsx",
"!**/*.test.ts.snap",
"!**/*.test-d.ts",
"!**/*.bench.ts",
"!tsconfig.build.json"
],
"license": "Apache-2.0",
"main": "./dist/cjs/exports/nexus.js",
"module": "./dist/esm/exports/nexus.js",
"name": "@thirdweb-dev/nexus",
"peerDependencies": {
"typescript": ">=5.0.4"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
},
"repository": {
"type": "git",
"url": "git+https://github.com/thirdweb-dev/js.git#main"
},
"scripts": {
"build": "pnpm clean && pnpm build:types && pnpm build:cjs && pnpm build:esm",
"build:cjs": "tsc --noCheck --project ./tsconfig.build.json --module commonjs --outDir ./dist/cjs --verbatimModuleSyntax false && printf '{\"type\":\"commonjs\"}' > ./dist/cjs/package.json",
"build:esm": "tsc --noCheck --project ./tsconfig.build.json --module es2020 --outDir ./dist/esm && printf '{\"type\": \"module\",\"sideEffects\":false}' > ./dist/esm/package.json",
"build:types": "tsc --project ./tsconfig.build.json --module nodenext --moduleResolution nodenext --declarationDir ./dist/types --emitDeclarationOnly --declaration --declarationMap",
"clean": "rimraf dist",
"dev": "tsc --project ./tsconfig.build.json --module nodenext --moduleResolution nodenext --outDir ./dist/esm --watch",
"dev:cjs": "printf '{\"type\":\"commonjs\"}' > ./dist/cjs/package.json && tsc --noCheck --project ./tsconfig.build.json --module commonjs --outDir ./dist/cjs --verbatimModuleSyntax false --watch",
"dev:esm": "printf '{\"type\": \"module\",\"sideEffects\":false}' > ./dist/esm/package.json && tsc --noCheck --project ./tsconfig.build.json --module es2020 --outDir ./dist/esm --watch",
"fix": "biome check ./src --fix",
"format": "biome format ./src --write",
"knip": "knip",
"lint": "knip && biome check ./src && tsc --project ./tsconfig.build.json --module nodenext --moduleResolution nodenext --noEmit",
"size": "size-limit",
"typecheck": "tsc --project ./tsconfig.build.json --module nodenext --moduleResolution nodenext --noEmit"
},
"sideEffects": false,
"type": "module",
"types": "./dist/types/exports/nexus.d.ts",
"typings": "./dist/types/exports/nexus.d.ts",
"version": "0.1.0"
}
+320
View File
@@ -0,0 +1,320 @@
import { ChainIdToNetwork, type Money, moneySchema } from "x402/types";
import { decodePayment } from "./encode.js";
import type { ThirdwebX402Facilitator } from "./facilitator.js";
import {
networkToChainId,
type RequestedPaymentPayload,
type RequestedPaymentRequirements,
} from "./schemas.js";
import {
type DefaultAsset,
type ERC20TokenAmount,
type PaymentArgs,
type PaymentRequiredResult,
type SupportedSignatureType,
x402Version,
} from "./types.js";
import { toUnits } from "./utils.js";
type GetPaymentRequirementsResult = {
status: 200;
paymentRequirements: RequestedPaymentRequirements[];
selectedPaymentRequirements: RequestedPaymentRequirements;
decodedPayment: RequestedPaymentPayload;
};
/**
* Decodes a payment request and returns the payment requirements, selected payment requirements, and decoded payment
* @param args
* @returns The payment requirements, selected payment requirements, and decoded payment
*/
export async function decodePaymentRequest(
args: PaymentArgs,
): Promise<GetPaymentRequirementsResult | PaymentRequiredResult> {
const {
price,
network,
facilitator,
payTo,
resourceUrl,
routeConfig = {},
method,
paymentData,
} = args;
const {
description,
mimeType,
maxTimeoutSeconds,
inputSchema,
outputSchema,
errorMessages,
discoverable,
} = routeConfig;
let chainId: number;
try {
chainId = networkToChainId(network);
} catch (error) {
return {
status: 402,
responseHeaders: { "Content-Type": "application/json" },
responseBody: {
x402Version,
error:
error instanceof Error
? error.message
: `Invalid network: ${network}`,
accepts: [],
},
};
}
const atomicAmountForAsset = await processPriceToAtomicAmount(
price,
chainId,
facilitator,
);
if ("error" in atomicAmountForAsset) {
return {
status: 402,
responseHeaders: { "Content-Type": "application/json" },
responseBody: {
x402Version,
error: atomicAmountForAsset.error,
accepts: [],
},
};
}
const { maxAmountRequired, asset } = atomicAmountForAsset;
const paymentRequirements: RequestedPaymentRequirements[] = [];
const mappedNetwork = ChainIdToNetwork[chainId];
paymentRequirements.push({
scheme: "exact",
network: mappedNetwork ? mappedNetwork : `eip155:${chainId}`,
maxAmountRequired,
resource: resourceUrl,
description: description ?? "",
mimeType: mimeType ?? "application/json",
payTo: facilitator.address as `0x${string}`, // always pay to the facilitator address first
maxTimeoutSeconds: maxTimeoutSeconds ?? 86400,
asset: asset.address as `0x${string}`,
outputSchema: {
input: {
type: "http",
method,
discoverable: discoverable ?? true,
...inputSchema,
},
output: outputSchema,
},
extra: {
recipientAddress: payTo, // input payTo is the final recipient address
...((asset as ERC20TokenAmount["asset"]).eip712 ?? {}),
},
});
// Check for payment header
if (!paymentData) {
return {
status: 402,
responseHeaders: {
"Content-Type": "application/json",
},
responseBody: {
x402Version,
error: errorMessages?.paymentRequired || "X-PAYMENT header is required",
accepts: paymentRequirements,
},
};
}
// decode b64 payment
let decodedPayment: RequestedPaymentPayload;
try {
decodedPayment = decodePayment(paymentData);
decodedPayment.x402Version = x402Version;
} catch (error) {
return {
status: 402,
responseHeaders: {
"Content-Type": "application/json",
},
responseBody: {
x402Version,
error:
errorMessages?.invalidPayment ||
(error instanceof Error ? error.message : "Invalid payment"),
accepts: paymentRequirements,
},
};
}
const selectedPaymentRequirements = paymentRequirements.find(
(value) =>
value.scheme === decodedPayment.scheme &&
networkToChainId(value.network) ===
networkToChainId(decodedPayment.network),
);
if (!selectedPaymentRequirements) {
return {
status: 402,
responseHeaders: {
"Content-Type": "application/json",
},
responseBody: {
x402Version,
error:
errorMessages?.noMatchingRequirements ||
"Unable to find matching payment requirements",
accepts: paymentRequirements,
},
};
}
return {
status: 200,
paymentRequirements,
decodedPayment,
selectedPaymentRequirements,
};
}
/**
* Parses the amount from the given price
*
* @param price - The price to parse
* @param network - The network to get the default asset for
* @returns The parsed amount or an error message
*/
async function processPriceToAtomicAmount(
price: Money | ERC20TokenAmount,
chainId: number,
facilitator: ThirdwebX402Facilitator,
): Promise<
{ maxAmountRequired: string; asset: DefaultAsset } | { error: string }
> {
// Handle USDC amount (string) or token amount (ERC20TokenAmount)
let maxAmountRequired: string;
let asset: DefaultAsset;
if (typeof price === "string" || typeof price === "number") {
// USDC amount in dollars
const parsedAmount = moneySchema.safeParse(price);
if (!parsedAmount.success) {
return {
error: `Invalid price (price: ${price}). Must be in the form "$3.10", 0.10, "0.001", ${parsedAmount.error}`,
};
}
const parsedUsdAmount = parsedAmount.data;
const defaultAsset = await getDefaultAsset(chainId, facilitator);
if (!defaultAsset) {
return {
error: `Unable to get default asset on chain ${chainId}. Please specify an asset in the payment requirements.`,
};
}
asset = defaultAsset;
maxAmountRequired = toUnits(
parsedUsdAmount.toString(),
defaultAsset.decimals,
).toString();
} else {
// Token amount in atomic units
maxAmountRequired = price.amount;
const tokenExtras = await getOrDetectTokenExtras({
facilitator,
partialAsset: price.asset,
chainId,
});
if (!tokenExtras) {
return {
error: `Unable to find token information for ${price.asset.address} on chain ${chainId}. Please specify the asset decimals and eip712 information in the asset options.`,
};
}
asset = {
address: price.asset.address,
decimals: tokenExtras.decimals,
eip712: {
name: tokenExtras.name,
version: tokenExtras.version,
primaryType: tokenExtras.primaryType,
},
};
}
return {
maxAmountRequired,
asset,
};
}
async function getDefaultAsset(
chainId: number,
facilitator: ThirdwebX402Facilitator,
): Promise<DefaultAsset | undefined> {
const supportedAssets = await facilitator.supported();
const matchingAsset = supportedAssets.kinds.find(
(supported) => networkToChainId(supported.network) === chainId,
);
const assetConfig = matchingAsset?.extra?.defaultAsset as DefaultAsset;
return assetConfig;
}
async function getOrDetectTokenExtras(args: {
facilitator: ThirdwebX402Facilitator;
partialAsset: ERC20TokenAmount["asset"];
chainId: number;
}): Promise<
| {
name: string;
version: string;
decimals: number;
primaryType: SupportedSignatureType;
}
| undefined
> {
const { facilitator, partialAsset, chainId } = args;
if (
partialAsset.eip712?.name &&
partialAsset.eip712?.version &&
partialAsset.decimals !== undefined
) {
return {
name: partialAsset.eip712.name,
version: partialAsset.eip712.version,
decimals: partialAsset.decimals,
primaryType: partialAsset.eip712.primaryType,
};
}
// read from facilitator
const response = await facilitator
.supported({
chainId,
tokenAddress: partialAsset.address,
})
.catch(() => {
return {
kinds: [],
};
});
const exactScheme = response.kinds?.find((kind) => kind.scheme === "exact");
if (!exactScheme) {
return undefined;
}
const supportedAsset = exactScheme.extra?.supportedAssets?.find(
(asset) =>
asset.address.toLowerCase() === partialAsset.address.toLowerCase(),
);
if (!supportedAsset) {
return undefined;
}
return {
name: supportedAsset.eip712.name,
version: supportedAsset.eip712.version,
decimals: supportedAsset.decimals,
primaryType: supportedAsset.eip712.primaryType as SupportedSignatureType,
};
}
+81
View File
@@ -0,0 +1,81 @@
import type { ExactEvmPayload } from "x402/types";
import {
type RequestedPaymentPayload,
RequestedPaymentPayloadSchema,
} from "./schemas.js";
/**
* Encodes a payment payload into a base64 string, ensuring bigint values are properly stringified
*
* @param payment - The payment payload to encode
* @returns A base64 encoded string representation of the payment payload
*/
export function encodePayment(payment: RequestedPaymentPayload): string {
let safe: RequestedPaymentPayload;
// evm
const evmPayload = payment.payload as ExactEvmPayload;
safe = {
...payment,
payload: {
...evmPayload,
authorization: Object.fromEntries(
Object.entries(evmPayload.authorization).map(([key, value]) => [
key,
typeof value === "bigint" ? (value as bigint).toString() : value,
]),
) as ExactEvmPayload["authorization"],
},
};
return safeBase64Encode(JSON.stringify(safe));
}
/**
* Decodes a base64 encoded payment string back into a PaymentPayload object
*
* @param payment - The base64 encoded payment string to decode
* @returns The decoded and validated PaymentPayload object
*/
export function decodePayment(payment: string): RequestedPaymentPayload {
const decoded = safeBase64Decode(payment);
const parsed = JSON.parse(decoded);
const obj: RequestedPaymentPayload = {
...parsed,
payload: parsed.payload as ExactEvmPayload,
};
const validated = RequestedPaymentPayloadSchema.parse(obj);
return validated;
}
/**
* Encodes a string to base64 format
*
* @param data - The string to be encoded to base64
* @returns The base64 encoded string
*/
export function safeBase64Encode(data: string): string {
if (
typeof globalThis !== "undefined" &&
typeof globalThis.btoa === "function"
) {
return globalThis.btoa(data);
}
return Buffer.from(data).toString("base64");
}
/**
* Decodes a base64 string back to its original format
*
* @param data - The base64 encoded string to be decoded
* @returns The decoded string in UTF-8 format
*/
function safeBase64Decode(data: string): string {
if (
typeof globalThis !== "undefined" &&
typeof globalThis.atob === "function"
) {
return globalThis.atob(data);
}
return Buffer.from(data, "base64").toString("utf-8");
}
+24
View File
@@ -0,0 +1,24 @@
export type {
HTTPRequestStructure,
Money,
PaymentMiddlewareConfig,
Resource,
} from "x402/types";
export { decodePayment, encodePayment } from "../encode.js";
export {
createFacilitator,
type ThirdwebX402Facilitator,
type ThirdwebX402FacilitatorConfig,
type WaitUntil,
} from "../facilitator.js";
export { settlePayment } from "../settle-payment.js";
export type {
ERC20TokenAmount,
PaymentArgs,
PaymentRequiredResult,
SettlePaymentArgs,
SettlePaymentResult,
SupportedSignatureType,
VerifyPaymentResult,
} from "../types.js";
export { verifyPayment } from "../verify-payment.js";
+233
View File
@@ -0,0 +1,233 @@
import type { VerifyResponse } from "x402/types";
import type {
FacilitatorSettleResponse,
FacilitatorSupportedResponse,
FacilitatorVerifyResponse,
RequestedPaymentPayload,
RequestedPaymentRequirements,
} from "./schemas.js";
import { stringify } from "./utils.js";
export type WaitUntil = "simulated" | "submitted" | "confirmed";
export type ThirdwebX402FacilitatorConfig = {
walletSecret: string;
walletAddress: string;
waitUntil?: WaitUntil;
baseUrl?: string;
};
/**
* facilitator for the x402 payment protocol.
* @public
*/
export type ThirdwebX402Facilitator = {
url: `${string}://${string}`;
address: string;
createAuthHeaders: () => Promise<{
verify: Record<string, string>;
settle: Record<string, string>;
supported: Record<string, string>;
list: Record<string, string>;
}>;
verify: (
payload: RequestedPaymentPayload,
paymentRequirements: RequestedPaymentRequirements,
) => Promise<FacilitatorVerifyResponse>;
settle: (
payload: RequestedPaymentPayload,
paymentRequirements: RequestedPaymentRequirements,
waitUntil?: WaitUntil,
) => Promise<FacilitatorSettleResponse>;
supported: (filters?: {
chainId: number;
tokenAddress?: string;
}) => Promise<FacilitatorSupportedResponse>;
};
const DEFAULT_BASE_URL = "https://nexus-api.thirdweb.com";
/**
* Creates a facilitator for the x402 payment protocol.
* You can use this with `settlePayment` or with any x402 middleware to enable settling transactions with your thirdweb server wallet.
*
* @param config - The configuration for the facilitator
* @returns a x402 compatible FacilitatorConfig
*
* @example
* ```ts
* import { createFacilitator } from "@thirdweb-dev/nexus";
* import { paymentMiddleware } from 'x402-hono'
*
* const facilitator = createFacilitator({
* walletSecret: <your-wallet-secret>,
* walletAddress: <your-wallet-address>,
* });
*
* // add the facilitator to any x402 payment middleware
* const middleware = paymentMiddleware(
* facilitator.address,
* {
* "/api/paywall": {
* price: "$0.01",
* network: "base-sepolia",
* config: {
* description: "Access to paid content",
* },
* },
* },
* facilitator,
* );
* ```
*
* #### Configuration Options
*
* ```ts
* const thirdwebX402Facilitator = createFacilitator({
* walletSecret: <your-wallet-secret>,
* walletAddress: <your-wallet-address>,
* // Optional: Wait behavior for settlements
* // - "simulated": Only simulate the transaction (fastest)
* // - "submitted": Wait until transaction is submitted
* // - "confirmed": Wait for full on-chain confirmation (slowest, default)
* waitUntil: "confirmed",
* });
* ```
*
*/
export function createFacilitator(
config: ThirdwebX402FacilitatorConfig,
): ThirdwebX402Facilitator {
if (!config.walletSecret) {
throw new Error("Wallet secret is required for the x402 facilitator");
}
if (!config.walletAddress) {
throw new Error("Wallet address is required for the x402 facilitator");
}
const BASE_URL = config.baseUrl ?? DEFAULT_BASE_URL;
const AUTH_HEADERS = {
verify: {
authorization: `Bearer ${config.walletSecret}`,
},
settle: {
authorization: `Bearer ${config.walletSecret}`,
},
supported: {
authorization: `Bearer ${config.walletSecret}`,
},
list: {
authorization: `Bearer ${config.walletSecret}`,
},
} as const;
return {
url: BASE_URL as `${string}://${string}`,
address: config.walletAddress,
createAuthHeaders: async () => AUTH_HEADERS,
/**
* Verifies a payment payload with the facilitator service
*
* @param payload - The payment payload to verify
* @param paymentRequirements - The payment requirements to verify against
* @returns A promise that resolves to the verification response
*/
async verify(
payload: RequestedPaymentPayload,
paymentRequirements: RequestedPaymentRequirements,
): Promise<FacilitatorVerifyResponse> {
let headers = { "Content-Type": "application/json" };
headers = { ...headers, ...AUTH_HEADERS.verify };
const res = await fetch(new URL("/verify", BASE_URL), {
method: "POST",
headers,
body: stringify({
x402Version: payload.x402Version,
paymentPayload: payload,
paymentRequirements: paymentRequirements,
}),
});
if (res.status !== 200) {
const text = `${res.statusText} ${await res.text()}`;
throw new Error(`Failed to verify payment: ${res.status} ${text}`);
}
const data = await res.json();
return data as VerifyResponse;
},
/**
* Settles a payment with the facilitator service
*
* @param payload - The payment payload to settle
* @param paymentRequirements - The payment requirements for the settlement
* @returns A promise that resolves to the settlement response
*/
async settle(
payload: RequestedPaymentPayload,
paymentRequirements: RequestedPaymentRequirements,
waitUntil?: WaitUntil,
): Promise<FacilitatorSettleResponse> {
let headers = { "Content-Type": "application/json" };
headers = { ...headers, ...AUTH_HEADERS.settle };
const waitUntilParam = waitUntil || config.waitUntil;
const res = await fetch(new URL("/settle", BASE_URL), {
method: "POST",
headers,
body: stringify({
x402Version: payload.x402Version,
paymentPayload: payload,
paymentRequirements: paymentRequirements,
...(waitUntilParam ? { waitUntil: waitUntilParam } : {}),
}),
});
if (res.status !== 200) {
const text = `${res.statusText} ${await res.text()}`;
throw new Error(`Failed to settle payment: ${res.status} ${text}`);
}
const data = await res.json();
return data as FacilitatorSettleResponse;
},
/**
* Gets the supported payment kinds from the facilitator service.
*
* @returns A promise that resolves to the supported payment kinds
*/
async supported(filters?: {
chainId: number;
tokenAddress?: string;
}): Promise<FacilitatorSupportedResponse> {
const headers = {
"Content-Type": "application/json",
...AUTH_HEADERS.supported,
};
const supportedUrl = new URL("/supported", BASE_URL);
if (filters?.chainId) {
supportedUrl.searchParams.set("chainId", filters.chainId.toString());
}
if (filters?.tokenAddress) {
supportedUrl.searchParams.set("tokenAddress", filters.tokenAddress);
}
const res = await fetch(supportedUrl, { headers });
if (res.status !== 200) {
throw new Error(
`Failed to get supported payment kinds: ${res.statusText}`,
);
}
const data = await res.json();
return data as FacilitatorSupportedResponse;
},
} as const satisfies ThirdwebX402Facilitator;
}
+109
View File
@@ -0,0 +1,109 @@
import {
EvmNetworkToChainId,
type Network,
PaymentPayloadSchema,
PaymentRequirementsSchema,
SettleResponseSchema,
SupportedPaymentKindsResponseSchema,
VerifyResponseSchema,
} from "x402/types";
import { z } from "zod";
const FacilitatorNetworkSchema = z.string();
export type FacilitatorNetwork = z.infer<typeof FacilitatorNetworkSchema>;
export const RequestedPaymentPayloadSchema = PaymentPayloadSchema.extend({
network: FacilitatorNetworkSchema,
});
export type RequestedPaymentPayload = z.infer<
typeof RequestedPaymentPayloadSchema
>;
const RequestedPaymentRequirementsSchema = PaymentRequirementsSchema.extend({
network: FacilitatorNetworkSchema,
});
export type RequestedPaymentRequirements = z.infer<
typeof RequestedPaymentRequirementsSchema
>;
const FacilitatorSettleResponseSchema = SettleResponseSchema.extend({
network: FacilitatorNetworkSchema,
errorMessage: z.string().optional(),
});
export type FacilitatorSettleResponse = z.infer<
typeof FacilitatorSettleResponseSchema
>;
const FacilitatorVerifyResponseSchema = VerifyResponseSchema.extend({
errorMessage: z.string().optional(),
});
export type FacilitatorVerifyResponse = z.infer<
typeof FacilitatorVerifyResponseSchema
>;
export const SupportedSignatureTypeSchema = z.enum([
"TransferWithAuthorization",
"Permit",
]);
export const FacilitatorSupportedAssetSchema = z.object({
address: z.string(),
decimals: z.number(),
eip712: z.object({
name: z.string(),
version: z.string(),
primaryType: SupportedSignatureTypeSchema,
}),
});
const FacilitatorSupportedResponseSchema =
SupportedPaymentKindsResponseSchema.extend({
kinds: z.array(
z.object({
x402Version: z.literal(1),
scheme: z.literal("exact"),
network: FacilitatorNetworkSchema,
extra: z
.object({
defaultAsset: FacilitatorSupportedAssetSchema.optional(),
supportedAssets: z
.array(FacilitatorSupportedAssetSchema)
.optional(),
})
.optional(),
}),
),
}).describe("Supported payment kinds for this facilitator");
export type FacilitatorSupportedResponse = z.infer<
typeof FacilitatorSupportedResponseSchema
>;
export function networkToChainId(network: string): number {
if (network.startsWith("eip155:")) {
const chainId = parseInt(network.split(":")[1] ?? "0");
if (!Number.isNaN(chainId) && chainId > 0) {
return chainId;
} else {
throw new Error(`Invalid network: ${network}`);
}
}
// attempt to parse it as just an integer
const maybeChainId = parseInt(network);
if (!Number.isNaN(maybeChainId) && maybeChainId > 0) {
return maybeChainId;
}
const mappedChainId = EvmNetworkToChainId.get(network as Network);
if (!mappedChainId) {
throw new Error(`Invalid network: ${network}`);
}
// TODO (402): support solana networks
if (mappedChainId === 101 || mappedChainId === 103) {
throw new Error("Solana networks not supported yet.");
}
return mappedChainId;
}
+177
View File
@@ -0,0 +1,177 @@
import { decodePaymentRequest } from "./common.js";
import { safeBase64Encode } from "./encode.js";
import {
type SettlePaymentArgs,
type SettlePaymentResult,
x402Version,
} from "./types.js";
import { stringify } from "./utils.js";
/**
* Verifies and processes X402 payments for protected resources.
*
* This function implements the X402 payment protocol, verifying payment proofs
* and settling payments through a facilitator service. It handles the complete
* payment flow from validation to settlement.
*
* @param args - Configuration object containing payment verification parameters
* @returns A promise that resolves to either a successful payment result (200) or payment required error (402)
*
* @example
*
* ### Next.js API route example
*
* ```ts
* // Usage in a Next.js API route
* import { settlePayment, createFacilitator } from "@thirdweb-dev/nexus";
* import { arbitrumSepolia } from "thirdweb/chains";
*
* const facilitator = createFacilitator({
* walletSecret: <your-wallet-secret>,
* walletAddress: <your-wallet-address>,
* });
*
* export async function GET(request: Request) {
* const paymentData = request.headers.get("x-payment");
*
* // verify and process the payment
* const result = await settlePayment({
* resourceUrl: "https://api.example.com/premium-content",
* method: "GET",
* paymentData,
* network: arbitrumSepolia, // or any other chain
* price: "$0.10", // or { amount: "100000", asset: { address: "0x...", decimals: 6 } }
* facilitator,
* routeConfig: {
* description: "Access to premium API content",
* mimeType: "application/json",
* maxTimeoutSeconds: 300,
* },
* });
*
* if (result.status === 200) {
* // Payment verified and settled successfully
* return Response.json({ data: "premium content" });
* } else {
* // Payment required
* return Response.json(result.responseBody, {
* status: result.status,
* headers: result.responseHeaders,
* });
* }
* }
* ```
*
* ### Express middleware example
*
* ```ts
* // Usage in Express middleware
* import express from "express";
* import { settlePayment, createFacilitator } from "@thirdweb-dev/nexus";
* import { arbitrumSepolia } from "thirdweb/chains";
*
* const facilitator = createFacilitator({
* walletSecret: <your-wallet-secret>,
* walletAddress: <your-wallet-address>,
* });
*
* const app = express();
*
* async function paymentMiddleware(req, res, next) {
* // verify and process the payment
* const result = await settlePayment({
* resourceUrl: `${req.protocol}://${req.get('host')}${req.originalUrl}`,
* method: req.method,
* paymentData: req.headers["x-payment"],
* network: arbitrumSepolia, // or any other chain
* price: "$0.05",
* waitUntil: "submitted",
* facilitator,
* });
*
* if (result.status === 200) {
* // Set payment receipt headers and continue
* Object.entries(result.responseHeaders).forEach(([key, value]) => {
* res.setHeader(key, value);
* });
* next();
* } else {
* // Return payment required response
* res.status(result.status)
* .set(result.responseHeaders)
* .json(result.responseBody);
* }
* }
*
* app.get("/api/premium", paymentMiddleware, (req, res) => {
* res.json({ message: "This is premium content!" });
* });
* ```
*
* @public
* @beta
*/
export async function settlePayment(
args: SettlePaymentArgs,
): Promise<SettlePaymentResult> {
const { routeConfig = {}, facilitator } = args;
const { errorMessages } = routeConfig;
const decodePaymentResult = await decodePaymentRequest(args);
if (decodePaymentResult.status !== 200) {
return decodePaymentResult;
}
const { selectedPaymentRequirements, decodedPayment, paymentRequirements } =
decodePaymentResult;
try {
const settlement = await facilitator.settle(
decodedPayment,
selectedPaymentRequirements,
args.waitUntil,
);
if (settlement.success) {
return {
status: 200,
paymentReceipt: settlement,
responseHeaders: {
"Access-Control-Expose-Headers": "X-PAYMENT-RESPONSE",
"X-PAYMENT-RESPONSE": safeBase64Encode(stringify(settlement)),
},
};
} else {
const error = settlement.errorReason || "Settlement error";
return {
status: 402,
responseHeaders: {
"Content-Type": "application/json",
},
responseBody: {
x402Version,
error,
errorMessage:
errorMessages?.settlementFailed || settlement.errorMessage,
accepts: paymentRequirements,
},
};
}
} catch (error) {
return {
status: 402,
responseHeaders: {
"Content-Type": "application/json",
},
responseBody: {
x402Version,
error: "Settlement error",
errorMessage:
errorMessages?.settlementFailed ||
(error instanceof Error ? error.message : undefined),
accepts: paymentRequirements,
},
};
}
}
+110
View File
@@ -0,0 +1,110 @@
import type { Money, PaymentMiddlewareConfig } from "x402/types";
import type z from "zod";
import type { ThirdwebX402Facilitator, WaitUntil } from "./facilitator.js";
import type {
FacilitatorNetwork,
FacilitatorSettleResponse,
FacilitatorSupportedAssetSchema,
RequestedPaymentPayload,
RequestedPaymentRequirements,
SupportedSignatureTypeSchema,
} from "./schemas.js";
export const x402Version = 1;
/**
* Configuration object for verifying or processing X402 payments.
*
* @public
*/
export type PaymentArgs = {
/** The URL of the resource being protected by the payment */
resourceUrl: string;
/** The HTTP method used to access the resource */
method: "GET" | "POST" | ({} & string);
/** The payment data/proof provided by the client, typically from the X-PAYMENT header */
paymentData?: string | null;
/** The blockchain network where the payment should be processed */
network: FacilitatorNetwork;
/** The price for accessing the resource - either a USD amount (e.g., "$0.10") or a specific token amount */
price: Money | ERC20TokenAmount;
/** The payment facilitator instance used to verify and settle payments */
facilitator: ThirdwebX402Facilitator;
/** Optional configuration for the payment middleware route */
routeConfig?: PaymentMiddlewareConfig;
/** Optional recipient address to receive the payment if different from your facilitator address */
payTo?: string;
};
export type SettlePaymentArgs = PaymentArgs & {
waitUntil?: WaitUntil;
};
export type PaymentRequiredResult = {
/** HTTP 402 - Payment Required, verification or processing failed or payment missing */
status: 402;
/** The error response body containing payment requirements */
responseBody: {
/** The X402 protocol version */
x402Version: number;
/** error code */
error: string;
/** Human-readable error message */
errorMessage?: string;
/** Array of acceptable payment methods and requirements */
accepts: RequestedPaymentRequirements[];
/** Optional payer address if verification partially succeeded */
payer?: string;
};
/** Response headers for the error response */
responseHeaders: Record<string, string>;
};
/**
* The result of a payment settlement operation.
*
* @public
*/
export type SettlePaymentResult =
| {
/** HTTP 200 - Payment was successfully processed */
status: 200;
/** Response headers including payment receipt information */
responseHeaders: Record<string, string>;
/** The payment receipt from the payment facilitator */
paymentReceipt: FacilitatorSettleResponse;
}
| PaymentRequiredResult;
/**
* The result of a payment verification operation.
*
* @public
*/
export type VerifyPaymentResult =
| {
/** HTTP 200 - Payment was successfully verified */
status: 200;
decodedPayment: RequestedPaymentPayload;
selectedPaymentRequirements: RequestedPaymentRequirements;
}
| PaymentRequiredResult;
export type SupportedSignatureType = z.infer<
typeof SupportedSignatureTypeSchema
>;
export type ERC20TokenAmount = {
amount: string;
asset: {
address: `0x${string}`;
decimals?: number;
eip712?: {
name: string;
version: string;
primaryType: SupportedSignatureType;
};
};
};
export type DefaultAsset = z.infer<typeof FacilitatorSupportedAssetSchema>;
+93
View File
@@ -0,0 +1,93 @@
/**
* Stringify a JSON object and convert all bigint values to string
*
* If you are getting this error: "Exception: Do not know how to serialize a BigInt",
* you probably can use this function to parse the data.
* Because bigint is not an accepted value of the JSON format.
*
* @returns An object with all bigint values converted to string
* @example
* ```ts
* import { stringify } from "thirdweb/utils";
* const obj = { tokenId: 0n };
* const str = stringify(obj); // "{"tokenId":"0"}"
* ```
* @utils
*/
export function stringify(
// biome-ignore lint/suspicious/noExplicitAny: JSON.stringify signature
value: any,
// biome-ignore lint/suspicious/noExplicitAny: JSON.stringify signature
replacer?: ((this: any, key: string, value: any) => any) | null,
space?: string | number,
) {
const res = JSON.stringify(
value,
(key, value_) => {
const value__ = typeof value_ === "bigint" ? value_.toString() : value_;
return typeof replacer === "function" ? replacer(key, value__) : value__;
},
space,
);
return res;
}
/**
* Converts a string representation of a number with decimal places to a BigInt representation.
* @param tokens - The string representation of the number, including the integer and fraction parts.
* @param decimals - The number of decimal places to include in the BigInt representation.
* @returns The BigInt representation of the number.
* @example
* ```ts
* import { toUnits } from "thirdweb/utils";
* toUnits('1', 18)
* // 1000000000000000000n
* ```
* @utils
*/
export function toUnits(tokens: string, decimals: number): bigint {
if (tokens.includes("e")) {
tokens = Number(tokens).toFixed(decimals);
}
let [integerPart, fractionPart = ""] = tokens.split(".") as [string, string];
const prefix = integerPart.startsWith("-") ? "-" : "";
if (prefix) {
integerPart = integerPart.slice(1);
}
fractionPart = fractionPart.padEnd(decimals, "0"); // Ensure fraction part is at least 'decimals' long.
if (decimals === 0) {
// Check if there's any fraction part that would necessitate rounding up the integer part.
if (fractionPart[0] && Number.parseInt(fractionPart[0]) >= 5) {
integerPart = (BigInt(integerPart) + 1n).toString();
}
fractionPart = ""; // No fraction part is needed when decimals === 0.
} else {
// When decimals > 0, handle potential rounding based on the digit right after the specified decimal places.
if (fractionPart.length > decimals) {
const roundingDigit = fractionPart[decimals];
if (roundingDigit && Number.parseInt(roundingDigit, 10) >= 5) {
// If rounding is needed, add 1 to the last included digit of the fraction part.
const roundedFraction =
BigInt(fractionPart.substring(0, decimals)) + 1n;
fractionPart = roundedFraction.toString().padStart(decimals, "0");
if (fractionPart.length > decimals) {
// If rounding the fraction results in a length increase (e.g., .999 -> 1.000), increment the integer part.
integerPart = (BigInt(integerPart) + 1n).toString();
// Adjust the fraction part if it's longer than the specified decimals due to rounding up.
fractionPart = fractionPart.substring(fractionPart.length - decimals);
}
} else {
// If no rounding is necessary, just truncate the fraction part to the specified number of decimals.
fractionPart = fractionPart.substring(0, decimals);
}
}
// If the fraction part is shorter than the specified decimals, it's already handled by padEnd() above.
}
// Combine the integer and fraction parts into the final BigInt representation.
return BigInt(`${prefix}${integerPart}${fractionPart}`);
}
+128
View File
@@ -0,0 +1,128 @@
import { decodePaymentRequest } from "./common.js";
import {
type PaymentArgs,
type VerifyPaymentResult,
x402Version,
} from "./types.js";
/**
* Verifies X402 payments for protected resources. This function only verifies the payment,
* you should use `settlePayment` to settle the payment.
*
* @param args - Configuration object containing payment verification parameters
* @returns A promise that resolves to either a successful verification result (200) or payment required error (402)
*
* @example
* ```ts
* // Usage in a Next.js API route
* import { verifyPayment, createFacilitator } from "@thirdweb-dev/nexus";
* import { arbitrumSepolia } from "thirdweb/chains";
*
* const facilitator = createFacilitator({
* walletSecret: <your-wallet-secret>,
* walletAddress: <your-wallet-address>,
* });
*
* export async function GET(request: Request) {
* const paymentData = request.headers.get("x-payment");
*
* const paymentArgs = {
* resourceUrl: "https://api.example.com/premium-content",
* method: "GET",
* paymentData,
* network: arbitrumSepolia, // or any other chain
* price: "$0.10", // or { amount: "100000", asset: { address: "0x...", decimals: 6 } }
* facilitator,
* routeConfig: {
* description: "Access to premium API content",
* mimeType: "application/json",
* maxTimeoutSeconds: 300,
* },
* };
*
* // verify the payment
* const result = await verifyPayment(paymentArgs);
*
* if (result.status === 200) {
* // Payment verified, but not settled yet
* // you can do the work that requires payment first
* const result = await doSomething();
* // then settle the payment
* const settleResult = await settlePayment(paymentArgs);
*
* // then return the result
* return Response.json(result);
* } else {
* // verification failed, return payment required
* return Response.json(result.responseBody, {
* status: result.status,
* headers: result.responseHeaders,
* });
* }
* }
* ```
*
* @public
* @beta
*/
export async function verifyPayment(
args: PaymentArgs,
): Promise<VerifyPaymentResult> {
const { routeConfig = {}, facilitator } = args;
const { errorMessages } = routeConfig;
const decodePaymentResult = await decodePaymentRequest(args);
if (decodePaymentResult.status !== 200) {
return decodePaymentResult;
}
const { selectedPaymentRequirements, decodedPayment, paymentRequirements } =
decodePaymentResult;
// Verify payment
try {
const verification = await facilitator.verify(
decodedPayment,
selectedPaymentRequirements,
);
if (verification.isValid) {
return {
status: 200,
decodedPayment,
selectedPaymentRequirements,
};
} else {
const error = verification.invalidReason || "Verification failed";
return {
status: 402,
responseHeaders: {
"Content-Type": "application/json",
},
responseBody: {
x402Version,
error: error,
errorMessage:
errorMessages?.verificationFailed || verification.errorMessage,
accepts: paymentRequirements,
},
};
}
} catch (error) {
return {
status: 402,
responseHeaders: {
"Content-Type": "application/json",
},
responseBody: {
x402Version,
error: "Verification error",
errorMessage:
errorMessages?.verificationFailed ||
(error instanceof Error ? error.message : undefined),
accepts: paymentRequirements,
},
};
}
}
+48
View File
@@ -0,0 +1,48 @@
{
// This tsconfig file contains the shared config for the build (tsconfig.build.json) and type checking (tsconfig.json) config.
"compilerOptions": {
// Incremental builds
// NOTE: Enabling incremental builds speeds up `tsc`. Keep in mind though that it does not reliably bust the cache when the `tsconfig.json` file changes.
"allowJs": false,
"allowSyntheticDefaultImports": true,
"checkJs": false,
// Interop constraints
"esModuleInterop": false,
"exactOptionalPropertyTypes": false,
"forceConsistentCasingInFileNames": true,
"importHelpers": true,
// Incremental builds
// NOTE: Enabling incremental builds speeds up `tsc`. Keep in mind though that it does not reliably bust the cache when the `tsconfig.json` file changes.
"incremental": false,
// jsx for "/react" portion
"jsx": "react-jsx",
"lib": [
"ES2022", // By using ES2022 we get access to the `.cause` property on `Error` instances.
"DOM" // We are adding `DOM` here to get the `fetch`, etc. types. This should be removed once these types are available via DefinitelyTyped.
],
"module": "NodeNext",
// Language and environment
"moduleResolution": "NodeNext",
"noFallthroughCasesInSwitch": true,
"noImplicitOverride": true,
"noImplicitReturns": true,
"noUncheckedIndexedAccess": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
// Skip type checking for node modules
"skipLibCheck": true,
// Type checking
"strict": true,
"target": "ES2021",
"useDefineForClassFields": true,
"useUnknownInCatchVariables": true,
"verbatimModuleSyntax": true
},
// This tsconfig file contains the shared config for the build (tsconfig.build.json) and type checking (tsconfig.json) config.
"include": []
}
+16
View File
@@ -0,0 +1,16 @@
{
"compilerOptions": {
"moduleResolution": "node",
"rootDir": "./src",
"sourceMap": true
},
"exclude": [
"src/**/*.test.ts",
"src/**/*.test.tsx",
"src/**/*.test-d.ts",
"src/**/*.bench.ts",
"src/**/*.macro.ts"
],
"extends": "./tsconfig.base.json",
"include": ["src"]
}
+13
View File
@@ -0,0 +1,13 @@
{
// This configuration is used for local development and type checking.
"compilerOptions": {
"baseUrl": ".",
"outDir": "./dist",
"paths": {
"~test/*": ["./test/src/*"]
}
},
"exclude": [],
"extends": "./tsconfig.base.json",
"include": ["src", "test"]
}
+1 -1
View File
@@ -6,7 +6,7 @@
"path": "./dist/esm/exports/thirdweb.js"
},
{
"limit": "375 kB",
"limit": "380 kB",
"name": "thirdweb (cjs)",
"path": "./dist/cjs/exports/thirdweb.js"
},
+20
View File
@@ -1,5 +1,25 @@
# thirdweb
## 5.110.6
### Patch Changes
- [#8334](https://github.com/thirdweb-dev/js/pull/8334) [`6c318f8`](https://github.com/thirdweb-dev/js/commit/6c318f83d63aa8acce46da666e43638e410f799c) Thanks [@kumaryash90](https://github.com/kumaryash90)! - Update implementations
- [#8306](https://github.com/thirdweb-dev/js/pull/8306) [`759b93b`](https://github.com/thirdweb-dev/js/commit/759b93beff7ab10a3a2fa3b9dd25ca628efb9267) Thanks [@alecananian](https://github.com/alecananian)! - Moved `@storybook/react` package to be a dev dependency
- [#8332](https://github.com/thirdweb-dev/js/pull/8332) [`8e357b3`](https://github.com/thirdweb-dev/js/commit/8e357b3cb39dcc7a8595a83224098a9c44585389) Thanks [@jnsdls](https://github.com/jnsdls)! - expose some useful erc20 extensions
## 5.110.5
### Patch Changes
- [#8327](https://github.com/thirdweb-dev/js/pull/8327) [`c08fb13`](https://github.com/thirdweb-dev/js/commit/c08fb1356e77be9eeb0569905fcb2ab76a516dc3) Thanks [@MananTank](https://github.com/MananTank)! - Fix Safe wallet connection issues with WalletConnect
- [#8331](https://github.com/thirdweb-dev/js/pull/8331) [`bcbb3f5`](https://github.com/thirdweb-dev/js/commit/bcbb3f540a8eab2b02f9261b779bd2aad3464598) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Loosen network schema
- [#8325](https://github.com/thirdweb-dev/js/pull/8325) [`e0c8312`](https://github.com/thirdweb-dev/js/commit/e0c83121c201eb2dbdfa6a6b1bbb637f5ad2a21d) Thanks [@Yash094](https://github.com/Yash094)! - Add Arc testnet to chains package
## 5.110.4
### Patch Changes
+2 -2
View File
@@ -21,7 +21,6 @@
"@radix-ui/react-focus-scope": "1.1.7",
"@radix-ui/react-icons": "1.3.2",
"@radix-ui/react-tooltip": "1.2.7",
"@storybook/react": "9.0.15",
"@tanstack/react-query": "5.81.5",
"@thirdweb-dev/engine": "workspace:*",
"@thirdweb-dev/insight": "workspace:*",
@@ -57,6 +56,7 @@
"@storybook/addon-docs": "9.0.15",
"@storybook/addon-links": "9.0.15",
"@storybook/addon-onboarding": "9.0.15",
"@storybook/react": "9.0.15",
"@storybook/react-vite": "9.0.15",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.3.0",
@@ -430,5 +430,5 @@
}
},
"typings": "./dist/types/exports/thirdweb.d.ts",
"version": "5.110.4"
"version": "5.110.6"
}
@@ -0,0 +1,17 @@
import { defineChain } from "../utils.js";
/**
* @chain
*/
export const arcTestnet = /* @__PURE__ */ defineChain({
blockExplorers: [
{
name: "Arc Testnet Explorer",
url: "https://testnet.arcscan.app",
},
],
id: 5042002,
name: "Arc Testnet",
nativeCurrency: { decimals: 6, name: "USDC", symbol: "USDC" },
testnet: true,
});
@@ -1,6 +1,6 @@
export const ZKSYNC_IMPLEMENTATIONS: Record<number, Record<string, string>> = {
232: {
MarketplaceV3: "0x9742f5ac11958cFAd151eBF0Fc31302fA409036E",
MarketplaceV3: "0xB7387cF425eC8Da1b04b8E34De86245cb32bCA6D",
},
300: {
MarketplaceV3: "0x58e0F289C7dD2025eBd0696d913ECC0fdc1CC8bc",
@@ -18,7 +18,7 @@ export const ZKSYNC_IMPLEMENTATIONS: Record<number, Record<string, string>> = {
MarketplaceV3: "0x2dA4Dd326A6482679547071be21f74685d730504",
},
37111: {
MarketplaceV3: "0x56Abb6a3f25DCcdaDa106191053b1CC54C196DEE",
MarketplaceV3: "0xF8fd1016420c2B7832714241d36Efe55D41df126",
},
50104: {
MarketplaceV3: "0x292c324920d4120F928dA6f04548442fa10B8B0f",
@@ -32,12 +32,12 @@ export const ZKSYNC_IMPLEMENTATIONS: Record<number, Record<string, string>> = {
};
export const ZKSYNC_WETH: Record<number, string> = {
232: "0xE5ecd226b3032910CEaa43ba92EE8232f8237553",
232: "0x6bDc36E20D267Ff0dd6097799f82e78907105e2F",
300: "0x0462C05457Fed440740Ff3696bDd2D0577411e34",
324: "0x5AEa5775959fBC2557Cc8789bC1bf90A239D9a91",
2741: "0x3439153EB7AF838Ad19d56E1571FBD09333C2809",
11124: "0x9EDCde0257F2386Ce177C3a7FCdd97787F0D841d",
37111: "0xaA91D645D7a6C1aeaa5988e0547267B77d33fe16",
37111: "0xeee5a340Cdc9c179Db25dea45AcfD5FE8d4d3eB8",
50104: "0x2b1A859dE6A55c553520D7780Bc5805712B128F9",
555271: "0xb0b8b267d44c64BA6dD1Daf442949887c85199f6",
531050104: "0x9B7EA63EF5062661570afe548e3AD691D5652527",
+1
View File
@@ -10,6 +10,7 @@ export { anvil } from "../chains/chain-definitions/anvil.js";
export { arbitrum } from "../chains/chain-definitions/arbitrum.js";
export { arbitrumNova } from "../chains/chain-definitions/arbitrum-nova.js";
export { arbitrumSepolia } from "../chains/chain-definitions/arbitrum-sepolia.js";
export { arcTestnet } from "../chains/chain-definitions/arc-testnet.js";
export { assetChainTestnet } from "../chains/chain-definitions/assetchain-testnet.js";
export { astriaEvmDusknet } from "../chains/chain-definitions/astria-evm-dusknet.js";
export { avalanche } from "../chains/chain-definitions/avalanche.js";
@@ -1,3 +1,5 @@
export type { Abi } from "abitype";
export { formatCompilerMetadata } from "../contract/actions/compiler-metadata.js";
export { getBytecode } from "../contract/actions/get-bytecode.js";
export { getCompilerMetadata } from "../contract/actions/get-compiler-metadata.js";
@@ -17,10 +17,6 @@ export {
getActiveClaimConditionId,
isGetActiveClaimConditionIdSupported,
} from "../../extensions/erc20/__generated__/IDropERC20/read/getActiveClaimConditionId.js";
/**
* DROPS extension for ERC20
*/
// READ
export {
getClaimConditionById,
isGetClaimConditionByIdSupported,
@@ -43,6 +39,15 @@ export {
balanceOf,
} from "../../extensions/erc20/__generated__/IERC20/read/balanceOf.js";
export { totalSupply } from "../../extensions/erc20/__generated__/IERC20/read/totalSupply.js";
/**
* PERMIT extension for ERC20
*/
export { nonces } from "../../extensions/erc20/__generated__/IERC20Permit/read/nonces.js";
export { isPermitSupported } from "../../extensions/erc20/__generated__/IERC20Permit/write/permit.js";
/**
* DROPS extension for ERC20
*/
// READ
export {
type TokensMintedEventFilters,
tokensMintedEvent,
@@ -63,6 +68,7 @@ export {
type WithdrawParams,
withdraw,
} from "../../extensions/erc20/__generated__/IWETH/write/withdraw.js";
export { isTransferWithAuthorizationSupported } from "../../extensions/erc20/__generated__/USDC/write/transferWithAuthorization.js";
export {
type CanClaimParams,
type CanClaimResult,
@@ -157,7 +157,7 @@ export async function connectWC(
);
const currentChainId = chainsToRequest[0]?.split(":")[1] || 1;
const providerChainId = normalizeChainId(currentChainId);
const account = firstAccountOn(provider.session, `eip155:1`); // grab the address from mainnet
const account = firstAccountOn(provider.session, `eip155:1`); // grab the address from mainnet if available
const address = account;
if (!address) {
throw new Error("No accounts found on provider.");
@@ -291,7 +291,8 @@ function hasChainEnabled(session: WCSession, caip: string) {
}
function firstAccountOn(session: WCSession, caip: string): string | null {
const ns = getNS(session);
const hit = ns?.accounts?.find((a) => a.startsWith(`${caip}:`));
const hit =
ns?.accounts?.find((a) => a.startsWith(`${caip}:`)) || ns?.accounts[0];
return hit ? (hit.split(":")[2] ?? null) : null;
}
function anyRoutableChain(session: WCSession): string | null {
+1 -14
View File
@@ -11,20 +11,7 @@ import {
import { z } from "zod";
import type { Chain } from "../chains/types.js";
const FacilitatorNetworkSchema = z.union([
z.literal("base-sepolia"),
z.literal("base"),
z.literal("avalanche-fuji"),
z.literal("avalanche"),
z.literal("iotex"),
z.literal("solana-devnet"),
z.literal("solana"),
z.literal("sei"),
z.literal("sei-testnet"),
z.string().refine((value) => value.startsWith("eip155:"), {
message: "Invalid network",
}),
]);
const FacilitatorNetworkSchema = z.string();
export type FacilitatorNetwork = z.infer<typeof FacilitatorNetworkSchema>;
+4
View File
@@ -1,5 +1,9 @@
# @thirdweb-dev/wagmi-adapter
## 0.2.174
## 0.2.173
## 0.2.172
## 0.2.171
+1 -1
View File
@@ -56,5 +56,5 @@
"type": "module",
"types": "./dist/types/exports/thirdweb.d.ts",
"typings": "./dist/types/exports/thirdweb.d.ts",
"version": "0.2.172"
"version": "0.2.174"
}
+1516 -1332
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -96,7 +96,7 @@
"thirdweb-dashboard#dev": {
"dependsOn": ["^build"]
},
"thirdweb#update-version": {
"update-version": {
"inputs": ["$TURBO_DEFAULT$", "package.json"],
"outputs": ["src/version.ts"]
},