Compare commits
26
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f61beace93 | ||
|
|
08403dd9da | ||
|
|
36f3191f3e | ||
|
|
7d2a446ece | ||
|
|
ab575726fe | ||
|
|
8e28b0f5e7 | ||
|
|
e7f1ce43b0 | ||
|
|
224b7241dc | ||
|
|
a0eee749a2 | ||
|
|
20bbad1abc | ||
|
|
a430160e6b | ||
|
|
5745481584 | ||
|
|
afdedb1feb | ||
|
|
01293857fa | ||
|
|
ecdcf82cf3 | ||
|
|
12b07aad9a | ||
|
|
c0e2c9457f | ||
|
|
654b8ab351 | ||
|
|
a5578abf66 | ||
|
|
541e488d5b | ||
|
|
088f2567f6 | ||
|
|
6803e8ca09 | ||
|
|
d60e544a24 | ||
|
|
cc20f93e17 | ||
|
|
329bccec4b | ||
|
|
02d55a063e |
+1
-1
@@ -43,7 +43,7 @@
|
||||
"@playwright/test": "1.31.2",
|
||||
"@trivago/prettier-plugin-sort-imports": "^4.1.1",
|
||||
"@types/node": "^18.15.11",
|
||||
"eslint": "^8.37.0",
|
||||
"eslint": "^8.42.0",
|
||||
"mocha": "^10.2.0",
|
||||
"prettier": "^2.8.7",
|
||||
"turbo": "^1.9.8",
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
# @thirdweb-dev/auth
|
||||
|
||||
## 3.2.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#1150](https://github.com/thirdweb-dev/js/pull/1150) [`d60e544a`](https://github.com/thirdweb-dev/js/commit/d60e544a240b93312743e62096ee2dc77d0c1bd1) Thanks [@adam-maj](https://github.com/adam-maj)! - Add support for multipled logged in accounts and token refreshing to auth
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1165](https://github.com/thirdweb-dev/js/pull/1165) [`088f2567`](https://github.com/thirdweb-dev/js/commit/088f2567f615360edd44776b20ae0bedff250f43) Thanks [@adam-maj](https://github.com/adam-maj)! - Include chain id in login payload
|
||||
|
||||
- Updated dependencies [[`654b8ab3`](https://github.com/thirdweb-dev/js/commit/654b8ab35184e02127a5a47f05d78606dd5b29ca), [`329bccec`](https://github.com/thirdweb-dev/js/commit/329bccec4b88b02db5bac6c1415158928843376a)]:
|
||||
- @thirdweb-dev/wallets@0.3.0
|
||||
|
||||
## 3.1.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/auth",
|
||||
"version": "3.1.1",
|
||||
"version": "3.2.0",
|
||||
"main": "dist/thirdweb-dev-auth.cjs.js",
|
||||
"module": "dist/thirdweb-dev-auth.esm.js",
|
||||
"browser": {
|
||||
@@ -101,7 +101,7 @@
|
||||
"@typescript-eslint/parser": "^5.33.0",
|
||||
"chai": "^4.3.6",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"eslint": "^8.21.0",
|
||||
"eslint": "^8.42.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-config-thirdweb": "workspace:*",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
const THIRDWEB_AUTH_COOKIE_PREFIX = `thirdweb_auth`;
|
||||
export const THIRDWEB_AUTH_TOKEN_COOKIE_PREFIX = `${THIRDWEB_AUTH_COOKIE_PREFIX}_token`;
|
||||
export const THIRDWEB_AUTH_ACTIVE_ACCOUNT_COOKIE = `${THIRDWEB_AUTH_COOKIE_PREFIX}_active_account`;
|
||||
|
||||
export const THIRDWEB_AUTH_DEFAULT_LOGIN_PAYLOAD_DURATION_IN_SECONDS = 60 * 10;
|
||||
export const THIRDWEB_AUTH_DEFAULT_TOKEN_DURATION_IN_SECONDS = 60 * 60 * 24;
|
||||
export const THIRDWEB_AUTH_DEFAULT_REFRESH_INTERVAL_IN_SECONDS = 5;
|
||||
@@ -1,3 +1,7 @@
|
||||
import {
|
||||
THIRDWEB_AUTH_DEFAULT_LOGIN_PAYLOAD_DURATION_IN_SECONDS,
|
||||
THIRDWEB_AUTH_DEFAULT_TOKEN_DURATION_IN_SECONDS,
|
||||
} from "../constants";
|
||||
import {
|
||||
LoginOptions,
|
||||
LoginPayload,
|
||||
@@ -15,6 +19,7 @@ import {
|
||||
Json,
|
||||
LoginOptionsSchema,
|
||||
AuthenticationPayload,
|
||||
AuthenticationPayloadDataInput,
|
||||
} from "./schema";
|
||||
import { isBrowser } from "./utils";
|
||||
import type { GenericAuthWallet } from "@thirdweb-dev/wallets";
|
||||
@@ -54,9 +59,17 @@ export class ThirdwebAuth {
|
||||
chain_id: chainId,
|
||||
nonce: parsedOptions?.nonce,
|
||||
expiration_time:
|
||||
parsedOptions?.expirationTime || new Date(Date.now() + 1000 * 60 * 10),
|
||||
parsedOptions?.expirationTime ||
|
||||
new Date(
|
||||
Date.now() +
|
||||
1000 * THIRDWEB_AUTH_DEFAULT_LOGIN_PAYLOAD_DURATION_IN_SECONDS,
|
||||
),
|
||||
invalid_before:
|
||||
parsedOptions?.invalidBefore || new Date(Date.now() - 1000 * 60 * 10),
|
||||
parsedOptions?.invalidBefore ||
|
||||
new Date(
|
||||
Date.now() -
|
||||
1000 * THIRDWEB_AUTH_DEFAULT_LOGIN_PAYLOAD_DURATION_IN_SECONDS,
|
||||
),
|
||||
resources: parsedOptions?.resources,
|
||||
});
|
||||
}
|
||||
@@ -218,41 +231,37 @@ export class ThirdwebAuth {
|
||||
}
|
||||
|
||||
const adminAddress = await this.wallet.getAddress();
|
||||
const payloadData = AuthenticationPayloadDataSchema.parse({
|
||||
return this.createToken({
|
||||
iss: adminAddress,
|
||||
sub: userAddress,
|
||||
aud: domain,
|
||||
nbf: parsedOptions?.invalidBefore || new Date(),
|
||||
exp:
|
||||
parsedOptions?.expirationTime ||
|
||||
new Date(Date.now() + 1000 * 60 * 60 * 5),
|
||||
new Date(
|
||||
Date.now() + 1000 * THIRDWEB_AUTH_DEFAULT_TOKEN_DURATION_IN_SECONDS,
|
||||
),
|
||||
iat: new Date(),
|
||||
jti: parsedOptions?.tokenId,
|
||||
ctx: session,
|
||||
});
|
||||
}
|
||||
|
||||
const message = JSON.stringify(payloadData);
|
||||
const signature = await this.wallet.signMessage(message);
|
||||
|
||||
// Header used for JWT token specifying hash algorithm
|
||||
const header = {
|
||||
// Specify ECDSA with SHA-256 for hashing algorithm
|
||||
alg: "ES256",
|
||||
typ: "JWT",
|
||||
};
|
||||
|
||||
const encodedHeader = Buffer.from(JSON.stringify(header)).toString(
|
||||
"base64",
|
||||
);
|
||||
const encodedData = Buffer.from(JSON.stringify(payloadData))
|
||||
.toString("base64")
|
||||
.replace(/=/g, "");
|
||||
const encodedSignature = Buffer.from(signature).toString("base64");
|
||||
|
||||
// Generate a JWT token with base64 encoded header, payload, and signature
|
||||
const token = `${encodedHeader}.${encodedData}.${encodedSignature}`;
|
||||
|
||||
return token;
|
||||
public async refresh(token: string, expirationTime?: Date): Promise<string> {
|
||||
const { payload } = this.parseToken(token);
|
||||
return this.createToken({
|
||||
iss: payload.iss,
|
||||
sub: payload.sub,
|
||||
aud: payload.aud,
|
||||
nbf: new Date(),
|
||||
exp:
|
||||
expirationTime ||
|
||||
new Date(
|
||||
Date.now() + 1000 * THIRDWEB_AUTH_DEFAULT_TOKEN_DURATION_IN_SECONDS,
|
||||
),
|
||||
iat: new Date(),
|
||||
ctx: payload.ctx,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -369,6 +378,35 @@ export class ThirdwebAuth {
|
||||
};
|
||||
}
|
||||
|
||||
private async createToken(
|
||||
payload: AuthenticationPayloadDataInput,
|
||||
): Promise<string> {
|
||||
const payloadData = AuthenticationPayloadDataSchema.parse(payload);
|
||||
|
||||
const message = JSON.stringify(payloadData);
|
||||
const signature = await this.wallet.signMessage(message);
|
||||
|
||||
// Header used for JWT token specifying hash algorithm
|
||||
const header = {
|
||||
// Specify ECDSA with SHA-256 for hashing algorithm
|
||||
alg: "ES256",
|
||||
typ: "JWT",
|
||||
};
|
||||
|
||||
const encodedHeader = Buffer.from(JSON.stringify(header)).toString(
|
||||
"base64",
|
||||
);
|
||||
const encodedData = Buffer.from(JSON.stringify(payloadData))
|
||||
.toString("base64")
|
||||
.replace(/=/g, "");
|
||||
const encodedSignature = Buffer.from(signature).toString("base64");
|
||||
|
||||
// Generate a JWT token with base64 encoded header, payload, and signature
|
||||
const token = `${encodedHeader}.${encodedData}.${encodedSignature}`;
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
private async verifySignature(
|
||||
message: string,
|
||||
signature: string,
|
||||
|
||||
@@ -180,6 +180,13 @@ export type AuthenticationPayloadData = z.output<
|
||||
typeof AuthenticationPayloadDataSchema
|
||||
>;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export type AuthenticationPayloadDataInput = z.input<
|
||||
typeof AuthenticationPayloadDataSchema
|
||||
>;
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
|
||||
@@ -1,24 +1,51 @@
|
||||
import {
|
||||
THIRDWEB_AUTH_ACTIVE_ACCOUNT_COOKIE,
|
||||
THIRDWEB_AUTH_TOKEN_COOKIE_PREFIX,
|
||||
} from "../../constants";
|
||||
import { Json } from "../../core/schema";
|
||||
import { ThirdwebAuthContext, ThirdwebAuthUser } from "../types";
|
||||
import { Request } from "express";
|
||||
|
||||
function getToken(req: Request): string | undefined {
|
||||
export function getCookie(req: Request, cookie: string): string | undefined {
|
||||
if (typeof req.cookies.get === "function") {
|
||||
return req.cookies.get(cookie);
|
||||
}
|
||||
|
||||
return req.cookies[cookie];
|
||||
}
|
||||
|
||||
export function getActiveCookie(req: Request): string | undefined {
|
||||
if (!req.cookies) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const activeAccount = getCookie(req, THIRDWEB_AUTH_ACTIVE_ACCOUNT_COOKIE);
|
||||
if (activeAccount) {
|
||||
return `${THIRDWEB_AUTH_TOKEN_COOKIE_PREFIX}_${activeAccount}`;
|
||||
}
|
||||
|
||||
// If active account is not present, then use the old default
|
||||
return THIRDWEB_AUTH_TOKEN_COOKIE_PREFIX;
|
||||
}
|
||||
|
||||
export function getToken(req: Request): string | undefined {
|
||||
if (req.headers["authorization"]) {
|
||||
const authorizationHeader = req.headers["authorization"].split(" ");
|
||||
if (authorizationHeader?.length === 2) {
|
||||
return authorizationHeader[1];
|
||||
}
|
||||
}
|
||||
|
||||
if (!req.cookies) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const cookie: string | undefined = !req.cookies
|
||||
? undefined
|
||||
: typeof req.cookies.get === "function"
|
||||
? (req.cookies as any).get("thirdweb_auth_token")
|
||||
: (req.cookies as any).thirdweb_auth_token;
|
||||
const activeCookie = getActiveCookie(req);
|
||||
if (!activeCookie) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return cookie;
|
||||
return getCookie(req, activeCookie);
|
||||
}
|
||||
|
||||
export async function getUser<
|
||||
|
||||
@@ -4,6 +4,7 @@ import payloadHandler from "./routes/payload";
|
||||
import loginHandler from "./routes/login";
|
||||
import logoutHandler from "./routes/logout";
|
||||
import userHandler from "./routes/user";
|
||||
import switchAccountHandler from "./routes/switch-account";
|
||||
import {
|
||||
ThirdwebAuthConfig,
|
||||
ThirdwebAuthContext,
|
||||
@@ -76,6 +77,13 @@ export function ThirdwebAuth<
|
||||
),
|
||||
);
|
||||
|
||||
router.post(
|
||||
"/switch-account",
|
||||
asyncHandler((req: Request, res: Response) =>
|
||||
switchAccountHandler(req, res, ctx as ThirdwebAuthContext),
|
||||
),
|
||||
);
|
||||
|
||||
return {
|
||||
authRouter: router,
|
||||
authMiddleware: cookieMiddleware,
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import {
|
||||
THIRDWEB_AUTH_ACTIVE_ACCOUNT_COOKIE,
|
||||
THIRDWEB_AUTH_TOKEN_COOKIE_PREFIX,
|
||||
} from "../../constants";
|
||||
import { GenerateOptions } from "../../core";
|
||||
import { LoginPayloadBodySchema, ThirdwebAuthContext } from "../types";
|
||||
import { serialize } from "cookie";
|
||||
@@ -70,9 +74,20 @@ export default async function handler(
|
||||
|
||||
// Securely set httpOnly cookie on request to prevent XSS on frontend
|
||||
// And set path to / to enable thirdweb_auth_token usage on all endpoints
|
||||
res.setHeader(
|
||||
"Set-Cookie",
|
||||
serialize("thirdweb_auth_token", token, {
|
||||
res.setHeader("Set-Cookie", [
|
||||
serialize(
|
||||
`${THIRDWEB_AUTH_TOKEN_COOKIE_PREFIX}_${payload.payload.address}`,
|
||||
token,
|
||||
{
|
||||
domain: ctx.cookieOptions?.domain,
|
||||
path: ctx.cookieOptions?.path || "/",
|
||||
sameSite: ctx.cookieOptions?.sameSite || "none",
|
||||
expires: new Date(exp * 1000),
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
},
|
||||
),
|
||||
serialize(THIRDWEB_AUTH_ACTIVE_ACCOUNT_COOKIE, payload.payload.address, {
|
||||
domain: ctx.cookieOptions?.domain,
|
||||
path: ctx.cookieOptions?.path || "/",
|
||||
sameSite: ctx.cookieOptions?.sameSite || "none",
|
||||
@@ -80,7 +95,7 @@ export default async function handler(
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
}),
|
||||
);
|
||||
]);
|
||||
|
||||
// Send token in body and as cookie for frontend and backend use cases
|
||||
return res.status(200).json({ token });
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getUser } from "../helpers/user";
|
||||
import { getActiveCookie, getUser } from "../helpers/user";
|
||||
import { ThirdwebAuthContext } from "../types";
|
||||
import { serialize } from "cookie";
|
||||
import { Request, Response } from "express";
|
||||
@@ -14,6 +14,13 @@ export default async function handler(
|
||||
});
|
||||
}
|
||||
|
||||
const activeCookie = getActiveCookie(req);
|
||||
if (!activeCookie) {
|
||||
return res.status(400).json({
|
||||
error: "No logged in user to logout.",
|
||||
});
|
||||
}
|
||||
|
||||
if (ctx.callbacks?.onLogout) {
|
||||
const user = await getUser(req, ctx);
|
||||
if (user) {
|
||||
@@ -24,7 +31,7 @@ export default async function handler(
|
||||
// Set the access token to 'none' and expire in 5 seconds
|
||||
res.setHeader(
|
||||
"Set-Cookie",
|
||||
serialize("thirdweb_auth_token", "", {
|
||||
serialize(activeCookie, "", {
|
||||
domain: ctx.cookieOptions?.domain,
|
||||
path: ctx.cookieOptions?.path || "/",
|
||||
sameSite: ctx.cookieOptions?.sameSite || "none",
|
||||
|
||||
@@ -23,7 +23,7 @@ export default async function handler(
|
||||
statement: ctx.authOptions?.statement,
|
||||
uri: ctx.authOptions?.uri,
|
||||
version: ctx.authOptions?.version,
|
||||
chainId: ctx.authOptions?.chainId,
|
||||
chainId: parsedPayload.data.chainId || ctx.authOptions?.chainId,
|
||||
resources: ctx.authOptions?.resources,
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import { Request, Response } from "express";
|
||||
import { ActiveBodySchema, ThirdwebAuthContext } from "../types";
|
||||
import { serialize } from "cookie";
|
||||
import {
|
||||
THIRDWEB_AUTH_ACTIVE_ACCOUNT_COOKIE,
|
||||
THIRDWEB_AUTH_DEFAULT_TOKEN_DURATION_IN_SECONDS,
|
||||
THIRDWEB_AUTH_TOKEN_COOKIE_PREFIX,
|
||||
} from "../../constants";
|
||||
import { getCookie } from "../helpers/user";
|
||||
|
||||
export default async function handler(
|
||||
req: Request,
|
||||
res: Response,
|
||||
ctx: ThirdwebAuthContext,
|
||||
) {
|
||||
if (req.method !== "POST") {
|
||||
return res.status(405).json({
|
||||
error: "Invalid method. Only POST supported.",
|
||||
});
|
||||
}
|
||||
|
||||
const parsedPayload = ActiveBodySchema.safeParse(req.body);
|
||||
if (!parsedPayload.success) {
|
||||
return res.status(400).json({ error: "Please provide an address" });
|
||||
}
|
||||
|
||||
let cookieExpiration: Date;
|
||||
const cookie = getCookie(
|
||||
req,
|
||||
`${THIRDWEB_AUTH_TOKEN_COOKIE_PREFIX}_${parsedPayload.data.address}`,
|
||||
);
|
||||
|
||||
if (cookie) {
|
||||
// If the new account is already logged in, get the expiration time from the cookie
|
||||
const {
|
||||
payload: { exp },
|
||||
} = ctx.auth.parseToken(cookie);
|
||||
cookieExpiration = new Date(exp * 1000);
|
||||
} else if (ctx.authOptions?.tokenDurationInSeconds) {
|
||||
// Otherwise, if we have a token duration in seconds, set it to that
|
||||
cookieExpiration = new Date(
|
||||
Date.now() + 1000 * ctx.authOptions.tokenDurationInSeconds,
|
||||
);
|
||||
} else {
|
||||
// Otherwise, just default to 24 hours
|
||||
cookieExpiration = new Date(
|
||||
Date.now() + 1000 * THIRDWEB_AUTH_DEFAULT_TOKEN_DURATION_IN_SECONDS,
|
||||
);
|
||||
}
|
||||
|
||||
res.setHeader("Set-Cookie", [
|
||||
serialize(THIRDWEB_AUTH_ACTIVE_ACCOUNT_COOKIE, parsedPayload.data.address, {
|
||||
domain: ctx.cookieOptions?.domain,
|
||||
path: ctx.cookieOptions?.path || "/",
|
||||
sameSite: ctx.cookieOptions?.sameSite || "none",
|
||||
expires: cookieExpiration,
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
}),
|
||||
]);
|
||||
|
||||
return res.status(200).end();
|
||||
}
|
||||
@@ -1,6 +1,12 @@
|
||||
import { getUser } from "../helpers/user";
|
||||
import { serialize } from "cookie";
|
||||
import { getToken, getUser } from "../helpers/user";
|
||||
import { ThirdwebAuthContext } from "../types";
|
||||
import { Request, Response } from "express";
|
||||
import {
|
||||
THIRDWEB_AUTH_ACTIVE_ACCOUNT_COOKIE,
|
||||
THIRDWEB_AUTH_DEFAULT_REFRESH_INTERVAL_IN_SECONDS,
|
||||
THIRDWEB_AUTH_TOKEN_COOKIE_PREFIX,
|
||||
} from "../../constants";
|
||||
|
||||
export default async function handler(
|
||||
req: Request,
|
||||
@@ -14,5 +20,54 @@ export default async function handler(
|
||||
}
|
||||
|
||||
const user = await getUser(req, ctx);
|
||||
|
||||
// Importantly, make sure the user was actually logged in before refreshing
|
||||
if (user) {
|
||||
const token = getToken(req);
|
||||
if (token) {
|
||||
const payload = ctx.auth.parseToken(token);
|
||||
|
||||
const refreshDate = ctx.authOptions?.refreshIntervalInSeconds
|
||||
? new Date(
|
||||
payload.payload.iat * 1000 +
|
||||
ctx.authOptions.refreshIntervalInSeconds * 1000,
|
||||
)
|
||||
: new Date(
|
||||
payload.payload.iat * 1000 +
|
||||
THIRDWEB_AUTH_DEFAULT_REFRESH_INTERVAL_IN_SECONDS * 1000,
|
||||
);
|
||||
|
||||
if (new Date() > refreshDate) {
|
||||
const expirationTime = ctx.authOptions?.tokenDurationInSeconds
|
||||
? new Date(Date.now() + 1000 * ctx.authOptions.tokenDurationInSeconds)
|
||||
: undefined;
|
||||
const refreshedToken = await ctx.auth.refresh(token, expirationTime);
|
||||
const refreshedPayload = ctx.auth.parseToken(refreshedToken);
|
||||
res.setHeader("Set-Cookie", [
|
||||
serialize(
|
||||
`${THIRDWEB_AUTH_TOKEN_COOKIE_PREFIX}_${user.address}`,
|
||||
refreshedToken,
|
||||
{
|
||||
domain: ctx.cookieOptions?.domain,
|
||||
path: ctx.cookieOptions?.path || "/",
|
||||
sameSite: ctx.cookieOptions?.sameSite || "none",
|
||||
expires: new Date(refreshedPayload.payload.exp * 1000),
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
},
|
||||
),
|
||||
serialize(THIRDWEB_AUTH_ACTIVE_ACCOUNT_COOKIE, user.address, {
|
||||
domain: ctx.cookieOptions?.domain,
|
||||
path: ctx.cookieOptions?.path || "/",
|
||||
sameSite: ctx.cookieOptions?.sameSite || "none",
|
||||
expires: new Date(refreshedPayload.payload.exp * 1000),
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
}),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res.status(200).json(user);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,11 @@ import { z } from "zod";
|
||||
|
||||
export const PayloadBodySchema = z.object({
|
||||
address: z.string(),
|
||||
chainId: z.string().optional(),
|
||||
});
|
||||
|
||||
export const ActiveBodySchema = z.object({
|
||||
address: z.string(),
|
||||
});
|
||||
|
||||
export const LoginPayloadBodySchema = z.object({
|
||||
@@ -40,6 +45,7 @@ export type ThirdwebAuthConfig<
|
||||
| ((tokenId: string) => void)
|
||||
| ((tokenId: string) => Promise<void>);
|
||||
tokenDurationInSeconds?: number;
|
||||
refreshIntervalInSeconds?: number;
|
||||
};
|
||||
cookieOptions?: {
|
||||
domain?: string;
|
||||
|
||||
@@ -1,9 +1,40 @@
|
||||
import {
|
||||
THIRDWEB_AUTH_ACTIVE_ACCOUNT_COOKIE,
|
||||
THIRDWEB_AUTH_TOKEN_COOKIE_PREFIX,
|
||||
} from "../../constants";
|
||||
import { Json } from "../../core/schema";
|
||||
import { ThirdwebAuthContext, ThirdwebAuthUser } from "../types";
|
||||
import { GetServerSidePropsContext, NextApiRequest } from "next";
|
||||
import { NextRequest } from "next/server";
|
||||
|
||||
function getToken(
|
||||
export function getCookie(
|
||||
req: GetServerSidePropsContext["req"] | NextRequest | NextApiRequest,
|
||||
cookie: string,
|
||||
): string | undefined {
|
||||
if (typeof req.cookies.get === "function") {
|
||||
return req.cookies.get(cookie);
|
||||
}
|
||||
|
||||
return (req.cookies as any)[cookie];
|
||||
}
|
||||
|
||||
export function getActiveCookie(
|
||||
req: GetServerSidePropsContext["req"] | NextRequest | NextApiRequest,
|
||||
): string | undefined {
|
||||
if (!req.cookies) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const activeAccount = getCookie(req, THIRDWEB_AUTH_ACTIVE_ACCOUNT_COOKIE);
|
||||
if (activeAccount) {
|
||||
return `${THIRDWEB_AUTH_TOKEN_COOKIE_PREFIX}_${activeAccount}`;
|
||||
}
|
||||
|
||||
// If active account is not present, then use the old default
|
||||
return THIRDWEB_AUTH_TOKEN_COOKIE_PREFIX;
|
||||
}
|
||||
|
||||
export function getToken(
|
||||
req: GetServerSidePropsContext["req"] | NextRequest | NextApiRequest,
|
||||
): string | undefined {
|
||||
if (!!(req as NextApiRequest).headers["authorization"]) {
|
||||
@@ -13,17 +44,18 @@ function getToken(
|
||||
if (authorizationHeader?.length === 2) {
|
||||
return authorizationHeader[1];
|
||||
}
|
||||
}
|
||||
|
||||
if (!req.cookies) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const cookie: string | undefined = !req.cookies
|
||||
? undefined
|
||||
: typeof req.cookies.get === "function"
|
||||
? (req.cookies as any).get("thirdweb_auth_token")
|
||||
: (req.cookies as any).thirdweb_auth_token;
|
||||
const activeCookie = getActiveCookie(req);
|
||||
if (!activeCookie) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return cookie;
|
||||
return getCookie(req, activeCookie);
|
||||
}
|
||||
|
||||
export async function getUser<
|
||||
|
||||
@@ -4,6 +4,7 @@ import payloadHandler from "./routes/payload";
|
||||
import loginHandler from "./routes/login";
|
||||
import logoutHandler from "./routes/logout";
|
||||
import userHandler from "./routes/user";
|
||||
import switchAccountHandler from "./routes/switch-account";
|
||||
import {
|
||||
ThirdwebAuthConfig,
|
||||
ThirdwebAuthContext,
|
||||
@@ -36,6 +37,8 @@ async function ThirdwebAuthRouter(
|
||||
return await userHandler(req, res, ctx);
|
||||
case "logout":
|
||||
return await logoutHandler(req, res, ctx);
|
||||
case "switch-account":
|
||||
return await switchAccountHandler(req, res, ctx);
|
||||
default:
|
||||
return res.status(400).json({
|
||||
message: "Invalid route for authentication.",
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import {
|
||||
THIRDWEB_AUTH_ACTIVE_ACCOUNT_COOKIE,
|
||||
THIRDWEB_AUTH_TOKEN_COOKIE_PREFIX,
|
||||
} from "../../constants";
|
||||
import { GenerateOptions } from "../../core";
|
||||
import { LoginPayloadBodySchema, ThirdwebAuthContext } from "../types";
|
||||
import { serialize } from "cookie";
|
||||
@@ -70,9 +74,20 @@ export default async function handler(
|
||||
|
||||
// Securely set httpOnly cookie on request to prevent XSS on frontend
|
||||
// And set path to / to enable thirdweb_auth_token usage on all endpoints
|
||||
res.setHeader(
|
||||
"Set-Cookie",
|
||||
serialize("thirdweb_auth_token", token, {
|
||||
res.setHeader("Set-Cookie", [
|
||||
serialize(
|
||||
`${THIRDWEB_AUTH_TOKEN_COOKIE_PREFIX}_${payload.payload.address}`,
|
||||
token,
|
||||
{
|
||||
domain: ctx.cookieOptions?.domain,
|
||||
path: ctx.cookieOptions?.path || "/",
|
||||
sameSite: ctx.cookieOptions?.sameSite || "none",
|
||||
expires: new Date(exp * 1000),
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
},
|
||||
),
|
||||
serialize(THIRDWEB_AUTH_ACTIVE_ACCOUNT_COOKIE, payload.payload.address, {
|
||||
domain: ctx.cookieOptions?.domain,
|
||||
path: ctx.cookieOptions?.path || "/",
|
||||
sameSite: ctx.cookieOptions?.sameSite || "none",
|
||||
@@ -80,7 +95,7 @@ export default async function handler(
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
}),
|
||||
);
|
||||
]);
|
||||
|
||||
// Send token in body and as cookie for frontend and backend use cases
|
||||
return res.status(200).json({ token });
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getUser } from "../helpers/user";
|
||||
import { getActiveCookie, getUser } from "../helpers/user";
|
||||
import { ThirdwebAuthContext } from "../types";
|
||||
import { serialize } from "cookie";
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
@@ -14,6 +14,13 @@ export default async function handler(
|
||||
});
|
||||
}
|
||||
|
||||
const activeCookie = getActiveCookie(req);
|
||||
if (!activeCookie) {
|
||||
return res.status(400).json({
|
||||
error: "No logged in user to logout.",
|
||||
});
|
||||
}
|
||||
|
||||
if (ctx.callbacks?.onLogout) {
|
||||
const user = await getUser(req, ctx);
|
||||
if (user) {
|
||||
@@ -24,7 +31,7 @@ export default async function handler(
|
||||
// Set the access token to 'none' and expire in 5 seconds
|
||||
res.setHeader(
|
||||
"Set-Cookie",
|
||||
serialize("thirdweb_auth_token", "", {
|
||||
serialize(activeCookie, "", {
|
||||
domain: ctx.cookieOptions?.domain,
|
||||
path: ctx.cookieOptions?.path || "/",
|
||||
sameSite: ctx.cookieOptions?.sameSite || "none",
|
||||
|
||||
@@ -23,7 +23,7 @@ export default async function handler(
|
||||
statement: ctx.authOptions?.statement,
|
||||
uri: ctx.authOptions?.uri,
|
||||
version: ctx.authOptions?.version,
|
||||
chainId: ctx.authOptions?.chainId,
|
||||
chainId: parsedPayload.data.chainId || ctx.authOptions?.chainId,
|
||||
resources: ctx.authOptions?.resources,
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import { serialize } from "cookie";
|
||||
import { getCookie } from "../helpers/user";
|
||||
import { ActiveBodySchema, ThirdwebAuthContext } from "../types";
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
import {
|
||||
THIRDWEB_AUTH_ACTIVE_ACCOUNT_COOKIE,
|
||||
THIRDWEB_AUTH_DEFAULT_TOKEN_DURATION_IN_SECONDS,
|
||||
THIRDWEB_AUTH_TOKEN_COOKIE_PREFIX,
|
||||
} from "../../constants";
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse,
|
||||
ctx: ThirdwebAuthContext,
|
||||
) {
|
||||
if (req.method !== "POST") {
|
||||
return res.status(405).json({
|
||||
error: "Invalid method. Only POST supported.",
|
||||
});
|
||||
}
|
||||
|
||||
const parsedPayload = ActiveBodySchema.safeParse(req.body);
|
||||
if (!parsedPayload.success) {
|
||||
return res.status(400).json({ error: "Please provide an address" });
|
||||
}
|
||||
|
||||
let cookieExpiration: Date;
|
||||
const cookie = getCookie(
|
||||
req,
|
||||
`${THIRDWEB_AUTH_TOKEN_COOKIE_PREFIX}_${parsedPayload.data.address}`,
|
||||
);
|
||||
|
||||
if (cookie) {
|
||||
// If the new account is already logged in, get the expiration time from the cookie
|
||||
const {
|
||||
payload: { exp },
|
||||
} = ctx.auth.parseToken(cookie);
|
||||
cookieExpiration = new Date(exp * 1000);
|
||||
} else if (ctx.authOptions?.tokenDurationInSeconds) {
|
||||
// Otherwise, if we have a token duration in seconds, set it to that
|
||||
cookieExpiration = new Date(
|
||||
Date.now() + 1000 * ctx.authOptions.tokenDurationInSeconds,
|
||||
);
|
||||
} else {
|
||||
// Otherwise, just default to 24 hours
|
||||
cookieExpiration = new Date(
|
||||
Date.now() + 1000 * THIRDWEB_AUTH_DEFAULT_TOKEN_DURATION_IN_SECONDS,
|
||||
);
|
||||
}
|
||||
|
||||
res.setHeader("Set-Cookie", [
|
||||
serialize(THIRDWEB_AUTH_ACTIVE_ACCOUNT_COOKIE, parsedPayload.data.address, {
|
||||
domain: ctx.cookieOptions?.domain,
|
||||
path: ctx.cookieOptions?.path || "/",
|
||||
sameSite: ctx.cookieOptions?.sameSite || "none",
|
||||
expires: cookieExpiration,
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
}),
|
||||
]);
|
||||
|
||||
return res.status(200).end();
|
||||
}
|
||||
@@ -1,4 +1,10 @@
|
||||
import { getUser } from "../helpers/user";
|
||||
import { serialize } from "cookie";
|
||||
import {
|
||||
THIRDWEB_AUTH_ACTIVE_ACCOUNT_COOKIE,
|
||||
THIRDWEB_AUTH_DEFAULT_REFRESH_INTERVAL_IN_SECONDS,
|
||||
THIRDWEB_AUTH_TOKEN_COOKIE_PREFIX,
|
||||
} from "../../constants";
|
||||
import { getToken, getUser } from "../helpers/user";
|
||||
import { ThirdwebAuthContext } from "../types";
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
@@ -14,5 +20,54 @@ export default async function handler(
|
||||
}
|
||||
|
||||
const user = await getUser(req, ctx);
|
||||
|
||||
// Importantly, make sure the user was actually logged in before refreshing
|
||||
if (user) {
|
||||
const token = getToken(req);
|
||||
if (token) {
|
||||
const payload = ctx.auth.parseToken(token);
|
||||
|
||||
const refreshDate = ctx.authOptions?.refreshIntervalInSeconds
|
||||
? new Date(
|
||||
payload.payload.iat * 1000 +
|
||||
ctx.authOptions.refreshIntervalInSeconds * 1000,
|
||||
)
|
||||
: new Date(
|
||||
payload.payload.iat * 1000 +
|
||||
THIRDWEB_AUTH_DEFAULT_REFRESH_INTERVAL_IN_SECONDS * 1000,
|
||||
);
|
||||
|
||||
if (new Date() > refreshDate) {
|
||||
const expirationTime = ctx.authOptions?.tokenDurationInSeconds
|
||||
? new Date(Date.now() + 1000 * ctx.authOptions.tokenDurationInSeconds)
|
||||
: undefined;
|
||||
const refreshedToken = await ctx.auth.refresh(token, expirationTime);
|
||||
const refreshedPayload = ctx.auth.parseToken(refreshedToken);
|
||||
res.setHeader("Set-Cookie", [
|
||||
serialize(
|
||||
`${THIRDWEB_AUTH_TOKEN_COOKIE_PREFIX}_${user.address}`,
|
||||
refreshedToken,
|
||||
{
|
||||
domain: ctx.cookieOptions?.domain,
|
||||
path: ctx.cookieOptions?.path || "/",
|
||||
sameSite: ctx.cookieOptions?.sameSite || "none",
|
||||
expires: new Date(refreshedPayload.payload.exp * 1000),
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
},
|
||||
),
|
||||
serialize(THIRDWEB_AUTH_ACTIVE_ACCOUNT_COOKIE, user.address, {
|
||||
domain: ctx.cookieOptions?.domain,
|
||||
path: ctx.cookieOptions?.path || "/",
|
||||
sameSite: ctx.cookieOptions?.sameSite || "none",
|
||||
expires: new Date(refreshedPayload.payload.exp * 1000),
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
}),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res.status(200).json(user);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,11 @@ import { z } from "zod";
|
||||
|
||||
export const PayloadBodySchema = z.object({
|
||||
address: z.string(),
|
||||
chainId: z.string().optional(),
|
||||
});
|
||||
|
||||
export const ActiveBodySchema = z.object({
|
||||
address: z.string(),
|
||||
});
|
||||
|
||||
export const LoginPayloadBodySchema = z.object({
|
||||
@@ -18,7 +23,12 @@ type RequestType =
|
||||
| NextRequest
|
||||
| NextApiRequest;
|
||||
|
||||
export type ThirdwebAuthRoute = "payload" | "login" | "logout" | "user";
|
||||
export type ThirdwebAuthRoute =
|
||||
| "payload"
|
||||
| "login"
|
||||
| "logout"
|
||||
| "user"
|
||||
| "switch-account";
|
||||
|
||||
export type ThirdwebAuthUser<
|
||||
TData extends Json = Json,
|
||||
@@ -46,6 +56,7 @@ export type ThirdwebAuthConfig<
|
||||
| ((tokenId: string) => void)
|
||||
| ((tokenId: string) => Promise<void>);
|
||||
tokenDurationInSeconds?: number;
|
||||
refreshIntervalInSeconds?: number;
|
||||
};
|
||||
cookieOptions?: {
|
||||
domain?: string;
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @thirdweb-dev/chains
|
||||
|
||||
## 0.1.21
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1074](https://github.com/thirdweb-dev/js/pull/1074) [`cc20f93e`](https://github.com/thirdweb-dev/js/commit/cc20f93e178d86ee2b3f39102bbb0811de211f05) Thanks [@Marfuen](https://github.com/Marfuen)! - Enhance CLI Install command and update chains
|
||||
|
||||
- [#1185](https://github.com/thirdweb-dev/js/pull/1185) [`8e28b0f5`](https://github.com/thirdweb-dev/js/commit/8e28b0f5e75596d29273ed80269bcee6d209adb4) Thanks [@jnsdls](https://github.com/jnsdls)! - add Zora Mainnet
|
||||
|
||||
## 0.1.20
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import type { Chain } from "../src/types";
|
||||
export default {
|
||||
"name": "SatoshiChain Mainnet",
|
||||
"chain": "SATS",
|
||||
"icon": {
|
||||
"url": "ipfs://QmRegpZQBW4o1imYNsW3d27MQjygBSU23Gf6JKje26nvs7",
|
||||
"width": 1251,
|
||||
"height": 1251,
|
||||
"format": "png"
|
||||
},
|
||||
"rpc": [
|
||||
"https://satoshichain.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://mainnet-rpc.satoshichain.io"
|
||||
],
|
||||
"faucets": [],
|
||||
"nativeCurrency": {
|
||||
"name": "SatoshiChain Coin",
|
||||
"symbol": "SATS",
|
||||
"decimals": 18
|
||||
},
|
||||
"infoURL": "https://satoshichain.net",
|
||||
"shortName": "sats",
|
||||
"chainId": 12009,
|
||||
"networkId": 12009,
|
||||
"explorers": [
|
||||
{
|
||||
"name": "SatoshiChain Explorer",
|
||||
"url": "https://satoshiscan.io",
|
||||
"standard": "EIP3091"
|
||||
}
|
||||
],
|
||||
"testnet": false,
|
||||
"slug": "satoshichain"
|
||||
} as const satisfies Chain;
|
||||
@@ -0,0 +1,33 @@
|
||||
import type { Chain } from "../src/types";
|
||||
export default {
|
||||
"name": "Omni Testnet 1",
|
||||
"chain": "Omni",
|
||||
"rpc": [
|
||||
"https://omni-testnet-1.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://testnet-1.omni.network"
|
||||
],
|
||||
"features": [
|
||||
{
|
||||
"name": "EIP155"
|
||||
}
|
||||
],
|
||||
"faucets": [],
|
||||
"nativeCurrency": {
|
||||
"name": "Omni",
|
||||
"symbol": "OMNI",
|
||||
"decimals": 18
|
||||
},
|
||||
"infoURL": "https://docs.omni.network/",
|
||||
"shortName": "omni_testnet",
|
||||
"chainId": 165,
|
||||
"networkId": 165,
|
||||
"explorers": [
|
||||
{
|
||||
"name": "Omni Explorer",
|
||||
"url": "https://testnet-1.explorer.omni.network",
|
||||
"standard": "EIP3091"
|
||||
}
|
||||
],
|
||||
"testnet": true,
|
||||
"slug": "omni-testnet-1"
|
||||
} as const satisfies Chain;
|
||||
@@ -0,0 +1,21 @@
|
||||
import type { Chain } from "../src/types";
|
||||
export default {
|
||||
"name": "Shrapnel Subnet",
|
||||
"chain": "shrapnel",
|
||||
"rpc": [
|
||||
"https://shrapnel-subnet.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://subnets.avax.network/shrapnel/mainnet/rpc"
|
||||
],
|
||||
"faucets": [],
|
||||
"nativeCurrency": {
|
||||
"name": "Shrapnel Gas Token",
|
||||
"symbol": "SHRAPG",
|
||||
"decimals": 18
|
||||
},
|
||||
"infoURL": "https://www.shrapnel.com/",
|
||||
"shortName": "Shrapnel",
|
||||
"chainId": 2044,
|
||||
"networkId": 2044,
|
||||
"testnet": false,
|
||||
"slug": "shrapnel-subnet"
|
||||
} as const satisfies Chain;
|
||||
@@ -11,7 +11,8 @@ export default {
|
||||
"rpc": [
|
||||
"https://lachain-lachain.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://rpc1.mainnet.lachain.network",
|
||||
"https://rpc2.mainnet.lachain.network"
|
||||
"https://rpc2.mainnet.lachain.network",
|
||||
"https://lachain.rpc-nodes.cedalio.dev"
|
||||
],
|
||||
"faucets": [],
|
||||
"nativeCurrency": {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { Chain } from "../src/types";
|
||||
export default {
|
||||
"name": "Metacodechain",
|
||||
"chain": "metacode",
|
||||
"name": "Jouleverse Mainnet",
|
||||
"chain": "Jouleverse",
|
||||
"rpc": [
|
||||
"https://metacodechain.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://j.blockcoach.com:8503"
|
||||
"https://jouleverse.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://rpc.jnsdao.com:8503"
|
||||
],
|
||||
"faucets": [],
|
||||
"nativeCurrency": {
|
||||
@@ -12,17 +12,17 @@ export default {
|
||||
"symbol": "J",
|
||||
"decimals": 18
|
||||
},
|
||||
"infoURL": "https://j.blockcoach.com:8089",
|
||||
"shortName": "metacode",
|
||||
"infoURL": "https://jnsdao.com",
|
||||
"shortName": "jouleverse",
|
||||
"chainId": 3666,
|
||||
"networkId": 3666,
|
||||
"explorers": [
|
||||
{
|
||||
"name": "meta",
|
||||
"url": "https://j.blockcoach.com:8089",
|
||||
"name": "jscan",
|
||||
"url": "https://jscan.jnsdao.com",
|
||||
"standard": "EIP3091"
|
||||
}
|
||||
],
|
||||
"testnet": false,
|
||||
"slug": "metacodechain"
|
||||
"slug": "jouleverse"
|
||||
} as const satisfies Chain;
|
||||
@@ -0,0 +1,34 @@
|
||||
import type { Chain } from "../src/types";
|
||||
export default {
|
||||
"name": "Ozone Chain Mainnet",
|
||||
"chain": "OZONE",
|
||||
"rpc": [
|
||||
"https://ozone-chain.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://node1.ozonechain.io"
|
||||
],
|
||||
"faucets": [],
|
||||
"nativeCurrency": {
|
||||
"name": "OZONE",
|
||||
"symbol": "OZO",
|
||||
"decimals": 18
|
||||
},
|
||||
"infoURL": "https://ozonechain.io",
|
||||
"shortName": "ozo",
|
||||
"chainId": 4000,
|
||||
"networkId": 4000,
|
||||
"icon": {
|
||||
"url": "ipfs://QmbM4weV8Bk6c9yNhosYntkVw39SNZtCHYGgWyXTxkevZ8",
|
||||
"width": 1600,
|
||||
"height": 1600,
|
||||
"format": "png"
|
||||
},
|
||||
"explorers": [
|
||||
{
|
||||
"name": "OZONE Scan",
|
||||
"url": "https://ozonescan.io",
|
||||
"standard": "EIP3091"
|
||||
}
|
||||
],
|
||||
"testnet": false,
|
||||
"slug": "ozone-chain"
|
||||
} as const satisfies Chain;
|
||||
@@ -0,0 +1,34 @@
|
||||
import type { Chain } from "../src/types";
|
||||
export default {
|
||||
"name": "Ozone Chain Testnet",
|
||||
"chain": "OZONE",
|
||||
"rpc": [
|
||||
"https://ozone-chain-testnet.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://node1.testnet.ozonechain.io"
|
||||
],
|
||||
"faucets": [],
|
||||
"nativeCurrency": {
|
||||
"name": "OZONE",
|
||||
"symbol": "OZO",
|
||||
"decimals": 18
|
||||
},
|
||||
"infoURL": "https://ozonechain.io",
|
||||
"shortName": "ozo_tst",
|
||||
"chainId": 401,
|
||||
"networkId": 401,
|
||||
"icon": {
|
||||
"url": "ipfs://QmbM4weV8Bk6c9yNhosYntkVw39SNZtCHYGgWyXTxkevZ8",
|
||||
"width": 1600,
|
||||
"height": 1600,
|
||||
"format": "png"
|
||||
},
|
||||
"explorers": [
|
||||
{
|
||||
"name": "OZONE Scan",
|
||||
"url": "https://testnet.ozonescan.io",
|
||||
"standard": "EIP3091"
|
||||
}
|
||||
],
|
||||
"testnet": true,
|
||||
"slug": "ozone-chain-testnet"
|
||||
} as const satisfies Chain;
|
||||
@@ -10,7 +10,8 @@ export default {
|
||||
},
|
||||
"rpc": [
|
||||
"https://latestnet.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://rpc.testnet.lachain.network"
|
||||
"https://rpc.testnet.lachain.network",
|
||||
"https://lachain-testnet.rpc-nodes.cedalio.dev"
|
||||
],
|
||||
"faucets": [
|
||||
"https://faucet.lachain.network"
|
||||
|
||||
@@ -12,7 +12,7 @@ export default {
|
||||
"rpc": [
|
||||
"https://wireshape-floripa-testnet.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://rpc-floripa.wireshape.org",
|
||||
"https://floripa.rpc.thirdweb.com"
|
||||
"https://wireshape-floripa-testnet.rpc.thirdweb.com"
|
||||
],
|
||||
"faucets": [],
|
||||
"nativeCurrency": {
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import type { Chain } from "../src/types";
|
||||
export default {
|
||||
"name": "Titan",
|
||||
"chain": "ETH",
|
||||
"rpc": [
|
||||
"https://titan.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://rpc.titan.tokamak.network",
|
||||
"wss://rpc.titan.tokamak.network/ws"
|
||||
],
|
||||
"faucets": [],
|
||||
"nativeCurrency": {
|
||||
"name": "Ether",
|
||||
"symbol": "ETH",
|
||||
"decimals": 18
|
||||
},
|
||||
"infoURL": "https://tokamak.network",
|
||||
"shortName": "teth",
|
||||
"chainId": 55004,
|
||||
"networkId": 55004,
|
||||
"explorers": [
|
||||
{
|
||||
"name": "blockscout",
|
||||
"url": "https://explorer.titan.tokamak.network",
|
||||
"standard": "EIP3091"
|
||||
}
|
||||
],
|
||||
"testnet": false,
|
||||
"slug": "titan"
|
||||
} as const satisfies Chain;
|
||||
@@ -10,7 +10,7 @@ export default {
|
||||
},
|
||||
"rpc": [
|
||||
"https://satoshichain-testnet.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://rpc.satoshichain.io"
|
||||
"https://testnet-rpc.satoshichain.io"
|
||||
],
|
||||
"faucets": [
|
||||
"https://faucet.satoshichain.io"
|
||||
@@ -21,13 +21,13 @@ export default {
|
||||
"decimals": 18
|
||||
},
|
||||
"infoURL": "https://satoshichain.net",
|
||||
"shortName": "sats",
|
||||
"shortName": "satst",
|
||||
"chainId": 5758,
|
||||
"networkId": 5758,
|
||||
"explorers": [
|
||||
{
|
||||
"name": "SatoshiChain Explorer",
|
||||
"url": "https://satoshiscan.io",
|
||||
"name": "SatoshiChain Testnet Explorer",
|
||||
"url": "https://testnet.satoshiscan.io",
|
||||
"standard": "EIP3091"
|
||||
}
|
||||
],
|
||||
|
||||
@@ -3,9 +3,9 @@ export default {
|
||||
"name": "ZetaChain Mainnet",
|
||||
"chain": "ZetaChain",
|
||||
"icon": {
|
||||
"url": "ipfs://QmeABfwZ2nAxDzYyqZ1LEypPgQFMjEyrx8FfnoPLkF8R3f",
|
||||
"width": 1280,
|
||||
"height": 1280,
|
||||
"url": "ipfs://QmP4Gnf4Lkp8q5LQVePNjAWxSqrw8vU2JAf7amcFz4vEUy",
|
||||
"width": 712,
|
||||
"height": 712,
|
||||
"format": "png"
|
||||
},
|
||||
"rpc": [
|
||||
@@ -18,7 +18,7 @@ export default {
|
||||
"symbol": "ZETA",
|
||||
"decimals": 18
|
||||
},
|
||||
"infoURL": "https://docs.zetachain.com/",
|
||||
"infoURL": "https://zetachain.com/docs/",
|
||||
"shortName": "zetachain-mainnet",
|
||||
"chainId": 7000,
|
||||
"networkId": 7000,
|
||||
|
||||
@@ -3,14 +3,15 @@ export default {
|
||||
"name": "ZetaChain Athens Testnet",
|
||||
"chain": "ZetaChain",
|
||||
"icon": {
|
||||
"url": "ipfs://QmeABfwZ2nAxDzYyqZ1LEypPgQFMjEyrx8FfnoPLkF8R3f",
|
||||
"width": 1280,
|
||||
"height": 1280,
|
||||
"url": "ipfs://QmP4Gnf4Lkp8q5LQVePNjAWxSqrw8vU2JAf7amcFz4vEUy",
|
||||
"width": 712,
|
||||
"height": 712,
|
||||
"format": "png"
|
||||
},
|
||||
"rpc": [
|
||||
"https://zetachain-athens-testnet.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://api.athens2.zetachain.com/evm"
|
||||
"https://zetachain-athens-evm.blockpi.network/v1/rpc/public",
|
||||
"wss://zetachain-athens.blockpi.network/rpc/v1/public/websocket"
|
||||
],
|
||||
"faucets": [
|
||||
"https://labs.zetachain.com/get-zeta"
|
||||
@@ -20,7 +21,7 @@ export default {
|
||||
"symbol": "aZETA",
|
||||
"decimals": 18
|
||||
},
|
||||
"infoURL": "https://docs.zetachain.com/",
|
||||
"infoURL": "https://zetachain.com/docs",
|
||||
"shortName": "zetachain-athens",
|
||||
"chainId": 7001,
|
||||
"networkId": 7001,
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import type { Chain } from "../src/types";
|
||||
export default {
|
||||
"name": "Zora",
|
||||
"chain": "ETH",
|
||||
"rpc": [
|
||||
"https://zora.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://rpc.zora.co/"
|
||||
],
|
||||
"faucets": [],
|
||||
"nativeCurrency": {
|
||||
"name": "Ether",
|
||||
"symbol": "ETH",
|
||||
"decimals": 18
|
||||
},
|
||||
"infoURL": "https://zora.co",
|
||||
"shortName": "zora",
|
||||
"chainId": 7777777,
|
||||
"networkId": 7777777,
|
||||
"explorers": [
|
||||
{
|
||||
"name": "Zora Network Explorer",
|
||||
"url": "https://explorer.zora.co",
|
||||
"standard": "EIP3091"
|
||||
}
|
||||
],
|
||||
"icon": {
|
||||
"url": "ipfs://QmZ6qaRwTPFEZUspwMUjaxC6KhmzcELdRQcQzS3P72Dzts/Vector.svg",
|
||||
"height": 512,
|
||||
"width": 512,
|
||||
"format": "svg"
|
||||
},
|
||||
"testnet": false,
|
||||
"slug": "zora"
|
||||
} as const satisfies Chain;
|
||||
@@ -0,0 +1,45 @@
|
||||
import type { Chain } from "../src/types";
|
||||
export default {
|
||||
"name": "CamDL Mainnet",
|
||||
"chain": "CADL",
|
||||
"rpc": [
|
||||
"https://camdl.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://rpc1.camdl.gov.kh/"
|
||||
],
|
||||
"faucets": [
|
||||
"https://faucet.camdl.gov.kh/"
|
||||
],
|
||||
"nativeCurrency": {
|
||||
"name": "CADL",
|
||||
"symbol": "CADL",
|
||||
"decimals": 18
|
||||
},
|
||||
"features": [
|
||||
{
|
||||
"name": "EIP155"
|
||||
}
|
||||
],
|
||||
"infoURL": "https://camdl.gov.kh/",
|
||||
"shortName": "camdl",
|
||||
"chainId": 95,
|
||||
"networkId": 95,
|
||||
"redFlags": [
|
||||
"reusedChainId"
|
||||
],
|
||||
"icon": {
|
||||
"url": "ipfs://QmW5Fpb2Ywnfqcj4ibvpbKvPv5Mo5eseWdYZnnUvLkj2Hp",
|
||||
"width": 1453,
|
||||
"height": 1453,
|
||||
"format": "png"
|
||||
},
|
||||
"explorers": [
|
||||
{
|
||||
"name": "CamDL Block Explorer",
|
||||
"url": "https://explorer.camdl.gov.kh",
|
||||
"standard": "EIP3091"
|
||||
}
|
||||
],
|
||||
"status": "active",
|
||||
"testnet": false,
|
||||
"slug": "camdl"
|
||||
} as const satisfies Chain;
|
||||
@@ -0,0 +1,12 @@
|
||||
// @ts-check
|
||||
/** @typedef { import("../../src/types").Chain } Chain */
|
||||
|
||||
/** @type {Partial<Chain>} */
|
||||
export default {
|
||||
icon: {
|
||||
url: "ipfs://QmZ6qaRwTPFEZUspwMUjaxC6KhmzcELdRQcQzS3P72Dzts/Vector.svg",
|
||||
height: 512,
|
||||
width: 512,
|
||||
format: "svg",
|
||||
},
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/chains",
|
||||
"version": "0.1.20",
|
||||
"version": "0.1.21",
|
||||
"main": "dist/thirdweb-dev-chains.cjs.js",
|
||||
"module": "dist/thirdweb-dev-chains.esm.js",
|
||||
"browser": {
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,5 +1,23 @@
|
||||
# thirdweb
|
||||
|
||||
## 0.10.23
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1184](https://github.com/thirdweb-dev/js/pull/1184) [`e7f1ce43`](https://github.com/thirdweb-dev/js/commit/e7f1ce43b04c6ea81fd05c02c8392a16c38d6053) Thanks [@Marfuen](https://github.com/Marfuen)! - Fix typo issues with python detectors
|
||||
|
||||
- [#1074](https://github.com/thirdweb-dev/js/pull/1074) [`cc20f93e`](https://github.com/thirdweb-dev/js/commit/cc20f93e178d86ee2b3f39102bbb0811de211f05) Thanks [@Marfuen](https://github.com/Marfuen)! - Enhance CLI Install command and update chains
|
||||
|
||||
- [#1154](https://github.com/thirdweb-dev/js/pull/1154) [`02d55a06`](https://github.com/thirdweb-dev/js/commit/02d55a063e7c53761d57e6cb6bf63ea78e454d70) Thanks [@beauwilliams](https://github.com/beauwilliams)! - fix: always use latest CLI version in CI
|
||||
|
||||
- [#1171](https://github.com/thirdweb-dev/js/pull/1171) [`c0e2c945`](https://github.com/thirdweb-dev/js/commit/c0e2c9457f5ba992163b175f85b0f4b92b070f48) Thanks [@Marfuen](https://github.com/Marfuen)! - Minor bug fixes and improvements
|
||||
|
||||
- [#1183](https://github.com/thirdweb-dev/js/pull/1183) [`224b7241`](https://github.com/thirdweb-dev/js/commit/224b7241dc82155d3207a2d16523ceaae5532f40) Thanks [@Marfuen](https://github.com/Marfuen)! - Python detectors bug fixes
|
||||
|
||||
- Updated dependencies [[`cc20f93e`](https://github.com/thirdweb-dev/js/commit/cc20f93e178d86ee2b3f39102bbb0811de211f05), [`a430160e`](https://github.com/thirdweb-dev/js/commit/a430160e6b4771c03d97f6ede91f1aeaa043e50e), [`12b07aad`](https://github.com/thirdweb-dev/js/commit/12b07aad9ae3176daf9d05864247d4806a16c9d2), [`7d2a446e`](https://github.com/thirdweb-dev/js/commit/7d2a446ecef9c6c14959d31e9a66537783b9adac), [`8e28b0f5`](https://github.com/thirdweb-dev/js/commit/8e28b0f5e75596d29273ed80269bcee6d209adb4), [`01293857`](https://github.com/thirdweb-dev/js/commit/01293857fac8531bd94764203cd24b3daa4db51f), [`20bbad1a`](https://github.com/thirdweb-dev/js/commit/20bbad1abcb1ec573318d326b09278492a488abd)]:
|
||||
- @thirdweb-dev/chains@0.1.21
|
||||
- @thirdweb-dev/sdk@3.10.22
|
||||
|
||||
## 0.10.22
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -34,9 +34,6 @@ describe("npx thirdweb detect", () => {
|
||||
// wait for program to finish
|
||||
await create.waitForFinish();
|
||||
|
||||
// the process should exit with code 0
|
||||
expect(create.getExitCode()).toEqual(0);
|
||||
|
||||
// check if the contract was created
|
||||
expect(await exists("thirdweb-contracts/contracts/Contract.sol")).toEqual(
|
||||
true,
|
||||
@@ -56,14 +53,15 @@ describe("npx thirdweb detect", () => {
|
||||
"./thirdweb-contracts",
|
||||
);
|
||||
|
||||
// Select pnpm as package manager.
|
||||
await detect.pressKey("arrowDown");
|
||||
await detect.pressKey("enter");
|
||||
|
||||
// wait for program to finish
|
||||
await detect.waitForFinish();
|
||||
|
||||
const lines = detect.getStdout();
|
||||
|
||||
// check that detect excited successfully
|
||||
expect(detect.getExitCode()).toEqual(0);
|
||||
|
||||
// Detected extensions
|
||||
expect(lines.findIndex((line) => line.includes("ERC721"))).toBeGreaterThan(
|
||||
-1,
|
||||
@@ -86,9 +84,6 @@ describe("npx thirdweb detect", () => {
|
||||
lines.findIndex((line) => line.includes("- ERC721SignatureMint")),
|
||||
).toBeGreaterThan(-1);
|
||||
|
||||
// the process should exit with code 0
|
||||
expect(detect.getExitCode()).toEqual(0);
|
||||
|
||||
await cleanup(); // cleanup after test
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "thirdweb",
|
||||
"main": "dist/cli/index.js",
|
||||
"version": "0.10.22",
|
||||
"version": "0.10.23",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/cli",
|
||||
"author": "thirdweb eng <[email protected]>",
|
||||
"license": "Apache-2.0",
|
||||
@@ -47,6 +47,7 @@
|
||||
"got": "11.8.5",
|
||||
"inquirer": "^8.2.3",
|
||||
"js-yaml": "^4.1.0",
|
||||
"node-fetch": "^3.3.1",
|
||||
"open": "^8.4.0",
|
||||
"prompts": "^2.4.2",
|
||||
"rimraf": "^3.0.2",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
import { detectExtensions } from "../common/feature-detector";
|
||||
import { detectProjectV2 } from "../common/project-detector-v2";
|
||||
import { detectProject } from "../common/project-detector";
|
||||
import { processProject } from "../common/processor";
|
||||
import { cliVersion, pkg } from "../constants/urls";
|
||||
import { info, logger, spinner } from "../core/helpers/logger";
|
||||
@@ -9,7 +9,6 @@ import { twCreate } from "../create/command";
|
||||
import { deploy } from "../deploy";
|
||||
import { generate } from "../generate/command";
|
||||
import { findPackageInstallation } from "../helpers/detect-local-packages";
|
||||
import { install } from "../install/command";
|
||||
import { upload } from "../storage/command";
|
||||
import { ThirdwebStorage } from "@thirdweb-dev/storage";
|
||||
import chalk from "chalk";
|
||||
@@ -18,6 +17,7 @@ import { Command } from "commander";
|
||||
import open from "open";
|
||||
import prompts from "prompts";
|
||||
import Cache from "sync-disk-cache";
|
||||
import { install } from "../install";
|
||||
|
||||
const main = async () => {
|
||||
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
||||
@@ -204,6 +204,7 @@ const main = async () => {
|
||||
)
|
||||
.option("--nightly", "Install the nightly version of packages.")
|
||||
.option("--dev", "Install the dev version of packages")
|
||||
.option("-d, --debug", "show debug logs")
|
||||
.action(async (path, options) => {
|
||||
await install(path, options);
|
||||
});
|
||||
@@ -398,25 +399,13 @@ const main = async () => {
|
||||
|
||||
program
|
||||
.command("detect")
|
||||
.description(
|
||||
"(deprecated) Compile contracts and detect implemented thirdweb contract extensions",
|
||||
)
|
||||
.option("-p, --path <project-path>", "path to project", ".")
|
||||
.option("-d, --debug", "show debug logs")
|
||||
.option("-a, --all", "run detection on all contracts")
|
||||
.action(async (options) => {
|
||||
await detectExtensions(options);
|
||||
});
|
||||
|
||||
program
|
||||
.command("detect-project")
|
||||
.description(
|
||||
"Detect the type of project your are running and let you know what it is.",
|
||||
)
|
||||
.option("-p, --path <project-path>", "path to project", ".")
|
||||
.option("-d, --debug", "show debug logs")
|
||||
.action(async (options) => {
|
||||
await detectProjectV2(options);
|
||||
await detectProject(options);
|
||||
});
|
||||
|
||||
program
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
import detectPackageManager from "../core/detection/detectPackageManager";
|
||||
import detectFramework from "../core/detection/detectFramework";
|
||||
import detectLibrary from "../core/detection/detectLibrary";
|
||||
import detectLanguage from "../core/detection/detectLanguage";
|
||||
import { logger } from "../core/helpers/logger";
|
||||
import path from "path";
|
||||
import { ContractLibrariesType, contractLibraries } from "../core/types/ProjectType";
|
||||
|
||||
export async function detectProjectV2(options: any) {
|
||||
logger.setSettings({
|
||||
minLevel: options.debug ? "debug" : "info",
|
||||
});
|
||||
|
||||
let projectPath = process.cwd();
|
||||
if (options.path) {
|
||||
logger.debug("Overriding project path to " + options.path);
|
||||
|
||||
const resolvedPath = (options.path as string).startsWith("/")
|
||||
? options.path
|
||||
: path.resolve(`${projectPath}/${options.path}`);
|
||||
projectPath = resolvedPath;
|
||||
}
|
||||
|
||||
logger.debug("Processing project at path " + projectPath);
|
||||
|
||||
const detectedPackageManager = await detectPackageManager(
|
||||
projectPath,
|
||||
options,
|
||||
);
|
||||
const detectedLanguage = await detectLanguage(projectPath, options);
|
||||
const detectedLibrary = await detectLibrary(
|
||||
projectPath,
|
||||
options,
|
||||
detectedPackageManager,
|
||||
);
|
||||
const detectedFramework = await detectFramework(
|
||||
projectPath,
|
||||
options,
|
||||
detectedPackageManager,
|
||||
);
|
||||
const detectedAppType = contractLibraries.includes(detectedFramework as ContractLibrariesType) ? "contract" : "app";
|
||||
|
||||
logger.debug("Detected package manager: " + detectedPackageManager);
|
||||
logger.debug("Detected library: " + detectedLibrary);
|
||||
logger.debug("Detected language: " + detectedLanguage);
|
||||
logger.debug("Detected framework: " + detectedFramework);
|
||||
logger.debug("Detected app type: " + detectedAppType);
|
||||
}
|
||||
@@ -1,17 +1,20 @@
|
||||
import { ALWAYS_SUGGESTED } from "../constants/features";
|
||||
import build from "../core/builder/build";
|
||||
import detect from "../core/detection/detect";
|
||||
import detectPackageManager from "../core/detection/detectPackageManager";
|
||||
import detectFramework from "../core/detection/detectFramework";
|
||||
import detectLibrary from "../core/detection/detectLibrary";
|
||||
import detectLanguage from "../core/detection/detectLanguage";
|
||||
import { logger, spinner } from "../core/helpers/logger";
|
||||
import path from "path";
|
||||
import { ContractLibrariesType, contractLibraries } from "../core/types/ProjectType";
|
||||
import { FeatureWithEnabled, detectFeatures } from "@thirdweb-dev/sdk";
|
||||
import chalk from "chalk";
|
||||
import ora from "ora";
|
||||
import { ContractPayload } from "../core/interfaces/ContractPayload";
|
||||
import { createContractsPrompt } from "../core/helpers/selector";
|
||||
import { ContractFeatures, Feature } from "../core/interfaces/ContractFeatures";
|
||||
import { ContractPayload } from "../core/interfaces/ContractPayload";
|
||||
import { getPkgManager } from "../create/helpers/get-pkg-manager";
|
||||
import { detectFeatures, FeatureWithEnabled } from "@thirdweb-dev/sdk";
|
||||
import chalk from "chalk";
|
||||
import { existsSync, readFileSync } from "fs";
|
||||
import ora from "ora";
|
||||
import path from "path";
|
||||
import { getPkgManager } from "../create/helpers/get-pkg-manager";
|
||||
import { ALWAYS_SUGGESTED } from "../constants/features";
|
||||
import build from "../core/builder/build";
|
||||
|
||||
export async function detectProject(options: any) {
|
||||
logger.setSettings({
|
||||
@@ -30,115 +33,137 @@ export async function detectProject(options: any) {
|
||||
|
||||
logger.debug("Processing project at path " + projectPath);
|
||||
|
||||
const packageManagerType = await detectPackageManager(projectPath, options);
|
||||
console.log(packageManagerType);
|
||||
const projectType = await detect(projectPath, options);
|
||||
|
||||
let compiledResult;
|
||||
const compileLoader = spinner("Compiling project...");
|
||||
try {
|
||||
compiledResult = await build(projectPath, projectType, options);
|
||||
} catch (e) {
|
||||
compileLoader.fail("Compilation failed");
|
||||
logger.error(e);
|
||||
process.exit(1);
|
||||
}
|
||||
compileLoader.succeed("Compilation successful");
|
||||
|
||||
let selectedContracts: ContractPayload[] = [];
|
||||
if (compiledResult.contracts.length === 1) {
|
||||
selectedContracts = [compiledResult.contracts[0]];
|
||||
} else {
|
||||
if (options.all) {
|
||||
selectedContracts = compiledResult.contracts;
|
||||
} else {
|
||||
const choices = compiledResult.contracts.map((c) => ({
|
||||
name: c.name,
|
||||
value: c,
|
||||
}));
|
||||
const prompt = createContractsPrompt(
|
||||
choices,
|
||||
"Choose which contracts to run detection on",
|
||||
);
|
||||
const selection: Record<string, ContractPayload> = await prompt.run();
|
||||
selectedContracts = Object.keys(selection).map((key) => selection[key]);
|
||||
}
|
||||
}
|
||||
|
||||
let contractsWithFeatures: ContractFeatures[] = selectedContracts.map(
|
||||
(contract) => {
|
||||
const abi: Parameters<typeof detectFeatures>[0] = JSON.parse(
|
||||
contract.metadata,
|
||||
)["output"]["abi"];
|
||||
const features = extractFeatures(detectFeatures(abi));
|
||||
|
||||
const enabledFeatures: Feature[] = features.enabledFeatures.map(
|
||||
(feature) => ({
|
||||
name: feature.name,
|
||||
reference: `https://portal.thirdweb.com/interfaces/${feature.name.toLowerCase()}`,
|
||||
}),
|
||||
);
|
||||
const suggestedFeatures: Feature[] = features.suggestedFeatures.map(
|
||||
(feature) => ({
|
||||
name: feature.name,
|
||||
reference: `https://portal.thirdweb.com/interfaces/${feature.name.toLowerCase()}`,
|
||||
}),
|
||||
);
|
||||
|
||||
return {
|
||||
name: contract.name,
|
||||
enabledFeatures,
|
||||
suggestedFeatures,
|
||||
};
|
||||
},
|
||||
const detectedPackageManager = await detectPackageManager(
|
||||
projectPath,
|
||||
options,
|
||||
);
|
||||
const detectedLanguage = await detectLanguage(projectPath, options);
|
||||
const detectedLibrary = await detectLibrary(
|
||||
projectPath,
|
||||
options,
|
||||
detectedPackageManager,
|
||||
);
|
||||
const detectedFramework = await detectFramework(
|
||||
projectPath,
|
||||
options,
|
||||
detectedPackageManager,
|
||||
);
|
||||
const detectedAppType = detectedFramework !== "none" ? contractLibraries.includes(detectedFramework as ContractLibrariesType) ? "contract" : "app" : "app";
|
||||
const detectedContractLibrary = detectedAppType === "contract" ? detectedFramework as ContractLibrariesType : "none";
|
||||
|
||||
contractsWithFeatures.map((contractWithFeatures) => {
|
||||
logger.info(`\n`);
|
||||
if (contractWithFeatures.enabledFeatures.length === 0) {
|
||||
ora(
|
||||
`No extensions detected on ${chalk.blueBright(
|
||||
contractWithFeatures.name,
|
||||
)}`,
|
||||
).stopAndPersist({ symbol: "🔎" });
|
||||
} else {
|
||||
ora(
|
||||
`Detected extension on ${chalk.blueBright(contractWithFeatures.name)}`,
|
||||
).stopAndPersist({ symbol: "🔎" });
|
||||
contractWithFeatures.enabledFeatures.map((feature) => {
|
||||
logger.info(`✔️ ${chalk.green(feature.name)}`);
|
||||
});
|
||||
logger.info("Detected package manager: " + detectedPackageManager);
|
||||
logger.info("Detected library: " + detectedLibrary);
|
||||
logger.info("Detected language: " + detectedLanguage);
|
||||
logger.info("Detected framework: " + detectedFramework);
|
||||
logger.info("Detected app type: " + detectedAppType);
|
||||
|
||||
if (detectedAppType === "contract") {
|
||||
let compiledResult;
|
||||
const compileLoader = spinner("Compiling project...");
|
||||
try {
|
||||
compiledResult = await build(projectPath, detectedContractLibrary, options);
|
||||
} catch (e) {
|
||||
compileLoader.fail("Compilation failed");
|
||||
logger.error(e);
|
||||
process.exit(1);
|
||||
}
|
||||
logger.info(``);
|
||||
ora(`Suggested extensions`).info();
|
||||
contractWithFeatures.suggestedFeatures.map((feature) => {
|
||||
logger.info(
|
||||
`${chalk.dim(chalk.gray(`-`))} ${chalk.gray(
|
||||
feature.name,
|
||||
)} - ${chalk.dim(chalk.gray(feature.reference))}`,
|
||||
);
|
||||
});
|
||||
compileLoader.succeed("Compilation successful");
|
||||
|
||||
let deployCmd = `npx thirdweb@latest deploy`;
|
||||
if (existsSync(projectPath + "/package.json")) {
|
||||
const packageManager = getPkgManager();
|
||||
const useYarn = packageManager === "yarn";
|
||||
const pkgJson = JSON.parse(
|
||||
readFileSync(projectPath + "/package.json", "utf-8"),
|
||||
);
|
||||
if (pkgJson?.scripts?.deploy === deployCmd) {
|
||||
deployCmd = `${packageManager}${useYarn ? "" : " run"} deploy`;
|
||||
let selectedContracts: ContractPayload[] = [];
|
||||
if (compiledResult.contracts.length === 1) {
|
||||
selectedContracts = [compiledResult.contracts[0]];
|
||||
} else {
|
||||
if (options.all) {
|
||||
selectedContracts = compiledResult.contracts;
|
||||
} else {
|
||||
const choices = compiledResult.contracts.map((c) => ({
|
||||
name: c.name,
|
||||
value: c,
|
||||
}));
|
||||
const prompt = createContractsPrompt(
|
||||
choices,
|
||||
"Choose which contracts to run detection on",
|
||||
);
|
||||
const selection: Record<string, ContractPayload> = await prompt.run();
|
||||
selectedContracts = Object.keys(selection).map((key) => selection[key]);
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(``);
|
||||
ora(
|
||||
`Once you're done writing your contracts, you can run the following command to deploy them:`,
|
||||
).info();
|
||||
logger.info(``);
|
||||
logger.info(` ${chalk.cyan(deployCmd)}`);
|
||||
logger.info(``);
|
||||
});
|
||||
let contractsWithFeatures: ContractFeatures[] = selectedContracts.map(
|
||||
(contract) => {
|
||||
const abi: Parameters<typeof detectFeatures>[0] = JSON.parse(
|
||||
contract.metadata,
|
||||
)["output"]["abi"];
|
||||
const features = extractFeatures(detectFeatures(abi));
|
||||
|
||||
const enabledFeatures: Feature[] = features.enabledFeatures.map(
|
||||
(feature) => ({
|
||||
name: feature.name,
|
||||
reference: `https://portal.thirdweb.com/interfaces/${feature.name.toLowerCase()}`,
|
||||
}),
|
||||
);
|
||||
const suggestedFeatures: Feature[] = features.suggestedFeatures.map(
|
||||
(feature) => ({
|
||||
name: feature.name,
|
||||
reference: `https://portal.thirdweb.com/interfaces/${feature.name.toLowerCase()}`,
|
||||
}),
|
||||
);
|
||||
|
||||
return {
|
||||
name: contract.name,
|
||||
enabledFeatures,
|
||||
suggestedFeatures,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
contractsWithFeatures.map((contractWithFeatures) => {
|
||||
logger.info(`\n`);
|
||||
if (contractWithFeatures.enabledFeatures.length === 0) {
|
||||
ora(
|
||||
`No extensions detected on ${chalk.blueBright(
|
||||
contractWithFeatures.name,
|
||||
)}`,
|
||||
).stopAndPersist({ symbol: "🔎" });
|
||||
} else {
|
||||
ora(
|
||||
`Detected extension on ${chalk.blueBright(contractWithFeatures.name)}`,
|
||||
).stopAndPersist({ symbol: "🔎" });
|
||||
contractWithFeatures.enabledFeatures.map((feature) => {
|
||||
logger.info(`✔️ ${chalk.green(feature.name)}`);
|
||||
});
|
||||
}
|
||||
logger.info(``);
|
||||
ora(`Suggested extensions`).info();
|
||||
contractWithFeatures.suggestedFeatures.map((feature) => {
|
||||
logger.info(
|
||||
`${chalk.dim(chalk.gray(`-`))} ${chalk.gray(
|
||||
feature.name,
|
||||
)} - ${chalk.dim(chalk.gray(feature.reference))}`,
|
||||
);
|
||||
});
|
||||
|
||||
let deployCmd = `npx thirdweb@latest deploy`;
|
||||
if (existsSync(projectPath + "/package.json")) {
|
||||
const packageManager = getPkgManager();
|
||||
const useYarn = packageManager === "yarn";
|
||||
const pkgJson = JSON.parse(
|
||||
readFileSync(projectPath + "/package.json", "utf-8"),
|
||||
);
|
||||
if (pkgJson?.scripts?.deploy === deployCmd) {
|
||||
deployCmd = `${packageManager}${useYarn ? "" : " run"} deploy`;
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(``);
|
||||
ora(
|
||||
`Once you're done writing your contracts, you can run the following command to deploy them:`,
|
||||
).info();
|
||||
logger.info(``);
|
||||
logger.info(` ${chalk.cyan(deployCmd)}`);
|
||||
logger.info(``);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function extractFeatures(
|
||||
|
||||
@@ -29,7 +29,6 @@ export default async function detect(
|
||||
options: any,
|
||||
detectedPackageManager: PackageManagerType,
|
||||
): Promise<FrameworkType> {
|
||||
// We could optimize further if we want, by only running the detectors that match the package manager.
|
||||
const frameworkDetectors: FrameworkDetector[] = [
|
||||
new BrownieDetector(),
|
||||
new CRADetector(),
|
||||
@@ -66,6 +65,10 @@ export default async function detect(
|
||||
return possibleFrameworks[0];
|
||||
}
|
||||
|
||||
if (possibleFrameworks.includes("expo") && possibleFrameworks.includes("react-native-cli")) {
|
||||
return "expo";
|
||||
}
|
||||
|
||||
info(
|
||||
`Detected multiple possible frameworks: ${possibleFrameworks
|
||||
.map((s) => `"${s}"`)
|
||||
|
||||
@@ -30,6 +30,10 @@ export default async function detect(
|
||||
return possibleLanguages[0];
|
||||
}
|
||||
|
||||
if (possibleLanguages.includes("typescript") && possibleLanguages.includes("javascript")) {
|
||||
return "typescript";
|
||||
}
|
||||
|
||||
info(
|
||||
`Detected multiple possible languages: ${possibleLanguages
|
||||
.map((s) => `"${s}"`)
|
||||
|
||||
@@ -36,6 +36,10 @@ export default async function detect(
|
||||
return possibleLibraries[0];
|
||||
}
|
||||
|
||||
if (possibleLibraries.includes("react-native") && possibleLibraries.includes("react")) {
|
||||
return "react-native";
|
||||
}
|
||||
|
||||
info(
|
||||
`Detected multiple possible libraries: ${possibleLibraries
|
||||
.map((s) => `"${s}"`)
|
||||
|
||||
@@ -5,10 +5,12 @@ import inquirer from "inquirer";
|
||||
import NPMDetector from "./packageManagers/npm";
|
||||
import YarnDetector from "./packageManagers/yarn";
|
||||
import PnpmDetector from "./packageManagers/pnpm";
|
||||
import CondaDetector from "./packageManagers/conda";
|
||||
import PipDetector from "./packageManagers/pip";
|
||||
import PipEnvDetector from "./packageManagers/pipenv";
|
||||
import PoetryDetector from "./packageManagers/poetry";
|
||||
import BrownieDetector from "./packageManagers/brownie";
|
||||
import FoundryDetector from "./packageManagers/foundry";
|
||||
import GoModulesDetector from "./packageManagers/goModules";
|
||||
|
||||
export default async function detect(
|
||||
path: string,
|
||||
@@ -18,14 +20,18 @@ export default async function detect(
|
||||
new NPMDetector(),
|
||||
new YarnDetector(),
|
||||
new PnpmDetector(),
|
||||
new CondaDetector(),
|
||||
new PipDetector(),
|
||||
new PipEnvDetector(),
|
||||
new PoetryDetector(),
|
||||
new GoModulesDetector(),
|
||||
new BrownieDetector(),
|
||||
new FoundryDetector(),
|
||||
];
|
||||
|
||||
const possiblePackageManagers = packageManagerDetectors
|
||||
.filter((detector) => detector.matches(path))
|
||||
.filter((detector) => {
|
||||
return detector.matches(path);
|
||||
})
|
||||
.map((detector) => detector.packageManagerType);
|
||||
|
||||
if (!possiblePackageManagers.length) {
|
||||
@@ -36,6 +42,10 @@ export default async function detect(
|
||||
return possiblePackageManagers[0];
|
||||
}
|
||||
|
||||
if (possiblePackageManagers.includes("brownie") && possiblePackageManagers.includes("pip")) {
|
||||
return "brownie";
|
||||
}
|
||||
|
||||
info(
|
||||
`Detected multiple possible package managers: ${possiblePackageManagers
|
||||
.map((s) => `"${s}"`)
|
||||
|
||||
@@ -20,10 +20,16 @@ export default class BrownieDetector implements FrameworkDetector {
|
||||
existsSync(path + file),
|
||||
);
|
||||
|
||||
const additionalDirectoriesToCheck = ["/build", "/contracts", "/reports"];
|
||||
const additionalDirectoriesExist = additionalDirectoriesToCheck.every((dir) => {
|
||||
return existsSync(path + dir);
|
||||
});
|
||||
|
||||
return (
|
||||
dependencies.includes("brownie") ||
|
||||
devDependencies.includes("brownie") ||
|
||||
!!dependencies.find(dep => dep.includes("brownie")) ||
|
||||
!!devDependencies.find(dep => dep.includes("brownie")) ||
|
||||
additionalFilesExist ||
|
||||
additionalDirectoriesExist ||
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
@@ -16,8 +16,8 @@ export default class CRADetector implements FrameworkDetector {
|
||||
const packageJson = readFileSync(packageJsonPath);
|
||||
const { dependencies, devDependencies } = parsePackageJson(packageJson);
|
||||
const craDependencyExists =
|
||||
dependencies["react-scripts"] ||
|
||||
devDependencies["react-scripts"] ||
|
||||
!!dependencies["react-scripts"] ||
|
||||
!!devDependencies["react-scripts"] ||
|
||||
false;
|
||||
|
||||
return craDependencyExists;
|
||||
|
||||
@@ -18,8 +18,8 @@ export default class DjangoDetector implements FrameworkDetector {
|
||||
);
|
||||
|
||||
return (
|
||||
dependencies.includes("django") ||
|
||||
devDependencies.includes("django") ||
|
||||
!!dependencies.find(dep => dep.includes("Django")) ||
|
||||
!!devDependencies.find(dep => dep.includes("Django")) ||
|
||||
additionalFilesExist ||
|
||||
false
|
||||
);
|
||||
|
||||
@@ -16,14 +16,14 @@ export default class ExpoDetector implements FrameworkDetector {
|
||||
const packageJson = readFileSync(packageJsonPath);
|
||||
const { dependencies, devDependencies } = parsePackageJson(packageJson);
|
||||
|
||||
const additionalFilesToCheck = ["/app.json", "/app.config.js", "/assets"];
|
||||
const additionalFilesToCheck = ["/app.json", "/app.config.js"];
|
||||
const additionalFilesExist = additionalFilesToCheck.some((file) =>
|
||||
existsSync(path + file),
|
||||
);
|
||||
|
||||
const expoDependencyExists =
|
||||
dependencies["expo"] ||
|
||||
devDependencies["expo"] ||
|
||||
!!dependencies["expo"] ||
|
||||
!!devDependencies["expo"] ||
|
||||
additionalFilesExist ||
|
||||
false;
|
||||
|
||||
|
||||
@@ -12,14 +12,14 @@ export default class FastAPIDetector implements FrameworkDetector {
|
||||
packageManager,
|
||||
);
|
||||
|
||||
const additionalFilesToCheck = ["/app.py", "/main.py"];
|
||||
const additionalFilesToCheck: string[] = [];
|
||||
const additionalFilesExist = additionalFilesToCheck.some((file) =>
|
||||
existsSync(path + file),
|
||||
);
|
||||
|
||||
return (
|
||||
dependencies.includes("fastapi") ||
|
||||
devDependencies.includes("fastapi") ||
|
||||
!!dependencies.find(dep => dep.includes("fastapi")) ||
|
||||
!!devDependencies.find(dep => dep.includes("fastapi")) ||
|
||||
additionalFilesExist ||
|
||||
false
|
||||
);
|
||||
|
||||
@@ -16,19 +16,14 @@ export default class FastifyDetector implements FrameworkDetector {
|
||||
const packageJson = readFileSync(packageJsonPath);
|
||||
const { dependencies, devDependencies } = parsePackageJson(packageJson);
|
||||
|
||||
const additionalFilesToCheck = [
|
||||
"/server.js",
|
||||
"/app.js",
|
||||
"/server.ts",
|
||||
"/app.ts",
|
||||
];
|
||||
const additionalFilesToCheck: string[] = [];
|
||||
const additionalFilesExist = additionalFilesToCheck.some((file) =>
|
||||
existsSync(path + file),
|
||||
);
|
||||
|
||||
const fastifyDependencyExists =
|
||||
dependencies["fastify"] ||
|
||||
devDependencies["fastify"] ||
|
||||
!!dependencies["fastify"] ||
|
||||
!!devDependencies["fastify"] ||
|
||||
additionalFilesExist ||
|
||||
false;
|
||||
|
||||
|
||||
@@ -12,14 +12,14 @@ export default class FlaskDetector implements FrameworkDetector {
|
||||
packageManager,
|
||||
);
|
||||
|
||||
const additionalFilesToCheck = ["/app.py", "/application.py"];
|
||||
const additionalFilesToCheck: string[] = [];
|
||||
const additionalFilesExist = additionalFilesToCheck.some((file) =>
|
||||
existsSync(path + file),
|
||||
);
|
||||
|
||||
return (
|
||||
dependencies.includes("flask") ||
|
||||
devDependencies.includes("flask") ||
|
||||
!!dependencies.find(dep => dep.includes("Flask")) ||
|
||||
!!devDependencies.find(dep => dep.includes("Flask")) ||
|
||||
additionalFilesExist ||
|
||||
false
|
||||
);
|
||||
|
||||
@@ -22,8 +22,8 @@ export default class GatsbyDetector implements FrameworkDetector {
|
||||
);
|
||||
|
||||
const gatsbyDependencyExists =
|
||||
dependencies["gatsby"] ||
|
||||
devDependencies["gatsby"] ||
|
||||
!!dependencies["gatsby"] ||
|
||||
!!devDependencies["gatsby"] ||
|
||||
additionalFilesExist ||
|
||||
false;
|
||||
|
||||
|
||||
@@ -16,14 +16,14 @@ export default class HardhatDetector implements FrameworkDetector {
|
||||
const packageJson = readFileSync(packageJsonPath);
|
||||
const { dependencies, devDependencies } = parsePackageJson(packageJson);
|
||||
|
||||
const additionalFilesToCheck = ["/hardhat.config.js", "/contracts/"];
|
||||
const additionalFilesToCheck = ["/hardhat.config.js"];
|
||||
const additionalFilesExist = additionalFilesToCheck.some((file) =>
|
||||
existsSync(path + file),
|
||||
);
|
||||
|
||||
const hardhatDependencyExists =
|
||||
dependencies["hardhat"] ||
|
||||
devDependencies["hardhat"] ||
|
||||
!!dependencies["hardhat"] ||
|
||||
!!devDependencies["hardhat"] ||
|
||||
additionalFilesExist ||
|
||||
false;
|
||||
|
||||
|
||||
@@ -22,8 +22,8 @@ export default class NextDetector implements FrameworkDetector {
|
||||
);
|
||||
|
||||
const nextDependencyExists =
|
||||
dependencies["next"] ||
|
||||
devDependencies["next"] ||
|
||||
!!dependencies["next"] ||
|
||||
!!devDependencies["next"] ||
|
||||
additionalFilesExist ||
|
||||
false;
|
||||
|
||||
|
||||
@@ -18,8 +18,8 @@ export default class PopulusDetector implements FrameworkDetector {
|
||||
);
|
||||
|
||||
return (
|
||||
dependencies.includes("populus") ||
|
||||
devDependencies.includes("populus") ||
|
||||
!!dependencies.find(dep => dep === "populus") ||
|
||||
!!devDependencies.find(dep => dep === "populus") ||
|
||||
additionalFilesExist ||
|
||||
false
|
||||
);
|
||||
|
||||
@@ -16,14 +16,14 @@ export default class ReactNativeCLIDetector implements FrameworkDetector {
|
||||
const packageJson = readFileSync(packageJsonPath);
|
||||
const { dependencies, devDependencies } = parsePackageJson(packageJson);
|
||||
|
||||
const additionalFilesToCheck = ["/android", "/ios"];
|
||||
const additionalFilesToCheck: string[] = [];
|
||||
const additionalFilesExist = additionalFilesToCheck.some((file) =>
|
||||
existsSync(path + file),
|
||||
);
|
||||
|
||||
return (
|
||||
dependencies["react-native"] ||
|
||||
devDependencies["react-native"] ||
|
||||
!!dependencies["react-native"] ||
|
||||
!!devDependencies["react-native"] ||
|
||||
additionalFilesExist ||
|
||||
false
|
||||
);
|
||||
|
||||
@@ -19,15 +19,14 @@ export default class TruffleDetector implements FrameworkDetector {
|
||||
const additionalFilesToCheck = [
|
||||
"/truffle.config.js",
|
||||
"/truffle.js",
|
||||
"/contracts/",
|
||||
];
|
||||
const additionalFilesExist = additionalFilesToCheck.some((file) =>
|
||||
existsSync(path + file),
|
||||
);
|
||||
|
||||
return (
|
||||
dependencies["truffle"] ||
|
||||
devDependencies["truffle"] ||
|
||||
!!dependencies["truffle"] ||
|
||||
!!devDependencies["truffle"] ||
|
||||
additionalFilesExist ||
|
||||
false
|
||||
);
|
||||
|
||||
@@ -22,8 +22,8 @@ export default class VueDetector implements FrameworkDetector {
|
||||
);
|
||||
|
||||
return (
|
||||
dependencies["vue"] ||
|
||||
devDependencies["vue"] ||
|
||||
!!dependencies["vue"] ||
|
||||
!!devDependencies["vue"] ||
|
||||
additionalFilesExist ||
|
||||
false
|
||||
);
|
||||
|
||||
@@ -12,7 +12,7 @@ export default class Web3PyDetector implements LibraryDetector {
|
||||
);
|
||||
|
||||
return (
|
||||
dependencies.includes("web3") || devDependencies.includes("web3") || false
|
||||
!!dependencies.find(dep => dep.includes("web3")) || !!devDependencies.find(dep => dep.includes("web3")) || false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { PackageManagerDetector } from "../detector";
|
||||
import { existsSync } from "fs";
|
||||
import { PackageManagerType } from "../../types/ProjectType";
|
||||
|
||||
export default class BrownieDetector implements PackageManagerDetector {
|
||||
public packageManagerType: PackageManagerType = "brownie";
|
||||
|
||||
public matches(path: string): boolean {
|
||||
|
||||
const additionalFilesToCheck = [
|
||||
"/brownie-config.yaml",
|
||||
"/brownie-config.json",
|
||||
];
|
||||
const additionalFilesExist = additionalFilesToCheck.some((file) =>
|
||||
existsSync(path + file),
|
||||
);
|
||||
|
||||
// Check that all of these exist.
|
||||
const additionalDirectoriesToCheck = ["/build", "/contracts", "/reports"];
|
||||
const additionalDirectoriesExist = additionalDirectoriesToCheck.every((dir) => {
|
||||
return existsSync(path + dir);
|
||||
});
|
||||
|
||||
return (
|
||||
additionalFilesExist ||
|
||||
additionalDirectoriesExist ||
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
import { PackageManagerDetector } from "../detector";
|
||||
import { existsSync } from "fs";
|
||||
import { PackageManagerType } from "../../types/ProjectType";
|
||||
|
||||
export default class CondaDetector implements PackageManagerDetector {
|
||||
public packageManagerType: PackageManagerType = "conda";
|
||||
|
||||
public matches(path: string): boolean {
|
||||
const files = [
|
||||
"/environment.yml",
|
||||
"/environment.yaml",
|
||||
"/conda-requirements.txt",
|
||||
"/.conda",
|
||||
"/.condarc",
|
||||
];
|
||||
|
||||
// Check if at least one of these files exists in the current path.
|
||||
return files.some((file) => existsSync(path + file));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { PackageManagerType } from "../../types/ProjectType";
|
||||
import { PackageManagerDetector } from "../detector";
|
||||
import { existsSync } from "fs";
|
||||
|
||||
export default class FoundryDetector implements PackageManagerDetector {
|
||||
public packageManagerType: PackageManagerType = "foundry";
|
||||
|
||||
public matches(path: string): boolean {
|
||||
const additionalFilesToCheck = ["/foundry.toml", "/lib/forge-std/"];
|
||||
const additionalFilesExist = additionalFilesToCheck.some((file) =>
|
||||
existsSync(path + file),
|
||||
);
|
||||
|
||||
return additionalFilesExist || false;
|
||||
}
|
||||
}
|
||||
@@ -18,10 +18,11 @@ export type PackageManagerType =
|
||||
| "npm"
|
||||
| "pnpm"
|
||||
| "pip"
|
||||
| "conda"
|
||||
| "pipenv"
|
||||
| "poetry"
|
||||
| "go-modules"
|
||||
| "brownie"
|
||||
| "foundry"
|
||||
| "none";
|
||||
export type LanguageType =
|
||||
| "javascript"
|
||||
@@ -59,14 +60,13 @@ export type FrameworkType =
|
||||
export type LibraryType =
|
||||
| "react"
|
||||
| "react-native"
|
||||
| "vue"
|
||||
| "svelte"
|
||||
| "vite"
|
||||
| "express"
|
||||
| "web3py"
|
||||
| "playmaker"
|
||||
| "none";
|
||||
export type AppType = "app" | "contract" | "library" | "none";
|
||||
export type AppType = "app" | "contract" | "none";
|
||||
export type ContractLibrariesType =
|
||||
| "brownie"
|
||||
| "foundry"
|
||||
@@ -83,4 +83,4 @@ export const contractLibraries: readonly ContractLibrariesType[] = [
|
||||
"truffle",
|
||||
"solc",
|
||||
"none",
|
||||
] as const;
|
||||
] as const;
|
||||
@@ -1,4 +1,4 @@
|
||||
import spawn from "cross-spawn";
|
||||
import { spawn } from "cross-spawn";
|
||||
|
||||
export async function runCommand(
|
||||
command: string,
|
||||
@@ -9,8 +9,9 @@ export async function runCommand(
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
/**
|
||||
* Spawn the installation process.
|
||||
*/
|
||||
*/
|
||||
const child = spawn(command, args);
|
||||
|
||||
child.stdout?.on("data", (data) => {
|
||||
if (onData) {
|
||||
onData(data);
|
||||
@@ -20,17 +21,22 @@ export async function runCommand(
|
||||
}
|
||||
});
|
||||
|
||||
child.stderr?.on("error", (err) => {
|
||||
child.stderr?.on("data", (data) => {
|
||||
if (logging) {
|
||||
console.error(err);
|
||||
console.error(data.toString());
|
||||
}
|
||||
});
|
||||
|
||||
child.on("error", (err) => {
|
||||
console.log("Spawn error", err)
|
||||
});
|
||||
|
||||
child.on("close", (code) => {
|
||||
if (code !== 0) {
|
||||
reject({ command: `${command}` });
|
||||
reject({ command: `${command}`, exitCode: code });
|
||||
return;
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -197,8 +197,8 @@ export async function generate(options: GenerateOptions) {
|
||||
|
||||
const postinstall = packageJson.scripts?.postinstall
|
||||
? packageJson.scripts.postinstall +
|
||||
` && export THIRDWEB_CLI_SKIP_INTRO=true && npx thirdweb generate --skip-update-check`
|
||||
: `export THIRDWEB_CLI_SKIP_INTRO=true && npx thirdweb generate --skip-update-check`;
|
||||
` && export THIRDWEB_CLI_SKIP_INTRO=true && npx --yes thirdweb@latest generate`
|
||||
: `export THIRDWEB_CLI_SKIP_INTRO=true && npx --yes thirdweb@latest generate`;
|
||||
|
||||
fs.writeFileSync(
|
||||
packageJsonPath,
|
||||
|
||||
@@ -1,159 +0,0 @@
|
||||
import BrownieDetector from "../core/detection/brownie";
|
||||
import CRADetector from "../core/detection/cra";
|
||||
import FoundryDetector from "../core/detection/foundry";
|
||||
import HardhatDetector from "../core/detection/hardhat";
|
||||
import NextDetector from "../core/detection/next";
|
||||
import NPMDetector from "../core/detection/npm";
|
||||
import SolcDetector from "../core/detection/solc";
|
||||
import TruffleDetector from "../core/detection/truffle";
|
||||
import ViteDetector from "../core/detection/vite";
|
||||
import YarnDetector from "../core/detection/yarn";
|
||||
import { runCommand } from "../create/helpers/run-command";
|
||||
import fs from "fs";
|
||||
import ora from "ora";
|
||||
import { info } from "../core/helpers/logger";
|
||||
|
||||
const EXCLUDED_DEPENDENCIES = ["@thirdweb-dev/chain-icons"];
|
||||
|
||||
export async function install(projectPath = ".", options: any) {
|
||||
const supportedContractFrameworks = [
|
||||
new BrownieDetector(),
|
||||
new FoundryDetector(),
|
||||
new HardhatDetector(),
|
||||
new TruffleDetector(),
|
||||
new SolcDetector(),
|
||||
].filter((detector) => detector.matches(projectPath));
|
||||
|
||||
const supportedAppFrameworks = [
|
||||
new ViteDetector(),
|
||||
new NextDetector(),
|
||||
new CRADetector(),
|
||||
].filter((detector) => detector.matches(projectPath));
|
||||
|
||||
const hasYarn = new YarnDetector().matches(".");
|
||||
const hasNPM = new NPMDetector().matches(".");
|
||||
|
||||
let packageJson;
|
||||
try {
|
||||
packageJson = JSON.parse(
|
||||
fs.readFileSync(projectPath + "/package.json", "utf8"),
|
||||
);
|
||||
} catch (e) {
|
||||
console.error(
|
||||
"No package.json file found. Please run this command in the root of your project.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const hasEthers =
|
||||
!!packageJson?.dependencies?.ethers ||
|
||||
!!packageJson?.devDependencies?.ethers;
|
||||
|
||||
const version = options.dev
|
||||
? "@dev"
|
||||
: options.nightly
|
||||
? "@nightly"
|
||||
: "@latest";
|
||||
|
||||
if (
|
||||
supportedAppFrameworks.length === 0 &&
|
||||
supportedContractFrameworks.length === 0
|
||||
) {
|
||||
throw new Error("No supported project detected");
|
||||
}
|
||||
|
||||
const thirdwebDepsToUpdate = new Set<string>([
|
||||
...Object.keys(packageJson.dependencies || {}).filter((dep) =>
|
||||
dep.startsWith("@thirdweb-dev/"),
|
||||
),
|
||||
...Object.keys(packageJson.devDependencies || {}).filter((dep) =>
|
||||
dep.startsWith("@thirdweb-dev/"),
|
||||
),
|
||||
...Object.keys(packageJson.peerDependencies || {}).filter((dep) =>
|
||||
dep.startsWith("@thirdweb-dev/"),
|
||||
),
|
||||
]);
|
||||
|
||||
const thirdwebDepsToInstall = new Set<string>();
|
||||
|
||||
const otherDeps = new Set<string>();
|
||||
|
||||
// only expect one contract framework to be detected
|
||||
const contractFramework = supportedContractFrameworks[0];
|
||||
// only expect one app framework to be detected
|
||||
const appFramerwork = supportedAppFrameworks[0];
|
||||
|
||||
if (contractFramework) {
|
||||
info(`Detected project: "${contractFramework.projectType}"`);
|
||||
|
||||
if (!thirdwebDepsToUpdate.has(`@thirdweb-dev/contracts`)) {
|
||||
thirdwebDepsToInstall.add(`@thirdweb-dev/contracts`);
|
||||
}
|
||||
} else if (appFramerwork) {
|
||||
info(`Detected project: "${appFramerwork.projectType}"`);
|
||||
|
||||
if (!thirdwebDepsToUpdate.has(`@thirdweb-dev/react`)) {
|
||||
thirdwebDepsToInstall.add(`@thirdweb-dev/react`);
|
||||
}
|
||||
if (!thirdwebDepsToUpdate.has(`@thirdweb-dev/sdk`)) {
|
||||
thirdwebDepsToInstall.add(`@thirdweb-dev/sdk`);
|
||||
}
|
||||
if (!hasEthers) {
|
||||
otherDeps.add("ethers@5");
|
||||
}
|
||||
}
|
||||
|
||||
// remove exluded dependencies
|
||||
EXCLUDED_DEPENDENCIES.forEach((excludedDep) => {
|
||||
thirdwebDepsToUpdate.delete(excludedDep);
|
||||
thirdwebDepsToInstall.delete(excludedDep);
|
||||
});
|
||||
|
||||
let installer;
|
||||
let updater;
|
||||
|
||||
try {
|
||||
if (thirdwebDepsToInstall.size !== 0) {
|
||||
installer = ora(
|
||||
`Installing: "${[...thirdwebDepsToInstall].join('", "')}"`,
|
||||
).start();
|
||||
}
|
||||
if (thirdwebDepsToUpdate.size !== 0) {
|
||||
updater = ora(
|
||||
`Updating: "${[...thirdwebDepsToUpdate].join('", "')}"`,
|
||||
).start();
|
||||
}
|
||||
|
||||
const dependenciesToAdd = [
|
||||
...[...thirdwebDepsToInstall].map((dep) => `${dep}${version}`),
|
||||
...otherDeps,
|
||||
];
|
||||
|
||||
const dependenciesToUpdate = [...thirdwebDepsToUpdate].map(
|
||||
(dep) => `${dep}${version}`,
|
||||
);
|
||||
|
||||
if (hasYarn) {
|
||||
if (dependenciesToAdd.length !== 0) {
|
||||
await runCommand("yarn", ["add", ...dependenciesToAdd]);
|
||||
installer?.succeed(`Installed: "${dependenciesToAdd.join('", "')}"`);
|
||||
}
|
||||
if (dependenciesToUpdate.length !== 0) {
|
||||
await runCommand("yarn", ["upgrade", ...dependenciesToUpdate]);
|
||||
updater?.succeed(`Updated: "${dependenciesToUpdate.join('", "')}"`);
|
||||
}
|
||||
} else if (hasNPM) {
|
||||
if (dependenciesToAdd.length !== 0) {
|
||||
await runCommand("npm", ["install", ...dependenciesToAdd]);
|
||||
installer?.succeed(`Installed: "${dependenciesToAdd.join('", "')}"`);
|
||||
}
|
||||
if (dependenciesToUpdate.length !== 0) {
|
||||
await runCommand("npm", ["update", ...dependenciesToUpdate]);
|
||||
updater?.succeed(`Updated: "${dependenciesToUpdate.join('", "')}"`);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Can't install within project");
|
||||
return Promise.reject("Can't install within project");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
// Import required dependencies
|
||||
import { existsSync, readFileSync } from "fs";
|
||||
import ora from "ora";
|
||||
import inquirer from "inquirer";
|
||||
|
||||
// Import project dependencies
|
||||
import detectFramework from "../core/detection/detectFramework";
|
||||
import detectPackageManager from "../core/detection/detectPackageManager";
|
||||
import detectLibrary from "../core/detection/detectLibrary";
|
||||
import { ContractLibrariesType, contractLibraries } from "../core/types/ProjectType";
|
||||
import { checkIfBrowniePackageIsInstalled, getDependenciesForFoundry, getDependenciesForGo, getDependenciesForPython, installOrUpdate, parsePackageJson, processAppType, processContractAppType } from "../lib/utils";
|
||||
import { logger } from "../core/helpers/logger";
|
||||
|
||||
// Define excluded dependencies
|
||||
const EXCLUDED_DEPENDENCIES = ["@thirdweb-dev/chain-icons"];
|
||||
|
||||
export async function install(projectPath = ".", options: any) {
|
||||
const debug = options.debug || false;
|
||||
let installer;
|
||||
let updater;
|
||||
|
||||
// Initialize all dependencies.
|
||||
let jsDependencies: { [key: string]: string } | string[] = {};
|
||||
let jsDevDependencies: { [key: string]: string } | string[] = {};
|
||||
let pythonDependencies: string[] = [];
|
||||
let pythonDevDependencies: string[] = [];
|
||||
let peerDependencies: { [key: string]: string } = {};
|
||||
let goDependencies: { name: string; version: string }[] = [];
|
||||
const thirdwebDepsToInstall = new Set<string>();
|
||||
const otherDeps = new Set<string>();
|
||||
|
||||
// Initialize brownie and foundry checks
|
||||
let thirdwebExistsInBrownie = { package: "", exists: false };
|
||||
let thirdwebExistsInFoundry = false;
|
||||
|
||||
|
||||
// Detect the package manager, and framework used in the project
|
||||
const detectedPackageManager = await detectPackageManager(projectPath, options);
|
||||
const detectedFramework = await detectFramework(projectPath, options, detectedPackageManager);
|
||||
const detectedLibrary = await detectLibrary(projectPath, options, detectedPackageManager);
|
||||
|
||||
// Determine project type
|
||||
const detectedAppType = detectedFramework !== "none" ? contractLibraries.includes(detectedFramework as ContractLibrariesType) ? "contract" : "app" : "app";
|
||||
|
||||
// Identify which specific package manager is used
|
||||
const isBrowniePackageManager = detectedPackageManager === "brownie";
|
||||
const isFoundryPackageManager = detectedPackageManager === "foundry";
|
||||
const isGoPackageManager = detectedPackageManager === "go-modules";
|
||||
const isPythonPackageManager = ["pip", "pipenv", "poetry"].includes(detectedPackageManager);
|
||||
const isJSPackageManager = ["yarn", "pnpm", "npm"].includes(detectedPackageManager);
|
||||
|
||||
// Processing JS/TS dependencies
|
||||
if (isJSPackageManager) {
|
||||
let packageJson;
|
||||
const packageJsonPath = projectPath + "/package.json";
|
||||
|
||||
// Check if package.json exists
|
||||
if (!existsSync(packageJsonPath)) {
|
||||
console.error(
|
||||
"No package.json file found. Please run this command in the root of your project."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse package.json
|
||||
packageJson = readFileSync(packageJsonPath);
|
||||
const parsedJSDeps = parsePackageJson(packageJson);
|
||||
jsDependencies = parsedJSDeps.dependencies;
|
||||
jsDevDependencies = parsedJSDeps.devDependencies;
|
||||
peerDependencies = parsedJSDeps.peerDependencies;
|
||||
}
|
||||
|
||||
// Processing Python dependencies
|
||||
if (isPythonPackageManager) {
|
||||
const pythonDeps = getDependenciesForPython(projectPath, detectedPackageManager);
|
||||
pythonDependencies = pythonDeps.dependencies;
|
||||
pythonDevDependencies = pythonDeps.devDependencies;
|
||||
}
|
||||
|
||||
// Processing Brownie dependencies
|
||||
if (isBrowniePackageManager) {
|
||||
const regex = /\s*thirdweb-dev\/contracts@\d+\.\d+\.\d+/g;
|
||||
const res = await checkIfBrowniePackageIsInstalled(regex);
|
||||
thirdwebExistsInBrownie = res;
|
||||
}
|
||||
|
||||
// Processing Foundry dependencies
|
||||
if (isFoundryPackageManager) {
|
||||
const exists = await getDependenciesForFoundry(projectPath, "thirdweb-contracts");
|
||||
thirdwebExistsInFoundry = exists;
|
||||
}
|
||||
|
||||
// Processing Go dependencies
|
||||
if (isGoPackageManager) {
|
||||
const deps = getDependenciesForGo(projectPath);
|
||||
goDependencies = deps;
|
||||
}
|
||||
|
||||
const hasEthers =
|
||||
!!jsDependencies?.ethers ||
|
||||
!!jsDevDependencies?.ethers;
|
||||
|
||||
const version = !isJSPackageManager ? "" :
|
||||
options.dev
|
||||
? "@dev"
|
||||
: options.nightly
|
||||
? "@nightly" : "";
|
||||
|
||||
const thirdwebDepsToUpdate = new Set<string>(
|
||||
[
|
||||
...Object.keys(jsDependencies).filter((dep) =>
|
||||
dep.startsWith("@thirdweb-dev/"),
|
||||
),
|
||||
...Object.keys(jsDevDependencies).filter((dep) =>
|
||||
dep.startsWith("@thirdweb-dev/"),
|
||||
),
|
||||
...Object.keys(peerDependencies).filter((dep) =>
|
||||
dep.startsWith("@thirdweb-dev/"),
|
||||
),
|
||||
...pythonDependencies
|
||||
.filter((dep) => dep.startsWith("thirdweb-sdk"))
|
||||
.map((dep) => dep.split('==')[0]),
|
||||
...pythonDevDependencies
|
||||
.filter((dep) => dep.startsWith("thirdweb-sdk"))
|
||||
.map((dep) => dep.split('==')[0]),
|
||||
...goDependencies
|
||||
.filter((dep) => {
|
||||
console.log('dep', dep)
|
||||
return dep.name === "github.com/thirdweb-dev/go-sdk/v2"
|
||||
})
|
||||
.map((dep) => dep.name),
|
||||
].concat(thirdwebExistsInBrownie.exists ? [thirdwebExistsInBrownie.package] : [])
|
||||
.concat(thirdwebExistsInFoundry ? ["lib/thirdweb-contracts"] : [])
|
||||
);
|
||||
|
||||
// Figure out which dependencies to install and which to update.
|
||||
if (detectedAppType === "contract") {
|
||||
processContractAppType({ detectedPackageManager, thirdwebDepsToUpdate, thirdwebDepsToInstall, isJSPackageManager })
|
||||
} else if (detectedAppType === "app") {
|
||||
processAppType({
|
||||
detectedLibrary,
|
||||
detectedFramework,
|
||||
hasEthers,
|
||||
isGoPackageManager,
|
||||
isJSPackageManager,
|
||||
isPythonPackageManager,
|
||||
otherDeps,
|
||||
thirdwebDepsToInstall,
|
||||
thirdwebDepsToUpdate,
|
||||
})
|
||||
}
|
||||
|
||||
// remove exluded dependencies
|
||||
EXCLUDED_DEPENDENCIES.forEach((excludedDep) => {
|
||||
thirdwebDepsToUpdate.delete(excludedDep);
|
||||
thirdwebDepsToInstall.delete(excludedDep);
|
||||
});
|
||||
|
||||
// Ask which dependencies to install
|
||||
if (thirdwebDepsToInstall.size !== 0) {
|
||||
const answer = await inquirer.prompt({
|
||||
type: "checkbox",
|
||||
choices: [...thirdwebDepsToInstall],
|
||||
message: "Which thirdweb dependencies would you like to install?",
|
||||
name: "dependencies",
|
||||
default: [...thirdwebDepsToInstall],
|
||||
});
|
||||
|
||||
if (answer.dependencies.length !== thirdwebDepsToInstall.size) {
|
||||
thirdwebDepsToInstall.clear();
|
||||
answer.dependencies.forEach((dep: string) => thirdwebDepsToInstall.add(dep));
|
||||
}
|
||||
}
|
||||
|
||||
// Ask which dependencies to update
|
||||
if (thirdwebDepsToUpdate.size !== 0) {
|
||||
const answer = await inquirer.prompt({
|
||||
type: "checkbox",
|
||||
choices: [...thirdwebDepsToUpdate],
|
||||
message: "Which thirdweb dependencies would you like to update?",
|
||||
name: "dependencies",
|
||||
default: [...thirdwebDepsToUpdate],
|
||||
});
|
||||
|
||||
if (answer.dependencies.length !== thirdwebDepsToUpdate.size) {
|
||||
thirdwebDepsToUpdate.clear();
|
||||
answer.dependencies.forEach((dep: string) => thirdwebDepsToUpdate.add(dep));
|
||||
}
|
||||
}
|
||||
|
||||
if (thirdwebDepsToInstall.size === 0 && thirdwebDepsToUpdate.size === 0) {
|
||||
console.log("No thirdweb dependencies to install or update.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (debug) {
|
||||
logger.info("Detected package manager: " + detectedPackageManager);
|
||||
logger.info("Detected library: " + detectedLibrary);
|
||||
logger.info("Detected framework: " + detectedFramework);
|
||||
logger.info("Detected app type: " + detectedAppType);
|
||||
}
|
||||
|
||||
try {
|
||||
const dependenciesToAdd = [
|
||||
...[...thirdwebDepsToInstall].map((dep) => `${dep}${version}`),
|
||||
...otherDeps,
|
||||
];
|
||||
|
||||
const dependenciesToUpdate = [...thirdwebDepsToUpdate].map(
|
||||
(dep) => `${dep}${version}`,
|
||||
)
|
||||
|
||||
if (thirdwebDepsToInstall.size !== 0) {
|
||||
installer = ora(
|
||||
`Installing: "${[...thirdwebDepsToInstall].join('", "')}"`,
|
||||
).start();
|
||||
await installOrUpdate(detectedPackageManager, dependenciesToAdd, [], "install", { oldVersion: thirdwebExistsInBrownie.package, debug });
|
||||
installer?.succeed(`Installed: "${dependenciesToAdd.join('", "')}"`);
|
||||
}
|
||||
|
||||
if (thirdwebDepsToUpdate.size !== 0) {
|
||||
updater = ora(
|
||||
`Updating: "${[...thirdwebDepsToUpdate].join('", "')}"`,
|
||||
).start();
|
||||
await installOrUpdate(detectedPackageManager, [], dependenciesToUpdate, "update", {
|
||||
debug,
|
||||
});
|
||||
updater?.succeed(`Updated: "${dependenciesToUpdate.join('", "')}"`);
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
console.error("Can't install within project: ", err);
|
||||
return Promise.reject("Can't install within project");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { FrameworkType, LibraryType, PackageManagerType } from "../core/types/ProjectType";
|
||||
|
||||
export type IProcessContractAppTypeArgs = {
|
||||
detectedPackageManager: PackageManagerType;
|
||||
thirdwebDepsToUpdate: Set<string>;
|
||||
thirdwebDepsToInstall: Set<string>;
|
||||
isJSPackageManager: boolean;
|
||||
}
|
||||
|
||||
export type IProcessAppTypeArgs = {
|
||||
detectedLibrary: LibraryType;
|
||||
detectedFramework: FrameworkType;
|
||||
thirdwebDepsToUpdate: Set<string>;
|
||||
thirdwebDepsToInstall: Set<string>;
|
||||
isJSPackageManager: boolean;
|
||||
isPythonPackageManager: boolean;
|
||||
isGoPackageManager: boolean;
|
||||
hasEthers: boolean;
|
||||
otherDeps: Set<string>;
|
||||
}
|
||||
+276
-36
@@ -1,14 +1,40 @@
|
||||
import * as toml from "@iarna/toml";
|
||||
import * as yaml from "js-yaml";
|
||||
|
||||
import { PackageManagerType } from "../core/types/ProjectType";
|
||||
import { existsSync, readFileSync } from "fs";
|
||||
import { runCommand } from "../create/helpers/run-command";
|
||||
import path from "path";
|
||||
import { IProcessAppTypeArgs, IProcessContractAppTypeArgs } from "./types";
|
||||
|
||||
export const parsePackageJson = (packageJson: Buffer) => {
|
||||
export function convertDependenciesToStringFormat(dependenciesObject: object) {
|
||||
return Object.entries(dependenciesObject).map(([pkg, versionDetails]) => {
|
||||
let version = '';
|
||||
if (typeof versionDetails === 'string') {
|
||||
version = versionDetails;
|
||||
} else if (typeof versionDetails === 'object') {
|
||||
version = versionDetails.version;
|
||||
}
|
||||
// Replace '^' with nothing as '^' is not a valid version specifier in Python
|
||||
version = version.replace('^', '');
|
||||
return `${pkg}==${version}`;
|
||||
});
|
||||
}
|
||||
|
||||
export const parsePackageJson = (packageJson: Buffer): {
|
||||
dependencies: {
|
||||
[key: string]: string;
|
||||
};
|
||||
devDependencies: {
|
||||
[key: string]: string;
|
||||
};
|
||||
peerDependencies: {
|
||||
[key: string]: string;
|
||||
};
|
||||
} => {
|
||||
const packageJsonContent = JSON.parse(packageJson.toString());
|
||||
const dependencies = packageJsonContent.dependencies || {};
|
||||
const devDependencies = packageJsonContent.devDependencies || {};
|
||||
return { dependencies, devDependencies };
|
||||
const peerDependencies = packageJsonContent.peerDependencies || {};
|
||||
return { dependencies, devDependencies, peerDependencies };
|
||||
};
|
||||
|
||||
export const parseRequirementsTxt = (requirementsTxt: Buffer) => {
|
||||
@@ -30,20 +56,13 @@ export const parsePipFile = (PipFile: string) => {
|
||||
export const parsePyProjectToml = (pyProjectToml: string) => {
|
||||
const pyProjectFile = toml.parse(pyProjectToml) as any;
|
||||
const dependencies =
|
||||
(pyProjectFile["tool"]["poetry"]["dependencies"] as string[]) || [];
|
||||
(pyProjectFile["tool"]["poetry"]["dependencies"]) || [];
|
||||
const devDependencies =
|
||||
(pyProjectFile["tool"]["poetry"]["dev-dependencies"] as string[]) || [];
|
||||
return {
|
||||
dependencies,
|
||||
devDependencies,
|
||||
};
|
||||
};
|
||||
(pyProjectFile["tool"]["poetry"]["dev-dependencies"]) || [];
|
||||
|
||||
export const parseCondaYml = (condaYml: string) => {
|
||||
const conda = yaml.load(condaYml) as any;
|
||||
const dependencies = (conda.dependencies as string[]) || [];
|
||||
return {
|
||||
dependencies,
|
||||
dependencies: convertDependenciesToStringFormat(dependencies),
|
||||
devDependencies: convertDependenciesToStringFormat(devDependencies),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -82,44 +101,43 @@ export const fileContainsImport = (file: string, importToCheck: string) => {
|
||||
};
|
||||
|
||||
export const getDependenciesForPython = (
|
||||
path: string,
|
||||
filePath: string,
|
||||
packageManager: PackageManagerType,
|
||||
) => {
|
||||
let dependencies: string[] = [];
|
||||
let devDependencies: string[] = [];
|
||||
|
||||
switch (packageManager) {
|
||||
case "pip" || "pipenv":
|
||||
const foundPipFile = existsSync(path + "Pipfile");
|
||||
if (foundPipFile) {
|
||||
const pipFile = readFileSync(path + "Pipfile", "utf-8");
|
||||
const parsedPipFile = parsePipFile(pipFile);
|
||||
dependencies = parsedPipFile.dependencies;
|
||||
devDependencies = parsedPipFile.devDependencies;
|
||||
break;
|
||||
}
|
||||
const foundRequirementsTxt = existsSync(path + "/requirements.txt");
|
||||
case "pip":
|
||||
const foundRequirementsTxt = existsSync(filePath + "/requirements.txt");
|
||||
if (!foundRequirementsTxt) {
|
||||
// Create a temporary requirements.txt file
|
||||
// runCommand("pip", ["freeze"], true, undefined, "requirements.txt");
|
||||
break;
|
||||
}
|
||||
const requirementsTxt = readFileSync(path + "/requirements.txt");
|
||||
const requirementsTxt = readFileSync(filePath + "/requirements.txt");
|
||||
dependencies = parseRequirementsTxt(requirementsTxt).dependencies;
|
||||
// Delete file now that we're done with it
|
||||
// runCommand("rm", ["requirements.txt"], true);
|
||||
break;
|
||||
case "pipenv":
|
||||
const foundPipFile = existsSync(filePath + "Pipfile");
|
||||
if (!foundPipFile) {
|
||||
break;
|
||||
}
|
||||
const pipFile = readFileSync(filePath + "Pipfile", "utf-8");
|
||||
const parsedPipFile = parsePipFile(pipFile);
|
||||
dependencies = parsedPipFile.dependencies;
|
||||
devDependencies = parsedPipFile.devDependencies;
|
||||
break;
|
||||
case "poetry":
|
||||
const foundPyProjectToml = existsSync(path + "/pyproject.toml");
|
||||
const foundPyProjectToml = existsSync(filePath + "/pyproject.toml");
|
||||
if (!foundPyProjectToml) {
|
||||
break;
|
||||
}
|
||||
const pyProjectFile = readFileSync(path + "/pyproject.toml", "utf-8");
|
||||
const pyProjectFile = readFileSync(filePath + "/pyproject.toml", "utf-8");
|
||||
dependencies = parsePyProjectToml(pyProjectFile).dependencies;
|
||||
break;
|
||||
case "conda":
|
||||
const foundCondaYml = existsSync(path + "/environment.yml");
|
||||
if (!foundCondaYml) {
|
||||
break;
|
||||
}
|
||||
const condaYml = readFileSync(path + "/environment.yml", "utf-8");
|
||||
dependencies = parseCondaYml(condaYml).dependencies;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -129,3 +147,225 @@ export const getDependenciesForPython = (
|
||||
devDependencies,
|
||||
};
|
||||
};
|
||||
|
||||
export const getLatestVersion = async (packageName: string): Promise<any> => {
|
||||
try {
|
||||
const fetch = (await import('node-fetch')).default;
|
||||
const url = `https://registry.npmjs.org/${packageName}`
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
const data: any = await response.json();
|
||||
return data['dist-tags']['latest'];
|
||||
} catch (error) {
|
||||
console.error(`Failed to get latest version of ${packageName}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
export const installOrUpdate = async (packageManager: PackageManagerType, dependenciesToAdd: string[], dependenciesToUpdate: string[], typeOfAction: "install" | "update", options?: { oldVersion?: string, debug?: boolean }) => {
|
||||
let runner: string = "";
|
||||
let installCommand: string[] = [];
|
||||
let updateCommand: string[] = [];
|
||||
let deleteCommand: string[] = [];
|
||||
let printLogs = options?.debug || false;
|
||||
|
||||
switch (packageManager) {
|
||||
case "npm":
|
||||
runner = "npm";
|
||||
installCommand = ["install"];
|
||||
updateCommand = ["update"]
|
||||
break;
|
||||
case "yarn":
|
||||
runner = "yarn";
|
||||
installCommand = ["add"];
|
||||
updateCommand = ["add"];
|
||||
break;
|
||||
case "pnpm":
|
||||
runner = "pnpm";
|
||||
installCommand = ["install"];
|
||||
updateCommand = ["update"];
|
||||
break;
|
||||
case "pip":
|
||||
runner = "pip";
|
||||
installCommand = ["install"];
|
||||
updateCommand = ["install", "--upgrade"];
|
||||
break;
|
||||
case "pipenv":
|
||||
runner = "pipenv";
|
||||
installCommand = ["install"];
|
||||
updateCommand = ["update"];
|
||||
break;
|
||||
case "poetry":
|
||||
runner = "poetry";
|
||||
installCommand = ["add"];
|
||||
updateCommand = ["update"];
|
||||
break;
|
||||
case "go-modules":
|
||||
runner = "go";
|
||||
installCommand = ["get"];
|
||||
updateCommand = ["get", "-u"];
|
||||
break;
|
||||
case "brownie":
|
||||
runner = "brownie";
|
||||
installCommand = ["pm", "install"]
|
||||
updateCommand = ["pm", "update"]
|
||||
deleteCommand = ["pm", "delete"]
|
||||
break;
|
||||
case "foundry":
|
||||
runner = "forge";
|
||||
installCommand = ["install", "--no-commit"];
|
||||
updateCommand = ["update"];
|
||||
deleteCommand = ["remove"];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (typeOfAction === "install") {
|
||||
if (!dependenciesToAdd.length) {return;}
|
||||
const commands = [...installCommand, ...dependenciesToAdd];
|
||||
|
||||
await runCommand(runner, commands, printLogs);
|
||||
|
||||
if (options?.oldVersion) {
|
||||
await runCommand(runner, [...deleteCommand, options.oldVersion], printLogs);
|
||||
}
|
||||
}
|
||||
if (typeOfAction === "update") {
|
||||
if (!dependenciesToUpdate.length) {return;}
|
||||
const commands = [...updateCommand, ...dependenciesToUpdate];
|
||||
await runCommand(runner, commands, printLogs);
|
||||
}
|
||||
};
|
||||
|
||||
export const checkIfBrowniePackageIsInstalled = async (regex: RegExp) => {
|
||||
const deps = await getDependenciesForBrownie();
|
||||
const match = deps.match(regex);
|
||||
if (match !== null) {
|
||||
const packageAndVersion = match[0];
|
||||
return {
|
||||
package: packageAndVersion,
|
||||
exists: true,
|
||||
};
|
||||
}
|
||||
return {
|
||||
package: "",
|
||||
exists: false,
|
||||
};
|
||||
};
|
||||
|
||||
export const getDependenciesForBrownie = async () => {
|
||||
let deps = "";
|
||||
await runCommand("brownie", ["pm", "list"], true, (data) => {
|
||||
deps += data.toString();
|
||||
})
|
||||
// strip out colors from output
|
||||
deps = deps.replace(/\u001b\[\d+(;\d+)*m/g, '');
|
||||
return deps;
|
||||
};
|
||||
|
||||
export const getDependenciesForFoundry = async (filePath: string, libName: string) => {
|
||||
const fullPath = path.join(filePath, "lib", libName);
|
||||
return existsSync(fullPath);
|
||||
};
|
||||
|
||||
type GoModule = {
|
||||
name: string;
|
||||
version: string;
|
||||
};
|
||||
|
||||
|
||||
export const getDependenciesForGo = (filePath: string): GoModule[] => {
|
||||
const content = readFileSync(filePath + "/go.mod", "utf-8");
|
||||
const lines = content.split("\n");
|
||||
const modules: GoModule[] = [];
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.includes('github.com/thirdweb-dev')) {
|
||||
const words = line.split(/\s+/); // split on any amount of whitespace
|
||||
const name = words[1].trim();
|
||||
const version = words[2];
|
||||
modules.push({ name, version });
|
||||
}
|
||||
}
|
||||
|
||||
return modules;
|
||||
}
|
||||
|
||||
export const processContractAppType = async (args: IProcessContractAppTypeArgs): Promise<void> => {
|
||||
const { detectedPackageManager, thirdwebDepsToUpdate, thirdwebDepsToInstall, isJSPackageManager } = args;
|
||||
|
||||
if (isJSPackageManager) {
|
||||
if (!thirdwebDepsToUpdate.has(`@thirdweb-dev/contracts`)) {
|
||||
thirdwebDepsToInstall.add(`@thirdweb-dev/contracts`);
|
||||
}
|
||||
}
|
||||
|
||||
if (detectedPackageManager === "brownie") {
|
||||
const latestThirdwebContractVersion = await getLatestVersion("@thirdweb-dev/contracts");
|
||||
|
||||
// Exit out early if the user already has the latest version of thirdweb installed.
|
||||
if (thirdwebDepsToUpdate.has(`thirdweb-dev/contracts@${latestThirdwebContractVersion}`)) {
|
||||
return Promise.reject("You already have the latest version of thirdweb-dev/contracts installed.");
|
||||
}
|
||||
|
||||
if (!thirdwebDepsToUpdate.has(`thirdweb-dev/contracts@${latestThirdwebContractVersion}`)) {
|
||||
thirdwebDepsToInstall.add(`thirdweb-dev/contracts@${latestThirdwebContractVersion}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (detectedPackageManager === "foundry") {
|
||||
if (!thirdwebDepsToUpdate.has("lib/thirdweb-contracts")) {
|
||||
thirdwebDepsToInstall.add("thirdweb-contracts=thirdweb-dev/contracts");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const processAppType = async (args: IProcessAppTypeArgs): Promise<void> => {
|
||||
const { detectedFramework, thirdwebDepsToUpdate, thirdwebDepsToInstall, hasEthers, isJSPackageManager, otherDeps, isPythonPackageManager, isGoPackageManager, detectedLibrary } = args;
|
||||
|
||||
const reactlibs = [
|
||||
"react",
|
||||
"react-native",
|
||||
"next",
|
||||
"gatsby",
|
||||
"cra",
|
||||
"remix",
|
||||
"vue",
|
||||
"vite",
|
||||
"svelte",
|
||||
]
|
||||
const shouldInstallThirdwebReact = reactlibs.includes(detectedFramework as string);
|
||||
const shouldInstallThirdwebReactNative = detectedLibrary === "react-native";
|
||||
|
||||
if (isJSPackageManager) {
|
||||
if (!thirdwebDepsToUpdate.has(`@thirdweb-dev/react`) && shouldInstallThirdwebReact) {
|
||||
thirdwebDepsToInstall.add(`@thirdweb-dev/react`);
|
||||
}
|
||||
if (!thirdwebDepsToUpdate.has(`@thirdweb-dev/sdk`) && !shouldInstallThirdwebReactNative) {
|
||||
thirdwebDepsToInstall.add(`@thirdweb-dev/sdk`);
|
||||
}
|
||||
if (!thirdwebDepsToUpdate.has(`@thirdweb-dev/react-native`) && shouldInstallThirdwebReactNative) {
|
||||
thirdwebDepsToInstall.add(`@thirdweb-dev/react-native`);
|
||||
}
|
||||
if (!thirdwebDepsToUpdate.has(`@thirdweb-dev/react-native-compat`) && shouldInstallThirdwebReactNative) {
|
||||
thirdwebDepsToInstall.add(`@thirdweb-dev/react-native-compat`);
|
||||
}
|
||||
if (!hasEthers) {
|
||||
otherDeps.add("ethers@5");
|
||||
}
|
||||
}
|
||||
|
||||
if (isPythonPackageManager) {
|
||||
if (!thirdwebDepsToUpdate.has(`thirdweb-sdk`)) {
|
||||
thirdwebDepsToInstall.add(`thirdweb-sdk`);
|
||||
}
|
||||
}
|
||||
|
||||
if (isGoPackageManager) {
|
||||
if (!thirdwebDepsToUpdate.has("github.com/thirdweb-dev/go-sdk/v2")) {
|
||||
thirdwebDepsToInstall.add("github.com/thirdweb-dev/go-sdk/v2");
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,5 +1,22 @@
|
||||
# @thirdweb-dev/react-core
|
||||
|
||||
## 3.14.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1187](https://github.com/thirdweb-dev/js/pull/1187) [`08403dd9`](https://github.com/thirdweb-dev/js/commit/08403dd9da7284054173aaba6ef06ac39f560c08) Thanks [@adam-maj](https://github.com/adam-maj)! - Expose useSwitchAccount
|
||||
|
||||
- [#1150](https://github.com/thirdweb-dev/js/pull/1150) [`d60e544a`](https://github.com/thirdweb-dev/js/commit/d60e544a240b93312743e62096ee2dc77d0c1bd1) Thanks [@adam-maj](https://github.com/adam-maj)! - Add support for multipled logged in accounts and token refreshing to auth
|
||||
|
||||
- [#1165](https://github.com/thirdweb-dev/js/pull/1165) [`088f2567`](https://github.com/thirdweb-dev/js/commit/088f2567f615360edd44776b20ae0bedff250f43) Thanks [@adam-maj](https://github.com/adam-maj)! - Include chain id in login payload
|
||||
|
||||
- [#1186](https://github.com/thirdweb-dev/js/pull/1186) [`36f3191f`](https://github.com/thirdweb-dev/js/commit/36f3191f3e8819c878685de8caa393b71be8e65c) Thanks [@nachoiacovino](https://github.com/nachoiacovino)! - Fix useIsSmartWalletDeployed cache
|
||||
|
||||
- Updated dependencies [[`cc20f93e`](https://github.com/thirdweb-dev/js/commit/cc20f93e178d86ee2b3f39102bbb0811de211f05), [`654b8ab3`](https://github.com/thirdweb-dev/js/commit/654b8ab35184e02127a5a47f05d78606dd5b29ca), [`a430160e`](https://github.com/thirdweb-dev/js/commit/a430160e6b4771c03d97f6ede91f1aeaa043e50e), [`12b07aad`](https://github.com/thirdweb-dev/js/commit/12b07aad9ae3176daf9d05864247d4806a16c9d2), [`7d2a446e`](https://github.com/thirdweb-dev/js/commit/7d2a446ecef9c6c14959d31e9a66537783b9adac), [`8e28b0f5`](https://github.com/thirdweb-dev/js/commit/8e28b0f5e75596d29273ed80269bcee6d209adb4), [`01293857`](https://github.com/thirdweb-dev/js/commit/01293857fac8531bd94764203cd24b3daa4db51f), [`329bccec`](https://github.com/thirdweb-dev/js/commit/329bccec4b88b02db5bac6c1415158928843376a), [`20bbad1a`](https://github.com/thirdweb-dev/js/commit/20bbad1abcb1ec573318d326b09278492a488abd)]:
|
||||
- @thirdweb-dev/chains@0.1.21
|
||||
- @thirdweb-dev/wallets@0.3.0
|
||||
- @thirdweb-dev/sdk@3.10.22
|
||||
|
||||
## 3.13.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/react-core",
|
||||
"version": "3.13.1",
|
||||
"version": "3.14.0",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/react-core",
|
||||
"author": "thirdweb eng <[email protected]>",
|
||||
"license": "Apache-2.0",
|
||||
@@ -79,11 +79,12 @@
|
||||
"@solana/web3.js": "^1.62.0",
|
||||
"@thirdweb-dev/auth": "workspace:*",
|
||||
"@thirdweb-dev/tsconfig": "workspace:*",
|
||||
"@types/js-cookie": "^3.0.3",
|
||||
"@types/mime": "3.0.0",
|
||||
"@types/react": "^18.0.5",
|
||||
"@typescript-eslint/eslint-plugin": "^5.33.0",
|
||||
"@typescript-eslint/parser": "^5.33.0",
|
||||
"eslint": "^8.21.0",
|
||||
"eslint": "^8.42.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-config-thirdweb": "workspace:*",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
import React, { PropsWithChildren, useEffect, useMemo } from "react";
|
||||
import { useLogout, useUser } from "../hooks/auth";
|
||||
import {
|
||||
ThirdwebAuthConfig,
|
||||
ThirdwebAuthContext,
|
||||
useThirdwebAuthContext,
|
||||
} from "./thirdweb-auth";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { useWallet } from "../../core/hooks/wallet-hooks";
|
||||
import { ThirdwebAuthConfig, ThirdwebAuthContext } from "./thirdweb-auth";
|
||||
import { WalletData } from "@thirdweb-dev/wallets";
|
||||
import { useLogout } from "../hooks/auth";
|
||||
import { useSwitchAccount } from "../hooks/auth/useSwitchAccount";
|
||||
|
||||
export const ThirdwebAuthProvider: React.FC<
|
||||
PropsWithChildren<{ value?: ThirdwebAuthConfig }>
|
||||
@@ -23,35 +30,59 @@ export const ThirdwebAuthProvider: React.FC<
|
||||
return (
|
||||
<ThirdwebAuthContext.Provider value={authContext}>
|
||||
{children}
|
||||
<DisconnectOnAccountSwitch />
|
||||
<ChangeActiveWalletOnAccountSwitch />
|
||||
</ThirdwebAuthContext.Provider>
|
||||
);
|
||||
};
|
||||
function DisconnectOnAccountSwitch() {
|
||||
const { logout } = useLogout();
|
||||
const wallet = useWallet();
|
||||
const { isLoggedIn } = useUser();
|
||||
|
||||
function ChangeActiveWalletOnAccountSwitch() {
|
||||
const wallet = useWallet();
|
||||
const { logout } = useLogout();
|
||||
const { switchAccount } = useSwitchAccount();
|
||||
const authConfig = useThirdwebAuthContext();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
// When active wallet switches, switch the active account cookie and invalidate user query
|
||||
useEffect(() => {
|
||||
const handleChange = (data: { address?: string; chainId?: number }) => {
|
||||
// if the user changes their account, logout
|
||||
if (data.address) {
|
||||
logout();
|
||||
const handleSwitchAccount = async (data: WalletData) => {
|
||||
if (!data.address) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await switchAccount(data.address);
|
||||
} catch (err) {
|
||||
console.warn(
|
||||
`[Auth] Failed to switch account to ${data.address} with error:\n`,
|
||||
err,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const shouldAddListener = wallet && isLoggedIn;
|
||||
const handleLogout = async () => {
|
||||
try {
|
||||
await logout();
|
||||
} catch (err) {
|
||||
console.warn(`[Auth] Failed to logout with error:\n`, err);
|
||||
}
|
||||
};
|
||||
|
||||
const shouldAddListener = !!wallet && authConfig && authConfig.authUrl;
|
||||
|
||||
if (shouldAddListener) {
|
||||
wallet.addListener("change", handleChange);
|
||||
wallet.addListener("connect", handleSwitchAccount);
|
||||
wallet.addListener("change", handleSwitchAccount);
|
||||
wallet.addListener("disconnect", handleLogout);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (shouldAddListener) {
|
||||
wallet.removeListener("change", handleChange);
|
||||
wallet.removeListener("connect", handleSwitchAccount);
|
||||
wallet.removeListener("change", handleSwitchAccount);
|
||||
wallet.removeListener("disconnect", handleLogout);
|
||||
}
|
||||
};
|
||||
}, [wallet, logout, isLoggedIn]);
|
||||
}, [wallet, queryClient, authConfig, logout, switchAccount]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ export function useIsSmartWalletDeployed(
|
||||
): UseQueryResult<boolean> {
|
||||
const contractAddress = contract?.getAddress();
|
||||
return useQueryWithNetwork(
|
||||
cacheKeys.contract.smartWalletFactory.getAll(contractAddress),
|
||||
cacheKeys.contract.smartWalletFactory.isSmartWalletDeployed(contractAddress, admin),
|
||||
() => {
|
||||
requiredParamInvariant(contract, "No Contract instance provided");
|
||||
invariant(
|
||||
|
||||
@@ -8,6 +8,7 @@ export { useLogin, doLogin } from "./useLogin";
|
||||
export { useLogout } from "./useLogout";
|
||||
export type { UserWithData } from "./useUser";
|
||||
export { useUser } from "./useUser";
|
||||
export { useSwitchAccount } from "./useSwitchAccount";
|
||||
|
||||
export function useAuth() {
|
||||
const wallet = useWallet();
|
||||
|
||||
@@ -39,12 +39,13 @@ export function useLogin() {
|
||||
);
|
||||
|
||||
const address = await wallet.getAddress();
|
||||
const chainId = await wallet.getChainId();
|
||||
let res = await fetch(`${authConfig.authUrl}/payload`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ address }),
|
||||
body: JSON.stringify({ address, chainId: chainId.toString() }),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { useThirdwebAuthContext } from "../../contexts/thirdweb-auth";
|
||||
import { cacheKeys } from "../../utils/cache-keys";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import invariant from "tiny-invariant";
|
||||
|
||||
/**
|
||||
* Hook to switch the account of the active wallet
|
||||
*
|
||||
* @returns - A function to invoke to switch account.
|
||||
*
|
||||
* @see {@link https://portal.thirdweb.com/react/react.useswitchaccount?utm_source=sdk | Documentation}
|
||||
*
|
||||
* @beta
|
||||
*/
|
||||
export function useSwitchAccount() {
|
||||
const queryClient = useQueryClient();
|
||||
const authConfig = useThirdwebAuthContext();
|
||||
|
||||
const switchAccount = useMutation({
|
||||
mutationFn: async (address: string) => {
|
||||
invariant(
|
||||
authConfig,
|
||||
"Please specify an authConfig in the ThirdwebProvider",
|
||||
);
|
||||
invariant(
|
||||
authConfig.authUrl,
|
||||
"Please specify an authUrl in the authConfig.",
|
||||
);
|
||||
|
||||
await fetch(`${authConfig.authUrl}/switch-account`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
address,
|
||||
}),
|
||||
});
|
||||
|
||||
queryClient.invalidateQueries(cacheKeys.auth.user());
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
switchAccount: switchAccount.mutateAsync,
|
||||
isLoading: switchAccount.isLoading,
|
||||
};
|
||||
}
|
||||
@@ -202,7 +202,13 @@ export {
|
||||
// thirdweb hooks (work as long as at least `<ThirdwebSdkProvider>` is used)
|
||||
|
||||
// auth hooks
|
||||
export { useLogin, useLogout, useUser, useAuth } from "./hooks/auth";
|
||||
export {
|
||||
useLogin,
|
||||
useLogout,
|
||||
useUser,
|
||||
useAuth,
|
||||
useSwitchAccount,
|
||||
} from "./hooks/auth";
|
||||
export type { UserWithData } from "./hooks/auth";
|
||||
|
||||
// contract hooks
|
||||
|
||||
@@ -121,6 +121,8 @@ export const cacheKeys = {
|
||||
smartWalletFactory: {
|
||||
getAll: (contractAddress: RequiredParam<ContractAddress>) =>
|
||||
createContractCacheKey(contractAddress, ["smartWalletFactory"]),
|
||||
isSmartWalletDeployed: (contractAdress: RequiredParam<ContractAddress>, admin: RequiredParam<string>) =>
|
||||
createContractCacheKey(contractAdress, ["isSmartWalletDeployed", admin]),
|
||||
},
|
||||
|
||||
app: {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# @thirdweb-dev/react-native-compat
|
||||
|
||||
## 0.2.19
|
||||
|
||||
## 0.2.18
|
||||
|
||||
## 0.2.17
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/react-native-compat",
|
||||
"description": "Shims for Thirdweb in React Native Projects",
|
||||
"version": "0.2.18",
|
||||
"version": "0.2.19",
|
||||
"author": "thirdweb eng <[email protected]>",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/react-native-compat",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# @thirdweb-dev/react-native
|
||||
|
||||
## 0.2.19
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1182](https://github.com/thirdweb-dev/js/pull/1182) [`a0eee749`](https://github.com/thirdweb-dev/js/commit/a0eee749a27a8b288b20533d8bd60423f81da463) Thanks [@iketw](https://github.com/iketw)! - [RN] Added the option to import a new wallet in guest mode
|
||||
|
||||
- Updated dependencies [[`cc20f93e`](https://github.com/thirdweb-dev/js/commit/cc20f93e178d86ee2b3f39102bbb0811de211f05), [`08403dd9`](https://github.com/thirdweb-dev/js/commit/08403dd9da7284054173aaba6ef06ac39f560c08), [`654b8ab3`](https://github.com/thirdweb-dev/js/commit/654b8ab35184e02127a5a47f05d78606dd5b29ca), [`a430160e`](https://github.com/thirdweb-dev/js/commit/a430160e6b4771c03d97f6ede91f1aeaa043e50e), [`12b07aad`](https://github.com/thirdweb-dev/js/commit/12b07aad9ae3176daf9d05864247d4806a16c9d2), [`d60e544a`](https://github.com/thirdweb-dev/js/commit/d60e544a240b93312743e62096ee2dc77d0c1bd1), [`7d2a446e`](https://github.com/thirdweb-dev/js/commit/7d2a446ecef9c6c14959d31e9a66537783b9adac), [`088f2567`](https://github.com/thirdweb-dev/js/commit/088f2567f615360edd44776b20ae0bedff250f43), [`8e28b0f5`](https://github.com/thirdweb-dev/js/commit/8e28b0f5e75596d29273ed80269bcee6d209adb4), [`01293857`](https://github.com/thirdweb-dev/js/commit/01293857fac8531bd94764203cd24b3daa4db51f), [`329bccec`](https://github.com/thirdweb-dev/js/commit/329bccec4b88b02db5bac6c1415158928843376a), [`20bbad1a`](https://github.com/thirdweb-dev/js/commit/20bbad1abcb1ec573318d326b09278492a488abd), [`36f3191f`](https://github.com/thirdweb-dev/js/commit/36f3191f3e8819c878685de8caa393b71be8e65c)]:
|
||||
- @thirdweb-dev/chains@0.1.21
|
||||
- @thirdweb-dev/react-core@3.14.0
|
||||
- @thirdweb-dev/wallets@0.3.0
|
||||
- @thirdweb-dev/sdk@3.10.22
|
||||
|
||||
## 0.2.18
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/react-native",
|
||||
"version": "0.2.18",
|
||||
"version": "0.2.19",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/react-native",
|
||||
"author": "thirdweb eng <[email protected]>",
|
||||
"license": "Apache-2.0",
|
||||
@@ -60,7 +60,7 @@
|
||||
"@typescript-eslint/eslint-plugin": "^5.56.0",
|
||||
"@typescript-eslint/parser": "^5.56.0",
|
||||
"babel-jest": "^29.2.1",
|
||||
"eslint": "^8.21.0",
|
||||
"eslint": "^8.42.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-config-thirdweb": "workspace:*",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
|
||||
+51
@@ -7,6 +7,7 @@ import { WalletDetailsModalHeader } from "./WalletDetailsModalHeader";
|
||||
import {
|
||||
useActiveChain,
|
||||
useDisconnect,
|
||||
useSetConnectedWallet,
|
||||
useWallet,
|
||||
} from "@thirdweb-dev/react-core";
|
||||
import { SmartWallet, walletIds, LocalWallet } from "@thirdweb-dev/wallets";
|
||||
@@ -20,6 +21,7 @@ import Text from "../base/Text";
|
||||
import { useModalState } from "../../providers/ui-context-provider";
|
||||
import { CLOSE_MODAL_STATE, WalletDetailsModal } from "../../utils/modalTypes";
|
||||
import { useAppTheme } from "../../styles/hooks";
|
||||
import { LocalWalletImportModal } from "../ConnectWalletFlow/LocalWalletImportModal";
|
||||
|
||||
export const ConnectWalletDetailsModal = () => {
|
||||
const theme = useAppTheme();
|
||||
@@ -29,8 +31,10 @@ export const ConnectWalletDetailsModal = () => {
|
||||
const disconnect = useDisconnect();
|
||||
const [smartWallet, setSmartWallet] = useSmartWallet();
|
||||
const [addressCopied, setAddressCopied] = useState(false);
|
||||
const [isImportModalVisible, setIsImportModalVisible] = useState(false);
|
||||
const { modalState, setModalState } = useModalState();
|
||||
const { address } = (modalState as WalletDetailsModal).data;
|
||||
const setConnectedWallet = useSetConnectedWallet();
|
||||
|
||||
useEffect(() => {
|
||||
const timeout = setTimeout(() => {
|
||||
@@ -67,6 +71,24 @@ export const ConnectWalletDetailsModal = () => {
|
||||
setAddressCopied(true);
|
||||
};
|
||||
|
||||
const onImportLocalWalletPress = () => {
|
||||
setIsImportModalVisible(true);
|
||||
};
|
||||
|
||||
const onImportModalClose = () => {
|
||||
setIsImportModalVisible(false);
|
||||
};
|
||||
|
||||
const onWalletImported = useCallback(
|
||||
(localWalletInstance: LocalWallet) => {
|
||||
activeWallet?.disconnect();
|
||||
localWalletInstance.connect();
|
||||
|
||||
setConnectedWallet?.(localWalletInstance);
|
||||
},
|
||||
[activeWallet, setConnectedWallet],
|
||||
);
|
||||
|
||||
const getAdditionalActions = useCallback(() => {
|
||||
if (activeWallet?.walletId === SmartWallet.id || smartWallet) {
|
||||
return (
|
||||
@@ -103,6 +125,33 @@ export const ConnectWalletDetailsModal = () => {
|
||||
/>
|
||||
</BaseButton>
|
||||
|
||||
<BaseButton
|
||||
backgroundColor="background"
|
||||
borderColor="border"
|
||||
mb="sm"
|
||||
justifyContent="space-between"
|
||||
style={styles.exportWallet}
|
||||
onPress={onImportLocalWalletPress}
|
||||
>
|
||||
<>
|
||||
<PocketWalletIcon size={16} />
|
||||
<View style={styles.exportWalletInfo}>
|
||||
<Text variant="bodySmall">Import wallet</Text>
|
||||
</View>
|
||||
</>
|
||||
<RightArrowIcon
|
||||
height={10}
|
||||
width={10}
|
||||
color={theme.colors.iconPrimary}
|
||||
/>
|
||||
</BaseButton>
|
||||
|
||||
<LocalWalletImportModal
|
||||
isVisible={isImportModalVisible}
|
||||
onWalletImported={onWalletImported}
|
||||
onClose={onImportModalClose}
|
||||
/>
|
||||
|
||||
{activeWallet?.walletId === LocalWallet.id ? (
|
||||
<Text variant="error">
|
||||
{
|
||||
@@ -117,7 +166,9 @@ export const ConnectWalletDetailsModal = () => {
|
||||
return null;
|
||||
}, [
|
||||
activeWallet?.walletId,
|
||||
isImportModalVisible,
|
||||
onExportLocalWalletPress,
|
||||
onWalletImported,
|
||||
smartWallet,
|
||||
theme.colors.iconPrimary,
|
||||
]);
|
||||
|
||||
@@ -68,6 +68,7 @@ export function LocalWalletFlow({
|
||||
minWidth={130}
|
||||
onPress={onConnectPressInternal}
|
||||
style={styles.connectWalletButton}
|
||||
data-test="create-new-wallet-button"
|
||||
>
|
||||
{isCreatingWallet ? (
|
||||
<ActivityIndicator size="small" color="buttonTextColor" />
|
||||
@@ -85,7 +86,6 @@ export function LocalWalletFlow({
|
||||
|
||||
<LocalWalletImportModal
|
||||
isVisible={isImportModalVisible}
|
||||
localWallet={walletConfig}
|
||||
onWalletImported={connect}
|
||||
onClose={onImportModalClose}
|
||||
/>
|
||||
|
||||
+59
-18
@@ -10,54 +10,46 @@ import Box from "../base/Box";
|
||||
import { ModalHeaderTextClose } from "../base/modal/ModalHeaderTextClose";
|
||||
import { useCreateWalletInstance } from "@thirdweb-dev/react-core";
|
||||
import { PasswordInput } from "../PasswordInput";
|
||||
import type {
|
||||
LocalConfiguredWallet,
|
||||
LocalWalletInstance,
|
||||
} from "../../wallets/types/local-wallet";
|
||||
import type { LocalWalletInstance } from "../../wallets/types/local-wallet";
|
||||
import * as DocumentPicker from "expo-document-picker";
|
||||
import * as FileSystem from "expo-file-system";
|
||||
import { TWModal } from "../base/modal/TWModal";
|
||||
import { localWallet } from "../../wallets/wallets/local-wallet";
|
||||
import { TextInput } from "../base";
|
||||
|
||||
export type LocalWalletImportModalProps = {
|
||||
isVisible: boolean;
|
||||
onClose: () => void;
|
||||
localWallet: LocalConfiguredWallet;
|
||||
onWalletImported: (wallet: LocalWalletInstance) => void;
|
||||
};
|
||||
|
||||
export const LocalWalletImportModal = ({
|
||||
isVisible,
|
||||
onClose,
|
||||
localWallet,
|
||||
onWalletImported,
|
||||
}: LocalWalletImportModalProps) => {
|
||||
const [password, setPassword] = useState<string | undefined>();
|
||||
const [privateKey, setPrivateKey] = useState<string | undefined>(undefined);
|
||||
const [jsonFile, setJsonFile] = useState<string | undefined>();
|
||||
const [error, setError] = useState<string | undefined>();
|
||||
const [isImporting, setIsImporting] = useState<boolean>(false);
|
||||
const createWalletInstance = useCreateWalletInstance();
|
||||
|
||||
const onImportPress = async () => {
|
||||
setError(undefined);
|
||||
if (!password || !jsonFile) {
|
||||
setError("Please enter a password and select a file.");
|
||||
return;
|
||||
const createLocalWalletFromJsonFile = async () => {
|
||||
if (!jsonFile || !password) {
|
||||
return null;
|
||||
}
|
||||
|
||||
setIsImporting(true);
|
||||
|
||||
let json;
|
||||
try {
|
||||
json = await FileSystem.readAsStringAsync(jsonFile, {
|
||||
encoding: FileSystem.EncodingType.UTF8,
|
||||
});
|
||||
} catch (err) {
|
||||
setError("Error reading the file. Please try again.");
|
||||
setIsImporting(false);
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
const localWalletInstance = createWalletInstance(localWallet);
|
||||
const localWalletInstance = createWalletInstance(localWallet());
|
||||
|
||||
try {
|
||||
await localWalletInstance.import({
|
||||
@@ -66,7 +58,44 @@ export const LocalWalletImportModal = ({
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("Error importing the wallet", err);
|
||||
setError("Invalid password");
|
||||
return null;
|
||||
}
|
||||
|
||||
return localWalletInstance;
|
||||
};
|
||||
|
||||
const createLocalWalletPK = async () => {
|
||||
if (!privateKey) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const localWalletInstance = createWalletInstance(localWallet());
|
||||
|
||||
try {
|
||||
await localWalletInstance.import({
|
||||
privateKey: privateKey,
|
||||
encryption: false,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("Error importing the wallet", err);
|
||||
return null;
|
||||
}
|
||||
|
||||
return localWalletInstance;
|
||||
};
|
||||
|
||||
const onImportPress = async () => {
|
||||
setError(undefined);
|
||||
setIsImporting(true);
|
||||
|
||||
let localWalletInstance = await createLocalWalletFromJsonFile();
|
||||
|
||||
if (!localWalletInstance) {
|
||||
localWalletInstance = await createLocalWalletPK();
|
||||
}
|
||||
|
||||
if (!localWalletInstance) {
|
||||
setError("Please, double check your password or private key.");
|
||||
setIsImporting(false);
|
||||
return;
|
||||
}
|
||||
@@ -109,6 +138,10 @@ export const LocalWalletImportModal = ({
|
||||
}
|
||||
};
|
||||
|
||||
const onPrivateKeyEntered = (text: string) => {
|
||||
setPrivateKey(text);
|
||||
};
|
||||
|
||||
return (
|
||||
<TWModal isVisible={isVisible} backdropOpacity={0.7}>
|
||||
<KeyboardAvoidingView behavior="padding">
|
||||
@@ -146,6 +179,14 @@ export const LocalWalletImportModal = ({
|
||||
<Text variant="bodySmall" color="red" mt="xs" textAlign="left">
|
||||
{error}
|
||||
</Text>
|
||||
<Text variant="bodySmall" m="xs" textAlign="center">
|
||||
---- Or enter a private key ----
|
||||
</Text>
|
||||
<TextInput
|
||||
secureTextEntry={true}
|
||||
placeholder={"Private key"}
|
||||
onChangeText={onPrivateKeyEntered}
|
||||
/>
|
||||
<Box
|
||||
flexDirection="row"
|
||||
justifyContent="center"
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { utils } from "ethers";
|
||||
|
||||
/**
|
||||
* Shortens an EVM compatible address to show the first 6 and last 4 characters
|
||||
*
|
||||
@@ -14,3 +16,28 @@ export function shortenWalletAddress(str?: string, extraShort?: true) {
|
||||
str.length - (extraShort ? 3 : 4),
|
||||
)}`;
|
||||
}
|
||||
|
||||
export function shortenIfAddress(address?: string, extraShort?: true): string {
|
||||
if (!address) {
|
||||
return "";
|
||||
}
|
||||
if (isPossibleEVMAddress(address, true)) {
|
||||
return shortenWalletAddress(address, extraShort) || "";
|
||||
}
|
||||
return address;
|
||||
}
|
||||
|
||||
export function isEnsName(name: string): boolean {
|
||||
return name?.endsWith(".eth");
|
||||
}
|
||||
|
||||
// if a string is a valid address or ens name
|
||||
export function isPossibleEVMAddress(address?: string, ignoreEns?: boolean) {
|
||||
if (!address) {
|
||||
return false;
|
||||
}
|
||||
if (isEnsName(address) && !ignoreEns) {
|
||||
return true;
|
||||
}
|
||||
return utils.isAddress(address);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,42 @@
|
||||
# @thirdweb-dev/react
|
||||
|
||||
## 3.14.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#1160](https://github.com/thirdweb-dev/js/pull/1160) [`654b8ab3`](https://github.com/thirdweb-dev/js/commit/654b8ab35184e02127a5a47f05d78606dd5b29ca) Thanks [@zerts](https://github.com/zerts)! - Add Zerion Wallet
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1000](https://github.com/thirdweb-dev/js/pull/1000) [`329bccec`](https://github.com/thirdweb-dev/js/commit/329bccec4b88b02db5bac6c1415158928843376a) Thanks [@goosewobbler](https://github.com/goosewobbler)! - '[Wallets/React] Adds Frame as a supported wallet.'
|
||||
|
||||
You can now use Frame by adding `frameWallet()` in the ThirdwebProvider's `supportedWallets` prop.
|
||||
|
||||
```javascript
|
||||
import {
|
||||
ThirdwebProvider,
|
||||
localWallet,
|
||||
frameWallet,
|
||||
} from "@thirdweb-dev/react";
|
||||
|
||||
<ThirdwebProvider
|
||||
activeChain={activeChain}
|
||||
supportedWallets={[frameWallet(), localWallet()]}
|
||||
>
|
||||
<App />
|
||||
</ThirdwebProvider>;
|
||||
```
|
||||
|
||||
- [#1172](https://github.com/thirdweb-dev/js/pull/1172) [`ecdcf82c`](https://github.com/thirdweb-dev/js/commit/ecdcf82cf3ad4090b7c9da97a1a13e4987425b38) Thanks [@nachoiacovino](https://github.com/nachoiacovino)! - Add data-test to passworrd inputs
|
||||
|
||||
- [#1164](https://github.com/thirdweb-dev/js/pull/1164) [`a5578abf`](https://github.com/thirdweb-dev/js/commit/a5578abf664349bc608f3dcd9404ba103f7d8d43) Thanks [@kienngo98](https://github.com/kienngo98)! - reduce circular dependencies
|
||||
|
||||
- Updated dependencies [[`cc20f93e`](https://github.com/thirdweb-dev/js/commit/cc20f93e178d86ee2b3f39102bbb0811de211f05), [`08403dd9`](https://github.com/thirdweb-dev/js/commit/08403dd9da7284054173aaba6ef06ac39f560c08), [`654b8ab3`](https://github.com/thirdweb-dev/js/commit/654b8ab35184e02127a5a47f05d78606dd5b29ca), [`a430160e`](https://github.com/thirdweb-dev/js/commit/a430160e6b4771c03d97f6ede91f1aeaa043e50e), [`12b07aad`](https://github.com/thirdweb-dev/js/commit/12b07aad9ae3176daf9d05864247d4806a16c9d2), [`d60e544a`](https://github.com/thirdweb-dev/js/commit/d60e544a240b93312743e62096ee2dc77d0c1bd1), [`7d2a446e`](https://github.com/thirdweb-dev/js/commit/7d2a446ecef9c6c14959d31e9a66537783b9adac), [`088f2567`](https://github.com/thirdweb-dev/js/commit/088f2567f615360edd44776b20ae0bedff250f43), [`8e28b0f5`](https://github.com/thirdweb-dev/js/commit/8e28b0f5e75596d29273ed80269bcee6d209adb4), [`01293857`](https://github.com/thirdweb-dev/js/commit/01293857fac8531bd94764203cd24b3daa4db51f), [`329bccec`](https://github.com/thirdweb-dev/js/commit/329bccec4b88b02db5bac6c1415158928843376a), [`20bbad1a`](https://github.com/thirdweb-dev/js/commit/20bbad1abcb1ec573318d326b09278492a488abd), [`36f3191f`](https://github.com/thirdweb-dev/js/commit/36f3191f3e8819c878685de8caa393b71be8e65c)]:
|
||||
- @thirdweb-dev/chains@0.1.21
|
||||
- @thirdweb-dev/react-core@3.14.0
|
||||
- @thirdweb-dev/wallets@0.3.0
|
||||
- @thirdweb-dev/sdk@3.10.22
|
||||
|
||||
## 3.13.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/react",
|
||||
"version": "3.13.1",
|
||||
"version": "3.14.0",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/react",
|
||||
"author": "thirdweb eng <[email protected]>",
|
||||
"license": "Apache-2.0",
|
||||
@@ -104,7 +104,7 @@
|
||||
"@types/uuid": "^9.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.33.0",
|
||||
"@typescript-eslint/parser": "^5.33.0",
|
||||
"eslint": "^8.21.0",
|
||||
"eslint": "^8.42.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-config-thirdweb": "workspace:*",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Theme } from "../design-system";
|
||||
import type { Theme } from "../design-system";
|
||||
import styled from "@emotion/styled";
|
||||
import { Chain } from "@thirdweb-dev/chains";
|
||||
import { resolveIpfsUri } from "@thirdweb-dev/react-core";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Theme } from "../design-system";
|
||||
import type { Theme } from "../design-system";
|
||||
import { useClipboard } from "../evm/components/hooks/useCopyClipboard";
|
||||
import { ToolTip } from "./Tooltip";
|
||||
import styled from "@emotion/styled";
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Theme, fontSize, iconSize, radius, spacing } from "../design-system";
|
||||
import styled from "@emotion/styled";
|
||||
import { Spacer } from "./Spacer";
|
||||
import { isMobile } from "../evm/utils/isMobile";
|
||||
import { IconFC } from "../wallet/ConnectWallet/icons/types";
|
||||
import type { IconFC } from "../wallet/ConnectWallet/icons/types";
|
||||
|
||||
export const DragNDrop: React.FC<{
|
||||
extension: string;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Theme } from "../design-system";
|
||||
import type { Theme } from "../design-system";
|
||||
import { keyframes } from "@emotion/react";
|
||||
import styled from "@emotion/styled";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { fontSize, radius, shadow, spacing, Theme } from "../design-system";
|
||||
import { fontSize, radius, shadow, spacing, type Theme } from "../design-system";
|
||||
import { keyframes } from "@emotion/react";
|
||||
import styled from "@emotion/styled";
|
||||
import * as RXPopover from "@radix-ui/react-popover";
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user