Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
901789983f | ||
|
|
ad49560331 | ||
|
|
83198b2ea4 | ||
|
|
a138681b6c | ||
|
|
3a18c6e6a8 | ||
|
|
fdd8c17813 | ||
|
|
32c2ff62f8 | ||
|
|
bef65c8b69 | ||
|
|
7afc2055b9 | ||
|
|
8e9c51ee7b | ||
|
|
01a20cb7d5 | ||
|
|
e13b9a784a | ||
|
|
da4a4a540a | ||
|
|
5e5c82fb2e | ||
|
|
1d28988253 | ||
|
|
5352152835 | ||
|
|
decbbb8f7a | ||
|
|
ecd5ae1feb | ||
|
|
1caf6dd460 | ||
|
|
19204e4654 | ||
|
|
674c8d5e6c | ||
|
|
1eb0cd9314 |
@@ -89,7 +89,7 @@ jobs:
|
||||
- run: pnpm test
|
||||
|
||||
- name: Code Coverage
|
||||
uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3
|
||||
uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad # v5.5.3
|
||||
with:
|
||||
directory: packages/
|
||||
flags: packages
|
||||
|
||||
@@ -46,7 +46,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0
|
||||
uses: github/codeql-action/init@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
@@ -59,7 +59,7 @@ jobs:
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0
|
||||
uses: github/codeql-action/autobuild@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
@@ -72,4 +72,4 @@ jobs:
|
||||
# ./location_of_script_within_repo/buildscript.sh
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0
|
||||
uses: github/codeql-action/analyze@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1
|
||||
|
||||
@@ -105,6 +105,7 @@ type PartnerPermission = "PROMPT_USER_V1" | "FULL_CONTROL_V1";
|
||||
export type Partner = {
|
||||
id: string;
|
||||
name: string;
|
||||
imageUrl?: string;
|
||||
allowlistedDomains: string[];
|
||||
allowlistedBundleIds: string[];
|
||||
permissions: [PartnerPermission];
|
||||
|
||||
@@ -285,6 +285,11 @@ const chainMetaRecord = {
|
||||
"Lisk is focused on serving builders in high-growth markets like Africa and Southeast Asia. When you build on Lisk, you become part of the strongest Ethereum collective, alongside Coinbase, Sony, World, Kraken and Uniswap -- the OP Superchain.",
|
||||
headerImgUrl: liskBanner.src,
|
||||
},
|
||||
//Sei Mainnet
|
||||
1329: {
|
||||
about:
|
||||
"Sei is a blockchain designed for fast, cheap financial transactions, combining the network effects of Ethereum with the performance of Solana. Sei has processed more than four billion transactions across more than 80 million wallets and has become the #1 EVM chain by number of active users. The team is backed by top investors such as Multicoin, Jump, Coinbase Ventures, and Circle Ventures – and includes talent from companies like Robinhood, Google, Coinbase, Databricks, Uber, and Goldman Sachs.",
|
||||
},
|
||||
//Reactive Mainnet
|
||||
1597: {
|
||||
about:
|
||||
|
||||
+19
-2
@@ -3,6 +3,7 @@ import { useParams } from "next/navigation";
|
||||
import { toast } from "sonner";
|
||||
import type { ThirdwebClient } from "thirdweb";
|
||||
import type { Ecosystem, Partner } from "@/api/team/ecosystems";
|
||||
import { useDashboardStorageUpload } from "@/hooks/useDashboardStorageUpload";
|
||||
import { useDashboardRouter } from "@/lib/DashboardRouter";
|
||||
import { useAddPartner } from "../../hooks/use-add-partner";
|
||||
import { PartnerForm, type PartnerFormValues } from "./partner-form.client";
|
||||
@@ -23,6 +24,8 @@ export function AddPartnerForm({
|
||||
const teamSlug = params.team_slug as string;
|
||||
const ecosystemSlug = params.slug as string;
|
||||
|
||||
const storageUpload = useDashboardStorageUpload({ client });
|
||||
|
||||
const { mutateAsync: addPartner, isPending } = useAddPartner(
|
||||
{
|
||||
authToken,
|
||||
@@ -48,10 +51,23 @@ export function AddPartnerForm({
|
||||
},
|
||||
);
|
||||
|
||||
const handleSubmit = (
|
||||
const isUploading = storageUpload.isPending;
|
||||
|
||||
const handleSubmit = async (
|
||||
values: PartnerFormValues,
|
||||
finalAccessControl: Partner["accessControl"] | null,
|
||||
) => {
|
||||
let imageUrl: string | undefined;
|
||||
if (values.logo) {
|
||||
try {
|
||||
const [uri] = await storageUpload.mutateAsync([values.logo]);
|
||||
imageUrl = uri;
|
||||
} catch {
|
||||
toast.error("Failed to upload logo");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
addPartner({
|
||||
accessControl: finalAccessControl,
|
||||
allowlistedBundleIds: values.bundleIds
|
||||
@@ -61,6 +77,7 @@ export function AddPartnerForm({
|
||||
.split(/,| /)
|
||||
.filter((d) => d.length > 0),
|
||||
ecosystem,
|
||||
imageUrl,
|
||||
name: values.name,
|
||||
});
|
||||
};
|
||||
@@ -68,7 +85,7 @@ export function AddPartnerForm({
|
||||
return (
|
||||
<PartnerForm
|
||||
client={client}
|
||||
isSubmitting={isPending}
|
||||
isSubmitting={isPending || isUploading}
|
||||
onSubmit={handleSubmit}
|
||||
submitLabel="Add"
|
||||
/>
|
||||
|
||||
+101
-2
@@ -1,12 +1,13 @@
|
||||
"use client";
|
||||
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { PlusIcon, Trash2Icon } from "lucide-react";
|
||||
import { useId } from "react";
|
||||
import { PencilIcon, PlusIcon, Trash2Icon, XIcon } from "lucide-react";
|
||||
import { useId, useRef } from "react";
|
||||
import { useFieldArray, useForm } from "react-hook-form";
|
||||
import type { ThirdwebClient } from "thirdweb";
|
||||
import type { z } from "zod";
|
||||
import type { Partner } from "@/api/team/ecosystems";
|
||||
import { Img } from "@/components/blocks/Img";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Form,
|
||||
@@ -17,11 +18,13 @@ import {
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { ImageUpload } from "@/components/ui/image-upload";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Spinner } from "@/components/ui/Spinner";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { resolveSchemeWithErrorHandler } from "@/utils/resolveSchemeWithErrorHandler";
|
||||
import { partnerFormSchema } from "../../constants";
|
||||
import { AllowedOperationsSection } from "./allowed-operations-section";
|
||||
|
||||
@@ -118,6 +121,7 @@ export function PartnerForm({
|
||||
|
||||
const accessControlId = useId();
|
||||
const serverVerifierId = useId();
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
@@ -150,6 +154,101 @@ export function PartnerForm({
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="logo"
|
||||
render={() => {
|
||||
const removeLogo = form.watch("removeLogo");
|
||||
const logoFile = form.watch("logo");
|
||||
const newFilePreview = logoFile
|
||||
? URL.createObjectURL(logoFile)
|
||||
: undefined;
|
||||
const existingImageUrl =
|
||||
partner?.imageUrl && !removeLogo
|
||||
? resolveSchemeWithErrorHandler({
|
||||
client,
|
||||
uri: partner.imageUrl,
|
||||
})
|
||||
: undefined;
|
||||
const displayUrl = newFilePreview || existingImageUrl;
|
||||
|
||||
return (
|
||||
<FormItem>
|
||||
<FormLabel>Partner Logo</FormLabel>
|
||||
<FormControl>
|
||||
<div>
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
accept="image/png,image/jpeg,image/webp"
|
||||
className="hidden"
|
||||
onChange={(e) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (file) {
|
||||
form.setValue("logo", file, {
|
||||
shouldValidate: true,
|
||||
});
|
||||
form.setValue("removeLogo", false);
|
||||
}
|
||||
e.target.value = "";
|
||||
}}
|
||||
type="file"
|
||||
/>
|
||||
{displayUrl ? (
|
||||
<div className="relative inline-block">
|
||||
<Img
|
||||
alt={partner?.name ?? "Partner logo"}
|
||||
className="size-20 rounded-md border object-contain object-center"
|
||||
src={displayUrl}
|
||||
/>
|
||||
<Button
|
||||
aria-label="Remove logo"
|
||||
className="absolute -top-2 -right-2 size-6 rounded-full bg-background p-0 hover:bg-accent"
|
||||
onClick={() => {
|
||||
form.setValue("logo", undefined);
|
||||
form.setValue("removeLogo", true);
|
||||
}}
|
||||
size="icon"
|
||||
type="button"
|
||||
variant="ghost"
|
||||
>
|
||||
<XIcon className="size-3" />
|
||||
</Button>
|
||||
<Button
|
||||
aria-label="Change logo"
|
||||
className="absolute -bottom-2 -right-2 size-6 rounded-full bg-background p-0 hover:bg-accent"
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
size="icon"
|
||||
type="button"
|
||||
variant="ghost"
|
||||
>
|
||||
<PencilIcon className="size-3" />
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<ImageUpload
|
||||
accept="image/png, image/jpeg, image/webp"
|
||||
className="bg-background"
|
||||
onUpload={(files) => {
|
||||
if (files[0]) {
|
||||
form.setValue("logo", files[0], {
|
||||
shouldValidate: true,
|
||||
});
|
||||
form.setValue("removeLogo", false);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
Optional logo for this partner. Used in OTP emails sent to
|
||||
users authenticating through this partner.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
defaultValue=""
|
||||
|
||||
+27
-2
@@ -3,6 +3,7 @@ import { useParams } from "next/navigation";
|
||||
import { toast } from "sonner";
|
||||
import type { ThirdwebClient } from "thirdweb";
|
||||
import type { Ecosystem, Partner } from "@/api/team/ecosystems";
|
||||
import { useDashboardStorageUpload } from "@/hooks/useDashboardStorageUpload";
|
||||
import { useDashboardRouter } from "@/lib/DashboardRouter";
|
||||
import { useUpdatePartner } from "../../hooks/use-update-partner";
|
||||
import { PartnerForm, type PartnerFormValues } from "./partner-form.client";
|
||||
@@ -25,6 +26,8 @@ export function UpdatePartnerForm({
|
||||
const teamSlug = params.team_slug as string;
|
||||
const ecosystemSlug = params.slug as string;
|
||||
|
||||
const storageUpload = useDashboardStorageUpload({ client });
|
||||
|
||||
const { mutateAsync: updatePartner, isPending } = useUpdatePartner(
|
||||
{
|
||||
authToken,
|
||||
@@ -50,10 +53,31 @@ export function UpdatePartnerForm({
|
||||
},
|
||||
);
|
||||
|
||||
const handleSubmit = (
|
||||
const isUploading = storageUpload.isPending;
|
||||
|
||||
const handleSubmit = async (
|
||||
values: PartnerFormValues,
|
||||
finalAccessControl: Partner["accessControl"] | null,
|
||||
) => {
|
||||
// Determine imageUrl based on three states:
|
||||
// 1. New file uploaded → upload and use new URI
|
||||
// 2. Explicit removal → send null to clear
|
||||
// 3. No change → preserve existing partner imageUrl
|
||||
let imageUrl: string | null | undefined;
|
||||
if (values.logo) {
|
||||
try {
|
||||
const [uri] = await storageUpload.mutateAsync([values.logo]);
|
||||
imageUrl = uri;
|
||||
} catch {
|
||||
toast.error("Failed to upload logo");
|
||||
return;
|
||||
}
|
||||
} else if (values.removeLogo) {
|
||||
imageUrl = null;
|
||||
} else {
|
||||
imageUrl = partner.imageUrl;
|
||||
}
|
||||
|
||||
updatePartner({
|
||||
accessControl: finalAccessControl,
|
||||
allowlistedBundleIds: values.bundleIds
|
||||
@@ -63,6 +87,7 @@ export function UpdatePartnerForm({
|
||||
.split(/,| /)
|
||||
.filter((d) => d.length > 0),
|
||||
ecosystem,
|
||||
imageUrl,
|
||||
name: values.name,
|
||||
partnerId: partner.id,
|
||||
});
|
||||
@@ -71,7 +96,7 @@ export function UpdatePartnerForm({
|
||||
return (
|
||||
<PartnerForm
|
||||
client={client}
|
||||
isSubmitting={isPending}
|
||||
isSubmitting={isPending || isUploading}
|
||||
onSubmit={handleSubmit}
|
||||
partner={partner}
|
||||
submitLabel="Update"
|
||||
|
||||
+15
@@ -154,6 +154,21 @@ const allowedOperationsSchema = z.discriminatedUnion("signMethod", [
|
||||
|
||||
export const partnerFormSchema = z
|
||||
.object({
|
||||
logo: z
|
||||
.instanceof(File, {
|
||||
message: "Please select an image file",
|
||||
})
|
||||
.refine(
|
||||
(file) => ["image/png", "image/jpeg", "image/webp"].includes(file.type),
|
||||
{
|
||||
message: "Only PNG, JPG or WEBP images are allowed",
|
||||
},
|
||||
)
|
||||
.refine((file) => file.size <= 500 * 1024, {
|
||||
message: "Logo size must be less than 500KB",
|
||||
})
|
||||
.optional(),
|
||||
removeLogo: z.boolean().default(false),
|
||||
accessControl: z
|
||||
.object({
|
||||
allowedOperations: z
|
||||
|
||||
+2
@@ -8,6 +8,7 @@ import type { Ecosystem, Partner } from "@/api/team/ecosystems";
|
||||
type AddPartnerParams = {
|
||||
ecosystem: Ecosystem;
|
||||
name: string;
|
||||
imageUrl?: string;
|
||||
allowlistedDomains: string[];
|
||||
allowlistedBundleIds: string[];
|
||||
accessControl?: Partner["accessControl"] | null;
|
||||
@@ -37,6 +38,7 @@ export function useAddPartner(
|
||||
accessControl: params.accessControl ?? undefined,
|
||||
allowlistedBundleIds: params.allowlistedBundleIds,
|
||||
allowlistedDomains: params.allowlistedDomains,
|
||||
imageUrl: params.imageUrl,
|
||||
name: params.name,
|
||||
// TODO - remove the requirement for permissions in API endpoint
|
||||
permissions: ["FULL_CONTROL_V1"],
|
||||
|
||||
+2
@@ -9,6 +9,7 @@ type UpdatePartnerParams = {
|
||||
partnerId: string;
|
||||
ecosystem: Ecosystem;
|
||||
name: string;
|
||||
imageUrl?: string | null;
|
||||
allowlistedDomains: string[];
|
||||
allowlistedBundleIds: string[];
|
||||
accessControl?: {
|
||||
@@ -43,6 +44,7 @@ export function useUpdatePartner(
|
||||
accessControl: params.accessControl,
|
||||
allowlistedBundleIds: params.allowlistedBundleIds,
|
||||
allowlistedDomains: params.allowlistedDomains,
|
||||
imageUrl: params.imageUrl,
|
||||
name: params.name,
|
||||
}),
|
||||
|
||||
|
||||
+12
-4
@@ -20,6 +20,7 @@ import {
|
||||
createThirdwebClient,
|
||||
Engine,
|
||||
getContract,
|
||||
NATIVE_TOKEN_ADDRESS,
|
||||
readContract,
|
||||
type ThirdwebClient,
|
||||
toUnits,
|
||||
@@ -889,11 +890,16 @@ function SendProjectWalletModalContent(props: SendProjectWalletModalProps) {
|
||||
|
||||
const sendMutation = useMutation({
|
||||
mutationFn: async (values: SendFormValues) => {
|
||||
const normalizedTokenAddress =
|
||||
values.tokenAddress?.toLowerCase() ===
|
||||
NATIVE_TOKEN_ADDRESS.toLowerCase()
|
||||
? undefined
|
||||
: values.tokenAddress;
|
||||
let decimals = 18;
|
||||
if (values.tokenAddress) {
|
||||
if (normalizedTokenAddress) {
|
||||
const decimalsRpc = await readContract({
|
||||
contract: getContract({
|
||||
address: values.tokenAddress,
|
||||
address: normalizedTokenAddress,
|
||||
chain: selectedChain,
|
||||
client,
|
||||
}),
|
||||
@@ -914,7 +920,9 @@ function SendProjectWalletModalContent(props: SendProjectWalletModalProps) {
|
||||
teamId,
|
||||
walletAddress,
|
||||
secretKey: secretKeyValue,
|
||||
...(values.tokenAddress ? { tokenAddress: values.tokenAddress } : {}),
|
||||
...(normalizedTokenAddress
|
||||
? { tokenAddress: normalizedTokenAddress }
|
||||
: {}),
|
||||
...(vaultAccessTokenValue
|
||||
? { vaultAccessToken: vaultAccessTokenValue }
|
||||
: {}),
|
||||
@@ -956,7 +964,7 @@ function SendProjectWalletModalContent(props: SendProjectWalletModalProps) {
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit((values) => {
|
||||
void sendMutation.mutateAsync(values);
|
||||
sendMutation.mutate(values);
|
||||
})}
|
||||
>
|
||||
<div className="space-y-4 px-4 pb-8 lg:px-6">
|
||||
|
||||
@@ -46,11 +46,7 @@ export function DocLayout(props: DocLayoutProps) {
|
||||
"hidden xl:flex",
|
||||
)}
|
||||
>
|
||||
<DocSidebar
|
||||
{...props.sideBar}
|
||||
header={props.sidebarHeader}
|
||||
className="pt-1"
|
||||
/>
|
||||
<DocSidebar {...props.sideBar} header={props.sidebarHeader} />
|
||||
</aside>
|
||||
)}
|
||||
<div className="sticky top-sticky-top-height z-stickyMobileSidebar border-b bg-card/50 backdrop-blur-xl p-4 xl:hidden -mx-4">
|
||||
|
||||
@@ -68,7 +68,11 @@ export function DocSidebar(props: ReferenceSideBarProps) {
|
||||
{props.links.map((link, i) => (
|
||||
// biome-ignore lint/suspicious/noArrayIndexKey: TODO - fix this
|
||||
<li key={i}>
|
||||
<SidebarItem link={link} onLinkClick={props.onLinkClick} />
|
||||
<SidebarItem
|
||||
link={link}
|
||||
onLinkClick={props.onLinkClick}
|
||||
isFirst={i === 0}
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
@@ -76,7 +80,11 @@ export function DocSidebar(props: ReferenceSideBarProps) {
|
||||
);
|
||||
}
|
||||
|
||||
function SidebarItem(props: { link: SidebarLink; onLinkClick?: () => void }) {
|
||||
function SidebarItem(props: {
|
||||
link: SidebarLink;
|
||||
onLinkClick?: () => void;
|
||||
isFirst: boolean;
|
||||
}) {
|
||||
const pathname = usePathname();
|
||||
|
||||
if ("separator" in props.link) {
|
||||
@@ -92,6 +100,7 @@ function SidebarItem(props: { link: SidebarLink; onLinkClick?: () => void }) {
|
||||
if (link.isCollapsible === false) {
|
||||
return (
|
||||
<DocSidebarNonCollapsible
|
||||
isFirst={props.isFirst}
|
||||
key={link.name}
|
||||
linkGroup={link}
|
||||
onLinkClick={props.onLinkClick}
|
||||
@@ -145,13 +154,14 @@ function SidebarItem(props: { link: SidebarLink; onLinkClick?: () => void }) {
|
||||
function DocSidebarNonCollapsible(props: {
|
||||
linkGroup: LinkGroup;
|
||||
onLinkClick?: () => void;
|
||||
isFirst: boolean;
|
||||
}) {
|
||||
const pathname = usePathname();
|
||||
const { href, name, links, icon } = props.linkGroup;
|
||||
const isCategoryActive = href ? isSamePage(pathname, href) : false;
|
||||
|
||||
return (
|
||||
<div className="my-4">
|
||||
<div className={cn("my-4", props.isFirst && "mt-0")}>
|
||||
<div className="mb-2 flex items-center gap-2 rounded-lg text-foreground">
|
||||
{icon && <SidebarIcon icon={icon} />}
|
||||
{href ? (
|
||||
@@ -173,7 +183,11 @@ function DocSidebarNonCollapsible(props: {
|
||||
return (
|
||||
// biome-ignore lint/suspicious/noArrayIndexKey: TODO - fix this
|
||||
<li key={i}>
|
||||
<SidebarItem link={link} onLinkClick={props.onLinkClick} />
|
||||
<SidebarItem
|
||||
link={link}
|
||||
onLinkClick={props.onLinkClick}
|
||||
isFirst={i === 0}
|
||||
/>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
@@ -238,7 +252,11 @@ function DocSidebarCategory(props: {
|
||||
return (
|
||||
// biome-ignore lint/suspicious/noArrayIndexKey: TODO - fix this
|
||||
<li key={i}>
|
||||
<SidebarItem link={link} onLinkClick={props.onLinkClick} />
|
||||
<SidebarItem
|
||||
link={link}
|
||||
onLinkClick={props.onLinkClick}
|
||||
isFirst={i === 0}
|
||||
/>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -1,5 +1,53 @@
|
||||
# wagmi-inapp
|
||||
|
||||
## 0.0.56
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`83198b2`](https://github.com/thirdweb-dev/js/commit/83198b2ea4dd29b1debb7db2dc2eabba57e9c4d7), [`ad49560`](https://github.com/thirdweb-dev/js/commit/ad4956033107c52ecdf4cd9f5b2bff87867f3bec)]:
|
||||
- thirdweb@5.119.4
|
||||
- @thirdweb-dev/wagmi-adapter@0.2.211
|
||||
|
||||
## 0.0.55
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`3a18c6e`](https://github.com/thirdweb-dev/js/commit/3a18c6e6a8c12cecaf684fff2dfc5a73c77c40c9)]:
|
||||
- thirdweb@5.119.3
|
||||
- @thirdweb-dev/wagmi-adapter@0.2.210
|
||||
|
||||
## 0.0.54
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`32c2ff6`](https://github.com/thirdweb-dev/js/commit/32c2ff62f8a2ca8da82c1e8d9bb73def95ec41db)]:
|
||||
- thirdweb@5.119.2
|
||||
- @thirdweb-dev/wagmi-adapter@0.2.209
|
||||
|
||||
## 0.0.53
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`e13b9a7`](https://github.com/thirdweb-dev/js/commit/e13b9a784aca3a9b5d797fc6e011c6d1a10542e9), [`da4a4a5`](https://github.com/thirdweb-dev/js/commit/da4a4a540a6b141050af8fc8332621826d8dd835)]:
|
||||
- thirdweb@5.119.1
|
||||
- @thirdweb-dev/wagmi-adapter@0.2.208
|
||||
|
||||
## 0.0.52
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`5352152`](https://github.com/thirdweb-dev/js/commit/53521528353a00691e82405460d4fb869ac986de)]:
|
||||
- thirdweb@5.119.0
|
||||
- @thirdweb-dev/wagmi-adapter@0.2.207
|
||||
|
||||
## 0.0.51
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`1caf6dd`](https://github.com/thirdweb-dev/js/commit/1caf6dd4609ac72a06999ef0d70f31a371e5108a)]:
|
||||
- thirdweb@5.118.2
|
||||
- @thirdweb-dev/wagmi-adapter@0.2.206
|
||||
|
||||
## 0.0.50
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "wagmi-inapp",
|
||||
"private": true,
|
||||
"version": "0.0.50",
|
||||
"version": "0.0.56",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,47 @@
|
||||
# @thirdweb-dev/nebula
|
||||
|
||||
## 0.2.114
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`83198b2`](https://github.com/thirdweb-dev/js/commit/83198b2ea4dd29b1debb7db2dc2eabba57e9c4d7), [`ad49560`](https://github.com/thirdweb-dev/js/commit/ad4956033107c52ecdf4cd9f5b2bff87867f3bec)]:
|
||||
- thirdweb@5.119.4
|
||||
|
||||
## 0.2.113
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`3a18c6e`](https://github.com/thirdweb-dev/js/commit/3a18c6e6a8c12cecaf684fff2dfc5a73c77c40c9)]:
|
||||
- thirdweb@5.119.3
|
||||
|
||||
## 0.2.112
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`32c2ff6`](https://github.com/thirdweb-dev/js/commit/32c2ff62f8a2ca8da82c1e8d9bb73def95ec41db)]:
|
||||
- thirdweb@5.119.2
|
||||
|
||||
## 0.2.111
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`e13b9a7`](https://github.com/thirdweb-dev/js/commit/e13b9a784aca3a9b5d797fc6e011c6d1a10542e9), [`da4a4a5`](https://github.com/thirdweb-dev/js/commit/da4a4a540a6b141050af8fc8332621826d8dd835)]:
|
||||
- thirdweb@5.119.1
|
||||
|
||||
## 0.2.110
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`5352152`](https://github.com/thirdweb-dev/js/commit/53521528353a00691e82405460d4fb869ac986de)]:
|
||||
- thirdweb@5.119.0
|
||||
|
||||
## 0.2.109
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`1caf6dd`](https://github.com/thirdweb-dev/js/commit/1caf6dd4609ac72a06999ef0d70f31a371e5108a)]:
|
||||
- thirdweb@5.118.2
|
||||
|
||||
## 0.2.108
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -57,5 +57,5 @@
|
||||
"type": "module",
|
||||
"types": "./dist/types/exports/thirdweb.d.ts",
|
||||
"typings": "./dist/types/exports/thirdweb.d.ts",
|
||||
"version": "0.2.108"
|
||||
"version": "0.2.114"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,45 @@
|
||||
# thirdweb
|
||||
|
||||
## 5.119.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#8733](https://github.com/thirdweb-dev/js/pull/8733) [`83198b2`](https://github.com/thirdweb-dev/js/commit/83198b2ea4dd29b1debb7db2dc2eabba57e9c4d7) Thanks [@Dhaiwat10](https://github.com/Dhaiwat10)! - Updates the hardcoded ENS Universal Resolver address from 0xce01f8eee7E479C928F8919abD53E553a36CeF67 (old) to 0xeeeeeeee14d718c2b47d9923deab1335e144eeee (new)
|
||||
|
||||
- [#8735](https://github.com/thirdweb-dev/js/pull/8735) [`ad49560`](https://github.com/thirdweb-dev/js/commit/ad4956033107c52ecdf4cd9f5b2bff87867f3bec) Thanks [@0xFirekeeper](https://github.com/0xFirekeeper)! - ENS: add coinType param to reverse resolver
|
||||
|
||||
## 5.119.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#8726](https://github.com/thirdweb-dev/js/pull/8726) [`3a18c6e`](https://github.com/thirdweb-dev/js/commit/3a18c6e6a8c12cecaf684fff2dfc5a73c77c40c9) Thanks [@0xFirekeeper](https://github.com/0xFirekeeper)! - [CLI] fix stylus abi exports
|
||||
|
||||
## 5.119.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#8724](https://github.com/thirdweb-dev/js/pull/8724) [`32c2ff6`](https://github.com/thirdweb-dev/js/commit/32c2ff62f8a2ca8da82c1e8d9bb73def95ec41db) Thanks [@0xFirekeeper](https://github.com/0xFirekeeper)! - [CLI] create-stylus fixes
|
||||
|
||||
## 5.119.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#8700](https://github.com/thirdweb-dev/js/pull/8700) [`e13b9a7`](https://github.com/thirdweb-dev/js/commit/e13b9a784aca3a9b5d797fc6e011c6d1a10542e9) Thanks [@0xFirekeeper](https://github.com/0xFirekeeper)! - Deprecate the `thirdweb create` CLI command
|
||||
|
||||
- [#8698](https://github.com/thirdweb-dev/js/pull/8698) [`da4a4a5`](https://github.com/thirdweb-dev/js/commit/da4a4a540a6b141050af8fc8332621826d8dd835) Thanks [@Yash094](https://github.com/Yash094)! - Add `screen` prop to `useWalletDetailsModal` to allow opening the modal directly at a specific screen. Use `screen: "export"` to open directly at the Export Private Key screen.
|
||||
|
||||
## 5.119.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#8686](https://github.com/thirdweb-dev/js/pull/8686) [`5352152`](https://github.com/thirdweb-dev/js/commit/53521528353a00691e82405460d4fb869ac986de) Thanks [@Yash094](https://github.com/Yash094)! - Add `requireApproval` option to `useConnectModal` hook to require terms of service and privacy policy acceptance before connecting an in-app wallet
|
||||
|
||||
## 5.118.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#8671](https://github.com/thirdweb-dev/js/pull/8671) [`1caf6dd`](https://github.com/thirdweb-dev/js/commit/1caf6dd4609ac72a06999ef0d70f31a371e5108a) Thanks [@0xFirekeeper](https://github.com/0xFirekeeper)! - fix walletconnect not working on berachain and unallowlisted chains
|
||||
|
||||
## 5.118.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -431,5 +431,5 @@
|
||||
}
|
||||
},
|
||||
"typings": "./dist/types/exports/thirdweb.d.ts",
|
||||
"version": "5.118.1"
|
||||
"version": "5.119.4"
|
||||
}
|
||||
|
||||
@@ -44,6 +44,14 @@ async function main() {
|
||||
break;
|
||||
}
|
||||
|
||||
case "create": {
|
||||
console.info(
|
||||
"The 'thirdweb create' command is deprecated. Please use the thirdweb dashboard instead: https://thirdweb.com/dashboard",
|
||||
);
|
||||
process.exit(1);
|
||||
break;
|
||||
}
|
||||
|
||||
case "login": {
|
||||
// Not implemented yet
|
||||
console.info(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { parseAbiItem } from "abitype";
|
||||
import open from "open";
|
||||
import ora, { type Ora } from "ora";
|
||||
import prompts from "prompts";
|
||||
@@ -16,12 +17,6 @@ export async function publishStylus(secretKey?: string) {
|
||||
|
||||
checkPrerequisites(spinner, "cargo", ["--version"], "Rust (cargo)");
|
||||
checkPrerequisites(spinner, "rustc", ["--version"], "Rust compiler (rustc)");
|
||||
checkPrerequisites(
|
||||
spinner,
|
||||
"solc",
|
||||
["--version"],
|
||||
"Solidity compiler (solc)",
|
||||
);
|
||||
|
||||
const uri = await buildStylus(spinner, secretKey);
|
||||
|
||||
@@ -35,12 +30,6 @@ export async function deployStylus(secretKey?: string) {
|
||||
|
||||
checkPrerequisites(spinner, "cargo", ["--version"], "Rust (cargo)");
|
||||
checkPrerequisites(spinner, "rustc", ["--version"], "Rust compiler (rustc)");
|
||||
checkPrerequisites(
|
||||
spinner,
|
||||
"solc",
|
||||
["--version"],
|
||||
"Solidity compiler (solc)",
|
||||
);
|
||||
|
||||
const uri = await buildStylus(spinner, secretKey);
|
||||
|
||||
@@ -99,9 +88,9 @@ async function buildStylus(spinner: Ora, secretKey?: string) {
|
||||
}
|
||||
spinner.succeed("Initcode generated.");
|
||||
|
||||
// Step 3: Run stylus command to generate abi
|
||||
// Step 3: Run stylus command to generate abi (plain Solidity, no solc needed)
|
||||
spinner.start("Generating ABI...");
|
||||
const abiResult = spawnSync("cargo", ["stylus", "export-abi", "--json"], {
|
||||
const abiResult = spawnSync("cargo", ["stylus", "export-abi"], {
|
||||
encoding: "utf-8",
|
||||
});
|
||||
if (abiResult.status !== 0) {
|
||||
@@ -109,15 +98,21 @@ async function buildStylus(spinner: Ora, secretKey?: string) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const abiContent = abiResult.stdout.trim();
|
||||
if (!abiContent) {
|
||||
const solidityOutput = abiResult.stdout.trim();
|
||||
if (!solidityOutput) {
|
||||
spinner.fail("Failed to generate ABI.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const interfaces = parseSolidityInterfaces(solidityOutput);
|
||||
if (interfaces.length === 0) {
|
||||
spinner.fail("No interfaces found in ABI output.");
|
||||
process.exit(1);
|
||||
}
|
||||
spinner.succeed("ABI generated.");
|
||||
|
||||
// Step 3.5: detect the constructor
|
||||
spinner.start("Detecting constructor…");
|
||||
spinner.start("Detecting constructor\u2026");
|
||||
const constructorResult = spawnSync("cargo", ["stylus", "constructor"], {
|
||||
encoding: "utf-8",
|
||||
});
|
||||
@@ -127,23 +122,16 @@ async function buildStylus(spinner: Ora, secretKey?: string) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const constructorSigRaw = constructorResult.stdout.trim(); // e.g. "constructor(address owner)"
|
||||
const constructorSigRaw = constructorResult.stdout.trim();
|
||||
spinner.succeed(`Constructor found: ${constructorSigRaw || "none"}`);
|
||||
|
||||
// Step 4: Process the output
|
||||
const parts = abiContent.split(/======= <stdin>:/g).filter(Boolean);
|
||||
const contractNames = extractContractNamesFromExportAbi(abiContent);
|
||||
let selectedIndex = 0;
|
||||
|
||||
let selectedContractName: string | undefined;
|
||||
let selectedAbiContent: string | undefined;
|
||||
|
||||
if (contractNames.length === 1) {
|
||||
selectedContractName = contractNames[0]?.replace(/^I/, "");
|
||||
selectedAbiContent = parts[0];
|
||||
} else {
|
||||
if (interfaces.length > 1) {
|
||||
const response = await prompts({
|
||||
choices: contractNames.map((name, idx) => ({
|
||||
title: name,
|
||||
choices: interfaces.map((iface, idx) => ({
|
||||
title: iface.name,
|
||||
value: idx,
|
||||
})),
|
||||
message: "Select entrypoint:",
|
||||
@@ -151,46 +139,31 @@ async function buildStylus(spinner: Ora, secretKey?: string) {
|
||||
type: "select",
|
||||
});
|
||||
|
||||
const selectedIndex = response.contract;
|
||||
|
||||
if (typeof selectedIndex !== "number") {
|
||||
if (typeof response.contract !== "number") {
|
||||
spinner.fail("No contract selected.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
selectedContractName = contractNames[selectedIndex]?.replace(/^I/, "");
|
||||
selectedAbiContent = parts[selectedIndex];
|
||||
selectedIndex = response.contract;
|
||||
}
|
||||
|
||||
if (!selectedAbiContent) {
|
||||
throw new Error("Entrypoint not found");
|
||||
}
|
||||
|
||||
if (!selectedContractName) {
|
||||
spinner.fail("Error: Could not determine contract name from ABI output.");
|
||||
const selectedInterface = interfaces[selectedIndex];
|
||||
if (!selectedInterface) {
|
||||
spinner.fail("No interface found.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
let cleanedAbi = "";
|
||||
try {
|
||||
const jsonMatch = selectedAbiContent.match(/\[.*\]/s);
|
||||
if (jsonMatch) {
|
||||
cleanedAbi = jsonMatch[0];
|
||||
} else {
|
||||
throw new Error("No valid JSON ABI found in the file.");
|
||||
}
|
||||
} catch (error) {
|
||||
spinner.fail("Error: ABI file contains invalid format.");
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// biome-ignore lint/suspicious/noExplicitAny: <>
|
||||
const abiArray: any[] = JSON.parse(cleanedAbi);
|
||||
const selectedContractName = selectedInterface.name.replace(/^I/, "");
|
||||
// biome-ignore lint/suspicious/noExplicitAny: ABI is untyped JSON from parseAbiItem
|
||||
const abiArray: any[] = selectedInterface.abi;
|
||||
|
||||
const constructorAbi = constructorSigToAbi(constructorSigRaw);
|
||||
if (constructorAbi && !abiArray.some((e) => e.type === "constructor")) {
|
||||
abiArray.unshift(constructorAbi); // put it at the top for readability
|
||||
if (
|
||||
constructorAbi &&
|
||||
// biome-ignore lint/suspicious/noExplicitAny: ABI entries have varying shapes
|
||||
!abiArray.some((e: any) => e.type === "constructor")
|
||||
) {
|
||||
abiArray.unshift(constructorAbi);
|
||||
}
|
||||
|
||||
const metadata = {
|
||||
@@ -256,10 +229,94 @@ async function buildStylus(spinner: Ora, secretKey?: string) {
|
||||
}
|
||||
}
|
||||
|
||||
function extractContractNamesFromExportAbi(abiRawOutput: string): string[] {
|
||||
return [...abiRawOutput.matchAll(/<stdin>:(I?[A-Za-z0-9_]+)/g)]
|
||||
.map((m) => m[1])
|
||||
.filter((name): name is string => typeof name === "string");
|
||||
// biome-ignore lint/suspicious/noExplicitAny: ABI items from parseAbiItem are untyped
|
||||
type AbiEntry = any;
|
||||
type ParsedInterface = { name: string; abi: AbiEntry[] };
|
||||
|
||||
function parseSolidityInterfaces(source: string): ParsedInterface[] {
|
||||
const results: ParsedInterface[] = [];
|
||||
|
||||
const ifaceRegex = /interface\s+(I?[A-Za-z0-9_]+)\s*\{([\s\S]*?)\n\}/g;
|
||||
for (
|
||||
let ifaceMatch = ifaceRegex.exec(source);
|
||||
ifaceMatch !== null;
|
||||
ifaceMatch = ifaceRegex.exec(source)
|
||||
) {
|
||||
const name = ifaceMatch[1] ?? "";
|
||||
const body = ifaceMatch[2] ?? "";
|
||||
const abi: AbiEntry[] = [];
|
||||
|
||||
// Build struct lookup: name -> tuple type string
|
||||
const structs = new Map<string, string>();
|
||||
const structRegex = /struct\s+(\w+)\s*\{([^}]*)\}/g;
|
||||
for (
|
||||
let structMatch = structRegex.exec(body);
|
||||
structMatch !== null;
|
||||
structMatch = structRegex.exec(body)
|
||||
) {
|
||||
const fields = (structMatch[2] ?? "")
|
||||
.split(";")
|
||||
.map((f) => f.trim())
|
||||
.filter(Boolean)
|
||||
.map((f) => f.split(/\s+/)[0] ?? "");
|
||||
structs.set(structMatch[1] ?? "", `(${fields.join(",")})`);
|
||||
}
|
||||
|
||||
// Resolve struct references in a type string (iterative for nested structs)
|
||||
const resolveStructs = (sig: string): string => {
|
||||
let resolved = sig;
|
||||
for (let i = 0; i < 10; i++) {
|
||||
let changed = false;
|
||||
for (const [sName, sTuple] of structs) {
|
||||
const re = new RegExp(`\\b${sName}\\b(\\[\\])?`, "g");
|
||||
const next = resolved.replace(
|
||||
re,
|
||||
(_, arr) => `${sTuple}${arr ?? ""}`,
|
||||
);
|
||||
if (next !== resolved) {
|
||||
resolved = next;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (!changed) break;
|
||||
}
|
||||
return resolved;
|
||||
};
|
||||
|
||||
// Extract each statement (function/error/event) delimited by ;
|
||||
const statements = body
|
||||
.split(";")
|
||||
.map((s) => s.replace(/\n/g, " ").trim())
|
||||
.filter(
|
||||
(s) =>
|
||||
s.startsWith("function ") ||
|
||||
s.startsWith("error ") ||
|
||||
s.startsWith("event "),
|
||||
);
|
||||
|
||||
for (const stmt of statements) {
|
||||
// Strip Solidity qualifiers that abitype doesn't expect
|
||||
let cleaned = stmt
|
||||
.replace(/\b(external|public|internal|private)\b/g, "")
|
||||
.replace(/\b(memory|calldata|storage)\b/g, "")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
|
||||
// Resolve struct type names to tuple types
|
||||
cleaned = resolveStructs(cleaned);
|
||||
|
||||
try {
|
||||
const parsed = parseAbiItem(cleaned);
|
||||
abi.push(parsed);
|
||||
} catch {
|
||||
// Skip unparseable items
|
||||
}
|
||||
}
|
||||
|
||||
results.push({ abi, name });
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
function getUrl(hash: string, command: string) {
|
||||
|
||||
@@ -8,12 +8,6 @@ export async function createStylusProject() {
|
||||
|
||||
checkPrerequisites(spinner, "cargo", ["--version"], "Rust (cargo)");
|
||||
checkPrerequisites(spinner, "rustc", ["--version"], "Rust compiler (rustc)");
|
||||
checkPrerequisites(
|
||||
spinner,
|
||||
"solc",
|
||||
["--version"],
|
||||
"Solidity compiler (solc)",
|
||||
);
|
||||
|
||||
// Step 1: Ensure cargo is installed
|
||||
const cargoCheck = spawnSync("cargo", ["--version"]);
|
||||
@@ -24,7 +18,7 @@ export async function createStylusProject() {
|
||||
|
||||
// Step 2: Install stylus etc.
|
||||
spinner.start("Installing Stylus...");
|
||||
const install = spawnSync("cargo", ["install", "--force", "cargo-stylus"], {
|
||||
const install = spawnSync("cargo", ["install", "cargo-stylus"], {
|
||||
stdio: "inherit",
|
||||
});
|
||||
if (install.status !== 0) {
|
||||
@@ -33,7 +27,7 @@ export async function createStylusProject() {
|
||||
}
|
||||
spinner.succeed("Stylus installed.");
|
||||
|
||||
spawnSync("rustup", ["default", "1.87"], {
|
||||
spawnSync("rustup", ["default", "stable"], {
|
||||
stdio: "inherit",
|
||||
});
|
||||
spawnSync("rustup", ["target", "add", "wasm32-unknown-unknown"], {
|
||||
|
||||
+14
-6
@@ -14,14 +14,22 @@ export type ReverseParams = {
|
||||
type: "bytes";
|
||||
name: "reverseName";
|
||||
}>;
|
||||
coinType: AbiParameterToPrimitiveType<{
|
||||
type: "uint256";
|
||||
name: "coinType";
|
||||
}>;
|
||||
};
|
||||
|
||||
export const FN_SELECTOR = "0xec11c823" as const;
|
||||
export const FN_SELECTOR = "0x5d78a217" as const;
|
||||
const FN_INPUTS = [
|
||||
{
|
||||
name: "reverseName",
|
||||
type: "bytes",
|
||||
},
|
||||
{
|
||||
name: "coinType",
|
||||
type: "uint256",
|
||||
},
|
||||
] as const;
|
||||
const FN_OUTPUTS = [
|
||||
{
|
||||
@@ -33,9 +41,6 @@ const FN_OUTPUTS = [
|
||||
{
|
||||
type: "address",
|
||||
},
|
||||
{
|
||||
type: "address",
|
||||
},
|
||||
] as const;
|
||||
|
||||
/**
|
||||
@@ -70,7 +75,10 @@ export function isReverseSupported(availableSelectors: string[]) {
|
||||
* ```
|
||||
*/
|
||||
export function encodeReverseParams(options: ReverseParams) {
|
||||
return encodeAbiParameters(FN_INPUTS, [options.reverseName]);
|
||||
return encodeAbiParameters(FN_INPUTS, [
|
||||
options.reverseName,
|
||||
options.coinType,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -128,6 +136,6 @@ export async function reverse(options: BaseTransactionOptions<ReverseParams>) {
|
||||
return readContract({
|
||||
contract: options.contract,
|
||||
method: [FN_SELECTOR, FN_INPUTS, FN_OUTPUTS] as const,
|
||||
params: [options.reverseName],
|
||||
params: [options.reverseName, options.coinType],
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export const UNIVERSAL_RESOLVER_ADDRESS =
|
||||
"0xce01f8eee7E479C928F8919abD53E553a36CeF67";
|
||||
"0xeeeeeeee14d718c2b47d9923deab1335e144eeee";
|
||||
export const BASENAME_RESOLVER_ADDRESS =
|
||||
"0xC6d566A56A1aFf6508b41f6c90ff131615583BCD";
|
||||
export const BASE_SEPOLIA_BASENAME_RESOLVER_ADDRESS =
|
||||
|
||||
@@ -3,8 +3,6 @@ import { ethereum } from "../../chains/chain-definitions/ethereum.js";
|
||||
import type { Chain } from "../../chains/types.js";
|
||||
import type { ThirdwebClient } from "../../client/client.js";
|
||||
import { getContract } from "../../contract/contract.js";
|
||||
import { toHex } from "../../utils/encoding/hex.js";
|
||||
import { packetToBytes } from "../../utils/ens/packetToBytes.js";
|
||||
import { withCache } from "../../utils/promise/withCache.js";
|
||||
import { reverse } from "./__generated__/UniversalResolver/read/reverse.js";
|
||||
import { UNIVERSAL_RESOLVER_ADDRESS } from "./constants.js";
|
||||
@@ -33,7 +31,9 @@ export type ResolveNameOptions = {
|
||||
* @extension ENS
|
||||
* @returns A promise that resolves to the Ethereum address.
|
||||
*/
|
||||
export async function resolveName(options: ResolveNameOptions) {
|
||||
export async function resolveName(
|
||||
options: ResolveNameOptions,
|
||||
): Promise<string | null> {
|
||||
const { client, address, resolverAddress, resolverChain } = options;
|
||||
|
||||
return withCache(
|
||||
@@ -44,25 +44,28 @@ export async function resolveName(options: ResolveNameOptions) {
|
||||
client,
|
||||
});
|
||||
|
||||
const reverseName = toHex(
|
||||
packetToBytes(`${address.toLowerCase().substring(2)}.addr.reverse`),
|
||||
);
|
||||
|
||||
const [name, resolvedAddress] = await reverse({
|
||||
const [name] = await reverse({
|
||||
contract,
|
||||
reverseName,
|
||||
reverseName: address as `0x${string}`,
|
||||
coinType: 60n,
|
||||
}).catch((e) => {
|
||||
if ("data" in e && e.data === "0x7199966d") {
|
||||
return [null, address] as const;
|
||||
// Re-throw verification errors so callers can detect data integrity issues
|
||||
if (
|
||||
typeof e === "object" &&
|
||||
e !== null &&
|
||||
"data" in e &&
|
||||
typeof e.data === "string"
|
||||
) {
|
||||
// ReverseAddressMismatch(string,bytes) = 0xef9c03ce
|
||||
if (e.data.startsWith("0xef9c03ce")) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
throw e;
|
||||
// Swallow expected "no resolver" / "no name" errors
|
||||
return [null] as const;
|
||||
});
|
||||
|
||||
if (address.toLowerCase() !== resolvedAddress.toLowerCase()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return name;
|
||||
return name || null;
|
||||
},
|
||||
{
|
||||
cacheKey: `ens:name:${resolverChain?.id || 1}:${address}`,
|
||||
|
||||
@@ -383,8 +383,11 @@ export function DetailsModal(props: {
|
||||
connectOptions: DetailsModalConnectOptions | undefined;
|
||||
assetTabs?: AssetTabs[];
|
||||
showBalanceInFiat?: SupportedFiatCurrency;
|
||||
initialScreen?: WalletDetailsModalScreen;
|
||||
}) {
|
||||
const [screen, setScreen] = useState<WalletDetailsModalScreen>("main");
|
||||
const [screen, setScreen] = useState<WalletDetailsModalScreen>(
|
||||
props.initialScreen || "main",
|
||||
);
|
||||
const { disconnect } = useDisconnect();
|
||||
const [isOpen, setIsOpen] = useState(true);
|
||||
|
||||
@@ -887,7 +890,7 @@ export function DetailsModal(props: {
|
||||
supportedTokens={props.supportedTokens}
|
||||
/>
|
||||
);
|
||||
} else if (screen === "private-key") {
|
||||
} else if (screen === "private-key" || screen === "export") {
|
||||
content = (
|
||||
<PrivateKey
|
||||
client={client} // do not use the useCustomTheme hook to get this, it's not valid here
|
||||
@@ -1724,6 +1727,22 @@ export type UseWalletDetailsModalOptions = {
|
||||
* @param screen The name of the screen that was being shown when user closed the modal
|
||||
*/
|
||||
onClose?: (screen: string) => void;
|
||||
|
||||
/**
|
||||
* The initial screen to show when the modal opens.
|
||||
*
|
||||
* @defaultValue "main"
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* // Open directly to the Export Private Key screen
|
||||
* detailsModal.open({
|
||||
* client,
|
||||
* screen: "export"
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
screen?: WalletDetailsModalScreen;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -1750,6 +1769,14 @@ export type UseWalletDetailsModalOptions = {
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* ### Open directly to Export Private Key screen
|
||||
* ```tsx
|
||||
* detailsModal.open({
|
||||
* client,
|
||||
* screen: "export"
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* ### Callback for when the modal is closed
|
||||
* ```tsx
|
||||
* detailsModal.open({
|
||||
@@ -1800,6 +1827,7 @@ export function useWalletDetailsModal() {
|
||||
showTestnetFaucet: props.showTestnetFaucet,
|
||||
}}
|
||||
displayBalanceToken={props.displayBalanceToken}
|
||||
initialScreen={props.screen}
|
||||
locale={locale}
|
||||
onDisconnect={(info) => {
|
||||
props.onDisconnect?.(info);
|
||||
|
||||
@@ -118,6 +118,7 @@ function Modal(
|
||||
termsOfServiceUrl: props.termsOfServiceUrl,
|
||||
title: props.title,
|
||||
titleIconUrl: props.titleIcon,
|
||||
requireApproval: props.requireApproval,
|
||||
};
|
||||
}, [
|
||||
props.privacyPolicyUrl,
|
||||
@@ -125,6 +126,7 @@ function Modal(
|
||||
props.termsOfServiceUrl,
|
||||
props.title,
|
||||
props.titleIcon,
|
||||
props.requireApproval,
|
||||
]);
|
||||
|
||||
return (
|
||||
@@ -449,6 +451,22 @@ export type UseConnectModalOptions = {
|
||||
* Refer to the [`SiweAuthOptions`](https://portal.thirdweb.com/references/typescript/v5/SiweAuthOptions) for more details
|
||||
*/
|
||||
auth?: SiweAuthOptions;
|
||||
|
||||
/**
|
||||
* Require terms of service and privacy policy to be accepted before connecting an in-app wallet.
|
||||
*
|
||||
* By default it's `false`
|
||||
* @example
|
||||
* ```tsx
|
||||
* function Example() {
|
||||
* const { connect } = useConnectModal();
|
||||
* return <button onClick={() => connect({ client, requireApproval: true })}>
|
||||
* Connect
|
||||
* </button>
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
requireApproval?: boolean;
|
||||
};
|
||||
|
||||
// TODO: consilidate Button/Embed/Modal props into one type with extras
|
||||
|
||||
@@ -110,14 +110,11 @@ export async function connectWC(
|
||||
}
|
||||
|
||||
let optionalChains: Chain[] | undefined = wcOptions?.optionalChains;
|
||||
let chainToRequest = options.chain;
|
||||
const chainToRequest = options.chain;
|
||||
|
||||
// ignore the given options chains - and set the safe supported chains
|
||||
if (walletId === "global.safe") {
|
||||
optionalChains = chainsToRequestForSafe.map(getCachedChain);
|
||||
if (chainToRequest && !optionalChains.includes(chainToRequest)) {
|
||||
chainToRequest = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// For UniversalProvider, we still need chain configuration for session management
|
||||
@@ -386,17 +383,6 @@ async function initProvider(
|
||||
"@walletconnect/universal-provider"
|
||||
);
|
||||
|
||||
let optionalChains: Chain[] | undefined = wcOptions?.optionalChains;
|
||||
let chainToRequest = options.chain;
|
||||
|
||||
// ignore the given options chains - and set the safe supported chains
|
||||
if (walletId === "global.safe") {
|
||||
optionalChains = chainsToRequestForSafe.map(getCachedChain);
|
||||
if (chainToRequest && !optionalChains.includes(chainToRequest)) {
|
||||
chainToRequest = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const provider = await UniversalProvider.init({
|
||||
metadata: {
|
||||
description:
|
||||
@@ -736,4 +722,5 @@ const chainsToRequestForSafe = [
|
||||
1101, // Polygon zkEVM Mainnet
|
||||
324, // zkSync Era mainnet
|
||||
534352, // Scroll mainnet
|
||||
80094, // Berachain mainnet
|
||||
];
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# @thirdweb-dev/wagmi-adapter
|
||||
|
||||
## 0.2.211
|
||||
|
||||
## 0.2.210
|
||||
|
||||
## 0.2.209
|
||||
|
||||
## 0.2.208
|
||||
|
||||
## 0.2.207
|
||||
|
||||
## 0.2.206
|
||||
|
||||
## 0.2.205
|
||||
|
||||
## 0.2.204
|
||||
|
||||
@@ -56,5 +56,5 @@
|
||||
"type": "module",
|
||||
"types": "./dist/types/exports/thirdweb.d.ts",
|
||||
"typings": "./dist/types/exports/thirdweb.d.ts",
|
||||
"version": "0.2.205"
|
||||
"version": "0.2.211"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user