Compare commits

...
Author SHA1 Message Date
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
f3c55d7139 Version Packages (#8240)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-10-11 19:31:58 +07:00
Prithvish BaidyaandGitHub 9545b43675 Engine SDK updates (#8238) 2025-10-11 19:24:30 +07:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
869bbcfa68 Version Packages (#8237)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-10-11 07:42:22 +07:00
Prithvish BaidyaandGitHub 35c7d21e36 [Vault SDK] Add Solana operations support (#8168) 2025-10-11 07:37:27 +07:00
MananTank edf3aad8c4 [BLD-396] Dashboard: Fix changing tabs in /nfts/<id> page shows full page loading indicator (#8224)
<!--

## 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 enhances the `TokenIdPage` component by introducing `Suspense` for improved loading state handling and adjusting the layout of the loading spinner.

### Detailed summary
- Added `Suspense` for handling loading states.
- Updated the loading spinner layout with additional padding (`py-20`).
- Wrapped the tab rendering logic within the `Suspense` component for better user experience during data fetching.

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

<!-- end pr-codex -->

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

## Summary by CodeRabbit

- New Features
  - Introduced a loading experience for NFT tab content using Suspense, providing a fallback UI while content loads.
  - Improves responsiveness when switching tabs by deferring heavy content until ready.

- Style
  - Increased vertical padding for the pending NFT state to enhance visual spacing and readability.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-09 22:14:06 +00:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
ab4190e23a Version Packages (#8230)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-10-09 14:19:19 -07:00
Joaquim VergesandGitHub 9086cdba20 [Playground] Add payTo parameter to X402 payment API (#8231) 2025-10-09 14:19:00 -07:00
Joaquim VergesandGitHub 0082e996ca [SDK] Add x402 recipientAddress to payment request and replace deprecated payTo (#8226) 2025-10-09 13:51:19 -07:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
28955e0ac5 Version Packages (#8229)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-10-10 03:07:52 +07:00
FirekeeperandGitHub 05550d045f Changeset for a0c45cb25d (#8228) 2025-10-10 03:01:01 +07:00
FirekeeperandGitHub a0c45cb25d Export ERC7702 MinimalAccount helpers and events (#8227) 2025-10-10 02:57:14 +07:00
MananTank 6fc4d838ff [MNY-246] Dashboard: Add Buy button in tokens table, update supported tokens count (#8222)
<!--

## 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 token-related information and enhancing the `TokensTable` component in the dashboard, including the increase in the number of supported tokens and the addition of a buy button for each token.

### Detailed summary
- Updated token count from `3200+` to `4500+` in `page.tsx`.
- Changed description in `page.tsx` to reflect the new token count.
- Modified `TokensTable` to display the updated token count.
- Added a new button in `TokensTable` for buying tokens, including a link to the token's page.

> ✨ 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 a Buy action column with a button in the tokens table, including proper skeleton alignment for the new column.

- Style
  - Rounded token avatars for a refined look.
  - Simplified token name display by removing inline linking.

- Documentation
  - Updated copy to reflect “4500+ tokens” on the tokens page and bridge page, including Open Graph/description and UI labels.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-09 18:42:18 +00:00
MananTank fe940b3c66 [BLD-395] Dashboard: Show error UI when parsing CSV fails (#8223)
<!--

## 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 improves error handling in the `useCsvUpload` hook by adding a `try-catch` block around the CSV parsing logic. It ensures that if an error occurs during parsing or if the parsed data lacks an address, the component sets an error state and logs the error.

### Detailed summary
- Introduced a `try-catch` block around the CSV parsing logic.
- Added error handling to set `noCsv` to `true` if the parsed data's first entry does not have an `address`.
- Ensured that any errors during parsing are logged to the console.

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

<!-- end pr-codex -->

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

* **Bug Fixes**
  * Prevents crashes during CSV uploads by handling parse errors gracefully.
  * Ensures invalid or malformed CSV files are detected and flagged without updating data.
  * Maintains existing behavior for valid files, only updating data when required fields are present.
  * Improves reliability of the upload flow with clearer invalid-file handling.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-09 18:31:34 +00:00
FirekeeperandGitHub 080d7f2122 Refactor ProjectFTUX API key integration section (#8225) 2025-10-10 00:08:54 +07:00
FirekeeperandGitHub ae75073a4a Refactor project wallet setup and controls UI (#8220) 2025-10-09 22:09:20 +07:00
39 changed files with 4742 additions and 393 deletions
+9 -4
View File
@@ -119,12 +119,17 @@ export function useCsvUpload<
}
Papa.parse(csv, {
complete: (results) => {
const data = props.csvParser(results.data as T[]);
if (!data[0]?.address) {
try {
const data = props.csvParser(results.data as T[]);
if (!data[0]?.address) {
setNoCsv(true);
return;
}
setRawData(data);
} catch (error) {
console.error(error);
setNoCsv(true);
return;
}
setRawData(data);
},
header: true,
});
@@ -2,7 +2,7 @@
import { ExternalLinkIcon } from "lucide-react";
import Link from "next/link";
import { useState } from "react";
import { Suspense, useState } from "react";
import type { NFT, ThirdwebClient, ThirdwebContract } from "thirdweb";
import { getNFT as getErc721NFT } from "thirdweb/extensions/erc721";
import { getNFT as getErc1155NFT } from "thirdweb/extensions/erc1155";
@@ -89,7 +89,7 @@ export const TokenIdPage: React.FC<TokenIdPageProps> = ({
if (isPending) {
return (
<div className="flex h-[400px] items-center justify-center">
<div className="flex h-[400px] items-center justify-center py-20">
<Spinner className="size-10" />
</div>
);
@@ -179,15 +179,26 @@ export const TokenIdPage: React.FC<TokenIdPageProps> = ({
<NFTDetailsTab client={contract.client} nft={nft} />
)}
{tabs.map((tb) => {
return (
tb.title === tab && (
<div className="w-full" key={tb.title}>
{tb.children}
<Suspense
fallback={
<div className="h-full">
<div className="flex items-center gap-1.5">
<Spinner className="size-4" />
<p className="text-sm text-muted-foreground"> Loading </p>
</div>
)
);
})}
</div>
}
>
{tabs.map((tb) => {
return (
tb.title === tab && (
<div className="w-full" key={tb.title}>
{tb.children}
</div>
)
);
})}
</Suspense>
</div>
</div>
</div>
@@ -39,6 +39,7 @@ export function TokensTable(props: {
<TableHead>Price</TableHead>
<TableHead>Market cap</TableHead>
<TableHead>Volume (24h)</TableHead>
<TableHead></TableHead>
</TableRow>
</TableHeader>
<TableBody>
@@ -64,6 +65,9 @@ export function TokensTable(props: {
<TableCell>
<Skeleton className="ml-auto h-4 w-20" />
</TableCell>
<TableCell>
<Skeleton className="ml-auto h-4 w-20" />
</TableCell>
</TableRow>
))
: tokens.length > 0
@@ -81,6 +85,7 @@ export function TokensTable(props: {
<AvatarImage
src={token.iconUri ?? undefined}
alt={token.symbol}
className="rounded-full"
/>
<AvatarFallback>
{token.symbol?.slice(0, 2)?.toUpperCase()}
@@ -90,20 +95,9 @@ export function TokensTable(props: {
<span className="font-medium leading-none text-base">
{token.symbol}
</span>
<Link
href={
token.address.toLowerCase() ===
NATIVE_TOKEN_ADDRESS.toLowerCase()
? `/${token.chainId}`
: `/${token.chainId}/${token.address}`
}
prefetch={false}
className="before:absolute before:inset-0"
>
<span className="text-sm text-muted-foreground leading-none">
{token.name}
</span>
</Link>
<span className="text-sm text-muted-foreground leading-none">
{token.name}
</span>
</div>
</div>
</TableCell>
@@ -120,6 +114,29 @@ export function TokensTable(props: {
? formatUsdCompact(token.volume24hUsd)
: "N/A"}
</TableCell>
<TableCell>
<Button
asChild
variant="outline"
size="sm"
className="gap-2 rounded-full bg-card hover:border-foreground/50 text-muted-foreground hover:bg-inverted hover:text-inverted-foreground"
>
<Link
aria-label={`Buy ${token.symbol}`}
href={
token.address.toLowerCase() ===
NATIVE_TOKEN_ADDRESS.toLowerCase()
? `/${token.chainId}`
: `/${token.chainId}/${token.address}`
}
prefetch={false}
className="before:absolute before:inset-0"
>
Buy
<ArrowRightIcon className="size-3" />
</Link>
</Button>
</TableCell>
</TableRow>
);
})
@@ -36,7 +36,7 @@ export default async function Page() {
</h1>
<p className="text-muted-foreground">
85+ chains, 3200+ tokens and 9+ million routes supported
85+ chains, 4500+ tokens and 9+ million routes supported
</p>
</div>
</div>
@@ -1,45 +0,0 @@
"use client";
import { useState } from "react";
import { TabButtons } from "@/components/ui/tabs";
type TabKey =
| "ts"
| "react"
| "react-native"
| "dotnet"
| "unity"
| "unreal"
| "api"
| "curl";
const tabNames: Record<TabKey, string> = {
api: "API",
ts: "TypeScript",
react: "React",
"react-native": "React Native",
dotnet: ".NET",
unity: "Unity",
unreal: "Unreal Engine",
curl: "cURL",
};
export function IntegrateAPIKeyCodeTabs(props: {
tabs: Record<TabKey, React.ReactNode>;
}) {
const [tab, setTab] = useState<TabKey>("api");
return (
<div>
<TabButtons
tabs={Object.entries(tabNames).map(([key, name]) => ({
isActive: tab === key,
name,
onClick: () => setTab(key as TabKey),
}))}
/>
<div className="h-2" />
{props.tabs[tab]}
</div>
);
}
@@ -1,15 +1,11 @@
import {
ArrowLeftRightIcon,
ChevronRightIcon,
CircleAlertIcon,
ExternalLinkIcon,
} from "lucide-react";
import Link from "next/link";
import type { Project } from "@/api/project/projects";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { AlertDialogFooter } from "@/components/ui/alert-dialog";
import { CodeServer } from "@/components/ui/code/code.server";
import { UnderlineLink } from "@/components/ui/UnderlineLink";
import { DotNetIcon } from "@/icons/brand-icons/DotNetIcon";
import { GithubIcon } from "@/icons/brand-icons/GithubIcon";
import { ReactIcon } from "@/icons/brand-icons/ReactIcon";
@@ -25,10 +21,9 @@ import {
getProjectWallet,
type ProjectWalletSummary,
} from "@/lib/server/project-wallet";
import CreateServerWallet from "../../transactions/server-wallets/components/create-server-wallet.client";
import { ClientIDSection } from "./ClientIDSection";
import { IntegrateAPIKeyCodeTabs } from "./IntegrateAPIKeyCodeTabs";
import { ProjectWalletControls } from "./ProjectWalletControls.client";
import { ProjectWalletSetup } from "./ProjectWalletSetup.client";
import { SecretKeySection } from "./SecretKeySection";
export async function ProjectFTUX(props: {
@@ -47,10 +42,7 @@ export async function ProjectFTUX(props: {
wallet={projectWallet}
managementAccessToken={props.managementAccessToken}
/>
<IntegrateAPIKeySection
project={props.project}
teamSlug={props.teamSlug}
/>
<IntegrateAPIKeySection project={props.project} />
<ProductsSection
projectSlug={props.project.slug}
teamSlug={props.teamSlug}
@@ -84,8 +76,7 @@ export function ProjectWalletSection(props: {
Project Wallet
</p>
<p className="text-muted-foreground text-sm">
Default managed server wallet for this project. Use it for
deployments, payments, and API integrations.
Use it for deployments, payments, and API integrations.
</p>
</div>
</div>
@@ -108,22 +99,11 @@ export function ProjectWalletSection(props: {
</div>
</>
) : (
<Alert variant="info">
<CircleAlertIcon className="size-5" />
<AlertTitle>No default project wallet set</AlertTitle>
<AlertDescription>
Set a default project wallet to use for dashboard and API
integrations.
</AlertDescription>
<AlertDialogFooter className="flex justify-start sm:justify-start pt-4">
<CreateServerWallet
managementAccessToken={props.managementAccessToken}
project={props.project}
teamSlug={props.teamSlug}
setAsProjectWallet={true}
/>
</AlertDialogFooter>
</Alert>
<ProjectWalletSetup
managementAccessToken={props.managementAccessToken}
project={props.project}
teamSlug={props.teamSlug}
/>
)}
</div>
</div>
@@ -133,13 +113,7 @@ export function ProjectWalletSection(props: {
// Integrate API key section ------------------------------------------------------------
function IntegrateAPIKeySection({
project,
teamSlug,
}: {
project: Project;
teamSlug: string;
}) {
function IntegrateAPIKeySection({ project }: { project: Project }) {
const secretKeyMasked = project.secretKeys[0]?.masked;
const clientId = project.publishableKey;
@@ -161,194 +135,38 @@ function IntegrateAPIKeySection({
</div>
<div className="h-5" />
<IntegrationCodeExamples project={project} teamSlug={teamSlug} />
<div className="flex flex-col gap-3">
<p className="text-muted-foreground text-sm">
Run this command in your terminal to send a test transaction using
your Project Wallet.
</p>
<CodeServer
className="bg-background"
code={curlCodeExample(project)}
lang="bash"
/>
</div>
</div>
</section>
);
}
function IntegrationCodeExamples(props: {
project: Project;
teamSlug: string;
}) {
return (
<IntegrateAPIKeyCodeTabs
tabs={{
dotnet: (
<div className="flex flex-col gap-3">
<CodeServer
className="bg-background"
code={dotNotCodeExample(props.project)}
lang="csharp"
/>
<Alert className="bg-background" variant="info">
<CircleAlertIcon className="size-5" />
<AlertTitle>
Configure your app's bundle ID in "Allowed Bundle IDs" in
Project
</AlertTitle>
<AlertDescription className="leading-loose">
Go to{" "}
<UnderlineLink
href={`/team/${props.teamSlug}/${props.project.slug}/settings`}
target="_blank"
>
Project settings
</UnderlineLink>{" "}
and add your app's bundle ID to the "Allowed Bundle IDs" list.
</AlertDescription>
</Alert>
</div>
),
react: (
<CodeServer
className="bg-background"
code={reactCodeExample(props.project)}
lang="ts"
/>
),
"react-native": (
<CodeServer
className="bg-background"
code={reactCodeExample(props.project)}
lang="ts"
/>
),
ts: (
<CodeServer
className="bg-background"
code={typescriptCodeExample(props.project)}
lang="ts"
/>
),
unity: (
<Alert className="bg-background" variant="info">
<CircleAlertIcon className="size-5" />
<AlertTitle>
Configure Client ID in Thirdweb Manager prefab
</AlertTitle>
<AlertDescription className="leading-loose">
Configure "Client ID" and "Bundle ID" in{" "}
<UnderlineLink
href="https://portal.thirdweb.com/unity/v5/thirdwebmanager"
rel="noopener noreferrer"
target="_blank"
>
Thirdweb Manager prefab
</UnderlineLink>
<span className="block text-sm">
Make sure to configure your app's bundle ID in "Allowed Bundle
IDs" in{" "}
<UnderlineLink
href={`/team/${props.teamSlug}/${props.project.slug}/settings`}
rel="noopener noreferrer"
target="_blank"
>
Project settings
</UnderlineLink>
</span>
</AlertDescription>
</Alert>
),
unreal: (
<Alert className="bg-background" variant="info">
<CircleAlertIcon className="size-5" />
<AlertTitle>
Configure Client ID in Thirdweb Unreal Plugin{" "}
</AlertTitle>
<AlertDescription className="leading-loose">
Configure "Client ID" and "Bundle ID" in{" "}
<UnderlineLink
href="https://portal.thirdweb.com/unreal-engine/getting-started"
rel="noopener noreferrer"
target="_blank"
>
thirdweb plugin settings
</UnderlineLink>
<span className="block text-sm">
Make sure to configure your app's bundle ID in "Allowed Bundle
IDs" in{" "}
<UnderlineLink
href={`/team/${props.teamSlug}/${props.project.slug}/settings`}
rel="noopener noreferrer"
target="_blank"
>
Project settings
</UnderlineLink>
</span>
</AlertDescription>
</Alert>
),
api: (
<CodeServer
className="bg-background"
code={apiCodeExample(props.project)}
lang="javascript"
/>
),
curl: (
<CodeServer
className="bg-background"
code={curlCodeExample(props.project)}
lang="bash"
/>
),
}}
/>
);
}
const typescriptCodeExample = (project: Project) => `\
import { createThirdwebClient } from "thirdweb";
const client = createThirdwebClient({
// use clientId for client side usage
clientId: "${project.publishableKey}",
// use secretKey for server side usage
secretKey: "${project.secretKeys[0]?.masked}", // replace this with full secret key
});`;
const reactCodeExample = (project: Project) => `\
import { createThirdwebClient } from "thirdweb";
const client = createThirdwebClient({
clientId: "${project.publishableKey}",
});`;
const dotNotCodeExample = (project: Project) => `\
using Thirdweb;
// For web applications
var client = ThirdwebClient.Create(clientId: "${project.publishableKey}");
// For native applications - Replace "yourBundleId" with your app's bundle ID
var client = ThirdwebClient.Create(clientId: "${project.publishableKey}", bundleId: "yourBundleId");
// For backend applications (Note: below shown secret key is not the full secret key)
var client = ThirdwebClient.Create(secretKey: "${project.secretKeys[0]?.masked}");`;
const apiCodeExample = (project: Project): string => `\
// Server-side only: replace with your full secret key. Never expose in browser code.
fetch('https://api.thirdweb.com/v1/wallets/server', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-secret-key': '${project.secretKeys[0]?.masked ?? "<YOUR_SECRET_KEY>"}'
},
body: JSON.stringify({
identifier: 'treasury-wallet-123'
})
});`;
const curlCodeExample = (project: Project): string => `\
// Server-side only: replace with your full secret key. Never expose in browser code.
curl https://api.thirdweb.com/v1/wallets/server \\
curl https://api.thirdweb.com/v1/transactions \\
--request POST \\
--header 'Content-Type: application/json' \\
--header 'x-secret-key: ${project.secretKeys[0]?.masked ?? "<YOUR_SECRET_KEY>"}' \\
--data '{
"identifier": "treasury-wallet-123"
}'`;
"chainId": 421614,
"transactions": [
{
"data": "0x",
"to": "vitalik.eth",
"value": "0"
}
]
}'
`;
// products section ------------------------------------------------------------
function ProductsSection(props: { teamSlug: string; projectSlug: string }) {
@@ -48,9 +48,14 @@ import {
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { Spinner } from "@/components/ui/Spinner";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { getClientThirdwebClient } from "@/constants/thirdweb-client.client";
import { useV5DashboardChain } from "@/hooks/chains/v5-adapter";
import { useDashboardRouter } from "@/lib/DashboardRouter";
@@ -163,7 +168,7 @@ export function ProjectWalletControls(props: ProjectWalletControlsProps) {
},
onSuccess: async (_, wallet) => {
const descriptionLabel = wallet.label ?? wallet.address;
toast.success("Default project wallet updated", {
toast.success("Project wallet updated", {
description: `Now pointing to ${descriptionLabel}`,
});
await queryClient.invalidateQueries({
@@ -347,49 +352,66 @@ export function ProjectWalletControls(props: ProjectWalletControlsProps) {
default.
</p>
) : (
<RadioGroup
className="space-y-3"
onValueChange={(value) => setSelectedWalletId(value)}
value={selectedWalletId}
>
{serverWallets.map((wallet) => {
const isCurrent =
wallet.address.toLowerCase() === currentWalletAddressLower;
const isSelected = wallet.id === selectedWalletId;
return (
<Label
key={wallet.id}
htmlFor={`project-wallet-${wallet.id}`}
className={cn(
"flex cursor-pointer items-center gap-3 rounded-lg border border-border bg-card px-4 py-3 transition",
isSelected && "border-primary ring-2 ring-primary/20",
)}
>
<RadioGroupItem
id={`project-wallet-${wallet.id}`}
value={wallet.id}
className="mt-0.5"
/>
<div className="flex flex-1 flex-col overflow-hidden">
<div className="flex flex-col gap-3">
<Select
onValueChange={(value) => setSelectedWalletId(value)}
value={selectedWalletId ?? ""}
>
<SelectTrigger className="w-full">
<SelectValue placeholder="Select server wallet">
{selectedWallet ? (
<div className="flex items-center gap-2">
<span className="text-sm font-medium text-foreground">
{wallet.label ?? "Unnamed server wallet"}
</span>
{isCurrent && (
<Badge variant="success" className="text-xs">
current
</Badge>
)}
<WalletAddress
address={selectedWallet.address}
client={client}
/>
<div className="flex items-center gap-2">
<span className="text-sm text-foreground">
{selectedWallet.label ?? "Unnamed server wallet"}
</span>
{selectedWallet.address.toLowerCase() ===
currentWalletAddressLower && (
<Badge variant="success" className="text-xs">
current
</Badge>
)}
</div>
</div>
<span className="text-xs text-muted-foreground break-all">
{wallet.address}
</span>
</div>
</Label>
);
})}
</RadioGroup>
) : (
"Select server wallet"
)}
</SelectValue>
</SelectTrigger>
<SelectContent>
{serverWallets.map((wallet) => {
const isCurrent =
wallet.address.toLowerCase() ===
currentWalletAddressLower;
return (
<SelectItem key={wallet.id} value={wallet.id}>
<div className="flex items-center gap-2">
<WalletAddress
address={wallet.address}
client={client}
/>
<div className="flex items-center gap-2">
<span className="text-sm text-foreground">
{wallet.label ?? "Unnamed server wallet"}
</span>
{isCurrent && (
<Badge variant="success" className="text-xs">
current
</Badge>
)}
</div>
</div>
</SelectItem>
);
})}
</SelectContent>
</Select>
</div>
)}
</div>
@@ -0,0 +1,215 @@
"use client";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { CircleAlertIcon } from "lucide-react";
import { useMemo, useState } from "react";
import { toast } from "sonner";
import { listProjectServerWallets } from "@/actions/project-wallet/list-server-wallets";
import type { Project } from "@/api/project/projects";
import { WalletAddress } from "@/components/blocks/wallet-address";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/Spinner";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { getClientThirdwebClient } from "@/constants/thirdweb-client.client";
import { useDashboardRouter } from "@/lib/DashboardRouter";
import type { ProjectWalletSummary } from "@/lib/server/project-wallet";
import { updateDefaultProjectWallet } from "../../transactions/lib/vault.client";
import CreateServerWallet from "../../transactions/server-wallets/components/create-server-wallet.client";
export function ProjectWalletSetup(props: {
project: Project;
teamSlug: string;
managementAccessToken: string | undefined;
}) {
const { project, teamSlug, managementAccessToken } = props;
const router = useDashboardRouter();
const queryClient = useQueryClient();
const client = useMemo(() => getClientThirdwebClient(), []);
const [selectedWalletId, setSelectedWalletId] = useState<string | undefined>(
undefined,
);
const serverWalletsQuery = useQuery({
enabled: Boolean(managementAccessToken),
queryFn: async () => {
if (!managementAccessToken) {
return [] as ProjectWalletSummary[];
}
return listProjectServerWallets({
managementAccessToken,
projectId: project.id,
});
},
queryKey: ["project", project.id, "server-wallets", managementAccessToken],
staleTime: 60_000,
});
const serverWallets = serverWalletsQuery.data ?? [];
const effectiveSelectedWalletId = useMemo(() => {
if (
selectedWalletId &&
serverWallets.some((wallet) => wallet.id === selectedWalletId)
) {
return selectedWalletId;
}
return serverWallets[0]?.id;
}, [selectedWalletId, serverWallets]);
const selectedWallet = useMemo(() => {
if (!effectiveSelectedWalletId) {
return undefined;
}
return serverWallets.find(
(wallet) => wallet.id === effectiveSelectedWalletId,
);
}, [effectiveSelectedWalletId, serverWallets]);
const setDefaultWalletMutation = useMutation({
mutationFn: async (wallet: ProjectWalletSummary) => {
await updateDefaultProjectWallet({
project,
projectWalletAddress: wallet.address,
});
},
onError: (error) => {
const message =
error instanceof Error
? error.message
: "Failed to update project wallet";
toast.error(message);
},
onSuccess: async (_, wallet) => {
const descriptionLabel = wallet.label ?? wallet.address;
toast.success("Project wallet updated", {
description: `Now pointing to ${descriptionLabel}`,
});
await queryClient.invalidateQueries({
queryKey: [
"project",
project.id,
"server-wallets",
managementAccessToken,
],
});
router.refresh();
},
});
const canSelectExisting =
Boolean(managementAccessToken) && serverWallets.length > 0;
return (
<Alert variant="info">
<CircleAlertIcon className="size-5" />
<AlertTitle>No project wallet set</AlertTitle>
<AlertDescription>
Set a project wallet to use for dashboard and API integrations.
</AlertDescription>
<div className="mt-4 space-y-5">
{serverWalletsQuery.isLoading ? (
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<Spinner className="size-4" />
<span>Loading existing server wallets…</span>
</div>
) : canSelectExisting ? (
<div className="space-y-4">
<div className="space-y-2">
<p className="text-sm font-medium text-foreground">
Choose an existing server wallet
</p>
<p className="text-sm text-muted-foreground">
These wallets were already created for this project. Pick one to
set as the default.
</p>
<Select
onValueChange={(value) => setSelectedWalletId(value)}
value={effectiveSelectedWalletId ?? ""}
>
<SelectTrigger className="w-full">
<SelectValue placeholder="Select server wallet">
{selectedWallet ? (
<div className="flex items-center gap-2">
<WalletAddress
address={selectedWallet.address}
client={client}
/>
<span className="text-muted-foreground text-sm">
{selectedWallet.label ?? "Unnamed server wallet"}
</span>
</div>
) : (
"Select server wallet"
)}
</SelectValue>
</SelectTrigger>
<SelectContent>
{serverWallets.map((wallet) => (
<SelectItem key={wallet.id} value={wallet.id}>
<div className="flex items-center gap-2">
<WalletAddress
address={wallet.address}
client={client}
/>
<span className="text-muted-foreground text-sm">
{wallet.label ?? "Unnamed server wallet"}
</span>
</div>
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="flex flex-wrap items-center gap-3">
<Button
disabled={!selectedWallet || setDefaultWalletMutation.isPending}
onClick={() => {
if (selectedWallet) {
setDefaultWalletMutation.mutate(selectedWallet);
}
}}
type="button"
>
{setDefaultWalletMutation.isPending && (
<Spinner className="mr-2 size-4" />
)}
Set as project wallet
</Button>
<CreateServerWallet
managementAccessToken={managementAccessToken}
project={project}
teamSlug={teamSlug}
setAsProjectWallet
/>
</div>
</div>
) : (
<div className="space-y-3 text-sm text-muted-foreground">
<p>
Create a server wallet to start using transactions and other
project features.
</p>
<CreateServerWallet
managementAccessToken={managementAccessToken}
project={project}
teamSlug={teamSlug}
setAsProjectWallet
/>
</div>
)}
</div>
</Alert>
);
}
@@ -292,7 +292,7 @@ function ServerWalletTableRow(props: {
queryKey: ["smart-account-address", wallet.address, chainId],
});
// Get the default project wallet address
// Get the project wallet address
const engineCloudService = project.services.find(
(service) => service.name === "engineCloud",
);
@@ -422,7 +422,7 @@ function WalletActionsDropdown(props: {
});
},
onSuccess: () => {
toast.success("Wallet set as default project wallet");
toast.success("Wallet set as project wallet");
router.refresh();
},
onError: (error) => {
+2 -2
View File
@@ -14,7 +14,7 @@ import { bridgeAppThirdwebClient } from "./constants";
const title = "thirdweb Bridge: Buy, Bridge & Swap Crypto on 85+ Chains";
const description =
"Bridge and swap 3,200+ tokens across 85+ chains (Ethereum, Base, Optimism, Arbitrum, BNB & more). Best-price routing with near-instant finality";
"Bridge and swap 4500+ tokens across 85+ chains (Ethereum, Base, Optimism, Arbitrum, BNB & more). Best-price routing with near-instant finality";
export const metadata: Metadata = {
description,
@@ -73,7 +73,7 @@ export default async function BridgePage({
<div className="flex-wrap flex gap-3 items-center justify-center">
<DataPill>85+ Chains Supported</DataPill>
<DataPill>3200+ Tokens Supported</DataPill>
<DataPill>4500+ Tokens Supported</DataPill>
<DataPill>9+ Million Routes Available</DataPill>
</div>
</div>
@@ -7,24 +7,25 @@ import { token } from "../../payments/x402/components/constants";
// Allow streaming responses up to 5 minutes
export const maxDuration = 300;
const client = createThirdwebClient({
secretKey: process.env.THIRDWEB_SECRET_KEY as string,
});
const BACKEND_WALLET_ADDRESS = process.env.ENGINE_BACKEND_WALLET as string;
// const BACKEND_WALLET_ADDRESS = process.env.ENGINE_BACKEND_SMART_WALLET as string;
const ENGINE_VAULT_ACCESS_TOKEN = process.env
.ENGINE_VAULT_ACCESS_TOKEN as string;
// const API_URL = `https://${process.env.NEXT_PUBLIC_API_URL || "api.thirdweb.com"}`;
const API_URL = "http://localhost:3030";
const twFacilitator = facilitator({
baseUrl: `${API_URL}/v1/payments/x402`,
client,
serverWalletAddress: BACKEND_WALLET_ADDRESS,
vaultAccessToken: ENGINE_VAULT_ACCESS_TOKEN,
});
export async function GET(request: NextRequest) {
const client = createThirdwebClient({
secretKey: process.env.THIRDWEB_SECRET_KEY as string,
});
const BACKEND_WALLET_ADDRESS = process.env.ENGINE_BACKEND_WALLET as string;
// const BACKEND_WALLET_ADDRESS = process.env.ENGINE_BACKEND_SMART_WALLET as string;
const ENGINE_VAULT_ACCESS_TOKEN = process.env
.ENGINE_VAULT_ACCESS_TOKEN as string;
const API_URL = `https://${process.env.NEXT_PUBLIC_API_URL || "api.thirdweb.com"}`;
const twFacilitator = facilitator({
baseUrl: `${API_URL}/v1/payments/x402`,
client,
serverWalletAddress: BACKEND_WALLET_ADDRESS,
vaultAccessToken: ENGINE_VAULT_ACCESS_TOKEN,
});
const paymentData = request.headers.get("X-PAYMENT");
const queryParams = request.nextUrl.searchParams;
@@ -38,6 +39,7 @@ export async function GET(request: NextRequest) {
}
const amount = queryParams.get("amount") || "0.01";
const payTo = queryParams.get("payTo") ?? undefined;
const tokenAddress = queryParams.get("tokenAddress") || token.address;
const decimals = queryParams.get("decimals") || token.decimals.toString();
const waitUntil =
@@ -49,6 +51,7 @@ export async function GET(request: NextRequest) {
method: "GET",
paymentData,
network: defineChain(Number(chainId)),
payTo,
price: {
amount: toUnits(amount, parseInt(decimals)).toString(),
asset: {
@@ -45,6 +45,7 @@ export function X402LeftSection(props: {
const tokenId = useId();
const amountId = useId();
const waitUntilId = useId();
const payToId = useId();
const handleChainChange = (chainId: number) => {
setSelectedChain(chainId);
@@ -81,6 +82,13 @@ export function X402LeftSection(props: {
}));
};
const handlePayToChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setOptions((v) => ({
...v,
payTo: e.target.value as `0x${string}`,
}));
};
const handleWaitUntilChange = (
value: "simulated" | "submitted" | "confirmed",
) => {
@@ -140,6 +148,22 @@ export function X402LeftSection(props: {
)}
</div>
{/* Pay To input */}
<div className="flex flex-col gap-2">
<Label htmlFor={payToId}>Pay To Address</Label>
<Input
id={payToId}
type="text"
placeholder="0x..."
value={options.payTo}
onChange={handlePayToChange}
className="bg-card"
/>
<p className="text-sm text-muted-foreground">
The wallet address that will receive the payment
</p>
</div>
{/* Wait Until selection */}
<div className="flex flex-col gap-2">
<Label htmlFor={waitUntilId}>Wait Until</Label>
+24
View File
@@ -1,5 +1,29 @@
# wagmi-inapp
## 0.0.8
### Patch Changes
- Updated dependencies []:
- thirdweb@5.108.13
- @thirdweb-dev/wagmi-adapter@0.2.163
## 0.0.7
### Patch Changes
- Updated dependencies [[`0082e99`](https://github.com/thirdweb-dev/js/commit/0082e996cae47a326110e74457c72b3f3936743d), [`0082e99`](https://github.com/thirdweb-dev/js/commit/0082e996cae47a326110e74457c72b3f3936743d)]:
- thirdweb@5.108.12
- @thirdweb-dev/wagmi-adapter@0.2.162
## 0.0.6
### Patch Changes
- Updated dependencies [[`05550d0`](https://github.com/thirdweb-dev/js/commit/05550d045f291b8e53afd0d7e73c438bee202fb7)]:
- thirdweb@5.108.11
- @thirdweb-dev/wagmi-adapter@0.2.161
## 0.0.5
### Patch Changes
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "wagmi-inapp",
"private": true,
"version": "0.0.5",
"version": "0.0.8",
"type": "module",
"scripts": {
"dev": "vite",
+6
View File
@@ -1,5 +1,11 @@
# @thirdweb-dev/engine
## 3.3.0
### Minor Changes
- [#8238](https://github.com/thirdweb-dev/js/pull/8238) [`9545b43`](https://github.com/thirdweb-dev/js/commit/9545b4367598408dbb88c63d8a8fe21305f870b5) Thanks [@d4mr](https://github.com/d4mr)! - Add Solana endpoints
## 3.2.2
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/engine",
"version": "3.2.2",
"version": "3.3.0",
"repository": {
"type": "git",
"url": "git+https://github.com/thirdweb-dev/js.git#main"
+251
View File
@@ -11,11 +11,18 @@ import type {
CancelTransactionResponses,
CreateAccountData,
CreateAccountResponses,
CreateSolanaAccountData,
CreateSolanaAccountResponses,
EncodeContractData,
EncodeContractResponses,
GetActivityLogsData,
GetActivityLogsErrors,
GetActivityLogsResponses,
GetSolanaActivityLogsData,
GetSolanaActivityLogsErrors,
GetSolanaActivityLogsResponses,
GetSolanaTransactionsData,
GetSolanaTransactionsResponses,
GetTransactionAnalyticsData,
GetTransactionAnalyticsResponses,
GetTransactionAnalyticsSummaryData,
@@ -25,17 +32,30 @@ import type {
GetTransactionsResponses,
ListAccountsData,
ListAccountsResponses,
ListSolanaAccountsData,
ListSolanaAccountsResponses,
ReadContractData,
ReadContractResponses,
SearchActivityLogsData,
SearchActivityLogsErrors,
SearchActivityLogsResponses,
SearchSolanaActivityLogsData,
SearchSolanaActivityLogsErrors,
SearchSolanaActivityLogsResponses,
SearchSolanaTransactionsData,
SearchSolanaTransactionsResponses,
SearchTransactionsData,
SearchTransactionsResponses,
SendSolanaTransactionData,
SendSolanaTransactionResponses,
SendTransactionData,
SendTransactionResponses,
SignMessageData,
SignMessageResponses,
SignSolanaMessageData,
SignSolanaMessageResponses,
SignSolanaTransactionData,
SignSolanaTransactionResponses,
SignTypedDataData,
SignTypedDataResponses,
WriteContractData,
@@ -227,6 +247,33 @@ export const encodeContract = <ThrowOnError extends boolean = false>(
});
};
/**
* Send Solana Transaction
* Execute a Solana transaction with custom instructions
*/
export const sendSolanaTransaction = <ThrowOnError extends boolean = false>(
options: Options<SendSolanaTransactionData, ThrowOnError>,
) => {
return (options.client ?? _heyApiClient).post<
SendSolanaTransactionResponses,
unknown,
ThrowOnError
>({
security: [
{
name: "x-secret-key",
type: "apiKey",
},
],
url: "/v1/solana/transaction",
...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.
@@ -300,6 +347,110 @@ export const createAccount = <ThrowOnError extends boolean = false>(
});
};
/**
* List Solana Accounts
* List all Solana wallets provisioned for the project.
*/
export const listSolanaAccounts = <ThrowOnError extends boolean = false>(
options?: Options<ListSolanaAccountsData, ThrowOnError>,
) => {
return (options?.client ?? _heyApiClient).get<
ListSolanaAccountsResponses,
unknown,
ThrowOnError
>({
security: [
{
name: "x-secret-key",
type: "apiKey",
},
],
url: "/v1/solana/accounts",
...options,
});
};
/**
* Get or Create Solana Account
* Create a new solana wallet in the vault or return the existing wallet for a given label.
*/
export const createSolanaAccount = <ThrowOnError extends boolean = false>(
options?: Options<CreateSolanaAccountData, ThrowOnError>,
) => {
return (options?.client ?? _heyApiClient).post<
CreateSolanaAccountResponses,
unknown,
ThrowOnError
>({
security: [
{
name: "x-secret-key",
type: "apiKey",
},
],
url: "/v1/solana/accounts",
...options,
headers: {
"Content-Type": "application/json",
...options?.headers,
},
});
};
/**
* Sign Solana Message
* Sign an arbitrary message with a Solana wallet.
*/
export const signSolanaMessage = <ThrowOnError extends boolean = false>(
options?: Options<SignSolanaMessageData, ThrowOnError>,
) => {
return (options?.client ?? _heyApiClient).post<
SignSolanaMessageResponses,
unknown,
ThrowOnError
>({
security: [
{
name: "x-secret-key",
type: "apiKey",
},
],
url: "/v1/solana/sign-message",
...options,
headers: {
"Content-Type": "application/json",
...options?.headers,
},
});
};
/**
* Sign Solana Transaction
* Sign a serialized Solana transaction.
*/
export const signSolanaTransaction = <ThrowOnError extends boolean = false>(
options?: Options<SignSolanaTransactionData, ThrowOnError>,
) => {
return (options?.client ?? _heyApiClient).post<
SignSolanaTransactionResponses,
unknown,
ThrowOnError
>({
security: [
{
name: "x-secret-key",
type: "apiKey",
},
],
url: "/v1/solana/sign-transaction",
...options,
headers: {
"Content-Type": "application/json",
...options?.headers,
},
});
};
/**
* Get Transactions
* Search transactions with various filters and pagination
@@ -455,3 +606,103 @@ export const searchActivityLogs = <ThrowOnError extends boolean = false>(
},
});
};
/**
* Get Solana Transactions
* Search Solana transactions with various filters and pagination
*/
export const getSolanaTransactions = <ThrowOnError extends boolean = false>(
options?: Options<GetSolanaTransactionsData, ThrowOnError>,
) => {
return (options?.client ?? _heyApiClient).get<
GetSolanaTransactionsResponses,
unknown,
ThrowOnError
>({
security: [
{
name: "x-secret-key",
type: "apiKey",
},
],
url: "/v1/solana/transactions",
...options,
});
};
/**
* Search Solana Transactions
* Advanced search for Solana transactions with complex nested filters
*/
export const searchSolanaTransactions = <ThrowOnError extends boolean = false>(
options?: Options<SearchSolanaTransactionsData, ThrowOnError>,
) => {
return (options?.client ?? _heyApiClient).post<
SearchSolanaTransactionsResponses,
unknown,
ThrowOnError
>({
security: [
{
name: "x-secret-key",
type: "apiKey",
},
],
url: "/v1/solana/transactions/search",
...options,
headers: {
"Content-Type": "application/json",
...options?.headers,
},
});
};
/**
* Get Solana Activity Logs
* Get paginated activity logs for a specific Solana transaction with tenancy enforcement
*/
export const getSolanaActivityLogs = <ThrowOnError extends boolean = false>(
options: Options<GetSolanaActivityLogsData, ThrowOnError>,
) => {
return (options.client ?? _heyApiClient).get<
GetSolanaActivityLogsResponses,
GetSolanaActivityLogsErrors,
ThrowOnError
>({
security: [
{
name: "x-secret-key",
type: "apiKey",
},
],
url: "/v1/solana/transactions/activity-logs",
...options,
});
};
/**
* Search Solana Activity Logs
* Search Solana activity logs across transactions with advanced filtering and tenancy enforcement
*/
export const searchSolanaActivityLogs = <ThrowOnError extends boolean = false>(
options?: Options<SearchSolanaActivityLogsData, ThrowOnError>,
) => {
return (options?.client ?? _heyApiClient).post<
SearchSolanaActivityLogsResponses,
SearchSolanaActivityLogsErrors,
ThrowOnError
>({
security: [
{
name: "x-secret-key",
type: "apiKey",
},
],
url: "/v1/solana/transactions/activity-logs/search",
...options,
headers: {
"Content-Type": "application/json",
...options?.headers,
},
});
};
+792
View File
@@ -17,6 +17,19 @@ export type TransactionsFilterNested = {
filters: Array<TransactionsFilterValue | TransactionsFilterNested>;
};
export type SolanaTransactionsFilterValue = {
field: "id" | "chainId" | "signerAddress" | "signature";
values: Array<string>;
operation: "AND" | "OR";
};
export type SolanaTransactionsFilterNested = {
operation: "AND" | "OR";
filters: Array<
SolanaTransactionsFilterValue | SolanaTransactionsFilterNested
>;
};
/**
* EVM Address
* Used to represent an EVM address. This is a string of length 42 with a `0x` prefix. Non-checksummed addresses are also supported, but will be converted to checksummed.
@@ -112,6 +125,21 @@ export type BatchResultItemEncodeResultSuccessItemEngineError =
kind: RpcErrorKind;
type: "BUNDLER_ERROR";
}
| {
/**
* Solana chain identifier
*/
chain_id: string;
/**
* Human-readable error message
*/
message: string;
/**
* Structured error kind
*/
kind: SolanaRpcErrorKind;
type: "SOLANA_RPC_ERROR";
}
| {
message: string;
type: "VAULT_ERROR";
@@ -202,6 +230,21 @@ export type BatchResultItemReadResultSuccessItemEngineError =
kind: RpcErrorKind;
type: "BUNDLER_ERROR";
}
| {
/**
* Solana chain identifier
*/
chain_id: string;
/**
* Human-readable error message
*/
message: string;
/**
* Structured error kind
*/
kind: SolanaRpcErrorKind;
type: "SOLANA_RPC_ERROR";
}
| {
message: string;
type: "VAULT_ERROR";
@@ -301,6 +344,21 @@ export type BatchResultItemSignResultDataEngineError =
kind: RpcErrorKind;
type: "BUNDLER_ERROR";
}
| {
/**
* Solana chain identifier
*/
chain_id: string;
/**
* Human-readable error message
*/
message: string;
/**
* Structured error kind
*/
kind: SolanaRpcErrorKind;
type: "SOLANA_RPC_ERROR";
}
| {
message: string;
type: "VAULT_ERROR";
@@ -392,6 +450,11 @@ export type CancelResult =
}
| "NOT_FOUND";
/**
* Commitment Level
*/
export type CommitmentLevel = "confirmed" | "finalized";
/**
* Represents a contract function call with parameters
*
@@ -765,6 +828,8 @@ export type InnerTransaction = (null | TransactionTypeData) & {
gasLimit?: number | null;
};
export type InstructionDataEncoding = "hex" | "base64";
export type MessageFormatDef = "text" | "hex";
/**
@@ -783,6 +848,29 @@ export type MessageInput = {
export type MulticallDocType = boolean | AddressDef;
/**
* Pubkey
*/
export type PubkeyDef = string;
/**
* Response for a queued Solana transaction
*/
export type QueuedSolanaTransactionResponse = {
/**
* The idempotency key for this transaction
*/
transactionId: string;
/**
* The Solana chain
*/
chainId: SolanaChainId;
/**
* The signer address
*/
signerAddress: string;
};
/**
* # QueuedTransaction
* Response for any request that queues one or more transactions
@@ -909,6 +997,28 @@ export type RpcErrorResponse = {
data?: string | null;
};
/**
* Request to send a Solana transaction
*/
export type SendSolanaTransactionRequest = {
/**
* Idempotency key for this transaction (defaults to random UUID)
*/
idempotencyKey?: string;
/**
* List of Solana instructions to execute
*/
instructions: Array<SolanaInstructionData>;
/**
* Solana execution options
*/
executionOptions: SolanaExecutionOptions;
/**
* Webhook options for transaction status notifications
*/
webhookOptions?: Array<WebhookOptions>;
};
/**
* Incoming transaction request, parsed into InnerTransaction
* Exposed API will have varying `params` but will all parse into InnerTransaction before execution
@@ -975,6 +1085,9 @@ export type SerialisableAwsSignerError =
| {
message: string;
type: "UNKNOWN";
}
| {
type: "SIGNATURE_RECOVERY_FAILED";
};
export type SerializableReqwestError =
@@ -1149,6 +1262,196 @@ export type SigningOptions =
chainId: number;
};
/**
* Account metadata for Solana instructions
*/
export type SolanaAccountMeta = {
/**
* Public key of the account
*/
pubkey: PubkeyDef;
/**
* Whether the account should sign the transaction
*/
isSigner: boolean;
/**
* Whether the account is writable
*/
isWritable: boolean;
};
/**
* Solana chain identifier
*/
export type SolanaChainId = "solana:mainnet" | "solana:devnet" | "solana:local";
/**
* ### Solana Execution Options
* This struct configures Solana transaction execution.
*
* Solana execution sends transactions directly to Solana RPC nodes with
* support for priority fees, compute budgets, and commitment levels.
*
* ### Use Cases
* - SOL transfers
* - SPL token operations
* - Program interactions (NFTs, DeFi, etc.)
* - Arbitrary instruction composition
*
* ### Features
* - Versioned transactions with address lookup tables
* - Priority fee configuration for faster inclusion
* - Compute budget optimization
* - Configurable commitment levels
* - Blockhash retry mechanism
*/
export type SolanaExecutionOptions = {
/**
* The Solana address (public key) to send transactions from
* This account must have sufficient SOL to pay for transaction fees
*/
signerAddress: PubkeyDef;
/**
* Solana chain to use
*/
chainId: SolanaChainId;
/**
* Maximum number of times to retry fetching a new blockhash if transaction fails
* Default: 0 (fail fast)
* Max: 1000
*/
maxBlockhashRetries?: number;
/**
* Commitment level for transaction confirmation
* Options: "processed", "confirmed", "finalized"
* Default: "finalized"
*/
commitment?: CommitmentLevel;
priorityFee?: null | SolanaPriorityFee;
/**
* Compute unit limit for the transaction. If omitted, the transaction will use the default compute unit limit. If provided, engine will add the compute budget instruction to the transaction.
*/
computeUnitLimit?: number | null;
};
/**
* Solana instruction data provided by the user
* This is a simplified representation that will be converted to a proper Instruction
*/
export type SolanaInstructionData = {
/**
* Program ID to invoke
*/
programId: PubkeyDef;
/**
* Account keys that will be passed to the program
*/
accounts: Array<SolanaAccountMeta>;
/**
* Instruction data (hex-encoded or base64)
*/
data: string;
encoding: InstructionDataEncoding;
};
/**
* Solana Priority Fee
*/
export type SolanaPriorityFee =
| {
type: "auto";
}
| {
microLamportsPerUnit: number;
type: "manual";
}
| {
percentile: number;
type: "percentile";
};
/**
* Solana-specific RPC error types
*/
export type SolanaRpcErrorKind =
| {
message: string;
kind?: string | null;
type: "IO";
}
| {
message: string;
status?: number | null;
type: "REQWEST";
}
| {
code: number;
message: string;
/**
* Structured error data from Solana RPC
*/
data: SolanaRpcResponseErrorData;
type: "RPC_ERROR";
}
| {
message: string;
line?: number | null;
column?: number | null;
type: "SERDE_JSON";
}
| {
error_type: string;
message: string;
type: "TRANSACTION_ERROR";
}
| {
message: string;
type: "SIGNING_ERROR";
}
| {
message: string;
type: "CUSTOM";
}
| {
message: string;
type: "UNKNOWN";
};
/**
* Serializable version of Solana's RpcResponseErrorData
*/
export type SolanaRpcResponseErrorData =
| {
type: "EMPTY";
}
| {
/**
* Error message from simulation
*/
err?: string | null;
/**
* Logs from simulation
*/
logs?: Array<string> | null;
/**
* Accounts used in simulation
*/
accounts?: unknown;
/**
* Units consumed during simulation
*/
units_consumed?: number | null;
/**
* Return data from the transaction
*/
return_data?: unknown;
type: "SEND_TRANSACTION_PREFLIGHT_FAILURE";
}
| {
num_slots_behind?: number | null;
type: "NODE_UNHEALTHY";
};
/**
* Execution Option Variants
* All supported specific execution options are contained here
@@ -1174,6 +1477,26 @@ export type SuccessResponseEmptyIdempotencySetResponse = {
};
};
export type SuccessResponseQueuedSolanaTransactionResponse = {
/**
* Response for a queued Solana transaction
*/
result: {
/**
* The idempotency key for this transaction
*/
transactionId: string;
/**
* The Solana chain
*/
chainId: SolanaChainId;
/**
* The signer address
*/
signerAddress: string;
};
};
export type SuccessResponseQueuedTransactionsResponse = {
result: {
transactions: Array<QueuedTransaction>;
@@ -1498,6 +1821,32 @@ export type EncodeContractResponses = {
export type EncodeContractResponse =
EncodeContractResponses[keyof EncodeContractResponses];
export type SendSolanaTransactionData = {
/**
* Solana transaction request
*/
body: SendSolanaTransactionRequest;
headers?: {
/**
* Vault access token
*/
"x-vault-access-token"?: string | null;
};
path?: never;
query?: never;
url: "/v1/solana/transaction";
};
export type SendSolanaTransactionResponses = {
/**
* Solana transaction queued successfully
*/
202: SuccessResponseQueuedSolanaTransactionResponse;
};
export type SendSolanaTransactionResponse =
SendSolanaTransactionResponses[keyof SendSolanaTransactionResponses];
export type CancelTransactionData = {
body?: never;
path: {
@@ -1598,6 +1947,178 @@ export type CreateAccountResponses = {
export type CreateAccountResponse =
CreateAccountResponses[keyof CreateAccountResponses];
export type ListSolanaAccountsData = {
body?: never;
headers?: {
/**
* Vault Access Token used to access your EOA
*/
"x-vault-access-token"?: string;
};
path?: never;
query?: {
page?: number;
limit?: number;
};
url: "/v1/solana/accounts";
};
export type ListSolanaAccountsResponses = {
/**
* Solana accounts retrieved
*/
200: {
result: {
accounts: Array<{
/**
* Base58 encoded Solana public key
*/
publicKey: string;
label?: string;
createdAt: string;
updatedAt: string;
}>;
pagination: {
totalCount: number;
page: number;
limit: number;
};
};
};
};
export type ListSolanaAccountsResponse =
ListSolanaAccountsResponses[keyof ListSolanaAccountsResponses];
export type CreateSolanaAccountData = {
body?: {
label: string;
};
headers?: {
/**
* Vault Access Token used to access your EOA
*/
"x-vault-access-token"?: string;
};
path?: never;
query?: never;
url: "/v1/solana/accounts";
};
export type CreateSolanaAccountResponses = {
/**
* Solana account retrieved
*/
200: {
result: {
/**
* Base58 encoded Solana public key
*/
publicKey: string;
label?: string;
createdAt: string;
updatedAt: string;
};
};
/**
* Solana account created
*/
201: {
result: {
/**
* Base58 encoded Solana public key
*/
publicKey: string;
label?: string;
createdAt: string;
updatedAt: string;
};
};
};
export type CreateSolanaAccountResponse =
CreateSolanaAccountResponses[keyof CreateSolanaAccountResponses];
export type SignSolanaMessageData = {
body?: {
/**
* Base58 encoded Solana public key
*/
from: string;
message: string;
format?: "text" | "hex";
};
headers?: {
/**
* Vault Access Token used to access your EOA
*/
"x-vault-access-token"?: string;
};
path?: never;
query?: never;
url: "/v1/solana/sign-message";
};
export type SignSolanaMessageResponses = {
/**
* Message signed
*/
200: {
result: {
/**
* Base58 encoded signature
*/
signature: string;
};
};
};
export type SignSolanaMessageResponse =
SignSolanaMessageResponses[keyof SignSolanaMessageResponses];
export type SignSolanaTransactionData = {
body?: {
/**
* Base58 encoded Solana public key
*/
from: string;
/**
* Base64 encoded Solana transaction
*/
transaction: string;
};
headers?: {
/**
* Vault Access Token used to access your EOA
*/
"x-vault-access-token"?: string;
};
path?: never;
query?: never;
url: "/v1/solana/sign-transaction";
};
export type SignSolanaTransactionResponses = {
/**
* Transaction signed
*/
200: {
result: {
/**
* Base58 encoded signature
*/
signature: string;
/**
* Base58 encoded Solana public key
*/
signerPublicKey: string;
};
};
};
export type SignSolanaTransactionResponse =
SignSolanaTransactionResponses[keyof SignSolanaTransactionResponses];
export type GetTransactionsData = {
body?: never;
path?: never;
@@ -1967,6 +2488,277 @@ export type SearchActivityLogsResponses = {
export type SearchActivityLogsResponse =
SearchActivityLogsResponses[keyof SearchActivityLogsResponses];
export type GetSolanaTransactionsData = {
body?: never;
path?: never;
query?: {
page?: number;
limit?: number;
id?: string;
chainId?: string;
signerAddress?: string;
signature?: string;
status?: "QUEUED" | "SUBMITTED" | "CONFIRMED" | "FAILED";
sortBy?: "createdAt" | "confirmedAt";
sortDirection?: "asc" | "desc";
};
url: "/v1/solana/transactions";
};
export type GetSolanaTransactionsResponses = {
/**
* Solana Transactions
*/
200: {
result: {
transactions: Array<{
id: string;
clientId: string;
chainId: string;
signerAddress: string;
transactionParams:
| (string | number | boolean | null)
| {
[key: string]: unknown;
}
| Array<unknown>;
signature: string | null;
status: string | null;
confirmedAt: string | null;
confirmedAtSlot: string | null;
blockTime: number | null;
enrichedData:
| (string | number | boolean | null)
| {
[key: string]: unknown;
}
| Array<unknown>;
executionParams:
| (string | number | boolean | null)
| {
[key: string]: unknown;
}
| Array<unknown>;
executionResult:
| (string | number | boolean | null)
| {
[key: string]: unknown;
}
| Array<unknown>
| null;
createdAt: string;
errorMessage: string | null;
cancelledAt: string | null;
}>;
pagination: {
totalCount: number;
page: number;
limit: number;
};
};
};
};
export type GetSolanaTransactionsResponse =
GetSolanaTransactionsResponses[keyof GetSolanaTransactionsResponses];
export type SearchSolanaTransactionsData = {
body?: {
page?: number;
limit?: number;
filters?: Array<
SolanaTransactionsFilterValue | SolanaTransactionsFilterNested
>;
filtersOperation?: "AND" | "OR";
sortBy?: "createdAt" | "confirmedAt";
sortDirection?: "asc" | "desc";
};
path?: never;
query?: never;
url: "/v1/solana/transactions/search";
};
export type SearchSolanaTransactionsResponses = {
/**
* Solana Transactions
*/
200: {
result: {
transactions: Array<{
id: string;
clientId: string;
chainId: string;
signerAddress: string;
transactionParams:
| (string | number | boolean | null)
| {
[key: string]: unknown;
}
| Array<unknown>;
signature: string | null;
status: string | null;
confirmedAt: string | null;
confirmedAtSlot: string | null;
blockTime: number | null;
enrichedData:
| (string | number | boolean | null)
| {
[key: string]: unknown;
}
| Array<unknown>;
executionParams:
| (string | number | boolean | null)
| {
[key: string]: unknown;
}
| Array<unknown>;
executionResult:
| (string | number | boolean | null)
| {
[key: string]: unknown;
}
| Array<unknown>
| null;
createdAt: string;
errorMessage: string | null;
cancelledAt: string | null;
}>;
pagination: {
totalCount: number;
page: number;
limit: number;
};
};
};
};
export type SearchSolanaTransactionsResponse =
SearchSolanaTransactionsResponses[keyof SearchSolanaTransactionsResponses];
export type GetSolanaActivityLogsData = {
body?: never;
path?: never;
query: {
page?: number;
limit?: number;
transactionId: string;
eventType?: "SUCCESS" | "FAILURE" | "NACK";
stageName?: string;
executorName?: string;
sortBy?: "timestamp" | "createdAt";
sortDirection?: "asc" | "desc";
};
url: "/v1/solana/transactions/activity-logs";
};
export type GetSolanaActivityLogsErrors = {
/**
* Invalid request parameters
*/
400: unknown;
/**
* Transaction not found or not accessible
*/
404: unknown;
};
export type GetSolanaActivityLogsResponses = {
/**
* Solana Activity Logs
*/
200: {
result: {
activityLogs: Array<{
id: string;
transactionId: string;
eventType: string;
stageName: string;
executorName: string;
notificationId: string;
payload:
| (string | number | boolean | null)
| {
[key: string]: unknown;
}
| Array<unknown>;
timestamp: string;
createdAt: string;
}>;
transaction: {
id: string;
clientId: string;
};
pagination: {
totalCount: number;
page: number;
limit: number;
};
};
};
};
export type GetSolanaActivityLogsResponse =
GetSolanaActivityLogsResponses[keyof GetSolanaActivityLogsResponses];
export type SearchSolanaActivityLogsData = {
body?: {
page?: number;
limit?: number;
transactionIds?: Array<string>;
eventType?: "SUCCESS" | "FAILURE" | "NACK";
stageName?: string;
executorName?: string;
startDate?: string;
endDate?: string;
sortBy?: "timestamp" | "createdAt";
sortDirection?: "asc" | "desc";
};
path?: never;
query?: never;
url: "/v1/solana/transactions/activity-logs/search";
};
export type SearchSolanaActivityLogsErrors = {
/**
* Invalid request parameters
*/
400: unknown;
};
export type SearchSolanaActivityLogsResponses = {
/**
* Solana Activity Logs Search Results
*/
200: {
result: {
activityLogs: Array<{
id: string;
transactionId: string;
eventType: string;
stageName: string;
executorName: string;
notificationId: string;
payload:
| (string | number | boolean | null)
| {
[key: string]: unknown;
}
| Array<unknown>;
timestamp: string;
createdAt: string;
}>;
pagination: {
totalCount: number;
page: number;
limit: number;
};
};
};
};
export type SearchSolanaActivityLogsResponse =
SearchSolanaActivityLogsResponses[keyof SearchSolanaActivityLogsResponses];
export type ClientOptions = {
baseUrl: "https://engine.thirdweb.com" | (string & {});
};
+21
View File
@@ -1,5 +1,26 @@
# @thirdweb-dev/nebula
## 0.2.66
### Patch Changes
- Updated dependencies []:
- thirdweb@5.108.13
## 0.2.65
### Patch Changes
- Updated dependencies [[`0082e99`](https://github.com/thirdweb-dev/js/commit/0082e996cae47a326110e74457c72b3f3936743d), [`0082e99`](https://github.com/thirdweb-dev/js/commit/0082e996cae47a326110e74457c72b3f3936743d)]:
- thirdweb@5.108.12
## 0.2.64
### Patch Changes
- Updated dependencies [[`05550d0`](https://github.com/thirdweb-dev/js/commit/05550d045f291b8e53afd0d7e73c438bee202fb7)]:
- thirdweb@5.108.11
## 0.2.63
### Patch Changes
+1 -1
View File
@@ -57,5 +57,5 @@
"type": "module",
"types": "./dist/types/exports/thirdweb.d.ts",
"typings": "./dist/types/exports/thirdweb.d.ts",
"version": "0.2.63"
"version": "0.2.66"
}
+21
View File
@@ -1,5 +1,26 @@
# thirdweb
## 5.108.13
### Patch Changes
- Updated dependencies [[`9545b43`](https://github.com/thirdweb-dev/js/commit/9545b4367598408dbb88c63d8a8fe21305f870b5)]:
- @thirdweb-dev/engine@3.3.0
## 5.108.12
### Patch Changes
- [#8226](https://github.com/thirdweb-dev/js/pull/8226) [`0082e99`](https://github.com/thirdweb-dev/js/commit/0082e996cae47a326110e74457c72b3f3936743d) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Add optional recipientAddress to x402 settlePayment
- [#8226](https://github.com/thirdweb-dev/js/pull/8226) [`0082e99`](https://github.com/thirdweb-dev/js/commit/0082e996cae47a326110e74457c72b3f3936743d) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Expose 7702 utility functions
## 5.108.11
### Patch Changes
- [#8228](https://github.com/thirdweb-dev/js/pull/8228) [`05550d0`](https://github.com/thirdweb-dev/js/commit/05550d045f291b8e53afd0d7e73c438bee202fb7) Thanks [@0xFirekeeper](https://github.com/0xFirekeeper)! - Export ERC7702 MinimalAccount helpers and events
## 5.108.10
### Patch Changes
+1 -1
View File
@@ -430,5 +430,5 @@
}
},
"typings": "./dist/types/exports/thirdweb.d.ts",
"version": "5.108.10"
"version": "5.108.13"
}
@@ -1,5 +1,53 @@
// --- KEEEP IN SYNC with exports/wallets/in-app.ts ---
export { executedEvent } from "../../extensions/erc7702/__generated__/MinimalAccount/events/Executed.js";
export { sessionCreatedEvent } from "../../extensions/erc7702/__generated__/MinimalAccount/events/SessionCreated.js";
export { valueReceivedEvent } from "../../extensions/erc7702/__generated__/MinimalAccount/events/ValueReceived.js";
// ERC7702 generated helpers
export {
eip712Domain,
isEip712DomainSupported,
} from "../../extensions/erc7702/__generated__/MinimalAccount/read/eip712Domain.js";
export {
type GetCallPoliciesForSignerParams,
getCallPoliciesForSigner,
isGetCallPoliciesForSignerSupported,
} from "../../extensions/erc7702/__generated__/MinimalAccount/read/getCallPoliciesForSigner.js";
export {
type GetSessionExpirationForSignerParams,
getSessionExpirationForSigner,
isGetSessionExpirationForSignerSupported,
} from "../../extensions/erc7702/__generated__/MinimalAccount/read/getSessionExpirationForSigner.js";
export {
type GetSessionStateForSignerParams,
getSessionStateForSigner,
isGetSessionStateForSignerSupported,
} from "../../extensions/erc7702/__generated__/MinimalAccount/read/getSessionStateForSigner.js";
export {
type GetTransferPoliciesForSignerParams,
getTransferPoliciesForSigner,
isGetTransferPoliciesForSignerSupported,
} from "../../extensions/erc7702/__generated__/MinimalAccount/read/getTransferPoliciesForSigner.js";
export {
type IsWildcardSignerParams,
isIsWildcardSignerSupported,
isWildcardSigner,
} from "../../extensions/erc7702/__generated__/MinimalAccount/read/isWildcardSigner.js";
export {
type CreateSessionWithSigParams,
createSessionWithSig,
isCreateSessionWithSigSupported,
} from "../../extensions/erc7702/__generated__/MinimalAccount/write/createSessionWithSig.js";
export {
type ExecuteParams,
execute,
isExecuteSupported,
} from "../../extensions/erc7702/__generated__/MinimalAccount/write/execute.js";
export {
type ExecuteWithSigParams,
executeWithSig,
isExecuteWithSigSupported,
} from "../../extensions/erc7702/__generated__/MinimalAccount/write/executeWithSig.js";
//ACCOUNT
export {
type CreateSessionKeyOptions,
@@ -1,11 +1,60 @@
// --- KEEEP IN SYNC with exports/wallets/in-app.native.ts ---
export { executedEvent } from "../../extensions/erc7702/__generated__/MinimalAccount/events/Executed.js";
export { sessionCreatedEvent } from "../../extensions/erc7702/__generated__/MinimalAccount/events/SessionCreated.js";
export { valueReceivedEvent } from "../../extensions/erc7702/__generated__/MinimalAccount/events/ValueReceived.js";
// ERC7702 generated helpers
export {
eip712Domain,
isEip712DomainSupported,
} from "../../extensions/erc7702/__generated__/MinimalAccount/read/eip712Domain.js";
export {
type GetCallPoliciesForSignerParams,
getCallPoliciesForSigner,
isGetCallPoliciesForSignerSupported,
} from "../../extensions/erc7702/__generated__/MinimalAccount/read/getCallPoliciesForSigner.js";
export {
type GetSessionExpirationForSignerParams,
getSessionExpirationForSigner,
isGetSessionExpirationForSignerSupported,
} from "../../extensions/erc7702/__generated__/MinimalAccount/read/getSessionExpirationForSigner.js";
export {
type GetSessionStateForSignerParams,
getSessionStateForSigner,
isGetSessionStateForSignerSupported,
} from "../../extensions/erc7702/__generated__/MinimalAccount/read/getSessionStateForSigner.js";
export {
type GetTransferPoliciesForSignerParams,
getTransferPoliciesForSigner,
isGetTransferPoliciesForSignerSupported,
} from "../../extensions/erc7702/__generated__/MinimalAccount/read/getTransferPoliciesForSigner.js";
export {
type IsWildcardSignerParams,
isIsWildcardSignerSupported,
isWildcardSigner,
} from "../../extensions/erc7702/__generated__/MinimalAccount/read/isWildcardSigner.js";
export {
type CreateSessionWithSigParams,
createSessionWithSig,
isCreateSessionWithSigSupported,
} from "../../extensions/erc7702/__generated__/MinimalAccount/write/createSessionWithSig.js";
export {
type ExecuteParams,
execute,
isExecuteSupported,
} from "../../extensions/erc7702/__generated__/MinimalAccount/write/execute.js";
export {
type ExecuteWithSigParams,
executeWithSig,
isExecuteWithSigSupported,
} from "../../extensions/erc7702/__generated__/MinimalAccount/write/executeWithSig.js";
//ACCOUNT
export {
type CreateSessionKeyOptions,
createSessionKey,
isCreateSessionKeySupported,
} from "../../extensions/erc7702/account/createSessionKey.js";
export type {
Condition,
LimitType,
+3 -1
View File
@@ -44,6 +44,7 @@ export async function decodePaymentRequest(
price,
network,
facilitator,
payTo,
resourceUrl,
routeConfig = {},
method,
@@ -104,7 +105,7 @@ export async function decodePaymentRequest(
resource: resourceUrl,
description: description ?? "",
mimeType: mimeType ?? "application/json",
payTo: getAddress(facilitator.address),
payTo: getAddress(facilitator.address), // always pay to the facilitator address first
maxTimeoutSeconds: maxTimeoutSeconds ?? 300,
asset: getAddress(asset.address),
outputSchema: {
@@ -117,6 +118,7 @@ export async function decodePaymentRequest(
output: outputSchema,
},
extra: {
recipientAddress: payTo, // input payTo is the final recipient address
...((asset as ERC20TokenAmount["asset"]).eip712 ?? {}),
},
});
+1 -1
View File
@@ -34,7 +34,7 @@ export type PaymentArgs = {
facilitator: ThirdwebX402Facilitator;
/** Optional configuration for the payment middleware route */
routeConfig?: PaymentMiddlewareConfig;
/** @deprecated Use facilitator.address instead */
/** Optional recipient address to receive the payment if different from your facilitator address */
payTo?: string;
};
+6
View File
@@ -1,5 +1,11 @@
# @thirdweb-dev/react-native-adapter
## 0.1.0
### Minor Changes
- [#8168](https://github.com/thirdweb-dev/js/pull/8168) [`35c7d21`](https://github.com/thirdweb-dev/js/commit/35c7d21e368f9ea5636c8663ba31b7d473b5b343) Thanks [@d4mr](https://github.com/d4mr)! - Added Vault operations support for Solana.
## 0.0.3
### Patch Changes
+1 -13
View File
@@ -1,16 +1,4 @@
{
"$schema": "https://biomejs.dev/schemas/2.0.6/schema.json",
"extends": "//",
"overrides": [
{
"assist": {
"actions": {
"source": {
"useSortedKeys": "off"
}
}
},
"includes": ["package.json"]
}
]
"extends": "//"
}
+11 -2
View File
@@ -7,12 +7,20 @@
"@noble/ciphers": "^1.2.1",
"@noble/curves": "1.8.2",
"@noble/hashes": "1.7.2",
"@solana/addresses": "^3.0.0",
"@solana/keys": "^3.0.0",
"@solana/kit": "^4.0.0",
"@solana/rpc": "^3.0.0",
"@solana/transactions": "^3.0.0",
"abitype": "1.0.8",
"jose": "6.0.11"
},
"devDependencies": {
"@biomejs/biome": "2.0.6",
"rimraf": "6.0.1"
"@solana-program/system": "^0.8.1",
"bs58": "^6.0.0",
"rimraf": "6.0.1",
"vitest": "3.2.4"
},
"engines": {
"node": ">=18"
@@ -51,6 +59,7 @@
"build:esm": "tsc --project ./tsconfig.build.json --module es2020 --outDir ./dist/esm && printf '{\"type\": \"module\",\"sideEffects\":false}' > ./dist/esm/package.json",
"build:types": "tsc --project ./tsconfig.build.json --module esnext --declarationDir ./dist/types --emitDeclarationOnly --declaration --declarationMap",
"clean": "rimraf dist",
"test:solana": "vitest ./tests/solana.test.ts",
"dev": "tsc --project ./tsconfig.build.json --module es2020 --outDir ./dist/esm --watch",
"fix": "biome check ./src --fix",
"format": "biome format ./src --write",
@@ -59,5 +68,5 @@
"type": "module",
"types": "./dist/types/exports/thirdweb.d.ts",
"typings": "./dist/types/exports/thirdweb.d.ts",
"version": "0.0.3"
"version": "0.1.0"
}
@@ -3,15 +3,19 @@ export {
createEoa,
createServiceAccount,
createSignedAccessToken,
createSolanaAccount,
createVaultClient,
getServiceAccount,
listAccessTokens,
listEoas,
listSolanaAccounts,
ping,
revokeAccessToken,
rotateServiceAccount,
signAuthorization,
signMessage,
signSolanaMessage,
signSolanaTransaction,
signStructuredMessage,
signTransaction,
signTypedData,
@@ -31,6 +35,7 @@ export type {
CreateAccessTokenPayload,
CreateEoaPayload,
CreateServiceAccountPayload,
CreateSolanaAccountPayload,
EncryptedPayload,
GenericPayload,
GetAccessTokensData,
@@ -38,6 +43,7 @@ export type {
GetServiceAccountPayload,
ListAccessTokensPayload,
ListEoaPayload,
ListSolanaAccountsPayload,
Payload,
PingPayload,
PolicyComponent,
@@ -49,6 +55,8 @@ export type {
SignAuthorizationRules,
SignedAuthorization,
SignMessagePayload,
SignSolanaMessagePayload,
SignSolanaTransactionPayload,
SignStructuredMessageData,
SignStructuredMessageOptions,
SignStructuredMessagePayload,
+102 -1
View File
@@ -3,18 +3,27 @@ import { x25519 } from "@noble/curves/ed25519";
import { hkdf } from "@noble/hashes/hkdf";
import { sha256 } from "@noble/hashes/sha256";
import { bytesToHex, hexToBytes, randomBytes } from "@noble/hashes/utils";
import {
address,
getBase58Encoder,
getTransactionDecoder,
getTransactionEncoder,
} from "@solana/kit";
import type { TypedData } from "abitype";
import * as jose from "jose";
import type {
CheckedSignTypedDataPayload,
CreateAccessTokenPayload,
CreateEoaPayload,
CreateServiceAccountPayload,
CreateSolanaAccountPayload,
EncryptedPayload,
GetServiceAccountPayload,
ListAccessTokensPayload,
ListEoaPayload,
ListSolanaAccountsPayload,
Payload,
PingPayload,
PolicyComponent,
@@ -23,6 +32,8 @@ import type {
RotateServiceAccountPayload,
SignAuthorizationPayload,
SignMessagePayload,
SignSolanaMessagePayload,
SignSolanaTransactionPayload,
SignStructuredMessagePayload,
SignTransactionPayload,
UnencryptedErrorResponse,
@@ -447,6 +458,96 @@ export function listAccessTokens({
});
}
// ========== Solana Functions ==========
export function createSolanaAccount({
client,
request: options,
}: PayloadParams<CreateSolanaAccountPayload>) {
return sendRequest<CreateSolanaAccountPayload>({
client,
request: {
operation: "solana:create",
...options,
},
});
}
export function listSolanaAccounts({
client,
request: options,
}: PayloadParams<ListSolanaAccountsPayload>) {
return sendRequest<ListSolanaAccountsPayload>({
client,
request: {
operation: "solana:list",
...options,
},
});
}
export function signSolanaTransaction({
client,
request: options,
}: PayloadParams<SignSolanaTransactionPayload>) {
return sendRequest<SignSolanaTransactionPayload>({
client,
request: {
operation: "solana:signTransaction",
...options,
},
});
}
/**
* Reconstruct a signed solana transaction from the vault signature using @solana/kit
*/
export function reconstructSolanaSignedTransaction(
base64Transaction: string,
base58Signature: string,
signerPubkey: string,
): Uint8Array {
// Decode the base64 transaction into bytes
const base64TransactionBytes = new Uint8Array(
Buffer.from(base64Transaction, "base64"),
);
// Decode the transaction to get its structure
const transactionDecoder = getTransactionDecoder();
const transaction = transactionDecoder.decode(base64TransactionBytes);
// Decode the base58 signature to bytes
const base58Encoder = getBase58Encoder();
const signatureBytes = base58Encoder.encode(base58Signature);
// Add the signature to the transaction
const signedTransaction = {
...transaction,
signatures: {
...transaction.signatures,
[address(signerPubkey)]: signatureBytes,
},
};
// Re-encode the signed transaction
const transactionEncoder = getTransactionEncoder();
const signedTransactionBytes = transactionEncoder.encode(signedTransaction);
return new Uint8Array(signedTransactionBytes);
}
export function signSolanaMessage({
client,
request: options,
}: PayloadParams<SignSolanaMessagePayload>) {
return sendRequest<SignSolanaMessagePayload>({
client,
request: {
operation: "solana:signMessage",
...options,
},
});
}
const SIGNED_TOKEN_PREFIX = "vt_sat_";
const DEFAULT_SIGNING_CONTEXT = "encryption"; // Default context for HKDF
+154 -2
View File
@@ -58,7 +58,7 @@ export type UnencryptedErrorResponse = {
type EncryptedError = {
code: string;
message: string;
details?: string;
details?: string | { reason: string };
};
type GenericSuccessResponse<Data> = {
@@ -148,6 +148,50 @@ type SignMessageOptions = {
format?: "text" | "hex";
};
// ========== Solana Options Types ==========
// Solana message type that's compatible with both Kit and our Rust serialization
export type SolanaVersionedMessage = {
version: "legacy" | "v0";
header: {
numRequiredSignatures: number;
numReadonlySignedAccounts: number;
numReadonlyUnsignedAccounts: number;
};
staticAccountKeys: string[]; // Use string instead of SolanaAddress for simplicity
recentBlockhash: string;
instructions: Array<{
programIdIndex: number;
accountKeyIndexes: number[];
data: Uint8Array;
}>;
addressTableLookups?: Array<{
accountKey: string; // Use string instead of SolanaAddress
writableIndexes: number[];
readonlyIndexes: number[];
}>;
};
type CreateSolanaAccountOptions = {
metadata: Record<string, MetadataValue>;
};
type GetSolanaAccountsOptions = {
page?: number;
pageSize?: number;
};
type SignSolanaTransactionOptions = {
transaction: string; // Base64 encoded VersionedTransaction
from: string; // Use string for Solana public key
};
type SignSolanaMessageOptions = {
message: string;
from: string; // Use string for Solana public key
format?: "text" | "hex";
};
type CheckedSignTypedDataOptions<
Types extends TypedData,
PrimaryType extends keyof Types | "EIP712Domain" = keyof Types,
@@ -315,12 +359,63 @@ export type PolicyComponent =
useropV07?: UserOperationV07Rules;
};
}
| {
type: "solana:create";
requiredMetadataPatterns?: MetadataRule[];
allowedMetadataPatterns?: MetadataRule[];
}
| {
type: "solana:read";
metadataPatterns?: MetadataRule[];
}
| {
type: "solana:signTransaction";
allowlist?: string[]; // Use string for Solana addresses
metadataPatterns?: MetadataRule[];
payloadPatterns?: Record<string, Rule[]>;
transactionPatterns?: SolanaTransactionPatterns;
}
| {
type: "solana:signMessage";
allowlist?: string[]; // Use string for Solana addresses
metadataPatterns?: MetadataRule[];
messagePattern?: string;
}
| {
type: "accessToken:read";
metadataPatterns?: MetadataRule[];
revealSensitive: boolean;
};
// Solana transaction patterns
export type SolanaTransactionPatterns = {
programs?: ProgramAccessControl;
writableAccounts?: AccountAccessControl;
requiredCosigners?: string[];
maxInstructions?: number;
canPayFees?: boolean;
};
export type ProgramAccessControl =
| {
type: "allow";
programs: string[];
}
| {
type: "deny";
programs: string[];
};
export type AccountAccessControl =
| {
type: "allow";
accounts: string[];
}
| {
type: "deny";
accounts: string[];
};
type OwnerType = string; // Define based on your eoa models
type CreateAccessTokenOptions = {
@@ -399,6 +494,30 @@ type SignMessageData = {
signature: string;
};
// ========== Solana Data Types ==========
type CreateSolanaAccountData = {
pubkey: string;
metadata: Record<string, MetadataValue>;
createdAt: string;
updatedAt: string;
};
type GetSolanaAccountsData = {
accounts: CreateSolanaAccountData[];
totalCount: number;
page: number;
pageSize: number;
};
type SignSolanaTransactionData = {
signature: string; // Base58 encoded signature
signerPubkey: string; // Use string for Solana public key
};
type SignSolanaMessageData = {
signature: string; // Base58 encoded signature
};
type SignTypedDataData = {
signature: string;
};
@@ -553,6 +672,35 @@ export type RevokeAccessTokenPayload = GenericPayload<{
data: AccessTokenData;
}>;
// ========== Solana Payload Types ==========
export type CreateSolanaAccountPayload = GenericPayload<{
operation: "solana:create";
auth: Auth;
options: CreateSolanaAccountOptions;
data: CreateSolanaAccountData;
}>;
export type ListSolanaAccountsPayload = GenericPayload<{
operation: "solana:list";
auth: Auth;
options: GetSolanaAccountsOptions;
data: GetSolanaAccountsData;
}>;
export type SignSolanaTransactionPayload = GenericPayload<{
operation: "solana:signTransaction";
auth: Auth;
options: SignSolanaTransactionOptions;
data: SignSolanaTransactionData;
}>;
export type SignSolanaMessagePayload = GenericPayload<{
operation: "solana:signMessage";
auth: Auth;
options: SignSolanaMessageOptions;
data: SignSolanaMessageData;
}>;
// ========== Union of all payloads ==========
export type Payload =
| PingPayload
@@ -568,4 +716,8 @@ export type Payload =
| RevokeAccessTokenPayload
| SignAuthorizationPayload
| SignStructuredMessagePayload
| ListAccessTokensPayload;
| ListAccessTokensPayload
| CreateSolanaAccountPayload
| ListSolanaAccountsPayload
| SignSolanaTransactionPayload
| SignSolanaMessagePayload;
+6
View File
@@ -0,0 +1,6 @@
# Solana RPC Configuration
SOLANA_RPC_HTTP_URL=http://127.0.0.1:8899
SOLANA_RPC_WS_URL=ws://127.0.0.1:8900
# Vault Configuration
VAULT_URL=http://localhost:3000
+11
View File
@@ -0,0 +1,11 @@
import { dirname, join } from "node:path";
import { loadEnvFile } from "node:process";
import { fileURLToPath } from "node:url";
const __dirname = dirname(fileURLToPath(import.meta.url));
try {
loadEnvFile(join(__dirname, ".env"));
} catch (_error) {
console.warn("No .env file found, using default values");
}
File diff suppressed because it is too large Load Diff
+17
View File
@@ -0,0 +1,17 @@
import { join } from "node:path";
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
globals: true,
environment: "node",
testTimeout: 60000, // 60 seconds default timeout
hookTimeout: 60000, // 60 seconds for beforeAll/afterAll
teardownTimeout: 10000,
sequence: {
shuffle: false, // Run tests in order
},
// load .env.test
setupFiles: [join(__dirname, "./tests/env-setup.ts")],
},
});
+6
View File
@@ -1,5 +1,11 @@
# @thirdweb-dev/wagmi-adapter
## 0.2.163
## 0.2.162
## 0.2.161
## 0.2.160
## 0.2.159
+1 -1
View File
@@ -56,5 +56,5 @@
"type": "module",
"types": "./dist/types/exports/thirdweb.d.ts",
"typings": "./dist/types/exports/thirdweb.d.ts",
"version": "0.2.160"
"version": "0.2.163"
}
+1076 -15
View File
File diff suppressed because it is too large Load Diff