Compare commits

...
Author SHA1 Message Date
github-actions[bot]GitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
125c836d88 Version Packages (#1726)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-10-09 11:41:50 -07:00
Kien NgoandGitHub ec36b13a30 [SDK] Improve add/removeContract method for registry.ts (#1731) 2023-10-08 17:38:11 +00:00
Phillip HoandGitHub 52b1a0fcac chore: Make accountId optional in usage event type (#1729) 2023-10-08 05:26:26 +00:00
Kien NgoandGitHub a6c36724eb [SDK] Update transfer function for user wallet (#1728) 2023-10-07 13:10:16 +00:00
Kien NgoandGitHub 6abb845971 [SDK] Concurrent requests for NFT-related code (#1727) 2023-10-07 12:21:54 +00:00
FirekeeperandGitHub dc38a243c2 [Unity] Fix Google popup blocker (#1725) 2023-10-06 22:06:47 +00:00
github-actions[bot]GitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
03a03a196d Version Packages (#1711)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-10-06 18:29:30 +02:00
Manan TankandGitHub 931270479e [react-core] Make useWallet type definition simpler (#1719) 2023-10-06 08:42:40 +00:00
Winston YeoandGitHub 6f4c5cd746 feat(react-native): update iteration count for encryption (#1709) 2023-10-05 22:02:31 +00:00
d3c8626a5a [React] generify useWallet() (#1716)
Co-authored-by: Manan Tank <[email protected]>
2023-10-05 20:15:28 +00:00
Manan TankandGitHub ffb1bb0a60 [react] Show spinner in Google sign popup while its loading (#1717) 2023-10-05 20:09:44 +00:00
Joaquim VergesandGitHub 9bd01de5f9 [SmartWallet] New API for session keys (#1712) 2023-10-05 16:00:35 +00:00
f5caf6a350 [RN] Fix share encryption/decryption for embeddedWallet (#1715)
Co-authored-by: ElasticBottle <[email protected]>
2023-10-05 12:40:20 +00:00
Nacho IacovinoandGitHub 46672a490a Update upsell link for connect wallet (#1713) 2023-10-05 02:28:28 +00:00
f35fbec1be [SDK] Base Router implementation for dynamic contracts (#1308)
Co-authored-by: Joaquim Verges <[email protected]>
2023-10-04 15:14:57 +00:00
74 changed files with 13534 additions and 21724 deletions
+14
View File
@@ -1,5 +1,19 @@
# @thirdweb-dev/auth
## 3.2.48
### Patch Changes
- Updated dependencies []:
- @thirdweb-dev/wallets@1.3.5
## 3.2.47
### Patch Changes
- Updated dependencies [[`9bd01de5`](https://github.com/thirdweb-dev/js/commit/9bd01de5f9c388e758fba9af7899dc4a9c5a0101)]:
- @thirdweb-dev/wallets@1.3.4
## 3.2.46
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/auth",
"version": "3.2.46",
"version": "3.2.48",
"main": "dist/thirdweb-dev-auth.cjs.js",
"module": "dist/thirdweb-dev-auth.esm.js",
"browser": {
+18
View File
@@ -1,5 +1,23 @@
# thirdweb
## 0.12.10
### Patch Changes
- Updated dependencies [[`ec36b13a`](https://github.com/thirdweb-dev/js/commit/ec36b13a30e0071548df0b7a6eb5299e2e65e4f9), [`6abb8459`](https://github.com/thirdweb-dev/js/commit/6abb8459712e387b6d8b2edf7eb16fb906c05dae), [`a6c36724`](https://github.com/thirdweb-dev/js/commit/a6c36724eb930ee0abbce876bb7847c859c6fb48)]:
- @thirdweb-dev/sdk@3.10.67
- @thirdweb-dev/wallets@1.3.5
- @thirdweb-dev/auth@3.2.48
## 0.12.9
### Patch Changes
- Updated dependencies [[`9bd01de5`](https://github.com/thirdweb-dev/js/commit/9bd01de5f9c388e758fba9af7899dc4a9c5a0101), [`f35fbec1`](https://github.com/thirdweb-dev/js/commit/f35fbec1be14332d06e73b5f44f66975ef311d6c)]:
- @thirdweb-dev/wallets@1.3.4
- @thirdweb-dev/sdk@3.10.66
- @thirdweb-dev/auth@3.2.47
## 0.12.8
### Patch Changes
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "thirdweb",
"main": "dist/cli/index.js",
"version": "0.12.8",
"version": "0.12.10",
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/cli",
"author": "thirdweb eng <[email protected]>",
"license": "Apache-2.0",
+1 -1
View File
@@ -10,7 +10,7 @@
"license": "Apache-2.0",
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/contracts-js",
"dependencies": {
"@thirdweb-dev/contracts": "3.10.0"
"@thirdweb-dev/contracts": "3.10.1-0"
},
"devDependencies": {
"@babel/preset-env": "^7.22.9",
+60
View File
@@ -1,5 +1,65 @@
# @thirdweb-dev/react-core
## 3.16.5
### Patch Changes
- Updated dependencies [[`ec36b13a`](https://github.com/thirdweb-dev/js/commit/ec36b13a30e0071548df0b7a6eb5299e2e65e4f9), [`6abb8459`](https://github.com/thirdweb-dev/js/commit/6abb8459712e387b6d8b2edf7eb16fb906c05dae), [`a6c36724`](https://github.com/thirdweb-dev/js/commit/a6c36724eb930ee0abbce876bb7847c859c6fb48)]:
- @thirdweb-dev/sdk@3.10.67
- @thirdweb-dev/wallets@1.3.5
- @thirdweb-dev/auth@3.2.48
## 3.16.4
### Patch Changes
- [#1719](https://github.com/thirdweb-dev/js/pull/1719) [`93127047`](https://github.com/thirdweb-dev/js/commit/931270479ef227556a1077357a8c000b08de6e8d) Thanks [@MananTank](https://github.com/MananTank)! - Type refactor for useWallet hook
- [#1716](https://github.com/thirdweb-dev/js/pull/1716) [`d3c8626a`](https://github.com/thirdweb-dev/js/commit/d3c8626a5a8def882c1592b236048ebe88e85d49) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Better `useWallet()` API to obtain specific wallet instances.
```
const smartWallet = useWallet("smartWallet"); // returns a SmartWallet instance
const embeddedWallet = useWallet("embeddedWallet"); // returns a EmbeddedWallet instance
```
- [#1712](https://github.com/thirdweb-dev/js/pull/1712) [`9bd01de5`](https://github.com/thirdweb-dev/js/commit/9bd01de5f9c388e758fba9af7899dc4a9c5a0101) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Add `useCreateSessionKey` and `useRevokeSessionKey` hooks
```
const Component = () => {
const {
mutate: createSessionKey,
isLoading,
error,
} = useCreateSessionKey();
if (error) {
console.error("failed to create session key", error);
}
return (
<button
disabled={isLoading}
onClick={() => createSessionKey(
"0x...",
{
approvedCallTargets: ["0x..."], // the addresses of contracts that the session key can call
nativeTokenLimitPerTransaction: 0.1, // the maximum amount of native token (in ETH) that the session key can spend per transaction
startDate: new Date(), // the date when the session key becomes active
expirationDate = new Date(Date.now() + 24 * 60 * 60 * 1000); // the date when the session key expires
}
)}
>
Create Session Key
</button>
);
};
```
- Updated dependencies [[`9bd01de5`](https://github.com/thirdweb-dev/js/commit/9bd01de5f9c388e758fba9af7899dc4a9c5a0101), [`f35fbec1`](https://github.com/thirdweb-dev/js/commit/f35fbec1be14332d06e73b5f44f66975ef311d6c)]:
- @thirdweb-dev/[email protected]
- @thirdweb-dev/[email protected]
- @thirdweb-dev/[email protected]
## 3.16.3
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/react-core",
"version": "3.16.3",
"version": "3.16.5",
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/react-core",
"author": "thirdweb eng <[email protected]>",
"license": "Apache-2.0",
@@ -1,16 +1,74 @@
import { useWalletContext } from "../providers/thirdweb-wallet-provider";
import invariant from "tiny-invariant";
import type {
BloctoWallet,
CoinbaseWallet,
EmbeddedWallet,
FrameWallet,
LocalWallet,
MagicLink,
MetaMaskWallet,
PaperWallet,
PhantomWallet,
RainbowWallet,
SafeWallet,
SmartWallet,
TrustWallet,
WalletConnect,
walletIds,
} from "@thirdweb-dev/wallets";
import { WalletInstance } from "../types/wallet";
export type WalletId = (typeof walletIds)[keyof typeof walletIds];
type WalletIdToWalletTypeMap = {
metamask: MetaMaskWallet;
coinbase: CoinbaseWallet;
rainbowWallet: RainbowWallet;
blocto: BloctoWallet;
frame: FrameWallet;
localWallet: LocalWallet;
magicLink: MagicLink;
paper: PaperWallet;
smartWallet: SmartWallet;
safe: SafeWallet;
trust: TrustWallet;
embeddedWallet: EmbeddedWallet;
walletConnect: WalletConnect;
phantom: PhantomWallet;
walletConnectV1: WalletConnect;
};
/**
* @returns the current active wallet instance
*/
export function useWallet() {
export function useWallet<T extends WalletId>(
walletId: T,
): WalletIdToWalletTypeMap[T] | undefined;
export function useWallet(): WalletInstance | undefined;
export function useWallet<T extends WalletId>(walletId?: T) {
const context = useWalletContext();
invariant(
context,
"useWallet() hook must be used within a <ThirdwebProvider/>",
);
return context.activeWallet;
const activeWallet = context.activeWallet;
if (!activeWallet) {
return undefined;
}
// if walletId is provided, return the wallet instance only if it matches the walletId
if (walletId) {
if (activeWallet.walletId === walletId) {
return activeWallet as WalletIdToWalletTypeMap[T];
} else {
return undefined;
}
}
return activeWallet;
}
/**
@@ -1,27 +1,23 @@
import type { providers } from "ethers";
import {
RequiredParam,
requiredParamInvariant,
} from "../../../core/query-utils/required-param";
import { useSDKChainId } from "../useSDK";
import {
cacheKeys,
invalidateContractAndBalances,
} from "../../utils/cache-keys";
import { cacheKeys } from "../../utils/cache-keys";
import { useQueryWithNetwork } from "../query-utils/useQueryWithNetwork";
import {
useMutation,
UseMutationResult,
useQueryClient,
UseQueryResult,
} from "@tanstack/react-query";
import type {
SignerWithPermissions,
PermissionSnapshotInput,
SmartContract,
SignerPermissionsInput,
TransactionResult,
} from "@thirdweb-dev/sdk";
import invariant from "tiny-invariant";
import { WalletAddress } from "../../types";
import { useWallet } from "../../../core/hooks/wallet-hooks";
/** **********************/
/** READ HOOKS **/
@@ -127,65 +123,180 @@ export function useAccountAdminsAndSigners(
/** **********************/
/** WRITE HOOKS **/
/** **********************/
export type CreateSessionKeyInput = {
keyAddress: string;
permissions: SignerPermissionsInput;
};
/**
* Set the account's entire snapshot of permissions
* Create and add a session key for the smart wallet
*
* @example
* ```jsx
* const Component = () => {
* const { contract } = useContract("{{contract_address}}");
* const {
* mutate: setAccountSigners,
* mutate: createSessionKey,
* isLoading,
* error,
* } = useSetAccountSigners(contract);
* } = useCreateSessionKey();
*
* if (error) {
* console.error("failed to set account signers", error);
* console.error("failed to create session key", error);
* }
*
* return (
* <button
* disabled={isLoading}
* onClick={() => setAccountSigners("0x...")}
* onClick={() => createSessionKey(
* "0x...",
* {
* approvedCallTargets: ["0x..."], // the addresses of contracts that the session key can call
* nativeTokenLimitPerTransaction: 0.1, // the maximum amount of native token (in ETH) that the session key can spend per transaction
* startDate: new Date(), // the date when the session key becomes active
* expirationDate = new Date(Date.now() + 24 * 60 * 60 * 1000); // the date when the session key expires
* }
* )}
* >
* Set Account Signers
* Create Session Key
* </button>
* );
* };
* ```
*
* @param contract - an instance of a account contract
* @returns a mutation object that can be used to set the account signers
* @twfeature Account
* @see {@link https://portal.thirdweb.com/react/react.usesetaccountsigners?utm_source=sdk | Documentation}
* @beta
*/
export function useSetAccountSigners(
contract: RequiredParam<SmartContract>,
): UseMutationResult<
{ receipt: providers.TransactionReceipt },
export function useCreateSessionKey(): UseMutationResult<
TransactionResult,
unknown,
PermissionSnapshotInput,
unknown
CreateSessionKeyInput
> {
const activeChainId = useSDKChainId();
const contractAddress = contract?.getAddress();
const queryClient = useQueryClient();
return useMutation(
async (permissionsSnapshot: PermissionSnapshotInput) => {
requiredParamInvariant(contract, "contract is undefined");
return contract.account.resetAllPermissions(permissionsSnapshot);
},
{
onSettled: () =>
invalidateContractAndBalances(
queryClient,
contractAddress,
activeChainId,
),
},
);
const smartWallet = useWallet("smartWallet");
return useMutation(async (args: CreateSessionKeyInput) => {
requiredParamInvariant(smartWallet, "wallet is not connected");
return smartWallet.createSessionKey(args.keyAddress, args.permissions);
});
}
/**
* Revoke a session key on the smart wallet
*
* @example
* ```jsx
* const Component = () => {
* const {
* mutate: revokeSessionKey,
* isLoading,
* error,
* } = useRevokeSessionKey();
*
* if (error) {
* console.error("failed to revoke session key", error);
* }
*
* return (
* <button
* disabled={isLoading}
* onClick={() => revokeSessionKey("0x...")}
* >
* Revoke Session Key
* </button>
* );
* };
* ```
*
* @twfeature Account
*/
export function useRevokeSessionKey(): UseMutationResult<
TransactionResult,
unknown,
string
> {
const smartWallet = useWallet("smartWallet");
return useMutation(async (keyAddress: string) => {
requiredParamInvariant(smartWallet, "wallet is not connected");
return smartWallet.revokeSessionKey(keyAddress);
});
}
/**
* Add an additional admin on the smart wallet
*
* @example
* ```jsx
* const Component = () => {
* const {
* mutate: addAdmin,
* isLoading,
* error,
* } = useAddAdmin();
*
* if (error) {
* console.error("failed to add admin", error);
* }
*
* return (
* <button
* disabled={isLoading}
* onClick={() => addAdmin("0x...")}
* >
* Add admin
* </button>
* );
* };
* ```
*
* @twfeature Account
*/
export function useAddAdmin(): UseMutationResult<
TransactionResult,
unknown,
string
> {
const smartWallet = useWallet("smartWallet");
return useMutation(async (adminAddress: string) => {
requiredParamInvariant(smartWallet, "wallet is not connected");
return smartWallet.addAdmin(adminAddress);
});
}
/**
* Add an additional admin on the smart wallet
*
* @example
* ```jsx
* const Component = () => {
* const {
* mutate: removeAdmin,
* isLoading,
* error,
* } = useRemoveAdmin();
*
* if (error) {
* console.error("failed to remove admin", error);
* }
*
* return (
* <button
* disabled={isLoading}
* onClick={() => removeAdmin("0x...")}
* >
* Remove admin
* </button>
* );
* };
* ```
*
* @twfeature Account
*/
export function useRemoveAdmin(): UseMutationResult<
TransactionResult,
unknown,
string
> {
const smartWallet = useWallet("smartWallet");
return useMutation(async (adminAddress: string) => {
requiredParamInvariant(smartWallet, "wallet is not connected");
return smartWallet.removeAdmin(adminAddress);
});
}
+4 -1
View File
@@ -205,7 +205,10 @@ export {
// account
export {
useAccountSigners,
useSetAccountSigners,
useAddAdmin,
useRemoveAdmin,
useCreateSessionKey,
useRevokeSessionKey,
useAccountAdmins,
useAccountAdminsAndSigners,
} from "./hooks/async/account";
@@ -1,5 +1,9 @@
# @thirdweb-dev/react-native-compat
## 0.3.4
## 0.3.3
## 0.3.2
## 0.3.1
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@thirdweb-dev/react-native-compat",
"description": "Shims for Thirdweb in React Native Projects",
"version": "0.3.2",
"version": "0.3.4",
"author": "thirdweb eng <[email protected]>",
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/react-native-compat",
"license": "Apache-2.0",
+22
View File
@@ -1,5 +1,27 @@
# @thirdweb-dev/react-native
## 0.3.4
### Patch Changes
- Updated dependencies [[`ec36b13a`](https://github.com/thirdweb-dev/js/commit/ec36b13a30e0071548df0b7a6eb5299e2e65e4f9), [`6abb8459`](https://github.com/thirdweb-dev/js/commit/6abb8459712e387b6d8b2edf7eb16fb906c05dae), [`a6c36724`](https://github.com/thirdweb-dev/js/commit/a6c36724eb930ee0abbce876bb7847c859c6fb48)]:
- @thirdweb-dev/sdk@3.10.67
- @thirdweb-dev/react-core@3.16.5
- @thirdweb-dev/wallets@1.3.5
## 0.3.3
### Patch Changes
- [#1715](https://github.com/thirdweb-dev/js/pull/1715) [`f5caf6a3`](https://github.com/thirdweb-dev/js/commit/f5caf6a3502905666ae57f3304bb9074e09c9366) Thanks [@iketw](https://github.com/iketw)! - Fix encryption/decryption bug in embeddedWallet for React Native
- [#1709](https://github.com/thirdweb-dev/js/pull/1709) [`6f4c5cd7`](https://github.com/thirdweb-dev/js/commit/6f4c5cd746caf07f057e272aec9f204ba5905cd3) Thanks [@ElasticBottle](https://github.com/ElasticBottle)! - feat(react-native): update iteration count for embedded-wallet for better speed and future updates
- Updated dependencies [[`9bd01de5`](https://github.com/thirdweb-dev/js/commit/9bd01de5f9c388e758fba9af7899dc4a9c5a0101), [`93127047`](https://github.com/thirdweb-dev/js/commit/931270479ef227556a1077357a8c000b08de6e8d), [`d3c8626a`](https://github.com/thirdweb-dev/js/commit/d3c8626a5a8def882c1592b236048ebe88e85d49), [`f35fbec1`](https://github.com/thirdweb-dev/js/commit/f35fbec1be14332d06e73b5f44f66975ef311d6c), [`9bd01de5`](https://github.com/thirdweb-dev/js/commit/9bd01de5f9c388e758fba9af7899dc4a9c5a0101)]:
- @thirdweb-dev/wallets@1.3.4
- @thirdweb-dev/react-core@3.16.4
- @thirdweb-dev/sdk@3.10.66
## 0.3.2
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/react-native",
"version": "0.3.2",
"version": "0.3.4",
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/react-native",
"author": "thirdweb eng <[email protected]>",
"license": "Apache-2.0",
@@ -1,19 +1,21 @@
import AesGcmCrypto from "react-native-aes-gcm-crypto";
import { getRandomValues } from "../getRandomValues";
import QuickCrypto from "react-native-quick-crypto";
import { getRandomValues } from "../getRandomValues";
const ENCRYPTION_SEPARATOR = ":";
const KEY_ITERATION_COUNT = 5000000;
const DEPRECATED_KEY_ITERATION_COUNT = 5000000;
const CURRENT_KEY_ITERATION_COUNT = 650000;
const KEY_LENGTH = 256;
export async function getEncryptionKey(
pwd: string,
salt: ArrayBuffer,
iterationCounts: number,
): Promise<string> {
const key = QuickCrypto.pbkdf2Sync(
pwd,
salt,
KEY_ITERATION_COUNT,
iterationCounts,
KEY_LENGTH,
"sha256",
);
@@ -40,7 +42,9 @@ export async function encryptShareWeb(
pwd: string,
): Promise<string> {
const salt = getRandomValues(new Uint8Array(16));
const keyBase64 = await getEncryptionKey(pwd, salt);
const iterationCount = CURRENT_KEY_ITERATION_COUNT;
const keyBase64 = await getEncryptionKey(pwd, salt, iterationCount);
let encryptedValue;
try {
@@ -49,11 +53,23 @@ export async function encryptShareWeb(
throw new Error(`Error encrypting share: ${error}`);
}
const returnValue = `${encryptedValue.content}${ENCRYPTION_SEPARATOR}${
encryptedValue.iv
}${ENCRYPTION_SEPARATOR}${bufferToBase64(salt)}${ENCRYPTION_SEPARATOR}${
encryptedValue.tag
}`;
// Ref: https://github.com/craftzdog/react-native-aes-gcm-crypto/blob/master/android/src/main/java/com/reactnativeaesgcmcrypto/AesGcmCryptoModule.kt#L111
// Cipher text is a base64 string
const cipherTextBase64Buffer = Buffer.from(encryptedValue.content, "base64");
// Tag is a hex string
const tagHexBuffer = Buffer.from(encryptedValue.tag, "hex");
const cipherTextWithTag = Buffer.concat([
cipherTextBase64Buffer,
tagHexBuffer,
]);
// iv is a hex string
const ivBase64 = Buffer.from(encryptedValue.iv, "hex").toString("base64");
const returnValue = `${bufferToBase64(
cipherTextWithTag,
)}${ENCRYPTION_SEPARATOR}${ivBase64}${ENCRYPTION_SEPARATOR}${bufferToBase64(
salt,
)}${ENCRYPTION_SEPARATOR}${iterationCount}`;
return returnValue;
}
@@ -62,16 +78,58 @@ export async function decryptShareWeb(
encryptedShareDetails: string,
pwd: string,
): Promise<string> {
const [encryptedShare, iv, salt, tag] =
encryptedShareDetails.split(ENCRYPTION_SEPARATOR);
const [
encryptedShareWithTagBase64,
ivBase64,
saltBase64,
maybeIterationCount,
] = encryptedShareDetails.split(ENCRYPTION_SEPARATOR);
const key = await getEncryptionKey(pwd, base64ToBuffer(salt));
let iterationCount = maybeIterationCount
? parseInt(maybeIterationCount)
: undefined;
if (!iterationCount) {
iterationCount = DEPRECATED_KEY_ITERATION_COUNT;
}
const key = await getEncryptionKey(
pwd,
base64ToBuffer(saltBase64),
iterationCount,
);
const encryptedShareWithTagBuffer = Buffer.from(
encryptedShareWithTagBase64,
"base64",
);
// The tag is a 16 bytes long hex string
const tagBytesLength = 16;
const cipherTextBufferLength =
encryptedShareWithTagBuffer.length - tagBytesLength;
// Get cipherText and tag from encryptedShareWithTagBuffer
const cipherTextBuffer = Buffer.from(
encryptedShareWithTagBuffer.subarray(0, cipherTextBufferLength),
);
const tagBuffer = Buffer.from(
encryptedShareWithTagBuffer.subarray(cipherTextBufferLength),
);
const originalBase64CipherText = cipherTextBuffer.toString("base64");
// converting to hex since the decrypt function expects a hex string
// Ref: https://github.com/craftzdog/react-native-aes-gcm-crypto/blob/master/android/src/main/java/com/reactnativeaesgcmcrypto/AesGcmCryptoModule.kt#L111
const hexStringTag = tagBuffer.toString("hex");
// converting to hex since the decrypt function expects a hex string
// Ref: https://github.com/craftzdog/react-native-aes-gcm-crypto/blob/master/android/src/main/java/com/reactnativeaesgcmcrypto/AesGcmCryptoModule.kt#L111
const ivBufferHex = Buffer.from(ivBase64, "base64").toString("hex");
const normalizedShare = await AesGcmCrypto.decrypt(
encryptedShare,
originalBase64CipherText,
key,
iv,
tag,
ivBufferHex,
hexStringTag,
false,
);
+22
View File
@@ -1,5 +1,27 @@
# @thirdweb-dev/react
## 3.16.5
### Patch Changes
- Updated dependencies [[`ec36b13a`](https://github.com/thirdweb-dev/js/commit/ec36b13a30e0071548df0b7a6eb5299e2e65e4f9), [`6abb8459`](https://github.com/thirdweb-dev/js/commit/6abb8459712e387b6d8b2edf7eb16fb906c05dae), [`a6c36724`](https://github.com/thirdweb-dev/js/commit/a6c36724eb930ee0abbce876bb7847c859c6fb48)]:
- @thirdweb-dev/sdk@3.10.67
- @thirdweb-dev/react-core@3.16.5
- @thirdweb-dev/wallets@1.3.5
## 3.16.4
### Patch Changes
- [#1713](https://github.com/thirdweb-dev/js/pull/1713) [`46672a49`](https://github.com/thirdweb-dev/js/commit/46672a490a82924e3c4a003419e3eaad10d6dd8b) Thanks [@nachoiacovino](https://github.com/nachoiacovino)! - Update upsell link
- [#1717](https://github.com/thirdweb-dev/js/pull/1717) [`ffb1bb0a`](https://github.com/thirdweb-dev/js/commit/ffb1bb0a603a69c22eb8b6429326c23a2977e5a6) Thanks [@MananTank](https://github.com/MananTank)! - Add spinner in the Google Sign in popup instead of blank page
- Updated dependencies [[`9bd01de5`](https://github.com/thirdweb-dev/js/commit/9bd01de5f9c388e758fba9af7899dc4a9c5a0101), [`93127047`](https://github.com/thirdweb-dev/js/commit/931270479ef227556a1077357a8c000b08de6e8d), [`d3c8626a`](https://github.com/thirdweb-dev/js/commit/d3c8626a5a8def882c1592b236048ebe88e85d49), [`f35fbec1`](https://github.com/thirdweb-dev/js/commit/f35fbec1be14332d06e73b5f44f66975ef311d6c), [`9bd01de5`](https://github.com/thirdweb-dev/js/commit/9bd01de5f9c388e758fba9af7899dc4a9c5a0101)]:
- @thirdweb-dev/wallets@1.3.4
- @thirdweb-dev/react-core@3.16.4
- @thirdweb-dev/sdk@3.10.66
## 3.16.3
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/react",
"version": "3.16.3",
"version": "3.16.5",
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/react",
"author": "thirdweb eng <[email protected]>",
"license": "Apache-2.0",
+20
View File
@@ -43,3 +43,23 @@ export {
// react-core
export * from "@thirdweb-dev/react-core";
// wallets
export {
LocalWallet,
EmbeddedWallet,
SmartWallet,
SafeWallet,
WalletConnect,
PhantomWallet,
RainbowWallet,
MetaMaskWallet,
TrustWallet,
CoinbaseWallet,
BloctoWallet,
FrameWallet,
PaperWallet,
ZerionWallet,
MagicLink,
SignerWallet,
InjectedWallet,
} from "@thirdweb-dev/wallets";
@@ -106,7 +106,7 @@ export const WalletSelector: React.FC<{
color="primaryText"
hoverColor="accentText"
target="_blank"
href="https://thirdweb.com/dashboard/wallets/connect"
href="https://thirdweb.com/connect?utm_source=cw"
style={{
display: "flex",
alignItems: "center",
@@ -0,0 +1,67 @@
export function openGoogleSignInWindow() {
const win = window.open("", undefined, "width=350, height=500");
if (win) {
win.document.title = "Sign In - Google Accounts";
win.document.body.innerHTML = spinnerWindowHtml;
}
return win;
}
const spinnerWindowHtml = `
<svg class="loader" viewBox="0 0 50 50">
<circle
cx="25"
cy="25"
r="20"
fill="none"
stroke="#000"
stroke-width="4"
/>
</svg>
<style>
body,
html {
height: 100%;
margin: 0;
padding: 0;
}
body {
display: flex;
justify-content: center;
align-items: center;
}
.loader {
width: 15vw;
height: 15vw;
animation: spin 2s linear infinite;
}
.loader circle {
animation: loading 1.5s linear infinite;
}
@keyframes loading {
0% {
stroke-dasharray: 1, 150;
stroke-dashoffset: 0;
}
50% {
stroke-dasharray: 90, 150;
stroke-dashoffset: -35;
}
100% {
stroke-dasharray: 90, 150;
stroke-dashoffset: -124;
}
}
@keyframes spin {
100% {
transform: rotate(360deg);
}
}
</style>
`;
@@ -14,6 +14,7 @@ import { GoogleIcon } from "../../ConnectWallet/icons/GoogleIcon";
import { InputSelectionUI } from "../InputSelectionUI";
import type { EmbeddedWalletLoginType } from "./types";
import { TextDivider } from "../../../components/TextDivider";
import { openGoogleSignInWindow } from "../../utils/openGoogleSignInWindow";
export const EmbeddedWalletFormUI = (props: {
onSelect: (loginType: EmbeddedWalletLoginType) => void;
@@ -29,7 +30,8 @@ export const EmbeddedWalletFormUI = (props: {
try {
const embeddedWallet = createWalletInstance(props.walletConfig);
setConnectionStatus("connecting");
const googleWindow = window.open("", "Login", "width=350, height=500");
const googleWindow = openGoogleSignInWindow();
if (!googleWindow) {
throw new Error("Failed to open google login window");
}
@@ -15,6 +15,7 @@ import { ModalTitle } from "../../../components/modalElements";
import { Text } from "../../../components/text";
import { iconSize } from "../../../design-system";
import { GoogleIcon } from "../../ConnectWallet/icons/GoogleIcon";
import { openGoogleSignInWindow } from "../../utils/openGoogleSignInWindow";
export const EmbeddedWalletGoogleLogin = (
props: ConnectUIProps<EmbeddedWallet>,
@@ -29,7 +30,7 @@ export const EmbeddedWalletGoogleLogin = (
try {
const embeddedWallet = createWalletInstance(props.walletConfig);
setConnectionStatus("connecting");
const googleWindow = window.open("", "Login", "width=350, height=500");
const googleWindow = openGoogleSignInWindow();
if (!googleWindow) {
throw new Error("Failed to open google login window");
}
@@ -14,6 +14,7 @@ import { GoogleIcon } from "../../ConnectWallet/icons/GoogleIcon";
import { InputSelectionUI } from "../InputSelectionUI";
import { PaperLoginType } from "./types";
import { TextDivider } from "../../../components/TextDivider";
import { openGoogleSignInWindow } from "../../utils/openGoogleSignInWindow";
export const PaperFormUI = (props: {
onSelect: (loginType: PaperLoginType) => void;
@@ -30,7 +31,7 @@ export const PaperFormUI = (props: {
try {
const paperWallet = createWalletInstance(props.walletConfig);
setConnectionStatus("connecting");
const googleWindow = window.open("", "Login", "width=350, height=500");
const googleWindow = openGoogleSignInWindow();
if (!googleWindow) {
throw new Error("Failed to open google login window");
}
@@ -15,6 +15,7 @@ import { ModalTitle } from "../../../components/modalElements";
import { Text } from "../../../components/text";
import { iconSize } from "../../../design-system";
import { GoogleIcon } from "../../ConnectWallet/icons/GoogleIcon";
import { openGoogleSignInWindow } from "../../utils/openGoogleSignInWindow";
export const PaperGoogleLogin = (props: ConnectUIProps<PaperWallet>) => {
const { goBack, modalSize } = props;
@@ -29,7 +30,7 @@ export const PaperGoogleLogin = (props: ConnectUIProps<PaperWallet>) => {
try {
const paperWallet = createWalletInstance(props.walletConfig);
setConnectionStatus("connecting");
const googleWindow = window.open("", "Login", "width=350, height=500");
const googleWindow = openGoogleSignInWindow();
if (!googleWindow) {
throw new Error("Failed to open google login window");
}
+16
View File
@@ -1,5 +1,21 @@
# @thirdweb-dev/sdk
## 3.10.67
### Patch Changes
- [#1731](https://github.com/thirdweb-dev/js/pull/1731) [`ec36b13a`](https://github.com/thirdweb-dev/js/commit/ec36b13a30e0071548df0b7a6eb5299e2e65e4f9) Thanks [@kien-ngo](https://github.com/kien-ngo)! - Improve add/removeContract methods
- [#1727](https://github.com/thirdweb-dev/js/pull/1727) [`6abb8459`](https://github.com/thirdweb-dev/js/commit/6abb8459712e387b6d8b2edf7eb16fb906c05dae) Thanks [@kien-ngo](https://github.com/kien-ngo)! - [SDK] Improve concurrent requests on NFT-related code
- [#1728](https://github.com/thirdweb-dev/js/pull/1728) [`a6c36724`](https://github.com/thirdweb-dev/js/commit/a6c36724eb930ee0abbce876bb7847c859c6fb48) Thanks [@kien-ngo](https://github.com/kien-ngo)! - [SDK] Improve transfer function for user wallet
## 3.10.66
### Patch Changes
- [#1308](https://github.com/thirdweb-dev/js/pull/1308) [`f35fbec1`](https://github.com/thirdweb-dev/js/commit/f35fbec1be14332d06e73b5f44f66975ef311d6c) Thanks [@kumaryash90](https://github.com/kumaryash90)! - Base Router for dynamic contracts
## 3.10.65
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/sdk",
"version": "3.10.65",
"version": "3.10.67",
"description": "The main thirdweb SDK.",
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/sdk",
"license": "Apache-2.0",
+1 -1
View File
@@ -252,7 +252,7 @@ export class AuctionHasNotEndedError extends Error {
export class ExtensionNotImplementedError extends Error {
constructor(feature: Feature) {
super(
`This functionality is not available because the contract does not implement the '${feature.docLinks.contracts}' Extension. Learn how to unlock this functionality at https://portal.thirdweb.com/extensions `,
`This functionality is not available because the contract does not implement the '${feature.name}' Extension. Learn how to unlock this functionality at https://portal.thirdweb.com/extensions `,
);
}
}
+4 -2
View File
@@ -114,8 +114,10 @@ export async function fetchTokenMetadataForContract(
ERC165MetadataAbi,
provider,
) as IERC165;
const isERC721 = await erc165.supportsInterface(InterfaceId_IERC721);
const isERC1155 = await erc165.supportsInterface(InterfaceId_IERC1155);
const [isERC721, isERC1155] = await Promise.all([
erc165.supportsInterface(InterfaceId_IERC721),
erc165.supportsInterface(InterfaceId_IERC1155),
]);
if (isERC721) {
const erc721 = new Contract(
contractAddress,
@@ -43,7 +43,7 @@ export function generatePluginFunctions(
}
export function generateExtensionFunctions(
extensionAbi: any,
extensionAbi: Abi,
): ExtensionFunction[] {
const extensionInterface = new utils.Interface(extensionAbi);
const extensionFunctions: ExtensionFunction[] = [];
@@ -205,7 +205,7 @@ export const FEATURE_GASLESS = {
docLinks: {
sdk: "sdk.gaslesstransaction",
// TODO add the correct name for this once it's added to portal
contracts: "",
contracts: "IERC2771Context",
},
abis: [IERC2771ContextAbi],
features: {},
@@ -217,7 +217,7 @@ export const FEATURE_PACK_VRF = {
docLinks: {
sdk: "sdk.packvrf",
//TODO
contracts: "",
contracts: "IPackVRF",
},
abis: [IPackVRFAbi],
features: {},
@@ -229,7 +229,7 @@ export const FEATURE_PLUGIN_ROUTER = {
docLinks: {
sdk: "sdk.pluginrouter",
//TODO
contracts: "",
contracts: "PluginRouter",
},
abis: [getAllPluginsAbi],
features: {},
@@ -241,7 +241,7 @@ export const FEATURE_DYNAMIC_CONTRACT = {
docLinks: {
sdk: "",
//TODO
contracts: "",
contracts: "IExtensionsManager",
},
abis: [getAllExtensionsAbi],
features: {},
@@ -253,7 +253,7 @@ export const FEATURE_DIRECT_LISTINGS = {
docLinks: {
// TODO
sdk: "",
contracts: "",
contracts: "IDirectListings",
},
abis: [IDirectListingsAbi],
features: {},
@@ -265,7 +265,7 @@ export const FEATURE_ENGLISH_AUCTIONS = {
docLinks: {
// TODO
sdk: "",
contracts: "",
contracts: "IEnglishAuctions",
},
abis: [IEnglishAuctionsAbi],
features: {},
@@ -277,7 +277,7 @@ export const FEATURE_OFFERS = {
docLinks: {
// TODO
sdk: "",
contracts: "",
contracts: "IOffers",
},
abis: [IOffersAbi],
features: {},
@@ -289,7 +289,7 @@ export const FEATURE_ACCOUNT_FACTORY = {
docLinks: {
// TODO
sdk: "sdk.accountFactory",
contracts: "",
contracts: "IAccountFactory",
},
abis: [IAccountFactoryCore],
features: {},
@@ -301,7 +301,7 @@ export const FEATURE_ACCOUNT_PERMISSIONS = {
docLinks: {
// TODO
sdk: "sdk.account",
contracts: "",
contracts: "IAccountPermissions",
},
abis: [IAccountPermissions],
features: {},
@@ -313,7 +313,7 @@ export const FEATURE_ACCOUNT = {
docLinks: {
// TODO
sdk: "sdk.account",
contracts: "",
contracts: "IAccount",
},
abis: [IAccount],
features: { [FEATURE_ACCOUNT_PERMISSIONS.name]: FEATURE_ACCOUNT_PERMISSIONS },
@@ -325,7 +325,7 @@ export const FEATURE_AIRDROP_ERC20 = {
docLinks: {
// TODO
sdk: "",
contracts: "",
contracts: "IAirdropERC20",
},
abis: [IAirdropERC20],
features: {},
@@ -337,7 +337,7 @@ export const FEATURE_AIRDROP_ERC721 = {
docLinks: {
// TODO
sdk: "",
contracts: "",
contracts: "IAirdropERC721",
},
abis: [IAirdropERC721],
features: {},
@@ -349,7 +349,7 @@ export const FEATURE_AIRDROP_ERC1155 = {
docLinks: {
// TODO
sdk: "",
contracts: "",
contracts: "IAirdropERC1155",
},
abis: [IAirdropERC1155],
features: {},
@@ -226,8 +226,10 @@ export class NFTDrop extends StandardErc721<PrebuiltNFTDrop> {
* Get the total count NFTs in this drop contract, both claimed and unclaimed
*/
override async totalSupply() {
const claimed = await this.totalClaimedSupply();
const unclaimed = await this.totalUnclaimedSupply();
const [claimed, unclaimed] = await Promise.all([
this.totalClaimedSupply(),
this.totalUnclaimedSupply(),
]);
return claimed.add(unclaimed);
}
@@ -249,8 +249,10 @@ export class SignatureDrop extends StandardErc721<SignatureDropContract> {
* Get the total count NFTs in this drop contract, both claimed and unclaimed
*/
override async totalSupply() {
const claimed = await this.totalClaimedSupply();
const unclaimed = await this.totalUnclaimedSupply();
const [claimed, unclaimed] = await Promise.all([
this.totalClaimedSupply(),
this.totalUnclaimedSupply(),
]);
return claimed.add(unclaimed);
}
@@ -14,6 +14,7 @@ import type {
IRoyalty,
OffersLogic,
Ownable,
BaseRouter,
} from "@thirdweb-dev/contracts-js";
import { ThirdwebStorage } from "@thirdweb-dev/storage";
import { BaseContract, CallOverrides } from "ethers";
@@ -38,6 +39,7 @@ import {
FEATURE_PLATFORM_FEE,
FEATURE_PRIMARY_SALE,
FEATURE_ROYALTY,
FEATURE_DYNAMIC_CONTRACT,
} from "../constants/thirdweb-features";
import { Account } from "../core/classes/account";
import { AccountFactory } from "../core/classes/account-factory";
@@ -76,6 +78,7 @@ import { SDKOptions } from "../schema/sdk-options";
import { Address } from "../schema/shared/Address";
import { BaseContractInterface } from "../types/contract";
import { BaseERC1155, BaseERC20, BaseERC721 } from "../types/eips";
import { ExtensionManager } from "../core/classes/extension-manager";
/**
* Custom contract dynamic class with feature detection
@@ -116,9 +119,12 @@ export class SmartContract<
public encoder: ContractEncoder<TContract>;
public estimator: GasCostEstimator<TContract>;
public publishedMetadata: ContractPublishedMetadata<TContract>;
public abi: Abi;
public metadata: ContractMetadata<BaseContract, typeof CustomContractSchema>;
get abi(): Abi {
return AbiSchema.parse(this.contractWrapper.abi || []);
}
/**
* Handle royalties
*/
@@ -355,6 +361,10 @@ export class SmartContract<
return assertEnabled(this.detectAccount(), FEATURE_ACCOUNT);
}
get extensions(): ExtensionManager {
return assertEnabled(this.detectBaseRouter(), FEATURE_DYNAMIC_CONTRACT);
}
private _chainId: number;
get chainId() {
return this._chainId;
@@ -378,7 +388,6 @@ export class SmartContract<
this._chainId = chainId;
this.storage = storage;
this.contractWrapper = contractWrapper;
this.abi = AbiSchema.parse(abi || []);
this.events = new ContractEvents(this.contractWrapper);
this.encoder = new ContractEncoder(this.contractWrapper);
@@ -583,6 +592,18 @@ export class SmartContract<
return undefined;
}
private detectBaseRouter() {
if (
detectContractFeature<BaseRouter>(
this.contractWrapper,
FEATURE_DYNAMIC_CONTRACT.name,
)
) {
return new ExtensionManager(this.contractWrapper);
}
return undefined;
}
private detectAirdrop20() {
if (
detectContractFeature<AirdropERC20>(this.contractWrapper, "AirdropERC20")
@@ -409,7 +409,7 @@ export class AccountPermissions implements DetectableFeature {
*
* @example
* ```javascript
* const tx = await contract.account.updateAccess(signer, restrictions);
* const tx = await contract.account.updatePermissions(signer, permissions);
* const receipt = tx.receipt();
* ```
*
@@ -99,6 +99,22 @@ export class ContractWrapper<
) as TContract;
}
public updateAbi(updatedAbi: ContractInterface): void {
// re-connect the contract with the new signer / provider
this.writeContract = new Contract(
this.address,
updatedAbi,
this.getSignerOrProvider(),
) as TContract;
// setup the read only contract
this.readContract = this.writeContract.connect(
this.getProvider(),
) as TContract;
this.abi = AbiSchema.parse(updatedAbi);
}
/**
* @internal
*/
@@ -0,0 +1,522 @@
import type { BaseRouter } from "@thirdweb-dev/contracts-js";
import { DetectableFeature } from "../interfaces/DetectableFeature";
import { ContractWrapper } from "./contract-wrapper";
import { buildTransactionFunction } from "../../common/transactions";
import { Transaction } from "./transactions";
import {
Extension,
ExtensionFunction,
ExtensionMetadata,
FunctionInput,
} from "../../types/extension";
import { FEATURE_DYNAMIC_CONTRACT } from "../../constants/thirdweb-features";
import { ContractInterface } from "ethers";
import { generateExtensionFunctions } from "../../common/plugin/generatePluginFunctions";
import { Abi, AbiSchema, CommonContractSchema } from "../../schema";
import { utils } from "ethers";
import invariant from "tiny-invariant";
import { ExtensionAddedEvent } from "@thirdweb-dev/contracts-js/dist/declarations/src/CoreRouter";
import {
THIRDWEB_DEPLOYER,
deployContractDeterministic,
deployWithThrowawayDeployer,
fetchContractMetadataFromAddress,
fetchPublishedContractFromPolygon,
getDeploymentInfo,
} from "../../common";
import { joinABIs } from "../../common/plugin/joinABIs";
import { TransactionReceipt } from "@ethersproject/abstract-provider";
import { ExtensionRemovedEvent } from "@thirdweb-dev/contracts-js/dist/declarations/src/CoreRouter";
import { ExtensionReplacedEvent } from "@thirdweb-dev/contracts-js/dist/declarations/src/BaseRouter";
import { DynamicContractExtensionMetadataOrUri } from "../../types";
export class ExtensionManager implements DetectableFeature {
featureName = FEATURE_DYNAMIC_CONTRACT.name;
private contractWrapper: ContractWrapper<BaseRouter>;
constructor(contractWrapper: ContractWrapper<BaseRouter>) {
this.contractWrapper = contractWrapper;
}
getAddress(): string {
return this.contractWrapper.readContract.address;
}
/** ******************************
* READ FUNCTIONS
*******************************/
public async getAll(): Promise<Extension[]> {
const extensions: Extension[] =
await this.contractWrapper.readContract.getAllExtensions();
return extensions;
}
public async get(extensionName: string): Promise<Extension> {
const extension: Extension =
await this.contractWrapper.readContract.getExtension(extensionName);
return extension;
}
public async getExtensionAddress(extensionName: string): Promise<string> {
const extension = await this.get(extensionName);
return extension.metadata.implementation;
}
public async getAllFunctions(
extensionName: string,
): Promise<ExtensionFunction[]> {
const extension = await this.get(extensionName);
return extension.functions;
}
public async getExtensionForFunction(
functionInput: FunctionInput,
): Promise<ExtensionMetadata> {
let selector = functionInput.functionSelector;
if (!selector) {
invariant(
functionInput.functionSignature,
"Atleast one of function selector and signature must be provided",
);
selector = utils.id(functionInput.functionSignature).substring(0, 10);
}
const extensionMetadata: ExtensionMetadata =
await this.contractWrapper.readContract.getMetadataForFunction(selector);
return extensionMetadata;
}
public async getExtensionAddressForFunction(
functionInput: FunctionInput,
): Promise<string> {
const extensionMetadata = await this.getExtensionForFunction(functionInput);
return extensionMetadata.implementation;
}
/** ******************************
* WRITE FUNCTIONS
*******************************/
/**
* Adds an extension to the contract
*/
add = /* @__PURE__ */ buildTransactionFunction(
async (inputArgs: {
extension: Extension;
extensionAbi?: ContractInterface;
}): Promise<Transaction<Promise<TransactionReceipt>>> => {
return Transaction.fromContractWrapper({
contractWrapper: this.contractWrapper,
method: "addExtension",
args: [inputArgs.extension],
parse: async (receipt) => {
const events = this.contractWrapper.parseLogs<ExtensionAddedEvent>(
"ExtensionAdded",
receipt.logs,
);
if (events.length < 1) {
throw new Error("No ExtensionAdded event found");
}
const extensionAbi = inputArgs.extensionAbi
? AbiSchema.parse(inputArgs.extensionAbi)
: (
await fetchContractMetadataFromAddress(
inputArgs.extension.metadata.implementation,
this.contractWrapper.getProvider(),
this.contractWrapper.storage,
)
).abi;
const abiToAdd = this.filterAbiForAdd(
extensionAbi,
inputArgs.extension,
);
const updatedAbi = joinABIs([
AbiSchema.parse(this.contractWrapper.abi),
abiToAdd,
]);
this.contractWrapper.updateAbi(updatedAbi);
return receipt;
},
});
},
);
/**
* Adds a deployed extension to the contract
*/
addDeployed = /* @__PURE__ */ buildTransactionFunction(
async (inputArgs: {
extensionName: string;
extensionAddress: string;
extensionMetadata?: DynamicContractExtensionMetadataOrUri;
extensionAbi?: ContractInterface;
}): Promise<Transaction<Promise<TransactionReceipt>>> => {
let extensionAbi = inputArgs.extensionAbi;
if (!extensionAbi) {
const metadata = await fetchContractMetadataFromAddress(
inputArgs.extensionAddress,
this.contractWrapper.getProvider(),
this.contractWrapper.storage,
this.contractWrapper.options,
);
extensionAbi = metadata.abi;
}
invariant(extensionAbi, "Require extension ABI");
let extensionMetadataUri = "";
if (inputArgs.extensionMetadata) {
if (typeof inputArgs.extensionMetadata === "string") {
extensionMetadataUri = inputArgs.extensionMetadata;
} else {
const parsedMetadata = await CommonContractSchema.parseAsync(
inputArgs.extensionMetadata,
);
extensionMetadataUri = await this.contractWrapper.storage.upload(
parsedMetadata,
);
}
}
const extensionFunctions: ExtensionFunction[] =
generateExtensionFunctions(AbiSchema.parse(extensionAbi));
const extension: Extension = {
metadata: {
name: inputArgs.extensionName,
metadataURI: extensionMetadataUri,
implementation: inputArgs.extensionAddress,
},
functions: extensionFunctions,
};
return this.add.prepare({ extension, extensionAbi });
},
);
/**
* Adds a published extension to the contract, and deploys it deterministically if necessary
*/
addPublished = /* @__PURE__ */ buildTransactionFunction(
async (inputArgs: {
extensionName: string;
publisherAddress?: string;
version?: string;
extensionMetadataOverride?: DynamicContractExtensionMetadataOrUri;
}): Promise<Transaction<Promise<TransactionReceipt>>> => {
const version = inputArgs.version || "latest";
const { deployedExtensionAddress, extensionMetadata } =
await this.deployExtension(
inputArgs.extensionName,
inputArgs.publisherAddress || THIRDWEB_DEPLOYER,
version,
);
return this.addDeployed.prepare({
extensionName: inputArgs.extensionName,
extensionAddress: deployedExtensionAddress,
extensionMetadata:
inputArgs.extensionMetadataOverride || extensionMetadata,
});
},
);
replace = /* @__PURE__ */ buildTransactionFunction(
async (inputArgs: {
extension: Extension;
extensionAbi?: ContractInterface;
}): Promise<Transaction<Promise<TransactionReceipt>>> => {
return Transaction.fromContractWrapper({
contractWrapper: this.contractWrapper,
method: "replaceExtension",
args: [inputArgs.extension],
parse: async (receipt) => {
const events = this.contractWrapper.parseLogs<ExtensionReplacedEvent>(
"ExtensionReplaced",
receipt.logs,
);
if (events.length < 1) {
throw new Error("No ExtensionReplaced event found");
}
const extensionAbi = inputArgs.extensionAbi
? AbiSchema.parse(inputArgs.extensionAbi)
: (
await fetchContractMetadataFromAddress(
inputArgs.extension.metadata.implementation,
this.contractWrapper.getProvider(),
this.contractWrapper.storage,
)
).abi;
const contractAbi = this.filterAbiForRemove(
AbiSchema.parse(this.contractWrapper.abi),
extensionAbi,
);
const abiToAdd = this.filterAbiForAdd(
extensionAbi,
inputArgs.extension,
);
const updatedAbi = joinABIs([contractAbi, abiToAdd]);
this.contractWrapper.updateAbi(updatedAbi);
return receipt;
},
});
},
);
replaceDeployed = /* @__PURE__ */ buildTransactionFunction(
async (inputArgs: {
extensionName: string;
extensionAddress: string;
extensionMetadata?: DynamicContractExtensionMetadataOrUri;
extensionAbi?: ContractInterface;
}): Promise<Transaction<Promise<TransactionReceipt>>> => {
let extensionAbi = inputArgs.extensionAbi;
if (!extensionAbi) {
const metadata = await fetchContractMetadataFromAddress(
inputArgs.extensionAddress,
this.contractWrapper.getProvider(),
this.contractWrapper.storage,
this.contractWrapper.options,
);
extensionAbi = metadata.abi;
}
invariant(extensionAbi, "Require extension ABI");
let extensionMetadataUri = "";
if (inputArgs.extensionMetadata) {
if (typeof inputArgs.extensionMetadata === "string") {
extensionMetadataUri = inputArgs.extensionMetadata;
} else {
const parsedMetadata = await CommonContractSchema.parseAsync(
inputArgs.extensionMetadata,
);
extensionMetadataUri = await this.contractWrapper.storage.upload(
parsedMetadata,
);
}
}
const extensionFunctions: ExtensionFunction[] =
generateExtensionFunctions(AbiSchema.parse(extensionAbi));
const extension: Extension = {
metadata: {
name: inputArgs.extensionName,
metadataURI: extensionMetadataUri,
implementation: inputArgs.extensionAddress,
},
functions: extensionFunctions,
};
return this.replace.prepare({ extension, extensionAbi });
},
);
replacePublished = /* @__PURE__ */ buildTransactionFunction(
async (inputArgs: {
extensionName: string;
publisherAddress?: string;
version?: string;
extensionMetadataOverride?: DynamicContractExtensionMetadataOrUri;
}): Promise<Transaction<Promise<TransactionReceipt>>> => {
const version = inputArgs.version || "latest";
const { deployedExtensionAddress, extensionMetadata } =
await this.deployExtension(
inputArgs.extensionName,
inputArgs.publisherAddress || THIRDWEB_DEPLOYER,
version,
);
return this.replaceDeployed.prepare({
extensionName: inputArgs.extensionName,
extensionAddress: deployedExtensionAddress,
extensionMetadata:
inputArgs.extensionMetadataOverride || extensionMetadata,
});
},
);
remove = /* @__PURE__ */ buildTransactionFunction(
async (inputArgs: {
extensionName: string;
}): Promise<Transaction<Promise<TransactionReceipt>>> => {
const extensionAddress = await this.getExtensionAddress(
inputArgs.extensionName,
);
return Transaction.fromContractWrapper({
contractWrapper: this.contractWrapper,
method: "removeExtension",
args: [inputArgs.extensionName],
parse: async (receipt) => {
const events = this.contractWrapper.parseLogs<ExtensionRemovedEvent>(
"ExtensionRemoved",
receipt.logs,
);
if (events.length < 1) {
throw new Error("No ExtensionRemoved event found");
}
const extensionAbi = (
await fetchContractMetadataFromAddress(
extensionAddress,
this.contractWrapper.getProvider(),
this.contractWrapper.storage,
)
).abi;
const updatedAbi = this.filterAbiForRemove(
AbiSchema.parse(this.contractWrapper.abi),
extensionAbi,
);
this.contractWrapper.updateAbi(updatedAbi);
return receipt;
},
});
},
);
/** ******************************
* Internal / private
*******************************/
private filterAbiForAdd(extensionAbi: Abi, extension: Extension) {
const extensionAbiInterface = new utils.Interface(extensionAbi);
const extensionFunctionSelectors = extension.functions.map(
(fn) => fn.functionSelector,
);
const filtered = extensionAbi.filter((item) => {
const fnFragment = Object.values(new utils.Interface([item]).functions);
if (fnFragment.length === 0) {
return false;
}
const fnSigHash = extensionAbiInterface.getSighash(fnFragment[0]);
return extensionFunctionSelectors.includes(fnSigHash);
});
return filtered;
}
private filterAbiForRemove(fullAbi: Abi, abiToRemove: Abi) {
const fullAbiInterface = new utils.Interface(fullAbi);
const interfaceToRemove = new utils.Interface(abiToRemove);
const functionsToRemove = Object.values(interfaceToRemove.functions).map(
(fn) => interfaceToRemove.getSighash(fn),
);
const filtered = fullAbi.filter((item) => {
const fnFragment = Object.values(new utils.Interface([item]).functions);
if (fnFragment.length === 0) {
return false;
}
const fnSigHash = fullAbiInterface.getSighash(fnFragment[0]);
return !functionsToRemove.includes(fnSigHash);
});
return filtered;
}
private async deployExtension(
extensionName: string,
publisherAddress: string,
version: string = "latest",
): Promise<{ deployedExtensionAddress: string; extensionMetadata: string }> {
const published = await fetchPublishedContractFromPolygon(
publisherAddress,
extensionName,
version,
this.contractWrapper.storage,
this.contractWrapper.options.clientId,
this.contractWrapper.options.secretKey,
);
const deploymentInfo = await getDeploymentInfo(
published.metadataUri,
this.contractWrapper.storage,
this.contractWrapper.getProvider(),
"",
this.contractWrapper.options.clientId,
this.contractWrapper.options.secretKey,
);
const implementationAddress = deploymentInfo.find(
(i) => i.type === "implementation",
)?.transaction.predictedAddress as string;
// deploy infra + plugins + implementation using a throwaway Deployer contract
// filter out already deployed contracts (data is empty)
const transactionsToSend = deploymentInfo.filter(
(i) => i.transaction.data && i.transaction.data.length > 0,
);
const transactionsforDirectDeploy = transactionsToSend
.filter((i) => {
return i.type !== "infra";
})
.map((i) => i.transaction);
const transactionsForThrowawayDeployer = transactionsToSend
.filter((i) => {
return i.type === "infra";
})
.map((i) => i.transaction);
const signer = this.contractWrapper.getSigner();
invariant(signer, "Signer is required");
// deploy via throwaway deployer, multiple infra contracts in one transaction
await deployWithThrowawayDeployer(
signer,
transactionsForThrowawayDeployer,
{},
);
// send each transaction directly to Create2 factory
// process txns one at a time
for (const tx of transactionsforDirectDeploy) {
try {
await deployContractDeterministic(signer, tx);
} catch (e) {
console.debug(
`Error deploying contract at ${tx.predictedAddress}`,
(e as any)?.message,
);
}
}
return {
deployedExtensionAddress: implementationAddress,
extensionMetadata: published.metadataUri,
};
}
}
+8 -16
View File
@@ -48,15 +48,11 @@ export class ContractRegistry extends ContractWrapper<TWRegistry> {
): Promise<Transaction<TransactionResult>> => {
const deployerAddress = await this.getSignerAddress();
const contractEncoder = new ContractEncoder(this);
const encoded: string[] = await Promise.all(
contractAddresses.map(async (address) =>
contractEncoder.encode("add", [
deployerAddress,
await resolveAddress(address),
]),
),
const encoded = (
await Promise.all(contractAddresses.map((addr) => resolveAddress(addr)))
).map((address) =>
contractEncoder.encode("add", [deployerAddress, address]),
);
return Transaction.fromContractWrapper({
contractWrapper: this,
method: "multicall",
@@ -79,15 +75,11 @@ export class ContractRegistry extends ContractWrapper<TWRegistry> {
): Promise<Transaction<TransactionResult>> => {
const deployerAddress = await this.getSignerAddress();
const contractEncoder = new ContractEncoder(this);
const encoded: string[] = await Promise.all(
contractAddresses.map(async (address) =>
contractEncoder.encode("remove", [
deployerAddress,
await resolveAddress(address),
]),
),
const encoded = (
await Promise.all(contractAddresses.map((addr) => resolveAddress(addr)))
).map((address) =>
contractEncoder.encode("remove", [deployerAddress, address]),
);
return Transaction.fromContractWrapper({
contractWrapper: this,
method: "multicall",
+1 -1
View File
@@ -1103,7 +1103,7 @@ export class ContractDeployer extends RPCConnectionHandler {
parsedMetadata.seller_fee_basis_points,
];
return await this.deployReleasedContract.prepare(
return await this.deployPublishedContract.prepare(
THIRDWEB_DEPLOYER,
"OpenEditionERC721",
deployArgs,
@@ -98,15 +98,17 @@ export class UserWallet {
amount: Amount,
currencyAddress: AddressOrEns = NATIVE_TOKEN_ADDRESS,
): Promise<TransactionResult> {
const resolvedTo = await resolveAddress(to);
const resolvedCurrency = await resolveAddress(currencyAddress);
const [resolvedTo, resolvedCurrency, amountInWei] = await Promise.all([
resolveAddress(to),
resolveAddress(currencyAddress),
normalizePriceValue(
this.connection.getProvider(),
amount,
currencyAddress,
),
]);
const signer = this.requireWallet();
const amountInWei = await normalizePriceValue(
this.connection.getProvider(),
amount,
currencyAddress,
);
if (isNativeToken(resolvedCurrency)) {
// native token transfer
const from = await signer.getAddress();
@@ -0,0 +1,31 @@
import { BytesLikeSchema } from "../../../../core/schema/shared";
import { AddressSchema } from "../../shared/AddressSchema";
import { z } from "zod";
/**
* @internal
*/
export const ExtensionMetadataInput = /* @__PURE__ */ (() =>
z.object({
name: z.string(),
metadataURI: z.string(),
implementation: AddressSchema,
}))();
/**
* @internal
*/
export const ExtensionFunctionInput = /* @__PURE__ */ (() =>
z.object({
functionSelector: BytesLikeSchema,
functionSignature: z.string(),
}))();
/**
* @internal
*/
export const ExtensionInput = /* @__PURE__ */ (() =>
z.object({
metadata: ExtensionMetadataInput,
functions: z.array(ExtensionFunctionInput),
}))();
@@ -10,14 +10,16 @@ import { z } from "zod";
* @internal
*/
export const CommonContractSchema = /* @__PURE__ */ (() =>
z.object({
name: z.string(),
description: z.string().optional(),
image: FileOrBufferOrStringSchema.optional(),
external_link: z.string().optional(),
app_uri: z.string().optional(),
social_urls: z.record(z.string()).optional(),
}))();
z
.object({
name: z.string(),
description: z.string().optional(),
image: FileOrBufferOrStringSchema.optional(),
external_link: z.string().optional(),
app_uri: z.string().optional(),
social_urls: z.record(z.string()).optional(),
})
.catchall(z.unknown()))();
export type CommonContractSchemaInput = z.input<typeof CommonContractSchema>;
@@ -1,6 +1,7 @@
import { AddressOrEns } from "../../schema/shared/AddressOrEnsSchema";
import { FileOrBufferOrString } from "@thirdweb-dev/storage";
import type { BigNumberish, Bytes } from "ethers";
import { CommonContractSchemaInput } from "../../schema";
/**
* Options for deploying an NFT contract
@@ -407,3 +408,10 @@ export interface AirdropContractDeployMetadata {
*/
app_uri?: string;
}
/**
* @public
*/
export type DynamicContractExtensionMetadataOrUri =
| CommonContractSchemaInput
| string;
+29
View File
@@ -0,0 +1,29 @@
import {
ExtensionFunctionInput,
ExtensionInput,
ExtensionMetadataInput,
} from "../schema/contracts/common/extension";
import { z } from "zod";
/**
* Input model to pass an extension's metadata -- name, metadataURI, implementation address
* @public
*/
export type ExtensionMetadata = z.input<typeof ExtensionMetadataInput>;
/**
* Input model to pass a list function-selectors
* @public
*/
export type ExtensionFunction = z.input<typeof ExtensionFunctionInput>;
/**
* Input model to pass a list of extension-addresses + function-selectors
* @public
*/
export type Extension = z.input<typeof ExtensionInput>;
export type FunctionInput = {
functionSelector?: string;
functionSignature?: string;
};
+1 -1
View File
@@ -268,7 +268,7 @@ describe("Custom Contracts", async () => {
try {
c.roles.get("admin");
} catch (e) {
expectError(e, "contract does not implement the 'permissions' Extension");
expectError(e, "contract does not implement the 'Permissions' Extension");
}
});
@@ -0,0 +1,281 @@
import { assert } from "chai";
import { AbiSchema, SmartContract, isExtensionEnabled } from "../../src/evm";
import { jsonProvider, sdk, signers } from "./before-setup";
import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
import {
CoreRouter__factory,
OffersLogic__factory,
} from "@thirdweb-dev/contracts-js";
import { deployContractAndUploadMetadata } from "./utils";
import { Extension, ExtensionFunction } from "../../src/evm/types/extension";
import { generateExtensionFunctions } from "../../src/evm/common/plugin/generatePluginFunctions";
import invariant from "tiny-invariant";
describe("Base Router for Dynamic Contracts", async () => {
let adminWallet: SignerWithAddress;
let coreRouterAddress: string;
let coreRouter: SmartContract;
beforeEach(async () => {
await jsonProvider.send("hardhat_reset", []);
[adminWallet] = signers;
// deploy base router
coreRouterAddress = await deployContractAndUploadMetadata(
CoreRouter__factory.abi,
CoreRouter__factory.bytecode,
adminWallet,
[adminWallet.address, []],
);
// instantiate custom contract
coreRouter = await sdk.getContract(coreRouterAddress);
});
it("should detect base router in a dynamic contract", async () => {
const contract = await sdk.getContract(coreRouterAddress);
assert(isExtensionEnabled(contract.abi, "DynamicContract"));
});
it("should add extensions", async () => {
// deploy an extension contract
// Offers
const offersLogicAddress = await deployContractAndUploadMetadata(
OffersLogic__factory.abi,
OffersLogic__factory.bytecode,
adminWallet,
[],
);
// generate functions array for the extension
const extensionFunctions: ExtensionFunction[] = generateExtensionFunctions(
AbiSchema.parse(OffersLogic__factory.abi),
);
const routerInput: Extension = {
metadata: {
name: "OffersLogic",
metadataURI: "",
implementation: offersLogicAddress,
},
functions: extensionFunctions,
};
// add extension
await coreRouter.extensions.add({ extension: routerInput });
// read extension
const extensions = await coreRouter.extensions.getAll();
assert(extensions.length === 1);
assert(extensions[0].metadata.name === "OffersLogic");
// call function on extension
const totalOffers = await coreRouter.offers.getTotalCount();
assert(totalOffers.eq(0));
});
it("should add extensions with abi", async () => {
// deploy an extension contract
// Offers
const offersLogicAddress = await deployContractAndUploadMetadata(
OffersLogic__factory.abi,
OffersLogic__factory.bytecode,
adminWallet,
[],
);
// add extension
await coreRouter.extensions.addDeployed({
extensionName: "OffersLogic",
extensionAddress: offersLogicAddress,
extensionAbi: OffersLogic__factory.abi,
});
// read extension
const extensions = await coreRouter.extensions.getAll();
assert(extensions.length === 1);
assert(extensions[0].metadata.name === "OffersLogic");
});
it("should replace extensions", async () => {
// deploy an extension contract
// Offers
const offersLogicAddress = await deployContractAndUploadMetadata(
OffersLogic__factory.abi,
OffersLogic__factory.bytecode,
adminWallet,
[],
);
// add extension
await coreRouter.extensions.addDeployed({
extensionName: "OffersLogic",
extensionAddress: offersLogicAddress,
extensionAbi: OffersLogic__factory.abi,
});
// read extension
let extensions = await coreRouter.extensions.getAll();
assert(extensions.length === 1);
assert(extensions[0].metadata.name === "OffersLogic");
assert(isExtensionEnabled(coreRouter.abi, "Offers"));
// replace extension
const newAddress = await deployContractAndUploadMetadata(
OffersLogic__factory.abi,
OffersLogic__factory.bytecode,
adminWallet,
[],
);
const newFunctions: ExtensionFunction[] = generateExtensionFunctions(
AbiSchema.parse(OffersLogic__factory.abi),
);
invariant(newFunctions, "");
const routerInput: Extension = {
metadata: {
name: "OffersLogic",
metadataURI: "",
implementation: newAddress,
},
functions: newFunctions,
};
await coreRouter.extensions.replace({ extension: routerInput });
// read extension
extensions = await coreRouter.extensions.getAll();
assert(extensions.length === 1);
assert(extensions[0].metadata.name === "OffersLogic");
assert(extensions[0].metadata.implementation === newAddress);
// call function on extension
const totalOffers = await coreRouter.offers.getTotalCount();
assert(totalOffers.eq(0));
});
it("should update extensions with abi", async () => {
// deploy an extension contract
// Offers
const offersLogicAddress = await deployContractAndUploadMetadata(
OffersLogic__factory.abi,
OffersLogic__factory.bytecode,
adminWallet,
[],
);
// add extension
await coreRouter.extensions.addDeployed({
extensionName: "OffersLogic",
extensionAddress: offersLogicAddress,
extensionAbi: OffersLogic__factory.abi,
});
// read extension
let extensions = await coreRouter.extensions.getAll();
const oldURI = extensions[0].metadata.metadataURI;
assert(extensions.length === 1);
assert(extensions[0].metadata.name === "OffersLogic");
assert(isExtensionEnabled(coreRouter.abi, "Offers"));
// update extension
const newAddress = await deployContractAndUploadMetadata(
OffersLogic__factory.abi,
OffersLogic__factory.bytecode,
adminWallet,
[],
);
await coreRouter.extensions.replaceDeployed({
extensionName: "OffersLogic",
extensionAddress: newAddress,
extensionMetadata: { name: "OffersLogicV2" },
});
// read extension
extensions = await coreRouter.extensions.getAll();
assert(extensions.length === 1);
assert(extensions[0].metadata.name === "OffersLogic");
assert(extensions[0].metadata.implementation === newAddress);
assert(extensions[0].metadata.metadataURI !== oldURI);
assert(isExtensionEnabled(coreRouter.abi, "Offers"));
});
it("should remove extensions", async () => {
// deploy an extension contract
// Offers
const offersLogicAddress = await deployContractAndUploadMetadata(
OffersLogic__factory.abi,
OffersLogic__factory.bytecode,
adminWallet,
[],
);
const extensionFunctions: ExtensionFunction[] = generateExtensionFunctions(
AbiSchema.parse(OffersLogic__factory.abi),
);
const routerInput: Extension = {
metadata: {
name: "OffersLogic",
metadataURI: "",
implementation: offersLogicAddress,
},
functions: extensionFunctions,
};
// add extension
await coreRouter.extensions.add({ extension: routerInput });
// read extension
let extensions = await coreRouter.extensions.getAll();
assert(extensions.length === 1);
assert(extensions[0].metadata.name === "OffersLogic");
assert(isExtensionEnabled(coreRouter.abi, "Offers"));
// remove extension
await coreRouter.extensions.remove({
extensionName: routerInput.metadata.name,
});
// read extension
extensions = await coreRouter.extensions.getAll();
assert(extensions.length === 0);
// call function on extension
assert(!isExtensionEnabled(coreRouter.abi, "Offers"));
});
it("should get extension for function", async () => {
// deploy an extension contract
// Offers
const offersLogicAddress = await deployContractAndUploadMetadata(
OffersLogic__factory.abi,
OffersLogic__factory.bytecode,
adminWallet,
[],
);
// generate functions array for the extension
const extensionFunctions: ExtensionFunction[] = generateExtensionFunctions(
AbiSchema.parse(OffersLogic__factory.abi),
);
const routerInput: Extension = {
metadata: {
name: "OffersLogic",
metadataURI: "",
implementation: offersLogicAddress,
},
functions: extensionFunctions,
};
// add extension
await coreRouter.extensions.add({ extension: routerInput });
// read extension
const extension = await coreRouter.extensions.getExtensionForFunction({
functionSignature: "acceptOffer(uint256)",
});
assert(extension.implementation === offersLogicAddress);
assert(extension.name === "OffersLogic");
});
});
+6
View File
@@ -1,5 +1,11 @@
# @thirdweb-dev/service-utils
## 0.4.6
### Patch Changes
- [#1729](https://github.com/thirdweb-dev/js/pull/1729) [`52b1a0fc`](https://github.com/thirdweb-dev/js/commit/52b1a0fcacb136e8da417d883e5365f2ab558d59) Thanks [@arcoraven](https://github.com/arcoraven)! - Make accountId optional in publishUsageEvents. This will be hydrated internally.
## 0.4.5
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/service-utils",
"version": "0.4.5",
"version": "0.4.6",
"main": "dist/thirdweb-dev-service-utils.cjs.js",
"module": "dist/thirdweb-dev-service-utils.esm.js",
"exports": {
@@ -21,11 +21,16 @@ const usageEventSchema = z.object({
"bundler",
"paymaster",
"relayer",
"connectWallet",
]),
action: z.string(),
accountId: z.string(),
// Optional
/**
* The following fields are optional.
*/
accountId: z.string().optional(),
isClientEvent: z.boolean().optional(),
apiKeyId: z.string().optional(),
creatorWalletAddress: z.string().optional(),
clientId: z.string().optional(),
+20
View File
@@ -1,5 +1,25 @@
# @thirdweb-dev/unity-js-bridge
## 0.2.85
### Patch Changes
- [#1725](https://github.com/thirdweb-dev/js/pull/1725) [`dc38a243`](https://github.com/thirdweb-dev/js/commit/dc38a243c201d0070d75dd36b1a4f8d1eecbc438) Thanks [@0xFirekeeper](https://github.com/0xFirekeeper)! - [Unity] Fix google popup blocker
- Updated dependencies [[`ec36b13a`](https://github.com/thirdweb-dev/js/commit/ec36b13a30e0071548df0b7a6eb5299e2e65e4f9), [`6abb8459`](https://github.com/thirdweb-dev/js/commit/6abb8459712e387b6d8b2edf7eb16fb906c05dae), [`a6c36724`](https://github.com/thirdweb-dev/js/commit/a6c36724eb930ee0abbce876bb7847c859c6fb48)]:
- @thirdweb-dev/sdk@3.10.67
- @thirdweb-dev/wallets@1.3.5
- @thirdweb-dev/auth@3.2.48
## 0.2.84
### Patch Changes
- Updated dependencies [[`9bd01de5`](https://github.com/thirdweb-dev/js/commit/9bd01de5f9c388e758fba9af7899dc4a9c5a0101), [`f35fbec1`](https://github.com/thirdweb-dev/js/commit/f35fbec1be14332d06e73b5f44f66975ef311d6c)]:
- @thirdweb-dev/wallets@1.3.4
- @thirdweb-dev/sdk@3.10.66
- @thirdweb-dev/auth@3.2.47
## 0.2.83
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/unity-js-bridge",
"version": "0.2.83",
"version": "0.2.85",
"main": "dist/thirdweb-unity-bridge.js",
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/unity-js-bridge",
"license": "Apache-2.0",
@@ -300,10 +300,17 @@ class ThirdwebBridge implements TWBridge {
const embeddedWallet = walletInstance as EmbeddedWallet;
if (useGoogle?.toLowerCase() === "true") {
console.log("Using Google OAuth");
const googleWindow = this.openGoogleSignInWindow();
if (!googleWindow) {
throw new Error("Failed to open google login window");
}
await embeddedWallet.connect({
chainId: chainIdNumber,
loginType: "headless_google_oauth",
openedWindow: googleWindow,
closeOpenedWindow: (openedWindow) => {
openedWindow.close();
},
});
} else {
if (!email) {
@@ -634,6 +641,72 @@ class ThirdwebBridge implements TWBridge {
});
return localWallet;
}
public openGoogleSignInWindow() {
const win = window.open("", undefined, "width=350, height=500");
if (win) {
win.document.title = "Sign In - Google Accounts";
win.document.body.innerHTML = `
<svg class="loader" viewBox="0 0 50 50">
<circle
cx="25"
cy="25"
r="20"
fill="none"
stroke="#000"
stroke-width="4"
/>
</svg>
<style>
body,
html {
height: 100%;
margin: 0;
padding: 0;
}
body {
display: flex;
justify-content: center;
align-items: center;
}
.loader {
width: 15vw;
height: 15vw;
animation: spin 2s linear infinite;
}
.loader circle {
animation: loading 1.5s linear infinite;
}
@keyframes loading {
0% {
stroke-dasharray: 1, 150;
stroke-dashoffset: 0;
}
50% {
stroke-dasharray: 90, 150;
stroke-dashoffset: -35;
}
100% {
stroke-dasharray: 90, 150;
stroke-dashoffset: -124;
}
}
@keyframes spin {
100% {
transform: rotate(360deg);
}
}
</style>
`;
}
return win;
}
}
// add the bridge to the window object type
+29
View File
@@ -1,5 +1,34 @@
# @thirdweb-dev/wallets
## 1.3.5
### Patch Changes
- Updated dependencies [[`ec36b13a`](https://github.com/thirdweb-dev/js/commit/ec36b13a30e0071548df0b7a6eb5299e2e65e4f9), [`6abb8459`](https://github.com/thirdweb-dev/js/commit/6abb8459712e387b6d8b2edf7eb16fb906c05dae), [`a6c36724`](https://github.com/thirdweb-dev/js/commit/a6c36724eb930ee0abbce876bb7847c859c6fb48)]:
- @thirdweb-dev/sdk@3.10.67
## 1.3.4
### Patch Changes
- [#1712](https://github.com/thirdweb-dev/js/pull/1712) [`9bd01de5`](https://github.com/thirdweb-dev/js/commit/9bd01de5f9c388e758fba9af7899dc4a9c5a0101) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - New SmartWallet API for session keys
You can now add admins and scoped session keys to smart wallets directly with a simple API:
```
const smartWallet = new SmartWallet(config);
await smartWallet.connect({ personalWallet })
await smartWallet.createSessionKey(keyAddress, permissions);
await smartWallet.revokeSessionKey(keyAddress);
await smartWallet.addAdmin(adminAddress);
await smartWallet.removeAdmin(adminAddress);
```
- Updated dependencies [[`f35fbec1`](https://github.com/thirdweb-dev/js/commit/f35fbec1be14332d06e73b5f44f66975ef311d6c)]:
- @thirdweb-dev/sdk@3.10.66
## 1.3.3
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/wallets",
"version": "1.3.3",
"version": "1.3.5",
"main": "dist/thirdweb-dev-wallets.cjs.js",
"module": "dist/thirdweb-dev-wallets.esm.js",
"types": "dist/thirdweb-dev-wallets.cjs.d.ts",
@@ -17,6 +17,8 @@ import { BigNumber, ethers, providers } from "ethers";
import {
ChainOrRpcUrl,
getChainProvider,
SignerPermissionsInput,
SignerWithPermissions,
SmartContract,
ThirdwebSDK,
Transaction,
@@ -224,10 +226,13 @@ export class SmartWalletConnector extends Connector<SmartWalletConnectionArgs> {
throw new Error("Smart wallet already deployed");
}
const signer = await this.getSigner();
const tx = await signer.sendTransaction({
to: await signer.getAddress(),
data: "0x",
});
const tx = await signer.sendTransaction(
{
to: await signer.getAddress(),
data: "0x",
},
true, // batched tx flag to avoid hitting the Router fallback method
);
const receipt = await tx.wait();
return { receipt };
}
@@ -243,6 +248,65 @@ export class SmartWalletConnector extends Connector<SmartWalletConnectionArgs> {
return await this.accountApi.isAcountDeployed();
}
async deployIfNeeded(): Promise<void> {
const isDeployed = await this.isDeployed();
if (!isDeployed) {
await this.deploy();
}
}
async grantPermissions(
target: string,
permissions: SignerPermissionsInput,
): Promise<TransactionResult> {
await this.deployIfNeeded();
const accountContract = await this.getAccountContract();
return accountContract.account.grantPermissions(target, permissions);
}
async revokePermissions(target: string): Promise<TransactionResult> {
await this.deployIfNeeded();
const accountContract = await this.getAccountContract();
return accountContract.account.revokeAccess(target);
}
async addAdmin(target: string): Promise<TransactionResult> {
await this.deployIfNeeded();
const accountContract = await this.getAccountContract();
return accountContract.account.grantAdminPermissions(target);
}
async removeAdmin(target: string): Promise<TransactionResult> {
await this.deployIfNeeded();
const accountContract = await this.getAccountContract();
return accountContract.account.revokeAdminPermissions(target);
}
async getAllActiveSigners(): Promise<SignerWithPermissions[]> {
const isDeployed = await this.isDeployed();
if (isDeployed) {
const accountContract = await this.getAccountContract();
return accountContract.account.getAllAdminsAndSigners();
} else {
const personalWallet = await this.personalWallet?.getSigner();
if (!personalWallet) {
throw new Error("Personal wallet not connected");
}
return [
{
isAdmin: true,
signer: await personalWallet.getAddress(),
permissions: {
startDate: new Date(0),
expirationDate: new Date(0),
nativeTokenLimitPerTransaction: BigNumber.from(0),
approvedCallTargets: [],
},
},
];
}
}
/**
* Get the underlying account contract of the smart wallet.
* @returns the account contract of the smart wallet.
@@ -2,7 +2,7 @@ import { LOCAL_NODE_PKEY, SmartContract, ThirdwebSDK } from "@thirdweb-dev/sdk";
import { BigNumberish, BigNumber, ethers, utils } from "ethers";
import { AccountApiParams } from "../types";
import { BaseAccountAPI } from "./base-api";
import { MINIMAL_ACCOUNT_ABI } from "./constants";
import { ACCOUNT_CORE_ABI } from "./constants";
export class AccountAPI extends BaseAccountAPI {
sdk: ThirdwebSDK;
@@ -42,7 +42,7 @@ export class AccountAPI extends BaseAccountAPI {
} else {
this.accountContract = await this.sdk.getContract(
await this.getAccountAddress(),
MINIMAL_ACCOUNT_ABI,
ACCOUNT_CORE_ABI,
);
}
}
File diff suppressed because it is too large Load Diff
@@ -15,4 +15,4 @@ export const walletIds = {
walletConnect: "walletConnect",
phantom: "phantom",
// add new ids sorted alphabetically
};
} as const;
+1 -1
View File
@@ -12,7 +12,7 @@ export class BloctoWallet extends AbstractClientWallet<BloctoOptions> {
connector?: Connector;
name: string = "Blocto";
static id = walletIds.blocto;
static id = walletIds.blocto as string;
static meta: WalletMeta = {
name: "Blocto",
iconURL:
@@ -31,7 +31,7 @@ export class CoinbaseWallet extends AbstractClientWallet {
},
};
static id = walletIds.coinbase;
static id= walletIds.coinbase as string;
public get walletName() {
return "Coinbase Wallet" as const;
}
@@ -18,7 +18,7 @@ export class EmbeddedWallet extends AbstractClientWallet<
> {
connector?: Connector;
static id = walletIds.embeddedWallet;
static id = walletIds.embeddedWallet as string;
static meta = {
name: "Embedded Wallet",
+1 -1
View File
@@ -5,7 +5,7 @@ import { AbstractClientWallet, WalletOptions } from "./base";
export class FrameWallet extends AbstractClientWallet {
connector?: Connector;
static id = walletIds.frame;
static id = walletIds.frame as string;
public get walletName() {
return "Frame Wallet";
}
+1 -1
View File
@@ -4,7 +4,7 @@ import { AbstractClientWallet, WalletOptions } from "./base";
export class InjectedWallet extends AbstractClientWallet {
connector?: Connector;
static id = "injected" as const;
static id = "injected" as string;
public get walletName() {
return "Injected Wallet";
}
@@ -32,7 +32,7 @@ export class LocalWallet extends AbstractClientWallet<
ethersWallet?: Wallet;
#storage: AsyncStorage;
static id = walletIds.localWallet;
static id = walletIds.localWallet as string;
static meta = {
name: "Local Wallet",
+1 -1
View File
@@ -30,7 +30,7 @@ export class MagicLink extends AbstractClientWallet<
name: "Magic Link",
};
static id = walletIds.magicLink;
static id = walletIds.magicLink as string;
public get walletName() {
return "Magic Link" as const;
+1 -1
View File
@@ -55,7 +55,7 @@ export class MetaMaskWallet extends AbstractClientWallet<MetamaskAdditionalOptio
},
};
static id = walletIds.metamask;
static id = walletIds.metamask as string;
public get walletName() {
return "MetaMask" as const;
@@ -17,7 +17,7 @@ export class PaperWallet extends AbstractClientWallet<
> {
connector?: Connector;
static id = walletIds.paper;
static id = walletIds.paper as string;
static meta = {
name: "Paper Wallet",
+1 -1
View File
@@ -26,7 +26,7 @@ export class PhantomWallet extends AbstractClientWallet {
},
};
static id = walletIds.phantom;
static id = walletIds.phantom as string;
public get walletName() {
return "Phantom" as const;
@@ -55,7 +55,7 @@ export class RainbowWallet extends AbstractClientWallet<RainbowAdditionalOptions
},
};
static id = walletIds.rainbow;
static id = walletIds.rainbow as string;
public get walletName() {
return "Rainbow Wallet" as const;
+1 -1
View File
@@ -23,7 +23,7 @@ export class SafeWallet extends AbstractClientWallet<
"ipfs://QmbbyxDDmmLQh8DzzeUR6X6B75bESsNUFmbdvS3ZsQ2pN1/SafeToken.svg",
};
static id = walletIds.safe;
static id = walletIds.safe as string;
public get walletName() {
return "Safe Wallet" as const;
}
@@ -9,6 +9,8 @@ import {
Transaction,
TransactionResult,
SmartContract,
SignerPermissionsInput,
SignerWithPermissions,
} from "@thirdweb-dev/sdk";
import { walletIds } from "../constants/walletIds";
import {
@@ -41,7 +43,7 @@ export class SmartWallet
"ipfs://QmeAJVqn17aDNQhjEU3kcWVZCFBrfta8LzaDGkS8Egdiyk/smart-wallet.svg",
};
static id = walletIds.smartWallet;
static id = walletIds.smartWallet as string;
public get walletName() {
return "Smart Wallet" as const;
}
@@ -123,6 +125,16 @@ export class SmartWallet
return connector.deploy();
}
/**
* Manually deploy the smart wallet contract. If already deployed this will do nothing.
* Note that this is not necessary as the smart wallet will be deployed automatically on the first transaction the user makes.
* @returns the transaction receipt
*/
async deployIfNeeded(): Promise<void> {
const connector = await this.getConnector();
return connector.deployIfNeeded();
}
/**
* Check if the smart wallet contract is deployed
* @returns true if the smart wallet contract is deployed
@@ -132,6 +144,54 @@ export class SmartWallet
return connector.isDeployed();
}
/**
* Create and add a session key to the smart wallet.
* @param keyAddress the address of the session key to add.
* @param permissions the permissions to grant to the session key.
*/
async createSessionKey(
keyAddress: string,
permissions: SignerPermissionsInput,
): Promise<TransactionResult> {
const connector = await this.getConnector();
return connector.grantPermissions(keyAddress, permissions);
}
/**
* Remove a session key from the smart wallet.
* @param keyAddress the address of the session key to remove.
*/
async revokeSessionKey(keyAddress: string): Promise<TransactionResult> {
const connector = await this.getConnector();
return connector.revokePermissions(keyAddress);
}
/**
* Add another admin to the smart wallet.
* @param adminAddress the address of the admin to add.
*/
async addAdmin(adminAddress: string): Promise<TransactionResult> {
const connector = await this.getConnector();
return connector.addAdmin(adminAddress);
}
/**
* Remove an admin from the smart wallet.
* @param adminAddress the address of the admin to remove.
*/
async removeAdmin(adminAddress: string): Promise<TransactionResult> {
const connector = await this.getConnector();
return connector.removeAdmin(adminAddress);
}
/**
* Get all the admins and session keys active on the smart wallet.
*/
async getAllActiveSigners(): Promise<SignerWithPermissions[]> {
const connector = await this.getConnector();
return connector.getAllActiveSigners();
}
/**
* Get the underlying account contract of the smart wallet.
* @returns the account contract of the smart wallet.
+1 -1
View File
@@ -56,7 +56,7 @@ export class TrustWallet extends AbstractClientWallet<TrustAdditionalOptions> {
},
};
static id = walletIds.trust;
static id = walletIds.trust as string;
public get walletName() {
return "Trust Wallet" as const;
@@ -44,7 +44,7 @@ export class WalletConnect extends AbstractClientWallet<WalletConnectOptions> {
connector?: Connector;
static id = walletIds.walletConnect;
static id = walletIds.walletConnect as string;
static meta = {
name: "WalletConnect",
+1 -1
View File
@@ -40,7 +40,7 @@ export class ZerionWallet extends AbstractClientWallet<ZerionAdditionalOptions>
walletConnectConnector?: WalletConnectConnectorType;
isInjected: boolean;
static id = "zerion" as const;
static id = "zerion" as string;
static meta = {
name: "Zerion Wallet",
iconURL: "ipfs://Qmb1LhNtMUkzbgk1V8ZiUSRXjMJGRkS5HH3R71KyRgjdBG/zerion.png",
+10776 -21533
View File
File diff suppressed because it is too large Load Diff