Compare commits

...
Author SHA1 Message Date
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
7113f7c669 Version Packages (#7463)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-06-27 18:56:26 -07:00
f9d7935d84 Update engine API (#7427)
Signed-off-by: Prithvish Baidya <[email protected]>
Co-authored-by: Prithvish Baidya <[email protected]>
2025-06-27 18:41:37 -07:00
FirekeeperandGitHub acd5656d4d [SDK] EIP-7702 Session Keys (#7432) 2025-06-27 18:11:19 -07:00
gregandGitHub 6d1d344c48 Fix: Exclude IAW when smart wallets are used (#7454) 2025-06-27 13:36:11 -07:00
MananTank 26b8c320ae [TOOL-4899] Dashboard: fetch token icon from bridge api as fallback in coin asset page (#7460)
<!--

## 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 introduces a new utility function to fetch token information from a bridge and integrates it into the `ERC20PublicPage` component to enhance contract metadata with token icons.

### Detailed summary
- Added `fetchTokenInfoFromBridge` function in `fetch-coin-info.ts` to fetch token details from a bridge API.
- Integrated `fetchTokenInfoFromBridge` in the `ERC20PublicPage` component.
- Updated contract metadata to include the token icon if not already present.

> ✨ 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**
  * Enhanced ERC20 public pages to automatically display token icons when available, enriching token metadata with external information.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-27 19:46:52 +00:00
Phillip HoandGitHub c7f7f48222 chore: change webhook back to object (#7461) 2025-06-28 02:18:40 +08:00
MananTank e814248d9c [TOOL-4898] Dashboard: Replace PayEmbed with BuyWidget in asset page, add tracking (#7459)
<!--

## 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 `PayEmbed` component to `BuyWidget` in the `BuyTokenEmbed` function, enhancing the functionality with error reporting and success tracking for asset purchases.

### Detailed summary
- Replaced `PayEmbed` with `BuyWidget` in `BuyTokenEmbed`.
- Added `amount`, `chain`, and `className` props to `BuyWidget`.
- Implemented `onError` and `onSuccess` callbacks for analytics reporting.
- Updated the token address prop to ensure correct type.
- Modified chart className in `contract-analytics.tsx`.

> ✨ 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**
  * Updated the buy token section to use an improved widget with enhanced error and success handling, including dynamic titles and analytics reporting.

* **Style**
  * Adjusted the chart's aspect ratio for better display across different screen sizes.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-27 16:43:21 +00:00
MananTank d7a0613067 [TOOL-4897] Dashboard: Add Contract creator badge in Coin and NFT asset page (#7458)
<!--

## 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 introduces the `contractCreator` property to multiple components, allowing the display of the contract creator's address in the UI. It also adds a new function, `getContractCreator`, to retrieve the contract creator's information based on the contract's role.

### Detailed summary
- Added `contractCreator` prop to `NFTPublicPageLayout`, `ContractHeaderUI`, and `ContractCreatorBadge`.
- Implemented `getContractCreator` function to fetch the contract creator using `ThirdwebContract`.
- Updated `NFTPublicPage` and `ERC20PublicPage` to call `getContractCreator` and pass the result to the UI.
- Enhanced storybook stories to include `contractCreator` in various scenarios.

> ✨ 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**
  * Added display of contract creator information on ERC20 and NFT public pages.
  * Introduced a badge showing the contract creator's address in the contract header section.

* **Enhancements**
  * Contract headers now conditionally show the creator badge if creator data is available.
  * Storybook stories for contract headers updated to include contract creator scenarios for improved UI previews.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-27 16:31:14 +00:00
MananTank dbc031664d [TOOL-4867] Dashboard: Fix Coin asset page crash on "unlimited" maxClaimableSupply (#7457)
<!--

## 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 handling of the `totalSupply` property in the `SupplyClaimedProgress` component and related files to support the string value `"unlimited"` instead of using a constant value from `thirdweb/utils`.

### Detailed summary
- Changed `totalSupply` type from `bigint` to `bigint | "unlimited"` in `SupplyClaimedProgress`.
- Updated the condition to check for `"unlimited"` instead of `maxUint256`.
- Modified the story for `SupplyClaimedProgress` to use `"unlimited"` instead of `maxUint256`.
- Adjusted `totalSupply` handling in `TokenDropClaim` to return `"unlimited"` if `maxClaimableSupply` equals `maxUint256`.

> ✨ 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**
  * Updated the supply progress display to clearly indicate when the total supply is unlimited.
* **Refactor**
  * Improved internal handling and representation of unlimited supply for better clarity in the UI.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-27 16:20:18 +00:00
saminacodes 5640d48854 [Docs] Project Rebrand (#7455)
CORE-0000

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on renaming and rebranding various components and documentation from `Universal Bridge` and `Engine` to `Payments` and `Transactions`, respectively, to better reflect their functionality and purpose.

### Detailed summary
- Renamed `Universal Bridge` to `Payments` across multiple files.
- Renamed `Engine` to `Transactions` across various components and documentation.
- Updated metadata and titles in numerous `.mdx` files to reflect the new branding.
- Changed references in UI components and layout files to match new naming conventions.
- Adjusted descriptions and summaries in documentation to align with the new terms.
- Updated links and API references to point to the correct services.

> ✨ 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**
  * Expanded the learning resources section with updated and additional cards, including new topics like "Contracts" and "Vault".

* **Style**
  * Updated navigation and sidebar labels for improved clarity (e.g., "Connect" to "Wallets", "Bridge" to "Payments", "Engine" to "Transactions").
  * Swapped the order of the GitHub icon and theme switcher in the header for better usability.

* **Documentation**
  * Renamed all references from "Universal Bridge" to "Payments" and from "Engine" to "Transactions" across guides, FAQs, and documentation.
  * Updated instructions, headings, and content to reflect new product names and improved clarity.
  * Added a new FAQ entry explaining the difference between "Transactions Cloud" and "Dedicated" infrastructure.
  * Updated links and navigation paths to match new terminology.
  * Adjusted terminology in wallet-related documentation to replace "Connect" with "Wallets".
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-27 15:26:01 +00:00
GiselleNessi 9d6739ea59 updated text support page (#7447)
<!--

## 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 descriptions and titles of various features in the support section of the dashboard, as well as changing a header for improved clarity.

### Detailed summary
- Updated `description` and `title` for the wallets feature.
- Changed `description` and `title` for the universal bridge feature, along with its `viewAllUrl`.
- Modified `description` and `title` for the engine feature, including its `viewAllUrl`.
- Revised `description` for the insight feature.
- Changed the header from "Learning Resources" to "Support Articles".

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

<!-- end pr-codex -->

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

* **Refactor**
  * Updated product names and descriptions for greater clarity in the Support section.
  * Changed the heading above product cards from "Learning Resources" to "Support Articles."
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-27 10:52:59 +00:00
GiselleNessi dc49683357 removed next steps chainlist page (#7443)
<!--

## 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 removing unused components and updating product descriptions in the `page.tsx` and `products.ts` files.

### Detailed summary
- Deleted `NextSteps` and `ClaimChainSection` components from `page.tsx`.
- Removed related JSX comments in `page.tsx`.
- Updated product descriptions and names in `products.ts`:
  - Changed "Pre-built contracts & deployment tools" to "Create, deploy and manage smart contracts".
  - Changed "Client-side SDKs for wallets and blockchain interactions" to "Create and manage crypto wallets".
  - Changed "Engine" to "Transactions".
  - Changed "Account factory contracts, bundler & paymaster" to "Enable gas sponsorship for seamless transactions".
  - Changed "Point of sale solution for bridging, onramping & swapping" to "Enable payments in any token on any chain".
  - Changed "Query, transform and analyze blockchain data" to "Query and analyze blockchain data".

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

<!-- end pr-codex -->

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

* **Refactor**
  * Removed the "Next Steps" and "Claim Chain" sections from the chain page, including related external links and prompts.
* **Documentation**
  * Updated product names and descriptions to use clearer, more user-focused language for better understanding of features.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-27 10:42:56 +00:00
111 changed files with 4334 additions and 2907 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
{
"$schema": "https://biomejs.dev/schemas/2.0.0/schema.json",
"$schema": "https://biomejs.dev/schemas/2.0.4/schema.json",
"extends": "//"
}
@@ -1,37 +0,0 @@
"use client";
import { FileTextIcon } from "lucide-react";
import Link from "next/link";
import type { ChainMetadata } from "thirdweb/chains";
import { SectionTitle } from "../server/SectionTitle";
export default function NextSteps(props: { chain: ChainMetadata }) {
const { chain } = props;
return (
<section>
<SectionTitle title="Next Steps" />
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-3">
<div className="relative flex gap-3 rounded-lg border bg-card p-4 pr-8 transition-colors hover:border-active-border">
<FileTextIcon className="mt-0.5 size-5 shrink-0" />
<div>
<h3 className="mb-1.5 font-medium">
<Link
className="before:absolute before:inset-0"
href={
"https://blog.thirdweb.com/supercharge-user-adoption-integrate-embedded-wallets-in-minutes/"
}
rel="noopener noreferrer"
target="_blank"
>
Create a login for {chain.name}
</Link>
</h3>
<p className="text-muted-foreground text-sm">
Supercharge User Adoption—Integrate In-App Wallets in Minutes
</p>
</div>
</div>
</div>
</section>
);
}
@@ -1,31 +0,0 @@
import { ExternalLinkIcon } from "lucide-react";
import Link from "next/link";
import { Button } from "@/components/ui/button";
import { SectionTitle } from "./SectionTitle";
export function ClaimChainSection() {
return (
<section>
<SectionTitle title="Claim Chain" />
<div className="rounded-lg border bg-card p-4">
<p className="mb-1"> This chain page is unclaimed </p>
<p className="mb-5 text-muted-foreground text-sm">
Are you the owner of this ecosystem? Claim this page to add additional
information on this page to help your developers get started!
</p>
<Button asChild variant="outline">
<Link
className="gap-2"
href="https://share.hsforms.com/1qAViW3U5SoKbabNhRZy4Kgea58c"
rel="noopener noreferrer"
target="_blank"
>
<span>Claim Chain</span>
<ExternalLinkIcon className="-mt-[3px] size-4" />
</Link>
</Button>
</div>
</section>
);
}
@@ -3,9 +3,7 @@ import { getClientThirdwebClient } from "@/constants/thirdweb-client.client";
import { getRawAccount } from "../../../../account/settings/getAccount";
import { getChain, getChainMetadata } from "../../utils";
import { BuyFundsSection } from "./components/client/BuyFundsSection";
import NextSteps from "./components/client/NextSteps";
import { ChainOverviewSection } from "./components/server/ChainOverviewSection";
import { ClaimChainSection } from "./components/server/ClaimChainSection";
import { ChainCTA } from "./components/server/cta-card";
import { ExplorersSection } from "./components/server/explorer-section";
import { FaucetSection } from "./components/server/FaucetSection";
@@ -60,11 +58,6 @@ export default async function Page(props: {
{chain.services.filter((s) => s.enabled).length > 0 && (
<SupportedProductsSection services={chain.services} />
)}
{/*Next Steps */}
<NextSteps chain={chain} />
{/* Claim Chain */}
{!chainMetadata && <ClaimChainSection />}
</div>
);
}
@@ -0,0 +1,27 @@
import type { ThirdwebContract } from "thirdweb";
import { isOwnerSupported, owner } from "thirdweb/extensions/common";
import {
getRoleMember,
isGetRoleAdminSupported,
} from "thirdweb/extensions/permissions";
export async function getContractCreator(
contract: ThirdwebContract,
functionSelectors: string[],
) {
if (isOwnerSupported(functionSelectors)) {
return owner({
contract,
});
}
if (isGetRoleAdminSupported(functionSelectors)) {
return getRoleMember({
contract,
index: BigInt(0),
role: "admin",
});
}
return null;
}
@@ -1,6 +1,5 @@
import type { Meta, StoryObj } from "@storybook/nextjs";
import { maxUint256 } from "thirdweb/utils";
import { BadgeContainer } from "../../../../../../../../@/storybook/utils";
import { BadgeContainer } from "@/storybook/utils";
import { SupplyClaimedProgress } from "./supply-claimed-progress";
const meta = {
@@ -19,7 +18,7 @@ function StoryVariants() {
return (
<div className="container max-w-md space-y-10 py-10">
<BadgeContainer label="10 / Unlimited Supply">
<SupplyClaimedProgress claimedSupply={10n} totalSupply={maxUint256} />
<SupplyClaimedProgress claimedSupply={10n} totalSupply="unlimited" />
</BadgeContainer>
<BadgeContainer label="500/1000 Supply">
@@ -1,14 +1,13 @@
import { InfinityIcon } from "lucide-react";
import { maxUint256 } from "thirdweb/utils";
import { Progress } from "@/components/ui/progress";
import { supplyFormatter } from "../nft/format";
export function SupplyClaimedProgress(props: {
claimedSupply: bigint;
totalSupply: bigint;
totalSupply: bigint | "unlimited";
}) {
// if total supply is unlimited
if (props.totalSupply === maxUint256) {
if (props.totalSupply === "unlimited") {
return (
<p className="flex items-center justify-between gap-2">
<span className="font-medium text-sm">Claimed Supply </span>
@@ -0,0 +1,19 @@
import type { ThirdwebContract } from "thirdweb";
import { WalletAddress } from "@/components/blocks/wallet-address";
export function ContractCreatorBadge(props: {
contractCreator: string;
clientContract: ThirdwebContract;
}) {
return (
<div className="flex items-center gap-1.5 bg-card rounded-full px-2.5 py-1.5 border hover:bg-accent">
<span className="text-xs text-foreground">By</span>
<WalletAddress
address={props.contractCreator}
className="py-0 text-xs h-auto !no-underline"
client={props.clientContract.client}
iconClassName="size-3.5 hidden"
/>
</div>
);
}
@@ -88,6 +88,25 @@ export const WithImageAndMultipleSocialUrls: Story = {
args: {
chainMetadata: ethereumChainMetadata,
clientContract: mockContract,
contractCreator: null,
image: mockTokenImage,
name: "Sample Token",
socialUrls: {
discord: mockSocialUrls.discord,
github: mockSocialUrls.github,
telegram: mockSocialUrls.telegram,
twitter: mockSocialUrls.twitter,
website: mockSocialUrls.website,
},
symbol: "SMPL",
},
};
export const WithContractCreator: Story = {
args: {
chainMetadata: ethereumChainMetadata,
clientContract: mockContract,
contractCreator: "0x1234567890123456789012345678901234567890",
image: mockTokenImage,
name: "Sample Token",
socialUrls: {
@@ -105,6 +124,7 @@ export const WithBrokenImageAndSingleSocialUrl: Story = {
args: {
chainMetadata: ethereumChainMetadata,
clientContract: mockContract,
contractCreator: null,
image: "broken-image.png",
name: "Sample Token",
socialUrls: {
@@ -118,6 +138,7 @@ export const WithoutImageAndNoSocialUrls: Story = {
args: {
chainMetadata: ethereumChainMetadata,
clientContract: mockContract,
contractCreator: null,
image: undefined,
name: "Sample Token",
socialUrls: {},
@@ -129,6 +150,7 @@ export const LongNameAndLotsOfSocialUrls: Story = {
args: {
chainMetadata: ethereumChainMetadata,
clientContract: mockContract,
contractCreator: null,
image: "https://thirdweb.com/chain-icons/ethereum.svg",
name: "This is a very long token name that should wrap to multiple lines",
socialUrls: {
@@ -148,6 +170,7 @@ export const AllSocialUrls: Story = {
args: {
chainMetadata: ethereumChainMetadata,
clientContract: mockContract,
contractCreator: null,
image: "https://thirdweb.com/chain-icons/ethereum.svg",
name: "Sample Token",
socialUrls: {
@@ -171,6 +194,7 @@ export const InvalidSocialUrls: Story = {
args: {
chainMetadata: ethereumChainMetadata,
clientContract: mockContract,
contractCreator: null,
image: "https://thirdweb.com/chain-icons/ethereum.svg",
name: "Sample Token",
socialUrls: {
@@ -188,6 +212,7 @@ export const SomeSocialUrls: Story = {
args: {
chainMetadata: ethereumChainMetadata,
clientContract: mockContract,
contractCreator: null,
image: "https://thirdweb.com/chain-icons/ethereum.svg",
name: "Sample Token",
socialUrls: {
@@ -19,6 +19,7 @@ import { YoutubeIcon } from "@/icons/brand-icons/YoutubeIcon";
import { ChainIconClient } from "@/icons/ChainIcon";
import { cn } from "@/lib/utils";
import { resolveSchemeWithErrorHandler } from "@/utils/resolveSchemeWithErrorHandler";
import { ContractCreatorBadge } from "./ContractCreatorBadge";
const platformToIcons: Record<string, React.FC<{ className?: string }>> = {
discord: DiscordIcon,
@@ -42,6 +43,7 @@ export function ContractHeaderUI(props: {
clientContract: ThirdwebContract;
socialUrls: object;
imageClassName?: string;
contractCreator: string | null;
}) {
const socialUrls = useMemo(() => {
const socialUrlsValue: { name: string; href: string }[] = [];
@@ -147,6 +149,13 @@ export function ContractHeaderUI(props: {
{/* bottom row */}
<div className="flex flex-row flex-wrap items-center gap-2">
{props.contractCreator && (
<ContractCreatorBadge
clientContract={props.clientContract}
contractCreator={props.contractCreator}
/>
)}
<ToolTipLabel
contentClassName="max-w-[300px]"
label={
@@ -2,45 +2,47 @@
import { useTheme } from "next-themes";
import type { Chain, ThirdwebClient } from "thirdweb";
import { PayEmbed } from "thirdweb/react";
import { getSDKTheme } from "../../../../../../../../../@/utils/sdk-component-theme";
import { BuyWidget } from "thirdweb/react";
import {
reportAssetBuyFailed,
reportAssetBuySuccessful,
} from "@/analytics/report";
import { parseError } from "@/utils/errorParser";
import { getSDKTheme } from "@/utils/sdk-component-theme";
export function BuyTokenEmbed(props: {
client: ThirdwebClient;
chain: Chain;
tokenSymbol: string;
tokenName: string;
tokenAddress: string;
}) {
const { theme } = useTheme();
return (
<PayEmbed
<BuyWidget
amount="1"
chain={props.chain}
className="!rounded-xl !w-full"
client={props.client}
connectOptions={{
autoConnect: false,
}}
payOptions={{
metadata: {
name: `Buy ${props.tokenSymbol}`,
},
mode: "fund_wallet",
prefillBuy: {
allowEdits: {
amount: true,
chain: false,
token: false,
},
amount: "1",
chain: props.chain,
token: {
address: props.tokenAddress,
name: props.tokenName,
symbol: props.tokenSymbol,
},
},
onError={(e) => {
const errorMessage = parseError(e);
reportAssetBuyFailed({
assetType: "coin",
chainId: props.chain.id,
contractType: "DropERC20",
error: errorMessage,
});
}}
onSuccess={() => {
reportAssetBuySuccessful({
assetType: "coin",
chainId: props.chain.id,
contractType: "DropERC20",
});
}}
theme={getSDKTheme(theme === "light" ? "light" : "dark")}
tokenAddress={props.tokenAddress as `0x${string}`}
/>
);
}
@@ -26,7 +26,7 @@ import {
getApprovalForTransaction,
} from "thirdweb/extensions/erc20";
import { useActiveAccount, useSendTransaction } from "thirdweb/react";
import { getClaimParams } from "thirdweb/utils";
import { getClaimParams, maxUint256 } from "thirdweb/utils";
import {
reportAssetBuyFailed,
reportAssetBuySuccessful,
@@ -328,9 +328,16 @@ export function TokenDropClaim(props: {
claimedSupply={BigInt(
toTokens(props.claimCondition.supplyClaimed, props.decimals),
)}
totalSupply={BigInt(
toTokens(props.claimCondition.maxClaimableSupply, props.decimals),
)}
totalSupply={
props.claimCondition.maxClaimableSupply === maxUint256
? "unlimited"
: BigInt(
toTokens(
props.claimCondition.maxClaimableSupply,
props.decimals,
),
)
}
/>
<div className="h-4" />
@@ -39,7 +39,7 @@ export function ContractAnalyticsOverview(props: {
</div>
<ThirdwebAreaChart
cardContentClassName="p-0"
chartClassName="aspect-[1.5] lg:aspect-[3]"
chartClassName="aspect-[1.2] lg:aspect-[2.6]"
className="border-none bg-background p-0"
config={{
events: {
@@ -0,0 +1,32 @@
import { isProd } from "@/constants/env-utils";
export async function fetchTokenInfoFromBridge(params: {
chainId: number;
tokenAddress: string;
clientId: string;
}) {
try {
const res = await fetch(
`https://bridge.${isProd ? "thirdweb.com" : "thirdweb-dev.com"}/v1/tokens?chainId=${params.chainId}&tokenAddress=${params.tokenAddress}&clientId=${params.clientId}`,
);
if (!res.ok) {
return null;
}
const data = (await res.json()) as {
data: Array<{
iconUri: string;
address: string;
decimals: number;
name: string;
symbol: string;
priceUsd: number;
}>;
};
return data.data[0];
} catch {
return null;
}
}
@@ -2,6 +2,8 @@ import type { ThirdwebContract } from "thirdweb";
import type { ChainMetadata } from "thirdweb/chains";
import { getContractMetadata } from "thirdweb/extensions/common";
import { decimals, getActiveClaimCondition } from "thirdweb/extensions/erc20";
import { resolveFunctionSelectors } from "@/lib/selectors";
import { getContractCreator } from "../_components/getContractCreator";
import { PageHeader } from "../_components/PageHeader";
import { ContractHeaderUI } from "./_components/ContractHeader";
import { TokenDropClaim } from "./_components/claim-tokens/claim-tokens-ui";
@@ -9,6 +11,7 @@ import { ContractAnalyticsOverview } from "./_components/contract-analytics/cont
import { BuyTokenEmbed } from "./_components/PayEmbedSection";
import { TokenStats } from "./_components/PriceChart";
import { RecentTransfers } from "./_components/RecentTransfers";
import { fetchTokenInfoFromBridge } from "./_utils/fetch-coin-info";
import { getCurrencyMeta } from "./_utils/getCurrencyMeta";
export async function ERC20PublicPage(props: {
@@ -16,25 +19,43 @@ export async function ERC20PublicPage(props: {
clientContract: ThirdwebContract;
chainMetadata: ChainMetadata;
}) {
const [contractMetadata, activeClaimCondition, tokenDecimals] =
await Promise.all([
getContractMetadata({
contract: props.serverContract,
}),
getActiveClaimConditionWithErrorHandler(props.serverContract),
decimals({
contract: props.serverContract,
}),
]);
const [
contractMetadata,
activeClaimCondition,
tokenDecimals,
tokenInfo,
functionSelectors,
] = await Promise.all([
getContractMetadata({
contract: props.serverContract,
}),
getActiveClaimConditionWithErrorHandler(props.serverContract),
decimals({
contract: props.serverContract,
}),
fetchTokenInfoFromBridge({
chainId: props.serverContract.chain.id,
clientId: props.clientContract.client.clientId,
tokenAddress: props.serverContract.address,
}),
resolveFunctionSelectors(props.serverContract),
]);
const claimConditionCurrencyMeta = activeClaimCondition
? await getCurrencyMeta({
chain: props.serverContract.chain,
chainMetadata: props.chainMetadata,
client: props.serverContract.client,
currencyAddress: activeClaimCondition.currency,
}).catch(() => undefined)
: undefined;
if (!contractMetadata.image && tokenInfo) {
contractMetadata.image = tokenInfo.iconUri;
}
const [contractCreator, claimConditionCurrencyMeta] = await Promise.all([
getContractCreator(props.serverContract, functionSelectors),
activeClaimCondition
? getCurrencyMeta({
chain: props.serverContract.chain,
chainMetadata: props.chainMetadata,
client: props.serverContract.client,
currencyAddress: activeClaimCondition.currency,
}).catch(() => undefined)
: undefined,
]);
const buyEmbed = (
<BuyEmbed
@@ -62,6 +83,7 @@ export async function ERC20PublicPage(props: {
<ContractHeaderUI
chainMetadata={props.chainMetadata}
clientContract={props.clientContract}
contractCreator={contractCreator}
image={contractMetadata.image}
name={contractMetadata.name}
socialUrls={
@@ -141,8 +163,6 @@ function BuyEmbed(props: {
chain={props.clientContract.chain}
client={props.clientContract.client}
tokenAddress={props.clientContract.address}
tokenName={props.tokenName}
tokenSymbol={props.tokenSymbol}
/>
);
}
@@ -12,6 +12,7 @@ export function NFTPublicPageLayout(props: {
[key: string]: unknown;
};
children: React.ReactNode;
contractCreator: string | null;
}) {
return (
<div className="flex grow flex-col">
@@ -20,6 +21,7 @@ export function NFTPublicPageLayout(props: {
<ContractHeaderUI
chainMetadata={props.chainMetadata}
clientContract={props.clientContract}
contractCreator={props.contractCreator}
image={
typeof props.contractMetadata.image === "string"
? props.contractMetadata.image
@@ -6,6 +6,7 @@ import { ResponsiveLayout } from "@/components/blocks/Responsive";
import { Skeleton } from "@/components/ui/skeleton";
import { resolveFunctionSelectors } from "@/lib/selectors";
import { cn } from "@/lib/utils";
import { getContractCreator } from "../_components/getContractCreator";
import { NFTPublicPageLayout } from "./nft-page-layout";
import {
BuyNFTDropCardServer,
@@ -35,15 +36,17 @@ export async function NFTPublicPage(props: {
}),
]);
const nftDropClaimParams =
const [contractCreator, nftDropClaimParams] = await Promise.all([
getContractCreator(props.serverContract, functionSelectors),
props.type === "erc721"
? await getNFTDropClaimParams({
? getNFTDropClaimParams({
chainMetadata: props.chainMetadata,
functionSelectors,
serverContract: props.serverContract,
totalNFTCount,
})
: undefined;
: undefined,
]);
const _isTokenByIndexSupported =
props.type === "erc721" && isTokenByIndexSupported(functionSelectors);
@@ -101,6 +104,7 @@ export async function NFTPublicPage(props: {
<NFTPublicPageLayout
chainMetadata={props.chainMetadata}
clientContract={props.clientContract}
contractCreator={contractCreator}
contractMetadata={contractMetadata}
>
<ResponsiveLayout
@@ -10,18 +10,18 @@ import type { ChainSupportedService } from "@/types/chain";
export const products = [
{
description: "Pre-built contracts & deployment tools",
description: "Create, deploy and manage smart contracts",
icon: ContractIcon,
id: "contracts",
link: "https://thirdweb.com/explore",
name: "Contracts",
},
{
description: "Client-side SDKs for wallets and blockchain interactions",
description: "Create and manage crypto wallets",
icon: ConnectSDKIcon,
id: "connect-sdk",
link: "https://thirdweb.com/connect",
name: "Connect SDK",
name: "Wallets",
},
{
description: "Performant and scalable RPC service",
@@ -35,24 +35,24 @@ export const products = [
icon: EngineIcon,
id: "engine",
link: "https://thirdweb.com/engine",
name: "Engine",
name: "Transactions",
},
{
description: "Account factory contracts, bundler & paymaster",
description: "Enable gas sponsorship for seamless transactions",
icon: SmartAccountIcon,
id: "account-abstraction",
link: "https://portal.thirdweb.com/connect/account-abstraction/overview",
name: "Account Abstraction",
},
{
description: "Point of sale solution for bridging, onramping & swapping",
description: "Enable payments in any token on any chain",
icon: PayIcon,
id: "pay",
link: "https://portal.thirdweb.com/connect/pay/overview",
name: "Universal Bridge",
name: "Payments",
},
{
description: "Query, transform and analyze blockchain data",
description: "Query and analyze blockchain data",
icon: InsightIcon,
id: "insight",
link: "https://thirdweb.com/insight",
@@ -33,25 +33,25 @@ const HELP_PRODUCTS = [
"https://playground.thirdweb.com/connect/sign-in/button?tab=code",
},
{
description: "Wallets, auth, and onchain interactions",
description: "Create and manage crypto wallets",
icon: WalletIcon,
title: "Connect",
title: "Wallets",
viewAllUrl: "https://portal.thirdweb.com/connect",
},
{
description: "Bridge and onramp tokens on any chain",
description: "Enable payments on any tokens on any chain",
icon: PayIcon,
title: "Universal Bridge",
viewAllUrl: "https://portal.thirdweb.com/pay",
title: "Payments",
viewAllUrl: "https://portal.thirdweb.com/pay/troubleshoot",
},
{
description: "Reliable transactions and monitoring",
description: "Perform read and write transactions onchain",
icon: EngineIcon,
title: "Engine",
viewAllUrl: "https://portal.thirdweb.com/engine/v3",
title: "Transactions",
viewAllUrl: "https://portal.thirdweb.com/engine/v3/troubleshoot",
},
{
description: "Blockchain data queries and transformations",
description: "Query and analyze blockchain data",
icon: InsightIcon,
title: "Insight",
viewAllUrl: "https://portal.thirdweb.com/insight",
@@ -117,7 +117,7 @@ export default async function SupportPage() {
</header>
</div>
<section className="container flex flex-col gap-6">
<h2 className="font-bold text-3xl">Learning Resources</h2>
<h2 className="font-bold text-3xl">Support Articles</h2>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 md:gap-6 lg:grid-cols-3">
{HELP_PRODUCTS.map((product) => (
<Card className="relative col-span-1 bg-card" key={product.title}>
@@ -34,7 +34,14 @@ type ExecutionResult4337Serialized =
}
| {
status: "FAILED";
error: string;
error: {
stage: string;
message: string;
errorCode: string;
inner_error: object;
nonce_used: string;
account_address: string;
};
}
| {
status: "SUBMITTED";
@@ -3,7 +3,7 @@
import { format, formatDistanceToNowStrict } from "date-fns";
import { ExternalLinkIcon, InfoIcon } from "lucide-react";
import Link from "next/link";
import { type ThirdwebClient, toEther } from "thirdweb";
import { hexToNumber, isHex, type ThirdwebClient, toEther } from "thirdweb";
import type { Project } from "@/api/projects";
import { WalletAddress } from "@/components/blocks/wallet-address";
import { Badge } from "@/components/ui/badge";
@@ -43,10 +43,14 @@ export function TransactionDetailsUI({
const status = executionResult?.status as keyof typeof statusDetails;
const errorMessage =
executionResult && "error" in executionResult
? executionResult.error
? executionResult.error.message
: executionResult && "revertData" in executionResult
? executionResult.revertData?.revertReason
: null;
const errorDetails =
executionResult && "error" in executionResult
? executionResult.error
: undefined;
const chain = chainId ? idToChain.get(Number.parseInt(chainId)) : undefined;
const explorer = chain?.explorers?.[0];
@@ -64,7 +68,7 @@ export function TransactionDetailsUI({
// Gas information
const gasUsed =
executionResult && "actualGasUsed" in executionResult
? `${executionResult.actualGasUsed}`
? `${isHex(executionResult.actualGasUsed) ? hexToNumber(executionResult.actualGasUsed) : executionResult.actualGasUsed}`
: "N/A";
const gasCost =
@@ -244,9 +248,16 @@ export function TransactionDetailsUI({
</CardTitle>
</CardHeader>
<CardContent>
<div className="rounded-md bg-destructive/10 p-4 text-destructive">
{errorMessage}
</div>
{errorDetails ? (
<CodeClient
code={JSON.stringify(errorDetails, null, 2)}
lang="json"
/>
) : (
<div className="rounded-md bg-destructive/10 p-4 text-destructive">
{errorMessage}
</div>
)}
</CardContent>
</Card>
)}
@@ -328,7 +339,9 @@ export function TransactionDetailsUI({
Block Number
</div>
<div className="text-sm md:w-2/3">
{transaction.confirmedAtBlockNumber}
{isHex(transaction.confirmedAtBlockNumber)
? hexToNumber(transaction.confirmedAtBlockNumber)
: transaction.confirmedAtBlockNumber}
</div>
</div>
)}
+1 -1
View File
@@ -1,4 +1,4 @@
{
"$schema": "https://biomejs.dev/schemas/2.0.0/schema.json",
"$schema": "https://biomejs.dev/schemas/2.0.4/schema.json",
"extends": "//"
}
+1 -1
View File
@@ -1,4 +1,4 @@
{
"$schema": "https://biomejs.dev/schemas/2.0.0/schema.json",
"$schema": "https://biomejs.dev/schemas/2.0.4/schema.json",
"extends": "//"
}
+1 -1
View File
@@ -1,4 +1,4 @@
{
"$schema": "https://biomejs.dev/schemas/2.0.0/schema.json",
"$schema": "https://biomejs.dev/schemas/2.0.4/schema.json",
"extends": "//"
}
+18 -18
View File
@@ -33,15 +33,15 @@ import { ThirdwebIcon } from "../icons/thirdweb";
const links = [
{
href: "/connect",
name: "Connect",
name: "Wallets",
},
{
href: "/pay",
name: "Bridge",
name: "Payments",
},
{
href: "/engine",
name: "Engine",
name: "Transactions",
},
{
href: "/contracts",
@@ -181,7 +181,7 @@ const sdkLinks = [
const supportLinks = [
{
href: "/knowledge-base",
name: "Get thirdweb support",
name: "Articles",
},
{
href: "/account",
@@ -216,6 +216,20 @@ export function Header() {
</div>
<div className="flex items-center gap-3">
<div className="hidden xl:block">
<Link
className="text-foreground"
href="https://github.com/thirdweb-dev"
target="_blank"
>
<GithubIcon className="mx-3 size-6" />
</Link>
</div>
<div className="hidden xl:block">
<ThemeSwitcher className="border-none bg-transparent" />
</div>
<div className="hidden xl:block">
<DocSearch variant="search" />
</div>
@@ -231,20 +245,6 @@ export function Header() {
</Button>
</div>
<div className="hidden xl:block">
<ThemeSwitcher className="border-none bg-transparent" />
</div>
<div className="hidden xl:block">
<Link
className="text-foreground"
href="https://github.com/thirdweb-dev"
target="_blank"
>
<GithubIcon className="mx-3 size-6" />
</Link>
</div>
<div className="flex items-center gap-1 xl:hidden">
<ThemeSwitcher className="border-none bg-transparent" />
<DocSearch variant="icon" />
+1 -1
View File
@@ -9,7 +9,7 @@ export default async function Layout(props: { children: React.ReactNode }) {
sideBar={sidebar}
sidebarHeader={
<div className="flex-col items-center gap-1">
<p className="py-5 font-semibold text-foreground text-lg">Connect</p>
<p className="py-5 font-semibold text-foreground text-lg">Wallets</p>
<PlatformSelector selected="Overview" />
</div>
}
+6 -6
View File
@@ -34,9 +34,9 @@ export const metadata = createMetadata({
},
});
# What is Connect?
# Wallets
Connect is the complete toolkit for connecting every user to your application. It features customizable onboarding flows, self-custodial in-app wallets, account abstraction, onramps, and a performant API to interact with the blockchain.
Wallets is the complete toolkit for connecting every user to your application. It features customizable onboarding flows, self-custodial in-app wallets, account abstraction, onramps, and a performant API to interact with the blockchain.
## Live Playground
@@ -55,7 +55,7 @@ You can interact with the React SDK in the [Live Playground](https://playground.
## Quick starts
Get started with Connect in your preferred language.
Get started with Wallets in your preferred language.
<Grid>
<SDKCard
@@ -78,7 +78,7 @@ Get started with Connect in your preferred language.
/>
</Grid>
## With Connect, you can
## With Wallets, you can
- **Connect to 500+ different wallet providers** with support for every EVM network
- **Log in and authenticate your users** with customizable and secure [email, phone, passkeys and social login](https://portal.thirdweb.com/connect/in-app-wallet/overview) flows.
@@ -90,7 +90,7 @@ Get started with Connect in your preferred language.
<Callout variant="info" title="Have you generated a client ID?">
<div className="flex flex-col gap-4 items-start">
<p>
You'll need a client ID to access Connect's free blockchain APIs, storage, and more.
You'll need a client ID to access the Wallet APIs, storage, and more.
</p>
<Button asChild variant='outline'>
@@ -102,7 +102,7 @@ Get started with Connect in your preferred language.
## Supported Chains
Connect is supported on every EVM compatible chain. To view the full list, visit [thirdweb chainlist](https://thirdweb.com/chainlist).
Wallets are supported on every EVM compatible chain. To view the full list, visit [thirdweb chainlist](https://thirdweb.com/chainlist).
<DocImage src={SupportedChains} />
@@ -43,7 +43,7 @@ Yes, you can easily use account abstraction with in-app wallets to enable sponso
Thirdweb wallets can be used as a signer to a smart contract account (account abstraction), but it can also be used as a standalone EOA.
### Is there a way to display the logged-in email address in the Connect component, similar to the wallet address?
### Is there a way to display the logged-in email address in the Wallet component, similar to the wallet address?
Yes, we show the email by default in the details modal after you connect. To display the email anywhere else using React or React Native, [check out the code snippets for all platforms](/connect/in-app-wallet/how-to/interact-with-wallets#get-the-user-email).
@@ -40,7 +40,7 @@ Your client ID authenticates your application with thirdweb's services and provi
## Live Playground
Before diving into the implementation, check out our playground to see all the various capabilities of Connect:
Before diving into the implementation, check out our playground to see all the various capabilities of Wallets:
<ArticleIconCard
title="Try the demo"
@@ -224,7 +224,7 @@ Before diving into the implementation, check out our playground to see all the v
}
```
### Connect Button Component
### Wallet Button Component
Use the pre-built ConnectButton for a complete wallet connection UI:
@@ -8,4 +8,4 @@ If you are thinking of migrating your users off another embedded wallet provider
<Callout variant="info" title="Learn more">Learn more about Migration from our [GitHub repository](https://github.com/thirdweb-example/migrate)</Callout>
If you’d prefer to use your existing solutions for now and take advantage of thirdweb for other parts of your application, we recommend using the [Connect SDK adapters](/typescript/v5/adapters).
If you’d prefer to use your existing solutions for now and take advantage of thirdweb for other parts of your application, we recommend using the [Wallet SDK adapters](/typescript/v5/adapters).
+1 -1
View File
@@ -163,5 +163,5 @@ export const sidebar: SideBar = {
name: "FAQ",
},
],
name: "Engine",
name: "Transactions",
};
@@ -4,13 +4,13 @@ Smart server wallets are smart accounts designed to perform blockchain operation
## Benefits
Smart server wallets are the recommended way to perform blockchain operations with Engine.
Smart server wallets are the recommended way to perform blockchain operations with thirdweb Transactions.
- **Managed Gas Fees**: Smart server wallets manage gas fees for you by automatically paying the fees using the payment method associated with your thirdweb account. You do not have to worry about topping up
gas funds on your server wallets.
- **Secure Key Management**: Smart server wallets are secured by Vault. Read more about Vault in the Security section below.
- **Minimal Setup:** Smart server wallets can be created with just a few clicks directly in the thirdweb dashboard. No need to manage private keys or seed phrases or secure with external key management systems.
- **Higher Throughput:** Smart server wallets can handle higher throughput by using multi-dimensional nonces. While Engine handles concurrent transactions through a queue system, smart wallets use parallelization to process multiple transactions at the same time, more efficiently than EOAs.
- **Higher Throughput:** Smart server wallets can handle higher throughput by using multi-dimensional nonces. While thirdweb Transactions handles concurrent transactions through a queue system, smart wallets use parallelization to process multiple transactions at the same time, more efficiently than EOAs.
## Security
@@ -22,4 +22,3 @@ Keys used to manage server wallets including the admin key and access keys can b
Smart server wallets cost a gas premium fee for each transaction completed on any mainnet as part of account abstraction fees. There are no gas charges for testnets.
[See pricing page for details](https://thirdweb.com/pricing).
@@ -1,8 +0,0 @@
import { Details } from "@doc";
# Server Wallets
Smart server wallets are the recommended way to perform blockchain operations with Engine.
You can create smart server wallets by navigating to Engine > Server Wallets in your project dashboard.
+14 -6
View File
@@ -1,20 +1,28 @@
import { Callout, Details } from "@doc";
# Engine FAQs
# Transactions FAQs
<Details summary="How is pricing calculated for Engine Cloud?">
Pricing is calculated through the number of write requests (ex: /v1/write/contract) and sign requests (ex: /v1/sign/transaction) made through the Engine API. For requests costs, [see the thirdweb pricing page](https://thirdweb.com/pricing). Read requests made through Engine API are free, within the RPC limits/plan.
<Details summary="How is pricing calculated?">
Pricing is calculated through the number of write requests (ex: /v1/write/contract) and sign requests (ex: /v1/sign/transaction) made through the Transactions API. For requests costs, [see the thirdweb pricing page](https://thirdweb.com/pricing). Read requests made through Transactions API are free, within the RPC limits/plan.
For transactions through server wallets paid through the user's thirdweb account, users will pay a 5% premium on the gas fee of each transaction completed on any mainnet as part of account abstraction fees. There are no gas charges for testnets.
For requests through server wallets paid through the user's thirdweb account, users will pay a 5% premium on the gas fee of each request completed on any mainnet as part of account abstraction fees. There are no gas charges for testnets.
**Please note: Legacy pricing plans may be subject to the 10% fee specified when signed up instead of 5%. [See pricing page for updated plans and benefits](https://thirdweb.com/pricing)**
The breakdown for usage and transaction fees can be found in your usage dashboard under the team overview.
</Details>
<Details summary="Which chains does Engine Cloud support?">
While in beta, Engine Cloud currently supports every non ZK EVM-compatible chain which includes support for Account Abstraction. See the current list of supported chains.
<Details summary="Which chains does Transactions Cloud support?">
While in beta, Transactions Cloud currently supports every non ZK EVM-compatible chain which includes support for Account Abstraction. See the current list of supported chains.
If your chain is not listed, please [contact us](https://thirdweb.com/contact-us) to prioritize enabling support.
</Details>
<Details summary="What is the difference between Transactions Cloud and Dedicated?">
Cloud uses shared infrastructure that allows you to get started quickly with Transactions. It is ideal for most use cases and is the recommended way to use Transactions.
Dedicated is a self-hosted version of Transactions that allows you to run Transactions on your own infrastructure.
It is ideal for high throughput applications, or applications that require complete control over their infrastructure.
</Details>
@@ -4,21 +4,21 @@ import SendTransaction from '../assets/send-transaction.png';
# Get started
Learn how to get started with thirdweb Engine. This guide will walk you through the steps to start building with thirdweb Engine by creating a Vault, server wallets, and integrating into your application.
Learn how to get started with thirdweb Transactions. This guide will walk you through the steps to start building with thirdweb Transactions by creating a Vault, server wallets, and integrating into your application.
## Prerequisites
- Create a thirdweb project. If you don't have a project yet, [learn how to create a project](/account/api-keys/create).
## Engine Setup
## Transactions Setup
<Steps>
<Step title="Navigate to Engine">
In your project dashboard, navigate to **Engine** to get started.
<Step title="Navigate to Transactions">
In your project dashboard, navigate to **Transactions** to get started.
</Step>
<Step title="Create Vault">
Create a vault to manage your Engine's server wallets. After setup, you can manage your Vault to create additional access tokens or rotate admin keys.
Create a vault to manage your Transactions's server wallets. After setup, you can manage your Vault to create additional access tokens or rotate admin keys.
<Callout variant="info" title="Vault">
Vault is thirdweb's key management service designed to store smart server wallets non-custodially. [Learn more about Vault.](/vault)
@@ -27,10 +27,10 @@ Learn how to get started with thirdweb Engine. This guide will walk you through
</Step>
<Step title="Create Server Wallet">
Create a server wallet to perform blockchain actions with Engine.
Create a server wallet to perform blockchain actions with Transactions.
<Callout variant="info" title="Server Wallet">
Server wallets are smart wallets Engine uses to perform blockchain actions. [Learn more about server wallets](/engine/v2/configure-wallets/server-wallets).
Server wallets are smart wallets Transactions uses to perform blockchain actions. [Learn more about server wallets](/Transactions/v2/configure-wallets/server-wallets).
</Callout>
</Step>
@@ -42,7 +42,7 @@ Learn how to get started with thirdweb Engine. This guide will walk you through
</Step>
<Step title="Integrate with your app">
Integrate Engine into your application using the thirdweb SDK or Engine API. [View full API reference.](https://client.scalar.com/workspace/default/request/yq_Wx56PL4Ur6jOZsAOpA)
Integrate Transactions into your application using the thirdweb SDK or Transactions API. [View full API reference.](https://client.scalar.com/workspace/default/request/yq_Wx56PL4Ur6jOZsAOpA)
<Tabs defaultValue="curl">
<TabsList>
@@ -255,7 +255,7 @@ Learn how to get started with thirdweb Engine. This guide will walk you through
- The full catalog of extension functions
- The prepareContractCall function to encode your transactions
- The full account interface, predefined chains, and more
The SDK handles encoding your transactions, signing them to Engine and polling for status.
The SDK handles encoding your transactions, signing them to Transactions and polling for status.
</Callout>
</Step>
</Steps>
+5 -3
View File
@@ -10,7 +10,9 @@ export default async function Layout(props: { children: React.ReactNode }) {
sideBar={sidebar}
sidebarHeader={
<div className="flex-col items-center gap-1">
<p className="py-5 font-semibold text-foreground text-lg">Engine</p>
<p className="py-5 font-semibold text-foreground text-lg">
Transactions
</p>
<EngineVersionSelector selected="v3" />
</div>
}
@@ -22,6 +24,6 @@ export default async function Layout(props: { children: React.ReactNode }) {
export const metadata = createMetadata({
description:
"Engine is a backend HTTP server that calls smart contracts using your managed backend wallets.",
title: "Engine V3",
"thirdweb Transactions is a backend HTTP server that calls smart contracts using your managed backend wallets.",
title: "Transactions",
});
@@ -1,8 +1,8 @@
import { Callout } from "@doc";
# Migrating from Engine v2 to v3
# Migrating from Transactions v2 to v3
Learn how to migrate your applications from Engine v2 to Engine v3. We'll cover common operations and highlight the differences in API requests and authentication.
Learn how to migrate your applications from v2 to v3. We'll cover common operations and highlight the differences in API requests and authentication.
<Callout variant="info" title="Migrating Wallets">
If you'd like to migrate your wallets from v2 to v3 to also be secured by [Vault](/vault) or thirdweb's key management service, please [contact us for assistance.](https://thirdweb.com/support)
@@ -14,7 +14,7 @@ Migrating your contract write operations from v2 to v3 involves changes to the e
#### OLD (v2)
The v2 approach used your dedicated engine url, with a specific endpoint for each chain and contract and backend wallet address in the header.
The v2 approach used your dedicated Transactions url, with a specific endpoint for each chain and contract and backend wallet address in the header.
```bash
curl -X POST "<engine_url>/contract/<chain>/<contract_address>/write" \
@@ -173,9 +173,9 @@ curl https://engine.thirdweb.com/v1/read/contract \
## Extension functions
Engine v2 has a large number of endpoints to do specific contract calls, like mintTo, claimTo, etc.
V2 has a large number of endpoints to do specific contract calls, like mintTo, claimTo, etc.
Engine v3 does not have these endpoints yet, but they are coming soon. In the meantime, if your backend is written in typescript, we recommend using the [thirdweb SDK](/references/typescript/v5/functions#extensions) to do these operations:
V3 does not have these endpoints yet, but they are coming soon. In the meantime, if your backend is written in typescript, we recommend using the [thirdweb SDK](/references/typescript/v5/functions#extensions) to do these operations:
##### OLD (v2)
@@ -194,7 +194,7 @@ const response = await fetch(
metadataWithSupply: {
metadata: {
name: "Acme Inc. Superfan",
description: "Created with thirdweb Engine",
description: "Created with thirdweb",
image:
"ipfs://QmciR3WLJsf2BgzTSjbG5zCxsrEQ8PqsHK7JWGWsDSNo46/nft.png",
},
@@ -233,17 +233,17 @@ const transaction = mintTo({
supply: 1n, // The quantity of NFTs to mint
nft: {
name: "Acme Inc. Superfan",
description: "Created with thirdweb Engine",
description: "Created with thirdweb",
image:
"ipfs://QmciR3WLJsf2BgzTSjbG5zCxsrEQ8PqsHK7JWGWsDSNo46/nft.png",
},
});
// Enqueue the transaction via Engine
// Enqueue the transaction
const { transactionId } =
await serverWallet.enqueueTransaction({
transaction,
});
```
The entire thirdweb SDK is available to use with engine v3 in this manner using the [`Engine` namespace](/references/typescript/v5/functions#engine). Check the [thirdweb SDK docs](/references/typescript/v5/functions#extensions) for all available functions.
The entire thirdweb SDK is available to use with Transactions v3 in this manner using the [`Transactions` namespace](/references/typescript/v5/functions#engine). Check the [thirdweb SDK docs](/references/typescript/v5/functions#extensions) for all available functions.
+8 -9
View File
@@ -5,19 +5,19 @@ import SupportedChains from "../../_images/supported-chains.png";
export const metadata = createMetadata({
title: "thirdweb Engine",
title: "thirdweb Transactions",
description: "An open-source, backend server that reads, writes, and deploys contracts at production scale.",
});
# Engine
# Transactions
Engine provides a way to read and write to the blockchain at scale through simple API endpoints.
Transactions provides a way to read and write to the blockchain at scale through simple API endpoints.
<OpenSourceCard title="Engine" href="https://github.com/thirdweb-dev/engine" />
<OpenSourceCard title="Transactions" href="https://github.com/thirdweb-dev/engine" />
<Callout variant="info" title="Looking for the v2 documentation?">
We just launched Engine Cloud in beta — we're actively adding new features, endpoints, and performance improvements.
Engine Standard and Premium have now been rebranded as Engine Dedicated V2, with Engine Dedicated V3 on the way.
We just launched Transactions Cloud in beta — we're actively adding new features, endpoints, and performance improvements.
Engine Standard and Premium have now been rebranded as Transactions Dedicated V2, with Transactions Dedicated V3 on the way.
Looking for the previous version? [Head back to the Engine V2 documentation.](/engine/v2)
</Callout>
@@ -74,10 +74,9 @@ Looking for the previous version? [Head back to the Engine V2 documentation.](/e
description="Debug transaction failures with detailed timelines, onchain errors, and gas details."
iconUrl={<ActivityIcon />}
/>
/>
</div>
## Engine Cloud vs. Dedicated
## Cloud vs. Dedicated
| Feature | Cloud ☁️ | Dedicated (Coming Soon) |
|--------------------|-------------------------------------------|------------------------------------------|
@@ -90,7 +89,7 @@ Looking for the previous version? [Head back to the Engine V2 documentation.](/e
## Supported Chains
Engine is supported on every EVM compatible chain and private subnets. To view the full list, visit [thirdweb chainlist](https://thirdweb.com/chainlist?service=engine).
Transactions are supported on every EVM compatible chain and private subnets. To view the full list, visit [thirdweb chainlist](https://thirdweb.com/chainlist?service=engine).
<DocImage src={SupportedChains} />
+2 -2
View File
@@ -45,7 +45,7 @@ export const sidebar: SideBar = {
icon: <WalletIcon />,
links: [
{
href: `${engineV3Slug}/configure-wallets/smart-server-wallet`,
href: `${engineV3Slug}/configure-wallets/server-wallets`,
name: "Smart Server Wallet",
},
],
@@ -82,5 +82,5 @@ export const sidebar: SideBar = {
name: "FAQ",
},
],
name: "Engine",
name: "Transactions",
};
@@ -1,8 +1,8 @@
import { Details } from "@doc";
# Engine Troubleshoot Guide
# Transactions Troubleshoot Guide
<Details summary="Transactions stuck in a pending state using Engine Cloud.">
<Details summary="Requests stuck in a pending state using Transactions Cloud.">
Ensure that Account Abstraction is enabled for your project by navigating to Project Settings > Enabled Services > Account Abstraction.
@@ -1,10 +1,10 @@
# Engine TypeScript SDK
# Transactions TypeScript SDK
You can use the full TypeScript thirdweb SDK in your backend, allowing you to use:
- The full catalog of extension functions
- The prepareContractCall function to encode your transactions
- The full account interface, predefined chains, and more
The SDK handles encoding your transactions, signing them to Engine and polling for status.
The SDK handles encoding your transactions, signing them to Transactions and polling for status.
## Example
@@ -20,7 +20,7 @@ import {
createThirdwebClient,
sendTransaction,
getContract,
Engine,
Transactions,
} from "thirdweb";
import { baseSepolia } from "thirdweb/chains";
import { claimTo } from "thirdweb/extensions/1155";
@@ -49,7 +49,7 @@ const transaction = claimTo({
quantity: 1n, // The quantity of NFTs to mint
});
// Send the transaction via Engine
// Send the transaction via Transactions
const result = await sendTransaction({
account: serverWallet,
transaction,
+17 -5
View File
@@ -163,24 +163,30 @@ function LearningResourcesSection() {
return (
<section>
<SectionTitle anchorId="learning" title="Learning Resources" />
<Grid className="md:grid-cols-1 lg:grid-cols-2">
<Grid className="md:grid-cols-1 lg:grid-cols-3">
<ArticleCardIndex
description="Wallets, auth, and onchain interactions"
description="Create and manage crypto wallets"
href="/connect"
icon={ConnectIcon}
title="Connect"
title="Wallets"
/>
<ArticleCardIndex
description="Bridge and onramp tokens on any chain"
href="/pay"
icon={BridgeIcon}
title="Universal Bridge"
title="Payments"
/>
<ArticleCardIndex
description="Reliable transactions and monitoring"
href="/engine"
icon={EngineIcon}
title="Engine"
title="Transactions"
/>
<ArticleCardIndex
description="Create, deploy, and manage smart contracts"
href="/contracts"
icon={EngineIcon}
title="Contracts"
/>
<ArticleCardIndex
description="Blockchain data queries and transformations"
@@ -194,6 +200,12 @@ function LearningResourcesSection() {
icon={NebulaIcon}
title="Nebula"
/>
<ArticleCardIndex
description="Non-custodial key management service"
href="/vault"
icon={NebulaIcon}
title="Vault"
/>
</Grid>
</section>
);
@@ -1,17 +1,17 @@
import { createMetadata, DocImage } from "@doc";
export const metadata = createMetadata({
image: {
title: "thirdweb Universal Bridge - Customize ConnectButton",
title: "thirdweb Payments - Customize ConnectButton",
icon: "thirdweb",
},
title: "thirdweb Universal Bridge - Customize ConnectButton - thirdweb",
title: "thirdweb Payments - Customize ConnectButton - thirdweb",
description:
"thirdweb Universal Bridge - Customizing ConnectButton onramps and crypto purchase flows",
"thirdweb Payments - Customizing ConnectButton onramps and crypto purchase flows",
});
# Customize ConnectButton
Learn how to customize Universal Bridge within the `ConnectButton` interface. You can find a selection of popular customizations below. For the full list of props, you can [view the full reference](/references/typescript/v5/ConnectButtonProps).
Learn how to customize Payments within the `ConnectButton` interface. You can find a selection of popular customizations below. For the full list of props, you can [view the full reference](/references/typescript/v5/ConnectButtonProps).
---
@@ -39,7 +39,7 @@ You can enable users to select your token on a given chain by passing an array o
## Prefill Destination Token
In the case you want your users to purchase your token by default in your application, you can choose to pre-fill the Universal Bridge purchase flow with `prefillBuy` .
In the case you want your users to purchase your token by default in your application, you can choose to pre-fill the Payments purchase flow with `prefillBuy` .
For example, if you wanted users to only purchase Ethereum on Base network, you could do as follows:
@@ -137,7 +137,7 @@ In some cases, you may only want to show users fiat or crypto payment options fo
## Theme
You can set the theme for the Universal Bridge component, which is set to `"dark"` by default. theme can be set to either `"dark"` , `"light"` or a custom theme object.
You can set the theme for the Payments component, which is set to `"dark"` by default. theme can be set to either `"dark"` , `"light"` or a custom theme object.
We have [`lightTheme`](/references/typescript/v5/lightTheme) or [`darkTheme`](/references/typescript/v5/darkTheme) providers that you can override to kickstart customization.
@@ -1,17 +1,17 @@
import { createMetadata, DocImage } from "@doc";
export const metadata = createMetadata({
image: {
title: "thirdweb Universal Bridge - Customize sendTransaction",
title: "thirdweb Payments - Customize sendTransaction",
icon: "thirdweb",
},
title: "thirdweb Universal Bridge - Customize sendTransaction - thirdweb",
title: "thirdweb Payments - Customize sendTransaction - thirdweb",
description:
"thirdweb Universal Bridge- Customizing point of sale onramps and crypto purchase flows",
"thirdweb Payments- Customizing point of sale onramps and crypto purchase flows",
});
# useSendTransaction Customization
Learn how to customize the Universal Bridge interface when executing a transaction with `useSendTransaction`. You can find a selection of popular customizations below. For the full list of props, you can [view the full reference](/references/typescript/v5/SendTransactionConfig).
Learn how to customize the Payments interface when executing a transaction with `useSendTransaction`. You can find a selection of popular customizations below. For the full list of props, you can [view the full reference](/references/typescript/v5/SendTransactionConfig).
---
+18 -18
View File
@@ -2,11 +2,11 @@ import { createMetadata, Details } from "@doc";
export const metadata = createMetadata({
image: {
title: "thirdweb Universal Bridge - FAQs",
title: "thirdweb Payments - FAQs",
icon: "thirdweb",
},
title: "thirdweb Universal Bridge - FAQs - thirdweb",
description: "thirdweb Universal Bridge - Frequently Asked Questions",
title: "thirdweb Payments - FAQs - thirdweb",
description: "thirdweb Payments - Frequently Asked Questions",
});
# FAQs
@@ -16,35 +16,35 @@ thirdweb does not charge any fees for buy with fiat. Any fees on onramping are b
</Details>
<Details summary="What are the fees for Buy With Crypto?">
For buying with crypto or swap fees, thirdweb charges a 0.3% protocol fee. Developers may monetize their application by charging additional fees on swap transactions. To set a fee in your project page, navigate to Universal Bridge > Settings to set this fee.
For buying with crypto or swap fees, thirdweb charges a 0.3% protocol fee. Developers may monetize their application by charging additional fees on swap transactions. To set a fee in your project page, navigate to Payments > Settings to set this fee.
</Details>
<Details summary="What does the term “network fees” refer to?">
Network fees, also referred to as miner fees, are paid to the miner for processing crypto transactions and securing the respective network. These fees do not go to thirdweb.
</Details>
<Details summary="What does Universal Bridge use to operate under the hood?">
Universal Bridge is an asset router that combines thirdweb's own onchain infrastructure with existing bridging and swapping protocols to find the optimal route for any given transaction.
<Details summary="What does Payments use to operate under the hood?">
Payments is an asset router that combines thirdweb's own onchain infrastructure with existing bridging and swapping protocols to find the optimal route for any given transaction.
This can include intents, native bridges, third party bridges, cross-chain messaging protocols, thirdweb's custom bridges, and more. All routes are monitored and planned continuously for
health, price, and speed to guarantee the best path every time.
</Details>
<Details summary="How can I support a token on Universal Bridge?">
Universal Bridge is able to support most token that meet the following criteria:
<Details summary="How can I support a token on Payments?">
Payments is able to support most token that meet the following criteria:
- The token must have sufficient liquidity
- The token must be on a [supported Universal Bridge Chain](https://thirdweb.com/chainlist?service=pay).
- The token must be on a [supported Payments Chain](https://thirdweb.com/chainlist?service=pay).
To request support for a token, please visit the Universal Bridge tab in your project dashboard > Settings > "Don't see your token listed?" component and fill out the chain and token address information.
To request support for a token, please visit the Payments tab in your project dashboard > Settings > "Don't see your token listed?" component and fill out the chain and token address information.
Universal Bridge will automatically kick off the token route discovery process. Please check back the Universal Bridge modal after 20-40 minutes for the requested token.
Payments will automatically kick off the token route discovery process. Please check back the Payments modal after 20-40 minutes for the requested token.
</Details>
<Details summary="Am I able to use Universal Bridge with other wallet infrastructure providers?">
Yes, Universal Bridge is designed to be agnostic to wallet infrastructure. You can use it with any wallet service.
<Details summary="Am I able to use Payments with other wallet infrastructure providers?">
Yes, Payments is designed to be agnostic to wallet infrastructure. You can use it with any wallet service.
</Details>
<Details summary="Does Universal Bridge support Apple Pay or Google Pay?">
<Details summary="Does Payments support Apple Pay or Google Pay?">
Apple Pay and Google Pay are supported through the onramp providers depending on region. Coinbase, Stripe, and Transak currently support Apple Pay. Transak supports Google Pay. [Learn more about the supported onramp providers.](/connect/pay/onramp-providers).
</Details>
@@ -111,15 +111,15 @@ All other tokens will require an additional Buy With Crypto step.
Users will be required to complete KYC verification for their first purchase using 'Buy with Fiat.' After that, no further KYC will be needed for future purchases on the same platform.
</Details>
<Details summary="What cryptocurrencies does Universal Bridge support?">
Universal Bridge can support any currency on our [supported chains](https://thirdweb.com/chainlist?service=pay).
<Details summary="What cryptocurrencies does Payments support?">
Payments can support any currency on our [supported chains](https://thirdweb.com/chainlist?service=pay).
</Details>
<Details summary="How are cryptocurrency prices set in thirdweb Universal Bridge?">
<Details summary="How are cryptocurrency prices set in thirdweb Payments?">
Prices are set depending on the liquidity provider used to route your transaction.
</Details>
<Details summary="Does thirdweb Universal Bridge offer fiat payouts?">
<Details summary="Does thirdweb Payments offer fiat payouts?">
Fiat payouts are not currently available.
</Details>
+3 -3
View File
@@ -4,12 +4,12 @@ import FeeShare from './assets/fee-dash.png';
# Understanding fees
Understand how protocol, developer, and onramp fees work with Universal Bridge.
Understand how protocol, developer, and onramp fees work with Payments.
### Developer Fee
To monetize applications or games, you are able to set a developer fee on each crypto-to-crypto or swap transaction. You may set this percentage
through Project > Universal Bridge > Settings.
through Project > Payments > Settings.
<DocImage src={FeeShare} alt="Developer fee share" />
@@ -49,7 +49,7 @@ Example: The user pays 10.00 USDC.
### Onramp Fee
When onramping to crypto using the Universal Bridge, a fee is charged by the third-party provider. This fee is determined by the provider and is typically a percentage
When onramping to crypto using the Payments, a fee is charged by the third-party provider. This fee is determined by the provider and is typically a percentage
of the amount being onramped. This fee may vary based on the provider and payment method used.
There are no protocol fees on onramp transactions.
+7 -7
View File
@@ -13,17 +13,17 @@ import {
export const metadata = createMetadata({
image: {
title: "thirdweb Universal Bridge - Get Started",
title: "thirdweb Payments - Get Started",
icon: "thirdweb",
},
title: "Universal Bridge Implementation Guide — thirdweb docs",
title: "Payments Implementation Guide — thirdweb docs",
description:
"Learn how to implement cross-chain payments, bridging, and onramps with the Universal Bridge SDK",
"Learn how to implement cross-chain payments, bridging, and onramps with the Payments SDK",
});
# Get Started
Learn how to integrate Universal Bridge into your application for cross-chain payments, bridging, swapping, and fiat onramps. This guide covers the core SDK modules and practical implementation examples.
Learn how to integrate Payments into your application for cross-chain payments, bridging, swapping, and fiat onramps. This guide covers the core SDK modules and practical implementation examples.
---
@@ -39,7 +39,7 @@ Learn how to integrate Universal Bridge into your application for cross-chain pa
</Step>
<Step title="Get Your Client ID">
Log in to [the thirdweb dashboard](https://thirdweb.com/team). Navigate to the **Settings** page and create an API key to get your **Client ID**. You'll need your Client ID to interact with the Universal Bridge.
Log in to [the thirdweb dashboard](https://thirdweb.com/team). Navigate to the **Settings** page and create an API key to get your **Client ID**. You'll need your Client ID to interact with the Payments.
</Step>
<Step title="Initialize the Client">
@@ -231,7 +231,7 @@ console.log(`Found ${filteredRoutes.length} routes`);
## Transaction Status Tracking
Monitor the progress of your Universal Bridge transactions:
Monitor the progress of your Payments transactions:
```typescript
import { Bridge } from "thirdweb";
@@ -264,7 +264,7 @@ switch (status.status) {
## Error Handling
Universal Bridge functions throw `ApiError` for failed requests:
Payments functions throw `ApiError` for failed requests:
```typescript
import { Bridge, ApiError } from "thirdweb";
@@ -10,17 +10,17 @@ import OnrampStepOne from "../../assets/avax-to-usd.png";
export const metadata = createMetadata({
image: {
title: "thirdweb Universal Bridge - Cross-Chain Swapping",
title: "thirdweb Payments - Cross-Chain Swapping",
icon: "thirdweb",
},
title: "thirdweb Universal Bridge - Cross-Chain Swapping | thirdweb",
title: "thirdweb Payments - Cross-Chain Swapping | thirdweb",
description:
"Learn how to build a custom cross-chain swapping experience with thirdweb Universal Bridge.",
"Learn how to build a custom cross-chain swapping experience with thirdweb Payments.",
});
# Leverage Cross-Chain Swaps with Universal Bridge
# Leverage Cross-Chain Swaps with Payments
Learn how to enable your users to swap from any asset to any other with thirdweb's Universal Bridge.
Learn how to enable your users to swap from any asset to any other with thirdweb's Payments.
In this guide, we'll show you how to purchase 10 USDC on Optimism in Typescript.
@@ -13,15 +13,15 @@ import {
export const metadata = createMetadata({
image: {
title: "thirdweb Universal Bridge - NFT Checkout",
title: "thirdweb Payments - NFT Checkout",
icon: "thirdweb",
},
title: "thirdweb Universal Bridge - NFT Checkout | thirdweb",
title: "thirdweb Payments - NFT Checkout | thirdweb",
description:
"Learn how to build a complete NFT checkout flow with in-app wallet login, onramp, and minting using Universal Bridge.",
"Learn how to build a complete NFT checkout flow with in-app wallet login, onramp, and minting using Payments.",
});
# NFT Checkout with Universal Bridge
# NFT Checkout with Payments
Learn how to build a seamless NFT checkout experience that combines in-app wallet authentication, fiat-to-crypto onramps, and NFT minting. This tutorial demonstrates a complete user flow where users can purchase NFTs directly with fiat currency.
@@ -692,7 +692,7 @@ function App() {
<NFTCheckout />
<footer className="text-center mt-8 text-sm text-gray-500">
<p>Powered by thirdweb Universal Bridge</p>
<p>Powered by thirdweb Payments</p>
</footer>
</div>
</div>
@@ -13,19 +13,19 @@ import {
export const metadata = createMetadata({
image: {
title: "thirdweb Universal Bridge - Onramp Integration",
title: "thirdweb Payments - Onramp Integration",
icon: "thirdweb",
},
title: "thirdweb Universal Bridge - Onramp Integration | thirdweb",
title: "thirdweb Payments - Onramp Integration | thirdweb",
description:
"Learn how to integrate fiat-to-crypto onramps using Stripe, Coinbase, and Transak with Universal Bridge.",
"Learn how to integrate fiat-to-crypto onramps using Stripe, Coinbase, and Transak with Payments.",
});
# Fiat-to-Crypto Onramp Integration
Learn how to integrate seamless fiat-to-crypto onramps using Universal Bridge. This guide covers integration with Stripe, Coinbase, and Transak providers, enabling your users to purchase crypto directly with fiat currency.
Learn how to integrate seamless fiat-to-crypto onramps using Payments. This guide covers integration with Stripe, Coinbase, and Transak providers, enabling your users to purchase crypto directly with fiat currency.
Universal Bridge's onramp functionality provides a unified interface across multiple providers, automatic routing to the best rates, and comprehensive status tracking.
Payments's onramp functionality provides a unified interface across multiple providers, automatic routing to the best rates, and comprehensive status tracking.
---
@@ -196,4 +196,4 @@ import { Bridge } from "thirdweb";
- **[Onramp Providers](/pay/onramp-providers)** - Detailed provider comparison and features
- **[Webhooks](/pay/webhooks)** - Set up real-time onramp status notifications
- **[Testing](/pay/testing-pay)** - Test onramp flows in development mode
- **[Universal Bridge API](https://bridge.thirdweb.com/reference)** - Complete API reference
- **[Payments API](https://bridge.thirdweb.com/reference)** - Complete API reference
@@ -9,17 +9,17 @@ import {
export const metadata = createMetadata({
image: {
title: "thirdweb Universal Bridge - Smart Accounts",
title: "thirdweb Payments - Smart Accounts",
icon: "thirdweb",
},
title: "thirdweb Universal Bridge - Smart Accounts | thirdweb",
title: "thirdweb Payments - Smart Accounts | thirdweb",
description:
"Learn how to use EIP7702 smart accounts with batched transactions for seamless Universal Bridge experiences.",
"Learn how to use EIP7702 smart accounts with batched transactions for seamless Payments experiences.",
});
# Smart Accounts with Universal Bridge
# Smart Accounts with Payments
Learn how to leverage EIP7702 smart accounts to batch Universal Bridge transactions for optimal user experience. This approach enables gasless transactions, batched execution, and seamless cross-chain operations.
Learn how to leverage EIP7702 smart accounts to batch Payments transactions for optimal user experience. This approach enables gasless transactions, batched execution, and seamless cross-chain operations.
Smart accounts with EIP7702 allow you to upgrade any EOA (Externally Owned Account) into a smart account without code changes, enabling advanced features like transaction batching and gas sponsorship.
@@ -67,7 +67,7 @@ const account = await wallet.connect({
</Step>
<Step title='Prepare Batched Bridge Transactions'>
Use Universal Bridge to prepare transactions, then batch them per step (since each step may be on a different chain):
Use Payments to prepare transactions, then batch them per step (since each step may be on a different chain):
```typescript
import { Bridge, NATIVE_TOKEN_ADDRESS, toWei } from "thirdweb";
@@ -86,7 +86,7 @@ const bridgePreparation = await Bridge.Buy.prepare({
```
<Callout variant="important">
**Key Rule**: You can only batch transactions that are on the same chain. Since Universal Bridge steps may span multiple chains, you must batch transactions within each step separately, not across steps.
**Key Rule**: You can only batch transactions that are on the same chain. Since Payments steps may span multiple chains, you must batch transactions within each step separately, not across steps.
</Callout>
</Step>
@@ -151,4 +151,4 @@ for (let i = 0; i < bridgePreparation.steps.length; i++) {
- **[EIP7702 Documentation](/connect/account-abstraction/overview)** - Learn more about smart account features
- **[Batching Transactions](/typescript/v5/account-abstraction/batching-transactions)** - Explore advanced batching patterns
- **[Gas Sponsorship](/connect/account-abstraction/sponsoring-gas)** - Set up custom gas sponsorship rules
- **[Universal Bridge API](/pay/webhooks)** - Monitor cross-chain operations with webhooks
- **[Payments API](/pay/webhooks)** - Monitor cross-chain operations with webhooks
+3 -3
View File
@@ -12,10 +12,10 @@ export default async function Layout(props: { children: React.ReactNode }) {
export const metadata = createMetadata({
description:
"thirdweb Universal Bridge documentation. Learn how to use the thirdweb Universal Bridge to send and receive tokens across different blockchains.",
"thirdweb Payments documentation. Learn how to use the thirdweb Payments to send and receive tokens across different blockchains.",
image: {
icon: "nebula",
title: "thirdweb Universal Bridge Docs",
title: "thirdweb Payments Docs",
},
title: "thirdweb Universal Bridge Documentation",
title: "thirdweb Payments Documentation",
});
@@ -2,10 +2,10 @@ import { createMetadata } from "@doc";
export const metadata = createMetadata({
image: {
title: "thirdweb Universal Bridge - Onramp Providers",
title: "thirdweb Payments - Onramp Providers",
icon: "thirdweb",
},
title: "thirdweb Universal Bridge - Onramp Providers | thirdweb",
title: "thirdweb Payments - Onramp Providers | thirdweb",
description:
"Integrate onramp providers for any onchain transaction and set preferred providers",
});
@@ -13,7 +13,7 @@ export const metadata = createMetadata({
# Onramp Providers
**Onramp Providers** are services that allow users to buy crypto with fiat. You can set preferred providers for your application by using the [`preferredProvider`](/connect/pay/customization/payembed#preferred-provider) parameter in the `PayEmbed` component.
Universal Bridge supports the following onramp providers:
Payments supports the following onramp providers:
- **Coinbase**
- **Transak**
+9 -7
View File
@@ -5,17 +5,19 @@ import {RocketIcon, ArrowLeftRightIcon, WalletIcon, PencilIcon, ShieldCheckIcon,
export const metadata = createMetadata({
image: {
title: "thirdweb Universal Bridge",
title: "thirdweb Payments",
icon: "thirdweb",
},
title: "thirdweb Universal Bridge Docs: Web3 Payments, On-ramping, bridging & swapping",
title: "thirdweb Payments Docs: Web3 Payments, On-ramping, bridging & swapping",
description:
"Learn everything about thirdweb's web3 payments solution, Universal Bridge. Technical docs on onramping, bridging + swapping.",
"Learn everything about thirdweb's web3 payments solution, Payments. Technical docs on onramping, bridging + swapping.",
});
# Universal Bridge
# Payments
Universal Bridge allows you to create both simple and advanced payment flows for bridging, swapping, onramping, and peer-to-peer purchases. It's been used to drive millions in NFT sales, bridge native tokens to brand new chains, send stablecoins between users, and more. To get started check out the [SDK functions](https://portal.thirdweb.com/typescript/v5/buy/quote), [API reference](https://bridge.thirdweb.com/reference), or [playground](https://playground.thirdweb.com/connect/pay).
Payments allows you to create both simple and advanced payment flows for bridging, swapping, onramping, and peer-to-peer purchases. It's been used to drive millions in NFT sales, bridge native tokens to brand new chains, send stablecoins between users, and more.
To get started check out the [SDK functions](https://portal.thirdweb.com/typescript/v5/buy/quote), [API reference](https://bridge.thirdweb.com/reference), or [playground](https://playground.thirdweb.com/connect/pay).
<DocImage src={PayOverviewImage} />
@@ -64,7 +66,7 @@ Universal Bridge allows you to create both simple and advanced payment flows for
## SDK Modules
The Universal Bridge SDK is organized into several modules, each handling specific functionality:
The Payments SDK is organized into several modules, each handling specific functionality:
| Module | Purpose | Key Functions |
|--------|---------|---------------|
@@ -77,7 +79,7 @@ The Universal Bridge SDK is organized into several modules, each handling specif
## Supported Chains
Universal Bridge is supported on select EVM compatible chains. To view the full list, visit [thirdweb chainlist](https://thirdweb.com/chainlist?service=pay).
Payments is supported on select EVM compatible chains. To view the full list, visit [thirdweb chainlist](https://thirdweb.com/chainlist?service=pay).
<DocImage src={SupportedChains} />
+1 -1
View File
@@ -127,5 +127,5 @@ export const sidebar: SideBar = {
name: "FAQs",
},
],
name: "Universal Bridge",
name: "Payments",
};
@@ -2,15 +2,15 @@ import { createMetadata } from "@doc";
export const metadata = createMetadata({
image: {
title: "thirdweb Universal Bridge - Supported Chains",
title: "thirdweb Payments - Supported Chains",
icon: "thirdweb",
},
title: "thirdweb Universal Bridge - Supported Chains | thirdweb",
description: "List of chains supported by Universal Bridge.",
title: "thirdweb Payments - Supported Chains | thirdweb",
description: "List of chains supported by Payments.",
});
# Supported Chains
[View all supported chains with Universal Bridge](https://thirdweb.com/chainlist?service=pay).
[View all supported chains with Payments](https://thirdweb.com/chainlist?service=pay).
If you’d like to suggest a chain for us to support, [contact us](https://thirdweb.com/contact-us).
@@ -1,6 +1,6 @@
import {Details} from "@doc";
# Universal Bridge Troubleshoot Articles
# Payments Troubleshoot Articles
<Details summary="Your card does not support this type of purchase.">
When using onramp providers, you may encounter an error stating "There was an issue processing your payment and you have not been charged. Your card does not support this type of purchase."
@@ -12,7 +12,7 @@ To work around this issue, you can try using a different card or payment method,
<Details summary="Error 500: Internal Server Error">
If you encounter an "Error 500: Internal Server Error" or if the modal shows "Something went wrong" it may indicate you are using an
outdated version of the Universal Bridge service.
outdated version of the Payments service.
To resolve this issue, please upgrade your SDK to the latest version in your project. You can download the latest version with `npm install thirdweb@latest` or `yarn add thirdweb@latest`.
+3 -3
View File
@@ -2,9 +2,9 @@ import { createMetadata, DocImage, Callout } from "@doc";
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
export const metadata = createMetadata({
title: "Universal Bridge Webhooks Implementation Guide — thirdweb Docs",
title: "Payments Webhooks Implementation Guide — thirdweb Docs",
description:
"Learn how to set up webhooks for any Universal Bridge transactions.",
"Learn how to set up webhooks for any Payments transactions.",
});
# Webhooks
@@ -13,7 +13,7 @@ export const metadata = createMetadata({
**When using webhooks to distribute purchased items, always verify that the proper amount was paid to the proper address onchain using the provided transaction hash. Do not make any assumptions regarding onchain data based on webhook payloads.**
</Callout>
You can create a webhook in your project dashboard under the Universal Bridge tab. You'll be prompted to copy a secret key before saving the webhook. This will be used for verification on all webhook requests received by your backend.
You can create a webhook in your project dashboard under the Payments tab. You'll be prompted to copy a secret key before saving the webhook. This will be used for verification on all webhook requests received by your backend.
## Response Objects
+1 -1
View File
@@ -1,4 +1,4 @@
{
"$schema": "https://biomejs.dev/schemas/2.0.0/schema.json",
"$schema": "https://biomejs.dev/schemas/2.0.4/schema.json",
"extends": "//"
}
+1 -1
View File
@@ -1,5 +1,5 @@
{
"$schema": "https://biomejs.dev/schemas/2.0.0/schema.json",
"$schema": "https://biomejs.dev/schemas/2.0.4/schema.json",
"assist": {
"actions": {
"source": {
+6
View File
@@ -1,5 +1,11 @@
# @thirdweb-dev/engine
## 3.2.0
### Minor Changes
- [#7427](https://github.com/thirdweb-dev/js/pull/7427) [`f9d7935`](https://github.com/thirdweb-dev/js/commit/f9d7935d848cbb2dea3f5204d5bff69cd0c3a921) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - New engine v3 APIs - see changelog for breaking changes
## 3.1.0
### Minor Changes
+15 -2
View File
@@ -1,4 +1,17 @@
{
"$schema": "https://biomejs.dev/schemas/2.0.0/schema.json",
"extends": "//"
"$schema": "https://biomejs.dev/schemas/2.0.4/schema.json",
"linter": {
"rules": {
"correctness": {
"useImportExtensions": {
"fix": "safe",
"level": "error",
"options": {
"forceJsExtensions": true
}
}
}
}
},
"root": true
}
-1
View File
@@ -3,5 +3,4 @@ import { defineConfig } from "@hey-api/openapi-ts";
export default defineConfig({
input: "https://engine.thirdweb.com/openapi",
output: { format: "biome", lint: "biome", path: "src/client" },
plugins: ["@hey-api/client-fetch"],
});
+15 -12
View File
@@ -1,20 +1,26 @@
{
"name": "@thirdweb-dev/engine",
"version": "3.1.0",
"version": "3.2.0",
"repository": {
"type": "git",
"url": "git+https://github.com/thirdweb-dev/js.git#main"
},
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/thirdweb-dev/js/issues"
},
"author": "thirdweb eng <[email protected]>",
"type": "module",
"main": "./dist/cjs/exports/thirdweb.js",
"module": "./dist/esm/exports/thirdweb.js",
"types": "./dist/types/exports/thirdweb.d.ts",
"typings": "./dist/types/exports/thirdweb.d.ts",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/thirdweb-dev/js/issues"
},
"dependencies": {
"@hey-api/client-fetch": "0.10.0"
},
"engines": {
"node": ">=18"
},
"exports": {
".": {
"types": "./dist/types/exports/thirdweb.d.ts",
@@ -42,17 +48,14 @@
}
},
"scripts": {
"format": "biome format ./src --write",
"lint": "biome check ./src",
"fix": "biome check ./src --fix",
"build": "pnpm clean && pnpm build:cjs && pnpm build:esm && pnpm build:types",
"build:cjs": "tsc --project ./tsconfig.build.json --module commonjs --outDir ./dist/cjs --verbatimModuleSyntax false && printf '{\"type\":\"commonjs\"}' > ./dist/cjs/package.json",
"build:esm": "tsc --project ./tsconfig.build.json --module es2020 --outDir ./dist/esm && printf '{\"type\": \"module\",\"sideEffects\":false}' > ./dist/esm/package.json",
"build:generate": "openapi-ts && pnpm format && pnpm fix",
"build:types": "tsc --project ./tsconfig.build.json --module esnext --declarationDir ./dist/types --emitDeclarationOnly --declaration --declarationMap",
"clean": "rimraf dist",
"build:generate": "openapi-ts && pnpm format"
},
"engines": {
"node": ">=18"
"fix": "biome check --write ./src",
"format": "biome format --write ./src",
"lint": "biome check ./src"
}
}
+10 -10
View File
@@ -1,10 +1,10 @@
// This file is auto-generated by @hey-api/openapi-ts
import {
type Config,
createClient,
createConfig,
type ClientOptions as DefaultClientOptions,
type Config,
createClient,
createConfig,
type ClientOptions as DefaultClientOptions,
} from "./client/index.js";
import type { ClientOptions } from "./types.gen.js";
@@ -17,12 +17,12 @@ import type { ClientOptions } from "./types.gen.js";
* to ensure your client always has the correct values.
*/
export type CreateClientConfig<T extends DefaultClientOptions = ClientOptions> =
(
override?: Config<DefaultClientOptions & T>,
) => Config<Required<DefaultClientOptions> & T>;
(
override?: Config<DefaultClientOptions & T>,
) => Config<Required<DefaultClientOptions> & T>;
export const client = createClient(
createConfig<ClientOptions>({
baseUrl: "https://engine.thirdweb.com",
}),
createConfig<ClientOptions>({
baseUrl: "https://engine.thirdweb.com",
}),
);
+153 -153
View File
@@ -1,189 +1,189 @@
import type { Client, Config, RequestOptions } from "./types.js";
import {
buildUrl,
createConfig,
createInterceptors,
getParseAs,
mergeConfigs,
mergeHeaders,
setAuthParams,
buildUrl,
createConfig,
createInterceptors,
getParseAs,
mergeConfigs,
mergeHeaders,
setAuthParams,
} from "./utils.js";
type ReqInit = Omit<RequestInit, "body" | "headers"> & {
body?: any;
headers: ReturnType<typeof mergeHeaders>;
body?: any;
headers: ReturnType<typeof mergeHeaders>;
};
export const createClient = (config: Config = {}): Client => {
let _config = mergeConfigs(createConfig(), config);
let _config = mergeConfigs(createConfig(), config);
const getConfig = (): Config => ({ ..._config });
const getConfig = (): Config => ({ ..._config });
const setConfig = (config: Config): Config => {
_config = mergeConfigs(_config, config);
return getConfig();
};
const setConfig = (config: Config): Config => {
_config = mergeConfigs(_config, config);
return getConfig();
};
const interceptors = createInterceptors<
Request,
Response,
unknown,
RequestOptions
>();
const interceptors = createInterceptors<
Request,
Response,
unknown,
RequestOptions
>();
const request: Client["request"] = async (options) => {
const opts = {
..._config,
...options,
fetch: options.fetch ?? _config.fetch ?? globalThis.fetch,
headers: mergeHeaders(_config.headers, options.headers),
};
const request: Client["request"] = async (options) => {
const opts = {
..._config,
...options,
fetch: options.fetch ?? _config.fetch ?? globalThis.fetch,
headers: mergeHeaders(_config.headers, options.headers),
};
if (opts.security) {
await setAuthParams({
...opts,
security: opts.security,
});
}
if (opts.security) {
await setAuthParams({
...opts,
security: opts.security,
});
}
if (opts.body && opts.bodySerializer) {
opts.body = opts.bodySerializer(opts.body);
}
if (opts.body && opts.bodySerializer) {
opts.body = opts.bodySerializer(opts.body);
}
// remove Content-Type header if body is empty to avoid sending invalid requests
if (opts.body === undefined || opts.body === "") {
opts.headers.delete("Content-Type");
}
// remove Content-Type header if body is empty to avoid sending invalid requests
if (opts.body === undefined || opts.body === "") {
opts.headers.delete("Content-Type");
}
const url = buildUrl(opts);
const requestInit: ReqInit = {
redirect: "follow",
...opts,
};
const url = buildUrl(opts);
const requestInit: ReqInit = {
redirect: "follow",
...opts,
};
let request = new Request(url, requestInit);
let request = new Request(url, requestInit);
for (const fn of interceptors.request._fns) {
if (fn) {
request = await fn(request, opts);
}
}
for (const fn of interceptors.request._fns) {
if (fn) {
request = await fn(request, opts);
}
}
// fetch must be assigned here, otherwise it would throw the error:
// TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation
const _fetch = opts.fetch!;
let response = await _fetch(request);
// fetch must be assigned here, otherwise it would throw the error:
// TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation
const _fetch = opts.fetch!;
let response = await _fetch(request);
for (const fn of interceptors.response._fns) {
if (fn) {
response = await fn(response, request, opts);
}
}
for (const fn of interceptors.response._fns) {
if (fn) {
response = await fn(response, request, opts);
}
}
const result = {
request,
response,
};
const result = {
request,
response,
};
if (response.ok) {
if (
response.status === 204 ||
response.headers.get("Content-Length") === "0"
) {
return opts.responseStyle === "data"
? {}
: {
data: {},
...result,
};
}
if (response.ok) {
if (
response.status === 204 ||
response.headers.get("Content-Length") === "0"
) {
return opts.responseStyle === "data"
? {}
: {
data: {},
...result,
};
}
const parseAs =
(opts.parseAs === "auto"
? getParseAs(response.headers.get("Content-Type"))
: opts.parseAs) ?? "json";
const parseAs =
(opts.parseAs === "auto"
? getParseAs(response.headers.get("Content-Type"))
: opts.parseAs) ?? "json";
let data: any;
switch (parseAs) {
case "arrayBuffer":
case "blob":
case "formData":
case "json":
case "text":
data = await response[parseAs]();
break;
case "stream":
return opts.responseStyle === "data"
? response.body
: {
data: response.body,
...result,
};
}
let data: any;
switch (parseAs) {
case "arrayBuffer":
case "blob":
case "formData":
case "json":
case "text":
data = await response[parseAs]();
break;
case "stream":
return opts.responseStyle === "data"
? response.body
: {
data: response.body,
...result,
};
}
if (parseAs === "json") {
if (opts.responseValidator) {
await opts.responseValidator(data);
}
if (parseAs === "json") {
if (opts.responseValidator) {
await opts.responseValidator(data);
}
if (opts.responseTransformer) {
data = await opts.responseTransformer(data);
}
}
if (opts.responseTransformer) {
data = await opts.responseTransformer(data);
}
}
return opts.responseStyle === "data"
? data
: {
data,
...result,
};
}
return opts.responseStyle === "data"
? data
: {
data,
...result,
};
}
let error = await response.text();
let error = await response.text();
try {
error = JSON.parse(error);
} catch {
// noop
}
try {
error = JSON.parse(error);
} catch {
// noop
}
let finalError = error;
let finalError = error;
for (const fn of interceptors.error._fns) {
if (fn) {
finalError = (await fn(error, response, request, opts)) as string;
}
}
for (const fn of interceptors.error._fns) {
if (fn) {
finalError = (await fn(error, response, request, opts)) as string;
}
}
finalError = finalError || ({} as string);
finalError = finalError || ({} as string);
if (opts.throwOnError) {
throw finalError;
}
if (opts.throwOnError) {
throw finalError;
}
// TODO: we probably want to return error and improve types
return opts.responseStyle === "data"
? undefined
: {
error: finalError,
...result,
};
};
// TODO: we probably want to return error and improve types
return opts.responseStyle === "data"
? undefined
: {
error: finalError,
...result,
};
};
return {
buildUrl,
connect: (options) => request({ ...options, method: "CONNECT" }),
delete: (options) => request({ ...options, method: "DELETE" }),
get: (options) => request({ ...options, method: "GET" }),
getConfig,
head: (options) => request({ ...options, method: "HEAD" }),
interceptors,
options: (options) => request({ ...options, method: "OPTIONS" }),
patch: (options) => request({ ...options, method: "PATCH" }),
post: (options) => request({ ...options, method: "POST" }),
put: (options) => request({ ...options, method: "PUT" }),
request,
setConfig,
trace: (options) => request({ ...options, method: "TRACE" }),
};
return {
buildUrl,
connect: (options) => request({ ...options, method: "CONNECT" }),
delete: (options) => request({ ...options, method: "DELETE" }),
get: (options) => request({ ...options, method: "GET" }),
getConfig,
head: (options) => request({ ...options, method: "HEAD" }),
interceptors,
options: (options) => request({ ...options, method: "OPTIONS" }),
patch: (options) => request({ ...options, method: "PATCH" }),
post: (options) => request({ ...options, method: "POST" }),
put: (options) => request({ ...options, method: "PUT" }),
request,
setConfig,
trace: (options) => request({ ...options, method: "TRACE" }),
};
};
+13 -13
View File
@@ -1,22 +1,22 @@
export type { Auth } from "../core/auth.js";
export type { QuerySerializerOptions } from "../core/bodySerializer.js";
export {
formDataBodySerializer,
jsonBodySerializer,
urlSearchParamsBodySerializer,
formDataBodySerializer,
jsonBodySerializer,
urlSearchParamsBodySerializer,
} from "../core/bodySerializer.js";
export { buildClientParams } from "../core/params.js";
export { createClient } from "./client.js";
export type {
Client,
ClientOptions,
Config,
CreateClientConfig,
Options,
OptionsLegacyParser,
RequestOptions,
RequestResult,
ResponseStyle,
TDataShape,
Client,
ClientOptions,
Config,
CreateClientConfig,
Options,
OptionsLegacyParser,
RequestOptions,
RequestResult,
ResponseStyle,
TDataShape,
} from "./types.js";
export { createConfig, mergeHeaders } from "./utils.js";
+165 -165
View File
@@ -1,169 +1,169 @@
import type { Auth } from "../core/auth.js";
import type {
Client as CoreClient,
Config as CoreConfig,
Client as CoreClient,
Config as CoreConfig,
} from "../core/types.js";
import type { Middleware } from "./utils.js";
export type ResponseStyle = "data" | "fields";
export interface Config<T extends ClientOptions = ClientOptions>
extends Omit<RequestInit, "body" | "headers" | "method">,
CoreConfig {
/**
* Base URL for all requests made by this client.
*/
baseUrl?: T["baseUrl"];
/**
* Fetch API implementation. You can use this option to provide a custom
* fetch instance.
*
* @default globalThis.fetch
*/
fetch?: (request: Request) => ReturnType<typeof fetch>;
/**
* Please don't use the Fetch client for Next.js applications. The `next`
* options won't have any effect.
*
* Install {@link https://www.npmjs.com/package/@hey-api/client-next `@hey-api/client-next`} instead.
*/
next?: never;
/**
* Return the response data parsed in a specified format. By default, `auto`
* will infer the appropriate method from the `Content-Type` response header.
* You can override this behavior with any of the {@link Body} methods.
* Select `stream` if you don't want to parse response data at all.
*
* @default 'auto'
*/
parseAs?:
| "arrayBuffer"
| "auto"
| "blob"
| "formData"
| "json"
| "stream"
| "text";
/**
* Should we return only data or multiple fields (data, error, response, etc.)?
*
* @default 'fields'
*/
responseStyle?: ResponseStyle;
/**
* Throw an error instead of returning it in the response?
*
* @default false
*/
throwOnError?: T["throwOnError"];
extends Omit<RequestInit, "body" | "headers" | "method">,
CoreConfig {
/**
* Base URL for all requests made by this client.
*/
baseUrl?: T["baseUrl"];
/**
* Fetch API implementation. You can use this option to provide a custom
* fetch instance.
*
* @default globalThis.fetch
*/
fetch?: (request: Request) => ReturnType<typeof fetch>;
/**
* Please don't use the Fetch client for Next.js applications. The `next`
* options won't have any effect.
*
* Install {@link https://www.npmjs.com/package/@hey-api/client-next `@hey-api/client-next`} instead.
*/
next?: never;
/**
* Return the response data parsed in a specified format. By default, `auto`
* will infer the appropriate method from the `Content-Type` response header.
* You can override this behavior with any of the {@link Body} methods.
* Select `stream` if you don't want to parse response data at all.
*
* @default 'auto'
*/
parseAs?:
| "arrayBuffer"
| "auto"
| "blob"
| "formData"
| "json"
| "stream"
| "text";
/**
* Should we return only data or multiple fields (data, error, response, etc.)?
*
* @default 'fields'
*/
responseStyle?: ResponseStyle;
/**
* Throw an error instead of returning it in the response?
*
* @default false
*/
throwOnError?: T["throwOnError"];
}
export interface RequestOptions<
TResponseStyle extends ResponseStyle = "fields",
ThrowOnError extends boolean = boolean,
Url extends string = string,
TResponseStyle extends ResponseStyle = "fields",
ThrowOnError extends boolean = boolean,
Url extends string = string,
> extends Config<{
responseStyle: TResponseStyle;
throwOnError: ThrowOnError;
}> {
/**
* Any body that you want to add to your request.
*
* {@link https://developer.mozilla.org/docs/Web/API/fetch#body}
*/
body?: unknown;
path?: Record<string, unknown>;
query?: Record<string, unknown>;
/**
* Security mechanism(s) to use for the request.
*/
security?: ReadonlyArray<Auth>;
url: Url;
responseStyle: TResponseStyle;
throwOnError: ThrowOnError;
}> {
/**
* Any body that you want to add to your request.
*
* {@link https://developer.mozilla.org/docs/Web/API/fetch#body}
*/
body?: unknown;
path?: Record<string, unknown>;
query?: Record<string, unknown>;
/**
* Security mechanism(s) to use for the request.
*/
security?: ReadonlyArray<Auth>;
url: Url;
}
export type RequestResult<
TData = unknown,
TError = unknown,
ThrowOnError extends boolean = boolean,
TResponseStyle extends ResponseStyle = "fields",
TData = unknown,
TError = unknown,
ThrowOnError extends boolean = boolean,
TResponseStyle extends ResponseStyle = "fields",
> = ThrowOnError extends true
? Promise<
TResponseStyle extends "data"
? TData extends Record<string, unknown>
? TData[keyof TData]
: TData
: {
data: TData extends Record<string, unknown>
? TData[keyof TData]
: TData;
request: Request;
response: Response;
}
>
: Promise<
TResponseStyle extends "data"
?
| (TData extends Record<string, unknown>
? TData[keyof TData]
: TData)
| undefined
: (
| {
data: TData extends Record<string, unknown>
? TData[keyof TData]
: TData;
error: undefined;
}
| {
data: undefined;
error: TError extends Record<string, unknown>
? TError[keyof TError]
: TError;
}
) & {
request: Request;
response: Response;
}
>;
? Promise<
TResponseStyle extends "data"
? TData extends Record<string, unknown>
? TData[keyof TData]
: TData
: {
data: TData extends Record<string, unknown>
? TData[keyof TData]
: TData;
request: Request;
response: Response;
}
>
: Promise<
TResponseStyle extends "data"
?
| (TData extends Record<string, unknown>
? TData[keyof TData]
: TData)
| undefined
: (
| {
data: TData extends Record<string, unknown>
? TData[keyof TData]
: TData;
error: undefined;
}
| {
data: undefined;
error: TError extends Record<string, unknown>
? TError[keyof TError]
: TError;
}
) & {
request: Request;
response: Response;
}
>;
export interface ClientOptions {
baseUrl?: string;
responseStyle?: ResponseStyle;
throwOnError?: boolean;
baseUrl?: string;
responseStyle?: ResponseStyle;
throwOnError?: boolean;
}
type MethodFn = <
TData = unknown,
TError = unknown,
ThrowOnError extends boolean = false,
TResponseStyle extends ResponseStyle = "fields",
TData = unknown,
TError = unknown,
ThrowOnError extends boolean = false,
TResponseStyle extends ResponseStyle = "fields",
>(
options: Omit<RequestOptions<TResponseStyle, ThrowOnError>, "method">,
options: Omit<RequestOptions<TResponseStyle, ThrowOnError>, "method">,
) => RequestResult<TData, TError, ThrowOnError, TResponseStyle>;
type RequestFn = <
TData = unknown,
TError = unknown,
ThrowOnError extends boolean = false,
TResponseStyle extends ResponseStyle = "fields",
TData = unknown,
TError = unknown,
ThrowOnError extends boolean = false,
TResponseStyle extends ResponseStyle = "fields",
>(
options: Omit<RequestOptions<TResponseStyle, ThrowOnError>, "method"> &
Pick<Required<RequestOptions<TResponseStyle, ThrowOnError>>, "method">,
options: Omit<RequestOptions<TResponseStyle, ThrowOnError>, "method"> &
Pick<Required<RequestOptions<TResponseStyle, ThrowOnError>>, "method">,
) => RequestResult<TData, TError, ThrowOnError, TResponseStyle>;
type BuildUrlFn = <
TData extends {
body?: unknown;
path?: Record<string, unknown>;
query?: Record<string, unknown>;
url: string;
},
TData extends {
body?: unknown;
path?: Record<string, unknown>;
query?: Record<string, unknown>;
url: string;
},
>(
options: Pick<TData, "url"> & Options<TData>,
options: Pick<TData, "url"> & Options<TData>,
) => string;
export type Client = CoreClient<RequestFn, Config, MethodFn, BuildUrlFn> & {
interceptors: Middleware<Request, Response, unknown, RequestOptions>;
interceptors: Middleware<Request, Response, unknown, RequestOptions>;
};
/**
@@ -175,48 +175,48 @@ export type Client = CoreClient<RequestFn, Config, MethodFn, BuildUrlFn> & {
* to ensure your client always has the correct values.
*/
export type CreateClientConfig<T extends ClientOptions = ClientOptions> = (
override?: Config<ClientOptions & T>,
override?: Config<ClientOptions & T>,
) => Config<Required<ClientOptions> & T>;
export interface TDataShape {
body?: unknown;
headers?: unknown;
path?: unknown;
query?: unknown;
url: string;
body?: unknown;
headers?: unknown;
path?: unknown;
query?: unknown;
url: string;
}
type OmitKeys<T, K> = Pick<T, Exclude<keyof T, K>>;
export type Options<
TData extends TDataShape = TDataShape,
ThrowOnError extends boolean = boolean,
TResponseStyle extends ResponseStyle = "fields",
TData extends TDataShape = TDataShape,
ThrowOnError extends boolean = boolean,
TResponseStyle extends ResponseStyle = "fields",
> = OmitKeys<
RequestOptions<TResponseStyle, ThrowOnError>,
"body" | "path" | "query" | "url"
RequestOptions<TResponseStyle, ThrowOnError>,
"body" | "path" | "query" | "url"
> &
Omit<TData, "url">;
Omit<TData, "url">;
export type OptionsLegacyParser<
TData = unknown,
ThrowOnError extends boolean = boolean,
TResponseStyle extends ResponseStyle = "fields",
TData = unknown,
ThrowOnError extends boolean = boolean,
TResponseStyle extends ResponseStyle = "fields",
> = TData extends { body?: any }
? TData extends { headers?: any }
? OmitKeys<
RequestOptions<TResponseStyle, ThrowOnError>,
"body" | "headers" | "url"
> &
TData
: OmitKeys<RequestOptions<TResponseStyle, ThrowOnError>, "body" | "url"> &
TData &
Pick<RequestOptions<TResponseStyle, ThrowOnError>, "headers">
: TData extends { headers?: any }
? OmitKeys<
RequestOptions<TResponseStyle, ThrowOnError>,
"headers" | "url"
> &
TData &
Pick<RequestOptions<TResponseStyle, ThrowOnError>, "body">
: OmitKeys<RequestOptions<TResponseStyle, ThrowOnError>, "url"> & TData;
? TData extends { headers?: any }
? OmitKeys<
RequestOptions<TResponseStyle, ThrowOnError>,
"body" | "headers" | "url"
> &
TData
: OmitKeys<RequestOptions<TResponseStyle, ThrowOnError>, "body" | "url"> &
TData &
Pick<RequestOptions<TResponseStyle, ThrowOnError>, "headers">
: TData extends { headers?: any }
? OmitKeys<
RequestOptions<TResponseStyle, ThrowOnError>,
"headers" | "url"
> &
TData &
Pick<RequestOptions<TResponseStyle, ThrowOnError>, "body">
: OmitKeys<RequestOptions<TResponseStyle, ThrowOnError>, "url"> & TData;
+308 -308
View File
@@ -1,19 +1,19 @@
import { getAuthToken } from "../core/auth.js";
import type {
QuerySerializer,
QuerySerializerOptions,
QuerySerializer,
QuerySerializerOptions,
} from "../core/bodySerializer.js";
import { jsonBodySerializer } from "../core/bodySerializer.js";
import {
serializeArrayParam,
serializeObjectParam,
serializePrimitiveParam,
serializeArrayParam,
serializeObjectParam,
serializePrimitiveParam,
} from "../core/pathSerializer.js";
import type { Client, ClientOptions, Config, RequestOptions } from "./types.js";
interface PathSerializer {
path: Record<string, unknown>;
url: string;
path: Record<string, unknown>;
url: string;
}
const PATH_PARAM_RE = /\{[^{}]+\}/g;
@@ -23,395 +23,395 @@ type MatrixStyle = "label" | "matrix" | "simple";
type ArraySeparatorStyle = ArrayStyle | MatrixStyle;
const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => {
let url = _url;
const matches = _url.match(PATH_PARAM_RE);
if (matches) {
for (const match of matches) {
let explode = false;
let name = match.substring(1, match.length - 1);
let style: ArraySeparatorStyle = "simple";
let url = _url;
const matches = _url.match(PATH_PARAM_RE);
if (matches) {
for (const match of matches) {
let explode = false;
let name = match.substring(1, match.length - 1);
let style: ArraySeparatorStyle = "simple";
if (name.endsWith("*")) {
explode = true;
name = name.substring(0, name.length - 1);
}
if (name.endsWith("*")) {
explode = true;
name = name.substring(0, name.length - 1);
}
if (name.startsWith(".")) {
name = name.substring(1);
style = "label";
} else if (name.startsWith(";")) {
name = name.substring(1);
style = "matrix";
}
if (name.startsWith(".")) {
name = name.substring(1);
style = "label";
} else if (name.startsWith(";")) {
name = name.substring(1);
style = "matrix";
}
const value = path[name];
const value = path[name];
if (value === undefined || value === null) {
continue;
}
if (value === undefined || value === null) {
continue;
}
if (Array.isArray(value)) {
url = url.replace(
match,
serializeArrayParam({ explode, name, style, value }),
);
continue;
}
if (Array.isArray(value)) {
url = url.replace(
match,
serializeArrayParam({ explode, name, style, value }),
);
continue;
}
if (typeof value === "object") {
url = url.replace(
match,
serializeObjectParam({
explode,
name,
style,
value: value as Record<string, unknown>,
valueOnly: true,
}),
);
continue;
}
if (typeof value === "object") {
url = url.replace(
match,
serializeObjectParam({
explode,
name,
style,
value: value as Record<string, unknown>,
valueOnly: true,
}),
);
continue;
}
if (style === "matrix") {
url = url.replace(
match,
`;${serializePrimitiveParam({
name,
value: value as string,
})}`,
);
continue;
}
if (style === "matrix") {
url = url.replace(
match,
`;${serializePrimitiveParam({
name,
value: value as string,
})}`,
);
continue;
}
const replaceValue = encodeURIComponent(
style === "label" ? `.${value as string}` : (value as string),
);
url = url.replace(match, replaceValue);
}
}
return url;
const replaceValue = encodeURIComponent(
style === "label" ? `.${value as string}` : (value as string),
);
url = url.replace(match, replaceValue);
}
}
return url;
};
export const createQuerySerializer = <T = unknown>({
allowReserved,
array,
object,
allowReserved,
array,
object,
}: QuerySerializerOptions = {}) => {
const querySerializer = (queryParams: T) => {
const search: string[] = [];
if (queryParams && typeof queryParams === "object") {
for (const name in queryParams) {
const value = queryParams[name];
const querySerializer = (queryParams: T) => {
const search: string[] = [];
if (queryParams && typeof queryParams === "object") {
for (const name in queryParams) {
const value = queryParams[name];
if (value === undefined || value === null) {
continue;
}
if (value === undefined || value === null) {
continue;
}
if (Array.isArray(value)) {
const serializedArray = serializeArrayParam({
allowReserved,
explode: true,
name,
style: "form",
value,
...array,
});
if (serializedArray) search.push(serializedArray);
} else if (typeof value === "object") {
const serializedObject = serializeObjectParam({
allowReserved,
explode: true,
name,
style: "deepObject",
value: value as Record<string, unknown>,
...object,
});
if (serializedObject) search.push(serializedObject);
} else {
const serializedPrimitive = serializePrimitiveParam({
allowReserved,
name,
value: value as string,
});
if (serializedPrimitive) search.push(serializedPrimitive);
}
}
}
return search.join("&");
};
return querySerializer;
if (Array.isArray(value)) {
const serializedArray = serializeArrayParam({
allowReserved,
explode: true,
name,
style: "form",
value,
...array,
});
if (serializedArray) search.push(serializedArray);
} else if (typeof value === "object") {
const serializedObject = serializeObjectParam({
allowReserved,
explode: true,
name,
style: "deepObject",
value: value as Record<string, unknown>,
...object,
});
if (serializedObject) search.push(serializedObject);
} else {
const serializedPrimitive = serializePrimitiveParam({
allowReserved,
name,
value: value as string,
});
if (serializedPrimitive) search.push(serializedPrimitive);
}
}
}
return search.join("&");
};
return querySerializer;
};
/**
* Infers parseAs value from provided Content-Type header.
*/
export const getParseAs = (
contentType: string | null,
contentType: string | null,
): Exclude<Config["parseAs"], "auto"> => {
if (!contentType) {
// If no Content-Type header is provided, the best we can do is return the raw response body,
// which is effectively the same as the 'stream' option.
return "stream";
}
if (!contentType) {
// If no Content-Type header is provided, the best we can do is return the raw response body,
// which is effectively the same as the 'stream' option.
return "stream";
}
const cleanContent = contentType.split(";")[0]?.trim();
const cleanContent = contentType.split(";")[0]?.trim();
if (!cleanContent) {
return;
}
if (!cleanContent) {
return;
}
if (
cleanContent.startsWith("application/json") ||
cleanContent.endsWith("+json")
) {
return "json";
}
if (
cleanContent.startsWith("application/json") ||
cleanContent.endsWith("+json")
) {
return "json";
}
if (cleanContent === "multipart/form-data") {
return "formData";
}
if (cleanContent === "multipart/form-data") {
return "formData";
}
if (
["application/", "audio/", "image/", "video/"].some((type) =>
cleanContent.startsWith(type),
)
) {
return "blob";
}
if (
["application/", "audio/", "image/", "video/"].some((type) =>
cleanContent.startsWith(type),
)
) {
return "blob";
}
if (cleanContent.startsWith("text/")) {
return "text";
}
if (cleanContent.startsWith("text/")) {
return "text";
}
return;
return;
};
export const setAuthParams = async ({
security,
...options
security,
...options
}: Pick<Required<RequestOptions>, "security"> &
Pick<RequestOptions, "auth" | "query"> & {
headers: Headers;
}) => {
for (const auth of security) {
const token = await getAuthToken(auth, options.auth);
Pick<RequestOptions, "auth" | "query"> & {
headers: Headers;
}) => {
for (const auth of security) {
const token = await getAuthToken(auth, options.auth);
if (!token) {
continue;
}
if (!token) {
continue;
}
const name = auth.name ?? "Authorization";
const name = auth.name ?? "Authorization";
switch (auth.in) {
case "query":
if (!options.query) {
options.query = {};
}
options.query[name] = token;
break;
case "cookie":
options.headers.append("Cookie", `${name}=${token}`);
break;
case "header":
default:
options.headers.set(name, token);
break;
}
switch (auth.in) {
case "query":
if (!options.query) {
options.query = {};
}
options.query[name] = token;
break;
case "cookie":
options.headers.append("Cookie", `${name}=${token}`);
break;
case "header":
default:
options.headers.set(name, token);
break;
}
return;
}
return;
}
};
export const buildUrl: Client["buildUrl"] = (options) => {
const url = getUrl({
baseUrl: options.baseUrl as string,
path: options.path,
query: options.query,
querySerializer:
typeof options.querySerializer === "function"
? options.querySerializer
: createQuerySerializer(options.querySerializer),
url: options.url,
});
return url;
const url = getUrl({
baseUrl: options.baseUrl as string,
path: options.path,
query: options.query,
querySerializer:
typeof options.querySerializer === "function"
? options.querySerializer
: createQuerySerializer(options.querySerializer),
url: options.url,
});
return url;
};
export const getUrl = ({
baseUrl,
path,
query,
querySerializer,
url: _url,
baseUrl,
path,
query,
querySerializer,
url: _url,
}: {
baseUrl?: string;
path?: Record<string, unknown>;
query?: Record<string, unknown>;
querySerializer: QuerySerializer;
url: string;
baseUrl?: string;
path?: Record<string, unknown>;
query?: Record<string, unknown>;
querySerializer: QuerySerializer;
url: string;
}) => {
const pathUrl = _url.startsWith("/") ? _url : `/${_url}`;
let url = (baseUrl ?? "") + pathUrl;
if (path) {
url = defaultPathSerializer({ path, url });
}
let search = query ? querySerializer(query) : "";
if (search.startsWith("?")) {
search = search.substring(1);
}
if (search) {
url += `?${search}`;
}
return url;
const pathUrl = _url.startsWith("/") ? _url : `/${_url}`;
let url = (baseUrl ?? "") + pathUrl;
if (path) {
url = defaultPathSerializer({ path, url });
}
let search = query ? querySerializer(query) : "";
if (search.startsWith("?")) {
search = search.substring(1);
}
if (search) {
url += `?${search}`;
}
return url;
};
export const mergeConfigs = (a: Config, b: Config): Config => {
const config = { ...a, ...b };
if (config.baseUrl?.endsWith("/")) {
config.baseUrl = config.baseUrl.substring(0, config.baseUrl.length - 1);
}
config.headers = mergeHeaders(a.headers, b.headers);
return config;
const config = { ...a, ...b };
if (config.baseUrl?.endsWith("/")) {
config.baseUrl = config.baseUrl.substring(0, config.baseUrl.length - 1);
}
config.headers = mergeHeaders(a.headers, b.headers);
return config;
};
export const mergeHeaders = (
...headers: Array<Required<Config>["headers"] | undefined>
...headers: Array<Required<Config>["headers"] | undefined>
): Headers => {
const mergedHeaders = new Headers();
for (const header of headers) {
if (!header || typeof header !== "object") {
continue;
}
const mergedHeaders = new Headers();
for (const header of headers) {
if (!header || typeof header !== "object") {
continue;
}
const iterator =
header instanceof Headers ? header.entries() : Object.entries(header);
const iterator =
header instanceof Headers ? header.entries() : Object.entries(header);
for (const [key, value] of iterator) {
if (value === null) {
mergedHeaders.delete(key);
} else if (Array.isArray(value)) {
for (const v of value) {
mergedHeaders.append(key, v as string);
}
} else if (value !== undefined) {
// assume object headers are meant to be JSON stringified, i.e. their
// content value in OpenAPI specification is 'application/json'
mergedHeaders.set(
key,
typeof value === "object" ? JSON.stringify(value) : (value as string),
);
}
}
}
return mergedHeaders;
for (const [key, value] of iterator) {
if (value === null) {
mergedHeaders.delete(key);
} else if (Array.isArray(value)) {
for (const v of value) {
mergedHeaders.append(key, v as string);
}
} else if (value !== undefined) {
// assume object headers are meant to be JSON stringified, i.e. their
// content value in OpenAPI specification is 'application/json'
mergedHeaders.set(
key,
typeof value === "object" ? JSON.stringify(value) : (value as string),
);
}
}
}
return mergedHeaders;
};
type ErrInterceptor<Err, Res, Req, Options> = (
error: Err,
response: Res,
request: Req,
options: Options,
error: Err,
response: Res,
request: Req,
options: Options,
) => Err | Promise<Err>;
type ReqInterceptor<Req, Options> = (
request: Req,
options: Options,
request: Req,
options: Options,
) => Req | Promise<Req>;
type ResInterceptor<Res, Req, Options> = (
response: Res,
request: Req,
options: Options,
response: Res,
request: Req,
options: Options,
) => Res | Promise<Res>;
class Interceptors<Interceptor> {
_fns: (Interceptor | null)[];
_fns: (Interceptor | null)[];
constructor() {
this._fns = [];
}
constructor() {
this._fns = [];
}
clear() {
this._fns = [];
}
clear() {
this._fns = [];
}
getInterceptorIndex(id: number | Interceptor): number {
if (typeof id === "number") {
return this._fns[id] ? id : -1;
} else {
return this._fns.indexOf(id);
}
}
exists(id: number | Interceptor) {
const index = this.getInterceptorIndex(id);
return !!this._fns[index];
}
getInterceptorIndex(id: number | Interceptor): number {
if (typeof id === "number") {
return this._fns[id] ? id : -1;
} else {
return this._fns.indexOf(id);
}
}
exists(id: number | Interceptor) {
const index = this.getInterceptorIndex(id);
return !!this._fns[index];
}
eject(id: number | Interceptor) {
const index = this.getInterceptorIndex(id);
if (this._fns[index]) {
this._fns[index] = null;
}
}
eject(id: number | Interceptor) {
const index = this.getInterceptorIndex(id);
if (this._fns[index]) {
this._fns[index] = null;
}
}
update(id: number | Interceptor, fn: Interceptor) {
const index = this.getInterceptorIndex(id);
if (this._fns[index]) {
this._fns[index] = fn;
return id;
} else {
return false;
}
}
update(id: number | Interceptor, fn: Interceptor) {
const index = this.getInterceptorIndex(id);
if (this._fns[index]) {
this._fns[index] = fn;
return id;
} else {
return false;
}
}
use(fn: Interceptor) {
this._fns = [...this._fns, fn];
return this._fns.length - 1;
}
use(fn: Interceptor) {
this._fns = [...this._fns, fn];
return this._fns.length - 1;
}
}
// `createInterceptors()` response, meant for external use as it does not
// expose internals
export interface Middleware<Req, Res, Err, Options> {
error: Pick<
Interceptors<ErrInterceptor<Err, Res, Req, Options>>,
"eject" | "use"
>;
request: Pick<Interceptors<ReqInterceptor<Req, Options>>, "eject" | "use">;
response: Pick<
Interceptors<ResInterceptor<Res, Req, Options>>,
"eject" | "use"
>;
error: Pick<
Interceptors<ErrInterceptor<Err, Res, Req, Options>>,
"eject" | "use"
>;
request: Pick<Interceptors<ReqInterceptor<Req, Options>>, "eject" | "use">;
response: Pick<
Interceptors<ResInterceptor<Res, Req, Options>>,
"eject" | "use"
>;
}
// do not add `Middleware` as return type so we can use _fns internally
export const createInterceptors = <Req, Res, Err, Options>() => ({
error: new Interceptors<ErrInterceptor<Err, Res, Req, Options>>(),
request: new Interceptors<ReqInterceptor<Req, Options>>(),
response: new Interceptors<ResInterceptor<Res, Req, Options>>(),
error: new Interceptors<ErrInterceptor<Err, Res, Req, Options>>(),
request: new Interceptors<ReqInterceptor<Req, Options>>(),
response: new Interceptors<ResInterceptor<Res, Req, Options>>(),
});
const defaultQuerySerializer = createQuerySerializer({
allowReserved: false,
array: {
explode: true,
style: "form",
},
object: {
explode: true,
style: "deepObject",
},
allowReserved: false,
array: {
explode: true,
style: "form",
},
object: {
explode: true,
style: "deepObject",
},
});
const defaultHeaders = {
"Content-Type": "application/json",
"Content-Type": "application/json",
};
export const createConfig = <T extends ClientOptions = ClientOptions>(
override: Config<Omit<ClientOptions, keyof T> & T> = {},
override: Config<Omit<ClientOptions, keyof T> & T> = {},
): Config<Omit<ClientOptions, keyof T> & T> => ({
...jsonBodySerializer,
headers: defaultHeaders,
parseAs: "auto",
querySerializer: defaultQuerySerializer,
...override,
...jsonBodySerializer,
headers: defaultHeaders,
parseAs: "auto",
querySerializer: defaultQuerySerializer,
...override,
});
+28 -28
View File
@@ -1,40 +1,40 @@
export type AuthToken = string | undefined;
export interface Auth {
/**
* Which part of the request do we use to send the auth?
*
* @default 'header'
*/
in?: "header" | "query" | "cookie";
/**
* Header or query parameter name.
*
* @default 'Authorization'
*/
name?: string;
scheme?: "basic" | "bearer";
type: "apiKey" | "http";
/**
* Which part of the request do we use to send the auth?
*
* @default 'header'
*/
in?: "header" | "query" | "cookie";
/**
* Header or query parameter name.
*
* @default 'Authorization'
*/
name?: string;
scheme?: "basic" | "bearer";
type: "apiKey" | "http";
}
export const getAuthToken = async (
auth: Auth,
callback: ((auth: Auth) => Promise<AuthToken> | AuthToken) | AuthToken,
auth: Auth,
callback: ((auth: Auth) => Promise<AuthToken> | AuthToken) | AuthToken,
): Promise<string | undefined> => {
const token =
typeof callback === "function" ? await callback(auth) : callback;
const token =
typeof callback === "function" ? await callback(auth) : callback;
if (!token) {
return;
}
if (!token) {
return;
}
if (auth.scheme === "bearer") {
return `Bearer ${token}`;
}
if (auth.scheme === "bearer") {
return `Bearer ${token}`;
}
if (auth.scheme === "basic") {
return `Basic ${btoa(token)}`;
}
if (auth.scheme === "basic") {
return `Basic ${btoa(token)}`;
}
return token;
return token;
};
@@ -1,7 +1,7 @@
import type {
ArrayStyle,
ObjectStyle,
SerializerOptions,
ArrayStyle,
ObjectStyle,
SerializerOptions,
} from "./pathSerializer.js";
export type QuerySerializer = (query: Record<string, unknown>) => string;
@@ -9,76 +9,76 @@ export type QuerySerializer = (query: Record<string, unknown>) => string;
export type BodySerializer = (body: any) => any;
export interface QuerySerializerOptions {
allowReserved?: boolean;
array?: SerializerOptions<ArrayStyle>;
object?: SerializerOptions<ObjectStyle>;
allowReserved?: boolean;
array?: SerializerOptions<ArrayStyle>;
object?: SerializerOptions<ObjectStyle>;
}
const serializeFormDataPair = (data: FormData, key: string, value: unknown) => {
if (typeof value === "string" || value instanceof Blob) {
data.append(key, value);
} else {
data.append(key, JSON.stringify(value));
}
if (typeof value === "string" || value instanceof Blob) {
data.append(key, value);
} else {
data.append(key, JSON.stringify(value));
}
};
const serializeUrlSearchParamsPair = (
data: URLSearchParams,
key: string,
value: unknown,
data: URLSearchParams,
key: string,
value: unknown,
) => {
if (typeof value === "string") {
data.append(key, value);
} else {
data.append(key, JSON.stringify(value));
}
if (typeof value === "string") {
data.append(key, value);
} else {
data.append(key, JSON.stringify(value));
}
};
export const formDataBodySerializer = {
bodySerializer: <T extends Record<string, any> | Array<Record<string, any>>>(
body: T,
) => {
const data = new FormData();
bodySerializer: <T extends Record<string, any> | Array<Record<string, any>>>(
body: T,
) => {
const data = new FormData();
Object.entries(body).forEach(([key, value]) => {
if (value === undefined || value === null) {
return;
}
if (Array.isArray(value)) {
value.forEach((v) => serializeFormDataPair(data, key, v));
} else {
serializeFormDataPair(data, key, value);
}
});
Object.entries(body).forEach(([key, value]) => {
if (value === undefined || value === null) {
return;
}
if (Array.isArray(value)) {
value.forEach((v) => serializeFormDataPair(data, key, v));
} else {
serializeFormDataPair(data, key, value);
}
});
return data;
},
return data;
},
};
export const jsonBodySerializer = {
bodySerializer: <T>(body: T) =>
JSON.stringify(body, (_key, value) =>
typeof value === "bigint" ? value.toString() : value,
),
bodySerializer: <T>(body: T) =>
JSON.stringify(body, (_key, value) =>
typeof value === "bigint" ? value.toString() : value,
),
};
export const urlSearchParamsBodySerializer = {
bodySerializer: <T extends Record<string, any> | Array<Record<string, any>>>(
body: T,
) => {
const data = new URLSearchParams();
bodySerializer: <T extends Record<string, any> | Array<Record<string, any>>>(
body: T,
) => {
const data = new URLSearchParams();
Object.entries(body).forEach(([key, value]) => {
if (value === undefined || value === null) {
return;
}
if (Array.isArray(value)) {
value.forEach((v) => serializeUrlSearchParamsPair(data, key, v));
} else {
serializeUrlSearchParamsPair(data, key, value);
}
});
Object.entries(body).forEach(([key, value]) => {
if (value === undefined || value === null) {
return;
}
if (Array.isArray(value)) {
value.forEach((v) => serializeUrlSearchParamsPair(data, key, v));
} else {
serializeUrlSearchParamsPair(data, key, value);
}
});
return data.toString();
},
return data.toString();
},
};
+102 -102
View File
@@ -1,141 +1,141 @@
type Slot = "body" | "headers" | "path" | "query";
export type Field =
| {
in: Exclude<Slot, "body">;
key: string;
map?: string;
}
| {
in: Extract<Slot, "body">;
key?: string;
map?: string;
};
| {
in: Exclude<Slot, "body">;
key: string;
map?: string;
}
| {
in: Extract<Slot, "body">;
key?: string;
map?: string;
};
export interface Fields {
allowExtra?: Partial<Record<Slot, boolean>>;
args?: ReadonlyArray<Field>;
allowExtra?: Partial<Record<Slot, boolean>>;
args?: ReadonlyArray<Field>;
}
export type FieldsConfig = ReadonlyArray<Field | Fields>;
const extraPrefixesMap: Record<string, Slot> = {
$body_: "body",
$headers_: "headers",
$path_: "path",
$query_: "query",
$body_: "body",
$headers_: "headers",
$path_: "path",
$query_: "query",
};
const extraPrefixes = Object.entries(extraPrefixesMap);
type KeyMap = Map<
string,
{
in: Slot;
map?: string;
}
string,
{
in: Slot;
map?: string;
}
>;
const buildKeyMap = (fields: FieldsConfig, map?: KeyMap): KeyMap => {
if (!map) {
map = new Map();
}
if (!map) {
map = new Map();
}
for (const config of fields) {
if ("in" in config) {
if (config.key) {
map.set(config.key, {
in: config.in,
map: config.map,
});
}
} else if (config.args) {
buildKeyMap(config.args, map);
}
}
for (const config of fields) {
if ("in" in config) {
if (config.key) {
map.set(config.key, {
in: config.in,
map: config.map,
});
}
} else if (config.args) {
buildKeyMap(config.args, map);
}
}
return map;
return map;
};
interface Params {
body: unknown;
headers: Record<string, unknown>;
path: Record<string, unknown>;
query: Record<string, unknown>;
body: unknown;
headers: Record<string, unknown>;
path: Record<string, unknown>;
query: Record<string, unknown>;
}
const stripEmptySlots = (params: Params) => {
for (const [slot, value] of Object.entries(params)) {
if (value && typeof value === "object" && !Object.keys(value).length) {
delete params[slot as Slot];
}
}
for (const [slot, value] of Object.entries(params)) {
if (value && typeof value === "object" && !Object.keys(value).length) {
delete params[slot as Slot];
}
}
};
export const buildClientParams = (
args: ReadonlyArray<unknown>,
fields: FieldsConfig,
args: ReadonlyArray<unknown>,
fields: FieldsConfig,
) => {
const params: Params = {
body: {},
headers: {},
path: {},
query: {},
};
const params: Params = {
body: {},
headers: {},
path: {},
query: {},
};
const map = buildKeyMap(fields);
const map = buildKeyMap(fields);
let config: FieldsConfig[number] | undefined;
let config: FieldsConfig[number] | undefined;
for (const [index, arg] of args.entries()) {
if (fields[index]) {
config = fields[index];
}
for (const [index, arg] of args.entries()) {
if (fields[index]) {
config = fields[index];
}
if (!config) {
continue;
}
if (!config) {
continue;
}
if ("in" in config) {
if (config.key) {
const field = map.get(config.key)!;
const name = field.map || config.key;
(params[field.in] as Record<string, unknown>)[name] = arg;
} else {
params.body = arg;
}
} else {
for (const [key, value] of Object.entries(arg ?? {})) {
const field = map.get(key);
if ("in" in config) {
if (config.key) {
const field = map.get(config.key)!;
const name = field.map || config.key;
(params[field.in] as Record<string, unknown>)[name] = arg;
} else {
params.body = arg;
}
} else {
for (const [key, value] of Object.entries(arg ?? {})) {
const field = map.get(key);
if (field) {
const name = field.map || key;
(params[field.in] as Record<string, unknown>)[name] = value;
} else {
const extra = extraPrefixes.find(([prefix]) =>
key.startsWith(prefix),
);
if (field) {
const name = field.map || key;
(params[field.in] as Record<string, unknown>)[name] = value;
} else {
const extra = extraPrefixes.find(([prefix]) =>
key.startsWith(prefix),
);
if (extra) {
const [prefix, slot] = extra;
(params[slot] as Record<string, unknown>)[
key.slice(prefix.length)
] = value;
} else {
for (const [slot, allowed] of Object.entries(
config.allowExtra ?? {},
)) {
if (allowed) {
(params[slot as Slot] as Record<string, unknown>)[key] = value;
break;
}
}
}
}
}
}
}
if (extra) {
const [prefix, slot] = extra;
(params[slot] as Record<string, unknown>)[
key.slice(prefix.length)
] = value;
} else {
for (const [slot, allowed] of Object.entries(
config.allowExtra ?? {},
)) {
if (allowed) {
(params[slot as Slot] as Record<string, unknown>)[key] = value;
break;
}
}
}
}
}
}
}
stripEmptySlots(params);
stripEmptySlots(params);
return params;
return params;
};
+134 -134
View File
@@ -1,18 +1,18 @@
interface SerializeOptions<T>
extends SerializePrimitiveOptions,
SerializerOptions<T> {}
extends SerializePrimitiveOptions,
SerializerOptions<T> {}
interface SerializePrimitiveOptions {
allowReserved?: boolean;
name: string;
allowReserved?: boolean;
name: string;
}
export interface SerializerOptions<T> {
/**
* @default true
*/
explode: boolean;
style: T;
/**
* @default true
*/
explode: boolean;
style: T;
}
export type ArrayStyle = "form" | "spaceDelimited" | "pipeDelimited";
@@ -22,158 +22,158 @@ export type ObjectStyle = "form" | "deepObject";
type ObjectSeparatorStyle = ObjectStyle | MatrixStyle;
interface SerializePrimitiveParam extends SerializePrimitiveOptions {
value: string;
value: string;
}
export const separatorArrayExplode = (style: ArraySeparatorStyle) => {
switch (style) {
case "label":
return ".";
case "matrix":
return ";";
case "simple":
return ",";
default:
return "&";
}
switch (style) {
case "label":
return ".";
case "matrix":
return ";";
case "simple":
return ",";
default:
return "&";
}
};
export const separatorArrayNoExplode = (style: ArraySeparatorStyle) => {
switch (style) {
case "form":
return ",";
case "pipeDelimited":
return "|";
case "spaceDelimited":
return "%20";
default:
return ",";
}
switch (style) {
case "form":
return ",";
case "pipeDelimited":
return "|";
case "spaceDelimited":
return "%20";
default:
return ",";
}
};
export const separatorObjectExplode = (style: ObjectSeparatorStyle) => {
switch (style) {
case "label":
return ".";
case "matrix":
return ";";
case "simple":
return ",";
default:
return "&";
}
switch (style) {
case "label":
return ".";
case "matrix":
return ";";
case "simple":
return ",";
default:
return "&";
}
};
export const serializeArrayParam = ({
allowReserved,
explode,
name,
style,
value,
allowReserved,
explode,
name,
style,
value,
}: SerializeOptions<ArraySeparatorStyle> & {
value: unknown[];
value: unknown[];
}) => {
if (!explode) {
const joinedValues = (
allowReserved ? value : value.map((v) => encodeURIComponent(v as string))
).join(separatorArrayNoExplode(style));
switch (style) {
case "label":
return `.${joinedValues}`;
case "matrix":
return `;${name}=${joinedValues}`;
case "simple":
return joinedValues;
default:
return `${name}=${joinedValues}`;
}
}
if (!explode) {
const joinedValues = (
allowReserved ? value : value.map((v) => encodeURIComponent(v as string))
).join(separatorArrayNoExplode(style));
switch (style) {
case "label":
return `.${joinedValues}`;
case "matrix":
return `;${name}=${joinedValues}`;
case "simple":
return joinedValues;
default:
return `${name}=${joinedValues}`;
}
}
const separator = separatorArrayExplode(style);
const joinedValues = value
.map((v) => {
if (style === "label" || style === "simple") {
return allowReserved ? v : encodeURIComponent(v as string);
}
const separator = separatorArrayExplode(style);
const joinedValues = value
.map((v) => {
if (style === "label" || style === "simple") {
return allowReserved ? v : encodeURIComponent(v as string);
}
return serializePrimitiveParam({
allowReserved,
name,
value: v as string,
});
})
.join(separator);
return style === "label" || style === "matrix"
? separator + joinedValues
: joinedValues;
return serializePrimitiveParam({
allowReserved,
name,
value: v as string,
});
})
.join(separator);
return style === "label" || style === "matrix"
? separator + joinedValues
: joinedValues;
};
export const serializePrimitiveParam = ({
allowReserved,
name,
value,
allowReserved,
name,
value,
}: SerializePrimitiveParam) => {
if (value === undefined || value === null) {
return "";
}
if (value === undefined || value === null) {
return "";
}
if (typeof value === "object") {
throw new Error(
"Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.",
);
}
if (typeof value === "object") {
throw new Error(
"Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.",
);
}
return `${name}=${allowReserved ? value : encodeURIComponent(value)}`;
return `${name}=${allowReserved ? value : encodeURIComponent(value)}`;
};
export const serializeObjectParam = ({
allowReserved,
explode,
name,
style,
value,
valueOnly,
allowReserved,
explode,
name,
style,
value,
valueOnly,
}: SerializeOptions<ObjectSeparatorStyle> & {
value: Record<string, unknown> | Date;
valueOnly?: boolean;
value: Record<string, unknown> | Date;
valueOnly?: boolean;
}) => {
if (value instanceof Date) {
return valueOnly ? value.toISOString() : `${name}=${value.toISOString()}`;
}
if (value instanceof Date) {
return valueOnly ? value.toISOString() : `${name}=${value.toISOString()}`;
}
if (style !== "deepObject" && !explode) {
let values: string[] = [];
Object.entries(value).forEach(([key, v]) => {
values = [
...values,
key,
allowReserved ? (v as string) : encodeURIComponent(v as string),
];
});
const joinedValues = values.join(",");
switch (style) {
case "form":
return `${name}=${joinedValues}`;
case "label":
return `.${joinedValues}`;
case "matrix":
return `;${name}=${joinedValues}`;
default:
return joinedValues;
}
}
if (style !== "deepObject" && !explode) {
let values: string[] = [];
Object.entries(value).forEach(([key, v]) => {
values = [
...values,
key,
allowReserved ? (v as string) : encodeURIComponent(v as string),
];
});
const joinedValues = values.join(",");
switch (style) {
case "form":
return `${name}=${joinedValues}`;
case "label":
return `.${joinedValues}`;
case "matrix":
return `;${name}=${joinedValues}`;
default:
return joinedValues;
}
}
const separator = separatorObjectExplode(style);
const joinedValues = Object.entries(value)
.map(([key, v]) =>
serializePrimitiveParam({
allowReserved,
name: style === "deepObject" ? `${name}[${key}]` : key,
value: v as string,
}),
)
.join(separator);
return style === "label" || style === "matrix"
? separator + joinedValues
: joinedValues;
const separator = separatorObjectExplode(style);
const joinedValues = Object.entries(value)
.map(([key, v]) =>
serializePrimitiveParam({
allowReserved,
name: style === "deepObject" ? `${name}[${key}]` : key,
value: v as string,
}),
)
.join(separator);
return style === "label" || style === "matrix"
? separator + joinedValues
: joinedValues;
};
+88 -88
View File
@@ -1,98 +1,98 @@
import type { Auth, AuthToken } from "./auth.js";
import type {
BodySerializer,
QuerySerializer,
QuerySerializerOptions,
BodySerializer,
QuerySerializer,
QuerySerializerOptions,
} from "./bodySerializer.js";
export interface Client<
RequestFn = never,
Config = unknown,
MethodFn = never,
BuildUrlFn = never,
RequestFn = never,
Config = unknown,
MethodFn = never,
BuildUrlFn = never,
> {
/**
* Returns the final request URL.
*/
buildUrl: BuildUrlFn;
connect: MethodFn;
delete: MethodFn;
get: MethodFn;
getConfig: () => Config;
head: MethodFn;
options: MethodFn;
patch: MethodFn;
post: MethodFn;
put: MethodFn;
request: RequestFn;
setConfig: (config: Config) => Config;
trace: MethodFn;
/**
* Returns the final request URL.
*/
buildUrl: BuildUrlFn;
connect: MethodFn;
delete: MethodFn;
get: MethodFn;
getConfig: () => Config;
head: MethodFn;
options: MethodFn;
patch: MethodFn;
post: MethodFn;
put: MethodFn;
request: RequestFn;
setConfig: (config: Config) => Config;
trace: MethodFn;
}
export interface Config {
/**
* Auth token or a function returning auth token. The resolved value will be
* added to the request payload as defined by its `security` array.
*/
auth?: ((auth: Auth) => Promise<AuthToken> | AuthToken) | AuthToken;
/**
* A function for serializing request body parameter. By default,
* {@link JSON.stringify()} will be used.
*/
bodySerializer?: BodySerializer | null;
/**
* An object containing any HTTP headers that you want to pre-populate your
* `Headers` object with.
*
* {@link https://developer.mozilla.org/docs/Web/API/Headers/Headers#init See more}
*/
headers?:
| RequestInit["headers"]
| Record<
string,
| string
| number
| boolean
| (string | number | boolean)[]
| null
| undefined
| unknown
>;
/**
* The request method.
*
* {@link https://developer.mozilla.org/docs/Web/API/fetch#method See more}
*/
method?:
| "CONNECT"
| "DELETE"
| "GET"
| "HEAD"
| "OPTIONS"
| "PATCH"
| "POST"
| "PUT"
| "TRACE";
/**
* A function for serializing request query parameters. By default, arrays
* will be exploded in form style, objects will be exploded in deepObject
* style, and reserved characters are percent-encoded.
*
* This method will have no effect if the native `paramsSerializer()` Axios
* API function is used.
*
* {@link https://swagger.io/docs/specification/serialization/#query View examples}
*/
querySerializer?: QuerySerializer | QuerySerializerOptions;
/**
* A function transforming response data before it's returned. This is useful
* for post-processing data, e.g. converting ISO strings into Date objects.
*/
responseTransformer?: (data: unknown) => Promise<unknown>;
/**
* A function validating response data. This is useful if you want to ensure
* the response conforms to the desired shape, so it can be safely passed to
* the transformers and returned to the user.
*/
responseValidator?: (data: unknown) => Promise<unknown>;
/**
* Auth token or a function returning auth token. The resolved value will be
* added to the request payload as defined by its `security` array.
*/
auth?: ((auth: Auth) => Promise<AuthToken> | AuthToken) | AuthToken;
/**
* A function for serializing request body parameter. By default,
* {@link JSON.stringify()} will be used.
*/
bodySerializer?: BodySerializer | null;
/**
* An object containing any HTTP headers that you want to pre-populate your
* `Headers` object with.
*
* {@link https://developer.mozilla.org/docs/Web/API/Headers/Headers#init See more}
*/
headers?:
| RequestInit["headers"]
| Record<
string,
| string
| number
| boolean
| (string | number | boolean)[]
| null
| undefined
| unknown
>;
/**
* The request method.
*
* {@link https://developer.mozilla.org/docs/Web/API/fetch#method See more}
*/
method?:
| "CONNECT"
| "DELETE"
| "GET"
| "HEAD"
| "OPTIONS"
| "PATCH"
| "POST"
| "PUT"
| "TRACE";
/**
* A function for serializing request query parameters. By default, arrays
* will be exploded in form style, objects will be exploded in deepObject
* style, and reserved characters are percent-encoded.
*
* This method will have no effect if the native `paramsSerializer()` Axios
* API function is used.
*
* {@link https://swagger.io/docs/specification/serialization/#query View examples}
*/
querySerializer?: QuerySerializer | QuerySerializerOptions;
/**
* A function transforming response data before it's returned. This is useful
* for post-processing data, e.g. converting ISO strings into Date objects.
*/
responseTransformer?: (data: unknown) => Promise<unknown>;
/**
* A function validating response data. This is useful if you want to ensure
* the response conforms to the desired shape, so it can be safely passed to
* the transformers and returned to the user.
*/
responseValidator?: (data: unknown) => Promise<unknown>;
}
+402 -355
View File
@@ -1,57 +1,247 @@
// This file is auto-generated by @hey-api/openapi-ts
import type {
Client,
Options as ClientOptions,
TDataShape,
Client,
Options as ClientOptions,
TDataShape,
} from "./client/index.js";
import { client as _heyApiClient } from "./client.gen.js";
import type {
CreateAccountData,
CreateAccountResponses,
EncodeFunctionDataData,
EncodeFunctionDataResponses,
GetNativeBalanceData,
GetNativeBalanceErrors,
GetNativeBalanceResponses,
GetTransactionAnalyticsData,
GetTransactionAnalyticsResponses,
GetTransactionAnalyticsSummaryData,
GetTransactionAnalyticsSummaryErrors,
GetTransactionAnalyticsSummaryResponses,
ListAccountsData,
ListAccountsResponses,
ReadContractData,
ReadContractResponses,
SearchTransactionsData,
SearchTransactionsResponses,
SendTransactionData,
SendTransactionResponses,
SignMessageData,
SignMessageResponses,
SignTransactionData,
SignTransactionResponses,
SignTypedDataData,
SignTypedDataResponses,
WriteContractData,
WriteContractResponses,
CancelTransactionData,
CancelTransactionResponses,
CreateAccountData,
CreateAccountResponses,
EncodeContractData,
EncodeContractResponses,
GetActivityLogsData,
GetActivityLogsErrors,
GetActivityLogsResponses,
GetTransactionAnalyticsData,
GetTransactionAnalyticsResponses,
GetTransactionAnalyticsSummaryData,
GetTransactionAnalyticsSummaryErrors,
GetTransactionAnalyticsSummaryResponses,
GetTransactionsData,
GetTransactionsResponses,
ListAccountsData,
ListAccountsResponses,
ReadContractData,
ReadContractResponses,
SearchActivityLogsData,
SearchActivityLogsErrors,
SearchActivityLogsResponses,
SearchTransactionsData,
SearchTransactionsResponses,
SendTransactionData,
SendTransactionResponses,
SignMessageData,
SignMessageResponses,
SignTypedDataData,
SignTypedDataResponses,
WriteContractData,
WriteContractResponses,
} from "./types.gen.js";
export type Options<
TData extends TDataShape = TDataShape,
ThrowOnError extends boolean = boolean,
TData extends TDataShape = TDataShape,
ThrowOnError extends boolean = boolean,
> = ClientOptions<TData, ThrowOnError> & {
/**
* You can provide a client instance returned by `createClient()` instead of
* individual options. This might be also useful if you want to implement a
* custom client.
*/
client?: Client;
/**
* You can pass arbitrary values through the `meta` object. This can be
* used to access values that aren't defined as part of the SDK function.
*/
meta?: Record<string, unknown>;
/**
* You can provide a client instance returned by `createClient()` instead of
* individual options. This might be also useful if you want to implement a
* custom client.
*/
client?: Client;
/**
* You can pass arbitrary values through the `meta` object. This can be
* used to access values that aren't defined as part of the SDK function.
*/
meta?: Record<string, unknown>;
};
/**
* Write Contract
* Call a contract function with a transaction
*/
export const writeContract = <ThrowOnError extends boolean = false>(
options: Options<WriteContractData, ThrowOnError>,
) => {
return (options.client ?? _heyApiClient).post<
WriteContractResponses,
unknown,
ThrowOnError
>({
security: [
{
name: "x-secret-key",
type: "apiKey",
},
],
url: "/v1/write/contract",
...options,
headers: {
"Content-Type": "application/json",
...options.headers,
},
});
};
/**
* Write Transaction
* Execute raw transactions
*/
export const sendTransaction = <ThrowOnError extends boolean = false>(
options: Options<SendTransactionData, ThrowOnError>,
) => {
return (options.client ?? _heyApiClient).post<
SendTransactionResponses,
unknown,
ThrowOnError
>({
security: [
{
name: "x-secret-key",
type: "apiKey",
},
],
url: "/v1/write/transaction",
...options,
headers: {
"Content-Type": "application/json",
...options.headers,
},
});
};
/**
* Sign Message
* Sign messages using either EOA or Smart Account
*/
export const signMessage = <ThrowOnError extends boolean = false>(
options: Options<SignMessageData, ThrowOnError>,
) => {
return (options.client ?? _heyApiClient).post<
SignMessageResponses,
unknown,
ThrowOnError
>({
security: [
{
name: "x-secret-key",
type: "apiKey",
},
],
url: "/v1/sign/message",
...options,
headers: {
"Content-Type": "application/json",
...options.headers,
},
});
};
/**
* Sign Typed Data
* Sign EIP-712 typed data using either EOA or Smart Account
*/
export const signTypedData = <ThrowOnError extends boolean = false>(
options: Options<SignTypedDataData, ThrowOnError>,
) => {
return (options.client ?? _heyApiClient).post<
SignTypedDataResponses,
unknown,
ThrowOnError
>({
security: [
{
name: "x-secret-key",
type: "apiKey",
},
],
url: "/v1/sign/typed-data",
...options,
headers: {
"Content-Type": "application/json",
...options.headers,
},
});
};
/**
* Read Contract
* Read from multiple smart contracts using multicall
*/
export const readContract = <ThrowOnError extends boolean = false>(
options: Options<ReadContractData, ThrowOnError>,
) => {
return (options.client ?? _heyApiClient).post<
ReadContractResponses,
unknown,
ThrowOnError
>({
security: [
{
name: "x-secret-key",
type: "apiKey",
},
],
url: "/v1/read/contract",
...options,
headers: {
"Content-Type": "application/json",
...options.headers,
},
});
};
/**
* Encode Contract
* Encode contract function calls without execution
*/
export const encodeContract = <ThrowOnError extends boolean = false>(
options: Options<EncodeContractData, ThrowOnError>,
) => {
return (options.client ?? _heyApiClient).post<
EncodeContractResponses,
unknown,
ThrowOnError
>({
security: [
{
name: "x-secret-key",
type: "apiKey",
},
],
url: "/v1/encode/contract",
...options,
headers: {
"Content-Type": "application/json",
...options.headers,
},
});
};
/**
* Cancel Transaction
* Attempt to cancel a queued transaction. Transactions that have been sent and are waiting for mine cannot be cancelled.
*/
export const cancelTransaction = <ThrowOnError extends boolean = false>(
options: Options<CancelTransactionData, ThrowOnError>,
) => {
return (options.client ?? _heyApiClient).post<
CancelTransactionResponses,
unknown,
ThrowOnError
>({
security: [
{
name: "x-secret-key",
type: "apiKey",
},
],
url: "/v1/transactions/{id}/cancel",
...options,
});
};
/**
@@ -59,22 +249,22 @@ export type Options<
* List all engine server wallets for the current project. Returns an array of EOA addresses with their corresponding predicted smart account addresses.
*/
export const listAccounts = <ThrowOnError extends boolean = false>(
options?: Options<ListAccountsData, ThrowOnError>,
options?: Options<ListAccountsData, ThrowOnError>,
) => {
return (options?.client ?? _heyApiClient).get<
ListAccountsResponses,
unknown,
ThrowOnError
>({
security: [
{
name: "x-secret-key",
type: "apiKey",
},
],
url: "/v1/accounts",
...options,
});
return (options?.client ?? _heyApiClient).get<
ListAccountsResponses,
unknown,
ThrowOnError
>({
security: [
{
name: "x-secret-key",
type: "apiKey",
},
],
url: "/v1/accounts",
...options,
});
};
/**
@@ -82,269 +272,49 @@ export const listAccounts = <ThrowOnError extends boolean = false>(
* Create a new engine server wallet. This is a helper route for creating a new EOA with your KMS provider, provided as a convenient alternative to creating an EOA directly with your KMS provider. Your KMS credentials are not stored, and usage of created accounts require your KMS credentials to be sent with requests.
*/
export const createAccount = <ThrowOnError extends boolean = false>(
options?: Options<CreateAccountData, ThrowOnError>,
options?: Options<CreateAccountData, ThrowOnError>,
) => {
return (options?.client ?? _heyApiClient).post<
CreateAccountResponses,
unknown,
ThrowOnError
>({
security: [
{
name: "x-secret-key",
type: "apiKey",
},
],
url: "/v1/accounts",
...options,
headers: {
"Content-Type": "application/json",
...options?.headers,
},
});
return (options?.client ?? _heyApiClient).post<
CreateAccountResponses,
unknown,
ThrowOnError
>({
security: [
{
name: "x-secret-key",
type: "apiKey",
},
],
url: "/v1/accounts",
...options,
headers: {
"Content-Type": "application/json",
...options?.headers,
},
});
};
/**
* Write Contract
* Call a write function on a contract.
* Get Transactions
* Search transactions with various filters and pagination
*/
export const writeContract = <ThrowOnError extends boolean = false>(
options?: Options<WriteContractData, ThrowOnError>,
export const getTransactions = <ThrowOnError extends boolean = false>(
options?: Options<GetTransactionsData, ThrowOnError>,
) => {
return (options?.client ?? _heyApiClient).post<
WriteContractResponses,
unknown,
ThrowOnError
>({
security: [
{
name: "x-secret-key",
type: "apiKey",
},
],
url: "/v1/write/contract",
...options,
headers: {
"Content-Type": "application/json",
...options?.headers,
},
});
};
/**
* Send Transaction
* Send an encoded transaction or a batch of transactions
*/
export const sendTransaction = <ThrowOnError extends boolean = false>(
options?: Options<SendTransactionData, ThrowOnError>,
) => {
return (options?.client ?? _heyApiClient).post<
SendTransactionResponses,
unknown,
ThrowOnError
>({
security: [
{
name: "x-secret-key",
type: "apiKey",
},
],
url: "/v1/write/transaction",
...options,
headers: {
"Content-Type": "application/json",
...options?.headers,
},
});
};
/**
* Sign Transaction
* Sign transactions without sending them.
*/
export const signTransaction = <ThrowOnError extends boolean = false>(
options?: Options<SignTransactionData, ThrowOnError>,
) => {
return (options?.client ?? _heyApiClient).post<
SignTransactionResponses,
unknown,
ThrowOnError
>({
security: [
{
name: "x-secret-key",
type: "apiKey",
},
],
url: "/v1/sign/transaction",
...options,
headers: {
"Content-Type": "application/json",
...options?.headers,
},
});
};
/**
* Sign Message
* Sign arbitrary messages.
*/
export const signMessage = <ThrowOnError extends boolean = false>(
options?: Options<SignMessageData, ThrowOnError>,
) => {
return (options?.client ?? _heyApiClient).post<
SignMessageResponses,
unknown,
ThrowOnError
>({
security: [
{
name: "x-secret-key",
type: "apiKey",
},
],
url: "/v1/sign/message",
...options,
headers: {
"Content-Type": "application/json",
...options?.headers,
},
});
};
/**
* Sign Typed Data
* Sign EIP-712 typed data.
*/
export const signTypedData = <ThrowOnError extends boolean = false>(
options?: Options<SignTypedDataData, ThrowOnError>,
) => {
return (options?.client ?? _heyApiClient).post<
SignTypedDataResponses,
unknown,
ThrowOnError
>({
security: [
{
name: "x-secret-key",
type: "apiKey",
},
],
url: "/v1/sign/typed-data",
...options,
headers: {
"Content-Type": "application/json",
...options?.headers,
},
});
};
/**
* Read Contract
* Call read-only contract functions or batch read using multicall.
*/
export const readContract = <ThrowOnError extends boolean = false>(
options?: Options<ReadContractData, ThrowOnError>,
) => {
return (options?.client ?? _heyApiClient).post<
ReadContractResponses,
unknown,
ThrowOnError
>({
security: [
{
name: "x-secret-key",
type: "apiKey",
},
],
url: "/v1/read/contract",
...options,
headers: {
"Content-Type": "application/json",
...options?.headers,
},
});
};
/**
* Read Native Balance
* Fetches the native cryptocurrency balance (e.g., ETH, MATIC) for a given address on a specific chain.
*/
export const getNativeBalance = <ThrowOnError extends boolean = false>(
options?: Options<GetNativeBalanceData, ThrowOnError>,
) => {
return (options?.client ?? _heyApiClient).post<
GetNativeBalanceResponses,
GetNativeBalanceErrors,
ThrowOnError
>({
security: [
{
name: "x-secret-key",
type: "apiKey",
},
],
url: "/v1/read/balance",
...options,
headers: {
"Content-Type": "application/json",
...options?.headers,
},
});
};
/**
* Encode Function Data
* Encode a contract call into transaction parameters (to, data, value).
*/
export const encodeFunctionData = <ThrowOnError extends boolean = false>(
options?: Options<EncodeFunctionDataData, ThrowOnError>,
) => {
return (options?.client ?? _heyApiClient).post<
EncodeFunctionDataResponses,
unknown,
ThrowOnError
>({
security: [
{
name: "x-secret-key",
type: "apiKey",
},
],
url: "/v1/encode/contract",
...options,
headers: {
"Content-Type": "application/json",
...options?.headers,
},
});
};
/**
* Search Transactions
* Advanced search for transactions with complex nested filters
*/
export const searchTransactions = <ThrowOnError extends boolean = false>(
options?: Options<SearchTransactionsData, ThrowOnError>,
) => {
return (options?.client ?? _heyApiClient).post<
SearchTransactionsResponses,
unknown,
ThrowOnError
>({
security: [
{
name: "x-secret-key",
type: "apiKey",
},
],
url: "/v1/transactions/search",
...options,
headers: {
"Content-Type": "application/json",
...options?.headers,
},
});
return (options?.client ?? _heyApiClient).get<
GetTransactionsResponses,
unknown,
ThrowOnError
>({
security: [
{
name: "x-secret-key",
type: "apiKey",
},
],
url: "/v1/transactions",
...options,
});
};
/**
@@ -352,26 +322,26 @@ export const searchTransactions = <ThrowOnError extends boolean = false>(
* Get transaction count analytics over time with filtering
*/
export const getTransactionAnalytics = <ThrowOnError extends boolean = false>(
options?: Options<GetTransactionAnalyticsData, ThrowOnError>,
options?: Options<GetTransactionAnalyticsData, ThrowOnError>,
) => {
return (options?.client ?? _heyApiClient).post<
GetTransactionAnalyticsResponses,
unknown,
ThrowOnError
>({
security: [
{
name: "x-secret-key",
type: "apiKey",
},
],
url: "/v1/transactions/analytics",
...options,
headers: {
"Content-Type": "application/json",
...options?.headers,
},
});
return (options?.client ?? _heyApiClient).post<
GetTransactionAnalyticsResponses,
unknown,
ThrowOnError
>({
security: [
{
name: "x-secret-key",
type: "apiKey",
},
],
url: "/v1/transactions/analytics",
...options,
headers: {
"Content-Type": "application/json",
...options?.headers,
},
});
};
/**
@@ -379,26 +349,103 @@ export const getTransactionAnalytics = <ThrowOnError extends boolean = false>(
* Get a summary (total count and total gas calculation) for transactions within a time range, supporting complex nested filters.
*/
export const getTransactionAnalyticsSummary = <
ThrowOnError extends boolean = false,
ThrowOnError extends boolean = false,
>(
options?: Options<GetTransactionAnalyticsSummaryData, ThrowOnError>,
options?: Options<GetTransactionAnalyticsSummaryData, ThrowOnError>,
) => {
return (options?.client ?? _heyApiClient).post<
GetTransactionAnalyticsSummaryResponses,
GetTransactionAnalyticsSummaryErrors,
ThrowOnError
>({
security: [
{
name: "x-secret-key",
type: "apiKey",
},
],
url: "/v1/transactions/analytics-summary",
...options,
headers: {
"Content-Type": "application/json",
...options?.headers,
},
});
return (options?.client ?? _heyApiClient).post<
GetTransactionAnalyticsSummaryResponses,
GetTransactionAnalyticsSummaryErrors,
ThrowOnError
>({
security: [
{
name: "x-secret-key",
type: "apiKey",
},
],
url: "/v1/transactions/analytics-summary",
...options,
headers: {
"Content-Type": "application/json",
...options?.headers,
},
});
};
/**
* Search Transactions
* Advanced search for transactions with complex nested filters
*/
export const searchTransactions = <ThrowOnError extends boolean = false>(
options?: Options<SearchTransactionsData, ThrowOnError>,
) => {
return (options?.client ?? _heyApiClient).post<
SearchTransactionsResponses,
unknown,
ThrowOnError
>({
security: [
{
name: "x-secret-key",
type: "apiKey",
},
],
url: "/v1/transactions/search",
...options,
headers: {
"Content-Type": "application/json",
...options?.headers,
},
});
};
/**
* Get Activity Logs
* Get paginated activity logs for a specific transaction with tenancy enforcement
*/
export const getActivityLogs = <ThrowOnError extends boolean = false>(
options: Options<GetActivityLogsData, ThrowOnError>,
) => {
return (options.client ?? _heyApiClient).get<
GetActivityLogsResponses,
GetActivityLogsErrors,
ThrowOnError
>({
security: [
{
name: "x-secret-key",
type: "apiKey",
},
],
url: "/v1/transactions/activity-logs",
...options,
});
};
/**
* Search Activity Logs
* Search activity logs across transactions with advanced filtering and tenancy enforcement
*/
export const searchActivityLogs = <ThrowOnError extends boolean = false>(
options?: Options<SearchActivityLogsData, ThrowOnError>,
) => {
return (options?.client ?? _heyApiClient).post<
SearchActivityLogsResponses,
SearchActivityLogsErrors,
ThrowOnError
>({
security: [
{
name: "x-secret-key",
type: "apiKey",
},
],
url: "/v1/transactions/activity-logs/search",
...options,
headers: {
"Content-Type": "application/json",
...options?.headers,
},
});
};
File diff suppressed because it is too large Load Diff
+39 -25
View File
@@ -2,37 +2,51 @@ import type { Config } from "./client/client/index.js";
import { client } from "./client/client.gen.js";
export type EngineClientOptions = {
readonly clientId?: string;
readonly secretKey?: string;
readonly clientId?: string;
readonly secretKey?: string;
};
export function configure(
options: EngineClientOptions & { override?: Config },
options: EngineClientOptions & { override?: Config },
) {
client.setConfig({
bodySerializer: stringify,
headers: {
...(options.clientId && { "x-client-id": options.clientId }),
...(options.secretKey && { "x-secret-key": options.secretKey }),
},
...(options.override ?? {}),
});
client.setConfig({
bodySerializer: stringify,
headers: {
...(options.clientId && { "x-client-id": options.clientId }),
...(options.secretKey && { "x-secret-key": options.secretKey }),
},
...(options.override ?? {}),
});
}
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,
// 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;
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;
}
export type MaybeErrorResponse<D, E> = { result: D } | { error: E };
export function isErrorResponse<D, E>(
res: MaybeErrorResponse<D, E>,
): res is { error: E } {
return "error" in res;
}
export function isSuccessResponse<D, E>(
res: MaybeErrorResponse<D, E>,
): res is { result: D } {
return "result" in res;
}
+6 -1
View File
@@ -1,3 +1,8 @@
export type { CreateClientConfig } from "../client/client.gen.js";
export * from "../client/index.js";
export { configure, type EngineClientOptions } from "../configure.js";
export {
configure,
type EngineClientOptions,
isErrorResponse,
isSuccessResponse,
} from "../configure.js";
+14 -14
View File
@@ -1,16 +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"]
"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"]
}
+10 -10
View File
@@ -1,12 +1,12 @@
{
// This configuration is used for local development and type checking.
"compilerOptions": {
"baseUrl": ".",
"paths": {
"~test/*": ["./test/src/*"]
}
},
"exclude": [],
"extends": "./tsconfig.base.json",
"include": ["src", "test"]
// This configuration is used for local development and type checking.
"compilerOptions": {
"baseUrl": ".",
"paths": {
"~test/*": ["./test/src/*"]
}
},
"exclude": [],
"extends": "./tsconfig.base.json",
"include": ["src", "test"]
}
+1 -1
View File
@@ -1,4 +1,4 @@
{
"$schema": "https://biomejs.dev/schemas/2.0.0/schema.json",
"$schema": "https://biomejs.dev/schemas/2.0.4/schema.json",
"extends": "//"
}
+7
View File
@@ -1,5 +1,12 @@
# @thirdweb-dev/nebula
## 0.2.3
### Patch Changes
- Updated dependencies [[`acd5656`](https://github.com/thirdweb-dev/js/commit/acd5656d4d592e3a7ba4a3c09994418e0b654120), [`6d1d344`](https://github.com/thirdweb-dev/js/commit/6d1d344c48302d45aeb63532c84cc8e79c93e26f), [`f9d7935`](https://github.com/thirdweb-dev/js/commit/f9d7935d848cbb2dea3f5204d5bff69cd0c3a921)]:
- thirdweb@5.105.3
## 0.2.2
### Patch Changes
+1 -1
View File
@@ -1,4 +1,4 @@
{
"$schema": "https://biomejs.dev/schemas/2.0.0/schema.json",
"$schema": "https://biomejs.dev/schemas/2.0.4/schema.json",
"extends": "//"
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/nebula",
"version": "0.2.2",
"version": "0.2.3",
"repository": {
"type": "git",
"url": "git+https://github.com/thirdweb-dev/js.git#main"
+1 -1
View File
@@ -1,4 +1,4 @@
{
"$schema": "https://biomejs.dev/schemas/2.0.0/schema.json",
"$schema": "https://biomejs.dev/schemas/2.0.4/schema.json",
"extends": "//"
}
+6
View File
@@ -1,5 +1,11 @@
# @thirdweb-dev/service-utils
## 0.10.2
### Patch Changes
- [#7461](https://github.com/thirdweb-dev/js/pull/7461) [`c7f7f48`](https://github.com/thirdweb-dev/js/commit/c7f7f4822283a4d85aa36a0f255bc147c87864dc) Thanks [@arcoraven](https://github.com/arcoraven)! - chore: data in webhook payload is an object
## 0.10.1
### Patch Changes
+1 -1
View File
@@ -1,4 +1,4 @@
{
"$schema": "https://biomejs.dev/schemas/2.0.0/schema.json",
"$schema": "https://biomejs.dev/schemas/2.0.4/schema.json",
"extends": "//"
}
+1 -1
View File
@@ -69,5 +69,5 @@
}
},
"typings": "dist/types/index.d.ts",
"version": "0.10.1"
"version": "0.10.2"
}
@@ -2,6 +2,10 @@ import assert from "node:assert";
import { createId } from "@paralleldrive/cuid2";
import type { KafkaProducer } from "./kafka.js";
/**
* The event schema for webhook events.
* See more: https://github.com/thirdweb-dev/api-server/blob/dev/docs/webhooks.md
*/
interface WebhookEvent extends Record<string, unknown> {
id?: `evt_${string}`;
teamId: string;
@@ -11,9 +15,9 @@ interface WebhookEvent extends Record<string, unknown> {
*/
createdAt?: Date;
/**
* An array of your model (defined in api-server). Must not be empty.
* Your model defined in api-server.
*/
payload: Record<string, unknown>[];
data: Record<string, unknown>;
}
/**
@@ -42,7 +46,6 @@ export class WebhookEventProducer {
*/
async sendEvents(topic: string, events: WebhookEvent[]): Promise<void> {
const parsedEvents: WebhookEvent[] = events.map((event) => {
assert(event.payload.length > 0, "payload must not be empty");
assert(
event.teamId.startsWith("team_"),
"teamId must start with 'team_'",
+13
View File
@@ -1,5 +1,18 @@
# thirdweb
## 5.105.3
### Patch Changes
- [#7432](https://github.com/thirdweb-dev/js/pull/7432) [`acd5656`](https://github.com/thirdweb-dev/js/commit/acd5656d4d592e3a7ba4a3c09994418e0b654120) Thanks [@0xFirekeeper](https://github.com/0xFirekeeper)! - Introduces Session Keys to EIP-7702-powered In-App Wallets via a new createSessionKey extension
- [#7454](https://github.com/thirdweb-dev/js/pull/7454) [`6d1d344`](https://github.com/thirdweb-dev/js/commit/6d1d344c48302d45aeb63532c84cc8e79c93e26f) Thanks [@gregfromstl](https://github.com/gregfromstl)! - Prevent admin wallet from connecting when no IAW config is specified
- [#7427](https://github.com/thirdweb-dev/js/pull/7427) [`f9d7935`](https://github.com/thirdweb-dev/js/commit/f9d7935d848cbb2dea3f5204d5bff69cd0c3a921) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Support latest engine API
- Updated dependencies [[`f9d7935`](https://github.com/thirdweb-dev/js/commit/f9d7935d848cbb2dea3f5204d5bff69cd0c3a921)]:
- @thirdweb-dev/engine@3.2.0
## 5.105.2
### Patch Changes
+1 -1
View File
@@ -1,4 +1,4 @@
{
"$schema": "https://biomejs.dev/schemas/2.0.0/schema.json",
"$schema": "https://biomejs.dev/schemas/2.0.4/schema.json",
"extends": "//"
}
+1 -1
View File
@@ -404,5 +404,5 @@
}
},
"typings": "./dist/types/exports/thirdweb.d.ts",
"version": "5.105.2"
"version": "5.105.3"
}
@@ -8,14 +8,11 @@ import { baseSepolia } from "../chains/chain-definitions/base-sepolia.js";
import { sepolia } from "../chains/chain-definitions/sepolia.js";
import { getContract } from "../contract/contract.js";
import { setContractURI } from "../extensions/common/__generated__/IContractMetadata/write/setContractURI.js";
import { mintTo } from "../extensions/erc20/write/mintTo.js";
import { setApprovalForAll } from "../extensions/erc1155/__generated__/IERC1155/write/setApprovalForAll.js";
import { claimTo } from "../extensions/erc1155/drops/write/claimTo.js";
import { getAllActiveSigners } from "../extensions/erc4337/__generated__/IAccountPermissions/read/getAllActiveSigners.js";
import { sendTransaction } from "../transaction/actions/send-transaction.js";
import {
DEFAULT_ACCOUNT_FACTORY_V0_6,
ENTRYPOINT_ADDRESS_v0_6,
} from "../wallets/smart/lib/constants.js";
import { setThirdwebDomains } from "../utils/domains.js";
import { smartWallet } from "../wallets/smart/smart-wallet.js";
import { generateAccount } from "../wallets/utils/generateAccount.js";
import * as Engine from "./index.js";
@@ -34,15 +31,16 @@ describe.runIf(
let serverWallet: Engine.ServerWallet;
beforeAll(async () => {
// setThirdwebDomains({
// rpc: "rpc.thirdweb-dev.com",
// storage: "storage.thirdweb-dev.com",
// bundler: "bundler.thirdweb-dev.com",
// engineCloud: "engine.thirdweb-dev.com",
// });
setThirdwebDomains({
bundler: "bundler.thirdweb-dev.com",
engineCloud: "engine.thirdweb-dev.com",
// engineCloud: "localhost:3009",
rpc: "rpc.thirdweb-dev.com",
storage: "storage.thirdweb-dev.com",
});
serverWallet = Engine.serverWallet({
address: process.env.ENGINE_CLOUD_WALLET_ADDRESS as string,
chain: arbitrumSepolia,
chain: sepolia,
client: TEST_CLIENT,
vaultAccessToken: process.env.VAULT_TOKEN as string,
});
@@ -81,13 +79,9 @@ describe.runIf(
it("should sign typed data for EOA execution options", async () => {
const eoaServerWallet = Engine.serverWallet({
address: process.env.ENGINE_CLOUD_WALLET_ADDRESS as string,
address: process.env.ENGINE_CLOUD_WALLET_ADDRESS_EOA as string,
chain: arbitrumSepolia,
client: TEST_CLIENT,
executionOptions: {
address: process.env.ENGINE_CLOUD_WALLET_ADDRESS_EOA as string,
type: "eoa",
},
vaultAccessToken: process.env.VAULT_TOKEN as string,
});
@@ -157,14 +151,14 @@ describe.runIf(
it("should send a extension tx", async () => {
const tokenContract = getContract({
address: "0x87C52295891f208459F334975a3beE198fE75244",
address: "0x638263e3eAa3917a53630e61B1fBa685308024fa",
chain: baseSepolia,
client: TEST_CLIENT,
});
const claimTx = mintTo({
amount: "0.001",
const claimTx = setApprovalForAll({
approved: true,
contract: tokenContract,
to: serverWallet.address,
operator: "0x4b8ceC1Eb227950F0bfd034449B2781e689242A1",
});
const tx = await sendTransaction({
account: serverWallet,
@@ -175,19 +169,21 @@ describe.runIf(
it("should enqueue a batch of txs", async () => {
const tokenContract = getContract({
address: "0x87C52295891f208459F334975a3beE198fE75244",
address: "0x638263e3eAa3917a53630e61B1fBa685308024fa",
chain: baseSepolia,
client: TEST_CLIENT,
});
const claimTx1 = mintTo({
amount: "0.001",
const claimTx1 = claimTo({
contract: tokenContract,
quantity: 1n,
to: serverWallet.address,
tokenId: 2n,
});
const claimTx2 = mintTo({
amount: "0.002",
const claimTx2 = claimTo({
contract: tokenContract,
quantity: 1n,
to: serverWallet.address,
tokenId: 2n,
});
const tx = await serverWallet.enqueueBatchTransaction({
transactions: [claimTx1, claimTx2],
@@ -257,8 +253,7 @@ describe.runIf(
chain: sepolia,
client: TEST_CLIENT,
executionOptions: {
entrypointAddress: ENTRYPOINT_ADDRESS_v0_6,
factoryAddress: DEFAULT_ACCOUNT_FACTORY_V0_6,
entrypointVersion: "0.6",
signerAddress: sessionKeyAccountAddress,
smartAccountAddress: smartAccount.address,
type: "ERC4337",
+81 -27
View File
@@ -1,7 +1,8 @@
import {
type AaExecutionOptions,
type AaZksyncExecutionOptions,
type EoaExecutionOptions,
isSuccessResponse,
type SendTransactionData,
type SignMessageData,
type SpecificExecutionOptions,
sendTransaction,
signMessage,
signTypedData,
@@ -16,12 +17,15 @@ import { type Hex, toHex } from "../utils/encoding/hex.js";
import { getClientFetch } from "../utils/fetch.js";
import { stringify } from "../utils/json.js";
import { resolvePromisedValue } from "../utils/promise/resolve-promised-value.js";
import type { Prettify } from "../utils/type-utils.js";
import type {
Account,
SendTransactionOption,
} from "../wallets/interfaces/wallet.js";
import { waitForTransactionHash } from "./wait-for-tx-hash.js";
type ExecutionOptions = Prettify<SpecificExecutionOptions>;
/**
* Options for creating an server wallet.
*/
@@ -45,10 +49,7 @@ export type ServerWalletOptions = {
/**
* Optional custom execution options to use for sending transactions and signing data.
*/
executionOptions?:
| Omit<AaExecutionOptions, "chainId">
| Omit<AaZksyncExecutionOptions, "chainId">
| Omit<EoaExecutionOptions, "chainId">;
executionOptions?: ExecutionOptions;
};
export type ServerWallet = Account & {
@@ -150,20 +151,74 @@ export type ServerWallet = Account & {
export function serverWallet(options: ServerWalletOptions): ServerWallet {
const { client, vaultAccessToken, address, chain, executionOptions } =
options;
const headers: HeadersInit = {
"x-vault-access-token": vaultAccessToken,
};
const getExecutionOptions = (chainId: number) => {
return executionOptions
? {
...executionOptions,
chainId: chainId.toString(),
}
: {
chainId: chainId.toString(),
const getExecutionOptionsWithChainId = (
chainId: number,
): SendTransactionData["body"]["executionOptions"] => {
if (!executionOptions) {
return {
chainId,
from: address,
type: "auto",
};
}
switch (executionOptions.type) {
case "auto":
return {
chainId,
from: address,
type: "auto",
};
case "ERC4337":
return {
...executionOptions,
chainId,
type: "ERC4337",
};
}
};
const getSigningOptions = (
chainId: number | undefined,
): SignMessageData["body"]["signingOptions"] => {
// if no chainId passed specifically for this signature
// we HAVE TO fallback to EOA signature
if (!chainId) {
return {
from: address,
type: "eoa",
};
}
if (!executionOptions) {
return {
chainId,
from: address,
type: "auto",
};
}
switch (executionOptions.type) {
case "ERC4337": {
return {
chainId,
...executionOptions,
type: "ERC4337",
};
}
case "auto": {
return {
chainId,
from: address,
type: "auto",
};
}
}
};
const enqueueTx = async (transaction: SendTransactionOption[]) => {
@@ -184,10 +239,10 @@ export function serverWallet(options: ServerWalletOptions): ServerWallet {
}
}
const body = {
executionOptions: getExecutionOptions(chainId),
executionOptions: getExecutionOptionsWithChainId(chainId),
params: transaction.map((t) => ({
data: t.data,
to: t.to ?? undefined,
to: t.to,
value: t.value?.toString(),
})),
};
@@ -307,17 +362,16 @@ export function serverWallet(options: ServerWalletOptions): ServerWallet {
if (!signingChainId) {
throw new Error("Chain ID is required for signing messages");
}
const signResult = await signMessage({
baseUrl: getThirdwebBaseUrl("engineCloud"),
body: {
executionOptions: getExecutionOptions(signingChainId),
params: [
{
format: isBytes ? "hex" : "text",
message: engineMessage,
messageFormat: isBytes ? "hex" : "text",
},
],
signingOptions: getSigningOptions(signingChainId),
},
bodySerializer: stringify,
fetch: getClientFetch(client),
@@ -330,13 +384,13 @@ export function serverWallet(options: ServerWalletOptions): ServerWallet {
);
}
const signatureResult = signResult.data?.result.results[0];
if (signatureResult?.success) {
const signatureResult = signResult.data?.result[0];
if (signatureResult && isSuccessResponse(signatureResult)) {
return signatureResult.result.signature as Hex;
}
throw new Error(
`Failed to sign message: ${signatureResult?.error?.message || "Unknown error"}`,
`Failed to sign message: ${stringify(signatureResult?.error) || "Unknown error"}`,
);
},
signTypedData: async (typedData) => {
@@ -348,9 +402,9 @@ export function serverWallet(options: ServerWalletOptions): ServerWallet {
const signResult = await signTypedData({
baseUrl: getThirdwebBaseUrl("engineCloud"),
body: {
executionOptions: getExecutionOptions(signingChainId),
// biome-ignore lint/suspicious/noExplicitAny: TODO: fix ts / hey-api type clash
params: [typedData as any],
signingOptions: getSigningOptions(signingChainId),
},
bodySerializer: stringify,
fetch: getClientFetch(client),
@@ -363,13 +417,13 @@ export function serverWallet(options: ServerWalletOptions): ServerWallet {
);
}
const signatureResult = signResult.data?.result.results[0];
if (signatureResult?.success) {
const signatureResult = signResult.data?.result[0];
if (signatureResult && isSuccessResponse(signatureResult)) {
return signatureResult.result.signature as Hex;
}
throw new Error(
`Failed to sign message: ${signatureResult?.error?.message || "Unknown error"}`,
`Failed to sign message: ${stringify(signatureResult?.error) || "Unknown error"}`,
);
},
};
@@ -38,7 +38,7 @@ export async function waitForTransactionHash(args: {
switch (status) {
case "FAILED": {
throw new Error(
`Transaction failed: ${executionResult.error || "Unknown error"}`,
`Transaction failed: ${stringify(executionResult.error) || "Unknown error"}`,
);
}
case "CONFIRMED": {
@@ -1,5 +1,15 @@
// --- KEEEP IN SYNC with exports/wallets/in-app.ts ---
//ACCOUNT
export {
type CreateSessionKeyOptions,
createSessionKey,
isCreateSessionKeySupported,
} from "../../extensions/erc7702/account/createSessionKey.js";
export type {
Condition,
LimitType,
} from "../../extensions/erc7702/account/types.js";
export type {
GetAuthenticatedUserParams,
MultiStepAuthArgsType,
@@ -1,5 +1,15 @@
// --- KEEEP IN SYNC with exports/wallets/in-app.native.ts ---
//ACCOUNT
export {
type CreateSessionKeyOptions,
createSessionKey,
isCreateSessionKeySupported,
} from "../../extensions/erc7702/account/createSessionKey.js";
export type {
Condition,
LimitType,
} from "../../extensions/erc7702/account/types.js";
export {
getSocialIcon,
socialIcons,
@@ -0,0 +1,181 @@
import type { BaseTransactionOptions } from "../../../transaction/types.js";
import { randomBytesHex } from "../../../utils/random.js";
import type { Account } from "../../../wallets/interfaces/wallet.js";
import {
createSessionWithSig,
isCreateSessionWithSigSupported,
} from "../__generated__/MinimalAccount/write/createSessionWithSig.js";
import {
type CallSpecInput,
CallSpecRequest,
ConstraintRequest,
SessionSpecRequest,
type TransferSpecInput,
TransferSpecRequest,
UsageLimitRequest,
} from "./types.js";
/**
* @extension ERC7702
*/
export type CreateSessionKeyOptions = {
/**
* The admin account that will perform the operation.
*/
account: Account;
/**
* The address to add as a session key.
*/
sessionKeyAddress: string;
/**
* How long the session key should be valid for, in seconds.
*/
durationInSeconds: number;
/**
* Whether to grant full execution permissions to the session key.
*/
grantFullPermissions?: boolean;
/**
* Smart contract interaction policies to apply to the session key, ignored if grantFullPermissions is true.
*/
callPolicies?: CallSpecInput[];
/**
* Value transfer policies to apply to the session key, ignored if grantFullPermissions is true.
*/
transferPolicies?: TransferSpecInput[];
};
/**
* Creates session key permissions for a specified address.
* @param options - The options for the createSessionKey function.
* @param {Contract} options.contract - The EIP-7702 smart EOA contract to create the session key from
* @returns The transaction object to be sent.
* @example
* ```ts
* import { createSessionKey } from 'thirdweb/extensions/7702';
* import { sendTransaction } from 'thirdweb';
*
* const transaction = createSessionKey({
* account: account,
* contract: accountContract,
* sessionKeyAddress: TEST_ACCOUNT_A.address,
* durationInSeconds: 86400, // 1 day
* grantFullPermissions: true
*})
*
* await sendTransaction({ transaction, account });
* ```
* @extension ERC7702
*/
export function createSessionKey(
options: BaseTransactionOptions<CreateSessionKeyOptions>,
) {
const {
contract,
account,
sessionKeyAddress,
durationInSeconds,
grantFullPermissions,
callPolicies,
transferPolicies,
} = options;
if (durationInSeconds <= 0) {
throw new Error("durationInSeconds must be positive");
}
return createSessionWithSig({
async asyncParams() {
const req = {
callPolicies: (callPolicies || []).map((policy) => ({
constraints: (policy.constraints || []).map((constraint) => ({
condition: Number(constraint.condition),
index: constraint.index || BigInt(0),
limit: constraint.limit
? {
limit: constraint.limit.limit,
limitType: Number(constraint.limit.limitType),
period: constraint.limit.period,
}
: {
limit: BigInt(0),
limitType: 0,
period: BigInt(0),
},
refValue: constraint.refValue || "0x",
})),
maxValuePerUse: policy.maxValuePerUse || BigInt(0),
selector: policy.selector,
target: policy.target,
valueLimit: policy.valueLimit
? {
limit: policy.valueLimit.limit,
limitType: Number(policy.valueLimit.limitType),
period: policy.valueLimit.period,
}
: {
limit: BigInt(0),
limitType: 0,
period: BigInt(0),
},
})),
expiresAt: BigInt(Math.floor(Date.now() / 1000) + durationInSeconds),
isWildcard: grantFullPermissions ?? true,
signer: sessionKeyAddress,
transferPolicies: (transferPolicies || []).map((policy) => ({
maxValuePerUse: policy.maxValuePerUse || BigInt(0),
target: policy.target,
valueLimit: policy.valueLimit
? {
limit: policy.valueLimit.limit,
limitType: Number(policy.valueLimit.limitType),
period: policy.valueLimit.period,
}
: {
limit: BigInt(0),
limitType: 0,
period: BigInt(0),
},
})),
uid: await randomBytesHex(),
};
const signature = await account.signTypedData({
domain: {
chainId: contract.chain.id,
name: "MinimalAccount",
verifyingContract: contract.address,
version: "1",
},
message: req,
primaryType: "SessionSpec",
types: {
CallSpec: CallSpecRequest,
Constraint: ConstraintRequest,
SessionSpec: SessionSpecRequest,
TransferSpec: TransferSpecRequest,
UsageLimit: UsageLimitRequest,
},
});
return { sessionSpec: req, signature };
},
contract,
});
}
/**
* Checks if the `isCreateSessionKeySupported` method is supported by the given contract.
* @param availableSelectors An array of 4byte function selectors of the contract. You can get this in various ways, such as using "whatsabi" or if you have the ABI of the contract available you can use it to generate the selectors.
* @returns A boolean indicating if the `isAddSessionKeySupported` method is supported.
* @extension ERC7702
* @example
* ```ts
* import { isCreateSessionKeySupported } from "thirdweb/extensions/erc7702";
*
* const supported = isCreateSessionKeySupported(["0x..."]);
* ```
*/
export function isCreateSessionKeySupported(availableSelectors: string[]) {
return isCreateSessionWithSigSupported(availableSelectors);
}
@@ -0,0 +1,132 @@
import { defineChain } from "src/chains/utils.js";
import { prepareTransaction } from "src/transaction/prepare-transaction.js";
import { inAppWallet } from "src/wallets/in-app/web/in-app.js";
import type { Account } from "src/wallets/interfaces/wallet.js";
import { beforeAll, describe, expect, it } from "vitest";
import { TEST_CLIENT } from "../../../../test/src/test-clients.js";
import { TEST_ACCOUNT_A } from "../../../../test/src/test-wallets.js";
import { ZERO_ADDRESS } from "../../../constants/addresses.js";
import {
getContract,
type ThirdwebContract,
} from "../../../contract/contract.js";
import { parseEventLogs } from "../../../event/actions/parse-logs.js";
import { sendAndConfirmTransaction } from "../../../transaction/actions/send-and-confirm-transaction.js";
import { sessionCreatedEvent } from "../__generated__/MinimalAccount/events/SessionCreated.js";
import { createSessionKey } from "./createSessionKey.js";
import { Condition, LimitType } from "./types.js";
describe.runIf(process.env.TW_SECRET_KEY)(
"Session Key Behavior",
{
retry: 0,
timeout: 240_000,
},
() => {
const chainId = 11155111;
let account: Account;
let accountContract: ThirdwebContract;
beforeAll(async () => {
// Create 7702 Smart EOA
const wallet = inAppWallet({
executionMode: {
mode: "EIP7702",
sponsorGas: true,
},
});
account = await wallet.connect({
chain: defineChain(chainId),
client: TEST_CLIENT,
strategy: "guest",
});
// Send a null tx to trigger deploy/upgrade
await sendAndConfirmTransaction({
account: account,
transaction: prepareTransaction({
chain: defineChain(chainId),
client: TEST_CLIENT,
to: account.address,
value: 0n,
}),
});
// Will auto resolve abi since it's deployed
accountContract = getContract({
address: account.address,
chain: defineChain(chainId),
client: TEST_CLIENT,
});
}, 120_000);
it("should allow adding adminlike session keys", async () => {
const receipt = await sendAndConfirmTransaction({
account: account,
transaction: createSessionKey({
account: account,
contract: accountContract,
durationInSeconds: 86400,
grantFullPermissions: true, // 1 day
sessionKeyAddress: TEST_ACCOUNT_A.address,
}),
});
const logs = parseEventLogs({
events: [sessionCreatedEvent()],
logs: receipt.logs,
});
expect(logs[0]?.args.signer).toBe(TEST_ACCOUNT_A.address);
});
it("should allow adding granular session keys", async () => {
const receipt = await sendAndConfirmTransaction({
account: account,
transaction: createSessionKey({
account: account,
callPolicies: [
{
constraints: [
{
condition: Condition.Unconstrained,
index: 0n,
refValue:
"0x0000000000000000000000000000000000000000000000000000000000000000",
},
],
maxValuePerUse: 0n,
selector: "0x00000000",
target: ZERO_ADDRESS,
valueLimit: {
limit: 0n,
limitType: LimitType.Unlimited,
period: 0n,
},
},
],
contract: accountContract,
durationInSeconds: 86400, // 1 day
grantFullPermissions: false,
sessionKeyAddress: TEST_ACCOUNT_A.address,
transferPolicies: [
{
maxValuePerUse: 0n,
target: ZERO_ADDRESS,
valueLimit: {
limit: 0n,
limitType: 0,
period: 0n,
},
},
],
}),
});
const logs = parseEventLogs({
events: [sessionCreatedEvent()],
logs: receipt.logs,
});
expect(logs[0]?.args.signer).toBe(TEST_ACCOUNT_A.address);
expect(logs[0]?.args.sessionSpec.callPolicies).toHaveLength(1);
expect(logs[0]?.args.sessionSpec.transferPolicies).toHaveLength(1);
});
},
);
@@ -0,0 +1,94 @@
/* ────────────────────────────────
Enums
──────────────────────────────── */
export enum LimitType {
Unlimited = 0,
Lifetime = 1,
Allowance = 2,
}
export enum Condition {
Unconstrained = 0,
Equal = 1,
Greater = 2,
Less = 3,
GreaterOrEqual = 4,
LessOrEqual = 5,
NotEqual = 6,
}
/* ────────────────────────────────
Input types
──────────────────────────────── */
/* ---------- UsageLimit ---------- */
interface UsageLimitInput {
limitType: LimitType;
limit: bigint;
period: bigint;
}
/* ---------- Constraint ---------- */
interface ConstraintInput {
condition: Condition;
index: bigint;
refValue: `0x${string}`;
limit?: UsageLimitInput;
}
/* ---------- CallSpec ---------- */
export interface CallSpecInput {
target: `0x${string}`;
selector: `0x${string}`;
maxValuePerUse?: bigint;
valueLimit?: UsageLimitInput;
constraints?: ConstraintInput[];
}
/* ---------- TransferSpec ---------- */
export interface TransferSpecInput {
target: `0x${string}`;
maxValuePerUse?: bigint;
valueLimit?: UsageLimitInput;
}
/* ────────────────────────────────
EIP-712 structs
──────────────────────────────── */
export const UsageLimitRequest = [
{ name: "limitType", type: "uint8" },
{ name: "limit", type: "uint256" },
{ name: "period", type: "uint256" },
] as const;
export const ConstraintRequest = [
{ name: "condition", type: "uint8" },
{ name: "index", type: "uint64" },
{ name: "refValue", type: "bytes32" },
{ name: "limit", type: "UsageLimit" },
] as const;
export const CallSpecRequest = [
{ name: "target", type: "address" },
{ name: "selector", type: "bytes4" },
{ name: "maxValuePerUse", type: "uint256" },
{ name: "valueLimit", type: "UsageLimit" },
{ name: "constraints", type: "Constraint[]" },
] as const;
export const TransferSpecRequest = [
{ name: "target", type: "address" },
{ name: "maxValuePerUse", type: "uint256" },
{ name: "valueLimit", type: "UsageLimit" },
] as const;
export const SessionSpecRequest = [
{ name: "signer", type: "address" },
{ name: "isWildcard", type: "bool" },
{ name: "expiresAt", type: "uint256" },
{ name: "callPolicies", type: "CallSpec[]" },
{ name: "transferPolicies", type: "TransferSpec[]" },
{ name: "uid", type: "bytes32" },
] as const;

Some files were not shown because too many files have changed in this diff Show More