Compare commits

...
Author SHA1 Message Date
github-actions[bot]GitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
8b5e344f72 Version Packages (#1768)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-10-16 09:26:13 -07:00
Manan TankandGitHub 010955ff5c [react] - Fix OTP input not working well with Japanese keyboard (#1769) 2023-10-16 15:41:54 +00:00
Joaquim VergesandGitHub 956b5645e4 [SDK] Enable adding smart wallet admins with signature (#1756) 2023-10-15 23:46:45 +00:00
github-actions[bot]GitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
068db44603 Version Packages (#1761)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-10-14 17:18:54 -07:00
Manan TankandGitHub dd7d0fca2d [react] Fix missing "Continue as Guest" button in ConnectWallet in Social+Guest setup (#1766) 2023-10-14 23:29:07 +00:00
Kien NgoandGitHub 526176c471 [SDK] Perf improv to marketplace methods (#1764) 2023-10-14 21:18:11 +00:00
Kien NgoandGitHub 09120c923c [SDK] Perf improv for getDeploymentInfo (#1765) 2023-10-14 18:12:19 +00:00
Kien NgoandGitHub d1f8e95162 [SDK] Perf improve to fetchPublishedContractFromPolygon() (#1763) 2023-10-14 14:29:09 +00:00
Kien NgoandGitHub 6573556d0e [SDK] Perf improv for drop-claim-conditions (#1762) 2023-10-13 22:53:35 +00:00
iketwandGitHub fc62b91ee5 [RN] Adds Signing with Google to EmbeddedWallet (#1724) 2023-10-13 18:51:47 +00:00
Kien NgoandGitHub 08e9cd206b [React/React Native] Add pagination to useOwnedNFTs (#1758) 2023-10-13 17:23:43 +00:00
fb0f12ae28 refactor(react): OTP Input Readability Enhancements (#1692)
Co-authored-by: Manan Tank <[email protected]>
2023-10-13 06:43:20 -07:00
github-actions[bot]GitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
c710fc1c62 Version Packages (#1745)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-10-12 21:12:59 -07:00
iketwandGitHub f41ee6fb1c [RN] Correctly use custom themes (#1760) 2023-10-13 03:53:48 +00:00
iketwandGitHub 513c332ed0 [RN] Show selectUI component when passing one supportedWallet (#1759) 2023-10-13 03:02:47 +00:00
Kien NgoandGitHub 693f349db2 [SDK] Add pagination to getOwned for ERC721 & 1155 (#1695) 2023-10-12 23:34:39 +00:00
YashandGitHub 63ad0050f0 Update deployment setup (#1757) 2023-10-12 17:34:31 +00:00
iketwandGitHub dcdb0dcb83 [Wallets] Typo fixes for SW api (#1754) 2023-10-11 22:40:05 +00:00
iketwandGitHub b3b9edca29 [RN] Minor bug fix and UI improvements to connecting an app to a smar… (#1753) 2023-10-11 21:51:19 +00:00
Winston YeoandGitHub 1fbbc2d27d [SDK] update ApiKeyMetadata (#1752) 2023-10-11 20:38:44 +00:00
Joaquim VergesandGitHub 4b98e487d4 [Wallets] Add wallet analytics (#1730) 2023-10-11 20:19:22 +00:00
Phillip HoandGitHub 95380eaed4 chore: Add walletType to usage tracker payload (#1751) 2023-10-11 17:55:51 +00:00
Joaquim VergesandGitHub 1f3c64d361 [SDK] handle upgradeable proxies and optimize proxy resolution (#1747) 2023-10-11 16:39:06 +00:00
Manan TankandGitHub bb1666b92b [react] ConnectWallet UI fixes + improvements (#1750) 2023-10-11 13:20:40 +00:00
farhanW3andGitHub 0f12d1f903 RPC Override Fix (#1746) 2023-10-11 01:01:03 +00:00
Joaquim VergesandGitHub 74941603a9 [Wallets] Fix executeBatch type mismatch (#1744) 2023-10-10 21:24:08 +00:00
124 changed files with 7073 additions and 1158 deletions
+21
View File
@@ -1,5 +1,26 @@
# @thirdweb-dev/auth
## 4.0.4
### Patch Changes
- Updated dependencies []:
- @thirdweb-dev/wallets@2.0.4
## 4.0.3
### Patch Changes
- Updated dependencies []:
- @thirdweb-dev/wallets@2.0.3
## 4.0.2
### Patch Changes
- Updated dependencies [[`74941603`](https://github.com/thirdweb-dev/js/commit/74941603a96456da46a47147f67c98235ac55022), [`4b98e487`](https://github.com/thirdweb-dev/js/commit/4b98e487d4b47225d7095e5444dc5e7608db48a3), [`dcdb0dcb`](https://github.com/thirdweb-dev/js/commit/dcdb0dcb838168a22c8335738852316dfb6e6fc9), [`b3b9edca`](https://github.com/thirdweb-dev/js/commit/b3b9edca29e7d4053b14270a61caa89c34fecf9e)]:
- @thirdweb-dev/wallets@2.0.2
## 4.0.1
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/auth",
"version": "4.0.1",
"version": "4.0.4",
"main": "dist/thirdweb-dev-auth.cjs.js",
"module": "dist/thirdweb-dev-auth.esm.js",
"browser": {
+27
View File
@@ -1,5 +1,32 @@
# thirdweb
## 0.13.4
### Patch Changes
- Updated dependencies [[`956b5645`](https://github.com/thirdweb-dev/js/commit/956b5645e4266cba536abd27ee250dab5aa9a177)]:
- @thirdweb-dev/sdk@4.0.3
- @thirdweb-dev/wallets@2.0.4
- @thirdweb-dev/auth@4.0.4
## 0.13.3
### Patch Changes
- Updated dependencies [[`526176c4`](https://github.com/thirdweb-dev/js/commit/526176c47108c91d83bcfc5f0ca556274e6462d6), [`d1f8e951`](https://github.com/thirdweb-dev/js/commit/d1f8e951623f1691fdfe3d1e8645970d0a52eb06), [`09120c92`](https://github.com/thirdweb-dev/js/commit/09120c923cca804b9d4f5f779e5a53c97ecc8223), [`6573556d`](https://github.com/thirdweb-dev/js/commit/6573556d0e03efa0d6a157fccb0a1c08dd22d7cd)]:
- @thirdweb-dev/sdk@4.0.2
- @thirdweb-dev/wallets@2.0.3
- @thirdweb-dev/auth@4.0.3
## 0.13.2
### Patch Changes
- Updated dependencies [[`1f3c64d3`](https://github.com/thirdweb-dev/js/commit/1f3c64d3618903b9008cc7b633ea890b508e7800), [`74941603`](https://github.com/thirdweb-dev/js/commit/74941603a96456da46a47147f67c98235ac55022), [`4b98e487`](https://github.com/thirdweb-dev/js/commit/4b98e487d4b47225d7095e5444dc5e7608db48a3), [`693f349d`](https://github.com/thirdweb-dev/js/commit/693f349db2478e1cd0fc82a9ef9882f46832b125), [`dcdb0dcb`](https://github.com/thirdweb-dev/js/commit/dcdb0dcb838168a22c8335738852316dfb6e6fc9), [`0f12d1f9`](https://github.com/thirdweb-dev/js/commit/0f12d1f90320923309f085c372d3a5ed01dd4606), [`b3b9edca`](https://github.com/thirdweb-dev/js/commit/b3b9edca29e7d4053b14270a61caa89c34fecf9e), [`63ad0050`](https://github.com/thirdweb-dev/js/commit/63ad0050f0a24ec15b90933b5664d697618cf2d0)]:
- @thirdweb-dev/sdk@4.0.1
- @thirdweb-dev/wallets@2.0.2
- @thirdweb-dev/auth@4.0.2
## 0.13.1
### Patch Changes
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "thirdweb",
"main": "dist/cli/index.js",
"version": "0.13.1",
"version": "0.13.4",
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/cli",
"author": "thirdweb eng <[email protected]>",
"license": "Apache-2.0",
+12
View File
@@ -1,5 +1,17 @@
# @thirdweb-dev/contracts-js
## 1.3.15
### Patch Changes
- [#1756](https://github.com/thirdweb-dev/js/pull/1756) [`956b5645`](https://github.com/thirdweb-dev/js/commit/956b5645e4266cba536abd27ee250dab5aa9a177) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Enable adding smart wallet admins via bundler for new account contracts
## 1.3.14
### Patch Changes
- [#1757](https://github.com/thirdweb-dev/js/pull/1757) [`63ad0050`](https://github.com/thirdweb-dev/js/commit/63ad0050f0a24ec15b90933b5664d697618cf2d0) Thanks [@kumaryash90](https://github.com/kumaryash90)! - Update deployment and tests for MarketplaceV3 latest
## 1.3.13
### Patch Changes
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/contracts-js",
"version": "1.3.13",
"version": "1.3.15",
"main": "dist/thirdweb-dev-contracts-js.cjs.js",
"module": "dist/thirdweb-dev-contracts-js.esm.js",
"browser": {
@@ -10,7 +10,7 @@
"license": "Apache-2.0",
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/contracts-js",
"dependencies": {
"@thirdweb-dev/contracts": "3.10.1-0"
"@thirdweb-dev/contracts": "3.10.3-1"
},
"devDependencies": {
"@babel/preset-env": "^7.22.9",
+29
View File
@@ -1,5 +1,34 @@
# @thirdweb-dev/react-core
## 4.0.4
### Patch Changes
- Updated dependencies [[`956b5645`](https://github.com/thirdweb-dev/js/commit/956b5645e4266cba536abd27ee250dab5aa9a177)]:
- @thirdweb-dev/sdk@4.0.3
- @thirdweb-dev/wallets@2.0.4
- @thirdweb-dev/auth@4.0.4
## 4.0.3
### Patch Changes
- [#1758](https://github.com/thirdweb-dev/js/pull/1758) [`08e9cd20`](https://github.com/thirdweb-dev/js/commit/08e9cd206bcc37cf1e84a878dc78bc4f38e5092c) Thanks [@kien-ngo](https://github.com/kien-ngo)! - Add pagination to useOwnedNFTs
- Updated dependencies [[`526176c4`](https://github.com/thirdweb-dev/js/commit/526176c47108c91d83bcfc5f0ca556274e6462d6), [`d1f8e951`](https://github.com/thirdweb-dev/js/commit/d1f8e951623f1691fdfe3d1e8645970d0a52eb06), [`09120c92`](https://github.com/thirdweb-dev/js/commit/09120c923cca804b9d4f5f779e5a53c97ecc8223), [`6573556d`](https://github.com/thirdweb-dev/js/commit/6573556d0e03efa0d6a157fccb0a1c08dd22d7cd)]:
- @thirdweb-dev/sdk@4.0.2
- @thirdweb-dev/wallets@2.0.3
- @thirdweb-dev/auth@4.0.3
## 4.0.2
### Patch Changes
- Updated dependencies [[`1f3c64d3`](https://github.com/thirdweb-dev/js/commit/1f3c64d3618903b9008cc7b633ea890b508e7800), [`74941603`](https://github.com/thirdweb-dev/js/commit/74941603a96456da46a47147f67c98235ac55022), [`4b98e487`](https://github.com/thirdweb-dev/js/commit/4b98e487d4b47225d7095e5444dc5e7608db48a3), [`693f349d`](https://github.com/thirdweb-dev/js/commit/693f349db2478e1cd0fc82a9ef9882f46832b125), [`dcdb0dcb`](https://github.com/thirdweb-dev/js/commit/dcdb0dcb838168a22c8335738852316dfb6e6fc9), [`0f12d1f9`](https://github.com/thirdweb-dev/js/commit/0f12d1f90320923309f085c372d3a5ed01dd4606), [`b3b9edca`](https://github.com/thirdweb-dev/js/commit/b3b9edca29e7d4053b14270a61caa89c34fecf9e), [`63ad0050`](https://github.com/thirdweb-dev/js/commit/63ad0050f0a24ec15b90933b5664d697618cf2d0)]:
- @thirdweb-dev/sdk@4.0.1
- @thirdweb-dev/wallets@2.0.2
- @thirdweb-dev/auth@4.0.2
## 4.0.1
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/react-core",
"version": "4.0.1",
"version": "4.0.4",
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/react-core",
"author": "thirdweb eng <[email protected]>",
"license": "Apache-2.0",
@@ -225,11 +225,12 @@ export function useTotalCirculatingSupply(
*
* @example
* ```javascript
* const { data: ownedNFTs, isLoading, error } = useOwnedNFTs(contract, "{{wallet_address}}");
* const { data: ownedNFTs, isLoading, error } = useOwnedNFTs(contract, "{{wallet_address}}", { start: 0, count: 100 });
* ```
*
* @param contract - an instance of a {@link NFTContract}
* @param ownerWalletAddress - the wallet address to get owned tokens for
* @param queryParams - query params to pass to the query for pagination
* @returns a response object that includes the list of owned tokens
* @beta
* @twfeature ERC721Enumerable | ERC1155Enumerable | ERC721Supply
@@ -238,6 +239,7 @@ export function useTotalCirculatingSupply(
export function useOwnedNFTs<TContract extends NFTContract>(
contract: RequiredParam<TContract>,
ownerWalletAddress: RequiredParam<WalletAddress>,
queryParams?: QueryAllParams,
) {
const contractAddress = contract?.getAddress();
const { erc721, erc1155 } = getErcs(contract);
@@ -248,10 +250,10 @@ export function useOwnedNFTs<TContract extends NFTContract>(
requiredParamInvariant(contract, "No Contract instance provided");
invariant(ownerWalletAddress, "No wallet address provided");
if (erc721) {
return await erc721.getOwned(ownerWalletAddress);
return await erc721.getOwned(ownerWalletAddress, queryParams);
}
if (erc1155) {
return await erc1155.getOwned(ownerWalletAddress);
return await erc1155.getOwned(ownerWalletAddress, queryParams);
}
invariant(false, "Unknown NFT type");
},
@@ -1,5 +1,11 @@
# @thirdweb-dev/react-native-compat
## 0.3.9
## 0.3.8
## 0.3.7
## 0.3.6
## 0.3.5
+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.6",
"version": "0.3.9",
"author": "thirdweb eng <[email protected]>",
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/react-native-compat",
"license": "Apache-2.0",
+59
View File
@@ -1,5 +1,64 @@
# @thirdweb-dev/react-native
## 0.3.9
### Patch Changes
- Updated dependencies [[`956b5645`](https://github.com/thirdweb-dev/js/commit/956b5645e4266cba536abd27ee250dab5aa9a177)]:
- @thirdweb-dev/sdk@4.0.3
- @thirdweb-dev/wallets@2.0.4
- @thirdweb-dev/react-core@4.0.4
## 0.3.8
### Patch Changes
- [#1724](https://github.com/thirdweb-dev/js/pull/1724) [`fc62b91e`](https://github.com/thirdweb-dev/js/commit/fc62b91ee55b0adba01298f33b21352004d1b71d) Thanks [@iketw](https://github.com/iketw)! - Adds Google sign in to embeddedWallet in React Native
You need to add the `react-native-inappbrowser-reborn` package to your app, then you can do:
```javascript
<ThirdwebProvider
activeChain={activeChain}
clientId={"your-client-id"}
supportedWallets={[
embeddedWallet({
// if this is true AND oauthOptions is not set we set it to false internally
email: true,
// if this is set we enable Sign in with Google
oauthOptions: {
redirectUrl: "test15app://",
providers: ["google"],
},
}),
]}
>
<App />
</ThirdwebProvider>
```
- Updated dependencies [[`526176c4`](https://github.com/thirdweb-dev/js/commit/526176c47108c91d83bcfc5f0ca556274e6462d6), [`d1f8e951`](https://github.com/thirdweb-dev/js/commit/d1f8e951623f1691fdfe3d1e8645970d0a52eb06), [`09120c92`](https://github.com/thirdweb-dev/js/commit/09120c923cca804b9d4f5f779e5a53c97ecc8223), [`08e9cd20`](https://github.com/thirdweb-dev/js/commit/08e9cd206bcc37cf1e84a878dc78bc4f38e5092c), [`6573556d`](https://github.com/thirdweb-dev/js/commit/6573556d0e03efa0d6a157fccb0a1c08dd22d7cd)]:
- @thirdweb-dev/[email protected]
- @thirdweb-dev/[email protected]
- @thirdweb-dev/[email protected]
## 0.3.7
### Patch Changes
- [#1754](https://github.com/thirdweb-dev/js/pull/1754) [`dcdb0dcb`](https://github.com/thirdweb-dev/js/commit/dcdb0dcb838168a22c8335738852316dfb6e6fc9) Thanks [@iketw](https://github.com/iketw)! - Fix typos in SmartWallet api
- [#1760](https://github.com/thirdweb-dev/js/pull/1760) [`f41ee6fb`](https://github.com/thirdweb-dev/js/commit/f41ee6fb1cc3b1e58cc273fa45087d4c92ff97d1) Thanks [@iketw](https://github.com/iketw)! - Correctly pass theme via ConnectWallet
- [#1753](https://github.com/thirdweb-dev/js/pull/1753) [`b3b9edca`](https://github.com/thirdweb-dev/js/commit/b3b9edca29e7d4053b14270a61caa89c34fecf9e) Thanks [@iketw](https://github.com/iketw)! - Minor bug and UI fix for connecting an external app to a smart wallet
- [#1759](https://github.com/thirdweb-dev/js/pull/1759) [`513c332e`](https://github.com/thirdweb-dev/js/commit/513c332ed02df4ac316076cc03178a4702b12110) Thanks [@iketw](https://github.com/iketw)! - Fixes bug when passing only one wallet as a supportedWallet. The selectUI component must be displayed.
- Updated dependencies [[`1f3c64d3`](https://github.com/thirdweb-dev/js/commit/1f3c64d3618903b9008cc7b633ea890b508e7800), [`74941603`](https://github.com/thirdweb-dev/js/commit/74941603a96456da46a47147f67c98235ac55022), [`4b98e487`](https://github.com/thirdweb-dev/js/commit/4b98e487d4b47225d7095e5444dc5e7608db48a3), [`693f349d`](https://github.com/thirdweb-dev/js/commit/693f349db2478e1cd0fc82a9ef9882f46832b125), [`dcdb0dcb`](https://github.com/thirdweb-dev/js/commit/dcdb0dcb838168a22c8335738852316dfb6e6fc9), [`0f12d1f9`](https://github.com/thirdweb-dev/js/commit/0f12d1f90320923309f085c372d3a5ed01dd4606), [`b3b9edca`](https://github.com/thirdweb-dev/js/commit/b3b9edca29e7d4053b14270a61caa89c34fecf9e), [`63ad0050`](https://github.com/thirdweb-dev/js/commit/63ad0050f0a24ec15b90933b5664d697618cf2d0)]:
- @thirdweb-dev/[email protected]
- @thirdweb-dev/[email protected]
- @thirdweb-dev/[email protected]
## 0.3.6
### Patch Changes
+3 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/react-native",
"version": "0.3.6",
"version": "0.3.9",
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/react-native",
"author": "thirdweb eng <[email protected]>",
"license": "Apache-2.0",
@@ -89,7 +89,8 @@
"amazon-cognito-identity-js": "^6.3.3",
"react-native-quick-base64": "^2.0.7",
"react-native-quick-crypto": "^0.6.1",
"react-native-aes-gcm-crypto": "^0.2.2"
"react-native-aes-gcm-crypto": "^0.2.2",
"react-native-inappbrowser-reborn": "^3.7.0"
},
"peerDependencies": {
"ethers": ">=5.5.1 <6",
@@ -10,3 +10,4 @@ export * from "./right-arrow";
export * from "./wallet";
export * from "./money";
export * from "./switch";
export * from "./svgs";
@@ -1,3 +1,23 @@
export const GOOGLE_ICON = `<svg height={height} viewBox="0 0 24 24" width={width}>
<path
d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
fill="#4285F4"
/>
<path
d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
fill="#34A853"
/>
<path
d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
fill="#FBBC05"
/>
<path
d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
fill="#EA4335"
/>
<path d="M1 1h22v22H1z" fill="none" />
</svg>`;
export const EMAIL_WALLET_ICON = `<svg width="48" height="48" viewBox="0 0 48 48" fill="none">
<g clip-path="url(#clip0_3589_8649)">
<rect width="48" height="48" rx="8" fill="url(#paint0_linear_3589_8649)"/>
@@ -1,6 +1,6 @@
import { useWallet } from "@thirdweb-dev/react-core";
import { IWalletConnectReceiver } from "@thirdweb-dev/wallets";
import { useCallback, useEffect, useState } from "react";
import { useCallback, useEffect, useRef, useState } from "react";
import Box from "../base/Box";
import BaseButton from "../base/BaseButton";
import DisconnectIcon from "../../assets/disconnect";
@@ -21,6 +21,7 @@ const ConnectAppField = () => {
const [appMeta, setAppMeta] = useState<{ name: string; iconUrl: string }>();
const wallet = useWallet();
const [showQRCodeScan, setShowQRCodeScan] = useState(false);
const wcUriRef = useRef<string | undefined>();
const getAppMeta = useCallback(() => {
if (wallet && "isWCReceiverEnabled" in wallet) {
@@ -80,9 +81,10 @@ const ConnectAppField = () => {
};
const onQRCodeScan = (data: string) => {
if (wcUri !== data && data.startsWith("wc:")) {
if (wcUriRef.current !== data && data.startsWith("wc:")) {
setWCUri(data);
setShowQRCodeScan(false);
wcUriRef.current = data;
onWCPress(data);
}
@@ -102,6 +104,9 @@ const ConnectAppField = () => {
if (uriToUse?.startsWith("wc:")) {
(wallet as unknown as IWalletConnectReceiver).connectApp(uriToUse);
}
setWCUri(undefined);
wcUriRef.current = undefined;
};
const onScanQRPress = () => {
@@ -124,9 +129,11 @@ const ConnectAppField = () => {
value: wcUri,
placeholder: "wc://...",
placeholderTextColor: theme.colors.textSecondary,
numberOfLines: 1,
}}
containerProps={{
flex: 1,
pl: "xxs",
}}
/>
<BaseButton
@@ -164,12 +171,12 @@ const ConnectAppField = () => {
>
<>
{appMeta?.iconUrl ? (
<WalletIcon size={32} iconUri={appMeta.iconUrl} />
<WalletIcon size={24} iconUri={appMeta.iconUrl} />
) : (
<WalletConnectIcon width={16} height={16} />
<WalletConnectIcon width={24} height={24} />
)}
<View style={styles.exportWalletInfo}>
<Text variant="bodySmall">
<Text variant="bodySmall" numberOfLines={1}>
{appMeta ? appMeta.name : "Connect app"}
</Text>
</View>
@@ -215,8 +222,9 @@ const styles = StyleSheet.create({
justifyContent: "flex-start",
alignItems: "flex-start",
alignContent: "center",
flexDirection: "column",
flexDirection: "row",
marginLeft: 8,
marginRight: 8,
},
exportWallet: {
display: "flex",
@@ -238,7 +238,8 @@ export const ConnectWalletDetailsModal = ({
) : null}
</BaseButton>
) : null}
{activeWallet?.walletId === EmbeddedWallet.id ? (
{activeWallet?.walletId === EmbeddedWallet.id &&
(activeWallet as EmbeddedWallet).getEmail() ? (
<Box flexDirection="row" alignItems="center" mt="md">
<ActiveDot width={10} height={10} />
<Text variant="bodySmallSecondary" ml="xxs">
@@ -246,7 +247,11 @@ export const ConnectWalletDetailsModal = ({
</Text>
</Box>
) : null}
<Box flexDirection="row" justifyContent="space-evenly" mt="md">
<Box
flexDirection="row"
justifyContent="space-evenly"
marginVertical="md"
>
<SendButton supportedTokens={supportedTokens} />
<ReceiveButton />
</Box>
@@ -330,7 +335,6 @@ const styles = StyleSheet.create({
alignContent: "flex-start",
alignItems: "flex-start",
justifyContent: "flex-start",
marginTop: 24,
marginBottom: 8,
},
});
@@ -12,9 +12,9 @@ import { useEffect, useState } from "react";
import { useSmartWallet } from "../../providers/context-provider";
import RightArrowIcon from "../../assets/right-arrow";
import ConnectAppField from "./ConnectAppField";
import { useAppTheme } from "../../styles/hooks";
import DisconnectIcon from "../../assets/disconnect";
import { IconTextButton } from "../base/IconTextButton";
import { useGlobalTheme } from "../../providers/ui-context-provider";
export const SmartWalletAdditionalActions = ({
onExportPress,
@@ -26,7 +26,7 @@ export const SmartWalletAdditionalActions = ({
const [smartWalletAddress, setSmartWalletAddress] = useState<string>("");
const [showSmartWallet, setShowSmartWallet] = useState(false);
const activeWallet = useWallet();
const theme = useAppTheme();
const theme = useGlobalTheme();
const wallet = showSmartWallet
? smartWallet
@@ -1,7 +1,6 @@
import { AddressDisplay } from "../base/AddressDisplay";
import BaseButton from "../base/BaseButton";
import Text from "../base/Text";
import { WalletIcon } from "../base/WalletIcon";
import { useENS, useWallet } from "@thirdweb-dev/react-core";
import { StyleSheet } from "react-native";
import { LocalWallet, walletIds } from "@thirdweb-dev/wallets";
@@ -11,6 +10,7 @@ import { useMemo, useState } from "react";
import { TextBalance } from "../base/TextBalance";
import { SupportedTokens } from "../SendFunds/defaultTokens";
import { SMART_WALLET_ICON } from "../../assets/svgs";
import { ImageSvgUri } from "../base";
export type ConnectWalletDetailsProps = {
address?: string;
@@ -90,7 +90,13 @@ export const WalletDetailsButton = ({
alignContent="center"
justifyContent="flex-start"
>
<WalletIcon size={32} iconUri={avatarUrl || walletIconUrl} />
<Box borderRadius="lg" overflow="hidden">
<ImageSvgUri
width={32}
height={32}
imageUrl={avatarUrl || walletIconUrl}
/>
</Box>
<Box ml="md" justifyContent="center" alignItems="flex-start">
{activeWallet?.walletId === LocalWallet.id ? (
<Text variant="bodySmall" color="red">
@@ -1,5 +1,4 @@
import { Icon } from "../../assets/icon";
import { useAppTheme } from "../../styles/hooks";
import { AddressDisplay } from "../base/AddressDisplay";
import BaseButton from "../base/BaseButton";
import { WalletIcon } from "../base/WalletIcon";
@@ -13,6 +12,7 @@ import { TextBalance } from "../base/TextBalance";
import { walletIds } from "@thirdweb-dev/wallets";
import { SMART_WALLET_ICON } from "../../assets/svgs";
import Text from "../base/Text";
import { useGlobalTheme } from "../../providers/ui-context-provider";
interface WalletDetailsModalHeaderProps {
address?: string;
@@ -28,7 +28,7 @@ export const WalletDetailsModalHeader = ({
onAddressCopied,
tokenAddress,
}: WalletDetailsModalHeaderProps) => {
const theme = useAppTheme();
const theme = useGlobalTheme();
const activeWallet = useWallet();
const [showLoading, setShowLoading] = useState(false);
const ensQuery = useENS();
@@ -5,11 +5,11 @@ import { ReactNode, useState } from "react";
import Box from "../../base/Box";
import Text from "../../base/Text";
import ThirdwebLogo from "../../../assets/thirdweb-logo";
import { useAppTheme } from "../../../styles/hooks";
import { ChooseWalletContent } from "./ChooseWalletContent";
import { BaseButton, ImageSvgUri, WalletButton } from "../../base";
import { ActivityIndicator, Linking } from "react-native";
import { useTheme } from "@shopify/restyle";
import { useGlobalTheme } from "../../../providers/ui-context-provider";
export type ChooseWalletProps = {
headerText?: ReactNode | string;
@@ -34,7 +34,7 @@ export function ChooseWallet({
termsOfServiceUrl,
privacyPolicyUrl,
}: ChooseWalletProps) {
const theme = useAppTheme();
const theme = useGlobalTheme();
const themeLightDark = useTheme();
const [isConnecting, setIsConnecting] = useState(false);
const [isConnectAWalletEnabled, setIsConnectAWalletEnabled] = useState(false);
@@ -261,9 +261,7 @@ export function ChooseWallet({
) : null}
{showToSPrivacyPolicy ? (
<Box
mt="md"
pt="md"
height={50}
mt="sm"
marginHorizontal={!isConnectAWalletEnabled ? "xl" : "none"}
alignItems="center"
>
@@ -2,9 +2,11 @@ import { ActivityIndicator, StyleSheet } from "react-native";
import { BaseButton, Text } from "../base";
import { useConnectionStatus, useWallets } from "@thirdweb-dev/react-core";
import { useState, useEffect } from "react";
import { useModalState } from "../../providers/ui-context-provider";
import {
useGlobalTheme,
useModalState,
} from "../../providers/ui-context-provider";
import { ThemeProvider, ThemeProviderProps } from "../../styles/ThemeProvider";
import { useAppTheme } from "../../styles/hooks";
export type ConnectWalletButtonProps = {
theme?: ThemeProviderProps["theme"];
@@ -43,7 +45,7 @@ export const ConnectWalletButton = ({
buttonTitle,
theme,
}: ConnectWalletButtonProps) => {
const appTheme = useAppTheme();
const appTheme = useGlobalTheme();
const connectionStatus = useConnectionStatus();
const isWalletConnecting = connectionStatus === "connecting";
@@ -5,14 +5,16 @@ import { WalletConfig, useConnect, useWallets } from "@thirdweb-dev/react-core";
import { useCallback, useEffect, useState } from "react";
import { walletIds } from "@thirdweb-dev/wallets";
import { useColorScheme } from "react-native";
import { useModalState } from "../../providers/ui-context-provider";
import {
useGlobalTheme,
useModalState,
} from "../../providers/ui-context-provider";
import {
CLOSE_MODAL_STATE,
ConnectWalletFlowModal,
} from "../../utils/modalTypes";
import Box from "../base/Box";
import { ThemeProvider } from "../../styles/ThemeProvider";
import { useAppTheme } from "../../styles/hooks";
export const ConnectWalletFlow = () => {
const { modalState, setModalState } = useModalState();
@@ -30,7 +32,7 @@ export const ConnectWalletFlow = () => {
const [selectionData, setSelectionData] = useState<any>();
const supportedWallets = useWallets();
const theme = useColorScheme();
const appTheme = useAppTheme();
const appTheme = useGlobalTheme();
const connect = useConnect();
const onClose = useCallback(
@@ -60,8 +62,8 @@ export const ConnectWalletFlow = () => {
const onChooseWallet = useCallback(
(wallet: WalletConfig, data?: any) => {
setActiveWallet(wallet);
setSelectionData(data);
setActiveWallet(wallet);
// If the wallet has no custom connect UI, then connect it
if (!wallet.connectUI) {
@@ -74,13 +76,19 @@ export const ConnectWalletFlow = () => {
useEffect(() => {
// case when only one wallet is passed in supportedWallets
if (walletConfig) {
if (walletConfig.connectUI) {
// if there's a connection UI, then show it
setActiveWallet(walletConfig);
} else {
// if there's no connection UI, then connect the wallet
onChooseWallet(walletConfig);
// if there's a selection UI, then continue with the flow
if (walletConfig.selectUI) {
return;
}
if (walletConfig.connectUI) {
// if there's a connection UI and no selection UI, then show it
setActiveWallet(walletConfig);
return;
}
// if there's no connection UI or selectionUI, then automatically select it
onChooseWallet(walletConfig);
}
}, [onChooseWallet, walletConfig]);
@@ -30,7 +30,7 @@ export function ConnectingWallet({
subHeaderText={subHeaderText}
onClose={onClose}
/>
<WalletLoadingThumbnail imageSize={100}>
<WalletLoadingThumbnail imageSize={85}>
<ImageSvgUri height={80} width={80} imageUrl={wallet.meta.iconURL} />
</WalletLoadingThumbnail>
<View style={styles.connectingContainer}>
@@ -1,5 +1,5 @@
import { Icon } from "../../../assets/icon";
import { useAppTheme } from "../../../styles/hooks";
import { useGlobalTheme } from "../../../providers/ui-context-provider";
import Text from "../../base/Text";
import { FlexAlignType, StyleSheet, View } from "react-native";
@@ -20,7 +20,7 @@ export const ConnectWalletHeader = ({
onClose,
onBackPress,
}: ConnectWalletHeaderProps) => {
const theme = useAppTheme();
const theme = useGlobalTheme();
return (
<>
<View style={styles.header}>
@@ -16,7 +16,7 @@ import * as FileSystem from "expo-file-system";
import { TWModal } from "../base/modal/TWModal";
import { localWallet } from "../../wallets/wallets/local-wallet";
import { TextInput } from "../base";
import { useAppTheme } from "../../styles/hooks";
import { useGlobalTheme } from "../../providers/ui-context-provider";
export type LocalWalletImportModalProps = {
isVisible: boolean;
@@ -37,7 +37,7 @@ export const LocalWalletImportModal = ({
const [error, setError] = useState<string | undefined>();
const [isImporting, setIsImporting] = useState<boolean>(false);
const createWalletInstance = useCreateWalletInstance();
const theme = useAppTheme();
const theme = useGlobalTheme();
const createLocalWalletFromJsonFile = async () => {
if (!jsonFile || !password) {
@@ -15,7 +15,7 @@ import WalletLoadingThumbnail from "../../../wallets/wallets/wallet-connect/Wall
import ImageSvgUri from "../../base/ImageSvgUri";
import Box from "../../base/Box";
import { ConnectWalletHeader } from "../ConnectingWallet/ConnectingWalletHeader";
import { useAppTheme } from "../../../styles/hooks";
import { useGlobalTheme } from "../../../providers/ui-context-provider";
export const SmartWalletFlow = ({
connected,
@@ -24,7 +24,7 @@ export const SmartWalletFlow = ({
personalWalletConfig,
...props
}: ConnectUIProps<SmartWallet> & { personalWalletConfig: WalletConfig }) => {
const theme = useAppTheme();
const theme = useGlobalTheme();
const [connectedPersonalWallet, setConnectedPersonalWallet] =
useState<WalletInstance>();
const [personalWalletChainId, setPersonalWalletChaindId] = useState<
@@ -131,7 +131,7 @@ export const SmartWalletFlow = ({
onBackPress={onConnectingBackPress}
onClose={onConnectingClosePress}
/>
<WalletLoadingThumbnail imageSize={100}>
<WalletLoadingThumbnail imageSize={85}>
<ImageSvgUri
height={80}
width={80}
@@ -1,4 +1,7 @@
import { useModalState } from "../providers/ui-context-provider";
import {
useGlobalTheme,
useModalState,
} from "../providers/ui-context-provider";
import { ConnectWalletFlow } from "./ConnectWalletFlow/ConnectWalletFlow";
import { Dimensions, StyleSheet, View } from "react-native";
import { useMemo } from "react";
@@ -7,13 +10,13 @@ import { SessionRequestModal } from "./ConnectWalletDetails/SessionRequestModal"
import { SessionProposalModal } from "./ConnectWalletDetails/SessionProposalModal";
import { TWModal } from "./base/modal/TWModal";
import Box from "./base/Box";
import { useAppTheme } from "../styles/hooks";
// import { useAppTheme } from "../styles/hooks";
const MODAL_HEIGHT = Dimensions.get("window").height * 0.6;
const MODAL_HEIGHT = Dimensions.get("window").height * 0.7;
const DEVICE_WIDTH = Dimensions.get("window").width;
export const MainModal = () => {
const theme = useAppTheme();
const theme = useGlobalTheme();
const { modalState } = useModalState();
const { isOpen, isSheet } = modalState;
@@ -3,14 +3,14 @@ import Box from "./base/Box";
import { useState } from "react";
import EyeClosed from "../assets/eye-closed";
import EyeOpened from "../assets/eye-opened";
import { useAppTheme } from "../styles/hooks";
import { useGlobalTheme } from "../providers/ui-context-provider";
export const PasswordInput = ({
onChangeText,
}: {
onChangeText: TextInput["props"]["onChangeText"];
}) => {
const theme = useAppTheme();
const theme = useGlobalTheme();
const [showPassword, setShowPassword] = useState(false);
const onPressEyeIcon = () => {
@@ -2,7 +2,6 @@ import React, { useEffect, useState } from "react";
import { IconTextButton } from "./base/IconTextButton";
import { useAddress } from "@thirdweb-dev/react-core";
import { Dimensions, KeyboardAvoidingView } from "react-native";
import { useAppTheme } from "../styles/hooks";
import {
TWModal,
Box,
@@ -15,9 +14,10 @@ import {
import DownloadIcon from "../assets/download";
import CopyIcon from "../assets/copy";
import QRCode from "react-native-qrcode-svg";
import { useGlobalTheme } from "../providers/ui-context-provider";
export const ReceiveButton = () => {
const theme = useAppTheme();
const theme = useGlobalTheme();
const [isModalVisible, setIsModalVisible] = useState(false);
const onClose = () => {
@@ -60,7 +60,7 @@ export const ReceiveFundsModal = ({
isVisible,
onClose,
}: ReceiveFundsModalProps) => {
const theme = useAppTheme();
const theme = useGlobalTheme();
const address = useAddress();
const [addressCopied, setAddressCopied] = useState(false);
@@ -1,4 +1,3 @@
import { useAppTheme } from "../../styles/hooks";
import React, { useEffect, useMemo, useState } from "react";
import SendIcon from "../../assets/send";
import {
@@ -30,13 +29,14 @@ import { utils } from "ethers";
import LoadingTextAnimation from "../base/LoadingTextAnimation";
import CheckIcon from "../../assets/check";
import { TokenSelector } from "./TokenSelector";
import { useGlobalTheme } from "../../providers/ui-context-provider";
export const SendButton = ({
supportedTokens,
}: {
supportedTokens: SupportedTokens;
}) => {
const theme = useAppTheme();
const theme = useGlobalTheme();
const [isModalVisible, setIsModalVisible] = useState(false);
const onClose = () => {
@@ -139,12 +139,12 @@ const SendFundsForm = ({
onTokenSelectorPress: () => void;
token?: TokenInfo;
}) => {
const theme = useAppTheme();
const theme = useGlobalTheme();
const chain = useChain();
const wallet = useWallet();
const chainId = useChainId();
const [receiverAddress, setReceiverAddress] = useState("");
const [amount, setAmount] = useState("");
const [amount, setAmount] = useState("0");
const [showIcon, setShowIcon] = useState(false);
const tokenAddress = token?.address;
@@ -313,6 +313,7 @@ const SendFundsForm = ({
editable={!sendTokenMutation.isLoading}
returnKeyType={"done"}
keyboardType="numeric"
value={amount}
clearTextOnFocus={false}
style={{
color: theme.colors.textPrimary,
@@ -322,8 +323,6 @@ const SendFundsForm = ({
paddingHorizontal: 16,
}}
onChangeText={setAmount}
placeholder="0"
placeholderTextColor={theme.colors.textPrimary}
autoCapitalize="none"
autoCorrect={false}
/>
@@ -348,11 +347,18 @@ const SendFundsForm = ({
}}
>
{sendTokenMutation.isLoading ? (
<ActivityIndicator size="small" color={theme.colors.textPrimary} />
<ActivityIndicator
size="small"
color={theme.colors.accentButtonTextColor}
/>
) : showIcon ? (
<CheckIcon width={20} height={13} color={theme.colors.textPrimary} />
<CheckIcon
width={20}
height={13}
color={theme.colors.accentButtonTextColor}
/>
) : (
<Text variant="bodySmall" mr="sm">
<Text variant="bodySmall" color="accentButtonTextColor" mr="sm">
Send
</Text>
)}
@@ -15,10 +15,10 @@ import {
ScrollView,
TextInput,
} from "react-native";
import { useAppTheme } from "../../styles/hooks";
import { SelectTokenButton } from "./SelectTokenButton";
import CloseIcon from "../../assets/close";
import Text from "../base/Text";
import { useGlobalTheme } from "../../providers/ui-context-provider";
export function useToken(tokenAddress: string): {
isLoading: boolean;
@@ -82,7 +82,7 @@ export function TokenSelector(props: {
onBack: () => void;
supportedTokens: SupportedTokens;
}) {
const theme = useAppTheme();
const theme = useGlobalTheme();
const [input, setInput] = useState("");
const chainId = useChainId();
const nativeTokenInfo = useNativeToken();
@@ -35,7 +35,7 @@ const ImageSvgUri = ({
}
return (
<Box style={{ width: width, height: height }}>
<Box width={width} height={height}>
{error ? (
<SvgUri width={width} height={height} uri={resolvedImageUrl} />
) : (
@@ -1,7 +1,7 @@
import React, { useCallback, useEffect, useRef } from "react";
import { View, Animated, Easing, StyleSheet } from "react-native";
import { baseTheme } from "../../styles/theme";
import { useAppTheme } from "../../styles/hooks";
import { useGlobalTheme } from "../../providers/ui-context-provider";
type LoadingTextAnimationProps = {
text: string;
@@ -13,7 +13,7 @@ const LoadingTextAnimation = ({
textVariant,
}: LoadingTextAnimationProps) => {
const fadeAnim = useRef(new Animated.Value(0)).current;
const theme = useAppTheme();
const theme = useGlobalTheme();
const fadeInOut = useCallback(() => {
Animated.loop(
@@ -14,8 +14,8 @@ import { useSwitchChain, useSupportedChains } from "@thirdweb-dev/react-core";
import Box from "./Box";
import { ModalHeaderTextClose } from "./modal/ModalHeaderTextClose";
import { TWModal } from "./modal/TWModal";
import { useAppTheme } from "../../styles/hooks";
import { Chain } from "@thirdweb-dev/chains";
import { useGlobalTheme } from "../../providers/ui-context-provider";
type NetworkButtonProps = {
chain?: Chain;
@@ -35,7 +35,7 @@ export const NetworkButton = ({
padding,
...props
}: NetworkButtonProps) => {
const theme = useAppTheme();
const theme = useGlobalTheme();
const [isModalVisible, setIsModalVisible] = useState(false);
const [switchError, setSwitchError] = useState<string | undefined>();
const [isSwitching, setIsSwitching] = useState(false);
@@ -72,6 +72,7 @@ export const NetworkButton = ({
<>
<BaseButton
p={padding || "sm"}
paddingVertical="xs"
borderRadius="md"
borderWidth={0.5}
flexDirection="row"
@@ -82,13 +83,8 @@ export const NetworkButton = ({
{...props}
>
<Box flexDirection="row" alignItems="center">
<ChainIcon chainIconUrl={chain?.icon?.url} size={32} />
<Box
ml="md"
alignItems="flex-start"
justifyContent="center"
height={36}
>
<ChainIcon chainIconUrl={chain?.icon?.url} size={28} />
<Box ml="md" alignItems="flex-start" justifyContent="center">
<Text variant="bodyLarge">{chain?.name || "Unknown Network"}</Text>
{isSwitching ? (
<Box flexDirection="row" alignItems="center">
@@ -2,7 +2,7 @@ import { ReactElement, useEffect, useRef } from "react";
import { Theme } from "../../styles/theme";
import Text from "./Text";
import { Animated, StyleSheet } from "react-native";
import { useAppTheme } from "../../styles/hooks";
import { useGlobalTheme } from "../../providers/ui-context-provider";
export const TOAST_HEIGHT = 45;
@@ -19,7 +19,7 @@ export function Toast({
backgroundColor,
translateY = TOAST_HEIGHT,
}: ToastProps) {
const theme = useAppTheme();
const theme = useGlobalTheme();
const translateYRef = useRef(new Animated.Value(translateY)).current;
@@ -4,20 +4,27 @@ import ImageSvgUri from "./ImageSvgUri";
import { Label } from "./Label";
import Text from "./Text";
import Box from "./Box";
import { Palette } from "../../styles/colors";
type WalletButtonProps = {
onPress?: () => void;
walletIconUrl: string;
name: string;
nameColor?: keyof Palette;
labelText?: string;
recommended?: boolean;
iconWidth?: number;
iconHeight?: number;
} & React.ComponentProps<typeof BaseButton>;
export const WalletButton = ({
iconWidth = 48,
iconHeight = 48,
mb,
onPress,
walletIconUrl,
name,
nameColor = "textPrimary",
labelText,
recommended,
...props
@@ -36,9 +43,15 @@ export const WalletButton = ({
{...props}
>
<Box flexDirection="row" justifyContent="flex-start" alignItems="center">
<ImageSvgUri imageUrl={walletIconUrl} width={48} height={48} />
<ImageSvgUri
imageUrl={walletIconUrl}
width={iconWidth}
height={iconHeight}
/>
<Box ml="sm" alignItems="flex-start">
<Text variant="bodyLarge">{name}</Text>
<Text variant="bodyLarge" color={nameColor}>
{name}
</Text>
{recommended ? <Text variant="link">Recommended</Text> : null}
</Box>
</Box>
@@ -1,4 +1,4 @@
import { useAppTheme } from "../../styles/hooks";
import { useGlobalTheme } from "../../providers/ui-context-provider";
import BaseButton from "./BaseButton";
import Box from "./Box";
import ImageSvgUri from "./ImageSvgUri";
@@ -17,7 +17,7 @@ export const WalletButtonSquare = ({
name,
size,
}: WalletButtonSquareProps) => {
const theme = useAppTheme();
const theme = useGlobalTheme();
const marginVertical = theme.spacing.xs;
return (
@@ -1,6 +1,6 @@
import { useGlobalTheme } from "../../../providers/ui-context-provider";
import Text from "../Text";
import { TouchableOpacity, StyleSheet, ActivityIndicator } from "react-native";
import { useAppTheme } from "../../../styles/hooks";
export const ModalFooter = ({
footer,
@@ -11,7 +11,7 @@ export const ModalFooter = ({
isLoading?: boolean;
onPress?: () => void;
}) => {
const theme = useAppTheme();
const theme = useGlobalTheme();
return (
<TouchableOpacity style={styles.footer} onPress={onPress}>
@@ -1,9 +1,9 @@
import { View } from "react-native";
import { Icon } from "../../../assets/icon";
import { useAppTheme } from "../../../styles/hooks";
import Box from "../Box";
import Text from "../Text";
import { ReactNode } from "react";
import { useGlobalTheme } from "../../../providers/ui-context-provider";
interface ModalHeaderTextCloseProps {
onClose?: () => void;
@@ -19,7 +19,7 @@ export const ModalHeaderTextClose = ({
onBackPress,
...props
}: ModalHeaderTextCloseProps & React.ComponentProps<typeof Box>) => {
const theme = useAppTheme();
const theme = useGlobalTheme();
return (
<>
@@ -78,7 +78,7 @@ export const useGlobalTheme = (theme?: ThemeProviderProps["theme"]): Theme => {
const appTheme = useTheme();
const resultTheme = useMemo(() => {
const resp = getThemeObj(theme) || getThemeObj(context.theme) || appTheme;
const resp = getThemeObj(context.theme) || getThemeObj(theme) || appTheme;
return resp;
}, [theme, context, appTheme]);
@@ -1,12 +1,13 @@
import {
EmbeddedWalletConnectionArgs,
EmbeddedWalletConnectorOptions,
OauthOption,
} from "./types";
import type { Chain } from "@thirdweb-dev/chains";
import { Connector, normalizeChainId } from "@thirdweb-dev/wallets";
import { providers, Signer } from "ethers";
import { utils } from "ethers";
import { sendEmailOTP, validateEmailOTP } from "./embedded/auth";
import { sendEmailOTP, socialLogin, validateEmailOTP } from "./embedded/auth";
import { getEthersSigner } from "./embedded/signer";
import { logoutUser } from "./embedded/helpers/auth/logout";
import {
@@ -83,6 +84,36 @@ export class EmbeddedWalletConnector extends Connector<EmbeddedWalletConnectionA
return sendEmailOTP(email, this.options.clientId);
}
async socialLogin(oauthOption: OauthOption) {
try {
const { email } = await socialLogin(oauthOption, this.options.clientId);
this.email = email;
saveConnectedEmail(email);
} catch (error) {
console.error(
`Error while signing in with: ${oauthOption.provider}. ${error}`,
);
if (error instanceof Error) {
return { error: error.message };
} else {
return { error: "An unknown error occurred" };
}
}
try {
await this.getSigner();
this.emit("connected");
} catch (error) {
if (error instanceof Error) {
return { error: error.message };
} else {
return { error: "Error getting the signer" };
}
}
return { success: true };
}
async disconnect(): Promise<void> {
clearConnectedEmail();
await logoutUser(this.options.clientId);
@@ -19,6 +19,9 @@ import { postPaperAuth, prePaperAuth } from "./helpers/auth/middleware";
import { isDeviceSharePresentForUser } from "./helpers/storage/local";
import { getCognitoUser, setCognitoUser } from "./helpers/storage/state";
import { SendEmailOtpReturnType } from "@thirdweb-dev/wallets";
import { InAppBrowser } from "react-native-inappbrowser-reborn";
import { OauthOption } from "../types";
import { ROUTE_HEADLESS_GOOGLE_LOGIN } from "./helpers/constants";
export async function sendEmailOTP(
email: string,
@@ -122,3 +125,74 @@ export async function validateEmailOTP({
);
}
}
export async function socialLogin(oauthOptions: OauthOption, clientId: string) {
const headlessLoginLinkWithParams = `${ROUTE_HEADLESS_GOOGLE_LOGIN}?authProvider=${encodeURIComponent(
"google",
)}&baseUrl=${encodeURIComponent(
"https://ews.thirdweb.com",
)}&platform=${encodeURIComponent("mobile")}`;
const resp = await fetch(headlessLoginLinkWithParams);
if (!resp.ok) {
throw new Error("Error getting headless login link");
}
const json = await resp.json();
const { platformLoginLink } = json;
const completeLoginUrl = `${platformLoginLink}?developerClientId=${encodeURIComponent(
clientId || "",
)}&platform=${encodeURIComponent("mobile")}&redirectUrl=${encodeURIComponent(
oauthOptions.redirectUrl,
)}`;
const result = await InAppBrowser.openAuth(
completeLoginUrl,
oauthOptions.redirectUrl,
{
// iOS Properties
ephemeralWebSession: false,
// Android Properties
showTitle: false,
enableUrlBarHiding: false,
enableDefaultShare: false,
},
);
if (result.type !== "success") {
throw new Error("Error signing in. Please try again later.");
}
const decodedUrl = decodeURIComponent(result.url);
const parts = decodedUrl.split("?authResult=");
if (parts.length < 2) {
throw new Error("Malformed response from the login redirect");
}
const authResult = parts[1];
const { storedToken } = JSON.parse(authResult);
try {
const toStoreToken: AuthStoredTokenWithCookieReturnType["storedToken"] = {
jwtToken: storedToken.jwtToken,
authDetails: storedToken.authDetails,
authProvider: storedToken.authProvider,
developerClientId: storedToken.developerClientId,
cookieString: storedToken.cookieString,
// we should always store the jwt cookie since there's no concept of cookie in react native
shouldStoreCookieString: true,
isNewUser: storedToken.isNewUser,
};
await postPaperAuth(toStoreToken, clientId);
return { storedToken, email: storedToken.authDetails.email };
} catch (e) {
throw new Error(
`Malformed response from post authentication: ${JSON.stringify(e)}`,
);
}
}
@@ -30,4 +30,6 @@ export const ROUTE_STORE_USER_SHARES = `${ROUTE_2022_08_12_API_BASE_PATH}/embedd
export const ROUTE_GET_USER_SHARES = `${ROUTE_2022_08_12_API_BASE_PATH}/embedded-wallet/get-shares`;
export const ROUTE_VERIFY_THIRDWEB_CLIENT_ID = `${ROUTE_2022_08_12_API_BASE_PATH}/embedded-wallet/verify-thirdweb-client-id`;
export const ROUTE_HEADLESS_GOOGLE_LOGIN = `${BASE_URL}/api/2022-08-12/embedded-wallet/headless-login-link`;
export const ROUTE_AUTH_COGNITO_ID_TOKEN = `${ROUTE_2022_08_12_API_BASE_PATH}/embedded-wallet/cognito-id-token`;
@@ -1,11 +1,21 @@
import { Chain } from "@thirdweb-dev/chains";
import { WalletOptions } from "@thirdweb-dev/wallets";
import type {
RecoveryShareManagement,
PaperConstructorType,
AuthProvider,
} from "@paperxyz/embedded-wallet-service-sdk";
export type OauthOptions = {
providers: AuthProvider[];
redirectUrl: string;
};
export type OauthOption = {
provider: AuthProvider;
redirectUrl: string;
};
export interface EmbeddedConnectorOptions {
chains?: Chain[];
chainId?: number;
@@ -12,12 +12,14 @@ import {
TextInput,
TextInputKeyPressEventData,
} from "react-native";
import { useAppTheme } from "../../../styles/hooks";
import { ConnectWalletHeader } from "../../../components/ConnectWalletFlow/ConnectingWallet/ConnectingWalletHeader";
import Box from "../../../components/base/Box";
import Text from "../../../components/base/Text";
import BaseButton from "../../../components/base/BaseButton";
import * as Clipboard from "expo-clipboard";
import { StyleSheet } from "react-native";
import { EmbeddedSocialConnection } from "./EmbeddedSocialConnection";
import { useGlobalTheme } from "../../../providers/ui-context-provider";
const OTP_LENGTH = 6;
@@ -26,8 +28,9 @@ export const EmbeddedConnectionUI: React.FC<ConnectUIProps<EmbeddedWallet>> = ({
goBack,
selectionData,
onLocallyConnected,
...props
}) => {
const theme = useAppTheme();
const theme = useGlobalTheme();
const inputRefs = useRef<(TextInput | null)[]>([]);
const [values, setValues] = useState<string[]>(
new Array(OTP_LENGTH).fill(""),
@@ -104,6 +107,7 @@ export const EmbeddedConnectionUI: React.FC<ConnectUIProps<EmbeddedWallet>> = ({
inputRefs.current[i]?.setNativeProps({ text: newValues[i] });
}
setValues(newValues);
Clipboard.setStringAsync("");
return;
}
}
@@ -157,6 +161,18 @@ export const EmbeddedConnectionUI: React.FC<ConnectUIProps<EmbeddedWallet>> = ({
});
};
if (selectionData?.oauthOptions) {
return (
<EmbeddedSocialConnection
connected={() => {}}
goBack={goBack}
onLocallyConnected={onLocallyConnected}
selectionData={selectionData}
{...props}
/>
);
}
return (
<Box marginHorizontal="xl">
<ConnectWalletHeader
@@ -198,10 +214,8 @@ export const EmbeddedConnectionUI: React.FC<ConnectUIProps<EmbeddedWallet>> = ({
<TextInput
ref={(ref) => (inputRefs.current[index] = ref)}
style={{
fontSize: 20,
...styles.textInput,
color: theme.colors.textPrimary,
textAlign: "center",
height: 50,
}}
keyboardType="number-pad"
editable={!checkingOtp}
@@ -246,3 +260,11 @@ export const EmbeddedConnectionUI: React.FC<ConnectUIProps<EmbeddedWallet>> = ({
</Box>
);
};
const styles = StyleSheet.create({
textInput: {
fontSize: 20,
textAlign: "center",
height: 50,
},
});
@@ -1,7 +1,6 @@
import React, { useEffect, useState } from "react";
import { EmbeddedWallet } from "./EmbeddedWallet";
import { ActivityIndicator } from "react-native";
import { useAppTheme } from "../../../styles/hooks";
import {
SelectUIProps,
useCreateWalletInstance,
@@ -10,21 +9,30 @@ import Box from "../../../components/base/Box";
import Text from "../../../components/base/Text";
import BaseButton from "../../../components/base/BaseButton";
import { TextInput } from "../../../components/base/TextInput";
import { GOOGLE_ICON } from "../../../assets/svgs";
import { WalletButton } from "../../../components/base/WalletButton";
import { AuthProvider } from "@paperxyz/embedded-wallet-service-sdk";
import { OauthOptions } from "../../connectors/embedded-wallet/types";
import { useGlobalTheme } from "../../../providers/ui-context-provider";
/**
* UI for selecting wallet - this UI is rendered in the wallet selection screen
*/
export const EmailSelectionUI: React.FC<SelectUIProps<EmbeddedWallet>> = ({
onSelect,
walletConfig,
}) => {
const theme = useAppTheme();
const [email, setEmail] = useState<string>("");
export const EmailSelectionUI: React.FC<
SelectUIProps<EmbeddedWallet> & {
oauthOptions?: OauthOptions;
email?: boolean;
}
> = ({ onSelect, walletConfig, oauthOptions, email }) => {
const theme = useGlobalTheme();
const [emailInput, setEmailInput] = useState<string>("");
const [errorMessage, setErrorMessage] = useState<string>("");
const [isFetching, setIsFetching] = useState<boolean>(false);
const createWalletInstance = useCreateWalletInstance();
const [emailWallet, setEmailWallet] = useState<EmbeddedWallet | null>(null);
const isEmailEnabled = email === false ? false : true;
useEffect(() => {
const emailWalletInstance = createWalletInstance(
walletConfig,
@@ -39,17 +47,18 @@ export const EmailSelectionUI: React.FC<SelectUIProps<EmbeddedWallet>> = ({
};
const handleNetworkCall = () => {
if (validateEmail(email)) {
if (validateEmail(emailInput)) {
setErrorMessage("");
setIsFetching(true);
emailWallet
?.sendEmailOTP(email)
?.sendEmailOTP(emailInput)
.then((response) => {
onSelect({
...response,
email,
email: emailInput,
emailWallet,
oauthOptions: undefined,
});
})
.catch((error) => {
@@ -65,58 +74,109 @@ export const EmailSelectionUI: React.FC<SelectUIProps<EmbeddedWallet>> = ({
}
};
const onGoogleSignInPress = () => {
onSelect({
email: emailInput,
emailWallet,
oauthOptions: {
provider: oauthOptions?.providers[0],
redirectUrl: oauthOptions?.redirectUrl,
},
});
};
return (
<Box paddingHorizontal="xl" mt="lg">
<TextInput
textInputProps={{
placeholder: "Enter your email address",
placeholderTextColor: theme.colors.textSecondary,
onChangeText: setEmail,
style: {
fontSize: 14,
color: theme.colors.textPrimary,
lineHeight: 16,
padding: 0,
flex: 1,
},
value: email,
keyboardType: "email-address",
returnKeyType: "done",
autoCapitalize: "none",
autoCorrect: false,
autoComplete: "off",
clearTextOnFocus: false,
}}
containerProps={{
paddingHorizontal: "sm",
paddingVertical: "sm",
justifyContent: "flex-start",
}}
/>
<BaseButton
mt="md"
paddingVertical="md"
borderRadius="lg"
borderWidth={1}
borderColor="border"
backgroundColor="accentButtonColor"
onPress={handleNetworkCall}
>
{isFetching ? (
<ActivityIndicator
size={"small"}
color={theme.colors.accentButtonTextColor}
{oauthOptions?.providers.includes(AuthProvider.GOOGLE) ? (
<Box justifyContent="center">
<WalletButton
iconHeight={28}
iconWidth={28}
borderRadius="lg"
borderWidth={1}
borderColor="buttonBackgroundColor"
backgroundColor="buttonBackgroundColor"
nameColor="buttonTextColor"
justifyContent="center"
name="Sign in with Google"
walletIconUrl={GOOGLE_ICON}
onPress={onGoogleSignInPress}
/>
) : (
<Text
variant="bodySmall"
color="accentButtonTextColor"
fontWeight="700"
{isEmailEnabled ? (
<Box
mb="md"
mt="md"
flexDirection="row"
justifyContent="center"
alignItems="center"
>
<Box height={1} flex={1} backgroundColor="border" />
<Text
variant="subHeader"
textAlign="center"
marginHorizontal="xxs"
>
OR
</Text>
<Box height={1} flex={1} backgroundColor="border" />
</Box>
) : null}
</Box>
) : null}
{isEmailEnabled ? (
<>
<TextInput
textInputProps={{
placeholder: "Enter your email address",
placeholderTextColor: theme.colors.textSecondary,
onChangeText: setEmailInput,
style: {
fontSize: 14,
color: theme.colors.textPrimary,
lineHeight: 16,
padding: 0,
flex: 1,
},
value: emailInput,
keyboardType: "email-address",
returnKeyType: "done",
autoCapitalize: "none",
autoCorrect: false,
autoComplete: "off",
clearTextOnFocus: false,
}}
containerProps={{
paddingHorizontal: "sm",
paddingVertical: "sm",
justifyContent: "flex-start",
}}
/>
<BaseButton
mt="md"
paddingVertical="md"
borderRadius="lg"
borderWidth={1}
borderColor="border"
backgroundColor="accentButtonColor"
onPress={handleNetworkCall}
>
Continue
</Text>
)}
</BaseButton>
{isFetching ? (
<ActivityIndicator
size={"small"}
color={theme.colors.accentButtonTextColor}
/>
) : (
<Text
variant="bodySmall"
color="accentButtonTextColor"
fontWeight="700"
>
Continue
</Text>
)}
</BaseButton>
</>
) : null}
{errorMessage ? (
<Text variant="error" mt="xxs">
{errorMessage}
@@ -0,0 +1,86 @@
import { useCallback, useEffect, useState } from "react";
import React from "react";
import { ActivityIndicator } from "react-native";
import { EmbeddedWallet } from "./EmbeddedWallet";
import {
ConnectUIProps,
useSetConnectedWallet,
useSetConnectionStatus,
} from "@thirdweb-dev/react-core";
import { GOOGLE_ICON } from "../../../assets";
import { ConnectWalletHeader } from "../../../components/ConnectWalletFlow/ConnectingWallet/ConnectingWalletHeader";
import { Box, Text, WalletButton } from "../../../components/base";
export const EmbeddedSocialConnection: React.FC<
ConnectUIProps<EmbeddedWallet>
> = ({ goBack, selectionData, onLocallyConnected }) => {
const setConnectedWallet = useSetConnectedWallet();
const setConnectionStatus = useSetConnectionStatus();
const [errorMessage, setErrorMessage] = useState<string>();
const handleGoogleLogin = useCallback(() => {
setErrorMessage("");
setTimeout(() => {
(selectionData.emailWallet as EmbeddedWallet)
.socialLogin(selectionData?.oauthOptions)
.then(async (response) => {
if (response?.success) {
if (onLocallyConnected) {
onLocallyConnected(selectionData.emailWallet);
} else {
await setConnectedWallet(selectionData.emailWallet);
setConnectionStatus("connected");
}
} else {
setErrorMessage(
response?.error || "Error login in. Please try again later.",
);
}
})
.catch((error) => {
console.error("Error validating otp: ", error);
setErrorMessage("Error login in. Please try again later.");
});
}, 0);
}, [
onLocallyConnected,
selectionData.emailWallet,
selectionData.oauthOptions,
setConnectedWallet,
setConnectionStatus,
]);
useEffect(() => {
handleGoogleLogin();
}, [handleGoogleLogin]);
return (
<Box marginHorizontal="xl" height={200}>
<ConnectWalletHeader
middleContent={
<WalletButton
iconHeight={28}
iconWidth={28}
borderRadius="lg"
justifyContent="center"
name="Sign In"
walletIconUrl={GOOGLE_ICON}
/>
}
subHeaderText={""}
onBackPress={goBack}
onClose={goBack}
/>
<Box flex={1} justifyContent="center" alignItems="center">
{!errorMessage ? (
<ActivityIndicator size="large" />
) : (
<Text variant="error" numberOfLines={1}>
{errorMessage}
</Text>
)}
</Box>
</Box>
);
};
@@ -5,7 +5,10 @@ import {
walletIds,
} from "@thirdweb-dev/wallets";
import type { EmbeddedWalletConnector as EmbeddedConnectorType } from "../../connectors/embedded-wallet/embedded-connector";
import { EmbeddedWalletConnectionArgs } from "../../connectors/embedded-wallet/types";
import {
EmbeddedWalletConnectionArgs,
OauthOption,
} from "../../connectors/embedded-wallet/types";
import { EmbeddedWalletConnector } from "../../connectors/embedded-wallet/embedded-connector";
import { EMAIL_WALLET_ICON } from "../../../assets/svgs";
@@ -43,8 +46,6 @@ export class EmbeddedWallet extends AbstractClientWallet<
return this.connector;
}
// my methods
initializeConnector() {
this.connector = new EmbeddedWalletConnector({
...this.options,
@@ -63,6 +64,10 @@ export class EmbeddedWallet extends AbstractClientWallet<
return this.connector?.sendEmailOtp(email);
}
async socialLogin(oauthOption: OauthOption) {
return this.connector?.socialLogin(oauthOption);
}
onConnected = () => {
this.emit("message", { type: "connected" });
};
@@ -6,16 +6,40 @@ import {
} from "@thirdweb-dev/react-core";
import { EmbeddedConnectionUI } from "./EmbeddedConnectionUI";
import { EmailSelectionUI } from "./EmbeddedSelectionUI";
import { AuthProvider } from "@paperxyz/embedded-wallet-service-sdk";
type OAuthProvider = "google"; // currently we only have one
export type EmbeddedWalletConfig = {
recommended?: boolean;
// @default true - set false to disable
email?: boolean;
// @default { providers: ['google'] } - set false to disable
oauthOptions?:
| {
providers: OAuthProvider[];
redirectUrl: string;
}
| false;
};
export const embeddedWallet = (
config?: EmbeddedWalletConfig,
): WalletConfig<EmbeddedWallet> => {
const selectUI = (props: SelectUIProps<EmbeddedWallet>) => (
<EmailSelectionUI {...props} />
<EmailSelectionUI
{...props}
oauthOptions={
config?.oauthOptions
? {
providers: [AuthProvider.GOOGLE],
redirectUrl: config.oauthOptions.redirectUrl,
}
: undefined
}
// you cannot disable both email and oauth
email={!config?.oauthOptions && !config?.email ? true : config?.email}
/>
);
return {
@@ -29,6 +53,5 @@ export const embeddedWallet = (
},
selectUI: selectUI,
connectUI: EmbeddedConnectionUI,
recommended: config?.recommended,
};
};
@@ -17,9 +17,9 @@ import { TextInput } from "../../components/base/TextInput";
import Text from "../../components/base/Text";
import { useMagicLink } from "../../providers/context-provider";
import BaseButton from "../../components/base/BaseButton";
import { useAppTheme } from "../../styles/hooks";
import React from "react";
import { ConnectingWallet } from "../../components/ConnectWalletFlow/ConnectingWallet/ConnectingWallet";
import { useGlobalTheme } from "../../providers/ui-context-provider";
export const magicLink = (
magicLinkOptions: MagicLinkOptions & { recommended?: boolean },
@@ -54,7 +54,7 @@ export const magicLink = (
};
const MagicSelectionUI: React.FC<SelectUIProps<MagicLink>> = (props) => {
const theme = useAppTheme();
const theme = useGlobalTheme();
const [email, setEmail] = React.useState("");
const onContinuePress = () => {
@@ -19,9 +19,9 @@ import {
useSetConnectionStatus,
} from "@thirdweb-dev/react-core";
import { WalletConnect } from "./WalletConnect";
import { useAppTheme } from "../../../styles/hooks";
import { WalletConnectButton } from "./WalletConnectButton";
import { ModalHeaderTextClose } from "../../../components/base";
import { useGlobalTheme } from "../../../providers/ui-context-provider";
type WCWallet = {
iconURL: string;
@@ -41,7 +41,7 @@ export function WalletConnectUI({
goBack,
projectId,
}: ConnectUIProps<WalletConnect> & { projectId: string }) {
const theme = useAppTheme();
const theme = useGlobalTheme();
const [wallets, setWallets] = useState<WCWallet[]>([]);
const [error, setError] = useState<string | undefined>();
const [loading, setLoading] = useState<boolean>(true);
@@ -1,7 +1,7 @@
import { useEffect, useRef } from "react";
import { Animated, Easing, StyleSheet, View } from "react-native";
import Svg, { Rect } from "react-native-svg";
import { useAppTheme } from "../../../styles/hooks";
import { useGlobalTheme } from "../../../providers/ui-context-provider";
const AnimatedRect = Animated.createAnimatedComponent(Rect);
@@ -19,7 +19,7 @@ function WalletLoadingThumbnail({
showError,
imageSize = 50,
}: Props) {
const Theme = useAppTheme();
const Theme = useGlobalTheme();
const spinValue = useRef(new Animated.Value(0));
useEffect(() => {
+35
View File
@@ -1,5 +1,40 @@
# @thirdweb-dev/react
## 4.0.4
### Patch Changes
- [#1769](https://github.com/thirdweb-dev/js/pull/1769) [`010955ff`](https://github.com/thirdweb-dev/js/commit/010955ff5cc35a975b54c25992b8e65b40e033a1) Thanks [@MananTank](https://github.com/MananTank)! - Fix OTP input not working well with Japanese keyboard
- Updated dependencies [[`956b5645`](https://github.com/thirdweb-dev/js/commit/956b5645e4266cba536abd27ee250dab5aa9a177)]:
- @thirdweb-dev/sdk@4.0.3
- @thirdweb-dev/wallets@2.0.4
- @thirdweb-dev/react-core@4.0.4
## 4.0.3
### Patch Changes
- [#1766](https://github.com/thirdweb-dev/js/pull/1766) [`dd7d0fca`](https://github.com/thirdweb-dev/js/commit/dd7d0fca2d14a529343396a31939d49b1a074d3e) Thanks [@MananTank](https://github.com/MananTank)! - Fix Continue as Guest button missing when Social + Guest login is setup in ConnectWallet Modal
- [#1692](https://github.com/thirdweb-dev/js/pull/1692) [`fb0f12ae`](https://github.com/thirdweb-dev/js/commit/fb0f12ae2835f4eba60ba4a74b453151cab0d393) Thanks [@etnlbck](https://github.com/etnlbck)! - Refactored OTPInput component to be simpler to read
- Updated dependencies [[`526176c4`](https://github.com/thirdweb-dev/js/commit/526176c47108c91d83bcfc5f0ca556274e6462d6), [`d1f8e951`](https://github.com/thirdweb-dev/js/commit/d1f8e951623f1691fdfe3d1e8645970d0a52eb06), [`09120c92`](https://github.com/thirdweb-dev/js/commit/09120c923cca804b9d4f5f779e5a53c97ecc8223), [`08e9cd20`](https://github.com/thirdweb-dev/js/commit/08e9cd206bcc37cf1e84a878dc78bc4f38e5092c), [`6573556d`](https://github.com/thirdweb-dev/js/commit/6573556d0e03efa0d6a157fccb0a1c08dd22d7cd)]:
- @thirdweb-dev/sdk@4.0.2
- @thirdweb-dev/react-core@4.0.3
- @thirdweb-dev/wallets@2.0.3
## 4.0.2
### Patch Changes
- [#1750](https://github.com/thirdweb-dev/js/pull/1750) [`bb1666b9`](https://github.com/thirdweb-dev/js/commit/bb1666b92b5e151d78ea7dae6831297fb947d954) Thanks [@MananTank](https://github.com/MananTank)! - ConnectWallet UI tweaks/fixes
- Updated dependencies [[`1f3c64d3`](https://github.com/thirdweb-dev/js/commit/1f3c64d3618903b9008cc7b633ea890b508e7800), [`74941603`](https://github.com/thirdweb-dev/js/commit/74941603a96456da46a47147f67c98235ac55022), [`4b98e487`](https://github.com/thirdweb-dev/js/commit/4b98e487d4b47225d7095e5444dc5e7608db48a3), [`693f349d`](https://github.com/thirdweb-dev/js/commit/693f349db2478e1cd0fc82a9ef9882f46832b125), [`dcdb0dcb`](https://github.com/thirdweb-dev/js/commit/dcdb0dcb838168a22c8335738852316dfb6e6fc9), [`0f12d1f9`](https://github.com/thirdweb-dev/js/commit/0f12d1f90320923309f085c372d3a5ed01dd4606), [`b3b9edca`](https://github.com/thirdweb-dev/js/commit/b3b9edca29e7d4053b14270a61caa89c34fecf9e), [`63ad0050`](https://github.com/thirdweb-dev/js/commit/63ad0050f0a24ec15b90933b5664d697618cf2d0)]:
- @thirdweb-dev/sdk@4.0.1
- @thirdweb-dev/wallets@2.0.2
- @thirdweb-dev/react-core@4.0.2
## 4.0.1
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/react",
"version": "4.0.1",
"version": "4.0.4",
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/react",
"author": "thirdweb eng <[email protected]>",
"license": "Apache-2.0",
+5 -3
View File
@@ -36,15 +36,17 @@ export function OTPInput(props: {
data-error={props.isInvalid}
ref={(e) => (boxEls.current[i] = e)}
key={i}
value={otp[i] === undefined ? "" : otp[i]}
value={otp[i] ?? ""}
type="number"
pattern="[0-9]*"
variant="outline"
inputMode="numeric"
onPaste={(e) => {
const pastedData = e.clipboardData.getData("text/plain");
const newOTP = pastedData
.slice(0, props.digits)
.split("")
.filter((n) => /[0-9]/.test(n))
.filter((n) => /\d/.test(n))
.map(Number);
setOTP(newOTP);
@@ -100,7 +102,7 @@ export function OTPInput(props: {
}
}
if (!/[0-9]/.test(value) && value !== "") {
if (!/\d/.test(value) && value !== "") {
e.preventDefault();
return;
}
+17 -2
View File
@@ -1,7 +1,22 @@
import styled from "@emotion/styled";
import { fontSize, Theme } from "../design-system";
import { fontSize, spacing, Theme } from "../design-system";
export const TextDivider = styled.div<{ theme?: Theme }>`
export const TextDivider = (props: {
text: string;
py?: keyof typeof spacing;
}) => {
return (
<TextDividerEl
style={{
paddingBlock: props.py ? spacing[props.py] : 0,
}}
>
<span> {props.text}</span>
</TextDividerEl>
);
};
export const TextDividerEl = styled.div<{ theme?: Theme }>`
display: flex;
align-items: center;
color: ${(p) => p.theme.colors.secondaryText};
+2 -2
View File
@@ -63,13 +63,13 @@ function createThemeObj(colors: typeof darkColors) {
scrollbarBg: colors.base2,
walletSelectorButtonHoverBg: colors.base2,
separatorLine: colors.base3,
separatorLine: colors.base4,
secondaryIconColor: colors.secondaryText,
secondaryIconHoverBg: colors.base3,
secondaryIconHoverColor: colors.primaryText,
borderColor: colors.base3,
borderColor: colors.base4,
skeletonBg: colors.base3,
selectedTextColor: colors.base1,
+1
View File
@@ -62,4 +62,5 @@ export {
MagicLink,
SignerWallet,
InjectedWallet,
setWalletAnalyticsEnabled,
} from "@thirdweb-dev/wallets";
@@ -290,7 +290,7 @@ export function SendFundsForm(props: {
</CurrencyBadge>
</Container>
<Spacer y="xl" />
<Spacer y="xxl" />
{/* Submit */}
<Button
@@ -307,6 +307,7 @@ export function SendFundsForm(props: {
style={{
alignItems: "center",
gap: spacing.sm,
padding: spacing.md,
}}
>
{sendTokenMutation.isLoading ? "Sending" : "Send"}
@@ -56,20 +56,6 @@ export const WalletSelector: React.FC<{
nonLocalWalletConfigs.filter((w) => w.category !== "socialLogin"),
);
const showNewToWallets =
isCompact && (socialWallets.length === 0 || isWalletGroupExpanded);
// groups UI is showing a social login + grouping all eoa wallets together in a group
// do this if there is social login and more than 2 eoa wallets
const showGroupsUI =
isCompact && socialWallets.length >= 1 && eoaWallets.length >= 2;
const showTOS = isCompact && (termsOfServiceUrl || privacyPolicyUrl);
const showFooter = Boolean(
(!showGroupsUI && localWalletConfig) || showNewToWallets,
);
const continueAsGuest = localWalletConfig && (
<Button
fullWidth
@@ -138,7 +124,218 @@ export const WalletSelector: React.FC<{
props.selectWallet(wallet);
};
const showSeperatorLine = showNewToWallets && !continueAsGuest && showTOS;
const connectAWallet = (
<Button
fullWidth
variant="outline"
style={{
display: "flex",
justifyContent: "center",
gap: spacing.sm,
padding: spacing.md,
}}
onClick={() => {
setIsWalletGroupExpanded(true);
}}
>
<Container flex="row" gap="xxs">
{eoaWallets.slice(0, 2).map((w) => (
<Img
key={w.id}
width={iconSize.sm}
height={iconSize.sm}
src={w.meta.iconURL}
/>
))}
</Container>
Connect a wallet
</Button>
);
const newToWallets = (
<Container
flex="row"
style={{
justifyContent: "space-between",
}}
>
<Text color="secondaryText" size="sm" weight={500}>
New to wallets?
</Text>
<Link
weight={500}
size="sm"
target="_blank"
href="https://blog.thirdweb.com/web3-wallet/"
>
Get started
</Link>
</Container>
);
const tos =
termsOfServiceUrl || privacyPolicyUrl ? (
<TOS
termsOfServiceUrl={termsOfServiceUrl}
privacyPolicyUrl={privacyPolicyUrl}
/>
) : undefined;
let topSection: React.ReactNode;
let bottomSection: React.ReactNode;
// wide modal
if (!isCompact) {
topSection = (
<WalletSelection
walletConfigs={nonLocalWalletConfigs}
selectWallet={handleSelect}
/>
);
if (continueAsGuest) {
bottomSection = (
<ScreenBottomContainer>{continueAsGuest}</ScreenBottomContainer>
);
}
}
// compact
else {
// no social logins
if (socialWallets.length === 0) {
topSection = (
<WalletSelection
walletConfigs={nonLocalWalletConfigs}
selectWallet={handleSelect}
/>
);
bottomSection = (
<>
<Line />
<Container flex="column" p="lg" gap="lg">
{newToWallets}
{continueAsGuest}
</Container>
{!continueAsGuest && <Line />}
{tos && (
<Container
px="md"
style={{
paddingBottom: spacing.md,
paddingTop: continueAsGuest ? 0 : spacing.md,
}}
>
{tos}
</Container>
)}
</>
);
}
// social logins
else {
// not expanded state
if (!isWalletGroupExpanded) {
topSection = (
<Container px="xs">
<WalletSelection
walletConfigs={socialWallets}
selectWallet={handleSelect}
/>
{eoaWallets.length > 0 && (
<>
<TextDivider text="OR" />
<Spacer y="lg" />
</>
)}
</Container>
);
// only social login - no eoa wallets
if (eoaWallets.length === 0) {
bottomSection =
tos || continueAsGuest ? (
<>
<Spacer y="md" />
<Line />
{continueAsGuest && (
<Container p="lg"> {continueAsGuest}</Container>
)}
{tos && <Container p="md"> {tos} </Container>}
</>
) : (
<Spacer y="sm" />
);
}
// social login + eoa wallets
else {
// social login + More than 1 eoa wallets
if (eoaWallets.length > 1) {
bottomSection = (
<Container flex="column" gap="sm">
<Container px="lg" flex="column" gap="md">
{connectAWallet}
{continueAsGuest}
</Container>
{tos ? (
<Container p="md"> {tos} </Container>
) : (
<Spacer y="md" />
)}
</Container>
);
}
// social login + single eoa wallet
else {
bottomSection = (
<>
<Container px="lg">
<WalletSelection
walletConfigs={eoaWallets}
selectWallet={handleSelect}
/>
</Container>
{continueAsGuest && (
<Container flex="column" px="lg" gap="lg">
{continueAsGuest}
</Container>
)}
{tos ? (
<>
{continueAsGuest ? <Spacer y="md" /> : <Line />}
<Container p="md"> {tos} </Container>
</>
) : (
<>{continueAsGuest && <Spacer y="xl" />}</>
)}
</>
);
}
}
}
// expanded state
else {
topSection = (
<WalletSelection
walletConfigs={eoaWallets}
selectWallet={handleSelect}
/>
);
bottomSection = (
<ScreenBottomContainer>{newToWallets}</ScreenBottomContainer>
);
}
}
}
return (
<Container scrollY flex="column" animate="fadein" fullHeight>
@@ -160,130 +357,15 @@ export const WalletSelector: React.FC<{
<Container
expand
scrollY
px={nonLocalWalletConfigs.length === 1 ? "lg" : "md"}
px="md"
style={{
paddingTop: "2px",
}}
>
{showGroupsUI ? (
<>
{/* list of EOA wallets */}
{isWalletGroupExpanded ? (
<WalletSelection
walletConfigs={eoaWallets}
selectWallet={handleSelect}
/>
) : (
<Container px="xs">
<WalletSelection
walletConfigs={socialWallets}
selectWallet={handleSelect}
/>
<TextDivider>
<span> OR </span>
</TextDivider>
<Spacer y="lg" />
{/* connect a wallet */}
<Button
fullWidth
variant="outline"
style={{
display: "flex",
justifyContent: "center",
gap: spacing.sm,
padding: spacing.md,
}}
onClick={() => {
setIsWalletGroupExpanded(true);
}}
>
<Container flex="row" gap="xxs">
{eoaWallets.slice(0, 2).map((w) => (
<Img
key={w.id}
width={iconSize.sm}
height={iconSize.sm}
src={w.meta.iconURL}
/>
))}
</Container>
Connect a wallet
</Button>
{continueAsGuest ? (
<>
<Spacer y="md" />
{continueAsGuest}
<Spacer y="xl" />
</>
) : (
<Spacer y="xl" />
)}
</Container>
)}
</>
) : (
<WalletSelection
walletConfigs={nonLocalWalletConfigs}
selectWallet={handleSelect}
/>
)}
{topSection}
</Container>
{/* Footer */}
{showFooter && (
<ScreenBottomContainer>
{showNewToWallets && (
<Container
flex="row"
style={{
justifyContent: "space-between",
}}
>
<Text color="secondaryText" size="sm" weight={500}>
New to wallets?
</Text>
<Link
weight={500}
size="sm"
target="_blank"
href="https://blog.thirdweb.com/web3-wallet/"
>
Get started
</Link>
</Container>
)}
{!showGroupsUI && continueAsGuest}
</ScreenBottomContainer>
)}
{showTOS && !isWalletGroupExpanded && (
<div>
{showSeperatorLine && <Line />}
<Container
p="md"
style={
!showSeperatorLine
? {
paddingTop: 0,
}
: undefined
}
>
{isCompact && (
<TOS
termsOfServiceUrl={termsOfServiceUrl}
privacyPolicyUrl={privacyPolicyUrl}
/>
)}
</Container>
</div>
)}
{bottomSection}
</Container>
);
};
@@ -383,7 +465,7 @@ const WalletList = styled.ul<{ theme?: Theme }>`
margin: -2px;
padding-bottom: 0;
margin-bottom: 0;
padding-bottom: ${spacing.xl};
padding-bottom: ${spacing.lg};
`;
const WalletButton = styled.button<{ theme?: Theme }>`
@@ -428,7 +510,7 @@ function sortWalletConfigs(walletConfigs: WalletConfig[]) {
}
return 0;
})
// show the reccomended wallets even before that
// show the recommended wallets even before that
.sort((a, b) => {
if (a.recommended && !b.recommended) {
return -1;
@@ -64,15 +64,7 @@ export const EmbeddedWalletFormUI = (props: {
<GoogleIcon size={iconSize.md} />
Sign in with Google
</SocialButton>
<Spacer y="lg" />
<TextDivider>
<span>OR</span>
</TextDivider>
<Spacer y="lg" />
<TextDivider text="OR" py="lg" />
<InputSelectionUI
onSelect={(email) => props.onSelect({ email })}
placeholder="Enter your email address"
@@ -114,10 +114,10 @@ export const EmbeddedWalletOTPLoginUI: React.FC<
}}
>
{!isWideModal && <Spacer y="xl" />}
<Text>Enter the OTP sent to</Text>
<Text>Enter the verification code sent to</Text>
<Spacer y="sm" />
<Text color="primaryText">{email}</Text>
<Spacer y="xxl" />
<Spacer y="xl" />
</div>
<OTPInput
@@ -138,12 +138,12 @@ export const EmbeddedWalletOTPLoginUI: React.FC<
<FadeIn>
<Spacer y="md" />
<Text size="sm" color="danger" center>
Invalid OTP
Invalid verification code
</Text>
</FadeIn>
)}
<Spacer y="xxl" />
<Spacer y="xl" />
<Container px={isWideModal ? "xxl" : "lg"}>
{verifyStatus === "verifying" ? (
@@ -168,7 +168,7 @@ export const EmbeddedWalletOTPLoginUI: React.FC<
)}
</Container>
<Spacer y="xxl" />
<Spacer y="xl" />
{!isWideModal && <Line />}
@@ -176,7 +176,7 @@ export const EmbeddedWalletOTPLoginUI: React.FC<
{sendEmailOtpStatus === "error" && (
<>
<Text size="sm" center color="danger">
Failed to send OTP
Failed to send verification code
</Text>
</>
)}
@@ -190,14 +190,14 @@ export const EmbeddedWalletOTPLoginUI: React.FC<
textAlign: "center",
}}
>
<Text size="sm">Sending OTP</Text>
<Text size="sm">Sending verification code</Text>
<Spinner size="xs" color="secondaryText" />
</Container>
)}
{sendEmailOtpStatus === "sent" && (
<LinkButton onClick={sendEmail} type="button">
Resend OTP
Resend verification code
</LinkButton>
)}
</Container>
@@ -256,11 +256,7 @@ const MagicUI: React.FC<{
{showInputUI && (
<>
{showSeparator && (
<TextDivider>
<span> OR </span>
</TextDivider>
)}
{showSeparator && <TextDivider text="OR" />}
<InputSelectionUI
onSelect={props.onSelect}
placeholder={placeholder}
@@ -67,13 +67,7 @@ export const PaperFormUI = (props: {
Sign in with Google
</SocialButton>
<Spacer y="lg" />
<TextDivider>
<span>OR</span>
</TextDivider>
<Spacer y="lg" />
<TextDivider text="OR" py="lg" />
</>
)}
@@ -2,7 +2,6 @@ import styled from "@emotion/styled";
import { ConnectUIProps, useWalletContext } from "@thirdweb-dev/react-core";
import { PaperWallet } from "@thirdweb-dev/wallets";
import { useCallback, useEffect, useRef, useState } from "react";
import { FadeIn } from "../../../components/FadeIn";
import { OTPInput } from "../../../components/OTPInput";
import { Spacer } from "../../../components/Spacer";
import { Spinner } from "../../../components/Spinner";
@@ -135,7 +134,7 @@ export const PaperOTPLoginUI: React.FC<PaperOTPLoginUIProps> = (props) => {
}}
>
{!isWideModal && <Spacer y="lg" />}
<Text>Enter the OTP sent to</Text>
<Text>Enter the verification code sent to</Text>
<Spacer y="sm" />
<Text color="primaryText">{email}</Text>
<Spacer y="xl" />
@@ -159,6 +158,7 @@ export const PaperOTPLoginUI: React.FC<PaperOTPLoginUIProps> = (props) => {
{recoveryCodeRequired && (
<Container
animate="fadein"
px="lg"
style={{
textAlign: "center",
@@ -197,15 +197,16 @@ export const PaperOTPLoginUI: React.FC<PaperOTPLoginUIProps> = (props) => {
)}
{verifyStatus === "invalid" && (
<FadeIn>
<Container animate="fadein">
<Spacer y="md" />
<Text size="sm" color="danger" center>
Invalid OTP {recoveryCodeRequired ? "or recovery code" : ""}
Invalid verification code{" "}
{recoveryCodeRequired ? "or recovery code" : ""}
</Text>
</FadeIn>
</Container>
)}
<Spacer y={recoveryCodeRequired ? "xl" : "xxl"} />
<Spacer y="xl" />
<Container px={isWideModal ? "xxl" : "lg"}>
{verifyStatus === "verifying" ? (
@@ -230,15 +231,15 @@ export const PaperOTPLoginUI: React.FC<PaperOTPLoginUIProps> = (props) => {
)}
</Container>
<Spacer y={recoveryCodeRequired ? "xl" : "xxl"} />
<Spacer y={"xl"} />
{!isWideModal && <Line />}
<Container p={isWideModal ? undefined : "lg"}>
<Container p={isWideModal ? undefined : "lg"} animate="fadein">
{sendEmailStatus === "error" && (
<>
<Text size="sm" center color="danger">
Failed to send OTP
Failed to send verification code
</Text>
</>
)}
@@ -252,14 +253,14 @@ export const PaperOTPLoginUI: React.FC<PaperOTPLoginUIProps> = (props) => {
textAlign: "center",
}}
>
<Text size="sm">Sending OTP</Text>
<Text size="sm">Sending verification code</Text>
<Spinner size="xs" color="secondaryText" />
</Container>
)}
{typeof sendEmailStatus !== "string" && (
<LinkButton onClick={sendEmail} type="button">
Resend OTP
Resend verification code
</LinkButton>
)}
</Container>
+36
View File
@@ -1,5 +1,41 @@
# @thirdweb-dev/sdk
## 4.0.3
### Patch Changes
- [#1756](https://github.com/thirdweb-dev/js/pull/1756) [`956b5645`](https://github.com/thirdweb-dev/js/commit/956b5645e4266cba536abd27ee250dab5aa9a177) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Enable adding smart wallet admins via bundler for new account contracts
- Updated dependencies [[`956b5645`](https://github.com/thirdweb-dev/js/commit/956b5645e4266cba536abd27ee250dab5aa9a177)]:
- @thirdweb-dev/contracts-js@1.3.15
## 4.0.2
### Patch Changes
- [#1764](https://github.com/thirdweb-dev/js/pull/1764) [`526176c4`](https://github.com/thirdweb-dev/js/commit/526176c47108c91d83bcfc5f0ca556274e6462d6) Thanks [@kien-ngo](https://github.com/kien-ngo)! - Perf improvement for marketplace methods
- [#1763](https://github.com/thirdweb-dev/js/pull/1763) [`d1f8e951`](https://github.com/thirdweb-dev/js/commit/d1f8e951623f1691fdfe3d1e8645970d0a52eb06) Thanks [@kien-ngo](https://github.com/kien-ngo)! - Improve fetchPublishedContractFromPolygon
- [#1765](https://github.com/thirdweb-dev/js/pull/1765) [`09120c92`](https://github.com/thirdweb-dev/js/commit/09120c923cca804b9d4f5f779e5a53c97ecc8223) Thanks [@kien-ngo](https://github.com/kien-ngo)! - Perf improvement for getting deployment info
- [#1762](https://github.com/thirdweb-dev/js/pull/1762) [`6573556d`](https://github.com/thirdweb-dev/js/commit/6573556d0e03efa0d6a157fccb0a1c08dd22d7cd) Thanks [@kien-ngo](https://github.com/kien-ngo)! - Perf improvement for drop-claim-condition
## 4.0.1
### Patch Changes
- [#1747](https://github.com/thirdweb-dev/js/pull/1747) [`1f3c64d3`](https://github.com/thirdweb-dev/js/commit/1f3c64d3618903b9008cc7b633ea890b508e7800) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Handle upgradeable proxies and optimize proxy resolution
- [#1695](https://github.com/thirdweb-dev/js/pull/1695) [`693f349d`](https://github.com/thirdweb-dev/js/commit/693f349db2478e1cd0fc82a9ef9882f46832b125) Thanks [@kien-ngo](https://github.com/kien-ngo)! - Add optional pagination to getOwned NFTs
- [#1746](https://github.com/thirdweb-dev/js/pull/1746) [`0f12d1f9`](https://github.com/thirdweb-dev/js/commit/0f12d1f90320923309f085c372d3a5ed01dd4606) Thanks [@farhanW3](https://github.com/farhanW3)! - Fix for RPC Override Implementation to get user provided Url and not default RPC Url for the chain
- [#1757](https://github.com/thirdweb-dev/js/pull/1757) [`63ad0050`](https://github.com/thirdweb-dev/js/commit/63ad0050f0a24ec15b90933b5664d697618cf2d0) Thanks [@kumaryash90](https://github.com/kumaryash90)! - Update deployment and tests for MarketplaceV3 latest
- Updated dependencies [[`63ad0050`](https://github.com/thirdweb-dev/js/commit/63ad0050f0a24ec15b90933b5664d697618cf2d0)]:
- @thirdweb-dev/contracts-js@1.3.14
## 4.0.0
### Major Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/sdk",
"version": "4.0.0",
"version": "4.0.3",
"description": "The main thirdweb SDK.",
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/sdk",
"license": "Apache-2.0",
@@ -135,7 +135,7 @@ export async function encodeConstructorParamsForImplementation(
compilerMetadata.abi,
);
const constructorParamTypes = constructorParams.map((p) => {
if (p.type === "tuple[]") {
if (p.type === "tuple[]" || p.type === "tuple") {
return utils.ParamType.from(p);
} else {
return p.type;
@@ -156,31 +156,13 @@ export async function encodeConstructorParamsForImplementation(
return constructorParamMap[p.name].value;
}
if (p.name && p.name.includes("nativeTokenWrapper")) {
const chainId = (await provider.getNetwork()).chainId;
let nativeTokenWrapperAddress =
getNativeTokenByChainId(chainId).wrapped.address;
if (nativeTokenWrapperAddress === constants.AddressZero) {
const deploymentInfo = await computeDeploymentInfo(
"infra",
provider,
storage,
create2Factory,
{
contractName: "WETH9",
},
clientId,
secretKey,
);
if (!caches.deploymentPresets["WETH9"]) {
caches.deploymentPresets["WETH9"] = deploymentInfo;
}
nativeTokenWrapperAddress =
deploymentInfo.transaction.predictedAddress;
}
return nativeTokenWrapperAddress;
return await nativeTokenInputArg(
provider,
storage,
create2Factory,
clientId,
secretKey,
);
} else if (p.name && p.name.includes("trustedForwarder")) {
if (compilerMetadata.name === "Pack") {
// EOAForwarder for Pack
@@ -220,6 +202,27 @@ export async function encodeConstructorParamsForImplementation(
} else if (p.name && p.name.includes("royaltyEngineAddress")) {
const chainId = (await provider.getNetwork()).chainId;
return getRoyaltyEngineV1ByChainId(chainId);
} else if (p.name && p.name.includes("marketplaceV3Params")) {
const chainId = (await provider.getNetwork()).chainId;
const royaltyEngineAddress = getRoyaltyEngineV1ByChainId(chainId);
const nativeTokenWrapper = await nativeTokenInputArg(
provider,
storage,
create2Factory,
clientId,
secretKey,
);
const extensions = constructorParamMap
? constructorParamMap["_extensions"].value
: [];
return {
extensions: extensions,
royaltyEngineAddress: royaltyEngineAddress,
nativeTokenWrapper: nativeTokenWrapper,
};
} else {
throw new Error("Can't resolve constructor arguments");
}
@@ -232,3 +235,36 @@ export async function encodeConstructorParamsForImplementation(
);
return encodedArgs;
}
async function nativeTokenInputArg(
provider: providers.Provider,
storage: ThirdwebStorage,
create2Factory: string,
clientId?: string,
secretKey?: string,
): Promise<string> {
const chainId = (await provider.getNetwork()).chainId;
let nativeTokenWrapperAddress =
getNativeTokenByChainId(chainId).wrapped.address;
if (nativeTokenWrapperAddress === constants.AddressZero) {
const deploymentInfo = await computeDeploymentInfo(
"infra",
provider,
storage,
create2Factory,
{
contractName: "WETH9",
},
clientId,
secretKey,
);
if (!caches.deploymentPresets["WETH9"]) {
caches.deploymentPresets["WETH9"] = deploymentInfo;
}
nativeTokenWrapperAddress = deploymentInfo.transaction.predictedAddress;
}
return nativeTokenWrapperAddress;
}
@@ -50,18 +50,18 @@ export async function fetchPublishedContractFromPolygon(
});
// get the metadata for each version
const versionMetadata = await Promise.all(
allVersions.map(async (c) => {
return {
name: c.id,
publishedTimestamp: c.timestamp,
publishedMetadata: await fetchAndCacheDeployMetadata(
c.metadataUri,
storage,
),
};
}),
);
const versionMetadata = (
await Promise.all(
allVersions.map((c) =>
fetchAndCacheDeployMetadata(c.metadataUri, storage),
),
)
).map((item, index) => ({
name: allVersions[index].id,
publishedTimestamp: allVersions[index].timestamp,
publishedMetadata: item,
}));
// find the version that matches the version string
const versionMatch = versionMetadata.find(
(metadata) =>
@@ -37,15 +37,13 @@ export async function getDeploymentInfo(
secretKey?: string,
): Promise<DeploymentPreset[]> {
caches.deploymentPresets = {};
const create2FactoryAddress = create2Factory
? create2Factory
: await getCreate2FactoryAddress(provider);
const [create2FactoryAddress, { compilerMetadata, extendedMetadata }] =
await Promise.all([
create2Factory ? create2Factory : getCreate2FactoryAddress(provider),
fetchAndCacheDeployMetadata(metadataUri, storage),
]);
const customParams: ConstructorParamMap = {};
const finalDeploymentInfo: DeploymentPreset[] = [];
const { compilerMetadata, extendedMetadata } =
await fetchAndCacheDeployMetadata(metadataUri, storage);
const defaultExtensions = extendedMetadata?.defaultExtensions;
if (extendedMetadata?.routerType === "plugin" && defaultExtensions) {
@@ -65,16 +63,16 @@ export async function getDeploymentInfo(
const pluginMetadata = (
await Promise.all(
publishedExtensions.map(async (c) => {
return fetchAndCacheDeployMetadata(c.metadataUri, storage);
}),
publishedExtensions.map((c) =>
fetchAndCacheDeployMetadata(c.metadataUri, storage),
),
)
).map((fetchedMetadata) => fetchedMetadata.compilerMetadata);
// get deployment info for all plugins
const pluginDeploymentInfo = await Promise.all(
pluginMetadata.map(async (metadata) => {
const info = await computeDeploymentInfo(
pluginMetadata.map((metadata) =>
computeDeploymentInfo(
"plugin",
provider,
storage,
@@ -82,9 +80,8 @@ export async function getDeploymentInfo(
{ metadata: metadata },
clientId,
secretKey,
);
return info;
}),
),
),
);
// create constructor param input for PluginMap
@@ -142,8 +139,8 @@ export async function getDeploymentInfo(
// get deployment info for all extensions
const extensionDeploymentInfo = await Promise.all(
extensionMetadata.map(async (metadata) => {
const info = await computeDeploymentInfo(
extensionMetadata.map((metadata) =>
computeDeploymentInfo(
"extension",
provider,
storage,
@@ -151,9 +148,8 @@ export async function getDeploymentInfo(
{ metadata: metadata },
clientId,
secretKey,
);
return info;
}),
),
),
);
// create constructor param input for BaseRouter
@@ -179,29 +175,30 @@ export async function getDeploymentInfo(
finalDeploymentInfo.push(...extensionDeploymentInfo);
}
const implementationDeployInfo = await computeDeploymentInfo(
"implementation",
provider,
storage,
create2FactoryAddress,
{
metadata: compilerMetadata,
constructorParams: customParams,
},
clientId,
secretKey,
);
// get clone factory
const factoryInfo = await computeDeploymentInfo(
"infra",
provider,
storage,
create2FactoryAddress,
{ contractName: "TWCloneFactory" },
clientId,
secretKey,
);
const [implementationDeployInfo, factoryInfo] = await Promise.all([
computeDeploymentInfo(
"implementation",
provider,
storage,
create2FactoryAddress,
{
metadata: compilerMetadata,
constructorParams: customParams,
},
clientId,
secretKey,
),
// get clone factory
computeDeploymentInfo(
"infra",
provider,
storage,
create2FactoryAddress,
{ contractName: "TWCloneFactory" },
clientId,
secretKey,
),
]);
finalDeploymentInfo.push(factoryInfo);
finalDeploymentInfo.push(...Object.values(caches.deploymentPresets));
@@ -1,4 +1,4 @@
import { BigNumber, providers, constants, utils } from "ethers";
import { BigNumber, providers, constants, utils, Contract } from "ethers";
import { extractIPFSHashFromBytecode } from "./extractIPFSHashFromBytecode";
import { extractMinimalProxyImplementationAddress } from "./extractMinimalProxyImplementationAddress";
@@ -33,32 +33,65 @@ export async function resolveImplementation(
address: string,
provider: providers.Provider,
): Promise<{ address: string; bytecode: string }> {
let bytecode;
const bytecode = await fetchBytecode(address, provider);
// check minimal proxy first synchronously
const minimalProxyImplementationAddress =
extractMinimalProxyImplementationAddress(bytecode);
if (minimalProxyImplementationAddress) {
return {
address: minimalProxyImplementationAddress,
bytecode: await fetchBytecode(
minimalProxyImplementationAddress,
provider,
),
};
}
// check other proxy types
const impl = await Promise.all([
getImplementationFromStorageSlot(address, provider),
getImplementationFromContractCall(address, provider),
]);
for (const implementationAddress of impl) {
if (
implementationAddress &&
utils.isAddress(implementationAddress) &&
implementationAddress !== constants.AddressZero
) {
return {
address: implementationAddress,
bytecode: await fetchBytecode(implementationAddress, provider),
};
}
}
return { address, bytecode };
}
async function fetchBytecode(
address: string,
provider: providers.Provider,
): Promise<string> {
try {
bytecode = await provider.getCode(address);
const bytecode = await provider.getCode(address);
if (bytecode === "0x") {
const chain = await provider.getNetwork();
throw new Error(
`Contract at ${address} does not exist on chain '${chain.name}' (chainId: ${chain.chainId})`,
);
}
return bytecode;
} catch (e) {
throw new Error(`Failed to get bytecode for address ${address}: ${e}`);
}
}
if (bytecode === "0x") {
const chain = await provider.getNetwork();
throw new Error(
`Contract at ${address} does not exist on chain '${chain.name}' (chainId: ${chain.chainId})`,
);
}
try {
// TODO support other types of proxies
const implementationAddress =
extractMinimalProxyImplementationAddress(bytecode);
if (implementationAddress) {
return await resolveImplementation(implementationAddress, provider);
}
} catch (e) {
// ignore
}
// EIP-1967 proxy storage slots - https://eips.ethereum.org/EIPS/eip-1967
async function getImplementationFromStorageSlot(
address: string,
provider: providers.Provider,
): Promise<string | undefined> {
try {
const proxyStorage = await provider.getStorageAt(
address,
@@ -66,18 +99,36 @@ export async function resolveImplementation(
"0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc",
),
);
const implementationAddress = `0x${proxyStorage.slice(-40)}`;
if (
utils.isAddress(implementationAddress) &&
implementationAddress !== constants.AddressZero
) {
return await resolveImplementation(implementationAddress, provider);
}
return `0x${proxyStorage.slice(-40)}`;
} catch (e) {
// ignore
return undefined;
}
if (!bytecode) {
throw new Error(`Error fetching bytecode for ${address}`);
}
return { address, bytecode };
}
async function getImplementationFromContractCall(
address: string,
provider: providers.Provider,
): Promise<string | undefined> {
try {
const proxy = new Contract(address, UPGRADEABLE_PROXY_ABI, provider);
return await proxy.implementation();
} catch (e) {
return undefined;
}
}
const UPGRADEABLE_PROXY_ABI = [
{
type: "function",
name: "implementation",
inputs: [],
outputs: [
{
type: "address",
name: "",
internalType: "address",
},
],
stateMutability: "view",
},
];
+7 -7
View File
@@ -46,8 +46,10 @@ export async function isTokenApprovedForTransfer(
): Promise<boolean> {
try {
const erc165 = new Contract(assetContract, ERC165Abi, 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 asset = new Contract(assetContract, ERC721Abi, provider) as IERC721;
@@ -108,11 +110,9 @@ export async function handleTokenApproval(
contractWrapper.options,
contractWrapper.storage,
);
const isERC721 = await erc165.read("supportsInterface", [
InterfaceId_IERC721,
]);
const isERC1155 = await erc165.read("supportsInterface", [
InterfaceId_IERC1155,
const [isERC721, isERC1155] = await Promise.all([
erc165.read("supportsInterface", [InterfaceId_IERC721]),
erc165.read("supportsInterface", [InterfaceId_IERC1155]),
]);
// check for token approval
if (isERC721) {
@@ -73,6 +73,7 @@ import {
FEATURE_ACCOUNT,
FEATURE_ACCOUNT_FACTORY,
FEATURE_ACCOUNT_PERMISSIONS,
FEATURE_ACCOUNT_PERMISSIONS_V1,
} from "./thirdweb-features";
/**
@@ -146,6 +147,7 @@ export type Feature =
| typeof FEATURE_AIRDROP_ERC1155
| typeof FEATURE_ACCOUNT
| typeof FEATURE_ACCOUNT_PERMISSIONS
| typeof FEATURE_ACCOUNT_PERMISSIONS_V1
| typeof FEATURE_ACCOUNT_FACTORY;
export type Extension = Feature;
@@ -14,7 +14,7 @@ import IMintableERC721Abi from "@thirdweb-dev/contracts-js/dist/abis/IMintableER
import MulticallAbi from "@thirdweb-dev/contracts-js/dist/abis/IMulticall.json";
import SignatureMintERC721Abi from "@thirdweb-dev/contracts-js/dist/abis/ISignatureMintERC721.json";
import SignatureMintERC721_V1Abi from "@thirdweb-dev/contracts-js/dist/abis/ISignatureMintERC721_V1.json";
import TieredDropAbi from "@thirdweb-dev/contracts-js/dist/abis/LazyMintWithTier.json";
import TieredDropAbi from "@thirdweb-dev/contracts-js/dist/abis/LazyMintWithTier_V1.json";
import SharedMetadataAbi from "@thirdweb-dev/contracts-js/dist/abis/SharedMetadata.json";
import zora_IDropERC721 from "@thirdweb-dev/contracts-js/dist/abis/zora_IERC721Drop.json";
import ILoyaltyCardAbi from "@thirdweb-dev/contracts-js/dist/abis/ILoyaltyCard.json";
@@ -16,6 +16,7 @@ import IAirdropERC721 from "@thirdweb-dev/contracts-js/dist/abis/IAirdropERC721.
import IAirdropERC1155 from "@thirdweb-dev/contracts-js/dist/abis/IAirdropERC1155.json";
import IAccountFactoryCore from "@thirdweb-dev/contracts-js/dist/abis/IAccountFactoryCore.json";
import IAccountPermissions from "@thirdweb-dev/contracts-js/dist/abis/IAccountPermissions.json";
import IAccountPermissionsV1 from "@thirdweb-dev/contracts-js/dist/abis/IAccountPermissions_V1.json";
import IAccount from "@thirdweb-dev/contracts-js/dist/abis/IAccount.json";
export const getAllPluginsAbi = [
@@ -295,6 +296,18 @@ export const FEATURE_ACCOUNT_FACTORY = {
features: {},
} as const;
export const FEATURE_ACCOUNT_PERMISSIONS_V1 = {
name: "AccountPermissionsV1",
namespace: "accountPermissions",
docLinks: {
// TODO
sdk: "sdk.account",
contracts: "IAccountPermissions",
},
abis: [IAccountPermissionsV1],
features: {},
} as const;
export const FEATURE_ACCOUNT_PERMISSIONS = {
name: "AccountPermissions",
namespace: "accountPermissions",
@@ -316,7 +329,10 @@ export const FEATURE_ACCOUNT = {
contracts: "IAccount",
},
abis: [IAccount],
features: { [FEATURE_ACCOUNT_PERMISSIONS.name]: FEATURE_ACCOUNT_PERMISSIONS },
features: {
[FEATURE_ACCOUNT_PERMISSIONS.name]: FEATURE_ACCOUNT_PERMISSIONS,
[FEATURE_ACCOUNT_PERMISSIONS_V1.name]: FEATURE_ACCOUNT_PERMISSIONS_V1,
},
} as const;
export const FEATURE_AIRDROP_ERC20 = {
+1 -1
View File
@@ -44,7 +44,7 @@ export function getChainProvider(
options.supportedChains = [
// @ts-expect-error - we know this is a chain and it will work to build the map
network,
...options.supportedChains.filter((c) => c.chainId !== network.chainId),
...options.supportedChains.filter((c) => c.chainId === network.chainId),
];
}
@@ -216,8 +216,11 @@ export class EditionDrop extends StandardErc1155<PrebuiltEditionDrop> {
*
* @returns The NFT metadata for all NFTs in the contract.
*/
public async getOwned(walletAddress?: AddressOrEns): Promise<NFT[]> {
return this.erc1155.getOwned(walletAddress);
public async getOwned(
walletAddress?: AddressOrEns,
queryParams?: QueryAllParams,
): Promise<NFT[]> {
return this.erc1155.getOwned(walletAddress, queryParams);
}
/**
@@ -190,8 +190,11 @@ export class Edition extends StandardErc1155<TokenERC1155> {
*
* @returns The NFT metadata for all NFTs in the contract.
*/
public async getOwned(walletAddress?: AddressOrEns): Promise<NFT[]> {
return this.erc1155.getOwned(walletAddress);
public async getOwned(
walletAddress?: AddressOrEns,
queryParams?: QueryAllParams,
): Promise<NFT[]> {
return this.erc1155.getOwned(walletAddress, queryParams);
}
/**
@@ -7,24 +7,30 @@ import { Transaction } from "./transactions";
import type {
IAccountCore,
IAccountPermissions,
IAccountPermissions_V1,
} from "@thirdweb-dev/contracts-js";
import invariant from "tiny-invariant";
import { resolveAddress } from "../../common";
import { detectContractFeature, resolveAddress } from "../../common";
import { resolveOrGenerateId } from "../../common/signature-minting";
import { buildTransactionFunction } from "../../common/transactions";
import { AddressOrEns } from "../../schema";
import {
AdminFlag,
DEFAULT_PERMISSIONS,
PermissionSnapshotInput,
PermissionSnapshotOutput,
PermissionSnapshotSchema,
SignedSignerPermissionsPayload,
SignedSignerPermissionsPayloadV1,
SignerPermissionRequest,
SignerPermissionRequestV1,
SignerPermissions,
SignerPermissionsInput,
SignerPermissionsOutput,
SignerPermissionsSchema,
SignerWithPermissions,
} from "../../types";
import { AddressZero } from "../../constants/addresses/AddressZero";
export class AccountPermissions implements DetectableFeature {
featureName = FEATURE_ACCOUNT_PERMISSIONS.name;
@@ -85,21 +91,44 @@ export class AccountPermissions implements DetectableFeature {
private async sendSignerPermissionRequest(
signerAddress: string,
permissions: SignerPermissionsOutput,
adminFlag: AdminFlag,
): Promise<Transaction> {
if (
detectContractFeature<IAccountPermissions_V1>(
this.contractWrapper,
"AccountPermissionsV1",
)
) {
// legacy account permissions contract
// admin is set only via EOA
// signer permissions are set via EOA or admin but no wildcard
if (
adminFlag === AdminFlag.AddAdmin ||
adminFlag === AdminFlag.RemoveAdmin
) {
return Transaction.fromContractWrapper({
contractWrapper: this.contractWrapper,
method: "setAdmin",
args: [signerAddress, adminFlag === AdminFlag.AddAdmin],
});
} else {
const { payload, signature } = await this.generateLegacyPayload(
signerAddress,
permissions,
);
return Transaction.fromContractWrapper({
contractWrapper: this.contractWrapper,
method: "setPermissionsForSigner",
args: [payload, signature],
});
}
}
const { payload, signature } = await this.generatePayload(
signerAddress,
permissions,
adminFlag,
);
const [success] = await this.contractWrapper.read(
"verifySignerPermissionRequest",
[payload, signature],
);
if (!success) {
throw new Error(`Invalid signature.`);
}
return Transaction.fromContractWrapper({
contractWrapper: this.contractWrapper,
method: "setPermissionsForSigner",
@@ -107,29 +136,6 @@ export class AccountPermissions implements DetectableFeature {
});
}
private async buildSignerPermissionRequest(
signerAddress: string,
permissions: SignerPermissionsOutput,
): Promise<string> {
const { payload, signature } = await this.generatePayload(
signerAddress,
permissions,
);
const isValidSigner = await this.contractWrapper.read(
"verifySignerPermissionRequest",
[payload, signature],
);
if (!isValidSigner) {
throw new Error(`Invalid signature.`);
}
return this.contractWrapper.writeContract.interface.encodeFunctionData(
"setPermissionsForSigner",
[payload, signature],
);
}
/**
* Generate and sign a payload to grant or revoke a signer's access to the account.
*
@@ -141,11 +147,16 @@ export class AccountPermissions implements DetectableFeature {
private async generatePayload(
signerAddress: string,
permissions: SignerPermissionsOutput,
isAdmin: AdminFlag,
): Promise<SignedSignerPermissionsPayload> {
// Get payload struct.
const payload: IAccountPermissions.SignerPermissionRequestStruct = {
signer: signerAddress,
approvedTargets: permissions.approvedCallTargets,
isAdmin: isAdmin.valueOf(),
approvedTargets:
permissions.approvedCallTargets === "*"
? [AddressZero]
: permissions.approvedCallTargets,
nativeTokenLimitPerTransaction: utils.parseEther(
permissions.nativeTokenLimitPerTransaction,
),
@@ -182,6 +193,53 @@ export class AccountPermissions implements DetectableFeature {
return { payload, signature };
}
private async generateLegacyPayload(
signerAddress: string,
permissions: SignerPermissionsOutput,
): Promise<SignedSignerPermissionsPayloadV1> {
if (permissions.approvedCallTargets === "*") {
throw new Error(
"Wildcard call targets are not supported on legacy account permissions contract, please deploy an updated contract factory.",
);
}
// legacy account permissions contract
// admin is set only via EOA
// signer permissions are set via EOA or admin but no wildcard
const payload: IAccountPermissions_V1.SignerPermissionRequestStruct = {
signer: signerAddress,
approvedTargets: permissions.approvedCallTargets,
nativeTokenLimitPerTransaction: utils.parseEther(
permissions.nativeTokenLimitPerTransaction,
),
permissionStartTimestamp: permissions.startDate,
permissionEndTimestamp: permissions.expirationDate,
reqValidityStartTimestamp: 0,
// Req validity ends 10 years from now.
reqValidityEndTimestamp: BigNumber.from(
Math.floor(
new Date(Date.now() + 1000 * 60 * 60 * 24 * 365 * 10).getTime() /
1000,
),
),
uid: resolveOrGenerateId(undefined),
};
const chainId = await this.contractWrapper.getChainID();
const connectedSigner = this.contractWrapper.getSigner();
invariant(connectedSigner, "No signer available");
const signature = await this.contractWrapper.signTypedData(
connectedSigner,
{
name: "Account",
version: "1",
chainId,
verifyingContract: this.getAddress(),
},
{ SignerPermissionRequest: SignerPermissionRequestV1 },
payload,
);
return { payload, signature };
}
/*********************************
* READ FUNCTIONS
********************************/
@@ -320,11 +378,11 @@ export class AccountPermissions implements DetectableFeature {
grantAdminPermissions = /* @__PURE__ */ buildTransactionFunction(
async (signerAddress: AddressOrEns): Promise<Transaction> => {
const resolvedSignerAddress = await resolveAddress(signerAddress);
return Transaction.fromContractWrapper({
contractWrapper: this.contractWrapper,
method: "setAdmin",
args: [resolvedSignerAddress, true],
});
return await this.sendSignerPermissionRequest(
resolvedSignerAddress,
DEFAULT_PERMISSIONS,
AdminFlag.AddAdmin,
);
},
);
@@ -346,11 +404,11 @@ export class AccountPermissions implements DetectableFeature {
revokeAdminPermissions = /* @__PURE__ */ buildTransactionFunction(
async (signerAddress: AddressOrEns): Promise<Transaction> => {
const resolvedSignerAddress = await resolveAddress(signerAddress);
return Transaction.fromContractWrapper({
contractWrapper: this.contractWrapper,
method: "setAdmin",
args: [resolvedSignerAddress, false],
});
return await this.sendSignerPermissionRequest(
resolvedSignerAddress,
DEFAULT_PERMISSIONS,
AdminFlag.RemoveAdmin,
);
},
);
@@ -395,6 +453,7 @@ export class AccountPermissions implements DetectableFeature {
return await this.sendSignerPermissionRequest(
resolvedSignerAddress,
resolvedPermissions,
AdminFlag.None,
);
},
);
@@ -440,6 +499,7 @@ export class AccountPermissions implements DetectableFeature {
return await this.sendSignerPermissionRequest(
resolvedSignerAddress,
resolvedPermissions,
AdminFlag.None,
);
},
);
@@ -475,12 +535,16 @@ export class AccountPermissions implements DetectableFeature {
);
}
return await this.sendSignerPermissionRequest(resolvedSignerAddress, {
startDate: BigNumber.from(0),
expirationDate: BigNumber.from(0),
approvedCallTargets: [],
nativeTokenLimitPerTransaction: "0",
});
return await this.sendSignerPermissionRequest(
resolvedSignerAddress,
{
startDate: BigNumber.from(0),
expirationDate: BigNumber.from(0),
approvedCallTargets: [],
nativeTokenLimitPerTransaction: "0",
},
AdminFlag.None,
);
},
);
@@ -531,13 +595,17 @@ export class AccountPermissions implements DetectableFeature {
const newTargets = [...permissions.approvedTargets, resolvedTarget];
return await this.sendSignerPermissionRequest(resolvedSignerAddress, {
startDate: BigNumber.from(permissions.startTimestamp),
expirationDate: BigNumber.from(permissions.endTimestamp),
approvedCallTargets: newTargets,
nativeTokenLimitPerTransaction:
permissions.nativeTokenLimitPerTransaction.toString(),
});
return await this.sendSignerPermissionRequest(
resolvedSignerAddress,
{
startDate: BigNumber.from(permissions.startTimestamp),
expirationDate: BigNumber.from(permissions.endTimestamp),
approvedCallTargets: newTargets,
nativeTokenLimitPerTransaction:
permissions.nativeTokenLimitPerTransaction.toString(),
},
AdminFlag.None,
);
},
);
@@ -591,13 +659,17 @@ export class AccountPermissions implements DetectableFeature {
utils.getAddress(approvedTarget) !== utils.getAddress(resolvedTarget),
);
return await this.sendSignerPermissionRequest(resolvedSignerAddress, {
startDate: BigNumber.from(permissions.startTimestamp),
expirationDate: BigNumber.from(permissions.endTimestamp),
approvedCallTargets: newTargets,
nativeTokenLimitPerTransaction:
permissions.nativeTokenLimitPerTransaction.toString(),
});
return await this.sendSignerPermissionRequest(
resolvedSignerAddress,
{
startDate: BigNumber.from(permissions.startTimestamp),
expirationDate: BigNumber.from(permissions.endTimestamp),
approvedCallTargets: newTargets,
nativeTokenLimitPerTransaction:
permissions.nativeTokenLimitPerTransaction.toString(),
},
AdminFlag.None,
);
},
);
@@ -651,14 +723,16 @@ export class AccountPermissions implements DetectableFeature {
const allToMakeAdmin = resolvedSnapshot
.filter((item) => item.makeAdmin)
.map((item) => item.signer);
allAdmins.forEach((admin) => {
allAdmins.forEach(async (admin) => {
if (!allToMakeAdmin.includes(admin)) {
removeAdminData.push(
this.contractWrapper.writeContract.interface.encodeFunctionData(
"setAdmin",
[admin, false],
),
);
const data = (
await this.sendSignerPermissionRequest(
admin,
DEFAULT_PERMISSIONS,
AdminFlag.RemoveAdmin,
)
).encode();
removeAdminData.push(data);
}
});
@@ -672,13 +746,13 @@ export class AccountPermissions implements DetectableFeature {
await Promise.all(
allSigners.map(async (item) => {
if (!allToMakeSigners.includes(item.signer)) {
const data = await this.buildSignerPermissionRequest(item.signer, {
startDate: BigNumber.from(0),
expirationDate: BigNumber.from(0),
approvedCallTargets: [],
nativeTokenLimitPerTransaction: "0",
});
const data = (
await this.sendSignerPermissionRequest(
item.signer,
DEFAULT_PERMISSIONS,
AdminFlag.None,
)
).encode();
removeSignerData.push(data);
}
}),
@@ -687,18 +761,23 @@ export class AccountPermissions implements DetectableFeature {
for (const member of resolvedSnapshot) {
// Add new admin
if (member.makeAdmin) {
addAdminData.push(
this.contractWrapper.writeContract.interface.encodeFunctionData(
"setAdmin",
[member.signer, true],
),
);
const data = (
await this.sendSignerPermissionRequest(
member.signer,
DEFAULT_PERMISSIONS,
AdminFlag.AddAdmin,
)
).encode();
addAdminData.push(data);
} else {
// Add new scoped
const data = await this.buildSignerPermissionRequest(
member.signer,
member.permissions,
);
const data = (
await this.sendSignerPermissionRequest(
member.signer,
member.permissions,
AdminFlag.None,
)
).encode();
addOrUpdateSignerData.push(data);
}
}
+5 -1
View File
@@ -13,8 +13,8 @@ import {
SignerPermissionsInput,
SignerWithPermissions,
} from "../../types";
import { AccountPermissions } from "./account-permissions";
import { buildTransactionFunction } from "../../common/transactions";
import { AccountPermissions } from "./account-permissions";
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- TO BE REMOVED IN V4
export class Account<TContract extends IAccountCore>
@@ -34,6 +34,10 @@ export class Account<TContract extends IAccountCore>
detectContractFeature<IAccountCore>(
this.contractWrapper,
"AccountPermissions",
) ||
detectContractFeature<IAccountCore>(
this.contractWrapper,
"AccountPermissionsV1",
)
) {
return new AccountPermissions(this.contractWrapper);
@@ -100,11 +100,14 @@ export class DropClaimConditions<
public async getActive(
options?: ClaimConditionFetchOptions,
): Promise<ClaimCondition> {
const cc = await this.get();
const metadata = await this.metadata.get();
const [cc, metadata, tokenDecimals] = await Promise.all([
this.get(),
this.metadata.get(),
this.getTokenDecimals(),
]);
return await transformResultToClaimCondition(
cc,
await this.getTokenDecimals(),
tokenDecimals,
this.contractWrapper.getProvider(),
metadata.merkle || {},
this.storage,
@@ -236,12 +239,6 @@ export class DropClaimConditions<
let activeConditionIndex: BigNumber;
let claimCondition: ClaimCondition;
const decimals = await this.getTokenDecimals();
const quantityWithDecimals = utils.parseUnits(
AmountSchema.parse(quantity),
decimals,
);
if (addressToCheck === undefined) {
try {
addressToCheck = await this.contractWrapper.getSignerAddress();
@@ -255,7 +252,15 @@ export class DropClaimConditions<
return [ClaimEligibility.NoWallet];
}
const resolvedAddress = await resolveAddress(addressToCheck);
const [resolvedAddress, decimals] = await Promise.all([
resolveAddress(addressToCheck),
this.getTokenDecimals(),
]);
const quantityWithDecimals = utils.parseUnits(
AmountSchema.parse(quantity),
decimals,
);
try {
claimCondition = await this.getActive();
@@ -487,7 +492,7 @@ export class DropClaimConditions<
}
}
// if not within a browser conetext, check for wallet balance.
// if not within a browser context, check for wallet balance.
// In browser context, let the wallet do that job
if (claimCondition.price.gt(0) && isNode()) {
const totalPrice = claimCondition.price.mul(BigNumber.from(quantity));
@@ -528,8 +533,10 @@ export class DropClaimConditions<
const merkleRoot = claimCondition.merkleRoot;
const merkleRootArray = utils.stripZeros(merkleRoot);
if (merkleRootArray.length > 0) {
const metadata = await this.metadata.get();
const resolvedAddress = await resolveAddress(claimerAddress);
const [metadata, resolvedAddress] = await Promise.all([
this.metadata.get(),
resolveAddress(claimerAddress),
]);
return await fetchSnapshotEntryForAddress(
resolvedAddress,
merkleRoot.toString(),
@@ -820,13 +827,14 @@ export class DropClaimConditions<
decimals = 0,
address?: string,
): Promise<ClaimVerification> {
const addressToClaim = address
? address
: await this.contractWrapper.getSignerAddress();
const [addressToClaim, activeClaimConditions] = await Promise.all([
address ? address : this.contractWrapper.getSignerAddress(),
this.getActive(),
]);
return prepareClaim(
addressToClaim,
quantity,
await this.getActive(),
activeClaimConditions,
async () => (await this.metadata.get()).merkle,
decimals,
this.contractWrapper,
@@ -102,7 +102,10 @@ export class Erc1155Enumerable implements DetectableFeature {
*
* @returns The NFT metadata for all NFTs in the contract.
*/
public async owned(walletAddress?: AddressOrEns): Promise<NFT[]> {
public async owned(
walletAddress?: AddressOrEns,
queryParams?: QueryAllParams,
): Promise<NFT[]> {
const [address, maxId] = await Promise.all([
resolveAddress(
walletAddress || (await this.contractWrapper.getSignerAddress()),
@@ -114,7 +117,7 @@ export class Erc1155Enumerable implements DetectableFeature {
Array.from(Array(maxId.toNumber()).keys()),
]);
const ownedBalances = balances
let ownedBalances = balances
.map((b, i) => {
return {
tokenId: i,
@@ -122,6 +125,11 @@ export class Erc1155Enumerable implements DetectableFeature {
};
})
.filter((b) => b.balance.gt(0));
if (queryParams) {
const start = queryParams?.start || 0;
const count = queryParams?.count || DEFAULT_QUERY_ALL_COUNT;
ownedBalances = ownedBalances.slice(start, start + count);
}
const nfts = (
await Promise.all(
ownedBalances.map((item) => this.erc1155.get(item.tokenId.toString())),
@@ -494,12 +494,16 @@ export class Erc1155<
* @returns The NFT metadata for all NFTs in the contract.
* @twfeature ERC1155Enumerable
*/
public async getOwned(walletAddress?: AddressOrEns): Promise<NFT[]> {
public async getOwned(
walletAddress?: AddressOrEns,
queryParams?: QueryAllParams,
): Promise<NFT[]> {
if (walletAddress) {
walletAddress = await resolveAddress(walletAddress);
}
return assertEnabled(this.query, FEATURE_EDITION_ENUMERABLE).owned(
walletAddress,
queryParams,
);
}
@@ -8,6 +8,10 @@ import type { BaseERC721 } from "../../types/eips";
import { DetectableFeature } from "../interfaces/DetectableFeature";
import type { ContractWrapper } from "./contract-wrapper";
import type { Erc721 } from "./erc-721";
import {
DEFAULT_QUERY_ALL_COUNT,
QueryAllParams,
} from "../../../core/schema/QueryParams";
/**
* List owned ERC721 NFTs
@@ -46,10 +50,19 @@ export class Erc721Enumerable implements DetectableFeature {
* const nfts = await contract.nft.query.owned.all(address);
* ```
* @param walletAddress - the wallet address to query, defaults to the connected wallet
* @param queryParams - optional filtering to only fetch a subset of results.
* @returns The NFT metadata for all NFTs in the contract.
*/
public async all(walletAddress?: AddressOrEns): Promise<NFT[]> {
const tokenIds = await this.tokenIds(walletAddress);
public async all(
walletAddress?: AddressOrEns,
queryParams?: QueryAllParams,
): Promise<NFT[]> {
let tokenIds = await this.tokenIds(walletAddress);
if (queryParams) {
const start = queryParams?.start || 0;
const count = queryParams?.count || DEFAULT_QUERY_ALL_COUNT;
tokenIds = tokenIds.slice(start, start + count);
}
return await Promise.all(
tokenIds.map((tokenId) => this.erc721.get(tokenId.toString())),
);
@@ -95,13 +95,17 @@ export class StandardErc721<
* console.log(nfts);
* ```
* @param walletAddress - the wallet address to query, defaults to the connected wallet
* @param queryParams - optional filtering to only fetch a subset of results.
* @returns The NFT metadata for all NFTs in the contract.
*/
public async getOwned(walletAddress?: AddressOrEns): Promise<NFT[]> {
public async getOwned(
walletAddress?: AddressOrEns,
queryParams?: QueryAllParams,
): Promise<NFT[]> {
if (walletAddress) {
walletAddress = await resolveAddress(walletAddress);
}
return this.erc721.getOwned(walletAddress);
return this.erc721.getOwned(walletAddress, queryParams);
}
/**
+19 -6
View File
@@ -17,7 +17,10 @@ import type {
} from "@thirdweb-dev/contracts-js";
import type { ThirdwebStorage } from "@thirdweb-dev/storage";
import { BigNumber, BigNumberish, constants } from "ethers";
import type { QueryAllParams } from "../../../core/schema/QueryParams";
import {
DEFAULT_QUERY_ALL_COUNT,
type QueryAllParams,
} from "../../../core/schema/QueryParams";
import type {
NFT,
NFTMetadata,
@@ -415,25 +418,35 @@ export class Erc721<
* console.log(nfts);
* ```
* @param walletAddress - the wallet address to query, defaults to the connected wallet
* @param queryParams - optional filtering to only fetch a subset of results.
* @returns The NFT metadata for all NFTs in the contract.
* @twfeature ERC721Supply | ERC721Enumerable
*/
public async getOwned(walletAddress?: AddressOrEns) {
public async getOwned(
walletAddress?: AddressOrEns,
queryParams?: QueryAllParams,
) {
if (walletAddress) {
walletAddress = await resolveAddress(walletAddress);
}
if (this.query?.owned) {
return this.query.owned.all(walletAddress);
return this.query.owned.all(walletAddress, queryParams);
} else {
const [address, allOwners] = await Promise.all([
walletAddress || this.contractWrapper.getSignerAddress(),
this.getAllOwners(),
]);
let ownedTokens = (allOwners || []).filter(
(i) => address?.toLowerCase() === i.owner?.toLowerCase(),
);
if (queryParams) {
const start = queryParams?.start || 0;
const count = queryParams?.count || DEFAULT_QUERY_ALL_COUNT;
ownedTokens = ownedTokens.slice(start, start + count);
}
return await Promise.all(
(allOwners || [])
.filter((i) => address?.toLowerCase() === i.owner?.toLowerCase())
.map((i) => this.get(i.tokenId)),
ownedTokens.map(async (i) => this.get(i.tokenId)),
);
}
}
@@ -8,6 +8,10 @@ import type { BaseERC721 } from "../../types/eips";
import { DetectableFeature } from "../interfaces/DetectableFeature";
import type { ContractWrapper } from "./contract-wrapper";
import type { Erc721 } from "./erc-721";
import {
DEFAULT_QUERY_ALL_COUNT,
QueryAllParams,
} from "../../../core/schema/QueryParams";
/**
* List owned ERC721 NFTs
@@ -50,8 +54,16 @@ export class Erc721AQueryable implements DetectableFeature {
* @param walletAddress - the wallet address to query, defaults to the connected wallet
* @returns The NFT metadata for all NFTs in the contract.
*/
public async all(walletAddress?: AddressOrEns): Promise<NFT[]> {
const tokenIds = await this.tokenIds(walletAddress);
public async all(
walletAddress?: AddressOrEns,
queryParams?: QueryAllParams,
): Promise<NFT[]> {
let tokenIds = await this.tokenIds(walletAddress);
if (queryParams) {
const start = queryParams?.start || 0;
const count = queryParams?.count || DEFAULT_QUERY_ALL_COUNT;
tokenIds = tokenIds.slice(start, start + count);
}
return await Promise.all(
tokenIds.map((tokenId) => this.erc721.get(tokenId.toString())),
);
@@ -15,7 +15,6 @@ import { generateExtensionFunctions } from "../../common/plugin/generatePluginFu
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,
@@ -26,9 +25,12 @@ import {
} 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";
import {
ExtensionAddedEvent,
ExtensionRemovedEvent,
ExtensionReplacedEvent,
} from "@thirdweb-dev/contracts-js/dist/declarations/src/BaseRouter";
export class ExtensionManager implements DetectableFeature {
featureName = FEATURE_DYNAMIC_CONTRACT.name;
+1 -1
View File
@@ -61,7 +61,7 @@ export class ContractFactory extends ContractWrapper<TWFactory> {
[VoteInitializer.contractType]: 1,
[SplitInitializer.contractType]: 1,
[MarketplaceInitializer.contractType]: 2,
[MarketplaceV3Initializer.contractType]: 1,
[MarketplaceV3Initializer.contractType]: 3,
[PackInitializer.contractType]: 2,
};
@@ -285,12 +285,11 @@ export class MarketplaceAuction {
async (
listings: NewAuctionListing[],
): Promise<Transaction<TransactionResultWithId[]>> => {
const data = await Promise.all(
listings.map(async (listing) => {
const tx = await this.createListing.prepare(listing);
return tx.encode();
}),
);
const data = (
await Promise.all(
listings.map((listing) => this.createListing.prepare(listing)),
)
).map((tx) => tx.encode());
return Transaction.fromContractWrapper({
contractWrapper: this.contractWrapper,
@@ -576,7 +575,7 @@ export class MarketplaceAuction {
const [currentBidBufferBps, winningBid, listing] = await Promise.all([
this.getBidBufferBps(),
this.getWinningBid(listingId),
await this.validateListing(BigNumber.from(listingId)),
this.validateListing(BigNumber.from(listingId)),
]);
const currentBidOrReservePrice = winningBid
@@ -239,12 +239,11 @@ export class MarketplaceDirect {
async (
listings: NewDirectListing[],
): Promise<Transaction<TransactionResultWithId[]>> => {
const data = await Promise.all(
listings.map(async (listing) => {
const tx = await this.createListing.prepare(listing);
return tx.encode();
}),
);
const data = (
await Promise.all(
listings.map((listing) => this.createListing.prepare(listing)),
)
).map((tx) => tx.encode());
return Transaction.fromContractWrapper({
contractWrapper: this.contractWrapper,
@@ -414,12 +414,11 @@ export class MarketplaceV3DirectListings<TContract extends DirectListingsLogic>
async (
listings: DirectListingInputParams[],
): Promise<Transaction<TransactionResultWithId[]>> => {
const data = await Promise.all(
listings.map(async (listing) => {
const tx = await this.createListing.prepare(listing);
return tx.encode();
}),
);
const data = (
await Promise.all(
listings.map((listing) => this.createListing.prepare(listing)),
)
).map((tx) => tx.encode());
return Transaction.fromContractWrapper({
contractWrapper: this
@@ -404,12 +404,11 @@ export class MarketplaceV3EnglishAuctions<
async (
listings: EnglishAuctionInputParams[],
): Promise<Transaction<TransactionResultWithId[]>> => {
const data = await Promise.all(
listings.map(async (listing) => {
const tx = await this.createAuction.prepare(listing);
return tx.encode();
}),
);
const data = (
await Promise.all(
listings.map((listing) => this.createAuction.prepare(listing)),
)
).map((tx) => tx.encode());
return Transaction.fromContractWrapper({
contractWrapper: this
+1 -1
View File
@@ -890,7 +890,7 @@ function addChainToSupportedChains(
options = {
...options,
// @ts-expect-error - we know that the network is assignable despite the readonly mismatch
supportedChains: [...(options?.supportedChains || []), network],
supportedChains: [network, ...(options?.supportedChains || [])],
};
}
return options;
+38 -3
View File
@@ -1,4 +1,7 @@
import type { IAccountPermissions } from "@thirdweb-dev/contracts-js";
import type {
IAccountPermissions,
IAccountPermissions_V1,
} from "@thirdweb-dev/contracts-js";
import { BigNumber, BytesLike } from "ethers";
import { z } from "zod";
import { AmountSchema } from "../../core/schema/shared";
@@ -12,12 +15,27 @@ export type SignerPermissions = {
approvedCallTargets: string[];
};
export enum AdminFlag {
None = 0,
AddAdmin = 1,
RemoveAdmin = 2,
}
export const DEFAULT_PERMISSIONS = {
// eslint-disable-next-line better-tree-shaking/no-top-level-side-effects
startDate: BigNumber.from(0),
// eslint-disable-next-line better-tree-shaking/no-top-level-side-effects
expirationDate: BigNumber.from(0),
approvedCallTargets: [],
nativeTokenLimitPerTransaction: "0",
};
export const SignerPermissionsSchema = /* @__PURE__ */ (() =>
z.object({
startDate: StartDateSchema,
expirationDate: EndDateSchema,
nativeTokenLimitPerTransaction: AmountSchema.default(0),
approvedCallTargets: z.array(AddressOrEnsSchema),
approvedCallTargets: z.union([z.array(AddressOrEnsSchema), z.literal("*")]),
}))();
export type SignerPermissionsInput = z.input<typeof SignerPermissionsSchema>;
@@ -48,7 +66,12 @@ export type SignedSignerPermissionsPayload = {
signature: BytesLike;
};
export const SignerPermissionRequest = [
export type SignedSignerPermissionsPayloadV1 = {
payload: IAccountPermissions_V1.SignerPermissionRequestStruct;
signature: BytesLike;
};
export const SignerPermissionRequestV1 = [
{ name: "signer", type: "address" },
{ name: "approvedTargets", type: "address[]" },
{ name: "nativeTokenLimitPerTransaction", type: "uint256" },
@@ -58,3 +81,15 @@ export const SignerPermissionRequest = [
{ name: "reqValidityEndTimestamp", type: "uint128" },
{ name: "uid", type: "bytes32" },
];
export const SignerPermissionRequest = [
{ name: "signer", type: "address" },
{ name: "isAdmin", type: "uint8" },
{ name: "approvedTargets", type: "address[]" },
{ name: "nativeTokenLimitPerTransaction", type: "uint256" },
{ name: "permissionStartTimestamp", type: "uint128" },
{ name: "permissionEndTimestamp", type: "uint128" },
{ name: "reqValidityStartTimestamp", type: "uint128" },
{ name: "reqValidityEndTimestamp", type: "uint128" },
{ name: "uid", type: "bytes32" },
];
+112 -57
View File
@@ -9,7 +9,6 @@ import {
} from "../../src/evm";
import { ContractFactory, utils } from "ethers";
import EntrypointArtifact from "./mock/EntryPoint.json";
import AccountFactoryArtifact from "./mock/AccountFactory.json";
// Target ABIs
import IAccountCoreAbi from "@thirdweb-dev/contracts-js/dist/abis/IAccountCore.json";
@@ -17,7 +16,12 @@ import {
deployContractAndUploadMetadata,
mockUploadContractMetadata,
} from "./utils";
import { IAccountCore, IAccountFactory } from "@thirdweb-dev/contracts-js";
import {
AccountFactory__factory,
IAccountCore,
IAccountFactory,
} from "@thirdweb-dev/contracts-js";
import { AddressZero } from "../../src/evm/constants/addresses/AddressZero";
describe("Accounts with account factory", function () {
let accountFactory: AccountFactory<IAccountFactory>;
@@ -57,8 +61,8 @@ describe("Accounts with account factory", function () {
adminWallet,
).deploy();
const factoryAddress = await deployContractAndUploadMetadata(
AccountFactoryArtifact.abi,
AccountFactoryArtifact.bytecode.object,
AccountFactory__factory.abi,
AccountFactory__factory.bytecode,
adminWallet,
[entrypoint.address],
);
@@ -124,8 +128,8 @@ describe("Accounts with account factory", function () {
accountAddress,
IAccountCoreAbi,
);
const account = (await sdk.getContract(accountAddress)).account;
const allAdmins = await account.getAllAdmins();
const acc = (await sdk.getContract(accountAddress)).account;
const allAdmins = await acc.getAllAdmins();
assert.isTrue(
allAdmins.length === 1,
"Account should only have one admin.",
@@ -139,8 +143,9 @@ describe("Accounts with account factory", function () {
"Correct admin for account.",
);
const associatedAccounts =
await accountFactory.getAssociatedAccounts(admin);
const associatedAccounts = await accountFactory.getAssociatedAccounts(
admin,
);
assert.isTrue(
associatedAccounts.length === 1,
@@ -185,65 +190,65 @@ describe("Accounts with account factory", function () {
account = (await sdk.getContract(accountAddress)).account;
});
// it("Should be able to add another admin to the account.", async () => {
// assert.isFalse(
// await account.isAdmin(signer1Wallet.address),
// "New signer1 should not be an admin on the account.",
// );
it("Should be able to add another admin to the account.", async () => {
assert.isFalse(
await account.isAdmin(signer1Wallet.address),
"New signer1 should not be an admin on the account.",
);
// await account.grantAdminPermissions(signer1Wallet.address);
await account.grantAdminPermissions(signer1Wallet.address);
// assert.isTrue(
// await account.isAdmin(signer1Wallet.address),
// "New signer1 should be an admin on the account.",
// );
assert.isTrue(
await account.isAdmin(signer1Wallet.address),
"New signer1 should be an admin on the account.",
);
// const isAdmin = (await account.getAllAdmins()).includes(
// utils.getAddress(signer1Wallet.address),
// );
const isAdmin = (await account.getAllAdmins()).includes(
utils.getAddress(signer1Wallet.address),
);
// assert.isTrue(isAdmin, "New signer1 should be an admin on the account.");
assert.isTrue(isAdmin, "New signer1 should be an admin on the account.");
// assert.isTrue(
// (
// await accountFactory.getAssociatedAccounts(signer1Wallet.address)
// ).includes(account.getAddress()),
// "Wallet is an associated account of the signer.",
// );
// });
assert.isTrue(
(
await accountFactory.getAssociatedAccounts(signer1Wallet.address)
).includes(account.getAddress()),
"Wallet is an associated account of the signer.",
);
});
// it("Should be able to remove an admin from the account.", async () => {
// await account.grantAdminPermissions(signer1Wallet.address);
// assert.isTrue(
// await account.isAdmin(signer1Wallet.address),
// "New signer1 should be an admin on the account.",
// );
it("Should be able to remove an admin from the account.", async () => {
await account.grantAdminPermissions(signer1Wallet.address);
assert.isTrue(
await account.isAdmin(signer1Wallet.address),
"New signer1 should be an admin on the account.",
);
// await account.revokeAdminPermissions(signer1Wallet.address);
await account.revokeAdminPermissions(signer1Wallet.address);
// assert.isFalse(
// await account.isAdmin(signer1Wallet.address),
// "New signer1 should not be an admin on the account.",
// );
assert.isFalse(
await account.isAdmin(signer1Wallet.address),
"New signer1 should not be an admin on the account.",
);
// assert.isFalse(
// (await account.getAllSigners())
// .map((result) => utils.getAddress(result.signer))
// .includes(signer1Wallet.address),
// "New signer1 should not be an admin on the account.",
// );
assert.isFalse(
(await account.getAllSigners())
.map((result) => utils.getAddress(result.signer))
.includes(signer1Wallet.address),
"New signer1 should not be an admin on the account.",
);
// assert.isFalse(
// (await account.getAllAdmins()).includes(signer1Wallet.address),
// "New signer1 is not an associated signer of the account.",
// );
// assert.isFalse(
// (
// await accountFactory.getAssociatedAccounts(signer1Wallet.address)
// ).includes(account.getAddress()),
// "Wallet is not an associated account of the signer.",
// );
// });
assert.isFalse(
(await account.getAllAdmins()).includes(signer1Wallet.address),
"New signer1 is not an associated signer of the account.",
);
assert.isFalse(
(
await accountFactory.getAssociatedAccounts(signer1Wallet.address)
).includes(account.getAddress()),
"Wallet is not an associated account of the signer.",
);
});
it("Should be able to grant restricted access to a new signer.", async () => {
const signersWithRestrictions = await account.getAllSigners();
@@ -306,6 +311,56 @@ describe("Accounts with account factory", function () {
);
});
it("Should be able to restrict approved targets.", async () => {
// Grant access
await account.grantPermissions(signer1Wallet.address, {
nativeTokenLimitPerTransaction: "1",
approvedCallTargets: [adminWallet.address],
});
const s = await account.getAllSigners();
const restrictions = s.find(
(result) =>
utils.getAddress(result.signer) ===
utils.getAddress(signer1Wallet.address),
)?.permissions as SignerPermissions;
assert.strictEqual(
restrictions.approvedCallTargets.length,
1,
"New signer1 should have one approved call targets.",
);
assert.strictEqual(
restrictions.approvedCallTargets[0],
adminWallet.address,
"New signer1 should have the expected approved call targets.",
);
});
it("Should be able to approve any target.", async () => {
// Grant access
await account.grantPermissions(signer1Wallet.address, {
nativeTokenLimitPerTransaction: "1",
approvedCallTargets: "*",
});
const s = await account.getAllSigners();
const restrictions = s.find(
(result) =>
utils.getAddress(result.signer) ===
utils.getAddress(signer1Wallet.address),
)?.permissions as SignerPermissions;
assert.strictEqual(
restrictions.approvedCallTargets.length,
1,
"New signer1 should have one approved call targets.",
);
assert.strictEqual(
restrictions.approvedCallTargets[0],
AddressZero,
"New signer1 should have the expected approved call targets.",
);
});
it("Should not be able to grant restricted access to a signer who already has access.", async () => {
// Grant access to signer1
await account.grantPermissions(signer1Wallet.address, {
+30 -46
View File
@@ -1,4 +1,3 @@
import { ThirdwebSDK } from "../../src/evm";
import { SmartContract } from "../../src/evm/contracts/smart-contract";
import {
extendedMetadataMock,
@@ -14,19 +13,19 @@ import {
tieredDropBytecode,
tieredDropCompilerMetadata,
} from "./mock/tieredDropMetadata";
import {
DirectListingsLogic__factory,
EnglishAuctionsLogic__factory,
OffersLogic__factory,
} from "@thirdweb-dev/contracts-js";
import {
marketplaceV3Bytecode,
marketplaceV3CompilerMetadata,
} from "./mock/marketplaceV3Metadata";
import {
pluginMapBytecode,
pluginMapCompilerMetadata,
} from "./mock/pluginMapMetadata";
directListingsBytecode,
directListingsCompilerMetadata,
} from "./mock/directListingsMetadata";
import { offersBytecode, offersCompilerMetadata } from "./mock/offersMetadata";
import {
englishAuctionsBytecode,
englishAuctionsCompilerMetadata,
} from "./mock/englishAuctionsMetadata";
const itIf = (condition: boolean) => (condition ? it : it.skip);
@@ -112,7 +111,7 @@ describe("Any EVM Keyless Deploy", async () => {
{
...extendedMetadataMock,
deployType: "autoFactory",
routerType: "plugin",
routerType: "dynamic",
defaultExtensions: [
{
extensionName: "DirectListingsLogic",
@@ -136,7 +135,7 @@ describe("Any EVM Keyless Deploy", async () => {
},
publisher: await adminWallet.getAddress(),
},
"ipfs://QmaE7HVb4nwHp96omxmyPc7nKPZqsGB7ZbW6psznN6cSMk",
"ipfs://QmWrPyWEMDjnR3a5zp1rJHzU1EUGx6DR2hPBVHAkGGjhgm/0",
);
transactionCount = (await sdk.deployer.getTransactionsForDeploy(publishUri))
.length;
@@ -145,7 +144,7 @@ describe("Any EVM Keyless Deploy", async () => {
publishUri,
[
walletAddress, // defaultAdmin
"ipfs://QmP2QPzc81zg5rqhU9u7cDeSyD4aZH8RdVF8Nuh6rCCgxV", // contractUri
"ipfs://QmWrPyWEMDjnR3a5zp1rJHzU1EUGx6DR2hPBVHAkGGjhgm/0", // contractUri
[], // trustedForwarders
walletAddress, // platform fee recipient
0, // platform fee bps
@@ -167,27 +166,10 @@ describe("Any EVM Keyless Deploy", async () => {
before(async () => {
[adminWallet, claimerWallet] = signers;
await mockUploadMetadataWithBytecode(
"PluginMap",
pluginMapCompilerMetadata.output.abi,
pluginMapBytecode,
"",
{
...extendedMetadataMock,
deployType: "standard",
networksForDeployment: {
allNetworks: true,
networksEnabled: [],
},
publisher: adminWallet.address,
},
"ipfs://QmQHyopzH41En8KxvYrmATLCyuTMu8Hv5TEaxLc7gWJkLm/0",
);
await mockUploadMetadataWithBytecode(
"DirectListingsLogic",
DirectListingsLogic__factory.abi,
DirectListingsLogic__factory.bytecode,
directListingsCompilerMetadata.output.abi,
directListingsBytecode,
"",
{
...extendedMetadataMock,
@@ -203,8 +185,8 @@ describe("Any EVM Keyless Deploy", async () => {
await mockUploadMetadataWithBytecode(
"OffersLogic",
OffersLogic__factory.abi,
OffersLogic__factory.bytecode,
offersCompilerMetadata.output.abi,
offersBytecode,
"",
{
...extendedMetadataMock,
@@ -220,8 +202,8 @@ describe("Any EVM Keyless Deploy", async () => {
await mockUploadMetadataWithBytecode(
"EnglishAuctionsLogic",
EnglishAuctionsLogic__factory.abi,
EnglishAuctionsLogic__factory.bytecode,
englishAuctionsCompilerMetadata.output.abi,
englishAuctionsBytecode,
"",
{
...extendedMetadataMock,
@@ -256,20 +238,22 @@ describe("Any EVM Keyless Deploy", async () => {
});
// can only work if secret key is set, skip otherwise
itIf(process.env.TW_SECRET_KEY)("deploy marketplacev3", async () => {
it("deploy marketplacev3", async () => {
notificationCounter = 0;
transactionCount = 0;
const marketplace = await deployMarketplaceV3();
expect(notificationCounter).to.greaterThanOrEqual(12);
expect(transactionCount).to.greaterThanOrEqual(6);
let plugins = await marketplace.call("getAllPlugins");
let allPlugins = plugins.map((item: any) => item.pluginAddress);
let pluginsAddresses = Array.from(new Set(allPlugins));
let extensions = await marketplace.call("getAllExtensions");
let allExtensions = extensions.map(
(item: any) => item.metadata.implementation,
);
let extensionAddresses = Array.from(new Set(allExtensions));
expect(pluginsAddresses.length).to.equal(3);
expect(extensionAddresses.length).to.equal(3);
pluginsAddresses.forEach(async (address) => {
extensionAddresses.forEach(async (address) => {
expect(address).to.not.equal(ethers.constants.AddressZero);
const code = await adminWallet.provider?.getCode(address as string);
expect(code?.length).to.be.greaterThan(2);
@@ -282,13 +266,13 @@ describe("Any EVM Keyless Deploy", async () => {
expect(notificationCounter).to.equal(2);
expect(transactionCount).to.equal(1);
plugins = await marketplace2.call("getAllPlugins");
allPlugins = plugins.map((item: any) => item.pluginAddress);
pluginsAddresses = Array.from(new Set(allPlugins));
extensions = await marketplace2.call("getAllExtensions");
allExtensions = extensions.map((item: any) => item.metadata.implementation);
extensionAddresses = Array.from(new Set(allExtensions));
expect(pluginsAddresses.length).to.equal(3);
expect(extensionAddresses.length).to.equal(3);
pluginsAddresses.forEach(async (address) => {
extensionAddresses.forEach(async (address) => {
expect(address).to.not.equal(ethers.constants.AddressZero);
const code = await adminWallet.provider?.getCode(address as string);
expect(code?.length).to.be.greaterThan(2);
+58 -24
View File
@@ -63,12 +63,20 @@ import {
import { ThirdwebStorage } from "@thirdweb-dev/storage";
import { constants, ContractInterface, ethers } from "ethers";
import hardhat from "hardhat";
import { generatePluginFunctions } from "../../src/evm/common/plugin/generatePluginFunctions";
import {
generateExtensionFunctions,
generatePluginFunctions,
} from "../../src/evm/common/plugin/generatePluginFunctions";
import { mockUploadMetadataWithBytecode } from "./utils";
import {
bytecode as TWCloneFactoryBytecode,
abi as TWCloneFactoryAbi,
} from "./metadata/TWCloneFactory";
import {
Extension,
ExtensionFunction,
ExtensionMetadata,
} from "../../src/evm/types/extensions";
// it's there, trust me bro
const hardhatEthers = (hardhat as any).ethers;
@@ -222,7 +230,7 @@ export const mochaHooks = {
if (contractType === "custom" || contractType === "marketplace-v3") {
continue;
}
let factories: any[] = [];
const factories: any[] = [];
switch (contractType) {
case TokenInitializer.contractType:
factories.push(TokenERC20__factory);
@@ -359,48 +367,68 @@ async function setupMultichainRegistry(
// Setup marketplace-v3 for tests
async function setupMarketplaceV3(): Promise<string> {
const extensions: Extension[] = [];
// Direct Listings
const directListingsPluginAddress = await deployContractAndUploadMetadata(
const directListingsExtensionAddress = await deployContractAndUploadMetadata(
DirectListingsLogic__factory.abi,
DirectListingsLogic__factory.bytecode,
signer,
[mock_weth_address],
);
const pluginsDirectListings: Plugin[] = generatePluginFunctions(
directListingsPluginAddress,
AbiSchema.parse(DirectListingsLogic__factory.abi),
);
const directListingsFunctions: ExtensionFunction[] =
generateExtensionFunctions(
AbiSchema.parse(DirectListingsLogic__factory.abi),
);
const directListingsExtensionMetadata: ExtensionMetadata = {
name: "DirectListingsLogic",
metadataURI: "",
implementation: directListingsExtensionAddress,
};
extensions.push({
metadata: directListingsExtensionMetadata,
functions: directListingsFunctions,
});
// English Auctions
const englishAuctionPluginAddress = await deployContractAndUploadMetadata(
const englishAuctionExtensionAddress = await deployContractAndUploadMetadata(
EnglishAuctionsLogic__factory.abi,
EnglishAuctionsLogic__factory.bytecode,
signer,
[mock_weth_address],
);
const pluginsEnglishAuctions: Plugin[] = generatePluginFunctions(
englishAuctionPluginAddress,
AbiSchema.parse(EnglishAuctionsLogic__factory.abi),
);
const englishAuctionsFunctions: ExtensionFunction[] =
generateExtensionFunctions(
AbiSchema.parse(EnglishAuctionsLogic__factory.abi),
);
const englishAuctionsExtensionMetadata: ExtensionMetadata = {
name: "EnglishAuctionsLogic",
metadataURI: "",
implementation: englishAuctionExtensionAddress,
};
extensions.push({
metadata: englishAuctionsExtensionMetadata,
functions: englishAuctionsFunctions,
});
// Offers
const offersLogicPluginAddress = await deployContractAndUploadMetadata(
const offersLogicExtensionAddress = await deployContractAndUploadMetadata(
OffersLogic__factory.abi,
OffersLogic__factory.bytecode,
signer,
);
const pluginsOffers: Plugin[] = generatePluginFunctions(
offersLogicPluginAddress,
const offersFunctions: ExtensionFunction[] = generateExtensionFunctions(
AbiSchema.parse(OffersLogic__factory.abi),
);
// Map
const pluginMapAddress = await deployContractAndUploadMetadata(
PluginMap__factory.abi,
PluginMap__factory.bytecode,
signer,
[[...pluginsDirectListings, ...pluginsEnglishAuctions, ...pluginsOffers]],
);
const offersExtensionMetadata: ExtensionMetadata = {
name: "OffersLogic",
metadataURI: "",
implementation: offersLogicExtensionAddress,
};
extensions.push({
metadata: offersExtensionMetadata,
functions: offersFunctions,
});
// Router
const royaltyEngineAddress = constants.AddressZero;
@@ -408,7 +436,13 @@ async function setupMarketplaceV3(): Promise<string> {
MarketplaceV3__factory.abi,
MarketplaceV3__factory.bytecode,
signer,
[pluginMapAddress, royaltyEngineAddress],
[
{
extensions: extensions,
royaltyEngineAddress: royaltyEngineAddress,
nativeTokenWrapper: mock_weth_address,
},
],
"MarketplaceV3",
);
return marketplaceV3Address;
+96
View File
@@ -287,4 +287,100 @@ describe("Edition Contract", async () => {
expect(nfts[0].supply).to.be.equal("5");
expect(nfts[0].metadata.id).to.be.equal("1");
});
it("should respect pagination for getOwned (for edition.ts)", async () => {
const nfts = [] as { metadata: { name: string }; supply: number }[];
for (let i = 0; i < 10; i++) {
nfts.push({
metadata: { name: `Test${i}` },
supply: 10,
});
}
await bundleContract.mintBatch(nfts);
const total = await bundleContract.getTotalCount();
expect(total.toNumber()).to.eq(10);
const nftPage1 = await bundleContract.getOwned(adminWallet.address, {
count: 2,
start: 0,
});
expect(nftPage1).to.be.an("array").length(2);
expect(nftPage1[0].metadata.id).to.eq("0");
expect(nftPage1[1].metadata.id).to.eq("1");
const nftPage2 = await bundleContract.getOwned(adminWallet.address, {
count: 3,
start: 2,
});
expect(nftPage2).to.be.an("array").length(3);
expect(nftPage2[0].metadata.id).to.eq("2");
expect(nftPage2[1].metadata.id).to.eq("3");
expect(nftPage2[2].metadata.id).to.eq("4");
});
it("should respect pagination for getOwned (for erc-1155.ts)", async () => {
const nfts = [] as { metadata: { name: string }; supply: number }[];
for (let i = 0; i < 10; i++) {
nfts.push({
metadata: { name: `Test${i}` },
supply: 10,
});
}
await bundleContract.mintBatch(nfts);
const total = await bundleContract.getTotalCount();
expect(total.toNumber()).to.eq(10);
const nftPage1 = await bundleContract.erc1155.getOwned(
adminWallet.address,
{
count: 2,
start: 0,
},
);
expect(nftPage1).to.be.an("array").length(2);
expect(nftPage1[0].metadata.id).to.eq("0");
expect(nftPage1[1].metadata.id).to.eq("1");
const nftPage2 = await bundleContract.erc1155.getOwned(
adminWallet.address,
{
count: 3,
start: 2,
},
);
expect(nftPage2).to.be.an("array").length(3);
expect(nftPage2[0].metadata.id).to.eq("2");
expect(nftPage2[1].metadata.id).to.eq("3");
expect(nftPage2[2].metadata.id).to.eq("4");
});
it("getOwned should return all items when queryParams.count is greater than the total supply (edition.ts)", async () => {
const nfts = [] as { metadata: { name: string }; supply: number }[];
for (let i = 0; i < 10; i++) {
nfts.push({
metadata: { name: `Test${i}` },
supply: 10,
});
}
await bundleContract.mintBatch(nfts);
const items = await bundleContract.getOwned(undefined, {
count: 1000,
start: 0,
});
expect(items).to.be.an("array").length(nfts.length);
});
it("getOwned should return all items when queryParams.count is greater than the total supply (erc-1155.ts)", async () => {
const nfts = [] as { metadata: { name: string }; supply: number }[];
for (let i = 0; i < 10; i++) {
nfts.push({
metadata: { name: `Test${i}` },
supply: 10,
});
}
await bundleContract.mintBatch(nfts);
const items = await bundleContract.getOwned(undefined, {
count: 1000,
start: 0,
});
expect(items).to.be.an("array").length(nfts.length);
});
});

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