Compare commits
28
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b4847dd134 | ||
|
|
cf82c3b58c | ||
|
|
a17326634e | ||
|
|
70c4a119d6 | ||
|
|
f0443bd989 | ||
|
|
73462ef108 | ||
|
|
27c225a512 | ||
|
|
cb1c9937fa | ||
|
|
588b210601 | ||
|
|
7e483ec97f | ||
|
|
c902901cff | ||
|
|
205018f6e1 | ||
|
|
269e68c0a1 | ||
|
|
2888efc065 | ||
|
|
c753641570 | ||
|
|
4b0e63dcc0 | ||
|
|
6979810de5 | ||
|
|
4c37af49aa | ||
|
|
7858632429 | ||
|
|
0647f12498 | ||
|
|
3e1c4045e7 | ||
|
|
3ba8ba4288 | ||
|
|
dfd120a3a9 | ||
|
|
262edc6a46 | ||
|
|
4f79053ec3 | ||
|
|
2a91113a76 | ||
|
|
b103872daf | ||
|
|
7f54012ec6 |
@@ -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
|
||||
@@ -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
@@ -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",
|
||||
|
||||
@@ -1,5 +1,35 @@
|
||||
# @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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/auth",
|
||||
"version": "3.2.25",
|
||||
"version": "3.2.29",
|
||||
"main": "dist/thirdweb-dev-auth.cjs.js",
|
||||
"module": "dist/thirdweb-dev-auth.esm.js",
|
||||
"browser": {
|
||||
|
||||
+73
-423
@@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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>;
|
||||
@@ -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;
|
||||
};
|
||||
@@ -1,9 +0,0 @@
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export const isBrowser = () => typeof window !== "undefined";
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export const isNode = () => !isBrowser();
|
||||
@@ -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,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";
|
||||
|
||||
@@ -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());
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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()}'`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
# @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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
@@ -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"
|
||||
|
||||
@@ -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 +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"
|
||||
}
|
||||
],
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
],
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
],
|
||||
|
||||
@@ -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/"
|
||||
],
|
||||
|
||||
@@ -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/"
|
||||
],
|
||||
|
||||
@@ -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,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/chains",
|
||||
"version": "0.1.41",
|
||||
"version": "0.1.43",
|
||||
"main": "dist/thirdweb-dev-chains.cjs.js",
|
||||
"module": "dist/thirdweb-dev-chains.esm.js",
|
||||
"browser": {
|
||||
|
||||
@@ -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
@@ -1,5 +1,39 @@
|
||||
# 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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "thirdweb",
|
||||
"main": "dist/cli/index.js",
|
||||
"version": "0.11.8",
|
||||
"version": "0.11.12",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/cli",
|
||||
"author": "thirdweb eng <[email protected]>",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -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,5 +1,48 @@
|
||||
# @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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/react-core",
|
||||
"version": "3.14.25",
|
||||
"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"
|
||||
},
|
||||
|
||||
@@ -317,7 +317,7 @@ export function ThirdwebWalletProvider(
|
||||
),
|
||||
{
|
||||
ms: autoConnectTimeout,
|
||||
message: "Failed to Auto connect. Auto connect timed out.",
|
||||
message: autoConnectTimeoutErrorMessage,
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
@@ -346,7 +346,7 @@ 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) {
|
||||
@@ -552,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 />`;
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @thirdweb-dev/react-native-compat
|
||||
|
||||
## 0.2.48
|
||||
|
||||
## 0.2.47
|
||||
|
||||
## 0.2.46
|
||||
|
||||
## 0.2.45
|
||||
|
||||
## 0.2.44
|
||||
|
||||
## 0.2.43
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/react-native-compat",
|
||||
"description": "Shims for Thirdweb in React Native Projects",
|
||||
"version": "0.2.44",
|
||||
"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",
|
||||
|
||||
@@ -1,5 +1,53 @@
|
||||
# @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/[email protected]
|
||||
- @thirdweb-dev/[email protected]
|
||||
- @thirdweb-dev/[email protected]
|
||||
- @thirdweb-dev/[email protected]
|
||||
- @thirdweb-dev/[email protected]
|
||||
|
||||
## 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/[email protected]
|
||||
- @thirdweb-dev/[email protected]
|
||||
- @thirdweb-dev/[email protected]
|
||||
- @thirdweb-dev/[email protected]
|
||||
|
||||
## 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/[email protected]
|
||||
- @thirdweb-dev/[email protected]
|
||||
- @thirdweb-dev/[email protected]
|
||||
- @thirdweb-dev/[email protected]
|
||||
|
||||
## 0.2.45
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`2a91113a`](https://github.com/thirdweb-dev/js/commit/2a91113a760733fcff2aec90041f69e15de33905)]:
|
||||
- @thirdweb-dev/[email protected]
|
||||
- @thirdweb-dev/[email protected]
|
||||
- @thirdweb-dev/[email protected]
|
||||
|
||||
## 0.2.44
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/react-native",
|
||||
"version": "0.2.44",
|
||||
"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
|
||||
|
||||
+3
-1
@@ -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 })
|
||||
|
||||
@@ -1,5 +1,49 @@
|
||||
# @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/[email protected]
|
||||
- @thirdweb-dev/[email protected]
|
||||
- @thirdweb-dev/[email protected]
|
||||
- @thirdweb-dev/[email protected]
|
||||
|
||||
## 3.14.28
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`4b0e63dc`](https://github.com/thirdweb-dev/js/commit/4b0e63dcc0ca871ce9cef76f8a41ff290316741c)]:
|
||||
- @thirdweb-dev/[email protected]
|
||||
- @thirdweb-dev/[email protected]
|
||||
- @thirdweb-dev/[email protected]
|
||||
|
||||
## 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/[email protected]
|
||||
- @thirdweb-dev/[email protected]
|
||||
- @thirdweb-dev/[email protected]
|
||||
- @thirdweb-dev/[email protected]
|
||||
|
||||
## 3.14.26
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`2a91113a`](https://github.com/thirdweb-dev/js/commit/2a91113a760733fcff2aec90041f69e15de33905)]:
|
||||
- @thirdweb-dev/[email protected]
|
||||
- @thirdweb-dev/[email protected]
|
||||
- @thirdweb-dev/[email protected]
|
||||
|
||||
## 3.14.25
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/react",
|
||||
"version": "3.14.25",
|
||||
"version": "3.14.29",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/react",
|
||||
"author": "thirdweb eng <[email protected]>",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -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 && (
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,5 +1,43 @@
|
||||
# @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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/sdk",
|
||||
"version": "3.10.44",
|
||||
"version": "3.10.48",
|
||||
"description": "The main thirdweb SDK.",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/sdk",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -12,41 +12,12 @@ import { z } from "zod";
|
||||
export const BasicNFTInput = /* @__PURE__ */ (() =>
|
||||
z.object({
|
||||
name: z.union([z.string(), z.number()]).optional().nullable(),
|
||||
description: z
|
||||
.string()
|
||||
.nullable()
|
||||
.optional()
|
||||
.nullable()
|
||||
.transform((i) => {
|
||||
return sanitizeJSONString(i);
|
||||
}),
|
||||
description: z.string().nullable().optional().nullable(),
|
||||
image: FileOrBufferOrStringSchema.nullable().optional(),
|
||||
|
||||
animation_url: FileOrBufferOrStringSchema.optional().nullable(),
|
||||
}))();
|
||||
|
||||
function sanitizeJSONString(val: string | undefined | null) {
|
||||
if (!val) {
|
||||
return val;
|
||||
}
|
||||
return JSON.parse(JSON.stringify(val, escape));
|
||||
}
|
||||
|
||||
function escape(key: string, val: any) {
|
||||
if (typeof val !== "string") {
|
||||
return val;
|
||||
}
|
||||
return val
|
||||
.replace(/[\"]/g, '\\"')
|
||||
.replace(/[\\]/g, "\\\\")
|
||||
.replace(/[\/]/g, "\\/")
|
||||
.replace(/[\b]/g, "\\b")
|
||||
.replace(/[\f]/g, "\\f")
|
||||
.replace(/[\n]/g, "\\n")
|
||||
.replace(/[\r]/g, "\\r")
|
||||
.replace(/[\t]/g, "\\t");
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -366,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;
|
||||
}
|
||||
}
|
||||
@@ -376,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);
|
||||
@@ -508,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,7 +266,11 @@ export class ThirdwebSDK extends RPCConnectionHandler {
|
||||
) {
|
||||
const apiKeyType = typeof window !== "undefined" ? "clientId" : "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) {
|
||||
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);
|
||||
@@ -2194,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.",
|
||||
|
||||
@@ -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([
|
||||
{
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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([
|
||||
|
||||
@@ -1,5 +1,39 @@
|
||||
# @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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/service-utils",
|
||||
"version": "0.3.0",
|
||||
"version": "0.4.2",
|
||||
"main": "dist/thirdweb-dev-service-utils.cjs.js",
|
||||
"module": "dist/thirdweb-dev-service-utils.esm.js",
|
||||
"exports": {
|
||||
|
||||
@@ -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,
|
||||
@@ -73,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);
|
||||
@@ -134,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;
|
||||
}
|
||||
}
|
||||
@@ -167,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}`);
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ const usageEventSchema = z.object({
|
||||
transactionHash: z.string().optional(),
|
||||
gasLimit: z.number().nonnegative().optional(),
|
||||
gasPricePerUnit: z.string().optional(),
|
||||
userOpHash: z.string().optional(),
|
||||
});
|
||||
export type UsageEvent = z.infer<typeof usageEventSchema>;
|
||||
|
||||
@@ -63,7 +64,7 @@ export async function publishUsageEvents(
|
||||
secretAccessKey: string;
|
||||
region?: string;
|
||||
},
|
||||
): Promise<string> {
|
||||
): Promise<void> {
|
||||
const {
|
||||
queueUrl,
|
||||
accessKeyId,
|
||||
@@ -71,17 +72,21 @@ export async function publishUsageEvents(
|
||||
region = "us-west-2",
|
||||
} = config;
|
||||
|
||||
const entries = usageEvents.map((event) => ({
|
||||
Id: crypto.randomUUID(),
|
||||
MessageBody: JSON.stringify(event),
|
||||
}));
|
||||
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,
|
||||
});
|
||||
const res = await aws.fetch(`https://sqs.${region}.amazonaws.com`, {
|
||||
await aws.fetch(`https://sqs.${region}.amazonaws.com`, {
|
||||
headers: {
|
||||
"X-Amz-Target": "AmazonSQS.SendMessageBatch",
|
||||
"X-Amz-Date": new Date().toISOString(),
|
||||
@@ -92,5 +97,4 @@ export async function publishUsageEvents(
|
||||
Entries: entries,
|
||||
}),
|
||||
});
|
||||
return await res.text();
|
||||
}
|
||||
|
||||
@@ -61,12 +61,13 @@ export async function fetchKeyMetadataFromApi(
|
||||
"content-type": "application/json",
|
||||
},
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`Error fetching key metadata from API: ${response.statusText}`,
|
||||
);
|
||||
let json: ApiResponse
|
||||
try {
|
||||
json = await response.json();
|
||||
} catch (e) {
|
||||
throw new Error(`Error fetching key metadata from API: ${response.status} - ${response.statusText} - ${await response.text()}`);
|
||||
}
|
||||
return (await response.json()) as ApiResponse;
|
||||
return json;
|
||||
}
|
||||
|
||||
export async function fetchAccountFromApi(
|
||||
@@ -83,8 +84,11 @@ export async function fetchAccountFromApi(
|
||||
authorization: `Bearer ${jwt}`,
|
||||
},
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`Error fetching account from API: ${response.statusText}`);
|
||||
let json: ApiAccountResponse
|
||||
try {
|
||||
json = await response.json();
|
||||
} catch (e) {
|
||||
throw new Error(`Error fetching account from API: ${response.status} - ${response.statusText} - ${await response.text()}`);
|
||||
}
|
||||
return (await response.json()) as ApiAccountResponse;
|
||||
return json;
|
||||
}
|
||||
|
||||
@@ -114,8 +114,8 @@ export async function authorize(
|
||||
return {
|
||||
authorized: false,
|
||||
status: 500,
|
||||
errorMessage: "Failed to account information.",
|
||||
errorCode: "FAILED_TO_ACCOUNT",
|
||||
errorMessage: "Failed to get account information.",
|
||||
errorCode: "FAILED_TO_LOAD_ACCOUNT",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import type { IncomingHttpHeaders, IncomingMessage } from "node:http";
|
||||
import { createHash } from "node:crypto";
|
||||
import { authorize } from "../core/authorize";
|
||||
|
||||
import type { IncomingHttpHeaders, IncomingMessage } from "node:http";
|
||||
import type { AuthorizationInput } from "../core/authorize";
|
||||
import type { CoreServiceConfig } from "../core/api";
|
||||
import { authorize } from "../core/authorize";
|
||||
import type { AuthorizationResult } from "../core/authorize/types";
|
||||
import type { CoreAuthInput } from "../core/types";
|
||||
import type { ServerResponse } from "http";
|
||||
|
||||
export * from "../core/services";
|
||||
|
||||
@@ -123,7 +125,7 @@ export function extractAuthorizationData(
|
||||
const authorizationHeader = getHeader(headers, "authorization");
|
||||
if (authorizationHeader) {
|
||||
const [type, token] = authorizationHeader.split(" ");
|
||||
if (type.toLowerCase() === "bearer") {
|
||||
if (type.toLowerCase() === "bearer" && !!token) {
|
||||
jwt = token;
|
||||
}
|
||||
}
|
||||
@@ -147,3 +149,39 @@ export function hashSecretKey(secretKey: string) {
|
||||
export function deriveClientIdFromSecretKeyHash(secretKeyHash: string) {
|
||||
return secretKeyHash.slice(0, 32);
|
||||
}
|
||||
|
||||
export function logHttpRequest({
|
||||
source,
|
||||
clientId,
|
||||
req,
|
||||
res,
|
||||
isAuthed,
|
||||
statusMessage,
|
||||
}: AuthInput & {
|
||||
source: string;
|
||||
res: ServerResponse;
|
||||
isAuthed?: boolean;
|
||||
statusMessage?: Error | string;
|
||||
}) {
|
||||
const authorizationData = extractAuthorizationData({ req, clientId });
|
||||
const headers = req.headers;
|
||||
|
||||
const _statusMessage = statusMessage ?? res.statusMessage;
|
||||
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.statusCode,
|
||||
statusMessage: _statusMessage,
|
||||
sdkName: headers["x-sdk-name"] ?? "unknown",
|
||||
sdkVersion: headers["x-sdk-version"] ?? "unknown",
|
||||
platform: headers["x-sdk-platform"] ?? "unknown",
|
||||
}),
|
||||
);
|
||||
console.log(`statusMessage=${_statusMessage}`);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
# @thirdweb-dev/storage
|
||||
|
||||
## 1.2.5
|
||||
|
||||
### 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
|
||||
|
||||
- [#1470](https://github.com/thirdweb-dev/js/pull/1470) [`27c225a5`](https://github.com/thirdweb-dev/js/commit/27c225a51221417592b5940887abd1ccb8b67e47) Thanks [@jnsdls](https://github.com/jnsdls)! - fix storage secretKey and clientId handling
|
||||
|
||||
- [#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
|
||||
|
||||
## 1.2.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1455](https://github.com/thirdweb-dev/js/pull/1455) [`4c37af49`](https://github.com/thirdweb-dev/js/commit/4c37af49aa4fa66beb8ffe8ef556068a29f3c5d4) Thanks [@jnsdls](https://github.com/jnsdls)! - allow overriding the upload server url for IpfsUploader for advanced usage
|
||||
|
||||
## 1.2.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/storage",
|
||||
"version": "1.2.3",
|
||||
"version": "1.2.5",
|
||||
"main": "dist/thirdweb-dev-storage.cjs.js",
|
||||
"module": "dist/thirdweb-dev-storage.esm.js",
|
||||
"browser": {
|
||||
|
||||
@@ -3,6 +3,7 @@ import CIDTool from "cid-tool";
|
||||
import { getProcessEnv } from "./process";
|
||||
|
||||
const TW_HOSTNAME_SUFFIX = ".ipfscdn.io";
|
||||
const TW_STAGINGHOSTNAME_SUFFIX = ".thirdwebstorage-staging.com";
|
||||
const TW_GATEWAY_URLS = [
|
||||
`https://{clientId}${TW_HOSTNAME_SUFFIX}/ipfs/{cid}/{path}`,
|
||||
];
|
||||
@@ -13,7 +14,13 @@ const TW_GATEWAY_URLS = [
|
||||
* @returns
|
||||
*/
|
||||
export function isTwGatewayUrl(url: string): boolean {
|
||||
return new URL(url).hostname.endsWith(TW_HOSTNAME_SUFFIX);
|
||||
const hostname = new URL(url).hostname;
|
||||
const isProd = hostname.endsWith(TW_HOSTNAME_SUFFIX);
|
||||
if (isProd) {
|
||||
return true;
|
||||
}
|
||||
// fall back to also handle staging urls
|
||||
return hostname.endsWith(TW_STAGINGHOSTNAME_SUFFIX);
|
||||
}
|
||||
|
||||
const PUBLIC_GATEWAY_URLS = [
|
||||
@@ -66,7 +73,11 @@ export function parseGatewayUrls(
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export function getGatewayUrlForCid(gatewayUrl: string, cid: string): string {
|
||||
export function getGatewayUrlForCid(
|
||||
gatewayUrl: string,
|
||||
cid: string,
|
||||
clientId?: string,
|
||||
): string {
|
||||
const parts = cid.split("/");
|
||||
const hash = convertCidToV1(parts[0]);
|
||||
const filePath = parts.slice(1).join("/");
|
||||
@@ -86,6 +97,15 @@ export function getGatewayUrlForCid(gatewayUrl: string, cid: string): string {
|
||||
else {
|
||||
url += `${hash}/${filePath}`;
|
||||
}
|
||||
// if the URL contains the {clientId} token, replace it with the client ID
|
||||
if (gatewayUrl.includes("{clientId}")) {
|
||||
if (!clientId) {
|
||||
throw new Error(
|
||||
"Cannot use {clientId} in gateway URL without providing a client ID",
|
||||
);
|
||||
}
|
||||
url = url.replace("{clientId}", clientId);
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
@@ -148,6 +148,7 @@ export function replaceSchemeWithGatewayUrl(
|
||||
uri: string,
|
||||
gatewayUrls: GatewayUrls,
|
||||
index = 0,
|
||||
clientId?: string,
|
||||
): string | undefined {
|
||||
const scheme = Object.keys(gatewayUrls).find((s) => uri.startsWith(s));
|
||||
const schemeGatewayUrls = scheme ? gatewayUrls[scheme] : [];
|
||||
@@ -161,7 +162,7 @@ export function replaceSchemeWithGatewayUrl(
|
||||
}
|
||||
|
||||
const path = uri.replace(scheme, "");
|
||||
return getGatewayUrlForCid(schemeGatewayUrls[index], path);
|
||||
return getGatewayUrlForCid(schemeGatewayUrls[index], path, clientId);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -206,9 +207,15 @@ export function replaceObjectGatewayUrlsWithSchemes<TData = unknown>(
|
||||
export function replaceObjectSchemesWithGatewayUrls<TData = unknown>(
|
||||
data: TData,
|
||||
gatewayUrls: GatewayUrls,
|
||||
clientId?: string,
|
||||
): TData {
|
||||
if (typeof data === "string") {
|
||||
return replaceSchemeWithGatewayUrl(data, gatewayUrls) as any as TData;
|
||||
return replaceSchemeWithGatewayUrl(
|
||||
data,
|
||||
gatewayUrls,
|
||||
0,
|
||||
clientId,
|
||||
) as any as TData;
|
||||
}
|
||||
if (typeof data === "object") {
|
||||
if (!data) {
|
||||
@@ -219,13 +226,13 @@ export function replaceObjectSchemesWithGatewayUrls<TData = unknown>(
|
||||
}
|
||||
if (Array.isArray(data)) {
|
||||
return data.map((entry) =>
|
||||
replaceObjectSchemesWithGatewayUrls(entry, gatewayUrls),
|
||||
replaceObjectSchemesWithGatewayUrls(entry, gatewayUrls, clientId),
|
||||
) as any as TData;
|
||||
}
|
||||
return Object.fromEntries(
|
||||
Object.entries(data).map(([key, value]) => [
|
||||
key,
|
||||
replaceObjectSchemesWithGatewayUrls(value, gatewayUrls),
|
||||
replaceObjectSchemesWithGatewayUrls(value, gatewayUrls, clientId),
|
||||
]),
|
||||
) as any as TData;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { isTwGatewayUrl } from "../../common/urls";
|
||||
import { replaceSchemeWithGatewayUrl } from "../../common/utils";
|
||||
import { isBrowser, replaceSchemeWithGatewayUrl } from "../../common/utils";
|
||||
import {
|
||||
GatewayUrls,
|
||||
IStorageDownloader,
|
||||
IpfsDownloaderOptions,
|
||||
} from "../../types";
|
||||
import fetch, { Response } from "cross-fetch";
|
||||
import pkg from "../../../package.json";
|
||||
|
||||
/**
|
||||
* Default downloader used - handles downloading from all schemes specified in the gateway URLs configuration.
|
||||
@@ -54,7 +55,12 @@ export class StorageDownloader implements IStorageDownloader {
|
||||
}
|
||||
|
||||
// Replace recognized scheme with the highest priority gateway URL that hasn't already been attempted
|
||||
let resolvedUri = replaceSchemeWithGatewayUrl(uri, gatewayUrls, attempts);
|
||||
let resolvedUri = replaceSchemeWithGatewayUrl(
|
||||
uri,
|
||||
gatewayUrls,
|
||||
attempts,
|
||||
this.clientId,
|
||||
);
|
||||
// If every gateway URL we know about for the designated scheme has been tried (via recursion) and failed, throw an error
|
||||
if (!resolvedUri) {
|
||||
console.error(
|
||||
@@ -72,23 +78,24 @@ export class StorageDownloader implements IStorageDownloader {
|
||||
|
||||
let headers: HeadersInit = {};
|
||||
if (isTwGatewayUrl(resolvedUri)) {
|
||||
const bundleId =
|
||||
typeof globalThis !== "undefined" && "APP_BUNDLE_ID" in globalThis
|
||||
? ((globalThis as any).APP_BUNDLE_ID as string)
|
||||
: undefined;
|
||||
if (this.secretKey) {
|
||||
headers = { "x-secret-key": this.secretKey };
|
||||
} else if (this.clientId) {
|
||||
if (
|
||||
typeof globalThis !== "undefined" &&
|
||||
"APP_BUNDLE_ID" in globalThis
|
||||
) {
|
||||
resolvedUri =
|
||||
resolvedUri +
|
||||
`?bundleId=${(globalThis as any).APP_BUNDLE_ID as string}`;
|
||||
if (!resolvedUri.includes("bundleId")) {
|
||||
resolvedUri = resolvedUri + (bundleId ? `?bundleId=${bundleId}` : "");
|
||||
}
|
||||
headers = {
|
||||
"x-client-Id": this.clientId,
|
||||
};
|
||||
headers["x-client-Id"] = this.clientId;
|
||||
}
|
||||
// if we have a authorization token on global context then add that to the headers
|
||||
if (typeof globalThis !== "undefined" && "TW_AUTH_TOKEN" in globalThis) {
|
||||
if (
|
||||
typeof globalThis !== "undefined" &&
|
||||
"TW_AUTH_TOKEN" in globalThis &&
|
||||
typeof (globalThis as any).TW_AUTH_TOKEN === "string"
|
||||
) {
|
||||
headers = {
|
||||
...headers,
|
||||
authorization: `Bearer ${
|
||||
@@ -96,6 +103,14 @@ export class StorageDownloader implements IStorageDownloader {
|
||||
}`,
|
||||
};
|
||||
}
|
||||
|
||||
headers["x-sdk-version"] = pkg.version;
|
||||
headers["x-sdk-name"] = pkg.name;
|
||||
headers["x-sdk-platform"] = bundleId
|
||||
? "react-native"
|
||||
: isBrowser()
|
||||
? "browser"
|
||||
: "node";
|
||||
}
|
||||
|
||||
if (isTooManyRequests(resolvedUri)) {
|
||||
|
||||
@@ -60,6 +60,7 @@ export class ThirdwebStorage<T extends UploadOptions = IpfsUploadBatchOptions>
|
||||
private uploader: IStorageUploader<T>;
|
||||
private downloader: IStorageDownloader;
|
||||
private gatewayUrls: GatewayUrls;
|
||||
private clientId?: string;
|
||||
|
||||
constructor(options?: ThirdwebStorageOptions<T>) {
|
||||
this.uploader =
|
||||
@@ -67,6 +68,7 @@ export class ThirdwebStorage<T extends UploadOptions = IpfsUploadBatchOptions>
|
||||
new IpfsUploader({
|
||||
clientId: options?.clientId,
|
||||
secretKey: options?.secretKey,
|
||||
uploadServerUrl: options?.uploadServerUrl,
|
||||
});
|
||||
this.downloader =
|
||||
options?.downloader ||
|
||||
@@ -79,6 +81,7 @@ export class ThirdwebStorage<T extends UploadOptions = IpfsUploadBatchOptions>
|
||||
options?.clientId,
|
||||
options?.secretKey,
|
||||
);
|
||||
this.clientId = options?.clientId;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -95,7 +98,12 @@ export class ThirdwebStorage<T extends UploadOptions = IpfsUploadBatchOptions>
|
||||
* ```
|
||||
*/
|
||||
resolveScheme(url: string): string {
|
||||
return replaceSchemeWithGatewayUrl(url, this.gatewayUrls) as string;
|
||||
return replaceSchemeWithGatewayUrl(
|
||||
url,
|
||||
this.gatewayUrls,
|
||||
0,
|
||||
this.clientId,
|
||||
) as string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -132,7 +140,11 @@ export class ThirdwebStorage<T extends UploadOptions = IpfsUploadBatchOptions>
|
||||
|
||||
// If we get a JSON object, recursively replace any schemes with gatewayUrls
|
||||
const json = await res.json();
|
||||
return replaceObjectSchemesWithGatewayUrls(json, this.gatewayUrls) as TJSON;
|
||||
return replaceObjectSchemesWithGatewayUrls(
|
||||
json,
|
||||
this.gatewayUrls,
|
||||
this.clientId,
|
||||
) as TJSON;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -256,6 +268,7 @@ export class ThirdwebStorage<T extends UploadOptions = IpfsUploadBatchOptions>
|
||||
cleaned = replaceObjectSchemesWithGatewayUrls(
|
||||
cleaned,
|
||||
this.gatewayUrls,
|
||||
this.clientId,
|
||||
) as unknown[];
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
} from "../../types";
|
||||
import fetch from "cross-fetch";
|
||||
import FormData from "form-data";
|
||||
import pkg from "../../../package.json";
|
||||
|
||||
/**
|
||||
* Default uploader used - handles uploading arbitrary data to IPFS
|
||||
@@ -43,11 +44,13 @@ import FormData from "form-data";
|
||||
*/
|
||||
export class IpfsUploader implements IStorageUploader<IpfsUploadBatchOptions> {
|
||||
public uploadWithGatewayUrl: boolean;
|
||||
private uploadServerUrl: string;
|
||||
private clientId?: string;
|
||||
private secretKey?: string;
|
||||
|
||||
constructor(options?: IpfsUploaderOptions) {
|
||||
this.uploadWithGatewayUrl = options?.uploadWithGatewayUrl || false;
|
||||
this.uploadServerUrl = options?.uploadServerUrl || TW_UPLOAD_SERVER_URL;
|
||||
this.clientId = options?.clientId;
|
||||
this.secretKey = options?.secretKey;
|
||||
}
|
||||
@@ -239,7 +242,7 @@ export class IpfsUploader implements IStorageUploader<IpfsUploadBatchOptions> {
|
||||
if (options?.uploadWithoutDirectory) {
|
||||
return resolve([`ipfs://${cid}`]);
|
||||
} else {
|
||||
return resolve(fileNames.map((name) => `ipfs://${cid}/${name}`));
|
||||
return resolve(fileNames.map((n) => `ipfs://${cid}/${n}`));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,7 +271,7 @@ export class IpfsUploader implements IStorageUploader<IpfsUploadBatchOptions> {
|
||||
return reject(new Error("Unknown upload error occured"));
|
||||
});
|
||||
|
||||
xhr.open("POST", `${TW_UPLOAD_SERVER_URL}/ipfs/upload`);
|
||||
xhr.open("POST", `${this.uploadServerUrl}/ipfs/upload`);
|
||||
|
||||
if (this.secretKey) {
|
||||
xhr.setRequestHeader("x-secret-key", this.secretKey);
|
||||
@@ -276,15 +279,27 @@ export class IpfsUploader implements IStorageUploader<IpfsUploadBatchOptions> {
|
||||
xhr.setRequestHeader("x-client-id", this.clientId);
|
||||
}
|
||||
|
||||
if (typeof globalThis !== "undefined" && "APP_BUNDLE_ID" in globalThis) {
|
||||
xhr.setRequestHeader(
|
||||
"x-bundle-id",
|
||||
(globalThis as any).APP_BUNDLE_ID as string,
|
||||
);
|
||||
const bundleId =
|
||||
typeof globalThis !== "undefined" && "APP_BUNDLE_ID" in globalThis
|
||||
? ((globalThis as any).APP_BUNDLE_ID as string)
|
||||
: undefined;
|
||||
if (bundleId) {
|
||||
xhr.setRequestHeader("x-bundle-id", bundleId);
|
||||
}
|
||||
|
||||
xhr.setRequestHeader("x-sdk-version", pkg.version);
|
||||
xhr.setRequestHeader("x-sdk-name", pkg.name);
|
||||
xhr.setRequestHeader(
|
||||
"x-sdk-platform",
|
||||
bundleId ? "react-native" : isBrowser() ? "browser" : "node",
|
||||
);
|
||||
|
||||
// if we have a authorization token on global context then add that to the headers
|
||||
if (typeof globalThis !== "undefined" && "TW_AUTH_TOKEN" in globalThis) {
|
||||
if (
|
||||
typeof globalThis !== "undefined" &&
|
||||
"TW_AUTH_TOKEN" in globalThis &&
|
||||
typeof (globalThis as any).TW_AUTH_TOKEN === "string"
|
||||
) {
|
||||
xhr.setRequestHeader(
|
||||
"authorization",
|
||||
`Bearer ${(globalThis as any).TW_AUTH_TOKEN as string}`,
|
||||
@@ -317,13 +332,17 @@ export class IpfsUploader implements IStorageUploader<IpfsUploadBatchOptions> {
|
||||
}
|
||||
|
||||
// if we have a authorization token on global context then add that to the headers
|
||||
if (typeof globalThis !== "undefined" && "TW_AUTH_TOKEN" in globalThis) {
|
||||
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
|
||||
}`;
|
||||
}
|
||||
|
||||
const res = await fetch(`${TW_UPLOAD_SERVER_URL}/ipfs/upload`, {
|
||||
const res = await fetch(`${this.uploadServerUrl}/ipfs/upload`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
...headers,
|
||||
|
||||
@@ -5,6 +5,7 @@ export type ThirdwebStorageOptions<T extends UploadOptions> = {
|
||||
uploader?: IStorageUploader<T>;
|
||||
downloader?: IStorageDownloader;
|
||||
gatewayUrls?: GatewayUrls | string[];
|
||||
uploadServerUrl?: string;
|
||||
clientId?: string;
|
||||
secretKey?: string;
|
||||
};
|
||||
|
||||
@@ -56,6 +56,10 @@ export type IpfsUploaderOptions = {
|
||||
* You can get an secretKey here: https://thirdweb.com/create-api-key
|
||||
*/
|
||||
secretKey?: string;
|
||||
/**
|
||||
* Optional upload server url to use instead of the default. (Advanced Usage)
|
||||
*/
|
||||
uploadServerUrl?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,19 +4,22 @@ import { readFileSync } from "fs";
|
||||
import { getGatewayUrlForCid, IpfsUploader, ThirdwebStorage } from "../src";
|
||||
import { DEFAULT_GATEWAY_URLS, prepareGatewayUrls } from "../src/common/urls";
|
||||
|
||||
const apiSecretKey = process.env.TW_SECRET_KEY as string;
|
||||
// load env variables
|
||||
require("dotenv-mono").load();
|
||||
|
||||
const secretKey = process.env.TW_SECRET_KEY as string;
|
||||
|
||||
describe("IPFS", async () => {
|
||||
if (!apiSecretKey) {
|
||||
if (!secretKey) {
|
||||
throw new Error("TW_SECRET_KEY is not set in the environment variables");
|
||||
}
|
||||
const storage = new ThirdwebStorage({
|
||||
secretKey: apiSecretKey,
|
||||
secretKey: secretKey,
|
||||
});
|
||||
const authorizedUrls = prepareGatewayUrls(
|
||||
DEFAULT_GATEWAY_URLS,
|
||||
undefined,
|
||||
apiSecretKey,
|
||||
secretKey,
|
||||
);
|
||||
|
||||
it("Should replace tokens in tokenized gateway URL", async () => {
|
||||
@@ -103,6 +106,7 @@ describe("IPFS", async () => {
|
||||
...DEFAULT_GATEWAY_URLS["ipfs://"],
|
||||
],
|
||||
},
|
||||
secretKey,
|
||||
});
|
||||
|
||||
const uri = await storageWithBadGateways.upload(
|
||||
@@ -363,16 +367,16 @@ describe("IPFS", async () => {
|
||||
it("Should upload files with gateway URLs if specified on class", async () => {
|
||||
const uploader = new IpfsUploader({
|
||||
uploadWithGatewayUrl: true,
|
||||
secretKey: apiSecretKey,
|
||||
secretKey,
|
||||
});
|
||||
const singleStorage = new ThirdwebStorage({
|
||||
uploader,
|
||||
secretKey: apiSecretKey,
|
||||
secretKey,
|
||||
});
|
||||
const _authorizedUrls = prepareGatewayUrls(
|
||||
DEFAULT_GATEWAY_URLS,
|
||||
undefined,
|
||||
apiSecretKey,
|
||||
secretKey,
|
||||
);
|
||||
|
||||
const uri = await singleStorage.upload(
|
||||
@@ -454,7 +458,7 @@ describe("IPFS", async () => {
|
||||
getGatewayUrlForCid(
|
||||
authorizedUrls["ipfs://"][0],
|
||||
// CID changes based on file contents (prod gateway vs staging gateway since they get written)
|
||||
`bafybeidnzwz6rm3jdf6vx5z7eb5ssfmeq64b5ihhjc7ioivxtc2x3f5r4m/0`,
|
||||
`bafybeie4otkctycxgas34kjsvapcbqspqfj2xzjvyaet26i3nvlxv56oxy/0`,
|
||||
),
|
||||
);
|
||||
});
|
||||
@@ -462,16 +466,16 @@ describe("IPFS", async () => {
|
||||
it("Should return URIs with gateway URLs if specified on class", async () => {
|
||||
const uploader = new IpfsUploader({
|
||||
uploadWithGatewayUrl: true,
|
||||
secretKey: apiSecretKey,
|
||||
secretKey,
|
||||
});
|
||||
const storageSingleton = new ThirdwebStorage({
|
||||
uploader,
|
||||
secretKey: apiSecretKey,
|
||||
secretKey,
|
||||
});
|
||||
const _authorizedUrls = prepareGatewayUrls(
|
||||
DEFAULT_GATEWAY_URLS,
|
||||
undefined,
|
||||
apiSecretKey,
|
||||
secretKey,
|
||||
);
|
||||
|
||||
const uri = await storageSingleton.upload(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"extends": "@thirdweb-dev/tsconfig/base.json",
|
||||
"extends": "@thirdweb-dev/tsconfig/sdk.json",
|
||||
"include": ["."],
|
||||
"exclude": ["dist", "build", "node_modules"],
|
||||
"exclude": ["dist", "build", "node_modules"]
|
||||
}
|
||||
|
||||
@@ -1,5 +1,47 @@
|
||||
# @thirdweb-dev/unity-js-bridge
|
||||
|
||||
## 0.2.66
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#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
|
||||
|
||||
- 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
|
||||
|
||||
## 0.2.65
|
||||
|
||||
### 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/wallets@1.1.11
|
||||
- @thirdweb-dev/sdk@3.10.47
|
||||
- @thirdweb-dev/auth@3.2.28
|
||||
|
||||
## 0.2.64
|
||||
|
||||
### 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/auth@3.2.27
|
||||
- @thirdweb-dev/wallets@1.1.10
|
||||
|
||||
## 0.2.63
|
||||
|
||||
### 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
|
||||
- @thirdweb-dev/auth@3.2.26
|
||||
|
||||
## 0.2.62
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/unity-js-bridge",
|
||||
"version": "0.2.62",
|
||||
"version": "0.2.66",
|
||||
"main": "dist/thirdweb-unity-bridge.js",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/unity-js-bridge",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -71,13 +71,13 @@ interface TWBridge {
|
||||
initialize: (chain: ChainIdOrName, options: string) => void;
|
||||
connect: (
|
||||
wallet: PossibleWallet,
|
||||
chainId?: number,
|
||||
chainId: string,
|
||||
password?: string,
|
||||
email?: string,
|
||||
personalWallet?: PossibleWallet,
|
||||
) => Promise<string>;
|
||||
disconnect: () => Promise<void>;
|
||||
switchNetwork: (chainId: number) => Promise<void>;
|
||||
switchNetwork: (chainId: string) => Promise<void>;
|
||||
invoke: (route: string, payload: string) => Promise<string | undefined>;
|
||||
invokeListener: (
|
||||
taskId: string,
|
||||
@@ -299,7 +299,7 @@ class ThirdwebBridge implements TWBridge {
|
||||
|
||||
public async connect(
|
||||
wallet: PossibleWallet = "injected",
|
||||
chainId?: number | undefined,
|
||||
chainId: string,
|
||||
password?: string,
|
||||
email?: string,
|
||||
personalWallet: PossibleWallet = "localWallet",
|
||||
@@ -307,15 +307,13 @@ class ThirdwebBridge implements TWBridge {
|
||||
if (!this.activeSDK) {
|
||||
throw new Error("SDK not initialized");
|
||||
}
|
||||
if (chainId === 0) {
|
||||
chainId = undefined;
|
||||
}
|
||||
const walletInstance = this.walletMap.get(wallet);
|
||||
const chainIdNumber = Number(chainId);
|
||||
if (walletInstance) {
|
||||
// local wallet needs to be generated or loaded before connecting
|
||||
if (walletInstance.walletId === walletIds.localWallet) {
|
||||
await this.initializeLocalWallet(password as string);
|
||||
walletInstance.connect({ chainId });
|
||||
walletInstance.connect({ chainId: chainIdNumber });
|
||||
}
|
||||
|
||||
if (walletInstance.walletId === walletIds.magicLink) {
|
||||
@@ -323,13 +321,13 @@ class ThirdwebBridge implements TWBridge {
|
||||
if (!email) {
|
||||
throw new Error("Email is required for Magic Link Wallet");
|
||||
}
|
||||
await magicLinkWallet.connect({ chainId, email });
|
||||
await magicLinkWallet.connect({ chainId: chainIdNumber, email: email });
|
||||
} else if (walletInstance.walletId === walletIds.paper) {
|
||||
const paperWallet = walletInstance as PaperWallet;
|
||||
if (!email) {
|
||||
throw new Error("Email is required for Paper Wallet");
|
||||
}
|
||||
await paperWallet.connect({ chainId, email });
|
||||
await paperWallet.connect({ chainId: chainIdNumber, email: email });
|
||||
} else if (walletInstance.walletId === walletIds.smartWallet) {
|
||||
const smartWallet = walletInstance as SmartWallet;
|
||||
const eoaWallet = this.walletMap.get(personalWallet);
|
||||
@@ -351,7 +349,7 @@ class ThirdwebBridge implements TWBridge {
|
||||
);
|
||||
}
|
||||
} else {
|
||||
await walletInstance.connect({ chainId });
|
||||
await walletInstance.connect({ chainId: chainIdNumber });
|
||||
}
|
||||
|
||||
this.activeWallet = walletInstance;
|
||||
@@ -370,9 +368,9 @@ class ThirdwebBridge implements TWBridge {
|
||||
}
|
||||
}
|
||||
|
||||
public async switchNetwork(chainId: number) {
|
||||
public async switchNetwork(chainId: string) {
|
||||
if (chainId && this.activeWallet && "switchChain" in this.activeWallet) {
|
||||
await this.activeWallet.switchChain(chainId);
|
||||
await this.activeWallet.switchChain(Number(chainId));
|
||||
this.updateSDKSigner(await this.activeWallet.getSigner());
|
||||
} else {
|
||||
throw new Error("Error Switching Network");
|
||||
|
||||
@@ -1,5 +1,41 @@
|
||||
# @thirdweb-dev/wallets
|
||||
|
||||
## 1.1.12
|
||||
|
||||
### 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
|
||||
|
||||
- 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/sdk@3.10.48
|
||||
- @thirdweb-dev/chains@0.1.43
|
||||
|
||||
## 1.1.11
|
||||
|
||||
### 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 []:
|
||||
- @thirdweb-dev/sdk@3.10.47
|
||||
|
||||
## 1.1.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
|
||||
|
||||
## 1.1.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`2a91113a`](https://github.com/thirdweb-dev/js/commit/2a91113a760733fcff2aec90041f69e15de33905)]:
|
||||
- @thirdweb-dev/sdk@3.10.45
|
||||
|
||||
## 1.1.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user