Compare commits

...
Author SHA1 Message Date
github-actions[bot]GitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
b4847dd134 Version Packages (#1464)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-08-09 16:00:33 -07:00
Danny FridayandGitHub cf82c3b58c Improve error logging when authentication fails (#1475) 2023-08-09 21:37:09 +00:00
Adam MajmudarandGitHub a17326634e [Chains] Add back scroll-alpha-testnet (#1474) 2023-08-09 19:24:04 +00:00
70c4a119d6 Add sdk information in headers for various services (#1472)
Co-authored-by: Manan Tank <[email protected]>
2023-08-09 19:22:46 +00:00
Joaquim VergesandGitHub f0443bd989 [SDK] Expose erc721.claimConditions.getSupplyClaimedByWallet() (#1471) 2023-08-09 19:05:11 +00:00
Jonas DanielsandGitHub 73462ef108 [multi] only evaluate auth token if it is a valid string (#1473) 2023-08-09 18:07:26 +00:00
Jonas DanielsandGitHub 27c225a512 feat(urls): add clientId parameter to getGatewayUrlForCid (#1470) 2023-08-09 03:20:53 +00:00
Jonas DanielsandGitHub cb1c9937fa refactor(urls): Simplify authentication strategy handling (#1469) 2023-08-08 21:07:27 +00:00
Kien NgoandGitHub 588b210601 [react, react-native]Add option to hide the "Request Testnet Funds" button (#1458) 2023-08-08 08:38:37 -07:00
Phillip HoandGitHub 7e483ec97f [service-utils] Silence publishUsageEvents() output (#1466) 2023-08-08 00:20:09 +00:00
Joaquim VergesandGitHub c902901cff [All] Added PR template (#1465) 2023-08-07 23:57:31 +00:00
Phillip HoandGitHub 205018f6e1 [service-utils] Add userOpHash field (#1463) 2023-08-07 23:41:43 +00:00
FirekeeperandGitHub 269e68c0a1 Firekeeper/xai and large chainids (#1461) 2023-08-07 23:14:23 +00:00
Jonas DanielsandGitHub 2888efc065 build: Add TW_SECRET_KEY to test and e2e environments (#1462) 2023-08-07 21:14:00 +00:00
github-actions[bot]GitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
c753641570 Version Packages (#1457)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-08-07 13:21:25 -07:00
Manan TankandGitHub 4b0e63dcc0 [wallets] Save oAuthRedirectResult when auto connecting Magic Link with social login (#1460) 2023-08-07 16:48:02 +00:00
6979810de5 Fix typo in auth error message (#1454)
Co-authored-by: Jonas Daniels <[email protected]>
2023-08-06 04:10:05 +00:00
Jonas DanielsandGitHub 4c37af49aa feat: allow overriding the upload server url for IpfsUploader (#1455) 2023-08-05 21:12:33 +00:00
github-actions[bot]GitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
7858632429 Version Packages (#1451)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-08-04 18:15:57 -07:00
Phillip HoandGitHub 0647f12498 [service-utils] Remove optional fields in logging (#1453) 2023-08-05 00:24:56 +00:00
3e1c4045e7 chore: Add logHttpRequest helper func (#1448)
Co-authored-by: Danny Friday <[email protected]>
2023-08-04 23:18:54 +00:00
iketwandGitHub 3ba8ba4288 [RN] Upgrade coinbase wallet sdk (#1452) 2023-08-04 22:28:37 +00:00
Adam MajmudarandGitHub dfd120a3a9 [Auth] Split into functions & expose all auth functions on client (#1392) 2023-08-04 22:09:11 +00:00
Joaquim VergesandGitHub 262edc6a46 [SDK] Sanitize shared metadata descriptions strings (#1450) 2023-08-04 19:35:03 +00:00
github-actions[bot]GitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
4f79053ec3 Version Packages (#1446)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-08-04 10:41:06 -07:00
Joaquim VergesandGitHub 2a91113a76 [SDK] Revert sanitizing inputs (#1449) 2023-08-04 10:31:04 -07:00
Danny FridayandGitHub b103872daf Export extractAuthorizationData for CF Workers (#1447) 2023-08-03 23:54:08 +00:00
Mariano FuentesandGitHub 7f54012ec6 CLI warnings (#1442) 2023-08-03 22:48:25 +00:00
github-actions[bot]GitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
df36e27f32 Version Packages (#1431)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-08-03 15:30:22 -07:00
Danny FridayandGitHub 8c0dc9c880 Switch to domain with good reputation (#1443) 2023-08-03 22:16:04 +00:00
Joaquim VergesandGitHub 074a3eb01c [SDK] add jsdoc for deployOpenEdition (#1445) 2023-08-03 21:58:23 +00:00
Joaquim VergesandGitHub a5d0be939a [SDK] Optimize claimConditions.getAll() (#1444) 2023-08-03 21:50:02 +00:00
Phillip HoandGitHub 5ec0f064d2 [service-utils] Use aws4fetch for AWS calls from CF workers (#1440) 2023-08-03 20:40:46 +00:00
Joaquim VergesandGitHub becbb63736 [SDK] Sanitize NFT description strings (#1441) 2023-08-03 20:33:39 +00:00
farhanW3andGitHub 5ee700e804 updated error messages for API Keys (#1437) 2023-08-03 00:49:15 +00:00
Joaquim VergesandGitHub 67b145b94d [SDK] Expose deployPublishedContract alias (#1439) 2023-08-03 00:35:07 +00:00
Joaquim VergesandGitHub c0a6d2f182 [SDK] Expose sdk.deployer.deployOpenEdition() convenience function (#1438) 2023-08-03 00:11:05 +00:00
Joaquim VergesandGitHub fd9816556f [Wallets] better gas estimation for bundler calls (#1436) 2023-08-02 23:13:47 +00:00
Phillip HoandGitHub 2a3cd62dc9 Fix: Use string type for gasPricePerUnit (#1435) 2023-08-02 21:59:39 +00:00
iketwandGitHub c9bf4f05c1 [SDK] pass empty string if clientId is undefined (#1433) 2023-08-02 20:16:05 +00:00
Jonas DanielsandGitHub 9f5adc5c5c feat: Upgrade contracts package (#1432) 2023-08-02 19:39:50 +00:00
Phillip HoandGitHub d01e6396d1 [service-utils] Add a usage tracking helper fn to publish events (#1424) 2023-08-02 19:05:03 +00:00
nkrishangandGitHub 2283d21027 [SDK][REACT] Create useAccountAdmins and useAccountAdminsAndSigners (#1423) 2023-08-02 16:39:03 +00:00
iketwandGitHub ab36d84f1c [React] Allow passing secretKey for server-side rendering (#1429) 2023-08-02 16:17:24 +00:00
Manan TankandGitHub a9f2ff73c9 [wallets, react-core, react] minor fixes (#1430) 2023-08-02 15:47:18 +00:00
132 changed files with 3220 additions and 3669 deletions
+13
View File
@@ -0,0 +1,13 @@
## Problem solved
Short description of the bug fixed or feature added
## Changes made
- [ ] Public API changes: list the public API changes made if any
- [ ] Internal API changes: explain the internal logic changes
## How to test
- [ ] Automated tests: link to unit test file
- [ ] Manual tests: step by step instructions on how to test
+1
View File
@@ -17,6 +17,7 @@ concurrency:
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
TW_SECRET_KEY: ${{ secrets.TW_SECRET_KEY }}
jobs:
build:
+1 -1
View File
@@ -31,7 +31,7 @@
"hotlink-revert": "node ./scripts/hotlink/hotlink-revert.mjs",
"explain:major": "node ./scripts/changeset-explain-major.mjs"
},
"packageManager": "pnpm@8.1.0",
"packageManager": "pnpm@8.6.12",
"dependencies": {
"@changesets/changelog-github": "^0.4.8",
"@changesets/cli": "^2.26.2",
+37
View File
@@ -1,5 +1,42 @@
# @thirdweb-dev/auth
## 3.2.29
### Patch Changes
- Updated dependencies [[`73462ef1`](https://github.com/thirdweb-dev/js/commit/73462ef10800aeeb5976634e2bc6fb5d3e8501e4), [`70c4a119`](https://github.com/thirdweb-dev/js/commit/70c4a119d691a37ec999d9f6087902d532cc82ed)]:
- @thirdweb-dev/wallets@1.1.12
## 3.2.28
### Patch Changes
- Updated dependencies [[`4b0e63dc`](https://github.com/thirdweb-dev/js/commit/4b0e63dcc0ca871ce9cef76f8a41ff290316741c)]:
- @thirdweb-dev/wallets@1.1.11
## 3.2.27
### Patch Changes
- [#1392](https://github.com/thirdweb-dev/js/pull/1392) [`dfd120a3`](https://github.com/thirdweb-dev/js/commit/dfd120a3a9d1582c8b174265c92bf43dbbaf5c86) Thanks [@adam-maj](https://github.com/adam-maj)! - Expose functions from auth and update useAuth
- Updated dependencies []:
- @thirdweb-dev/wallets@1.1.10
## 3.2.26
### Patch Changes
- Updated dependencies []:
- @thirdweb-dev/wallets@1.1.9
## 3.2.25
### Patch Changes
- Updated dependencies [[`a9f2ff73`](https://github.com/thirdweb-dev/js/commit/a9f2ff73c9f83f05c3e2d3c53b23b2843e8a2576), [`fd981655`](https://github.com/thirdweb-dev/js/commit/fd9816556fe595b0c764e34dbcf15b0ad1677edb)]:
- @thirdweb-dev/wallets@1.1.8
## 3.2.24
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/auth",
"version": "3.2.24",
"version": "3.2.29",
"main": "dist/thirdweb-dev-auth.cjs.js",
"module": "dist/thirdweb-dev-auth.esm.js",
"browser": {
+73 -423
View File
@@ -1,28 +1,28 @@
import {
THIRDWEB_AUTH_DEFAULT_LOGIN_PAYLOAD_DURATION_IN_SECONDS,
THIRDWEB_AUTH_DEFAULT_TOKEN_DURATION_IN_SECONDS,
} from "../constants";
authenticateJWT,
generateJWT,
parseJWT,
refreshJWT,
} from "./functions/jwt";
import {
LoginOptions,
LoginPayload,
GenerateOptions,
LoginPayloadData,
LoginPayloadDataSchema,
AuthenticationPayloadDataSchema,
AuthenticationPayloadData,
VerifyOptionsSchema,
VerifyOptions,
GenerateOptionsSchema,
AuthenticateOptionsSchema,
AuthenticateOptions,
User,
Json,
LoginOptionsSchema,
AuthenticationPayload,
AuthenticationPayloadDataInput,
} from "./schema";
import { isBrowser } from "./utils";
buildAndSignLoginPayload,
buildLoginPayload,
signLoginPayload,
verifyLoginPayload,
} from "./functions/login";
import type { GenericAuthWallet } from "@thirdweb-dev/wallets";
import { Json, User } from "./schema/common";
import {
LoginOptionsWithOptionalDomain,
LoginPayload,
LoginPayloadData,
} from "./schema/login";
import { VerifyOptionsWithOptionalDomain } from "./schema/verify";
import { GenerateOptionsWithOptionalDomain } from "./schema/generate";
import {
AuthenticateOptionsWithOptionalDomain,
AuthenticationPayload,
} from "./schema/authenticate";
export class ThirdwebAuth {
private domain: string;
@@ -37,432 +37,82 @@ export class ThirdwebAuth {
this.wallet = wallet;
}
public async payload(options?: LoginOptions): Promise<LoginPayloadData> {
const parsedOptions = LoginOptionsSchema.parse(options);
let chainId: string | undefined = parsedOptions?.chainId;
if (!chainId && this.wallet.getChainId) {
try {
chainId = (await this.wallet.getChainId()).toString();
} catch {
// ignore error
}
}
return LoginPayloadDataSchema.parse({
type: this.wallet.type,
domain: parsedOptions?.domain || this.domain,
address: parsedOptions?.address || (await this.wallet.getAddress()),
statement: parsedOptions?.statement,
version: parsedOptions?.version,
uri: parsedOptions?.uri,
chain_id: chainId,
nonce: parsedOptions?.nonce,
expiration_time:
parsedOptions?.expirationTime ||
new Date(
Date.now() +
1000 * THIRDWEB_AUTH_DEFAULT_LOGIN_PAYLOAD_DURATION_IN_SECONDS,
),
invalid_before:
parsedOptions?.invalidBefore ||
new Date(
Date.now() -
1000 * THIRDWEB_AUTH_DEFAULT_LOGIN_PAYLOAD_DURATION_IN_SECONDS,
),
resources: parsedOptions?.resources,
public async payload(
options?: LoginOptionsWithOptionalDomain,
): Promise<LoginPayloadData> {
return buildLoginPayload({
wallet: this.wallet,
options: this.formatOptions(options),
});
}
public async loginWithPayload(
payload: LoginPayloadData,
): Promise<LoginPayload> {
const message = this.generateMessage(payload);
const signature = await this.wallet.signMessage(message);
return {
payload,
signature,
};
return signLoginPayload({ wallet: this.wallet, payload });
}
public async login(options?: LoginOptions): Promise<LoginPayload> {
const payloadData = await this.payload(options);
return await this.loginWithPayload(payloadData);
public async login(
options?: LoginOptionsWithOptionalDomain,
): Promise<LoginPayload> {
return buildAndSignLoginPayload({
wallet: this.wallet,
options: this.formatOptions(options),
});
}
public async verify(
payload: LoginPayload,
options?: VerifyOptions,
options?: VerifyOptionsWithOptionalDomain,
): Promise<string> {
const parsedOptions = VerifyOptionsSchema.parse(options);
if (payload.payload.type !== this.wallet.type) {
throw new Error(
`Expected chain type '${this.wallet.type}' does not match chain type on payload '${payload.payload.type}'`,
);
}
// Check that the intended domain matches the domain of the payload
const domain = parsedOptions?.domain || this.domain;
if (payload.payload.domain !== domain) {
throw new Error(
`Expected domain '${domain}' does not match domain on payload '${payload.payload.domain}'`,
);
}
// Check that the payload statement matches the expected statement
if (parsedOptions?.statement) {
if (payload.payload.statement !== parsedOptions.statement) {
throw new Error(
`Expected statement '${parsedOptions.statement}' does not match statement on payload '${payload.payload.statement}'`,
);
}
}
// Check that the intended URI matches the URI of the payload
if (parsedOptions?.uri) {
if (payload.payload.uri !== parsedOptions.uri) {
throw new Error(
`Expected URI '${parsedOptions.uri}' does not match URI on payload '${payload.payload.uri}'`,
);
}
}
// Check that the intended version matches the version of the payload
if (parsedOptions?.version) {
if (payload.payload.version !== parsedOptions.version) {
throw new Error(
`Expected version '${parsedOptions.version}' does not match version on payload '${payload.payload.version}'`,
);
}
}
// Check that the intended chain ID matches the chain ID of the payload
if (parsedOptions?.chainId) {
if (payload.payload.chain_id !== parsedOptions.chainId) {
throw new Error(
`Expected chain ID '${parsedOptions.chainId}' does not match chain ID on payload '${payload.payload.chain_id}'`,
);
}
}
// Check that the payload nonce is valid
if (parsedOptions?.validateNonce !== undefined) {
try {
await parsedOptions.validateNonce(payload.payload.nonce);
} catch (err) {
throw new Error(`Login request nonce is invalid`);
}
}
// Check that it isn't before the invalid before time
const currentTime = new Date();
if (currentTime < new Date(payload.payload.invalid_before)) {
throw new Error(`Login request is not yet valid`);
}
// Check that the payload hasn't expired
if (currentTime > new Date(payload.payload.expiration_time)) {
throw new Error(`Login request has expired`);
}
// Check that the specified resources are present on the payload
if (parsedOptions?.resources) {
const missingResources = parsedOptions.resources.filter(
(resource) => !payload.payload.resources?.includes(resource),
);
if (missingResources.length > 0) {
throw new Error(
`Login request is missing required resources: ${missingResources.join(
", ",
)}`,
);
}
}
// Check that the signing address is the claimed wallet address
const message = this.generateMessage(payload.payload);
const chainId =
this.wallet.type === "evm" && payload.payload.chain_id
? parseInt(payload.payload.chain_id)
: undefined;
const verified = await this.verifySignature(
message,
payload.signature,
payload.payload.address,
chainId,
);
if (!verified) {
throw new Error(
`Signer address does not match payload address '${payload.payload.address.toLowerCase()}'`,
);
}
return payload.payload.address;
return verifyLoginPayload({
wallet: this.wallet,
payload,
options: this.formatOptions(options),
});
}
public async generate(
payload: LoginPayload,
options?: GenerateOptions,
options?: GenerateOptionsWithOptionalDomain,
): Promise<string> {
if (isBrowser()) {
throw new Error(
"Authentication tokens should not be generated in the browser, as they must be signed by a server-side admin wallet.",
);
}
const parsedOptions = GenerateOptionsSchema.parse(options);
const domain = parsedOptions?.domain || this.domain;
const userAddress = await this.verify(payload, {
domain,
...parsedOptions?.verifyOptions,
});
let session: Json | undefined = undefined;
if (typeof parsedOptions?.session === "function") {
const sessionTrigger = (await parsedOptions.session(userAddress)) as Json;
if (sessionTrigger) {
session = sessionTrigger;
}
} else {
session = parsedOptions?.session;
}
const adminAddress = await this.wallet.getAddress();
return this.createToken({
iss: adminAddress,
sub: userAddress,
aud: domain,
nbf: parsedOptions?.invalidBefore || new Date(),
exp:
parsedOptions?.expirationTime ||
new Date(
Date.now() + 1000 * THIRDWEB_AUTH_DEFAULT_TOKEN_DURATION_IN_SECONDS,
),
iat: new Date(),
jti: parsedOptions?.tokenId,
ctx: session,
});
}
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,
});
}
/**
* Authenticate With Token
* @remarks Server-side function that authenticates the provided JWT token. This function verifies that
* the provided authentication token is valid and returns the address of the authenticated wallet.
*
* @param domain - The domain of the server-side application doing authentication
* @param token - The authentication token being used
* @returns The address of the authenticated wallet
*
* @example
* ```javascript
* const domain = "example.com";
* const loginPayload = await sdk.auth.login(domain);
* const token = await sdk.auth.generateAuthToken(domain, loginPayload);
*
* // Authenticate the token and get the address of authenticating users wallet
* const address = sdk.auth.authenticate(domain, token);
* ```
*/
public async authenticate<TSession extends Json = Json>(
token: string,
options?: AuthenticateOptions,
): Promise<User<TSession>> {
if (isBrowser()) {
throw new Error(
"Should not authenticate tokens in the browser, as they must be verified by the server-side admin wallet.",
);
}
const parsedOptions = AuthenticateOptionsSchema.parse(options);
const domain = parsedOptions?.domain || this.domain;
const { payload, signature } = this.parseToken(token);
// Check that the payload unique ID is valid
if (parsedOptions?.validateTokenId !== undefined) {
try {
await parsedOptions.validateTokenId(payload.jti);
} catch (err) {
throw new Error(`Token ID is invalid`);
}
}
// Check that the token audience matches the domain
if (payload.aud !== domain) {
throw new Error(
`Expected token to be for the domain '${domain}', but found token with domain '${payload.aud}'`,
);
}
// Check that the token is past the invalid before time
const currentTime = Math.floor(new Date().getTime() / 1000);
if (currentTime < payload.nbf) {
throw new Error(
`This token is invalid before epoch time '${payload.nbf}', current epoch time is '${currentTime}'`,
);
}
// Check that the token hasn't expired
if (currentTime > payload.exp) {
throw new Error(
`This token expired at epoch time '${payload.exp}', current epoch time is '${currentTime}'`,
);
}
// Check that the connected wallet matches the token issuer
const connectedAddress = await this.wallet.getAddress();
if (connectedAddress.toLowerCase() !== payload.iss.toLowerCase()) {
throw new Error(
`Expected the connected wallet address '${connectedAddress}' to match the token issuer address '${payload.iss}'`,
);
}
let chainId: number | undefined = undefined;
if (this.wallet.getChainId) {
try {
chainId = await this.wallet.getChainId();
} catch {
// ignore error
}
}
const verified = await this.verifySignature(
JSON.stringify(payload),
signature,
connectedAddress,
chainId,
);
if (!verified) {
throw new Error(
`The connected wallet address '${connectedAddress}' did not sign the token`,
);
}
return {
address: payload.sub,
session: payload.ctx as TSession | undefined,
};
}
public parseToken(token: string): AuthenticationPayload {
const encodedPayload = token.split(".")[1];
const encodedSignature = token.split(".")[2];
const payload: AuthenticationPayloadData = JSON.parse(
Buffer.from(encodedPayload, "base64").toString(),
);
const signature = Buffer.from(encodedSignature, "base64").toString();
return {
return generateJWT({
wallet: this.wallet,
payload,
signature,
};
options: this.formatOptions(options),
});
}
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;
public async refresh(jwt: string, expirationTime?: Date): Promise<string> {
return refreshJWT({
wallet: this.wallet,
jwt,
options: { expirationTime },
});
}
private async verifySignature(
message: string,
signature: string,
address: string,
chainId?: number,
) {
return this.wallet.verifySignature(message, signature, address, chainId);
public async authenticate<TSession extends Json = Json>(
jwt: string,
options?: AuthenticateOptionsWithOptionalDomain,
): Promise<User<TSession>> {
return authenticateJWT({
wallet: this.wallet,
jwt,
options: this.formatOptions(options),
});
}
/**
* Generates a EIP-4361 & CAIP-122 compliant message to sign based on the login payload
*/
private generateMessage(payload: LoginPayloadData): string {
const typeField = payload.type === "evm" ? "Ethereum" : "Solana";
const header = `${payload.domain} wants you to sign in with your ${typeField} account:`;
let prefix = [header, payload.address].join("\n");
prefix = [prefix, payload.statement].join("\n\n");
if (payload.statement) {
prefix += "\n";
}
public parseToken(jwt: string): AuthenticationPayload {
return parseJWT(jwt);
}
const suffixArray = [];
if (payload.uri) {
const uriField = `URI: ${payload.uri}`;
suffixArray.push(uriField);
}
const versionField = `Version: ${payload.version}`;
suffixArray.push(versionField);
if (payload.chain_id) {
const chainField = `Chain ID: ` + payload.chain_id || "1";
suffixArray.push(chainField);
}
const nonceField = `Nonce: ${payload.nonce}`;
suffixArray.push(nonceField);
const issuedAtField = `Issued At: ${payload.issued_at}`;
suffixArray.push(issuedAtField);
const expiryField = `Expiration Time: ${payload.expiration_time}`;
suffixArray.push(expiryField);
if (payload.invalid_before) {
const invalidBeforeField = `Not Before: ${payload.invalid_before}`;
suffixArray.push(invalidBeforeField);
}
if (payload.resources) {
suffixArray.push(
[`Resources:`, ...payload.resources.map((x) => `- ${x}`)].join("\n"),
);
}
const suffix = suffixArray.join("\n");
return [prefix, suffix].join("\n");
private formatOptions<TOptions extends { domain?: string }>(
options?: TOptions,
): Omit<TOptions, "domain"> & { domain: string } {
return options
? { ...options, domain: options?.domain || this.domain }
: ({ domain: this.domain } as Omit<TOptions, "domain"> & {
domain: string;
});
}
}
+226
View File
@@ -0,0 +1,226 @@
import {
AuthenticateOptionsSchema,
AuthenticationPayload,
AuthenticationPayloadData,
AuthenticationPayloadDataSchema,
} from "../schema/authenticate";
import { Json, User } from "../schema/common";
import {
BuildJwtParams,
RefreshJwtParams,
GenerateJwtParams,
AuthenticateJwtParams,
} from "../schema/functions";
import { GenerateOptionsSchema } from "../schema/generate";
import { RefreshOptionsSchema } from "../schema/refresh";
import { verifyLoginPayload } from "./login";
function isBrowser() {
return typeof window !== "undefined";
}
function base64encode(data: string): string {
if (isBrowser()) {
return window.btoa(data);
}
return Buffer.from(data).toString("base64").replace(/=/g, "");
}
function base64decode(data: string): string {
if (isBrowser()) {
return window.atob(data);
}
return Buffer.from(data, "base64").toString();
}
/**
* Build JWT token based on the authentication payload
*/
async function buildJWT({ wallet, payload }: BuildJwtParams): Promise<string> {
const payloadData = AuthenticationPayloadDataSchema.parse(payload);
const message = JSON.stringify(payloadData);
const signature = await 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 = base64encode(JSON.stringify(header));
const encodedData = base64encode(JSON.stringify(payloadData));
const encodedSignature = base64encode(signature);
// Generate a JWT with base64 encoded header, payload, and signature
const jwt = `${encodedHeader}.${encodedData}.${encodedSignature}`;
return jwt;
}
/**
* Generate a new JWT using a login payload
*/
export async function generateJWT({
wallet,
payload,
options,
}: GenerateJwtParams): Promise<string> {
const parsedOptions = GenerateOptionsSchema.parse(options);
const userAddress = await verifyLoginPayload({
wallet,
payload,
options: {
domain: parsedOptions.domain,
...parsedOptions.verifyOptions,
},
});
let session: Json | undefined = undefined;
if (typeof parsedOptions?.session === "function") {
const sessionTrigger = (await parsedOptions.session(userAddress)) as Json;
if (sessionTrigger) {
session = sessionTrigger;
}
} else {
session = parsedOptions?.session;
}
const adminAddress = await wallet.getAddress();
return buildJWT({
wallet,
payload: {
iss: adminAddress,
sub: userAddress,
aud: parsedOptions.domain,
nbf: parsedOptions?.invalidBefore || new Date(),
exp: parsedOptions.expirationTime,
iat: new Date(),
jti: parsedOptions?.tokenId,
ctx: session,
},
});
}
/**
* Parse data from an encoded auth JWT
*/
export function parseJWT(jwt: string): AuthenticationPayload {
const encodedPayload = jwt.split(".")[1];
const encodedSignature = jwt.split(".")[2];
const payload: AuthenticationPayloadData = JSON.parse(
base64decode(encodedPayload),
);
const signature = base64decode(encodedSignature);
return {
payload,
signature,
};
}
/**
* Refresh an existing JWT
*/
export async function refreshJWT({
wallet,
jwt,
options,
}: RefreshJwtParams): Promise<string> {
const { payload } = parseJWT(jwt);
const parsedOptions = RefreshOptionsSchema.parse(options);
return buildJWT({
wallet,
payload: {
iss: payload.iss,
sub: payload.sub,
aud: payload.aud,
nbf: new Date(),
exp: parsedOptions.expirationTime,
iat: new Date(),
ctx: payload.ctx,
},
});
}
/**
* Validate a JWT and extract the user's info
*/
export async function authenticateJWT<TSession extends Json = Json>({
wallet,
jwt,
options,
}: AuthenticateJwtParams): Promise<User<TSession>> {
const parsedOptions = AuthenticateOptionsSchema.parse(options);
const { payload, signature } = parseJWT(jwt);
// Check that the payload unique ID is valid
if (parsedOptions?.validateTokenId !== undefined) {
try {
await parsedOptions.validateTokenId(payload.jti);
} catch (err) {
throw new Error(`Token ID is invalid`);
}
}
// Check that the token audience matches the domain
if (payload.aud !== parsedOptions.domain) {
throw new Error(
`Expected token to be for the domain '${parsedOptions.domain}', but found token with domain '${payload.aud}'`,
);
}
// Check that the token is past the invalid before time
const currentTime = Math.floor(new Date().getTime() / 1000);
if (currentTime < payload.nbf) {
throw new Error(
`This token is invalid before epoch time '${payload.nbf}', current epoch time is '${currentTime}'`,
);
}
// Check that the token hasn't expired
if (currentTime > payload.exp) {
throw new Error(
`This token expired at epoch time '${payload.exp}', current epoch time is '${currentTime}'`,
);
}
// Check that the connected wallet matches the token issuer
const issuerAddress = parsedOptions.issuerAddress
? parsedOptions.issuerAddress
: await wallet.getAddress();
if (issuerAddress.toLowerCase() !== payload.iss.toLowerCase()) {
throw new Error(
`The expected issuer address '${issuerAddress}' did not match the token issuer address '${payload.iss}'`,
);
}
let chainId: number | undefined = undefined;
if (wallet.getChainId) {
try {
chainId = await wallet.getChainId();
} catch {
// ignore error
}
}
const verified = await wallet.verifySignature(
JSON.stringify(payload),
signature,
issuerAddress,
chainId,
);
if (!verified) {
throw new Error(
`The expected signer address '${issuerAddress}' did not sign the token`,
);
}
return {
address: payload.sub,
session: payload.ctx as TSession | undefined,
};
}
+224
View File
@@ -0,0 +1,224 @@
import {
BuildLoginPayloadParams,
SignLoginPayloadParams,
VerifyLoginPayloadParams,
} from "../schema/functions";
import {
LoginOptionsSchema,
LoginPayload,
LoginPayloadData,
LoginPayloadDataSchema,
} from "../schema/login";
import { VerifyOptionsSchema } from "../schema/verify";
/**
* Create an EIP-4361 & CAIP-122 compliant message to sign based on the login payload
*/
function createLoginMessage(payload: LoginPayloadData): string {
const typeField = payload.type === "evm" ? "Ethereum" : "Solana";
const header = `${payload.domain} wants you to sign in with your ${typeField} account:`;
let prefix = [header, payload.address].join("\n");
prefix = [prefix, payload.statement].join("\n\n");
if (payload.statement) {
prefix += "\n";
}
const suffixArray = [];
if (payload.uri) {
const uriField = `URI: ${payload.uri}`;
suffixArray.push(uriField);
}
const versionField = `Version: ${payload.version}`;
suffixArray.push(versionField);
if (payload.chain_id) {
const chainField = `Chain ID: ` + payload.chain_id || "1";
suffixArray.push(chainField);
}
const nonceField = `Nonce: ${payload.nonce}`;
suffixArray.push(nonceField);
const issuedAtField = `Issued At: ${payload.issued_at}`;
suffixArray.push(issuedAtField);
const expiryField = `Expiration Time: ${payload.expiration_time}`;
suffixArray.push(expiryField);
if (payload.invalid_before) {
const invalidBeforeField = `Not Before: ${payload.invalid_before}`;
suffixArray.push(invalidBeforeField);
}
if (payload.resources) {
suffixArray.push(
[`Resources:`, ...payload.resources.map((x) => `- ${x}`)].join("\n"),
);
}
const suffix = suffixArray.join("\n");
return [prefix, suffix].join("\n");
}
export async function buildLoginPayload({
wallet,
options,
}: BuildLoginPayloadParams): Promise<LoginPayloadData> {
const parsedOptions = LoginOptionsSchema.parse(options);
let chainId: string | undefined = parsedOptions?.chainId;
if (!chainId && wallet.getChainId) {
try {
chainId = (await wallet.getChainId()).toString();
} catch {
// ignore error
}
}
return LoginPayloadDataSchema.parse({
type: wallet.type,
domain: parsedOptions.domain,
address: parsedOptions?.address || (await wallet.getAddress()),
statement: parsedOptions?.statement,
version: parsedOptions?.version,
uri: parsedOptions?.uri,
chain_id: chainId,
nonce: parsedOptions?.nonce,
expiration_time: parsedOptions.expirationTime,
invalid_before: parsedOptions.invalidBefore,
resources: parsedOptions?.resources,
});
}
export async function signLoginPayload({
wallet,
payload,
}: SignLoginPayloadParams): Promise<LoginPayload> {
const message = createLoginMessage(payload);
const signature = await wallet.signMessage(message);
return {
payload,
signature,
};
}
export async function buildAndSignLoginPayload({
wallet,
options,
}: BuildLoginPayloadParams): Promise<LoginPayload> {
const payload = await buildLoginPayload({ wallet, options });
return signLoginPayload({ wallet, payload });
}
export async function verifyLoginPayload({
wallet,
payload,
options,
}: VerifyLoginPayloadParams): Promise<string> {
const parsedOptions = VerifyOptionsSchema.parse(options);
if (payload.payload.type !== wallet.type) {
throw new Error(
`Expected chain type '${wallet.type}' does not match chain type on payload '${payload.payload.type}'`,
);
}
// Check that the intended domain matches the domain of the payload
if (payload.payload.domain !== parsedOptions.domain) {
throw new Error(
`Expected domain '${parsedOptions.domain}' does not match domain on payload '${payload.payload.domain}'`,
);
}
// Check that the payload statement matches the expected statement
if (parsedOptions?.statement) {
if (payload.payload.statement !== parsedOptions.statement) {
throw new Error(
`Expected statement '${parsedOptions.statement}' does not match statement on payload '${payload.payload.statement}'`,
);
}
}
// Check that the intended URI matches the URI of the payload
if (parsedOptions?.uri) {
if (payload.payload.uri !== parsedOptions.uri) {
throw new Error(
`Expected URI '${parsedOptions.uri}' does not match URI on payload '${payload.payload.uri}'`,
);
}
}
// Check that the intended version matches the version of the payload
if (parsedOptions?.version) {
if (payload.payload.version !== parsedOptions.version) {
throw new Error(
`Expected version '${parsedOptions.version}' does not match version on payload '${payload.payload.version}'`,
);
}
}
// Check that the intended chain ID matches the chain ID of the payload
if (parsedOptions?.chainId) {
if (payload.payload.chain_id !== parsedOptions.chainId) {
throw new Error(
`Expected chain ID '${parsedOptions.chainId}' does not match chain ID on payload '${payload.payload.chain_id}'`,
);
}
}
// Check that the payload nonce is valid
if (parsedOptions?.validateNonce !== undefined) {
try {
await parsedOptions.validateNonce(payload.payload.nonce);
} catch (err) {
throw new Error(`Login request nonce is invalid`);
}
}
// Check that it isn't before the invalid before time
const currentTime = new Date();
if (currentTime < new Date(payload.payload.invalid_before)) {
throw new Error(`Login request is not yet valid`);
}
// Check that the payload hasn't expired
if (currentTime > new Date(payload.payload.expiration_time)) {
throw new Error(`Login request has expired`);
}
// Check that the specified resources are present on the payload
if (parsedOptions?.resources) {
const missingResources = parsedOptions.resources.filter(
(resource) => !payload.payload.resources?.includes(resource),
);
if (missingResources.length > 0) {
throw new Error(
`Login request is missing required resources: ${missingResources.join(
", ",
)}`,
);
}
}
// Check that the signing address is the claimed wallet address
const message = createLoginMessage(payload.payload);
const chainId =
wallet.type === "evm" && payload.payload.chain_id
? parseInt(payload.payload.chain_id)
: undefined;
const verified = await wallet.verifySignature(
message,
payload.signature,
payload.payload.address,
chainId,
);
if (!verified) {
throw new Error(
`Signer address does not match payload address '${payload.payload.address.toLowerCase()}'`,
);
}
return payload.payload.address;
}
+24 -1
View File
@@ -1,2 +1,25 @@
// Export thirdweb auth class
export { ThirdwebAuth } from "./auth";
export * from "./schema";
// Export individual auth functions
export {
generateJWT,
parseJWT,
refreshJWT,
authenticateJWT,
} from "./functions/jwt";
export {
buildLoginPayload,
signLoginPayload,
buildAndSignLoginPayload,
verifyLoginPayload,
} from "./functions/login";
// Export schema files individually
export * from "./schema/authenticate";
export * from "./schema/common";
export * from "./schema/functions";
export * from "./schema/generate";
export * from "./schema/login";
export * from "./schema/refresh";
export * from "./schema/verify";
-216
View File
@@ -1,216 +0,0 @@
import { utils, BigNumber } from "ethers";
import { v4 as uuidv4 } from "uuid";
import { z } from "zod";
export const AddressSchema = z.string().refine(
(arg) => utils.isAddress(arg),
(out) => {
return {
message: `${out} is not a valid address`,
};
},
);
export const RawDateSchema = z.date().transform((i) => {
return BigNumber.from(Math.floor(i.getTime() / 1000));
});
export const AccountTypeSchema = z.union([
z.literal("evm"),
z.literal("solana"),
]);
const literalSchema = z.union([z.string(), z.number(), z.boolean(), z.null()]);
type Literal = z.infer<typeof literalSchema>;
export type Json = Literal | { [key: string]: Json } | Json[];
const JsonSchema: z.ZodType<Json> = z.lazy(
() => z.union([literalSchema, z.array(JsonSchema), z.record(JsonSchema)]),
{ invalid_type_error: "Provided value was not valid JSON" },
);
/**
* @internal
*/
export const LoginOptionsSchema = z
.object({
domain: z.string().optional(),
address: z.string().optional(),
statement: z.string().optional(),
uri: z.string().optional(),
version: z.string().optional(),
chainId: z.string().optional(),
nonce: z.string().optional(),
expirationTime: z.date().optional(),
invalidBefore: z.date().optional(),
resources: z.array(z.string()).optional(),
})
.optional();
/**
* @internal
*/
export const LoginPayloadDataSchema = z.object({
type: AccountTypeSchema,
domain: z.string(),
address: z.string(),
statement: z
.string()
.default(
"Please ensure that the domain above matches the URL of the current website.",
),
uri: z.string().optional(),
version: z.string().default("1"),
chain_id: z.string().optional(),
nonce: z.string().default(uuidv4()),
issued_at: z
.date()
.default(new Date())
.transform((d) => d.toISOString()),
expiration_time: z.date().transform((d) => d.toISOString()),
invalid_before: z
.date()
.default(new Date())
.transform((d) => d.toISOString()),
resources: z.array(z.string()).optional(),
});
/**
* @internal
*/
export const LoginPayloadSchema = z.object({
payload: LoginPayloadDataSchema,
signature: z.string(),
});
/**
* @internal
*/
const VerifyOptionsSchemaRequired = z.object({
domain: z.string().optional(),
statement: z.string().optional(),
uri: z.string().optional(),
version: z.string().optional(),
chainId: z.string().optional(),
validateNonce: z.function().args(z.string()).optional(),
resources: z.array(z.string()).optional(),
});
/**
* @internal
*/
export const VerifyOptionsSchema = VerifyOptionsSchemaRequired.optional();
/**
* @internal
*/
export const GenerateOptionsSchema = z
.object({
domain: z.string().optional(),
tokenId: z.string().optional(),
expirationTime: z.date().optional(),
invalidBefore: z.date().optional(),
session: z.union([JsonSchema, z.function().args(z.string())]).optional(),
verifyOptions: VerifyOptionsSchemaRequired.omit({
domain: true,
}).optional(),
})
.optional();
/**
* @internal
*/
export const AuthenticationPayloadDataSchema = z.object({
iss: z.string(),
sub: z.string(),
aud: z.string(),
exp: RawDateSchema.transform((b) => b.toNumber()),
nbf: RawDateSchema.transform((b) => b.toNumber()),
iat: RawDateSchema.transform((b) => b.toNumber()),
jti: z.string().default(uuidv4()),
ctx: JsonSchema.optional(),
});
/**
* @internal
*/
export const AuthenticationPayloadSchema = z.object({
payload: AuthenticationPayloadDataSchema,
signature: z.string(),
});
/**
* @internal
*/
export const AuthenticateOptionsSchema = z
.object({
domain: z.string().optional(),
validateTokenId: z.function().args(z.string()).optional(),
})
.optional();
/**
* @public
*/
export type LoginOptions = z.input<typeof LoginOptionsSchema>;
/**
* @public
*/
export type LoginPayloadData = z.output<typeof LoginPayloadDataSchema>;
/**
* @public
*/
export type LoginPayload = z.output<typeof LoginPayloadSchema>;
/**
* @public
*/
export type VerifyOptions = z.input<typeof VerifyOptionsSchema>;
/**
* @public
*/
export type GenerateOptions = z.input<typeof GenerateOptionsSchema>;
/**
* @public
*/
export type AuthenticationPayloadData = z.output<
typeof AuthenticationPayloadDataSchema
>;
/**
* @internal
*/
export type AuthenticationPayloadDataInput = z.input<
typeof AuthenticationPayloadDataSchema
>;
/**
* @public
*/
export type AuthenticationPayload = z.output<
typeof AuthenticationPayloadSchema
>;
/**
* @public
*/
export type AuthenticateOptions = z.output<typeof AuthenticateOptionsSchema>;
/**
* @public
*/
export type User<TContext extends Json = Json> = {
address: string;
session?: TContext;
};
export const LoginPayloadOutputSchema = LoginPayloadSchema.extend({
payload: LoginPayloadDataSchema.extend({
issued_at: z.string(),
expiration_time: z.string(),
invalid_before: z.string(),
}),
});
@@ -0,0 +1,46 @@
import { z } from "zod";
import { v4 as uuidv4 } from "uuid";
import { AddressSchema, JsonSchema, RawDateSchema } from "./common";
export const AuthenticationPayloadDataSchema = z.object({
iss: z.string(),
sub: z.string(),
aud: z.string(),
exp: RawDateSchema,
nbf: RawDateSchema,
iat: RawDateSchema,
jti: z.string().default(uuidv4()),
ctx: JsonSchema.optional(),
});
export const AuthenticationPayloadSchema = z.object({
payload: AuthenticationPayloadDataSchema,
signature: z.string(),
});
export const AuthenticateOptionsSchema = z.object({
domain: z.string(),
issuerAddress: AddressSchema.optional(),
validateTokenId: z.function().args(z.string()).optional(),
});
export type AuthenticationPayloadDataInput = z.input<
typeof AuthenticationPayloadDataSchema
>;
export type AuthenticationPayloadData = z.output<
typeof AuthenticationPayloadDataSchema
>;
export type AuthenticationPayload = z.output<
typeof AuthenticationPayloadSchema
>;
export type AuthenticateOptions = z.output<typeof AuthenticateOptionsSchema>;
export type AuthenticateOptionsWithOptionalDomain = Omit<
AuthenticateOptions,
"domain"
> & {
domain?: string;
};
+34
View File
@@ -0,0 +1,34 @@
import { utils } from "ethers";
import { z } from "zod";
const literalSchema = z.union([z.string(), z.number(), z.boolean(), z.null()]);
type Literal = z.infer<typeof literalSchema>;
export type Json = Literal | { [key: string]: Json } | Json[];
export const JsonSchema: z.ZodType<Json> = z.lazy(
() => z.union([literalSchema, z.array(JsonSchema), z.record(JsonSchema)]),
{ invalid_type_error: "Provided value was not valid JSON" },
);
export const AddressSchema = z.string().refine(
(arg) => utils.isAddress(arg),
(out) => {
return {
message: `${out} is not a valid address`,
};
},
);
export const RawDateSchema = z.date().transform((i) => {
return Math.floor(i.getTime() / 1000);
});
export const AccountTypeSchema = z.union([
z.literal("evm"),
z.literal("solana"),
]);
export type User<TContext extends Json = Json> = {
address: string;
session?: TContext;
};
@@ -0,0 +1,48 @@
import { GenericAuthWallet } from "@thirdweb-dev/wallets";
import { LoginOptions, LoginPayload, LoginPayloadData } from "./login";
import { VerifyOptions } from "./verify";
import {
AuthenticateOptions,
AuthenticationPayloadDataInput,
} from "./authenticate";
import { RefreshOptions } from "./refresh";
import { GenerateOptions } from "./generate";
export type BuildLoginPayloadParams = {
wallet: GenericAuthWallet;
options: LoginOptions;
};
export type SignLoginPayloadParams = {
wallet: GenericAuthWallet;
payload: LoginPayloadData;
};
export type VerifyLoginPayloadParams = {
wallet: GenericAuthWallet;
payload: LoginPayload;
options: VerifyOptions;
};
export type BuildJwtParams = {
wallet: GenericAuthWallet;
payload: AuthenticationPayloadDataInput;
};
export type GenerateJwtParams = {
wallet: GenericAuthWallet;
payload: LoginPayload;
options: GenerateOptions;
};
export type RefreshJwtParams = {
wallet: GenericAuthWallet;
jwt: string;
options?: RefreshOptions;
};
export type AuthenticateJwtParams = {
wallet: GenericAuthWallet;
jwt: string;
options: AuthenticateOptions;
};
+31
View File
@@ -0,0 +1,31 @@
import { z } from "zod";
import { VerifyOptionsSchema } from "./verify";
import { JsonSchema } from "./common";
import { THIRDWEB_AUTH_DEFAULT_TOKEN_DURATION_IN_SECONDS } from "../../constants";
export const GenerateOptionsSchema = z.object({
domain: z.string(),
tokenId: z.string().optional(),
expirationTime: z
.date()
.default(
() =>
new Date(
Date.now() + 1000 * THIRDWEB_AUTH_DEFAULT_TOKEN_DURATION_IN_SECONDS,
),
),
invalidBefore: z.date().optional(),
session: z.union([JsonSchema, z.function().args(z.string())]).optional(),
verifyOptions: VerifyOptionsSchema.omit({
domain: true,
}).optional(),
});
export type GenerateOptions = z.input<typeof GenerateOptionsSchema>;
export type GenerateOptionsWithOptionalDomain = Omit<
GenerateOptions,
"domain"
> & {
domain?: string;
};
+81
View File
@@ -0,0 +1,81 @@
import { z } from "zod";
import { v4 as uuidv4 } from "uuid";
import { AccountTypeSchema } from "./common";
import { THIRDWEB_AUTH_DEFAULT_LOGIN_PAYLOAD_DURATION_IN_SECONDS } from "../../constants";
export const LoginOptionsSchema = z.object({
domain: z.string(),
address: z.string().optional(),
statement: z.string().optional(),
uri: z.string().optional(),
version: z.string().optional(),
chainId: z.string().optional(),
nonce: z.string().optional(),
expirationTime: z
.date()
.default(
() =>
new Date(
Date.now() +
1000 * THIRDWEB_AUTH_DEFAULT_LOGIN_PAYLOAD_DURATION_IN_SECONDS,
),
),
invalidBefore: z
.date()
.default(
() =>
new Date(
Date.now() -
1000 * THIRDWEB_AUTH_DEFAULT_LOGIN_PAYLOAD_DURATION_IN_SECONDS,
),
),
resources: z.array(z.string()).optional(),
});
export const LoginPayloadDataSchema = z.object({
type: AccountTypeSchema,
domain: z.string(),
address: z.string(),
statement: z
.string()
.default(
"Please ensure that the domain above matches the URL of the current website.",
),
uri: z.string().optional(),
version: z.string().default("1"),
chain_id: z.string().optional(),
nonce: z.string().default(uuidv4()),
issued_at: z
.date()
.default(new Date())
.transform((d) => d.toISOString()),
expiration_time: z.date().transform((d) => d.toISOString()),
invalid_before: z
.date()
.default(new Date())
.transform((d) => d.toISOString()),
resources: z.array(z.string()).optional(),
});
export const LoginPayloadSchema = z.object({
payload: LoginPayloadDataSchema,
signature: z.string(),
});
export const LoginPayloadOutputSchema = LoginPayloadSchema.extend({
payload: LoginPayloadDataSchema.extend({
issued_at: z.string(),
expiration_time: z.string(),
invalid_before: z.string(),
}),
});
export type LoginOptions = z.input<typeof LoginOptionsSchema>;
export type LoginPayloadData = z.output<typeof LoginPayloadDataSchema>;
export type LoginPayload = z.output<typeof LoginPayloadSchema>;
export type LoginOptionsWithOptionalDomain = Omit<LoginOptions, "domain"> & {
domain?: string;
};
+15
View File
@@ -0,0 +1,15 @@
import { z } from "zod";
import { THIRDWEB_AUTH_DEFAULT_TOKEN_DURATION_IN_SECONDS } from "../../constants";
export const RefreshOptionsSchema = z.object({
expirationTime: z
.date()
.default(
() =>
new Date(
Date.now() + 1000 * THIRDWEB_AUTH_DEFAULT_TOKEN_DURATION_IN_SECONDS,
),
),
});
export type RefreshOptions = z.input<typeof RefreshOptionsSchema>;
+17
View File
@@ -0,0 +1,17 @@
import { z } from "zod";
export const VerifyOptionsSchema = z.object({
domain: z.string(),
statement: z.string().optional(),
uri: z.string().optional(),
version: z.string().optional(),
chainId: z.string().optional(),
validateNonce: z.function().args(z.string()).optional(),
resources: z.array(z.string()).optional(),
});
export type VerifyOptions = z.input<typeof VerifyOptionsSchema>;
export type VerifyOptionsWithOptionalDomain = Omit<VerifyOptions, "domain"> & {
domain?: string;
};
-9
View File
@@ -1,9 +0,0 @@
/**
* @internal
*/
export const isBrowser = () => typeof window !== "undefined";
/**
* @internal
*/
export const isNode = () => !isBrowser();
+1 -1
View File
@@ -1,5 +1,5 @@
import { ThirdwebAuth } from "../../core";
import { Json, LoginPayloadOutputSchema, User } from "../../core/schema";
import { Json, LoginPayloadOutputSchema, User } from "../../core";
import type { GenericAuthWallet } from "@thirdweb-dev/wallets";
import { Request } from "express";
import { z } from "zod";
+1 -2
View File
@@ -1,5 +1,4 @@
import { ThirdwebAuth } from "../../core";
import { Json, LoginPayloadOutputSchema, User } from "../../core/schema";
import { Json, LoginPayloadOutputSchema, ThirdwebAuth, User } from "../../core";
import type { GenericAuthWallet } from "@thirdweb-dev/wallets";
import { GetServerSidePropsContext, NextApiRequest } from "next";
import { NextRequest } from "next/server";
+22 -2
View File
@@ -3,7 +3,7 @@ import { EthersWallet } from "@thirdweb-dev/wallets/evm/wallets/ethers";
import { expect } from "chai";
import { Wallet } from "ethers";
describe("Wallet Authentication", async () => {
describe("Wallet Authentication - EVM", async () => {
let adminWallet: any, signerWallet: any, attackerWallet: any;
let auth: ThirdwebAuth;
@@ -272,7 +272,7 @@ describe("Wallet Authentication", async () => {
expect.fail();
} catch (err: any) {
expect(err.message).to.contain(
`Expected the connected wallet address '${await signerWallet.getAddress()}' to match the token issuer address '${await adminWallet.getAddress()}'`,
`The expected issuer address '${await signerWallet.getAddress()}' did not match the token issuer address '${await adminWallet.getAddress()}'`,
);
}
});
@@ -350,4 +350,24 @@ describe("Wallet Authentication", async () => {
role: "admin",
});
});
it("Should authenticate with issuer address", async () => {
const payload = await auth.login();
auth.updateWallet(adminWallet);
const token = await auth.generate(payload);
auth.updateWallet(attackerWallet);
try {
await auth.authenticate(token);
expect.fail();
} catch (err: any) {
expect(err.message).to.contain("The expected issuer address");
}
const user = await auth.authenticate(token, {
issuerAddress: await adminWallet.getAddress(),
});
expect(user.address).to.equal(await signerWallet.getAddress());
});
});
+2 -2
View File
@@ -3,7 +3,7 @@ import { Keypair } from "@solana/web3.js";
import { KeypairWallet } from "@thirdweb-dev/wallets/solana/wallets/keypair";
import { expect } from "chai";
describe("Wallet Authentication", async () => {
describe("Wallet Authentication - Solana", async () => {
let adminWallet: any, signerWallet: any, attackerWallet: any;
let auth: ThirdwebAuth;
@@ -272,7 +272,7 @@ describe("Wallet Authentication", async () => {
expect.fail();
} catch (err: any) {
expect(err.message).to.contain(
`Expected the connected wallet address '${await signerWallet.getAddress()}' to match the token issuer address '${await adminWallet.getAddress()}'`,
`The expected issuer address '${await signerWallet.getAddress()}' did not match the token issuer address '${await adminWallet.getAddress()}'`,
);
}
});
+26
View File
@@ -1,5 +1,31 @@
# @thirdweb-dev/chains
## 0.1.43
### Patch Changes
- [#1469](https://github.com/thirdweb-dev/js/pull/1469) [`cb1c9937`](https://github.com/thirdweb-dev/js/commit/cb1c9937fadb2d06eb148cf9819f2b3601376308) Thanks [@jnsdls](https://github.com/jnsdls)! - update chains
- [#1474](https://github.com/thirdweb-dev/js/pull/1474) [`a1732663`](https://github.com/thirdweb-dev/js/commit/a17326634e758c3bf42f0cc3869b8792f1e18cc9) Thanks [@adam-maj](https://github.com/adam-maj)! - Add back scroll-alpha-testnet
- [#1458](https://github.com/thirdweb-dev/js/pull/1458) [`588b2106`](https://github.com/thirdweb-dev/js/commit/588b21060139a7c0bf5805e2c629a16792ed2c76) Thanks [@kien-ngo](https://github.com/kien-ngo)! - Update chains
- [#1461](https://github.com/thirdweb-dev/js/pull/1461) [`269e68c0`](https://github.com/thirdweb-dev/js/commit/269e68c0a15e8f78cb5b68c9456ca8094b9e1f30) Thanks [@0xFirekeeper](https://github.com/0xFirekeeper)! - add xai-goerli chain and support BigInteger from Unity
## 0.1.42
### Patch Changes
- [#1450](https://github.com/thirdweb-dev/js/pull/1450) [`262edc6a`](https://github.com/thirdweb-dev/js/commit/262edc6a46792da88f49ff6ef0a756a932a6a0cf) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - update chains
## 0.1.41
### Patch Changes
- [#1436](https://github.com/thirdweb-dev/js/pull/1436) [`fd981655`](https://github.com/thirdweb-dev/js/commit/fd9816556fe595b0c764e34dbcf15b0ad1677edb) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Updated chains
- [#1432](https://github.com/thirdweb-dev/js/pull/1432) [`9f5adc5c`](https://github.com/thirdweb-dev/js/commit/9f5adc5c5c2782ffb878759df481e5fb1e1740e5) Thanks [@jnsdls](https://github.com/jnsdls)! - update chains
## 0.1.40
### Patch Changes
+2 -2
View File
@@ -3,7 +3,7 @@ export default {
"name": "Ultron Testnet",
"chain": "Ultron",
"icon": {
"url": "ipfs://QmS4W4kY7XYBA4f52vuuytXh3YaTcNBXF14V9tEY6SNqhz",
"url": "ipfs://QmPC6odFVyAQrXJQaZJVFpEQfRNbzZ5BjDZ7KBKmXPaYDw",
"width": 512,
"height": 512,
"format": "png"
@@ -27,7 +27,7 @@ export default {
"name": "Ultron Testnet Explorer",
"url": "https://explorer.ultron-dev.io",
"icon": {
"url": "ipfs://QmS4W4kY7XYBA4f52vuuytXh3YaTcNBXF14V9tEY6SNqhz",
"url": "ipfs://QmPC6odFVyAQrXJQaZJVFpEQfRNbzZ5BjDZ7KBKmXPaYDw",
"width": 512,
"height": 512,
"format": "png"
+2 -2
View File
@@ -3,7 +3,7 @@ export default {
"name": "Ultron Mainnet",
"chain": "Ultron",
"icon": {
"url": "ipfs://QmS4W4kY7XYBA4f52vuuytXh3YaTcNBXF14V9tEY6SNqhz",
"url": "ipfs://QmPC6odFVyAQrXJQaZJVFpEQfRNbzZ5BjDZ7KBKmXPaYDw",
"width": 512,
"height": 512,
"format": "png"
@@ -27,7 +27,7 @@ export default {
"name": "Ultron Explorer",
"url": "https://ulxscan.com",
"icon": {
"url": "ipfs://QmS4W4kY7XYBA4f52vuuytXh3YaTcNBXF14V9tEY6SNqhz",
"url": "ipfs://QmPC6odFVyAQrXJQaZJVFpEQfRNbzZ5BjDZ7KBKmXPaYDw",
"width": 512,
"height": 512,
"format": "png"
+7 -7
View File
@@ -1,10 +1,10 @@
import type { Chain } from "../src/types";
export default {
"name": "Credit Smartchain Mainnet",
"name": "Credit Smart Chain",
"chain": "CREDIT",
"rpc": [
"https://credit-smartchain.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://mainnet-rpc.cscscan.io"
"https://credit-smart-chain.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://rpc.creditsmartchain.com"
],
"faucets": [],
"nativeCurrency": {
@@ -23,7 +23,7 @@ export default {
"infoURL": "https://creditsmartchain.com",
"shortName": "Credit",
"chainId": 13308,
"networkId": 1,
"networkId": 13308,
"icon": {
"url": "ipfs://bafkreifbso3gd4wu5wxl27xyurxctmuae2jyuy37guqtzx23nga6ba4ag4",
"width": 1000,
@@ -32,8 +32,8 @@ export default {
},
"explorers": [
{
"name": "CSC Scan",
"url": "https://explorer.cscscan.io",
"name": "Creditscan",
"url": "https://scan.creditsmartchain.com",
"icon": {
"url": "ipfs://bafkreifbso3gd4wu5wxl27xyurxctmuae2jyuy37guqtzx23nga6ba4ag4",
"width": 1000,
@@ -44,5 +44,5 @@ export default {
}
],
"testnet": false,
"slug": "credit-smartchain"
"slug": "credit-smart-chain"
} as const satisfies Chain;
+33
View File
@@ -0,0 +1,33 @@
import type { Chain } from "../src/types";
export default {
"name": "Beam Testnet",
"chain": "BEAM",
"rpc": [
"https://beam-testnet.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://subnets.avax.network/beam/testnet/rpc"
],
"features": [
{
"name": "EIP1559"
}
],
"faucets": [],
"nativeCurrency": {
"name": "Merit Circle",
"symbol": "MC",
"decimals": 18
},
"infoURL": "https://gaming.meritcircle.io",
"shortName": "BEAM",
"chainId": 13337,
"networkId": 13337,
"explorers": [
{
"name": "Beam Explorer",
"url": "https://subnets-test.avax.network/beam",
"standard": "EIP3091"
}
],
"testnet": true,
"slug": "beam-testnet"
} as const satisfies Chain;
+2 -2
View File
@@ -1,6 +1,6 @@
import type { Chain } from "../src/types";
export default {
"name": "Kalar Chain Mainnet",
"name": "Kalar Chain",
"chain": "KLC",
"icon": {
"url": "ipfs://bafkreihfoy2kgf2rebaoicso7z5h7ju46z6gtr64mskkths3qbfkrtnkjm",
@@ -10,7 +10,7 @@ export default {
},
"rpc": [
"https://kalar-chain.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"http://rpc-api.kalarchain.tech"
"https://rpc-api.kalarchain.tech"
],
"faucets": [],
"nativeCurrency": {
+24
View File
@@ -0,0 +1,24 @@
import type { Chain } from "../src/types";
export default {
"name": "Humanode Testnet 5 Israfel",
"chain": "Humanode Testnet 5",
"rpc": [
"https://humanode-testnet-5-israfel.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://explorer-rpc-http.testnet5.stages.humanode.io"
],
"faucets": [
"https://t.me/HumanodeTestnet5FaucetBot"
],
"nativeCurrency": {
"name": "HMND",
"symbol": "HMND",
"decimals": 18
},
"infoURL": "https://humanode.io",
"shortName": "hmnd-t5",
"chainId": 14853,
"networkId": 14853,
"explorers": [],
"testnet": true,
"slug": "humanode-testnet-5-israfel"
} as const satisfies Chain;
+34
View File
@@ -0,0 +1,34 @@
import type { Chain } from "../src/types";
export default {
"name": "WhiteBIT Network",
"chain": "WBT",
"rpc": [
"https://whitebit-network.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://rpc.whitebit.network"
],
"faucets": [],
"nativeCurrency": {
"name": "WhiteBIT Coin",
"symbol": "WBT",
"decimals": 18
},
"infoURL": "https://whitebit.network",
"shortName": "wbt",
"chainId": 1875,
"networkId": 1875,
"icon": {
"url": "ipfs://Qmbi6cqsQyDjkQSoxbNTTUy8WGyVEFqCtATX2aF4KLmCcZ",
"width": 512,
"height": 512,
"format": "png"
},
"explorers": [
{
"name": "wb-explorer",
"url": "https://explorer.whitebit.network",
"standard": "EIP3091"
}
],
"testnet": false,
"slug": "whitebit-network"
} as const satisfies Chain;
+6 -6
View File
@@ -3,9 +3,9 @@ export default {
"name": "Lightlink Phoenix Mainnet",
"chain": "Lightlink Phoenix Mainnet",
"icon": {
"url": "ipfs://QmNRUoMgx16hurD3au3ou5A9rmTLYmre8WiGmQEPFmP2Vo",
"width": 600,
"height": 600,
"url": "ipfs://QmXMDj6iAFn2ducQcUU1M87PMMdT2jfyL3Tp3Lz5uUD5Lv",
"width": 200,
"height": 200,
"format": "png"
},
"rpc": [
@@ -33,9 +33,9 @@ export default {
"name": "phoenix",
"url": "https://phoenix.lightlink.io",
"icon": {
"url": "ipfs://QmNRUoMgx16hurD3au3ou5A9rmTLYmre8WiGmQEPFmP2Vo",
"width": 600,
"height": 600,
"url": "ipfs://QmXMDj6iAFn2ducQcUU1M87PMMdT2jfyL3Tp3Lz5uUD5Lv",
"width": 200,
"height": 200,
"format": "png"
},
"standard": "EIP3091"
+6 -6
View File
@@ -3,9 +3,9 @@ export default {
"name": "Lightlink Pegasus Testnet",
"chain": "Lightlink Pegasus Testnet",
"icon": {
"url": "ipfs://QmNRUoMgx16hurD3au3ou5A9rmTLYmre8WiGmQEPFmP2Vo",
"width": 600,
"height": 600,
"url": "ipfs://QmXMDj6iAFn2ducQcUU1M87PMMdT2jfyL3Tp3Lz5uUD5Lv",
"width": 200,
"height": 200,
"format": "png"
},
"rpc": [
@@ -35,9 +35,9 @@ export default {
"name": "pegasus",
"url": "https://pegasus.lightlink.io",
"icon": {
"url": "ipfs://QmNRUoMgx16hurD3au3ou5A9rmTLYmre8WiGmQEPFmP2Vo",
"width": 600,
"height": 600,
"url": "ipfs://QmXMDj6iAFn2ducQcUU1M87PMMdT2jfyL3Tp3Lz5uUD5Lv",
"width": 200,
"height": 200,
"format": "png"
},
"standard": "EIP3091"
+1 -1
View File
@@ -4,7 +4,7 @@ export default {
"chain": "REAP",
"rpc": [
"https://reapchain-testnet.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://test-rpc.reapchain.org"
"https://test-eth.reapchain.org"
],
"faucets": [
"http://faucet.reapchain.com"
+42
View File
@@ -0,0 +1,42 @@
import type { Chain } from "../src/types";
export default {
"name": "Manta Pacific Testnet",
"chain": "Manta Pacific",
"rpc": [
"https://manta-pacific-testnet.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://manta-testnet.calderachain.xyz/http"
],
"faucets": [],
"nativeCurrency": {
"name": "Manta",
"symbol": "MANTA",
"decimals": 18
},
"features": [
{
"name": "EIP155"
},
{
"name": "EIP1559"
}
],
"infoURL": "https://manta-testnet.caldera.dev/",
"shortName": "manta",
"chainId": 3441005,
"networkId": 3441005,
"icon": {
"url": "ipfs://QmTckcVTViRZ3NqT36MTt8AvgBSmudrbgU3pi8AaNtthoV",
"width": 834,
"height": 834,
"format": "png"
},
"explorers": [
{
"name": "manta-testnet Explorer",
"url": "https://manta-testnet.calderaexplorer.xyz",
"standard": "EIP3091"
}
],
"testnet": true,
"slug": "manta-pacific-testnet"
} as const satisfies Chain;
+27
View File
@@ -0,0 +1,27 @@
import type { Chain } from "../src/types";
export default {
"name": "Xai Goerli Orbit",
"chain": "Xai Goerli Orbit Testnet",
"shortName": "xai-goerli",
"chainId": 47279324479,
"testnet": true,
"nativeCurrency": {
"name": "Xai Goerli Ether",
"symbol": "ETH",
"decimals": 18
},
"rpc": [
"https://xai-goerli-orbit.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://testnet.xai-chain.net/rpc"
],
"explorers": [
{
"name": "Xai Goerli Testnet Explorer",
"url": "https://testnet-explorer.xai-chain.net",
"standard": "EIP3091"
}
],
"faucets": [],
"infoURL": "https://xai.games/",
"slug": "xai-goerli-orbit"
} as const satisfies Chain;
+17 -4
View File
@@ -2,8 +2,11 @@ import type { Chain } from "../src/types";
export default {
"name": "Scroll Sepolia Testnet",
"chain": "ETH",
"status": "incubating",
"rpc": [],
"status": "active",
"rpc": [
"https://scroll-sepolia-testnet.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://sepolia-rpc.scroll.io/"
],
"faucets": [],
"nativeCurrency": {
"name": "Ether",
@@ -14,11 +17,21 @@ export default {
"shortName": "scr-sepolia",
"chainId": 534351,
"networkId": 534351,
"explorers": [],
"explorers": [
{
"name": "Scroll Sepolia Testnet Block Explorer",
"url": "https://sepolia-blockscout.scroll.io",
"standard": "EIP3091"
}
],
"parent": {
"type": "L2",
"chain": "eip155-11155111",
"bridges": []
"bridges": [
{
"url": "https://scroll.io/bridge"
}
]
},
"testnet": true,
"slug": "scroll-sepolia-testnet"
+16 -3
View File
@@ -3,7 +3,10 @@ export default {
"name": "Scroll",
"chain": "ETH",
"status": "incubating",
"rpc": [],
"rpc": [
"https://scroll.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://rpc.scroll.io"
],
"faucets": [],
"nativeCurrency": {
"name": "Ether",
@@ -14,11 +17,21 @@ export default {
"shortName": "scr",
"chainId": 534352,
"networkId": 534352,
"explorers": [],
"explorers": [
{
"name": "Scroll Mainnet Block Explorer",
"url": "https://blockscout.scroll.io",
"standard": "EIP3091"
}
],
"parent": {
"type": "L2",
"chain": "eip155-1",
"bridges": []
"bridges": [
{
"url": "https://scroll.io/bridge"
}
]
},
"testnet": false,
"slug": "scroll"
+2 -7
View File
@@ -2,7 +2,7 @@ import type { Chain } from "../src/types";
export default {
"name": "Scroll Alpha Testnet",
"chain": "ETH",
"status": "active",
"status": "deprecated",
"rpc": [
"https://scroll-alpha-testnet.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://alpha-rpc.scroll.io/l2"
@@ -20,12 +20,7 @@ export default {
"explorers": [
{
"name": "Scroll Alpha Testnet Block Explorer",
"url": "https://blockscout.scroll.io",
"standard": "EIP3091"
},
{
"name": "Scroll Alpha Testnet Block Explorer",
"url": "https://scrollscan.co",
"url": "https://alpha-blockscout.scroll.io",
"standard": "EIP3091"
}
],
+2 -1
View File
@@ -7,7 +7,8 @@ export default {
"https://rpc.syscoin.org",
"https://rpc.ankr.com/syscoin/${ANKR_API_KEY}",
"https://syscoin.public-rpc.com",
"wss://rpc.syscoin.org/wss"
"wss://rpc.syscoin.org/wss",
"https://syscoin-evm.publicnode.com"
],
"faucets": [
"https://faucet.syscoin.org"
+2 -1
View File
@@ -5,7 +5,8 @@ export default {
"rpc": [
"https://syscoin-tanenbaum-testnet.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://rpc.tanenbaum.io",
"wss://rpc.tanenbaum.io/wss"
"wss://rpc.tanenbaum.io/wss",
"https://syscoin-tanenbaum-evm.publicnode.com"
],
"faucets": [
"https://faucet.tanenbaum.io"
+6 -2
View File
@@ -9,7 +9,11 @@ export default {
"https://evm-dataseed3.meerscan.io",
"https://evm-dataseed.meerscan.com",
"https://evm-dataseed1.meerscan.com",
"https://evm-dataseed2.meerscan.com"
"https://evm-dataseed2.meerscan.com",
"https://qng.rpc.qitmeer.io",
"https://mainnet.meerlabs.com",
"https://rpc.dimai.ai",
"https://rpc.woowow.io"
],
"faucets": [],
"nativeCurrency": {
@@ -37,7 +41,7 @@ export default {
"height": 512,
"format": "png"
},
"url": "https://evm.meerscan.com",
"url": "https://qng.meerscan.io",
"standard": "none"
}
],
+10 -3
View File
@@ -2,8 +2,15 @@ import type { Chain } from "../src/types";
export default {
"name": "Qitmeer Network Testnet",
"chain": "MEER",
"rpc": [],
"faucets": [],
"rpc": [
"https://qitmeer-network-testnet.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://testnet-qng.rpc.qitmeer.io",
"https://testnet.meerlabs.com",
"https://meer.testnet.meerfans.club"
],
"faucets": [
"https://faucet.qitmeer.io"
],
"nativeCurrency": {
"name": "Qitmeer Testnet",
"symbol": "MEER-T",
@@ -28,7 +35,7 @@ export default {
"height": 512,
"format": "png"
},
"url": "https://testnet.qng.meerscan.io",
"url": "https://qng-testnet.meerscan.io",
"standard": "none"
}
],
+5 -1
View File
@@ -2,7 +2,11 @@ import type { Chain } from "../src/types";
export default {
"name": "IMPERIUM TESTNET",
"chain": "tIMP",
"rpc": [],
"rpc": [
"https://imperium-testnet.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://data-aws-testnet.imperiumchain.com",
"https://data-aws2-testnet.imperiumchain.com"
],
"faucets": [
"https://faucet.imperiumchain.com/"
],
+5 -1
View File
@@ -2,7 +2,11 @@ import type { Chain } from "../src/types";
export default {
"name": "IMPERIUM MAINNET",
"chain": "IMP",
"rpc": [],
"rpc": [
"https://imperium.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://data-aws-mainnet.imperiumchain.com",
"https://data-aws2-mainnet.imperiumchain.com"
],
"faucets": [
"https://faucet.imperiumchain.com/"
],
+2 -1
View File
@@ -11,7 +11,8 @@ export default {
"rpc": [
"https://mind-smart-chain.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://rpc-msc.mindchain.info/",
"https://seednode.mindchain.info"
"https://seednode.mindchain.info",
"wss://seednode.mindchain.info/ws"
],
"faucets": [],
"nativeCurrency": {
@@ -0,0 +1,26 @@
// @ts-check
/** @typedef { import("../../src/types").Chain } Chain */
/** @type {Omit<Chain, "slug">} */
export default {
name: "Xai Goerli Orbit",
chain: "Xai Goerli Orbit Testnet",
shortName: "xai-goerli",
chainId: 47279324479,
testnet: true,
nativeCurrency: {
name: "Xai Goerli Ether",
symbol: "ETH",
decimals: 18,
},
rpc: ["https://testnet.xai-chain.net/rpc"],
explorers: [
{
name: "Xai Goerli Testnet Explorer",
url: "https://testnet-explorer.xai-chain.net",
standard: "EIP3091",
},
],
faucets: [],
infoURL: "https://xai.games/",
};
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/chains",
"version": "0.1.40",
"version": "0.1.43",
"main": "dist/thirdweb-dev-chains.cjs.js",
"module": "dist/thirdweb-dev-chains.esm.js",
"browser": {
+2 -1
View File
@@ -105,7 +105,8 @@ for (const file of additionalChainsFiles) {
}
chains = chains
.filter((c) => c.status !== "deprecated")
// Keep scroll-alpha-testnet for now even though its deprecated
.filter((c) => c.status !== "deprecated" || c.chainId === 534353)
.map((chain) => {
if (overrides[chain.chainId]) {
chain = merge(chain, overrides[chain.chainId], {
File diff suppressed because one or more lines are too long
+44
View File
@@ -1,5 +1,49 @@
# thirdweb
## 0.11.12
### Patch Changes
- Updated dependencies [[`73462ef1`](https://github.com/thirdweb-dev/js/commit/73462ef10800aeeb5976634e2bc6fb5d3e8501e4), [`27c225a5`](https://github.com/thirdweb-dev/js/commit/27c225a51221417592b5940887abd1ccb8b67e47), [`cb1c9937`](https://github.com/thirdweb-dev/js/commit/cb1c9937fadb2d06eb148cf9819f2b3601376308), [`70c4a119`](https://github.com/thirdweb-dev/js/commit/70c4a119d691a37ec999d9f6087902d532cc82ed), [`a1732663`](https://github.com/thirdweb-dev/js/commit/a17326634e758c3bf42f0cc3869b8792f1e18cc9), [`f0443bd9`](https://github.com/thirdweb-dev/js/commit/f0443bd989dbba50d0836d1cf274bfb2f44a53fd), [`cb1c9937`](https://github.com/thirdweb-dev/js/commit/cb1c9937fadb2d06eb148cf9819f2b3601376308), [`588b2106`](https://github.com/thirdweb-dev/js/commit/588b21060139a7c0bf5805e2c629a16792ed2c76), [`269e68c0`](https://github.com/thirdweb-dev/js/commit/269e68c0a15e8f78cb5b68c9456ca8094b9e1f30)]:
- @thirdweb-dev/storage@1.2.5
- @thirdweb-dev/sdk@3.10.48
- @thirdweb-dev/chains@0.1.43
## 0.11.11
### Patch Changes
- Updated dependencies [[`4c37af49`](https://github.com/thirdweb-dev/js/commit/4c37af49aa4fa66beb8ffe8ef556068a29f3c5d4)]:
- @thirdweb-dev/storage@1.2.4
- @thirdweb-dev/sdk@3.10.47
## 0.11.10
### Patch Changes
- Updated dependencies [[`262edc6a`](https://github.com/thirdweb-dev/js/commit/262edc6a46792da88f49ff6ef0a756a932a6a0cf), [`262edc6a`](https://github.com/thirdweb-dev/js/commit/262edc6a46792da88f49ff6ef0a756a932a6a0cf)]:
- @thirdweb-dev/chains@0.1.42
- @thirdweb-dev/sdk@3.10.46
## 0.11.9
### Patch Changes
- [#1442](https://github.com/thirdweb-dev/js/pull/1442) [`7f54012e`](https://github.com/thirdweb-dev/js/commit/7f54012ec648b727b60784990e71b0efb4690934) Thanks [@Marfuen](https://github.com/Marfuen)! - - Warn users on windows to not use the powershell as there are some compatibility issues with it at the moment.
- Warn users if they are pasting a client id instead of a secret key.
- Updated dependencies [[`2a91113a`](https://github.com/thirdweb-dev/js/commit/2a91113a760733fcff2aec90041f69e15de33905)]:
- @thirdweb-dev/sdk@3.10.45
## 0.11.8
### Patch Changes
- [#1443](https://github.com/thirdweb-dev/js/pull/1443) [`8c0dc9c8`](https://github.com/thirdweb-dev/js/commit/8c0dc9c88084efcd184a416ed372c50c2fb8a209) Thanks [@nessup](https://github.com/nessup)! - Switch IPFS download domain used in CLI
- Updated dependencies [[`a5d0be93`](https://github.com/thirdweb-dev/js/commit/a5d0be939a143095e769b68cf86360bfe4720744), [`c0a6d2f1`](https://github.com/thirdweb-dev/js/commit/c0a6d2f18231310d143c9b0cb1b2ff59315087a4), [`becbb637`](https://github.com/thirdweb-dev/js/commit/becbb637367285ed3d8d7d131e1020bf23e38298), [`fd981655`](https://github.com/thirdweb-dev/js/commit/fd9816556fe595b0c764e34dbcf15b0ad1677edb), [`c9bf4f05`](https://github.com/thirdweb-dev/js/commit/c9bf4f05c161707c6eb799ecef82f462e4a82405), [`67b145b9`](https://github.com/thirdweb-dev/js/commit/67b145b94dafb2fb1d90553ed4829f0ed22e2907), [`5ee700e8`](https://github.com/thirdweb-dev/js/commit/5ee700e80438650fa253c25c0bee6658ce68d2cf), [`9f5adc5c`](https://github.com/thirdweb-dev/js/commit/9f5adc5c5c2782ffb878759df481e5fb1e1740e5)]:
- @thirdweb-dev/sdk@3.10.44
- @thirdweb-dev/chains@0.1.41
## 0.11.7
### Patch Changes
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "thirdweb",
"main": "dist/cli/index.js",
"version": "0.11.7",
"version": "0.11.12",
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/cli",
"author": "thirdweb eng <[email protected]>",
"license": "Apache-2.0",
+19
View File
@@ -2,6 +2,7 @@ import chalk from "chalk";
import prompts from "prompts";
import Cache, { CacheEntry } from "sync-disk-cache";
import { ApiResponse } from "../lib/types";
import os from "os";
export async function loginUser(
cache: Cache,
@@ -40,6 +41,10 @@ export function getSession(cache: Cache) {
export async function createSession(cache: Cache) {
try {
const isWindows = os.type() === "Windows_NT";
if (isWindows) {
console.log(chalk.yellow("Windows detected: if you are using powershell, there are some known issues with it that we are actively working on, please use git bash or the command prompt. Thank you for your understanding."));
}
const response = await prompts({
type: "invisible",
name: "apiSecretKey",
@@ -48,6 +53,20 @@ export async function createSession(cache: Cache) {
)}`,
});
const keyPassed = response.apiSecretKey;
if (!keyPassed) {
console.log(chalk.red("You need to pass an API secret key"));
process.exit(1);
}
if (keyPassed.length === 32) {
console.log(chalk.red(`This is not a valid secret key. To get your secret key
1. Create an API key at https://thirdweb.com/create-api-key
2. Store and copy your Secret Key. This will be shown only once.
3. Paste it in the CLI when prompted`));
process.exit(1);
}
try {
await validateKey(response.apiSecretKey);
} catch (error) {
+1 -4
View File
@@ -70,10 +70,7 @@ export async function deployApp(
try {
const uri = await upload(storage, distPath);
return `${uri.replace(
"ipfs://",
"https://ipfs-public.thirdwebcdn.com/ipfs/",
)}`;
return storage.resolveScheme(uri);
} catch (err) {
console.error("Can't upload project", err);
return Promise.reject("Can't upload project");
+6
View File
@@ -1,5 +1,11 @@
# @thirdweb-dev/contracts-js
## 1.3.11
### Patch Changes
- [#1432](https://github.com/thirdweb-dev/js/pull/1432) [`9f5adc5c`](https://github.com/thirdweb-dev/js/commit/9f5adc5c5c2782ffb878759df481e5fb1e1740e5) Thanks [@jnsdls](https://github.com/jnsdls)! - upgrade contracts package
## 1.3.10
### Patch Changes
@@ -1,4 +0,0 @@
{
"main": "dist/thirdweb-dev-contracts-js-factories-TWRegistryLegacy__factory.cjs.js",
"module": "dist/thirdweb-dev-contracts-js-factories-TWRegistryLegacy__factory.esm.js"
}
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/contracts-js",
"version": "1.3.10",
"version": "1.3.11",
"main": "dist/thirdweb-dev-contracts-js.cjs.js",
"module": "dist/thirdweb-dev-contracts-js.esm.js",
"browser": {
@@ -10,7 +10,7 @@
"license": "Apache-2.0",
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/contracts-js",
"dependencies": {
"@thirdweb-dev/contracts": "3.8.1-0"
"@thirdweb-dev/contracts": "3.8.3"
},
"devDependencies": {
"@babel/preset-env": "^7.22.9",
+58
View File
@@ -1,5 +1,63 @@
# @thirdweb-dev/react-core
## 3.14.29
### Patch Changes
- Updated dependencies [[`73462ef1`](https://github.com/thirdweb-dev/js/commit/73462ef10800aeeb5976634e2bc6fb5d3e8501e4), [`27c225a5`](https://github.com/thirdweb-dev/js/commit/27c225a51221417592b5940887abd1ccb8b67e47), [`cb1c9937`](https://github.com/thirdweb-dev/js/commit/cb1c9937fadb2d06eb148cf9819f2b3601376308), [`70c4a119`](https://github.com/thirdweb-dev/js/commit/70c4a119d691a37ec999d9f6087902d532cc82ed), [`a1732663`](https://github.com/thirdweb-dev/js/commit/a17326634e758c3bf42f0cc3869b8792f1e18cc9), [`f0443bd9`](https://github.com/thirdweb-dev/js/commit/f0443bd989dbba50d0836d1cf274bfb2f44a53fd), [`cb1c9937`](https://github.com/thirdweb-dev/js/commit/cb1c9937fadb2d06eb148cf9819f2b3601376308), [`588b2106`](https://github.com/thirdweb-dev/js/commit/588b21060139a7c0bf5805e2c629a16792ed2c76), [`269e68c0`](https://github.com/thirdweb-dev/js/commit/269e68c0a15e8f78cb5b68c9456ca8094b9e1f30)]:
- @thirdweb-dev/storage@1.2.5
- @thirdweb-dev/wallets@1.1.12
- @thirdweb-dev/sdk@3.10.48
- @thirdweb-dev/chains@0.1.43
- @thirdweb-dev/auth@3.2.29
## 3.14.28
### Patch Changes
- [#1460](https://github.com/thirdweb-dev/js/pull/1460) [`4b0e63dc`](https://github.com/thirdweb-dev/js/commit/4b0e63dcc0ca871ce9cef76f8a41ff290316741c) Thanks [@MananTank](https://github.com/MananTank)! - Save `oAuthRedirectResult` when autoconnecting Magic Link via social login
- Updated dependencies [[`4c37af49`](https://github.com/thirdweb-dev/js/commit/4c37af49aa4fa66beb8ffe8ef556068a29f3c5d4), [`4b0e63dc`](https://github.com/thirdweb-dev/js/commit/4b0e63dcc0ca871ce9cef76f8a41ff290316741c)]:
- @thirdweb-dev/storage@1.2.4
- @thirdweb-dev/wallets@1.1.11
- @thirdweb-dev/sdk@3.10.47
- @thirdweb-dev/auth@3.2.28
## 3.14.27
### Patch Changes
- [#1392](https://github.com/thirdweb-dev/js/pull/1392) [`dfd120a3`](https://github.com/thirdweb-dev/js/commit/dfd120a3a9d1582c8b174265c92bf43dbbaf5c86) Thanks [@adam-maj](https://github.com/adam-maj)! - Expose functions from auth and update useAuth
- Updated dependencies [[`262edc6a`](https://github.com/thirdweb-dev/js/commit/262edc6a46792da88f49ff6ef0a756a932a6a0cf), [`262edc6a`](https://github.com/thirdweb-dev/js/commit/262edc6a46792da88f49ff6ef0a756a932a6a0cf), [`dfd120a3`](https://github.com/thirdweb-dev/js/commit/dfd120a3a9d1582c8b174265c92bf43dbbaf5c86)]:
- @thirdweb-dev/chains@0.1.42
- @thirdweb-dev/sdk@3.10.46
- @thirdweb-dev/auth@3.2.27
- @thirdweb-dev/wallets@1.1.10
## 3.14.26
### Patch Changes
- Updated dependencies [[`2a91113a`](https://github.com/thirdweb-dev/js/commit/2a91113a760733fcff2aec90041f69e15de33905)]:
- @thirdweb-dev/sdk@3.10.45
- @thirdweb-dev/wallets@1.1.9
## 3.14.25
### Patch Changes
- [#1423](https://github.com/thirdweb-dev/js/pull/1423) [`2283d210`](https://github.com/thirdweb-dev/js/commit/2283d21027503e047c25df5ebb21bdde0734be9e) Thanks [@nkrishang](https://github.com/nkrishang)! - Create useAccountAdmins hook
- [#1429](https://github.com/thirdweb-dev/js/pull/1429) [`ab36d84f`](https://github.com/thirdweb-dev/js/commit/ab36d84f1ce5647fa52475b4a35ea29d5007ad9b) Thanks [@iketw](https://github.com/iketw)! - Pass secretKey in the React SDK for server-side rendering
- [#1437](https://github.com/thirdweb-dev/js/pull/1437) [`5ee700e8`](https://github.com/thirdweb-dev/js/commit/5ee700e80438650fa253c25c0bee6658ce68d2cf) Thanks [@farhanW3](https://github.com/farhanW3)! - Updated the error messages
- Updated dependencies [[`a5d0be93`](https://github.com/thirdweb-dev/js/commit/a5d0be939a143095e769b68cf86360bfe4720744), [`c0a6d2f1`](https://github.com/thirdweb-dev/js/commit/c0a6d2f18231310d143c9b0cb1b2ff59315087a4), [`a9f2ff73`](https://github.com/thirdweb-dev/js/commit/a9f2ff73c9f83f05c3e2d3c53b23b2843e8a2576), [`fd981655`](https://github.com/thirdweb-dev/js/commit/fd9816556fe595b0c764e34dbcf15b0ad1677edb), [`becbb637`](https://github.com/thirdweb-dev/js/commit/becbb637367285ed3d8d7d131e1020bf23e38298), [`fd981655`](https://github.com/thirdweb-dev/js/commit/fd9816556fe595b0c764e34dbcf15b0ad1677edb), [`c9bf4f05`](https://github.com/thirdweb-dev/js/commit/c9bf4f05c161707c6eb799ecef82f462e4a82405), [`67b145b9`](https://github.com/thirdweb-dev/js/commit/67b145b94dafb2fb1d90553ed4829f0ed22e2907), [`5ee700e8`](https://github.com/thirdweb-dev/js/commit/5ee700e80438650fa253c25c0bee6658ce68d2cf), [`9f5adc5c`](https://github.com/thirdweb-dev/js/commit/9f5adc5c5c2782ffb878759df481e5fb1e1740e5)]:
- @thirdweb-dev/sdk@3.10.44
- @thirdweb-dev/wallets@1.1.8
- @thirdweb-dev/chains@0.1.41
## 3.14.24
### Patch Changes
+2 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/react-core",
"version": "3.14.24",
"version": "3.14.29",
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/react-core",
"author": "thirdweb eng <[email protected]>",
"license": "Apache-2.0",
@@ -104,6 +104,7 @@
"@thirdweb-dev/sdk": "workspace:*",
"@thirdweb-dev/storage": "workspace:*",
"@thirdweb-dev/wallets": "workspace:*",
"@thirdweb-dev/auth": "workspace:*",
"mime": "3.0.0",
"tiny-invariant": "^1.2.0"
},
@@ -161,6 +161,7 @@ export const ThirdwebProviderCore = <TChains extends Chain[]>({
storageInterface={props.storageInterface}
authConfig={props.authConfig}
clientId={props.clientId}
secretKey={props.secretKey}
>
<ThirdwebAuthProvider value={props.authConfig}>
{props.children}
@@ -317,10 +317,11 @@ export function ThirdwebWalletProvider(
),
{
ms: autoConnectTimeout,
message: "Failed to Auto connect. Auto connect timed out.",
message: autoConnectTimeoutErrorMessage,
},
);
} catch (e) {
console.error("Failed to auto connect wallet");
console.error(e);
setConnectionStatus("disconnected");
return;
@@ -345,10 +346,11 @@ export function ThirdwebWalletProvider(
setConnectionStatus("connecting");
await timeoutPromise(wallet.autoConnect(walletInfo.connectParams), {
ms: autoConnectTimeout,
message: "Failed to Auto connect. Auto connect timed out.",
message: autoConnectTimeoutErrorMessage,
});
setConnectedWallet(wallet, walletInfo.connectParams, true);
} catch (e) {
console.error("Failed to auto connect wallet");
console.error(e);
lastConnectedWalletStorage.removeItem(
LAST_CONNECTED_WALLET_STORAGE_KEY,
@@ -550,3 +552,5 @@ function timeoutPromise<T>(
);
});
}
const autoConnectTimeoutErrorMessage = `Failed to Auto connect. Auto connect timed out. You can increase the timeout duration using the autoConnectTimeout prop on <ThirdwebProvider />`;
@@ -21,13 +21,14 @@ import type {
SmartContract,
} from "@thirdweb-dev/sdk";
import invariant from "tiny-invariant";
import { WalletAddress } from "../../types";
/** **********************/
/** READ HOOKS **/
/** **********************/
/**
* Get all accounts
* Get all signers of account
*
* @example
* ```javascript
@@ -50,7 +51,7 @@ export function useAccountSigners(
requiredParamInvariant(contract, "No Contract instance provided");
invariant(
contract.account.getAllSigners,
"Contract instance does not support contract.account.getSignersWithRestrictions",
"Contract instance does not support contract.account.getAllSigners",
);
return contract.account.getAllSigners();
},
@@ -58,6 +59,71 @@ export function useAccountSigners(
);
}
/**
* Get all admins of account
*
* @example
* ```javascript
* const { data: accounts, isLoading, error } = useAccountSigners(contract);
* ```
*
* @param contract - an instance of a account
* @returns a response object that includes an array of all admins of the provided account
* @twfeature Account
* @see {@link https://portal.thirdweb.com/react/react.useaccountadmins?utm_source=sdk | Documentation}
* @beta
*/
export function useAccountAdmins(
contract: RequiredParam<SmartContract>,
): UseQueryResult<WalletAddress[]> {
const contractAddress = contract?.getAddress();
return useQueryWithNetwork(
cacheKeys.contract.account.signers(contractAddress),
() => {
requiredParamInvariant(contract, "No Contract instance provided");
invariant(
contract.account.getAllAdmins,
"Contract instance does not support contract.account.getAllAdmins",
);
return contract.account.getAllAdmins();
},
{ enabled: !!contract },
);
}
/**
* Get all signers and admins of account
*
* @example
* ```javascript
* const { data: accounts, isLoading, error } = useAccountSigners(contract);
* ```
*
* @param contract - an instance of a account
* @returns a response object that includes an array of all admins of the provided account
* @twfeature Account
* @see {@link https://portal.thirdweb.com/react/react.useaccountadmins?utm_source=sdk | Documentation}
* @beta
*/
export function useAccountAdminsAndSigners(
contract: RequiredParam<SmartContract>,
): UseQueryResult<SignerWithPermissions[]> {
const contractAddress = contract?.getAddress();
return useQueryWithNetwork(
cacheKeys.contract.account.signers(contractAddress),
() => {
requiredParamInvariant(contract, "No Contract instance provided");
invariant(
contract.account.getAllAdminsAndSigners,
"Contract instance does not support contract.account.getAllAdminsAndSigners",
);
return contract.account.getAllAdminsAndSigners();
},
{ enabled: !!contract },
);
}
/** **********************/
/** WRITE HOOKS **/
/** **********************/
+14 -17
View File
@@ -1,10 +1,9 @@
import { LoginOptions } from "@thirdweb-dev/auth";
import { ThirdwebAuth } from "@thirdweb-dev/auth";
import { useWallet } from "../../../core/hooks/wallet-hooks";
import { useThirdwebAuthContext } from "../../contexts/thirdweb-auth";
import { doLogin } from "./useLogin";
import invariant from "tiny-invariant";
import { useMemo } from "react";
export { useLogin, doLogin } from "./useLogin";
export { useLogin } from "./useLogin";
export { useLogout } from "./useLogout";
export type { UserWithData } from "./useUser";
export { useUser } from "./useUser";
@@ -14,17 +13,15 @@ export function useAuth() {
const wallet = useWallet();
const authConfig = useThirdwebAuthContext();
return {
login: async (options?: LoginOptions) => {
invariant(
authConfig,
"Please specify an authConfig in the ThirdwebProvider",
);
invariant(wallet, "Need a connected a wallet!");
return doLogin(wallet, {
domain: authConfig.domain,
...options,
});
},
};
return useMemo(() => {
if (!authConfig?.domain) {
return undefined;
}
if (!wallet) {
return undefined;
}
return new ThirdwebAuth(wallet, authConfig.domain)
}, [wallet, authConfig, authConfig?.domain])
}
@@ -2,12 +2,7 @@ import { useWallet } from "../../../core/hooks/wallet-hooks";
import { useThirdwebAuthContext } from "../../contexts/thirdweb-auth";
import { cacheKeys } from "../../utils/cache-keys";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import type {
LoginOptions,
LoginPayload,
LoginPayloadData,
} from "@thirdweb-dev/auth";
import { GenericAuthWallet } from "@thirdweb-dev/wallets";
import { signLoginPayload, type LoginPayloadData } from "@thirdweb-dev/auth";
import invariant from "tiny-invariant";
import { AUTH_TOKEN_STORAGE_KEY } from "../../../core/constants/auth";
@@ -59,7 +54,7 @@ export function useLogin() {
throw new Error(`Failed to get payload`);
}
const payload = await doLoginWithPayload(wallet, payloadData);
const payload = await signLoginPayload({ wallet, payload: payloadData });
res = await fetch(`${authConfig.authUrl}/login`, {
method: "POST",
@@ -93,106 +88,3 @@ export function useLogin() {
isLoading: login.isLoading,
};
}
export async function doLoginWithPayload(
wallet: GenericAuthWallet,
payload: LoginPayloadData,
): Promise<LoginPayload> {
const message = generateMessage(payload);
const signature = await wallet.signMessage(message);
return {
payload,
signature,
};
}
export async function doLogin(
wallet: GenericAuthWallet,
options?: LoginOptions,
): Promise<LoginPayload> {
let chainId: string | undefined = options?.chainId;
if (!chainId && wallet.getChainId) {
try {
chainId = (await wallet.getChainId()).toString();
} catch {
// ignore error
}
}
invariant(options?.domain, "Please specify a domain to login with.");
// generate a pseudo-random nonce if none is provided
const nonce =
options?.nonce || Math.floor(Math.random() * 1000000000).toString();
const payloadData: LoginPayloadData = {
type: wallet.type,
domain: options.domain,
address: await wallet.getAddress(),
statement:
options?.statement ||
"Please ensure that the domain above matches the URL of the current website.",
version: options?.version || "1",
uri: options?.uri,
chain_id: chainId,
nonce: options?.nonce || nonce,
issued_at: new Date().toISOString(),
expiration_time: new Date(
options?.expirationTime || Date.now() + 1000 * 60 * 10,
).toISOString(),
invalid_before: new Date(
options?.invalidBefore || Date.now() - 1000 * 60 * 10,
).toISOString(),
resources: options?.resources,
};
return doLoginWithPayload(wallet, payloadData);
}
// generate straight from auth
function generateMessage(payload: LoginPayloadData): string {
const typeField = payload.type === "evm" ? "Ethereum" : "Solana";
const header = `${payload.domain} wants you to sign in with your ${typeField} account:`;
let prefix = [header, payload.address].join("\n");
prefix = [prefix, payload.statement].join("\n\n");
if (payload.statement) {
prefix += "\n";
}
const suffixArray = [];
if (payload.uri) {
const uriField = `URI: ${payload.uri}`;
suffixArray.push(uriField);
}
const versionField = `Version: ${payload.version}`;
suffixArray.push(versionField);
if (payload.chain_id) {
const chainField = `Chain ID: ` + payload.chain_id || "1";
suffixArray.push(chainField);
}
const nonceField = `Nonce: ${payload.nonce}`;
suffixArray.push(nonceField);
const issuedAtField = `Issued At: ${payload.issued_at}`;
suffixArray.push(issuedAtField);
const expiryField = `Expiration Time: ${payload.expiration_time}`;
suffixArray.push(expiryField);
if (payload.invalid_before) {
const invalidBeforeField = `Not Before: ${payload.invalid_before}`;
suffixArray.push(invalidBeforeField);
}
if (payload.resources) {
suffixArray.push(
[`Resources:`, ...payload.resources.map((x) => `- ${x}`)].join("\n"),
);
}
const suffix = suffixArray.join("\n");
return [prefix, suffix].join("\n");
}
+6 -1
View File
@@ -203,7 +203,12 @@ export {
} from "./hooks/async/account-factory";
// account
export { useAccountSigners, useSetAccountSigners } from "./hooks/async/account";
export {
useAccountSigners,
useSetAccountSigners,
useAccountAdmins,
useAccountAdminsAndSigners,
} from "./hooks/async/account";
// thirdweb hooks (work as long as at least `<ThirdwebSdkProvider>` is used)
@@ -21,6 +21,7 @@ const WrappedThirdwebSDKProvider = <TChains extends Chain[]>({
signer,
children,
clientId,
secretKey,
}: React.PropsWithChildren<
{ supportedChains: Readonly<TChains> } & Omit<
ThirdwebSDKProviderProps<TChains>,
@@ -92,7 +93,7 @@ const WrappedThirdwebSDKProvider = <TChains extends Chain[]>({
// sdk from chainId
sdk_ = new ThirdwebSDK(
chainId,
{ ...mergedOptions, clientId },
{ ...mergedOptions, clientId, secretKey },
storageInterface,
);
}
@@ -113,7 +114,14 @@ const WrappedThirdwebSDKProvider = <TChains extends Chain[]>({
(sdk_ as any)._chainId = chainId;
return sdk_;
}, [activeChainId, supportedChains, sdkOptions, storageInterface, clientId]);
}, [
activeChainId,
supportedChains,
sdkOptions,
storageInterface,
clientId,
secretKey,
]);
useEffect(() => {
// if we have an sdk and a signer update the signer
@@ -163,7 +171,7 @@ export const ThirdwebSDKProvider = <TChains extends Chain[]>({
}: React.PropsWithChildren<ThirdwebSDKProviderProps<TChains>>) => {
if (!clientId) {
checkClientIdOrSecretKey(
"No clientId provided in ThirdwebSDK. You will have limited access to thirdweb's services for storage, RPC, and account abstraction. You can get a clientId from https://thirdweb.com/create-api-key",
"No API key. Please provide a clientId. It is required to access thirdweb's services. You can create a key at https://thirdweb.com/create-api-key",
clientId,
undefined,
);
@@ -45,4 +45,7 @@ export interface ThirdwebSDKProviderProps<TChains extends Chain[]>
// client Id for thirdweb services
clientId?: string;
// pass in a secret key when doing server side rendering
secretKey?: string;
}
+10
View File
@@ -1,5 +1,15 @@
# @thirdweb-dev/react-native-compat
## 0.2.48
## 0.2.47
## 0.2.46
## 0.2.45
## 0.2.44
## 0.2.43
## 0.2.42
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@thirdweb-dev/react-native-compat",
"description": "Shims for Thirdweb in React Native Projects",
"version": "0.2.43",
"version": "0.2.48",
"author": "thirdweb eng <[email protected]>",
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/react-native-compat",
"license": "Apache-2.0",
+60
View File
@@ -1,5 +1,65 @@
# @thirdweb-dev/react-native
## 0.2.48
### Patch Changes
- [#1458](https://github.com/thirdweb-dev/js/pull/1458) [`588b2106`](https://github.com/thirdweb-dev/js/commit/588b21060139a7c0bf5805e2c629a16792ed2c76) Thanks [@kien-ngo](https://github.com/kien-ngo)! - Add `hideTestnetFaucet` to hide the "Request Testnet funds" button in ConnectWallet details dropdown in `@thirdweb-dev/react-native` and `@thirdweb-dev/react`.
```ts
<ConnectWallet hideTestnetFaucet={true} />
```
- Updated dependencies [[`73462ef1`](https://github.com/thirdweb-dev/js/commit/73462ef10800aeeb5976634e2bc6fb5d3e8501e4), [`27c225a5`](https://github.com/thirdweb-dev/js/commit/27c225a51221417592b5940887abd1ccb8b67e47), [`cb1c9937`](https://github.com/thirdweb-dev/js/commit/cb1c9937fadb2d06eb148cf9819f2b3601376308), [`70c4a119`](https://github.com/thirdweb-dev/js/commit/70c4a119d691a37ec999d9f6087902d532cc82ed), [`a1732663`](https://github.com/thirdweb-dev/js/commit/a17326634e758c3bf42f0cc3869b8792f1e18cc9), [`f0443bd9`](https://github.com/thirdweb-dev/js/commit/f0443bd989dbba50d0836d1cf274bfb2f44a53fd), [`cb1c9937`](https://github.com/thirdweb-dev/js/commit/cb1c9937fadb2d06eb148cf9819f2b3601376308), [`588b2106`](https://github.com/thirdweb-dev/js/commit/588b21060139a7c0bf5805e2c629a16792ed2c76), [`269e68c0`](https://github.com/thirdweb-dev/js/commit/269e68c0a15e8f78cb5b68c9456ca8094b9e1f30)]:
- @thirdweb-dev/storage@1.2.5
- @thirdweb-dev/wallets@1.1.12
- @thirdweb-dev/sdk@3.10.48
- @thirdweb-dev/chains@0.1.43
- @thirdweb-dev/react-core@3.14.29
## 0.2.47
### Patch Changes
- Updated dependencies [[`4c37af49`](https://github.com/thirdweb-dev/js/commit/4c37af49aa4fa66beb8ffe8ef556068a29f3c5d4), [`4b0e63dc`](https://github.com/thirdweb-dev/js/commit/4b0e63dcc0ca871ce9cef76f8a41ff290316741c)]:
- @thirdweb-dev/storage@1.2.4
- @thirdweb-dev/react-core@3.14.28
- @thirdweb-dev/wallets@1.1.11
- @thirdweb-dev/sdk@3.10.47
## 0.2.46
### Patch Changes
- [#1452](https://github.com/thirdweb-dev/js/pull/1452) [`3ba8ba42`](https://github.com/thirdweb-dev/js/commit/3ba8ba428845f5b2ae90ca73855be275fc78373c) Thanks [@iketw](https://github.com/iketw)! - Upgrade Coinbase wallet SDK; it adds support for React Native 0.72.3
- Updated dependencies [[`262edc6a`](https://github.com/thirdweb-dev/js/commit/262edc6a46792da88f49ff6ef0a756a932a6a0cf), [`262edc6a`](https://github.com/thirdweb-dev/js/commit/262edc6a46792da88f49ff6ef0a756a932a6a0cf), [`dfd120a3`](https://github.com/thirdweb-dev/js/commit/dfd120a3a9d1582c8b174265c92bf43dbbaf5c86)]:
- @thirdweb-dev/chains@0.1.42
- @thirdweb-dev/sdk@3.10.46
- @thirdweb-dev/react-core@3.14.27
- @thirdweb-dev/wallets@1.1.10
## 0.2.45
### Patch Changes
- Updated dependencies [[`2a91113a`](https://github.com/thirdweb-dev/js/commit/2a91113a760733fcff2aec90041f69e15de33905)]:
- @thirdweb-dev/sdk@3.10.45
- @thirdweb-dev/react-core@3.14.26
- @thirdweb-dev/wallets@1.1.9
## 0.2.44
### Patch Changes
- [#1429](https://github.com/thirdweb-dev/js/pull/1429) [`ab36d84f`](https://github.com/thirdweb-dev/js/commit/ab36d84f1ce5647fa52475b4a35ea29d5007ad9b) Thanks [@iketw](https://github.com/iketw)! - Pass secretKey in the React SDK for server-side rendering
- Updated dependencies [[`a5d0be93`](https://github.com/thirdweb-dev/js/commit/a5d0be939a143095e769b68cf86360bfe4720744), [`c0a6d2f1`](https://github.com/thirdweb-dev/js/commit/c0a6d2f18231310d143c9b0cb1b2ff59315087a4), [`a9f2ff73`](https://github.com/thirdweb-dev/js/commit/a9f2ff73c9f83f05c3e2d3c53b23b2843e8a2576), [`fd981655`](https://github.com/thirdweb-dev/js/commit/fd9816556fe595b0c764e34dbcf15b0ad1677edb), [`becbb637`](https://github.com/thirdweb-dev/js/commit/becbb637367285ed3d8d7d131e1020bf23e38298), [`2283d210`](https://github.com/thirdweb-dev/js/commit/2283d21027503e047c25df5ebb21bdde0734be9e), [`fd981655`](https://github.com/thirdweb-dev/js/commit/fd9816556fe595b0c764e34dbcf15b0ad1677edb), [`ab36d84f`](https://github.com/thirdweb-dev/js/commit/ab36d84f1ce5647fa52475b4a35ea29d5007ad9b), [`c9bf4f05`](https://github.com/thirdweb-dev/js/commit/c9bf4f05c161707c6eb799ecef82f462e4a82405), [`67b145b9`](https://github.com/thirdweb-dev/js/commit/67b145b94dafb2fb1d90553ed4829f0ed22e2907), [`5ee700e8`](https://github.com/thirdweb-dev/js/commit/5ee700e80438650fa253c25c0bee6658ce68d2cf), [`9f5adc5c`](https://github.com/thirdweb-dev/js/commit/9f5adc5c5c2782ffb878759df481e5fb1e1740e5)]:
- @thirdweb-dev/sdk@3.10.44
- @thirdweb-dev/wallets@1.1.8
- @thirdweb-dev/react-core@3.14.25
- @thirdweb-dev/chains@0.1.41
## 0.2.43
### Patch Changes
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/react-native",
"version": "0.2.43",
"version": "0.2.48",
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/react-native",
"author": "thirdweb eng <[email protected]>",
"license": "Apache-2.0",
@@ -24,7 +24,7 @@
"android"
],
"dependencies": {
"@coinbase/wallet-mobile-sdk": "1.0.6",
"@coinbase/wallet-mobile-sdk": "1.0.7",
"@magic-sdk/provider": "17.2.0",
"@magic-sdk/react-native-bare": "^18.5.0",
"@shopify/restyle": "^2.4.2",
@@ -19,6 +19,13 @@ export type ConnectWalletProps = {
* render custom rows in the Connect Wallet Details modal
*/
extraRows?: ConnectWalletDetailsProps["extraRows"];
/**
* Hide option to request testnet funds for testnets in dropdown
*
* @default false
*/
hideTestnetFaucet?: boolean;
} & ConnectWalletButtonProps;
export const ConnectWallet = ({
@@ -27,6 +34,7 @@ export const ConnectWallet = ({
buttonTitle,
modalTitle,
extraRows,
hideTestnetFaucet,
}: ConnectWalletProps) => {
const fadeAnim = useRef(new Animated.Value(0)).current;
const address = useAddress();
@@ -47,6 +55,7 @@ export const ConnectWallet = ({
address={address}
detailsButton={detailsButton}
extraRows={extraRows}
hideTestnetFaucet={hideTestnetFaucet}
/>
) : (
<ConnectWalletButton
@@ -32,11 +32,13 @@ export const ConnectWalletDetailsModal = ({
onClosePress,
extraRows,
address,
hideTestnetFaucet,
}: {
isVisible: boolean;
onClosePress: () => void;
extraRows?: React.FC;
address?: string;
hideTestnetFaucet?: boolean;
}) => {
const theme = useAppTheme();
const [isExportModalVisible, setIsExportModalVisible] = useState(false);
@@ -198,7 +200,7 @@ export const ConnectWalletDetailsModal = ({
<Text variant="bodySmallSecondary">Current Network</Text>
</View>
<NetworkButton chain={chain} enableSwitchModal={true} />
{chain?.testnet && chain?.faucets?.length ? (
{!hideTestnetFaucet && chain?.testnet && chain?.faucets?.length ? (
<IconTextButton
mt="xs"
text="Request Testnet Funds"
@@ -14,12 +14,14 @@ export type ConnectWalletDetailsProps = {
address?: string;
detailsButton?: React.FC<{ onPress: () => void }>;
extraRows?: React.FC;
hideTestnetFaucet?: boolean;
};
export const WalletDetailsButton = ({
address,
detailsButton,
extraRows,
hideTestnetFaucet,
}: ConnectWalletDetailsProps) => {
const activeWallet = useWallet();
const chain = useChain();
@@ -46,6 +48,7 @@ export const WalletDetailsButton = ({
onClosePress={onPress}
extraRows={extraRows}
address={address}
hideTestnetFaucet={hideTestnetFaucet}
/>
{detailsButton ? (
detailsButton({ onPress })
@@ -18,7 +18,10 @@ import { walletIds } from "@thirdweb-dev/wallets";
import { ThirdwebStorage } from "../../core/storage/storage";
interface ThirdwebProviderProps<TChains extends Chain[]>
extends Omit<ThirdwebProviderCoreProps<TChains>, "supportedWallets"> {
extends Omit<
ThirdwebProviderCoreProps<TChains>,
"supportedWallets" | "secretKey"
> {
/**
* Wallets that will be supported by the dApp
* @defaultValue [MetaMaskWallet, CoinbaseWallet]
+58
View File
@@ -1,5 +1,63 @@
# @thirdweb-dev/react
## 3.14.29
### Patch Changes
- [#1458](https://github.com/thirdweb-dev/js/pull/1458) [`588b2106`](https://github.com/thirdweb-dev/js/commit/588b21060139a7c0bf5805e2c629a16792ed2c76) Thanks [@kien-ngo](https://github.com/kien-ngo)! - Add `hideTestnetFaucet` to hide the "Request Testnet funds" button in ConnectWallet details dropdown in `@thirdweb-dev/react-native` and `@thirdweb-dev/react`.
```ts
<ConnectWallet hideTestnetFaucet={true} />
```
- Updated dependencies [[`73462ef1`](https://github.com/thirdweb-dev/js/commit/73462ef10800aeeb5976634e2bc6fb5d3e8501e4), [`cb1c9937`](https://github.com/thirdweb-dev/js/commit/cb1c9937fadb2d06eb148cf9819f2b3601376308), [`70c4a119`](https://github.com/thirdweb-dev/js/commit/70c4a119d691a37ec999d9f6087902d532cc82ed), [`a1732663`](https://github.com/thirdweb-dev/js/commit/a17326634e758c3bf42f0cc3869b8792f1e18cc9), [`f0443bd9`](https://github.com/thirdweb-dev/js/commit/f0443bd989dbba50d0836d1cf274bfb2f44a53fd), [`cb1c9937`](https://github.com/thirdweb-dev/js/commit/cb1c9937fadb2d06eb148cf9819f2b3601376308), [`588b2106`](https://github.com/thirdweb-dev/js/commit/588b21060139a7c0bf5805e2c629a16792ed2c76), [`269e68c0`](https://github.com/thirdweb-dev/js/commit/269e68c0a15e8f78cb5b68c9456ca8094b9e1f30)]:
- @thirdweb-dev/wallets@1.1.12
- @thirdweb-dev/sdk@3.10.48
- @thirdweb-dev/chains@0.1.43
- @thirdweb-dev/react-core@3.14.29
## 3.14.28
### Patch Changes
- Updated dependencies [[`4b0e63dc`](https://github.com/thirdweb-dev/js/commit/4b0e63dcc0ca871ce9cef76f8a41ff290316741c)]:
- @thirdweb-dev/react-core@3.14.28
- @thirdweb-dev/wallets@1.1.11
- @thirdweb-dev/sdk@3.10.47
## 3.14.27
### Patch Changes
- Updated dependencies [[`262edc6a`](https://github.com/thirdweb-dev/js/commit/262edc6a46792da88f49ff6ef0a756a932a6a0cf), [`262edc6a`](https://github.com/thirdweb-dev/js/commit/262edc6a46792da88f49ff6ef0a756a932a6a0cf), [`dfd120a3`](https://github.com/thirdweb-dev/js/commit/dfd120a3a9d1582c8b174265c92bf43dbbaf5c86)]:
- @thirdweb-dev/chains@0.1.42
- @thirdweb-dev/sdk@3.10.46
- @thirdweb-dev/react-core@3.14.27
- @thirdweb-dev/wallets@1.1.10
## 3.14.26
### Patch Changes
- Updated dependencies [[`2a91113a`](https://github.com/thirdweb-dev/js/commit/2a91113a760733fcff2aec90041f69e15de33905)]:
- @thirdweb-dev/sdk@3.10.45
- @thirdweb-dev/react-core@3.14.26
- @thirdweb-dev/wallets@1.1.9
## 3.14.25
### Patch Changes
- [#1429](https://github.com/thirdweb-dev/js/pull/1429) [`ab36d84f`](https://github.com/thirdweb-dev/js/commit/ab36d84f1ce5647fa52475b4a35ea29d5007ad9b) Thanks [@iketw](https://github.com/iketw)! - Pass secretKey in the React SDK for server-side rendering
- [#1430](https://github.com/thirdweb-dev/js/pull/1430) [`a9f2ff73`](https://github.com/thirdweb-dev/js/commit/a9f2ff73c9f83f05c3e2d3c53b23b2843e8a2576) Thanks [@MananTank](https://github.com/MananTank)! - - Add base-gor as a valid safe address prefix
- Log failed to autoconnect error on react-core too
- Updated dependencies [[`a5d0be93`](https://github.com/thirdweb-dev/js/commit/a5d0be939a143095e769b68cf86360bfe4720744), [`c0a6d2f1`](https://github.com/thirdweb-dev/js/commit/c0a6d2f18231310d143c9b0cb1b2ff59315087a4), [`a9f2ff73`](https://github.com/thirdweb-dev/js/commit/a9f2ff73c9f83f05c3e2d3c53b23b2843e8a2576), [`fd981655`](https://github.com/thirdweb-dev/js/commit/fd9816556fe595b0c764e34dbcf15b0ad1677edb), [`becbb637`](https://github.com/thirdweb-dev/js/commit/becbb637367285ed3d8d7d131e1020bf23e38298), [`2283d210`](https://github.com/thirdweb-dev/js/commit/2283d21027503e047c25df5ebb21bdde0734be9e), [`fd981655`](https://github.com/thirdweb-dev/js/commit/fd9816556fe595b0c764e34dbcf15b0ad1677edb), [`ab36d84f`](https://github.com/thirdweb-dev/js/commit/ab36d84f1ce5647fa52475b4a35ea29d5007ad9b), [`c9bf4f05`](https://github.com/thirdweb-dev/js/commit/c9bf4f05c161707c6eb799ecef82f462e4a82405), [`67b145b9`](https://github.com/thirdweb-dev/js/commit/67b145b94dafb2fb1d90553ed4829f0ed22e2907), [`5ee700e8`](https://github.com/thirdweb-dev/js/commit/5ee700e80438650fa253c25c0bee6658ce68d2cf), [`9f5adc5c`](https://github.com/thirdweb-dev/js/commit/9f5adc5c5c2782ffb878759df481e5fb1e1740e5)]:
- @thirdweb-dev/sdk@3.10.44
- @thirdweb-dev/wallets@1.1.8
- @thirdweb-dev/react-core@3.14.25
- @thirdweb-dev/chains@0.1.41
## 3.14.24
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/react",
"version": "3.14.24",
"version": "3.14.29",
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/react",
"author": "thirdweb eng <[email protected]>",
"license": "Apache-2.0",
@@ -56,7 +56,6 @@ interface ThirdwebProviderProps<TChains extends Chain[]>
export const ThirdwebProvider = <
TChains extends Chain[] = typeof defaultChains,
>({
clientId: clientId,
supportedWallets,
theme,
children,
@@ -69,7 +68,6 @@ export const ThirdwebProvider = <
<ThemeProvider theme={theme === "dark" ? darkTheme : lightTheme}>
<ThirdwebProviderCore
theme={theme}
clientId={clientId}
supportedWallets={wallets}
{...restProps}
>
@@ -47,6 +47,13 @@ type ConnectWalletProps = {
};
style?: React.CSSProperties;
networkSelector?: Omit<NetworkSelectorProps, "theme" | "onClose" | "chains">;
/**
* Hide option to request testnet funds for testnets in dropdown
*
* @default false
*/
hideTestnetFaucet?: boolean;
};
const TW_CONNECT_WALLET = "tw-connect-wallet";
@@ -163,6 +170,7 @@ export const ConnectWallet: React.FC<ConnectWalletProps> = (props) => {
theme={theme}
style={props.style}
detailsBtn={props.detailsBtn}
hideTestnetFaucet={props.hideTestnetFaucet}
onDisconnect={() => {
if (authConfig?.authUrl) {
logout();
@@ -65,6 +65,7 @@ export const ConnectedWalletDetails: React.FC<{
networkSelector?: Omit<NetworkSelectorProps, "theme" | "onClose" | "chains">;
className?: string;
detailsBtn?: () => JSX.Element;
hideTestnetFaucet?: boolean;
}> = (props) => {
const disconnect = useDisconnect();
const chains = useSupportedChains();
@@ -322,32 +323,33 @@ export const ConnectedWalletDetails: React.FC<{
)}
{/* Request Testnet funds */}
{((chain?.faucets && chain.faucets.length > 0) ||
chain?.chainId === Localhost.chainId) && (
<MenuLink
href={chain?.faucets ? chain.faucets[0] : "#"}
target="_blank"
as="a"
onClick={async (e) => {
if (chain.chainId === Localhost.chainId) {
e.preventDefault();
setOpen(false);
await sdk?.wallet.requestFunds(10);
await balanceQuery.refetch();
}
}}
style={{
textDecoration: "none",
color: "inherit",
fontSize: fontSize.sm,
}}
>
<SecondaryIconContainer>
<FundsIcon size={iconSize.sm} />
</SecondaryIconContainer>
Request Testnet Funds
</MenuLink>
)}
{!props.hideTestnetFaucet &&
((chain?.faucets && chain.faucets.length > 0) ||
chain?.chainId === Localhost.chainId) && (
<MenuLink
href={chain?.faucets ? chain.faucets[0] : "#"}
target="_blank"
as="a"
onClick={async (e) => {
if (chain.chainId === Localhost.chainId) {
e.preventDefault();
setOpen(false);
await sdk?.wallet.requestFunds(10);
await balanceQuery.refetch();
}
}}
style={{
textDecoration: "none",
color: "inherit",
fontSize: fontSize.sm,
}}
>
<SecondaryIconContainer>
<FundsIcon size={iconSize.sm} />
</SecondaryIconContainer>
Request Testnet Funds
</MenuLink>
)}
{/* Export Wallet */}
{activeWallet?.walletId === walletIds.localWallet && (
@@ -39,6 +39,7 @@ export const gnosisAddressPrefixToChainId = {
bnb: 56,
oeth: 10,
gor: 5,
"base-gor": 84531,
} as const;
export const SelectAccount: React.FC<{
File diff suppressed because one or more lines are too long
+58
View File
@@ -1,5 +1,63 @@
# @thirdweb-dev/sdk
## 3.10.48
### Patch Changes
- [#1473](https://github.com/thirdweb-dev/js/pull/1473) [`73462ef1`](https://github.com/thirdweb-dev/js/commit/73462ef10800aeeb5976634e2bc6fb5d3e8501e4) Thanks [@jnsdls](https://github.com/jnsdls)! - do not pass empty auth tokens
- [#1472](https://github.com/thirdweb-dev/js/pull/1472) [`70c4a119`](https://github.com/thirdweb-dev/js/commit/70c4a119d691a37ec999d9f6087902d532cc82ed) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Add sdk information to service request headers
- [#1471](https://github.com/thirdweb-dev/js/pull/1471) [`f0443bd9`](https://github.com/thirdweb-dev/js/commit/f0443bd989dbba50d0836d1cf274bfb2f44a53fd) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Expose erc721.claimConditions.getSupplyClaimedByWallet()
- [#1469](https://github.com/thirdweb-dev/js/pull/1469) [`cb1c9937`](https://github.com/thirdweb-dev/js/commit/cb1c9937fadb2d06eb148cf9819f2b3601376308) Thanks [@jnsdls](https://github.com/jnsdls)! - fix secret key auth strategy for RPC
- Updated dependencies [[`73462ef1`](https://github.com/thirdweb-dev/js/commit/73462ef10800aeeb5976634e2bc6fb5d3e8501e4), [`27c225a5`](https://github.com/thirdweb-dev/js/commit/27c225a51221417592b5940887abd1ccb8b67e47), [`cb1c9937`](https://github.com/thirdweb-dev/js/commit/cb1c9937fadb2d06eb148cf9819f2b3601376308), [`70c4a119`](https://github.com/thirdweb-dev/js/commit/70c4a119d691a37ec999d9f6087902d532cc82ed), [`a1732663`](https://github.com/thirdweb-dev/js/commit/a17326634e758c3bf42f0cc3869b8792f1e18cc9), [`588b2106`](https://github.com/thirdweb-dev/js/commit/588b21060139a7c0bf5805e2c629a16792ed2c76), [`269e68c0`](https://github.com/thirdweb-dev/js/commit/269e68c0a15e8f78cb5b68c9456ca8094b9e1f30)]:
- @thirdweb-dev/storage@1.2.5
- @thirdweb-dev/chains@0.1.43
## 3.10.47
### Patch Changes
- Updated dependencies [[`4c37af49`](https://github.com/thirdweb-dev/js/commit/4c37af49aa4fa66beb8ffe8ef556068a29f3c5d4)]:
- @thirdweb-dev/storage@1.2.4
## 3.10.46
### Patch Changes
- [#1450](https://github.com/thirdweb-dev/js/pull/1450) [`262edc6a`](https://github.com/thirdweb-dev/js/commit/262edc6a46792da88f49ff6ef0a756a932a6a0cf) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Sanitize description strings for OpenEdition sharedMetadata
- Updated dependencies [[`262edc6a`](https://github.com/thirdweb-dev/js/commit/262edc6a46792da88f49ff6ef0a756a932a6a0cf)]:
- @thirdweb-dev/chains@0.1.42
## 3.10.45
### Patch Changes
- [#1449](https://github.com/thirdweb-dev/js/pull/1449) [`2a91113a`](https://github.com/thirdweb-dev/js/commit/2a91113a760733fcff2aec90041f69e15de33905) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Revert sanitizing inputs
## 3.10.44
### Patch Changes
- [#1444](https://github.com/thirdweb-dev/js/pull/1444) [`a5d0be93`](https://github.com/thirdweb-dev/js/commit/a5d0be939a143095e769b68cf86360bfe4720744) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Optimize claimConditions.getAll()
- [#1438](https://github.com/thirdweb-dev/js/pull/1438) [`c0a6d2f1`](https://github.com/thirdweb-dev/js/commit/c0a6d2f18231310d143c9b0cb1b2ff59315087a4) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Add sdk.deployer.deployOpenEdition() convenience function
- [#1441](https://github.com/thirdweb-dev/js/pull/1441) [`becbb637`](https://github.com/thirdweb-dev/js/commit/becbb637367285ed3d8d7d131e1020bf23e38298) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Sanitize NFT description input strings
- [#1433](https://github.com/thirdweb-dev/js/pull/1433) [`c9bf4f05`](https://github.com/thirdweb-dev/js/commit/c9bf4f05c161707c6eb799ecef82f462e4a82405) Thanks [@iketw](https://github.com/iketw)! - Pass empty string when clientId is undefined
- [#1439](https://github.com/thirdweb-dev/js/pull/1439) [`67b145b9`](https://github.com/thirdweb-dev/js/commit/67b145b94dafb2fb1d90553ed4829f0ed22e2907) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Expose deployPublishContract alias
- [#1437](https://github.com/thirdweb-dev/js/pull/1437) [`5ee700e8`](https://github.com/thirdweb-dev/js/commit/5ee700e80438650fa253c25c0bee6658ce68d2cf) Thanks [@farhanW3](https://github.com/farhanW3)! - Updated the error messages
- Updated dependencies [[`fd981655`](https://github.com/thirdweb-dev/js/commit/fd9816556fe595b0c764e34dbcf15b0ad1677edb), [`9f5adc5c`](https://github.com/thirdweb-dev/js/commit/9f5adc5c5c2782ffb878759df481e5fb1e1740e5), [`9f5adc5c`](https://github.com/thirdweb-dev/js/commit/9f5adc5c5c2782ffb878759df481e5fb1e1740e5)]:
- @thirdweb-dev/chains@0.1.41
- @thirdweb-dev/contracts-js@1.3.11
## 3.10.43
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/sdk",
"version": "3.10.43",
"version": "3.10.48",
"description": "The main thirdweb SDK.",
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/sdk",
"license": "Apache-2.0",
+68 -15
View File
@@ -8,6 +8,8 @@ import { getValidChainRPCs } from "@thirdweb-dev/chains";
import type { Chain } from "@thirdweb-dev/chains";
import { providers } from "ethers";
import type { Signer } from "ethers";
import pkg from "../../../package.json";
import { isBrowser } from "@thirdweb-dev/storage";
/**
* @internal
@@ -60,7 +62,7 @@ export function getChainProvider(
// if we still don't have an url fall back to just using the chainId or slug in the rpc and try that
if (!rpcUrl) {
rpcUrl = `https://${chainId || network}.rpc.thirdweb.com/${
options.clientId
options.clientId || ""
}`;
}
@@ -159,26 +161,76 @@ export function getProviderFromRpcUrl(
) {
try {
const headers: HeadersInit = {};
// will be used to make sure we don't cache providers with different auth strategies
let authStrategy = "none";
if (isTwUrl(rpcUrl)) {
if (sdkOptions?.clientId) {
headers["x-client-id"] = sdkOptions?.clientId;
// bundleId may already be injected
if (!rpcUrl.includes("bundleId")) {
rpcUrl =
rpcUrl +
(typeof globalThis !== "undefined" && "APP_BUNDLE_ID" in globalThis
? `?bundleId=${(globalThis as any).APP_BUNDLE_ID as string}`
: "");
// if we have a secret key passed in the SDK options we want to always use that
if (sdkOptions?.secretKey) {
// compute the clientId from the secret key
// should only be used on Node.js in a backend/script context
if (typeof window !== "undefined") {
throw new Error("Cannot use secretKey in browser context");
}
} else if (sdkOptions?.secretKey) {
headers["x-secret-key"] = sdkOptions?.secretKey;
// this is on purpose because we're using the crypto module only in node
// try to trick webpack :)
const pto = "pto";
// eslint-disable-next-line @typescript-eslint/no-var-requires
const crypto = require("cry" + pto);
const hashedSecretKey = crypto
.createHash("sha256")
.update(sdkOptions.secretKey)
.digest("hex");
const derivedClientId = hashedSecretKey.slice(0, 32);
const utilizedRpcUrl = new URL(rpcUrl);
// always set the clientId on the path to the derived client id
utilizedRpcUrl.pathname = derivedClientId;
// set the headers
headers["x-client-id"] = derivedClientId;
headers["x-secret-key"] = sdkOptions.secretKey;
// set the final rpc url
rpcUrl = utilizedRpcUrl.toString();
authStrategy = "secretKey";
}
// if we have a tw auth token on global context add it to the headers
if (typeof globalThis !== "undefined" && "TW_AUTH_TOKEN" in globalThis) {
// if we do NOT have a secret key but we have a client id we want to use that
else if (sdkOptions?.clientId) {
const utilizedRpcUrl = new URL(rpcUrl);
// always set the clientId on the path to the client id
utilizedRpcUrl.pathname = sdkOptions.clientId;
// set the headers
headers["x-client-id"] = sdkOptions.clientId;
// set the final rpc url
rpcUrl = utilizedRpcUrl.toString();
authStrategy = "clientId";
}
// if we *also* have a tw auth token on global context add it to the headers (in addition to anything else)
if (
typeof globalThis !== "undefined" &&
"TW_AUTH_TOKEN" in globalThis &&
typeof (globalThis as any).TW_AUTH_TOKEN === "string"
) {
headers["authorization"] = `Bearer ${
(globalThis as any).TW_AUTH_TOKEN as string
}`;
authStrategy = "twAuthToken";
}
const bundleId =
typeof globalThis !== "undefined" && "APP_BUNDLE_ID" in globalThis
? ((globalThis as any).APP_BUNDLE_ID as string)
: undefined;
if (!rpcUrl.includes("bundleId")) {
rpcUrl = rpcUrl + (bundleId ? `?bundleId=${bundleId}` : "");
}
headers["x-sdk-version"] = pkg.version;
headers["x-sdk-name"] = pkg.name;
headers["x-sdk-platform"] = bundleId
? "react-native"
: isBrowser()
? "browser"
: "node";
}
const match = rpcUrl.match(/^(ws|http)s?:/i);
// Try the JSON batch provider if available
@@ -187,7 +239,7 @@ export function getProviderFromRpcUrl(
case "http":
case "https":
// Create a unique cache key for these params
const seralizedOpts = `${rpcUrl}-${chainId || -1}`;
const seralizedOpts = `${rpcUrl}-${chainId || -1}-${authStrategy}`;
// Check if we have a provider in our cache already
const existingProvider = RPC_PROVIDER_MAP.get(seralizedOpts);
@@ -217,6 +269,7 @@ export function getProviderFromRpcUrl(
case "ws":
case "wss":
// Use the WebSocketProvider for ws:// URLs
// TODO: handle auth for WS at some point
return new providers.WebSocketProvider(rpcUrl, chainId);
}
}
@@ -270,6 +270,39 @@ export class Account<TContract extends IAccountCore>
);
}
/**
* Get all admins and non-admin signers with permissions to use the account.
*
* @example
* ```javascript
* const allAdminsAndSigners = await contract.account.getAllAdminsAndSigners();
* ```
*
* @returns all admins and non-admin signers with permissions to use the account.
*
* @twfeature Account
*/
public async getAllAdminsAndSigners(): Promise<SignerWithPermissions[]> {
const allAdmins = await this.getAllAdmins();
const transformedAdmins: SignerWithPermissions[] = allAdmins.map(
(admin) => {
return {
isAdmin: true,
signer: admin,
permissions: {
startDate: new Date(0),
expirationDate: new Date(0),
nativeTokenLimitPerTransaction: BigNumber.from(0),
approvedCallTargets: [],
},
};
},
);
const allSigners = await this.getAllSigners();
return [...transformedAdmins, ...allSigners];
}
/*********************************
* WRITE FUNCTIONS
********************************/
@@ -158,14 +158,17 @@ export class DropClaimConditions<
await this.contractWrapper.readContract.claimCondition();
const startId = currentStartId.toNumber();
const count = countBn.toNumber();
const conditions: AbstractClaimConditionContractStruct[] = [];
const conditions: Promise<AbstractClaimConditionContractStruct>[] = [];
for (let i = startId; i < startId + count; i++) {
conditions.push(await this.get(i));
conditions.push(this.get(i));
}
const metadata = await this.metadata.get();
const decimals = await this.getTokenDecimals();
const [metadata, decimals, ...fetchedConditions] = await Promise.all([
this.metadata.get(),
this.getTokenDecimals(),
...conditions,
]);
return Promise.all(
conditions.map((c) =>
fetchedConditions.map((c) =>
transformResultToClaimCondition(
c,
decimals,
@@ -363,7 +366,25 @@ export class DropClaimConditions<
"Merkle proof verification failed:",
"reason" in e ? e.reason : e,
);
reasons.push(ClaimEligibility.AddressNotAllowed);
const reason = (e as any).reason;
switch (reason) {
case "!Qty":
reasons.push(ClaimEligibility.OverMaxClaimablePerWallet);
break;
case "!PriceOrCurrency":
reasons.push(ClaimEligibility.WrongPriceOrCurrency);
break;
case "!MaxSupply":
reasons.push(ClaimEligibility.NotEnoughSupply);
break;
case "cant claim yet":
reasons.push(ClaimEligibility.ClaimPhaseNotStarted);
break;
default: {
reasons.push(ClaimEligibility.AddressNotAllowed);
break;
}
}
return reasons;
}
}
@@ -373,38 +394,35 @@ export class DropClaimConditions<
this.isNewSinglePhaseDrop(this.contractWrapper) ||
this.isNewMultiphaseDrop(this.contractWrapper)
) {
const claimerProofs = await this.getClaimerProofs(resolvedAddress);
let claimedSupply = BigNumber.from(0);
let maxClaimable = convertQuantityToBigNumber(
claimCondition.maxClaimablePerWallet,
decimals,
);
if (this.isNewSinglePhaseDrop(this.contractWrapper)) {
claimedSupply =
await this.contractWrapper.readContract.getSupplyClaimedByWallet(
resolvedAddress,
);
try {
claimedSupply = await this.getSupplyClaimedByWallet(resolvedAddress);
} catch (e) {
// no-op
}
if (this.isNewMultiphaseDrop(this.contractWrapper)) {
const activeClaimConditionId =
await this.contractWrapper.readContract.getActiveClaimConditionId();
claimedSupply =
await this.contractWrapper.readContract.getSupplyClaimedByWallet(
activeClaimConditionId,
resolvedAddress,
);
}
if (claimerProofs) {
if (allowListEntry) {
maxClaimable = convertQuantityToBigNumber(
claimerProofs.maxClaimable,
allowListEntry.maxClaimable,
decimals,
);
}
if (
maxClaimable.gt(0) &&
maxClaimable.lt(claimedSupply.add(quantityWithDecimals))
) {
reasons.push(ClaimEligibility.OverMaxClaimablePerWallet);
return reasons;
}
// if there is no allowlist, or if there is an allowlist and the address is not in it
// if maxClaimable is 0, we consider it as the address is not allowed
if (!hasAllowList || (hasAllowList && !allowListEntry)) {
if (maxClaimable.lte(claimedSupply) || maxClaimable.eq(0)) {
reasons.push(ClaimEligibility.AddressNotAllowed);
@@ -505,6 +523,34 @@ export class DropClaimConditions<
}
}
/**
* Get the total supply claimed by a specific wallet
* @param walletAddress the wallet address to check
* @returns the total supply claimed
*/
public async getSupplyClaimedByWallet(
walletAddress: AddressOrEns,
): Promise<BigNumber> {
const resolvedAddress = await resolveAddress(walletAddress);
if (this.isNewSinglePhaseDrop(this.contractWrapper)) {
return await this.contractWrapper.readContract.getSupplyClaimedByWallet(
resolvedAddress,
);
}
if (this.isNewMultiphaseDrop(this.contractWrapper)) {
const activeClaimConditionId =
await this.contractWrapper.readContract.getActiveClaimConditionId();
return await this.contractWrapper.readContract.getSupplyClaimedByWallet(
activeClaimConditionId,
resolvedAddress,
);
}
throw new Error(
"This contract does not support the getSupplyClaimedByWallet function",
);
}
/** ***************************************
* WRITE FUNCTIONS
*****************************************/
@@ -50,6 +50,7 @@ import { processClaimConditionInputs } from "../../common/claim-conditions/proce
import { transformResultToClaimCondition } from "../../common/claim-conditions/transformResultToClaimCondition";
import { updateExistingClaimConditions } from "../../common/claim-conditions/updateExistingClaimConditions";
import { fetchSnapshotEntryForAddress } from "../../common/claim-conditions/fetchSnapshotEntryForAddress";
import { convertQuantityToBigNumber } from "../../common/claim-conditions/convertQuantityToBigNumber";
/**
* Manages claim conditions for Edition Drop contracts
@@ -371,7 +372,25 @@ export class DropErc1155ClaimConditions<
"Merkle proof verification failed:",
"reason" in e ? e.reason : e,
);
reasons.push(ClaimEligibility.AddressNotAllowed);
const reason = (e as any).reason;
switch (reason) {
case "!Qty":
reasons.push(ClaimEligibility.OverMaxClaimablePerWallet);
break;
case "!PriceOrCurrency":
reasons.push(ClaimEligibility.WrongPriceOrCurrency);
break;
case "!MaxSupply":
reasons.push(ClaimEligibility.NotEnoughSupply);
break;
case "cant claim yet":
reasons.push(ClaimEligibility.ClaimPhaseNotStarted);
break;
default: {
reasons.push(ClaimEligibility.AddressNotAllowed);
break;
}
}
return reasons;
}
}
@@ -381,8 +400,37 @@ export class DropErc1155ClaimConditions<
this.isNewSinglePhaseDrop(this.contractWrapper) ||
this.isNewMultiphaseDrop(this.contractWrapper)
) {
let claimedSupply = BigNumber.from(0);
let maxClaimable = convertQuantityToBigNumber(
claimCondition.maxClaimablePerWallet,
0,
);
try {
claimedSupply = await this.getSupplyClaimedByWallet(
tokenId,
resolvedAddress,
);
} catch (e) {
// no-op
}
if (allowListEntry) {
maxClaimable = convertQuantityToBigNumber(
allowListEntry.maxClaimable,
0,
);
}
if (maxClaimable.gt(0) && maxClaimable.lt(claimedSupply.add(quantity))) {
reasons.push(ClaimEligibility.OverMaxClaimablePerWallet);
return reasons;
}
// if there is no allowlist, or if there is an allowlist and the address is not in it
// if maxClaimable is 0, we consider it as the address is not allowed
if (!hasAllowList || (hasAllowList && !allowListEntry)) {
if (claimCondition.maxClaimablePerWallet === "0") {
if (maxClaimable.lte(claimedSupply) || maxClaimable.eq(0)) {
reasons.push(ClaimEligibility.AddressNotAllowed);
return reasons;
}
@@ -483,6 +531,39 @@ export class DropErc1155ClaimConditions<
}
}
/**
* Get the total supply claimed by a specific wallet
* @param walletAddress the wallet address to check
* @returns the total supply claimed
*/
public async getSupplyClaimedByWallet(
tokenId: BigNumberish,
walletAddress: AddressOrEns,
): Promise<BigNumber> {
const resolvedAddress = await resolveAddress(walletAddress);
if (this.isNewSinglePhaseDrop(this.contractWrapper)) {
return await this.contractWrapper.readContract.getSupplyClaimedByWallet(
tokenId,
resolvedAddress,
);
}
if (this.isNewMultiphaseDrop(this.contractWrapper)) {
const activeClaimConditionId =
await this.contractWrapper.readContract.getActiveClaimConditionId(
tokenId,
);
return await this.contractWrapper.readContract.getSupplyClaimedByWallet(
tokenId,
activeClaimConditionId,
resolvedAddress,
);
}
throw new Error(
"This contract does not support the getSupplyClaimedByWallet function",
);
}
/** ***************************************
* WRITE FUNCTIONS
*****************************************/
@@ -87,6 +87,10 @@ export class Erc721SharedMetadata implements DetectableFeature {
metadata: BasicNFTInput,
): Promise<Transaction<TransactionResult>> => {
const parsedMetadata = BasicNFTInput.parse(metadata);
// cleanup description
parsedMetadata.description = this.sanitizeJSONString(
parsedMetadata.description,
);
// take the input and upload image and animation if it is not a URI already
const batch = [];
@@ -122,4 +126,12 @@ export class Erc721SharedMetadata implements DetectableFeature {
});
},
);
private sanitizeJSONString(val: string | undefined | null) {
if (!val) {
return val;
}
const sanitized = JSON.stringify(val);
return sanitized.slice(1, sanitized.length - 1);
}
}
+84 -6
View File
@@ -89,6 +89,7 @@ import type {
DeployOptions,
DeployMetadata,
DeployEvent,
OpenEditionContractDeployMetadata,
} from "../types/deploy";
import type { ContractWithMetadata } from "../types/registry";
import { DeploySchemaForPrebuiltContractType } from "../contracts";
@@ -113,6 +114,7 @@ import { LoyaltyCardContractDeploy } from "../schema/contracts/loyalty-card";
import { getDefaultTrustedForwarders } from "../constants";
import { checkClientIdOrSecretKey } from "../../core/utils/apiKey";
import { getProcessEnv } from "../../core/utils/process";
import { DropErc721ContractSchema } from "../schema";
/**
* The main entry point for the thirdweb SDK
@@ -263,11 +265,15 @@ export class ThirdwebSDK extends RPCConnectionHandler {
storage?: IThirdwebStorage,
) {
const apiKeyType = typeof window !== "undefined" ? "clientId" : "secretKey";
checkClientIdOrSecretKey(
`No ${apiKeyType} provided in ThirdwebSDK. You will have limited access to thirdweb's services for storage, RPC, and account abstraction. You can get a ${apiKeyType} from https://thirdweb.com/create-api-key`,
options.clientId,
options.secretKey,
);
let warnMessage = `No API key. Please provide a ${apiKeyType}. It is required to access thirdweb's services. You can create a key at https://thirdweb.com/create-api-key`;
if (
typeof window === "undefined" &&
!options.secretKey &&
options.clientId
) {
warnMessage = `Please provide a secret key instead of the clientId. Create a new API Key at https://thirdweb.com/create-api-key`;
}
checkClientIdOrSecretKey(warnMessage, options.clientId, options.secretKey);
if (isChainConfig(network)) {
options = {
@@ -1021,6 +1027,63 @@ export class ContractDeployer extends RPCConnectionHandler {
},
);
/**
* Deploys a new OpenEditionERC721 contract
*
* @remarks Deploys a OpenEdition contract and returns the address of the deployed contract
*
* @example
* ```javascript
* const contractAddress = await sdk.deployer.deployOpenEdition({
* name: "My Open Edition",
* primary_sale_recipient: "your-address",
* });
* ```
* @param metadata - the contract metadata
* @returns the address of the deployed contract
*/
deployOpenEdition = /* @__PURE__ */ buildDeployTransactionFunction(
async (
metadata: OpenEditionContractDeployMetadata,
options?: DeployOptions,
): Promise<DeployTransaction> => {
const parsedMetadata = await DropErc721ContractSchema.deploy.parseAsync(
metadata,
);
const contractURI = await this.storage.upload(parsedMetadata);
const chainId = (await this.getProvider().getNetwork()).chainId;
const trustedForwarders = getDefaultTrustedForwarders(chainId);
// add default forwarders to any custom forwarders passed in
if (
metadata.trusted_forwarders &&
metadata.trusted_forwarders.length > 0
) {
trustedForwarders.push(...metadata.trusted_forwarders);
}
const signerAddress = await this.getSigner()?.getAddress();
const deployArgs = [
signerAddress,
parsedMetadata.name,
parsedMetadata.symbol,
contractURI,
trustedForwarders,
parsedMetadata.primary_sale_recipient,
parsedMetadata.fee_recipient,
parsedMetadata.seller_fee_basis_points,
];
return await this.deployReleasedContract.prepare(
THIRDWEB_DEPLOYER,
"OpenEditionERC721",
deployArgs,
options,
);
},
);
/**
* Deploys a new SignatureDrop contract
*
@@ -1457,6 +1520,8 @@ export class ContractDeployer extends RPCConnectionHandler {
* @param publisherAddress the address of the publisher
* @param contractName the name of the contract to deploy
* @param constructorParams the constructor params to pass to the contract
*
* @deprecated use deployPublishedContract instead
*/
deployReleasedContract = /* @__PURE__ */ buildDeployTransactionFunction(
async (
@@ -1479,6 +1544,16 @@ export class ContractDeployer extends RPCConnectionHandler {
},
);
/**
* Deploy any published contract by its name
* @param publisherAddress the address of the publisher
* @param contractName the name of the contract to deploy
* @param constructorParams the constructor params to pass to the contract
* @param version Optional: the version of the contract to deploy or "latest"
* @param options Optional: the deploy options
*/
deployPublishedContract = this.deployReleasedContract;
/**
* Deploy a proxy contract of a given implementation via the given factory
* @param factoryAddress
@@ -2123,7 +2198,10 @@ export class ContractDeployer extends RPCConnectionHandler {
version: string,
) {
const address = await resolveAddress(publisherAddress);
const publishedContract = await new ThirdwebSDK("polygon")
const publishedContract = await new ThirdwebSDK("polygon", {
clientId: this.options.clientId,
secretKey: this.options.secretKey,
})
.getPublisher()
.getVersion(address, contractName, version);
if (!publishedContract) {
@@ -5,8 +5,14 @@ export enum ClaimEligibility {
WaitBeforeNextClaimTransaction = "Not enough time since last claim transaction. Please wait.",
ClaimPhaseNotStarted = "Claim phase has not started yet.",
AlreadyClaimed = "You have already claimed the token.",
WrongPriceOrCurrency = "Incorrect price or currency.",
OverMaxClaimablePerWallet = "Cannot claim more than maximum allowed quantity.",
NotEnoughTokens = "There are not enough tokens in the wallet to pay for the claim.",
NoActiveClaimPhase = "There is no active claim phase at the moment. Please check back in later.",
+1
View File
@@ -23,6 +23,7 @@ export type SignerPermissionsInput = z.input<typeof SignerPermissionsSchema>;
export type SignerPermissionsOutput = z.output<typeof SignerPermissionsSchema>;
export type SignerWithPermissions = {
isAdmin?: boolean;
signer: string;
permissions: SignerPermissions;
};
@@ -58,6 +58,53 @@ export interface NFTContractDeployMetadata {
app_uri?: string;
}
/**
* Options for deploying an OpenEdition contract
* @public
*/
export interface OpenEditionContractDeployMetadata {
/**
* name of the contract
*/
name: string;
/**
* Optional description of the contract
*/
description?: string;
/**
* Optional image for the contract
*/
image?: FileOrBufferOrString;
/**
* Optional url for the contract
*/
external_link?: string;
/**
* Symbol for the NFTs
*/
symbol?: string;
/**
* Custom gasless trusted forwarder addresses
*/
trusted_forwarders?: AddressOrEns[];
/**
* The address that will receive the proceeds from primary sales
*/
primary_sale_recipient: AddressOrEns;
/**
* The address that will receive the proceeds from secondary sales (royalties)
*/
fee_recipient?: AddressOrEns;
/**
* The percentage (in basis points) of royalties for secondary sales
*/
seller_fee_basis_points?: number;
/**
* The default app for this contract
*/
app_uri?: string;
}
/**
* Options for deploying a Token contract
* @public
@@ -326,6 +326,121 @@ describe("Edition Drop Contract (V4)", async () => {
expect(balance).to.deep.equal(BigNumber.from(2));
});
it("should check eligibility reasons max claims per wallet with allowlist", async () => {
await bdContract.createBatch([
{ name: "name", description: "description" },
]);
await bdContract.claimConditions.set(0, [{}]);
let c = await bdContract.claimConditions.getSupplyClaimedByWallet(
0,
w1.address,
);
expect(c.toNumber()).to.eq(0);
await sdk.updateSignerOrProvider(w1);
await bdContract.claim(0, 2);
c = await bdContract.claimConditions.getSupplyClaimedByWallet(
0,
w1.address,
);
expect(c.toNumber()).to.eq(2);
});
it("should check eligibility reasons max claims per wallet with allowlist", async () => {
await bdContract.createBatch([
{ name: "name", description: "description" },
]);
await bdContract.claimConditions.set(0, [
{
maxClaimablePerWallet: 0,
snapshot: [
{ address: w1.address, maxClaimable: 2 },
{ address: w2.address, maxClaimable: 1 },
],
},
]);
let r = await bdContract.claimConditions.getClaimIneligibilityReasons(
0,
2,
await w1.getAddress(),
);
expect(r.length).to.eq(0);
r = await bdContract.claimConditions.getClaimIneligibilityReasons(
0,
10,
await w1.getAddress(),
);
expect(r[0]).to.eq(ClaimEligibility.OverMaxClaimablePerWallet);
r = await bdContract.claimConditions.getClaimIneligibilityReasons(
0,
10,
await w3.getAddress(),
);
expect(r[0]).to.eq(ClaimEligibility.AddressNotAllowed);
await sdk.updateSignerOrProvider(w1);
await bdContract.claim(0, 2);
r = await bdContract.claimConditions.getClaimIneligibilityReasons(
0,
1,
await w1.getAddress(),
);
expect(r[0]).to.eq(ClaimEligibility.OverMaxClaimablePerWallet);
r = await bdContract.claimConditions.getClaimIneligibilityReasons(
0,
2,
await w2.getAddress(),
);
expect(r[0]).to.eq(ClaimEligibility.OverMaxClaimablePerWallet);
r = await bdContract.claimConditions.getClaimIneligibilityReasons(
0,
2,
await w3.getAddress(),
);
expect(r[0]).to.eq(ClaimEligibility.AddressNotAllowed);
});
it("should check eligibility reasons max claims per wallet", async () => {
await bdContract.createBatch([
{ name: "name", description: "description" },
]);
await bdContract.claimConditions.set(0, [
{
maxClaimablePerWallet: 1,
},
]);
let r = await bdContract.claimConditions.getClaimIneligibilityReasons(
0,
1,
await w1.getAddress(),
);
expect(r.length).to.eq(0);
r = await bdContract.claimConditions.getClaimIneligibilityReasons(
0,
2,
await w1.getAddress(),
);
expect(r[0]).to.eq(ClaimEligibility.OverMaxClaimablePerWallet);
await sdk.updateSignerOrProvider(w1);
await bdContract.claim(0, 1);
r = await bdContract.claimConditions.getClaimIneligibilityReasons(
0,
1,
await w1.getAddress(),
);
expect(r[0]).to.eq(ClaimEligibility.OverMaxClaimablePerWallet);
});
it("should return addresses of all the claimers", async () => {
await bdContract.createBatch([
{
+2
View File
@@ -7,6 +7,8 @@ describe("Error Handling", async () => {
it("should throw proper error on account with no balance", async () => {
const newSdk = ThirdwebSDK.fromSigner(
Wallet.createRandom().connect(sdk.getProvider()),
undefined,
{ secretKey: process.env.TW_SECRET_KEY },
);
try {
+111
View File
@@ -594,6 +594,117 @@ describe("NFT Drop Contract (v4)", async () => {
}
});
it("should check eligibility reasons max claims per wallet with allowlist", async () => {
await dropContract.createBatch([
{ name: "name", description: "description" },
{ name: "name2", description: "description" },
{ name: "name3", description: "description" },
{ name: "name4", description: "description" },
]);
await dropContract.claimConditions.set([{}]);
let c = await dropContract.claimConditions.getSupplyClaimedByWallet(
w1.address,
);
expect(c.toNumber()).to.eq(0);
await sdk.updateSignerOrProvider(w1);
await dropContract.claim(2);
c = await dropContract.claimConditions.getSupplyClaimedByWallet(w1.address);
expect(c.toNumber()).to.eq(2);
});
it("should check eligibility reasons max claims per wallet with allowlist", async () => {
await dropContract.createBatch([
{ name: "name", description: "description" },
{ name: "name2", description: "description" },
{ name: "name3", description: "description" },
{ name: "name4", description: "description" },
]);
await dropContract.claimConditions.set([
{
maxClaimablePerWallet: 0,
snapshot: [
{ address: w1.address, maxClaimable: 2 },
{ address: w2.address, maxClaimable: 1 },
],
},
]);
let r = await dropContract.claimConditions.getClaimIneligibilityReasons(
2,
await w1.getAddress(),
);
expect(r.length).to.eq(0);
r = await dropContract.claimConditions.getClaimIneligibilityReasons(
10,
await w1.getAddress(),
);
expect(r[0]).to.eq(ClaimEligibility.OverMaxClaimablePerWallet);
r = await dropContract.claimConditions.getClaimIneligibilityReasons(
10,
await w3.getAddress(),
);
expect(r[0]).to.eq(ClaimEligibility.AddressNotAllowed);
await sdk.updateSignerOrProvider(w1);
await dropContract.claim(2);
r = await dropContract.claimConditions.getClaimIneligibilityReasons(
1,
await w1.getAddress(),
);
expect(r[0]).to.eq(ClaimEligibility.OverMaxClaimablePerWallet);
r = await dropContract.claimConditions.getClaimIneligibilityReasons(
2,
await w2.getAddress(),
);
expect(r[0]).to.eq(ClaimEligibility.OverMaxClaimablePerWallet);
r = await dropContract.claimConditions.getClaimIneligibilityReasons(
2,
await w3.getAddress(),
);
expect(r[0]).to.eq(ClaimEligibility.AddressNotAllowed);
});
it("should check eligibility reasons max claims per wallet", async () => {
await dropContract.createBatch([
{ name: "name", description: "description" },
{ name: "name2", description: "description" },
{ name: "name3", description: "description" },
{ name: "name4", description: "description" },
]);
await dropContract.claimConditions.set([
{
maxClaimablePerWallet: 1,
},
]);
let r = await dropContract.claimConditions.getClaimIneligibilityReasons(
1,
await w1.getAddress(),
);
expect(r.length).to.eq(0);
r = await dropContract.claimConditions.getClaimIneligibilityReasons(
2,
await w1.getAddress(),
);
expect(r[0]).to.eq(ClaimEligibility.OverMaxClaimablePerWallet);
await sdk.updateSignerOrProvider(w1);
await dropContract.claim(1);
r = await dropContract.claimConditions.getClaimIneligibilityReasons(
1,
await w1.getAddress(),
);
expect(r[0]).to.eq(ClaimEligibility.OverMaxClaimablePerWallet);
});
it("should return the newly claimed token", async () => {
await dropContract.claimConditions.set([{}]);
await dropContract.createBatch([
+48
View File
@@ -1,5 +1,53 @@
# @thirdweb-dev/service-utils
## 0.4.2
### Patch Changes
- [#1463](https://github.com/thirdweb-dev/js/pull/1463) [`205018f6`](https://github.com/thirdweb-dev/js/commit/205018f6e191d2a2fb89f1bee276d0ea89643f30) Thanks [@arcoraven](https://github.com/arcoraven)! - Add userOpHash
- [#1473](https://github.com/thirdweb-dev/js/pull/1473) [`73462ef1`](https://github.com/thirdweb-dev/js/commit/73462ef10800aeeb5976634e2bc6fb5d3e8501e4) Thanks [@jnsdls](https://github.com/jnsdls)! - handle possible empty auth tokens
- [#1475](https://github.com/thirdweb-dev/js/pull/1475) [`cf82c3b5`](https://github.com/thirdweb-dev/js/commit/cf82c3b58c92714d006f3880ec84a84893da861b) Thanks [@nessup](https://github.com/nessup)! - Improve error logging when authentication fails
- [#1466](https://github.com/thirdweb-dev/js/pull/1466) [`7e483ec9`](https://github.com/thirdweb-dev/js/commit/7e483ec97f576067d0220518b86d00794da94b4d) Thanks [@arcoraven](https://github.com/arcoraven)! - Silence output for publishUsageEvents()
## 0.4.1
### Patch Changes
- [#1454](https://github.com/thirdweb-dev/js/pull/1454) [`6979810d`](https://github.com/thirdweb-dev/js/commit/6979810de5c3fc700c7b5b91ac1567d9cdf50607) Thanks [@nessup](https://github.com/nessup)! - Update error message
## 0.4.0
### Minor Changes
- [#1448](https://github.com/thirdweb-dev/js/pull/1448) [`3e1c4045`](https://github.com/thirdweb-dev/js/commit/3e1c4045e7c58e2fe58e2ab6a7f767c8f5e206e9) Thanks [@arcoraven](https://github.com/arcoraven)! - Add logHttpRequest helper func
### Patch Changes
- [#1453](https://github.com/thirdweb-dev/js/pull/1453) [`0647f124`](https://github.com/thirdweb-dev/js/commit/0647f12498ed1cdd5aca4dcea5bd3cf0d5d3a23b) Thanks [@arcoraven](https://github.com/arcoraven)! - Remove clientId from logRequest
## 0.3.1
### Patch Changes
- [#1447](https://github.com/thirdweb-dev/js/pull/1447) [`b103872d`](https://github.com/thirdweb-dev/js/commit/b103872daff87b032082a433713d16b9dee13082) Thanks [@nessup](https://github.com/nessup)! - Export extractAuthorizationData for CF Workers
## 0.3.0
### Minor Changes
- [#1424](https://github.com/thirdweb-dev/js/pull/1424) [`d01e6396`](https://github.com/thirdweb-dev/js/commit/d01e6396d176a162aaacb10792f0d9abade62a1f) Thanks [@arcoraven](https://github.com/arcoraven)! - Add helper to publish usage events from backend services
### Patch Changes
- [#1440](https://github.com/thirdweb-dev/js/pull/1440) [`5ec0f064`](https://github.com/thirdweb-dev/js/commit/5ec0f064d27acbd5225934dfe8ea2cd2c5af3997) Thanks [@arcoraven](https://github.com/arcoraven)! - [service-utils] Switch to aws4fetch to call AWS services
- [#1437](https://github.com/thirdweb-dev/js/pull/1437) [`5ee700e8`](https://github.com/thirdweb-dev/js/commit/5ee700e80438650fa253c25c0bee6658ce68d2cf) Thanks [@farhanW3](https://github.com/farhanW3)! - Updated the error messages
- [#1435](https://github.com/thirdweb-dev/js/pull/1435) [`2a3cd62d`](https://github.com/thirdweb-dev/js/commit/2a3cd62dc9af6accae5c2c48ef4956d139a637e4) Thanks [@arcoraven](https://github.com/arcoraven)! - Use string type for gasPricePerUnit
## 0.2.5
### Patch Changes
+7 -1
View File
@@ -1 +1,7 @@
### todo
# Service Utils
This repository contains utilities that handle common requirements for thirdweb services.
- `/cf-worker`: for use only in Cloudflare Worker services
- `/node`: for use in self-hosted services
- `/core`: for use in any service
+6 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/service-utils",
"version": "0.2.5",
"version": "0.4.2",
"main": "dist/thirdweb-dev-service-utils.cjs.js",
"module": "dist/thirdweb-dev-service-utils.esm.js",
"exports": {
@@ -41,6 +41,7 @@
"devDependencies": {
"@cloudflare/workers-types": "^4.20230724.0",
"@preconstruct/cli": "2.7.0",
"@smithy/types": "^2.0.2",
"@thirdweb-dev/tsconfig": "workspace:*",
"@types/jest": "^29.5.3",
"@types/node": "^18.17.1",
@@ -61,5 +62,9 @@
"build": "tsc && preconstruct build",
"push": "yalc push",
"test": "jest"
},
"dependencies": {
"aws4fetch": "^1.0.17",
"zod": "^3.20.2"
}
}
+42 -3
View File
@@ -1,4 +1,8 @@
import type { ExecutionContext, KVNamespace } from "@cloudflare/workers-types";
import type {
ExecutionContext,
KVNamespace,
Response,
} from "@cloudflare/workers-types";
import type {
ApiKeyMetadata,
AccountMetadata,
@@ -12,6 +16,7 @@ import type { AuthorizationResult } from "../core/authorize/types";
import type { CoreAuthInput } from "../core/types";
export * from "../core/services";
export * from "./usage";
type WorkerServiceConfig = CoreServiceConfig & {
kvStore: KVNamespace;
@@ -72,7 +77,7 @@ export async function authorizeWorker(
});
}
async function extractAuthorizationData(
export async function extractAuthorizationData(
authInput: AuthInput,
): Promise<AuthorizationInput> {
const requestUrl = new URL(authInput.req.url);
@@ -133,7 +138,7 @@ async function extractAuthorizationData(
const authHeader = headers.get("authorization");
if (authHeader) {
const [type, token] = authHeader.split(" ");
if (type.toLowerCase() === "bearer") {
if (type.toLowerCase() === "bearer" && !!token) {
jwt = token;
}
}
@@ -166,3 +171,37 @@ function bufferToHex(buffer: ArrayBuffer) {
.map((x) => x.toString(16).padStart(2, "0"))
.join("");
}
export async function logHttpRequest({
source,
clientId,
req,
res,
isAuthed,
statusMessage,
}: AuthInput & {
source: string;
res: Response;
isAuthed?: boolean;
statusMessage?: Error | string;
}) {
const authorizationData = await extractAuthorizationData({ req, clientId });
const headers = req.headers;
console.log(
JSON.stringify({
source,
pathname: req.url,
hasSecretKey: !!authorizationData.secretKey,
hasClientId: !!authorizationData.clientId,
hasJwt: !!authorizationData.jwt,
clientId: authorizationData.clientId,
isAuthed: !!isAuthed ?? null,
status: res.status,
sdkName: headers.get("x-sdk-name") ?? "unknown",
sdkVersion: headers.get("x-sdk-version") ?? "unknown",
platform: headers.get("x-sdk-platform") ?? "unknown",
}),
);
console.log(`statusMessage=${statusMessage ?? res.statusText}`);
}
@@ -0,0 +1,100 @@
import { AwsClient } from "aws4fetch";
import { z } from "zod";
// Initialize a singleton for aws usage.
let _aws: AwsClient | undefined;
function getAws(options: ConstructorParameters<typeof AwsClient>[0]) {
if (!_aws) {
_aws = new AwsClient(options);
}
return _aws;
}
/**
* Types
*/
const usageEventSchema = z.object({
source: z.enum([
"wallet",
"rpc",
"storage",
"bundler",
"paymaster",
"relayer",
]),
action: z.string(),
accountId: z.string(),
// Optional
apiKeyId: z.string().optional(),
creatorWalletAddress: z.string().optional(),
clientId: z.string().optional(),
walletAddress: z.string().optional(),
chainId: z.number().int().positive().optional(),
provider: z.string().optional(),
mimeType: z.string().optional(),
fileSize: z.number().int().nonnegative().optional(),
fileCid: z.string().optional(),
transactionHash: z.string().optional(),
gasLimit: z.number().nonnegative().optional(),
gasPricePerUnit: z.string().optional(),
userOpHash: z.string().optional(),
});
export type UsageEvent = z.infer<typeof usageEventSchema>;
/**
* Publish usage events. Provide the relevant fields for your application.
*
* Usage in Cloudflare Workers:
* ctx.waitUntil(
* publishUsageEvents(
* [event1, event2],
* { queueUrl, accessKeyId, secretAccessKey },
* )
* )
*
* @param usageEvents
* @param config
*/
export async function publishUsageEvents(
usageEvents: UsageEvent[],
config: {
queueUrl: string;
accessKeyId: string;
secretAccessKey: string;
region?: string;
},
): Promise<void> {
const {
queueUrl,
accessKeyId,
secretAccessKey,
region = "us-west-2",
} = config;
const entries = usageEvents.map((event) => {
// Enforce schema of usage event.
const parsed = usageEventSchema.parse(event);
return {
Id: crypto.randomUUID(),
MessageBody: JSON.stringify(parsed),
};
});
const aws = getAws({
accessKeyId,
secretAccessKey,
region,
});
await aws.fetch(`https://sqs.${region}.amazonaws.com`, {
headers: {
"X-Amz-Target": "AmazonSQS.SendMessageBatch",
"X-Amz-Date": new Date().toISOString(),
"Content-Type": "application/x-amz-json-1.0",
},
body: JSON.stringify({
QueueUrl: queueUrl,
Entries: entries,
}),
});
}

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