Compare commits

..
Author SHA1 Message Date
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
117 changed files with 6644 additions and 1004 deletions
+14
View File
@@ -1,5 +1,19 @@
# @thirdweb-dev/auth
## 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.3",
"main": "dist/thirdweb-dev-auth.cjs.js",
"module": "dist/thirdweb-dev-auth.esm.js",
"browser": {
+18
View File
@@ -1,5 +1,23 @@
# thirdweb
## 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.3",
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/cli",
"author": "thirdweb eng <[email protected]>",
"license": "Apache-2.0",
+6
View File
@@ -1,5 +1,11 @@
# @thirdweb-dev/contracts-js
## 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.14",
"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.2-3"
},
"devDependencies": {
"@babel/preset-env": "^7.22.9",
+20
View File
@@ -1,5 +1,25 @@
# @thirdweb-dev/react-core
## 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.3",
"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,9 @@
# @thirdweb-dev/react-native-compat
## 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.8",
"author": "thirdweb eng <[email protected]>",
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/react-native-compat",
"license": "Apache-2.0",
+50
View File
@@ -1,5 +1,55 @@
# @thirdweb-dev/react-native
## 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.8",
"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(() => {
+24
View File
@@ -1,5 +1,29 @@
# @thirdweb-dev/react
## 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.3",
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/react",
"author": "thirdweb eng <[email protected]>",
"license": "Apache-2.0",
+3 -3
View File
@@ -36,7 +36,7 @@ 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"
variant="outline"
onPaste={(e) => {
@@ -44,7 +44,7 @@ export function OTPInput(props: {
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 +100,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>
+27
View File
@@ -1,5 +1,32 @@
# @thirdweb-dev/sdk
## 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.2",
"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) {
@@ -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";
+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);
}
/**
@@ -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())),
);
+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;
+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);
+57 -23
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;
@@ -359,48 +367,68 @@ async function setupMultichainRegistry(
// Setup marketplace-v3 for tests
async function setupMarketplaceV3(): Promise<string> {
let 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);
});
});
+28 -19
View File
@@ -46,11 +46,11 @@ describe("Marketplace V3", async () => {
MarketplaceV3Initializer.contractType,
{
name: "Test Marketplace",
platform_fee_recipient: adminWallet.address,
},
),
"marketplace-v3",
);
dummyNftContract = await sdk.getNFTCollection(
await sdk.deployer.deployBuiltInContract(
NFTCollectionInitializer.contractType,
@@ -228,8 +228,9 @@ describe("Marketplace V3", async () => {
isReservedListing: false,
});
const listing =
await marketplaceContract.directListings.getListing(listingId);
const listing = await marketplaceContract.directListings.getListing(
listingId,
);
expect(listing.currencyContractAddress.toLowerCase()).to.equal(
NATIVE_TOKEN_ADDRESS.toLowerCase(),
);
@@ -374,10 +375,12 @@ describe("Marketplace V3", async () => {
});
it("should return correct listing", async () => {
const listingOne =
await marketplaceContract.directListings.getListing(directListingOneId);
const listingTwo =
await marketplaceContract.directListings.getListing(directListingTwoId);
const listingOne = await marketplaceContract.directListings.getListing(
directListingOneId,
);
const listingTwo = await marketplaceContract.directListings.getListing(
directListingTwoId,
);
assert.equal(listingOne.tokenId.toString(), "0");
assert.equal(listingOne.asset.id.toString(), "0");
assert.equal(listingOne.asset.name, "Test 0");
@@ -463,8 +466,9 @@ describe("Marketplace V3", async () => {
});
it("should correctly cancel a direct listing", async () => {
const listing =
await marketplaceContract.directListings.getListing(directListingId);
const listing = await marketplaceContract.directListings.getListing(
directListingId,
);
assert.equal(listing.quantity.toString(), "1");
await marketplaceContract.directListings.cancelListing(directListingId);
@@ -646,10 +650,12 @@ describe("Marketplace V3", async () => {
});
it("should return correct auction", async () => {
const auctionOne =
await marketplaceContract.englishAuctions.getAuction(auctionOneId);
const auctionTwo =
await marketplaceContract.englishAuctions.getAuction(auctionTwoId);
const auctionOne = await marketplaceContract.englishAuctions.getAuction(
auctionOneId,
);
const auctionTwo = await marketplaceContract.englishAuctions.getAuction(
auctionTwoId,
);
assert.equal(auctionOne.tokenId.toString(), "2");
assert.equal(auctionOne.asset.id.toString(), "2");
assert.equal(auctionOne.asset.name, "Test 3");
@@ -692,8 +698,9 @@ describe("Marketplace V3", async () => {
await sdk.updateSignerOrProvider(bobWallet);
await marketplaceContract.englishAuctions.makeBid(auctionId, 0.3);
let winningBid =
await marketplaceContract.englishAuctions.getWinningBid(auctionId);
let winningBid = await marketplaceContract.englishAuctions.getWinningBid(
auctionId,
);
assert.equal(winningBid?.bidderAddress, bobWallet.address);
assert.equal(
@@ -706,8 +713,9 @@ describe("Marketplace V3", async () => {
await sdk.updateSignerOrProvider(samWallet);
await marketplaceContract.englishAuctions.makeBid(auctionId, 0.6);
winningBid =
await marketplaceContract.englishAuctions.getWinningBid(auctionId);
winningBid = await marketplaceContract.englishAuctions.getWinningBid(
auctionId,
);
assert.equal(winningBid?.bidderAddress, samWallet.address);
assert.equal(
winningBid?.bidAmount.toString(),
@@ -1095,8 +1103,9 @@ describe("Marketplace V3", async () => {
it("should not allow you to update an active direct listing", async () => {
const pricePerToken = ethers.utils.parseUnits("0.1");
const directListing =
await marketplaceContract.directListings.getListing(directListingId);
const directListing = await marketplaceContract.directListings.getListing(
directListingId,
);
assert.equal(
directListing.pricePerToken.toString(),
pricePerToken.toString(),
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+66
View File
@@ -285,4 +285,70 @@ describe("NFT Contract", async () => {
expect(records[0].tokenId).to.eq(1);
expect(records[1].tokenId).to.eq(2);
});
it("should respect pagination for getOwned (erc-721-standard.ts)", async () => {
const _tokenIds: number[] = Array.from({ length: 11 }, (_, index) => index); // [0, 1, ... 10]
const metadata = _tokenIds.map((num) => ({ name: `Test${num}` }));
await nftContract.mintBatch(metadata);
const nftPage1 = await nftContract.getOwned(undefined, {
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 nftContract.getOwned(undefined, {
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 (erc-721.ts)", async () => {
const _tokenIds: number[] = Array.from({ length: 11 }, (_, index) => index); // [0, 1, ... 10]
const metadata = _tokenIds.map((num) => ({ name: `Test${num}` }));
await nftContract.mintBatch(metadata);
const nftPage1 = await nftContract.erc721.getOwned(undefined, {
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 nftContract.erc721.getOwned(undefined, {
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 item when queryParams.count is greater than the total supply (erc-721-standard.ts)", async () => {
const _tokenIds: number[] = Array.from({ length: 11 }, (_, index) => index); // [0, 1, ... 10]
const metadata = _tokenIds.map((num) => ({ name: `Test${num}` }));
await nftContract.mintBatch(metadata);
const nfts = await nftContract.getOwned(undefined, {
count: 1000,
start: 0,
});
expect(nfts).to.be.an("array").length(_tokenIds.length);
});
it("getOwned should return all items when queryParams.count is greater than the total supply (erc-721.ts)", async () => {
const _tokenIds: number[] = Array.from({ length: 11 }, (_, index) => index); // [0, 1, ... 10]
const metadata = _tokenIds.map((num) => ({ name: `Test${num}` }));
await nftContract.mintBatch(metadata);
const nfts = await nftContract.erc721.getOwned(undefined, {
count: 1000,
start: 0,
});
expect(nfts).to.be.an("array").length(_tokenIds.length);
});
});
+8
View File
@@ -1,5 +1,13 @@
# @thirdweb-dev/service-utils
## 0.4.7
### Patch Changes
- [#1752](https://github.com/thirdweb-dev/js/pull/1752) [`1fbbc2d2`](https://github.com/thirdweb-dev/js/commit/1fbbc2d27d88af6029267e6993dd79e077a60ab2) Thanks [@ElasticBottle](https://github.com/ElasticBottle)! - chore(service-utils): add `redirecrUrls` type to `ApiKeyMetadata`
- [#1751](https://github.com/thirdweb-dev/js/pull/1751) [`95380eae`](https://github.com/thirdweb-dev/js/commit/95380eaed4aad93d47ece4da2b45fdbba2d14205) Thanks [@arcoraven](https://github.com/arcoraven)! - Add walletType to usage payload
## 0.4.6
### Patch Changes

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