Compare commits

...
Author SHA1 Message Date
github-actions[bot]andGitHub 9187e0f2cf Version Packages (#1894) 2023-11-09 10:42:07 -08:00
20d0f53b46 [wallet, react] Add Core wallet (Avalanche) (#1891)
Co-authored-by: Manan Tank <[email protected]>
2023-11-09 15:22:14 +00:00
iketwandGitHub 0dcb15b7e6 [Chore(chains)] sync chains (#1911) 2023-11-08 20:00:43 +00:00
iketwandGitHub ba49a82463 [RN] UI improvements (#1909) 2023-11-08 16:55:02 +00:00
Phillip HoandGitHub 8d1b8a47e6 chore: Add checkout as a usage type (#1907) 2023-11-07 21:21:00 +00:00
b90d3af5b8 [React] Add option to hide personal wallet switch to React (#1906)
Co-authored-by: Manan Tank <[email protected]>
2023-11-07 16:09:32 +00:00
c34ac468fb [react] Add Localization (#1847)
Signed-off-by: Manan Tank <[email protected]>
Co-authored-by: ikethirdweb <[email protected]>
2023-11-07 15:36:13 +00:00
YashandGitHub fefeaeef18 [SDK] Fix publish (#1809) 2023-11-06 13:46:17 +00:00
Adam MajmudarandGitHub 91c2352b03 [SDK] Add support for engine relayer (#1896) 2023-11-06 00:19:58 +00:00
iketwandGitHub 9f454d1e08 [RN] Improve modal title customization (#1900) 2023-11-04 14:23:31 +00:00
iketwandGitHub 59e5541094 [RN] Removes magic support in favor of embedded wallet (#1892) 2023-11-02 21:14:29 +00:00
iketwandGitHub 8e104b443e [RN] Updates Japanese translations (#1893) 2023-11-02 19:18:01 +00:00
github-actions[bot]GitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
0eed017bb9 Version Packages (#1889)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-11-01 16:06:38 -07:00
Joaquim VergesandGitHub 789700cf60 [React] Fix sign in button display logic (#1888) 2023-11-01 22:56:00 +00:00
github-actions[bot]GitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
570d71e8d6 Version Packages (#1882)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-11-01 13:03:47 -07:00
961ef644d1 [SmartWallet] Improved useSmartWallet hook (#1885)
Co-authored-by: ikethirdweb <[email protected]>
2023-11-01 19:54:46 +00:00
Edward SunandGitHub 6fcd09ddbd Show sign in button if user address and active wallet address are mis-matched (#1886) 2023-11-01 19:22:35 +00:00
Kien NgoandGitHub a9d32f3c90 [SDK] Reduce bundle size by lazy loading some large modules (#1871) 2023-11-01 03:35:24 +00:00
iketwandGitHub 8ec0d00c75 [RN] Fix typo in readme (#1881) 2023-10-31 22:46:59 +00:00
+3
Joaquim VergesGitHubElasticBottleManan TankWinston Yeoikethirdweb0xFirekeeperiketwvuittont60Jonas Danielsgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>github-actions[bot] <github-actions[bot]@users.noreply.github.com>yoanslvy
0acc530f8b [Wallet, React] New Embedded Wallet API (#1846)
Signed-off-by: vuittont60 <[email protected]>
Signed-off-by: Manan Tank <[email protected]>
Co-authored-by: ElasticBottle <[email protected]>
Co-authored-by: Manan Tank <[email protected]>
Co-authored-by: Winston Yeo <[email protected]>
Co-authored-by: ikethirdweb <[email protected]>
Co-authored-by: 0xFirekeeper <[email protected]>
Co-authored-by: iketw <[email protected]>
Co-authored-by: vuittont60 <[email protected]>
Co-authored-by: Jonas Daniels <[email protected]>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: yoanslvy <[email protected]>
2023-10-31 22:42:15 +00:00
github-actions[bot]GitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
7a6f516b4f Version Packages (#1877)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-10-31 15:41:13 -07:00
iketwandGitHub 7461e2a914 [RN] Fixes wallet icons border bug (#1880) 2023-10-31 22:31:20 +00:00
iketwandGitHub 178609a062 [RN] Adds Japanese and Spanish support (#1878) 2023-10-31 00:55:17 +00:00
Kien NgoandGitHub 8ea8f6e954 [SDK] Lazy load JSON files (#1862)
Signed-off-by: Kien Ngo <[email protected]>
2023-10-31 00:40:54 +00:00
iketwandGitHub b49efc24ac Revert "[WALLET + REACT] Add Cometh Connect" (#1879) 2023-10-31 00:03:08 +00:00
6853e87a49 [WALLET + REACT] Add Cometh Connect (#1749)
Signed-off-by: Manan Tank <[email protected]>
Co-authored-by: Manan Tank <[email protected]>
Co-authored-by: Joaquim Verges <[email protected]>
2023-10-30 16:01:11 -07:00
230 changed files with 6904 additions and 1885 deletions
+14
View File
@@ -1,5 +1,19 @@
# @thirdweb-dev/auth
## 4.1.4
### Patch Changes
- Updated dependencies [[`20d0f53b`](https://github.com/thirdweb-dev/js/commit/20d0f53b46a2975a0a14ddaa4a377f2f758804db)]:
- @thirdweb-dev/wallets@2.1.1
## 4.1.3
### Patch Changes
- Updated dependencies [[`0acc530f`](https://github.com/thirdweb-dev/js/commit/0acc530f8bbee59672d9705724edc278bd853d9a)]:
- @thirdweb-dev/wallets@2.1.0
## 4.1.2
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/auth",
"version": "4.1.2",
"version": "4.1.4",
"main": "dist/thirdweb-dev-auth.cjs.js",
"module": "dist/thirdweb-dev-auth.esm.js",
"browser": {
+6
View File
@@ -1,5 +1,11 @@
# @thirdweb-dev/chains
## 0.1.58
### Patch Changes
- [#1911](https://github.com/thirdweb-dev/js/pull/1911) [`0dcb15b7`](https://github.com/thirdweb-dev/js/commit/0dcb15b7e647acd038b0ec8b1a2b200808aae00e) Thanks [@iketw](https://github.com/iketw)! - Syncs Chains
## 0.1.57
### Patch Changes
+13 -1
View File
@@ -2,7 +2,19 @@ import type { Chain } from "../src/types";
export default {
"chain": "FUSE",
"chainId": 122,
"explorers": [],
"explorers": [
{
"name": "blockscout",
"url": "https://explorer.fuse.io",
"standard": "EIP3091",
"icon": {
"url": "ipfs://QmYtUimyqHkkFxYdbXXRbUqNg2VLPUg6Uu2C2nmFWowiZM",
"width": 551,
"height": 540,
"format": "png"
}
}
],
"faucets": [],
"features": [],
"icon": {
+8 -4
View File
@@ -9,7 +9,10 @@ export default {
"standard": "EIP3091"
}
],
"faucets": [],
"faucets": [
"https://faucet.avax.network/?subnet=beam",
"https://faucet.onbeam.com"
],
"features": [
{
"name": "EIP1559"
@@ -24,15 +27,16 @@ export default {
"infoURL": "https://www.onbeam.com",
"name": "Beam Testnet",
"nativeCurrency": {
"name": "Merit Circle",
"symbol": "MC",
"name": "Beam",
"symbol": "BEAM",
"decimals": 18
},
"networkId": 13337,
"rpc": [
"https://beam-testnet.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://13337.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://subnets.avax.network/beam/testnet/rpc"
"https://subnets.avax.network/beam/testnet/rpc",
"wss://subnets.avax.network/beam/testnet/ws"
],
"shortName": "beam-testnet",
"slug": "beam-testnet",
+1
View File
@@ -26,5 +26,6 @@ export default {
"shortName": "hmy-s2",
"slip44": 1023,
"slug": "harmony-shard-2",
"status": "deprecated",
"testnet": false
} as const satisfies Chain;
+1
View File
@@ -26,5 +26,6 @@ export default {
"shortName": "hmy-s3",
"slip44": 1023,
"slug": "harmony-shard-3",
"status": "deprecated",
"testnet": false
} as const satisfies Chain;
+34
View File
@@ -0,0 +1,34 @@
import type { Chain } from "../src/types";
export default {
"chain": "Roll",
"chainId": 1748,
"explorers": [
{
"name": "Blockscout",
"url": "https://roll.calderaexplorer.xyz/",
"standard": "EIP3091"
}
],
"faucets": [],
"features": [],
"icon": {
"url": "ipfs://QmSNKW3HsFekHrTdgt7aAukExwYeYzHZEbG49ZDLYRfkKV/roll_logo%20-%20Ellie%20Li.png",
"width": 3600,
"height": 3600,
"format": ".png"
},
"infoURL": "https://tryroll.com/",
"name": "Roll Testnet",
"nativeCurrency": {
"name": "Ether",
"symbol": "ETH",
"decimals": 18
},
"networkId": 1748,
"redFlags": [],
"rpc": [],
"shortName": "Roll",
"slug": "roll-testnet",
"testnet": true,
"title": "Roll Testnet"
} as const satisfies Chain;
+6 -6
View File
@@ -1,6 +1,6 @@
import type { Chain } from "../src/types";
export default {
"chain": "okbchain",
"chain": "xgon",
"chainId": 195,
"explorers": [
{
@@ -14,20 +14,20 @@ export default {
],
"features": [],
"infoURL": "https://www.okx.com/okbc/docs/dev/quick-start/introduction/introduction-to-okbchain",
"name": "OKBChain Testnet",
"name": "Xgon Testnet",
"nativeCurrency": {
"name": "OKBChain Global Utility Token in testnet",
"name": "Xgon Global Utility Token in testnet",
"symbol": "OKB",
"decimals": 18
},
"networkId": 195,
"rpc": [
"https://okbchain-testnet.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://xgon-testnet.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://195.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://okbtestrpc.okbchain.org"
"https://testrpc.xgon.io"
],
"shortName": "tokb",
"slug": "okbchain-testnet",
"slug": "xgon-testnet",
"status": "active",
"testnet": true
} as const satisfies Chain;
+5 -5
View File
@@ -1,21 +1,21 @@
import type { Chain } from "../src/types";
export default {
"chain": "okbchain",
"chain": "xgon",
"chainId": 196,
"explorers": [],
"faucets": [],
"features": [],
"infoURL": "https://www.okex.com/okc",
"name": "OKBChain Mainnet",
"infoURL": "https://www.okx.com/okbc/docs/dev/quick-start/introduction/introduction-to-okbchain",
"name": "Xgon Mainnet",
"nativeCurrency": {
"name": "OKBChain Global Utility Token",
"name": "Xgon Global Utility Token",
"symbol": "OKB",
"decimals": 18
},
"networkId": 196,
"rpc": [],
"shortName": "okb",
"slug": "okbchain",
"slug": "xgon",
"status": "incubating",
"testnet": false
} as const satisfies Chain;
+41
View File
@@ -0,0 +1,41 @@
import type { Chain } from "../src/types";
export default {
"chain": "VinuChain Testnet",
"chainId": 206,
"explorers": [
{
"name": "VinuScan Testnet",
"url": "https://testnet.vinuscan.com",
"standard": "none",
"icon": {
"url": "ipfs://bafkreiayq7a5qncxzlilfnvpgzt4pwfxbof2qrqr2nyd2suztfummpty4i",
"width": 2000,
"height": 2000,
"format": "jpg"
}
}
],
"faucets": [],
"icon": {
"url": "ipfs://bafkreidtie4srt52ry67wd3dpxhklmug2ozrawvf3jdlljveinp3frutra",
"width": 2000,
"height": 392,
"format": "png"
},
"infoURL": "https://vitainu.org",
"name": "VinuChain Testnet",
"nativeCurrency": {
"name": "VinuChain",
"symbol": "VС",
"decimals": 18
},
"networkId": 206,
"rpc": [
"https://vinuchain-testnet.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://206.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://vinufoundation-rpc.com"
],
"shortName": "VCTEST",
"slug": "vinuchain-testnet",
"testnet": true
} as const satisfies Chain;
+3 -3
View File
@@ -17,9 +17,9 @@ export default {
],
"faucets": [],
"icon": {
"url": "ipfs://bafkreidtie4srt52ry67wd3dpxhklmug2ozrawvf3jdlljveinp3frutra",
"width": 2000,
"height": 392,
"url": "ipfs://bafybeiderli6uaaxk7eboyf7g4abnybgrkbglmqbkmf25z57ode7zdzmvy",
"width": 613,
"height": 613,
"format": "png"
},
"infoURL": "https://vitainu.org",
-2
View File
@@ -34,10 +34,8 @@ export default {
"https://kava.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://2222.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://evm.kava.io",
"https://evm2.kava.io",
"https://kava-rpc.gateway.pokt.network",
"wss://wevm.kava.io",
"wss://wevm2.kava.io",
"https://kava-evm.publicnode.com",
"wss://kava-evm.publicnode.com"
],
+6 -4
View File
@@ -10,11 +10,12 @@ export default {
}
],
"faucets": [],
"features": [],
"icon": {
"url": "ipfs://QmT84suD2ZmTSraJBfeHhTNst2vXctQijNCztok9XiVcUR",
"width": 3600,
"height": 3600,
"format": "png"
"url": "ipfs://QmYcGHTPheBwLv9zoJfQAJ7NB6MCtXe2zV7XqPyiTJg3WP/Oasys_Large%20Logo%20Black.png",
"width": 1250,
"height": 1250,
"format": ".png"
},
"infoURL": "https://oasys.games",
"name": "Oasys Mainnet",
@@ -24,6 +25,7 @@ export default {
"decimals": 18
},
"networkId": 248,
"redFlags": [],
"rpc": [
"https://oasys.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://248.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
+39
View File
@@ -0,0 +1,39 @@
import type { Chain } from "../src/types";
export default {
"chain": "MCH Verse",
"chainId": 29548,
"explorers": [
{
"name": "MCH Verse Explorer",
"url": "https://explorer.oasys.mycryptoheroes.net",
"standard": "EIP3091"
}
],
"faucets": [],
"icon": {
"url": "ipfs://QmZZnwR1y6cU1sare2TQmwqkNDLXQxD4GdPrmHLmUoPtbU",
"width": 4000,
"height": 4000,
"format": "png"
},
"infoURL": "https://www.mycryptoheroes.net/verse",
"name": "MCH Verse Mainnet",
"nativeCurrency": {
"name": "OAS",
"symbol": "OAS",
"decimals": 18
},
"networkId": 29548,
"parent": {
"type": "L2",
"chain": "eip155-248"
},
"rpc": [
"https://mch-verse.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://29548.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://rpc.oasys.mycryptoheroes.net"
],
"shortName": "MCHV",
"slug": "mch-verse",
"testnet": false
} as const satisfies Chain;
+45
View File
@@ -0,0 +1,45 @@
import type { Chain } from "../src/types";
export default {
"chain": "N3-Test",
"chainId": 333333,
"explorers": [
{
"name": "Nativ3 Test Explorer",
"url": "https://scantest.nativ3.network",
"standard": "EIP3091"
}
],
"faucets": [],
"icon": {
"url": "ipfs://QmVzJDndPui6qBSeJWe5kMLA56C3KpVhqqqk9xvVKE1DGb",
"width": 256,
"height": 256,
"format": "png"
},
"infoURL": "https://nativ3.network",
"name": "Nativ3 Testnet",
"nativeCurrency": {
"name": "USNT",
"symbol": "USNT",
"decimals": 18
},
"networkId": 333333,
"parent": {
"type": "L2",
"chain": "eip155-421613",
"bridges": [
{
"url": "https://bridgetest.nativ3.network"
}
]
},
"rpc": [
"https://nativ3-testnet.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://333333.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://rpctest.nativ3.network",
"wss://wstest.nativ3.network"
],
"shortName": "N3-Test",
"slug": "nativ3-testnet",
"testnet": true
} as const satisfies Chain;
+10 -10
View File
@@ -1,24 +1,24 @@
import type { Chain } from "../src/types";
export default {
"chain": "BTC",
"chain": "BOTANIX",
"chainId": 3636,
"explorers": [
{
"name": "Botanix",
"url": "https://testnet.btxtestchain.com",
"name": "3xpl",
"url": "https://3xpl.com/botanix",
"standard": "EIP3091"
}
],
"faucets": [
"https://faucet.btxtestchain.com"
"https://faucet.botanixlabs.dev"
],
"icon": {
"url": "ipfs://Qmf2iSjcrZwUDKhCVY9ZzfbSV2He2HSssbcG2yMz1mDerm",
"width": 32,
"height": 32,
"url": "ipfs://QmVE5s2pXiqdMnAcxhAmWkZYhpFB5CysypeLyPKzT4rGYe",
"width": 512,
"height": 512,
"format": "png"
},
"infoURL": "https://btxtestchain.com",
"infoURL": "https://botanixlabs.xyz",
"name": "Botanix Testnet",
"nativeCurrency": {
"name": "Botanix",
@@ -29,9 +29,9 @@ export default {
"rpc": [
"https://botanix-testnet.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://3636.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://testnet-rpc.btxtestchain.com"
"https://node.botanixlabs.dev"
],
"shortName": "BTCt",
"shortName": "BTNX",
"slug": "botanix-testnet",
"testnet": true
} as const satisfies Chain;
+3 -3
View File
@@ -13,9 +13,9 @@ export default {
"https://faucet.btxtestchain.com"
],
"icon": {
"url": "ipfs://Qmf2iSjcrZwUDKhCVY9ZzfbSV2He2HSssbcG2yMz1mDerm",
"width": 32,
"height": 32,
"url": "ipfs://QmVE5s2pXiqdMnAcxhAmWkZYhpFB5CysypeLyPKzT4rGYe",
"width": 512,
"height": 512,
"format": "png"
},
"infoURL": "https://btxtestchain.com",
+45
View File
@@ -0,0 +1,45 @@
import type { Chain } from "../src/types";
export default {
"chain": "Nativ3",
"chainId": 399,
"explorers": [
{
"name": "N3scan",
"url": "https://scan.nativ3.network",
"standard": "EIP3091"
}
],
"faucets": [],
"icon": {
"url": "ipfs://QmVzJDndPui6qBSeJWe5kMLA56C3KpVhqqqk9xvVKE1DGb",
"width": 256,
"height": 256,
"format": "png"
},
"infoURL": "https://nativ3.network",
"name": "Nativ3 Mainnet",
"nativeCurrency": {
"name": "USNT",
"symbol": "USNT",
"decimals": 18
},
"networkId": 399,
"parent": {
"type": "L2",
"chain": "eip155-42161",
"bridges": [
{
"url": "https://bridge.nativ3.network"
}
]
},
"rpc": [
"https://nativ3.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://399.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://rpc.nativ3.network",
"wss://ws.nativ3.network"
],
"shortName": "N3",
"slug": "nativ3",
"testnet": false
} as const satisfies Chain;
+7 -4
View File
@@ -9,7 +9,9 @@ export default {
"standard": "EIP3091"
}
],
"faucets": [],
"faucets": [
"https://faucet.onbeam.com"
],
"features": [
{
"name": "EIP1559"
@@ -24,15 +26,16 @@ export default {
"infoURL": "https://www.onbeam.com",
"name": "Beam",
"nativeCurrency": {
"name": "Merit Circle",
"symbol": "MC",
"name": "Beam",
"symbol": "BEAM",
"decimals": 18
},
"networkId": 4337,
"rpc": [
"https://beam.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://4337.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://subnets.avax.network/beam/mainnet/rpc"
"https://subnets.avax.network/beam/mainnet/rpc",
"wss://subnets.avax.network/beam/mainnet/ws"
],
"shortName": "beam",
"slug": "beam",
+31
View File
@@ -0,0 +1,31 @@
import type { Chain } from "../src/types";
export default {
"chain": "ETH",
"chainId": 5003,
"explorers": [
{
"name": "blockscout",
"url": "https://explorer.sepolia.mantle.xyz",
"standard": "EIP3091"
}
],
"faucets": [
"https://faucet.sepolia.mantle.xyz"
],
"infoURL": "https://mantle.xyz",
"name": "Mantle Sepolia Testnet",
"nativeCurrency": {
"name": "Sepolia Mantle",
"symbol": "MNT",
"decimals": 18
},
"networkId": 5003,
"rpc": [
"https://mantle-sepolia-testnet.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://5003.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://rpc.sepolia.mantle.xyz"
],
"shortName": "mnt-sep",
"slug": "mantle-sepolia-testnet",
"testnet": true
} as const satisfies Chain;
+49
View File
@@ -0,0 +1,49 @@
import type { Chain } from "../src/types";
export default {
"chain": "Electroneum",
"chainId": 52014,
"explorers": [
{
"name": "blockscout",
"url": "https://blockexplorer.electroneum.com",
"standard": "EIP3091",
"icon": {
"url": "ipfs://QmVgFqXA3kkCrVYGcWFF7Mhx8JUSe9vSCauNamuKWSvCym",
"width": 1000,
"height": 1000,
"format": "png"
}
}
],
"faucets": [],
"features": [
{
"name": "EIP155"
},
{
"name": "EIP1559"
}
],
"icon": {
"url": "ipfs://QmVgFqXA3kkCrVYGcWFF7Mhx8JUSe9vSCauNamuKWSvCym",
"width": 1000,
"height": 1000,
"format": "png"
},
"infoURL": "https://electroneum.com",
"name": "Electroneum Mainnet",
"nativeCurrency": {
"name": "Electroneum",
"symbol": "ETN",
"decimals": 18
},
"networkId": 52014,
"rpc": [
"https://electroneum.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://52014.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://api.electroneum.com"
],
"shortName": "etn-mainnet",
"slug": "electroneum",
"testnet": false
} as const satisfies Chain;
+49
View File
@@ -0,0 +1,49 @@
import type { Chain } from "../src/types";
export default {
"chain": "Electroneum",
"chainId": 5201420,
"explorers": [
{
"name": "blockscout",
"url": "https://blockexplorer.thesecurityteam.rocks",
"standard": "EIP3091",
"icon": {
"url": "ipfs://QmVgFqXA3kkCrVYGcWFF7Mhx8JUSe9vSCauNamuKWSvCym",
"width": 1000,
"height": 1000,
"format": "png"
}
}
],
"faucets": [],
"features": [
{
"name": "EIP155"
},
{
"name": "EIP1559"
}
],
"icon": {
"url": "ipfs://QmVgFqXA3kkCrVYGcWFF7Mhx8JUSe9vSCauNamuKWSvCym",
"width": 1000,
"height": 1000,
"format": "png"
},
"infoURL": "https://electroneum.com",
"name": "Electroneum Testnet",
"nativeCurrency": {
"name": "Electroneum",
"symbol": "ETN",
"decimals": 18
},
"networkId": 5201420,
"rpc": [
"https://electroneum-testnet.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://5201420.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://api.electroneum.com"
],
"shortName": "etn-testnet",
"slug": "electroneum-testnet",
"testnet": true
} as const satisfies Chain;
+1 -2
View File
@@ -25,8 +25,7 @@ export default {
"https://570.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://rpc.rollux.com",
"https://rollux.public-rpc.com",
"wss://rpc.rollux.com/wss",
"https://rpc.ankr.com/rollux/${ANKR_API_KEY}"
"wss://rpc.rollux.com/wss"
],
"shortName": "sys-rollux",
"slug": "rollux",
+6
View File
@@ -13,6 +13,12 @@ export default {
"https://faucet.triangleplatform.com/frame"
],
"features": [],
"icon": {
"url": "ipfs://QmTS1P9sh49zg5xL5BUsRLWDz3vVoJCtCNf9yN1TTnsuQ3",
"width": 321,
"height": 320,
"format": "png"
},
"infoURL": "https://frame.xyz",
"name": "Frame Testnet",
"nativeCurrency": {
+36
View File
@@ -0,0 +1,36 @@
import type { Chain } from "../src/types";
export default {
"chain": "TWT",
"chainId": 894538,
"explorers": [
{
"name": "explorer",
"url": "https://subnets-test.avax.network/thirdweb",
"standard": "EIP3091"
}
],
"faucets": [],
"features": [],
"icon": {
"url": "ipfs://QmXDGoJKuuwQKzY1Y7ZhgULg8hcmveu6PVaDaYQzEjKwkQ/Thirdweb-Icon-Black-BG.png",
"width": 512,
"height": 512,
"format": "png"
},
"name": "thirdweb test subnet",
"nativeCurrency": {
"name": "TWT",
"symbol": "TWT",
"decimals": 18
},
"networkId": 894538,
"redFlags": [],
"rpc": [
"https://thirdweb-test-subnet.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://894538.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://subnets.avax.network/thirdweb/testnet/rpc"
],
"shortName": "twt",
"slug": "thirdweb-test-subnet",
"testnet": true
} as const satisfies Chain;
+42
View File
@@ -0,0 +1,42 @@
import type { Chain } from "../src/types";
export default {
"chain": "XCAP",
"chainId": 9322252,
"explorers": [
{
"name": "blockscout",
"url": "https://xcap-mainnet.explorer.xcap.network",
"standard": "EIP3091",
"icon": {
"url": "ipfs://QmYtUimyqHkkFxYdbXXRbUqNg2VLPUg6Uu2C2nmFWowiZM",
"width": 551,
"height": 540,
"format": "png"
}
}
],
"faucets": [],
"icon": {
"url": "ipfs://QmeTj6tfaw9qf9wnEUQh7PnCpNWyvm56e7kY35kANRWNxj",
"width": 1600,
"height": 1600,
"format": "png"
},
"infoURL": "https://xcap.network/",
"name": "XCAP",
"nativeCurrency": {
"name": "Gas",
"symbol": "GAS",
"decimals": 18
},
"networkId": 9322252,
"rpc": [
"https://xcap.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://9322252.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://xcap-mainnet.relay.xcap.network/znzvh2ueyvm2yts5fv5gnul395jbkfb2/rpc1"
],
"shortName": "xcap",
"slug": "xcap",
"testnet": false,
"title": "XCAP Mainnet"
} as const satisfies Chain;
+42
View File
@@ -0,0 +1,42 @@
import type { Chain } from "../src/types";
export default {
"chain": "XCAP",
"chainId": 9322253,
"explorers": [
{
"name": "blockscout",
"url": "https://xcap-milvine.explorer.xcap.network",
"standard": "EIP3091",
"icon": {
"url": "ipfs://QmYtUimyqHkkFxYdbXXRbUqNg2VLPUg6Uu2C2nmFWowiZM",
"width": 551,
"height": 540,
"format": "png"
}
}
],
"faucets": [],
"icon": {
"url": "ipfs://QmeTj6tfaw9qf9wnEUQh7PnCpNWyvm56e7kY35kANRWNxj",
"width": 1600,
"height": 1600,
"format": "png"
},
"infoURL": "https://xcap.network/",
"name": "Milvine",
"nativeCurrency": {
"name": "Gas",
"symbol": "GAS",
"decimals": 18
},
"networkId": 9322253,
"rpc": [
"https://milvine.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://9322253.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://xcap-milvine.relay.xcap.network/zj5l55ftsgi027kz4nf14vs8d89inego/rpc1"
],
"shortName": "milv",
"slug": "milvine",
"testnet": true,
"title": "XCAP Testnet Milvine"
} as const satisfies Chain;
+35
View File
@@ -0,0 +1,35 @@
import type { Chain } from "../src/types";
export default {
"chain": "Ecrox Chain",
"chainId": 988207,
"explorers": [
{
"name": "Ecrox Chain Explorer",
"url": "https://ecroxscan.com",
"standard": "EIP3091"
}
],
"faucets": [],
"icon": {
"url": "ipfs://bafkreibnqdlwgotu4hwx6oeu4ye3huobr3eaiuvv55n5tolyxya2q7edmq",
"width": 300,
"height": 300,
"format": "png"
},
"infoURL": "https://ecroxcoin.io/",
"name": "Ecrox Chain Mainnet",
"nativeCurrency": {
"name": "ECROX COIN",
"symbol": "ECROX",
"decimals": 18
},
"networkId": 988207,
"rpc": [
"https://ecrox-chain.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://988207.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://mainnet-rpc.ecroxscan.com/"
],
"shortName": "ecrox",
"slug": "ecrox-chain",
"testnet": false
} as const satisfies Chain;
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/chains",
"version": "0.1.57",
"version": "0.1.58",
"main": "dist/thirdweb-dev-chains.cjs.js",
"module": "dist/thirdweb-dev-chains.esm.js",
"browser": {
File diff suppressed because one or more lines are too long
+19
View File
@@ -1,5 +1,24 @@
# thirdweb
## 0.13.12
### Patch Changes
- Updated dependencies [[`0dcb15b7`](https://github.com/thirdweb-dev/js/commit/0dcb15b7e647acd038b0ec8b1a2b200808aae00e), [`fefeaeef`](https://github.com/thirdweb-dev/js/commit/fefeaeef18f52a75462c43da8b85a77975d32e01), [`20d0f53b`](https://github.com/thirdweb-dev/js/commit/20d0f53b46a2975a0a14ddaa4a377f2f758804db), [`91c2352b`](https://github.com/thirdweb-dev/js/commit/91c2352b03d7cf1c3cbbbc98846f248b0f707ea7)]:
- @thirdweb-dev/chains@0.1.58
- @thirdweb-dev/sdk@4.0.11
- @thirdweb-dev/wallets@2.1.1
- @thirdweb-dev/auth@4.1.4
## 0.13.11
### Patch Changes
- Updated dependencies [[`a9d32f3c`](https://github.com/thirdweb-dev/js/commit/a9d32f3c90251a459e17a19eca803bbfdeeaeb79), [`0acc530f`](https://github.com/thirdweb-dev/js/commit/0acc530f8bbee59672d9705724edc278bd853d9a)]:
- @thirdweb-dev/sdk@4.0.10
- @thirdweb-dev/wallets@2.1.0
- @thirdweb-dev/auth@4.1.3
## 0.13.10
### Patch Changes
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "thirdweb",
"main": "dist/cli/index.js",
"version": "0.13.10",
"version": "0.13.12",
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/cli",
"author": "thirdweb eng <[email protected]>",
"license": "Apache-2.0",
+21
View File
@@ -1,5 +1,26 @@
# @thirdweb-dev/react-core
## 4.1.2
### Patch Changes
- Updated dependencies [[`0dcb15b7`](https://github.com/thirdweb-dev/js/commit/0dcb15b7e647acd038b0ec8b1a2b200808aae00e), [`fefeaeef`](https://github.com/thirdweb-dev/js/commit/fefeaeef18f52a75462c43da8b85a77975d32e01), [`20d0f53b`](https://github.com/thirdweb-dev/js/commit/20d0f53b46a2975a0a14ddaa4a377f2f758804db), [`91c2352b`](https://github.com/thirdweb-dev/js/commit/91c2352b03d7cf1c3cbbbc98846f248b0f707ea7)]:
- @thirdweb-dev/chains@0.1.58
- @thirdweb-dev/sdk@4.0.11
- @thirdweb-dev/wallets@2.1.1
- @thirdweb-dev/auth@4.1.4
## 4.1.1
## 4.1.0
### Patch Changes
- Updated dependencies [[`a9d32f3c`](https://github.com/thirdweb-dev/js/commit/a9d32f3c90251a459e17a19eca803bbfdeeaeb79), [`0acc530f`](https://github.com/thirdweb-dev/js/commit/0acc530f8bbee59672d9705724edc278bd853d9a)]:
- @thirdweb-dev/sdk@4.0.10
- @thirdweb-dev/wallets@2.1.0
- @thirdweb-dev/auth@4.1.3
## 4.0.10
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/react-core",
"version": "4.0.10",
"version": "4.1.2",
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/react-core",
"author": "thirdweb eng <[email protected]>",
"license": "Apache-2.0",
@@ -3,6 +3,7 @@ import invariant from "tiny-invariant";
import type {
BloctoWallet,
CoinbaseWallet,
CoreWallet,
EmbeddedWallet,
FrameWallet,
LocalWallet,
@@ -26,6 +27,7 @@ export type WalletId = (typeof walletIds)[keyof typeof walletIds];
type WalletIdToWalletTypeMap = {
metamask: MetaMaskWallet;
coinbase: CoinbaseWallet;
coreWallet: CoreWallet;
rainbowWallet: RainbowWallet;
blocto: BloctoWallet;
frame: FrameWallet;
@@ -199,7 +199,7 @@ export function ThirdwebWalletProvider(
const walletInfo: LastConnectedWalletInfo = {
walletId: walletConfig.id,
connectParams,
connectParams: connectParams || wallet.getConnectParams(),
};
// if personal wallet exists, we need to replace the connectParams.personalWallet to a stringifiable version
@@ -1,5 +1,13 @@
# @thirdweb-dev/react-native-compat
## 0.4.2
## 0.4.1
## 0.4.0
## 0.3.16
## 0.3.15
## 0.3.14
+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.15",
"version": "0.4.2",
"author": "thirdweb eng <[email protected]>",
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/react-native-compat",
"license": "Apache-2.0",
+142
View File
@@ -1,5 +1,147 @@
# @thirdweb-dev/react-native
## 0.4.2
### Patch Changes
- [#1893](https://github.com/thirdweb-dev/js/pull/1893) [`8e104b44`](https://github.com/thirdweb-dev/js/commit/8e104b443e4a11908be660656c525ad237115835) Thanks [@iketw](https://github.com/iketw)! - Updates Japanese translations
- [#1893](https://github.com/thirdweb-dev/js/pull/1893) [`8e104b44`](https://github.com/thirdweb-dev/js/commit/8e104b443e4a11908be660656c525ad237115835) Thanks [@iketw](https://github.com/iketw)! - Adds support for Japanese and Spanish
You can now pass 'ja' or 'es' to our ThirdwebProvider:
```
import { ThirdwebProvider } from '@thirdweb-dev/react-native';
<ThirdwebProvider locale={'ja'}>
<App />
</ ThirdwebProvider>
```
- [#1892](https://github.com/thirdweb-dev/js/pull/1892) [`59e55410`](https://github.com/thirdweb-dev/js/commit/59e55410942f3cb08570623f990487868aaed107) Thanks [@iketw](https://github.com/iketw)! - Deprecates magicLink in favor of embedded wallet
- [#1900](https://github.com/thirdweb-dev/js/pull/1900) [`9f454d1e`](https://github.com/thirdweb-dev/js/commit/9f454d1e08ebf5280a3266791d1843ddd7e24242) Thanks [@iketw](https://github.com/iketw)! - Export Locale types and improve modal title customization
- [#1909](https://github.com/thirdweb-dev/js/pull/1909) [`ba49a824`](https://github.com/thirdweb-dev/js/commit/ba49a82463487735dc19bf33ab2f7a3e9cce08ac) Thanks [@iketw](https://github.com/iketw)! - UI improvements
- Updated dependencies [[`0dcb15b7`](https://github.com/thirdweb-dev/js/commit/0dcb15b7e647acd038b0ec8b1a2b200808aae00e), [`fefeaeef`](https://github.com/thirdweb-dev/js/commit/fefeaeef18f52a75462c43da8b85a77975d32e01), [`20d0f53b`](https://github.com/thirdweb-dev/js/commit/20d0f53b46a2975a0a14ddaa4a377f2f758804db), [`91c2352b`](https://github.com/thirdweb-dev/js/commit/91c2352b03d7cf1c3cbbbc98846f248b0f707ea7)]:
- @thirdweb-dev/[email protected]
- @thirdweb-dev/[email protected]
- @thirdweb-dev/[email protected]
- @thirdweb-dev/[email protected]
## 0.4.1
### Patch Changes
- Updated dependencies []:
- @thirdweb-dev/[email protected]
## 0.4.0
### Minor Changes
- [#1846](https://github.com/thirdweb-dev/js/pull/1846) [`0acc530f`](https://github.com/thirdweb-dev/js/commit/0acc530f8bbee59672d9705724edc278bd853d9a) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - New API to configure authentication options on `embeddedWallet`
```ts
// default - google sign in is enabled
embeddedWallet();
// this is same as
embeddedWallet({
auth: {
options: ["email"],
},
});
// add google sign in
embeddedWallet({
auth: {
options: ["email", "google"],
redirectUrl: "your_app://deeplink",
},
});
```
### Patch Changes
- [#1885](https://github.com/thirdweb-dev/js/pull/1885) [`961ef644`](https://github.com/thirdweb-dev/js/commit/961ef644d1b1adaad08c071903cd53aacac50bb4) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Improved `useSmartWallet()` hook
Example with metamask:
```ts
const { connect } = useSmartWallet(metamaskWallet(), {
factoryAddress: factoryAddress,
gasless: true,
});
const onClick = async () => {
// nothing to do here, all handled internally
await connect();
};
```
```ts
Example with localWallet:
const { connect } = useSmartWallet(localWallet(), {
factoryAddress: factoryAddress,
gasless: true,
});
const onClick = async () => {
// function to 'load' the local wallet before using it
await connect({
connectPersonalWallet: async (w) => {
await w.generate();
await w.connect();
}
});
}
```
```ts
Example with embeddedWallet:
const { connect } = useSmartWallet(embeddedWallet(), {
factoryAddress: factoryAddress,
gasless: true,
});
const onClick = async () => {
// function to 'auth' the embedded wallet before using it
await connect({
connectPersonalWallet: async (w) => {
const authResult = await w.authenticate({ strategy: "google" });
await w.connect({ authResult });
}
});
}
```
- Updated dependencies [[`a9d32f3c`](https://github.com/thirdweb-dev/js/commit/a9d32f3c90251a459e17a19eca803bbfdeeaeb79), [`0acc530f`](https://github.com/thirdweb-dev/js/commit/0acc530f8bbee59672d9705724edc278bd853d9a)]:
- @thirdweb-dev/[email protected]
- @thirdweb-dev/[email protected]
- @thirdweb-dev/[email protected]
## 0.3.16
### Patch Changes
- [#1880](https://github.com/thirdweb-dev/js/pull/1880) [`7461e2a9`](https://github.com/thirdweb-dev/js/commit/7461e2a9145549b7e08bc34d87d44be9bcff2c07) Thanks [@iketw](https://github.com/iketw)! - Fixes wallet icons borders bug
- [#1878](https://github.com/thirdweb-dev/js/pull/1878) [`178609a0`](https://github.com/thirdweb-dev/js/commit/178609a0622b571560188a4266247eb342759ea1) Thanks [@iketw](https://github.com/iketw)! - Adds support for Japanese and Spanish
You can now pass 'ja' or 'es' to our ThirdwebProvider:
```
import { ThirdwebProvider } from '@thirdweb-dev/react-native';
<ThirdwebProvider locale={'ja'}>
<App />
</ ThirdwebProvider>
```
## 0.3.15
### Patch Changes
+1 -1
View File
@@ -12,7 +12,7 @@
</p>
<p><strong>Powering react-native dapps.</strong></p>
The go to librarly for seamless Web3 integration in React Native apps.
The go to library for seamless Web3 integration in React Native apps.
<br />
+1 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/react-native",
"version": "0.3.15",
"version": "0.4.2",
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/react-native",
"author": "thirdweb eng <[email protected]>",
"license": "Apache-2.0",
@@ -31,7 +31,6 @@
"@magic-sdk/provider": "17.2.0",
"@magic-sdk/react-native-bare": "^18.5.0",
"@paperxyz/embedded-wallet-service-sdk": "^1.2.4",
"@react-native-community/checkbox": "^0.5.16",
"@shopify/restyle": "^2.4.2",
"@tanstack/react-query": "^4.33.0",
"@thirdweb-dev/chains": "workspace:*",
@@ -0,0 +1,17 @@
import Svg, { Path } from "react-native-svg";
import { IconStyleProp } from "./types";
const CheckedIcon = ({ width, height, color }: IconStyleProp) => {
return (
<Svg width={width} height={height} viewBox="0 0 15 15" fill="none">
<Path
d="M11.4669 3.72684C11.7558 3.91574 11.8369 4.30308 11.648 4.59198L7.39799 11.092C7.29783 11.2452 7.13556 11.3467 6.95402 11.3699C6.77247 11.3931 6.58989 11.3355 6.45446 11.2124L3.70446 8.71241C3.44905 8.48022 3.43023 8.08494 3.66242 7.82953C3.89461 7.57412 4.28989 7.55529 4.5453 7.78749L6.75292 9.79441L10.6018 3.90792C10.7907 3.61902 11.178 3.53795 11.4669 3.72684Z"
fill={color}
fill-rule="evenodd"
clip-rule="evenodd"
/>
</Svg>
);
};
export default CheckedIcon;
@@ -14,7 +14,11 @@ import { ConnectWalletButton } from "./ConnectWalletFlow/ConnectWalletButton";
import { ConnectWalletButtonProps } from "./ConnectWalletFlow/ConnectWalletButton";
import BaseButton from "./base/BaseButton";
import Text from "./base/Text";
import { useLocale, useUIContext } from "../providers/ui-context-provider";
import {
useGlobalTheme,
useLocale,
useUIContext,
} from "../providers/ui-context-provider";
import { ThemeProvider } from "../styles/ThemeProvider";
import { SupportedTokens, defaultTokens } from "./SendFunds/defaultTokens";
@@ -81,6 +85,7 @@ export const ConnectWallet = ({
privacyPolicyUrl,
supportedTokens,
}: ConnectWalletProps) => {
const globalTheme = useGlobalTheme();
const l = useLocale();
const fadeAnim = useRef(new Animated.Value(0)).current;
const address = useAddress();
@@ -140,7 +145,10 @@ export const ConnectWallet = ({
style={styles.connectWalletButton}
>
{switching ? (
<ActivityIndicator size="small" color="buttonTextColor" />
<ActivityIndicator
size="small"
color={globalTheme.colors.buttonTextColor}
/>
) : (
<Text variant="bodyLarge" color="buttonTextColor">
{l.common.switch_network}
@@ -84,9 +84,9 @@ const ConnectAppField = () => {
const onQRCodeScan = (data: string) => {
if (wcUriRef.current !== data && data.startsWith("wc:")) {
wcUriRef.current = data;
setWCUri(data);
setShowQRCodeScan(false);
wcUriRef.current = data;
onWCPress(data);
}
@@ -180,7 +180,7 @@ export const ExportLocalWalletModal = ({
onPress={onContinuePress}
>
{isExporting ? (
<ActivityIndicator size="small" color="buttonTextColor" />
<ActivityIndicator size="small" color="black" />
) : (
<Text variant="bodySmall" color="black">
{l.connect_wallet_details.backup}
@@ -1,6 +1,7 @@
import { RNCamera } from "react-native-camera";
import { TWModal } from "../base/modal/TWModal";
import { ModalHeaderTextClose } from "../base/modal/ModalHeaderTextClose";
import { SafeAreaView } from "react-native";
export const QRCodeScan = ({
isVisible,
@@ -13,21 +14,26 @@ export const QRCodeScan = ({
}) => {
return (
<TWModal isVisible={isVisible} backdropOpacity={1}>
<ModalHeaderTextClose headerText={"Scan to Connect"} onClose={onClose} />
<RNCamera
style={{ flex: 1 }}
type={RNCamera.Constants.Type.back}
captureAudio={false}
androidCameraPermissionOptions={{
title: "Permission to use camera",
message: "We need your permission to use your camera",
buttonPositive: "Ok",
buttonNegative: "Cancel",
}}
onBarCodeRead={({ data }) => {
onQRCodeScan(data);
}}
/>
<SafeAreaView style={{ flex: 1, backgroundColor: "transparent" }}>
<ModalHeaderTextClose
headerText={"Scan to Connect"}
onClose={onClose}
/>
<RNCamera
style={{ flex: 1 }}
type={RNCamera.Constants.Type.back}
captureAudio={false}
androidCameraPermissionOptions={{
title: "Permission to use camera",
message: "We need your permission to use your camera",
buttonPositive: "Ok",
buttonNegative: "Cancel",
}}
onBarCodeRead={({ data }) => {
onQRCodeScan(data);
}}
/>
</SafeAreaView>
</TWModal>
);
};
@@ -10,7 +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";
import { WalletIcon } from "../base";
import { useLocale } from "../../providers/ui-context-provider";
export type ConnectWalletDetailsProps = {
@@ -92,13 +92,7 @@ export const WalletDetailsButton = ({
alignContent="center"
justifyContent="flex-start"
>
<Box borderRadius="lg" overflow="hidden">
<ImageSvgUri
width={32}
height={32}
imageUrl={avatarUrl || walletIconUrl}
/>
</Box>
<WalletIcon iconUri={avatarUrl || walletIconUrl} size={32} />
<Box ml="md" justifyContent="center" alignItems="flex-start">
{activeWallet?.walletId === LocalWallet.id ? (
<Text variant="bodySmall" color="red">
@@ -104,35 +104,31 @@ export function ChooseWallet({
onBackPress={isConnectAWalletEnabled ? onBackPress : undefined}
onClose={onClose}
headerText={
headerText ? (
headerText
) : (
<Box
flexDirection="row"
alignItems="center"
alignContent="center"
justifyContent="center"
>
{modalTitleIconUrl !== undefined ? (
modalTitleIconUrl.length === 0 ? null : (
<ImageSvgUri
width={26}
height={15}
imageUrl={modalTitleIconUrl}
/>
)
) : (
<ThirdwebLogo
<Box
flexDirection="row"
alignItems="center"
alignContent="center"
justifyContent="center"
>
{modalTitleIconUrl !== undefined ? (
modalTitleIconUrl.length === 0 ? null : (
<ImageSvgUri
width={26}
height={15}
color={theme.colors.backgroundInverted}
imageUrl={modalTitleIconUrl}
/>
)}
<Text variant="headerBold" ml="xxs" fontSize={20} lineHeight={24}>
{l.connect_wallet_details.connect}
</Text>
</Box>
)
)
) : (
<ThirdwebLogo
width={26}
height={15}
color={theme.colors.backgroundInverted}
/>
)}
<Text variant="headerBold" ml="xxs" fontSize={20} lineHeight={24}>
{headerText ? headerText : l.connect_wallet_details.connect}
</Text>
</Box>
}
subHeaderText={subHeaderText}
/>
@@ -104,7 +104,10 @@ export const ConnectWalletButton = ({
>
<Text variant="bodyLargeBold" color="buttonTextColor">
{showButtonSpinner ? (
<ActivityIndicator size="small" color="buttonTextColor" />
<ActivityIndicator
size="small"
color={appTheme.colors.buttonTextColor}
/>
) : buttonTitle ? (
buttonTitle
) : (
@@ -61,7 +61,7 @@ export const ConnectWalletHeader = ({
</Text>
) : null}
{subHeaderText ? (
<Text variant="subHeader" mt="md">
<Text variant="subHeader" mt="md" textAlign="center">
{subHeaderText}
</Text>
) : null}
@@ -74,7 +74,7 @@ export function LocalWalletFlow({
data-test="create-new-wallet-button"
>
{isCreatingWallet ? (
<ActivityIndicator size="small" color="buttonTextColor" />
<ActivityIndicator size="small" color="black" />
) : (
<Text variant="bodyLarge" color="black">
{l.local_wallet.create_new_wallet}
@@ -228,7 +228,7 @@ export const LocalWalletImportModal = ({
onPress={onImportPress}
>
{isImporting ? (
<ActivityIndicator size="small" color="buttonTextColor" />
<ActivityIndicator size="small" color="black" />
) : (
<Text variant="bodySmall" color="black">
{l.common.import}
@@ -10,7 +10,6 @@ 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";
const MODAL_HEIGHT = Dimensions.get("window").height * 0.7;
const DEVICE_WIDTH = Dimensions.get("window").width;
@@ -25,8 +24,6 @@ export const MainModal = () => {
switch (modalState?.view) {
case "ConnectWalletFlow":
return <ConnectWalletFlow />;
// case "WalletDetails":
// return <ConnectWalletDetailsModal />;
case "WalletConnectSessionRequestModal":
return <SessionRequestModal />;
case "WalletConnectSessionProposalModal":
@@ -18,7 +18,7 @@ import type { CallOverrides, ContractInterface } from "ethers";
import { PropsWithChildren, useEffect } from "react";
import { ActivityIndicator, StyleSheet } from "react-native";
import invariant from "tiny-invariant";
import { useUIContext } from "../providers/ui-context-provider";
import { useGlobalTheme, useUIContext } from "../providers/ui-context-provider";
type ActionFn = (contract: SmartContract) => Promise<any>;
@@ -74,6 +74,7 @@ export const Web3Button = <TAction extends ActionFn>({
theme,
connectWalletProps,
}: PropsWithChildren<Web3ButtonProps<TAction>>) => {
const globalTheme = useGlobalTheme();
const address = useAddress();
const activeWallet = useWallet();
const walletChainId = useChainId();
@@ -155,7 +156,12 @@ export const Web3Button = <TAction extends ActionFn>({
connectionStatus === "connecting" ||
connectionStatus === "unknown"
) {
content = <ActivityIndicator size="small" color="buttonTextColor" />;
content = (
<ActivityIndicator
size="small"
color={globalTheme.colors.buttonTextColor}
/>
);
buttonLoading = true;
}
}
@@ -0,0 +1,47 @@
import React, { useRef, useEffect } from "react";
import { Animated, ViewProps } from "react-native";
interface BouncingContainerProps extends ViewProps {
duration?: number;
}
const BouncingContainer: React.FC<BouncingContainerProps> = ({
children,
duration = 500,
...props
}) => {
const bounceValue = useRef(new Animated.Value(0)).current;
useEffect(() => {
Animated.loop(
Animated.sequence([
Animated.timing(bounceValue, {
toValue: 10,
duration,
useNativeDriver: true,
}),
Animated.timing(bounceValue, {
toValue: 0,
duration,
useNativeDriver: true,
}),
]),
).start();
}, [bounceValue, duration]);
return (
<Animated.View
{...props}
style={[
props.style,
{
transform: [{ translateY: bounceValue }],
},
]}
>
{children}
</Animated.View>
);
};
export default BouncingContainer;
@@ -0,0 +1,43 @@
import React, { useState } from "react";
import Text from "./Text";
import CheckedIcon from "../../assets/checked-icon";
import BaseButton from "./BaseButton";
import Box from "./Box";
interface CheckboxProps {
label: string;
color: string;
value?: boolean;
onToggle?: (value: boolean) => void;
}
const Checkbox: React.FC<CheckboxProps> = ({
label,
color,
onToggle,
value = false,
}) => {
const [isChecked, setIsChecked] = useState(value);
const onPress = () => {
setIsChecked(!isChecked);
onToggle?.(!isChecked);
};
return (
<BaseButton flexDirection="row" alignItems="center" onPress={onPress}>
<Box borderRadius="md" style={{ borderColor: color }} borderWidth={1}>
{isChecked ? (
<CheckedIcon width={20} height={20} color={color} />
) : (
<Box width={20} height={20} />
)}
</Box>
<Text variant="bodySmall" ml="xxs">
{label}
</Text>
</BaseButton>
);
};
export default Checkbox;
@@ -1,3 +1,4 @@
import Box from "./Box";
import ImageSvgUri from "./ImageSvgUri";
export const WalletIcon = ({
@@ -7,5 +8,9 @@ export const WalletIcon = ({
iconUri: string;
size: number;
}) => {
return <ImageSvgUri imageUrl={iconUri} width={size} height={size} />;
return (
<Box borderRadius="sm" overflow="hidden">
<ImageSvgUri imageUrl={iconUri} width={size} height={size} />
</Box>
);
};
+112
View File
@@ -0,0 +1,112 @@
export const _en = {
connect_wallet: {
label: "Connect Wallet",
},
connect_wallet_details: {
additional_actions: "Additional Actions",
address_copied_clipboard: "Address copied to clipboard",
backup_wallet: "Backup Wallet",
backup_personal_wallet: "Backup personal wallet",
import_wallet: "Import Wallet",
connected_to_smart_wallet: "Connected to a Smart Wallet",
current_network: "Current Network",
backup: "Backup",
connect_to_app: "Connect to App",
guest: "Guest",
connect: "Connect",
new_to_wallets: "New to wallets?",
view_transaction_history: "View Transaction History",
get_started: "Get started",
receive_funds: "Receive Funds",
search_or_paste_token: "Search or paste token address",
connect_a_wallet: "Connect a wallet",
continue_as_guest: "Continue as guest",
tos: "Terms of Service",
privacy_policy: "Privacy Policy",
by_connecting_you_agree: "By connecting, you agree to the",
copy_address_or_scan:
"Copy the wallet address or scan the QR code to send funds to this wallet.",
request_testnet_funds: "Request Testnet Funds",
your_address: "Your address",
qr_code: "QR Code",
select_token: "Select Token",
send_to: "Send to",
send_funds: "Send Funds",
no_tokens_found: "No Tokens found",
confirm_in_wallet: "Confirm in your wallet",
select_network: "Select Network",
switch_to: "Switch to",
no_supported_chains_detected: "No supported chains detected",
recommended: "Recommended",
network_mismatch:
"There's a network mismatch between your contract and your wallet",
},
connecting_wallet: {
creating_encrypting:
"Creating, encrypting and securing your device wallet.",
connecting_your_wallet: "Connecting your wallet",
connecting_through_pop_up:
"Login and connect your app through the wallet pop-up",
},
local_wallet: {
guest_wallet: "Guest Wallet",
backup_your_wallet: "Backup your wallet",
create_new_wallet: "Create new wallet",
private_key_mnemonic: "Or Private key or Mnemonic",
private_key_mnemonic_placeholder: "Private key / Mnemonic",
application_can_authorize_transactions:
"The application can authorize any transactions on behalf of the wallet without any approvals. We recommend only connecting to trusted applications.",
double_check_password: "Please, double check your password or private key.",
error_accessing_file: "Error accessing the file. Please try again.",
wallet_address: "Wallet Address",
this_will_download_json:
"This will download a JSON file containing your wallet information onto your device encrypted with the password.",
this_is_a_temporary_wallet:
"This is a temporary guest wallet. Download a backup if you don't want to loose access to it.",
},
smart_wallet: {
switch_to_smart: "Switch to Smart Wallet",
switch_to_personal: "Switch to Personal Wallet",
network_mismatch: "Network Mismatch",
connecting: "Connecting",
},
embedded_wallet: {
request_new_code: "Request new code",
sign_in: "Sign In",
sign_in_google: "Sign in with Google",
enter_your_email: "Enter your email address",
forgot_password: "Forgot password",
enter_account_recovery_code: "Enter account recovery code",
backup_your_account: "Backup your account",
create_password: "Create password",
enter_password: "Enter password",
set_password_message: "Set a password to secure your account",
enter_password_for_email: "Enter password for",
make_sure_you_save_it: "Make sure you save it",
},
wallet_connect: {
no_results_found: "No results found",
search_wallets: "Search Wallets",
},
common: {
connect_app: "Connect app",
unknown_network: "Unknown Network",
fetching: "Fetching...",
password: "Password",
reject: "Reject",
approve: "Approve",
switch_network: "Switch Network",
import: "Import",
username: "Username",
amount: "Amount",
send: "Send",
receive: "Receive",
continue: "Continue",
error_switching_network: "Error switching network",
or: "OR",
from: "from",
to: "from",
next: "Next",
learn_more: "Learn More",
},
};
+113
View File
@@ -0,0 +1,113 @@
export const _es = {
connect_wallet: {
label: "Conectar Cartera",
},
connect_wallet_details: {
additional_actions: "Acciones Adicionales",
address_copied_clipboard: "Dirección copiada al portapapeles",
backup_wallet: "Respaldar Cartera",
backup_personal_wallet: "Respaldar cartera personal",
import_wallet: "Importar Cartera",
connected_to_smart_wallet: "Conectado a una Cartera Inteligente",
current_network: "Red Actual",
backup: "Respaldar",
connect_to_app: "Conectar a la Aplicación",
guest: "Invitado",
connect: "Conectar",
new_to_wallets: "¿Nuevo en carteras?",
view_transaction_history: "Ver Historial de Transacciones",
get_started: "Comenzar",
receive_funds: "Recibir Fondos",
search_or_paste_token: "Buscar o pegar dirección del token",
connect_a_wallet: "Conectar una cartera",
continue_as_guest: "Continuar como invitado",
tos: "Términos de Servicio",
privacy_policy: "Política de Privacidad",
by_connecting_you_agree: "Al conectar, aceptas los",
copy_address_or_scan:
"Copia la dirección de la cartera o escanea el código QR para enviar fondos a esta cartera.",
request_testnet_funds: "Solicitar Fondos de Testnet",
your_address: "Tu dirección",
qr_code: "Código QR",
select_token: "Seleccionar Token",
send_to: "Enviar a",
send_funds: "Enviar Fondos",
no_tokens_found: "No se encontraron tokens",
confirm_in_wallet: "Confirma en tu cartera",
select_network: "Seleccionar Red",
switch_to: "Cambiar a",
no_supported_chains_detected: "No se detectaron cadenas compatibles",
recommended: "Recomendado",
network_mismatch: "Hay un desajuste de red entre tu contrato y tu cartera",
},
connecting_wallet: {
creating_encrypting:
"Creando, cifrando y asegurando tu cartera de dispositivo.",
connecting_your_wallet: "Conectando tu cartera",
connecting_through_pop_up:
"Iniciar sesión y conectar tu aplicación a través del pop-up de la cartera",
},
local_wallet: {
guest_wallet: "Cartera de Invitado",
backup_your_wallet: "Haz un respaldo de tu cartera",
create_new_wallet: "Crear nueva cartera",
private_key_mnemonic: "O clave privada o frase mnemotécnica",
private_key_mnemonic_placeholder: "Clave privada / Frase mnemotécnica",
application_can_authorize_transactions:
"La aplicación puede autorizar cualquier transacción en nombre de la cartera sin aprobaciones. Recomendamos conectar solo a aplicaciones de confianza.",
double_check_password: "Por favor, verifica tu contraseña o clave privada.",
error_accessing_file:
"Error al acceder al archivo. Por favor, inténtalo de nuevo.",
wallet_address: "Dirección de la Cartera",
this_will_download_json:
"Esto descargará un archivo JSON que contiene la información de tu cartera en tu dispositivo cifrada con la contraseña.",
this_is_a_temporary_wallet:
"Esta es una cartera de invitado temporal. Descarga un respaldo si no quieres perder el acceso a ella.",
},
smart_wallet: {
switch_to_smart: "Cambiar a Cartera Inteligente",
switch_to_personal: "Cambiar a Cartera Personal",
network_mismatch: "Desajuste de Red",
connecting: "Conectando",
},
embedded_wallet: {
request_new_code: "Solicitar nuevo código",
sign_in: "Iniciar Sesión",
sign_in_google: "Iniciar sesión con Google",
enter_your_email: "Ingresa tu dirección de correo electrónico",
forgot_password: "Olvidé mi contraseña",
enter_account_recovery_code: "Ingresa el código de recuperación de cuenta",
backup_your_account: "Haz un respaldo de tu cuenta",
enter_password: "Introduce contraseña",
create_password: "Crear contraseña",
set_password_message:
"Establece una contraseña para tu cuenta. Necesitarás esta contraseña cuando te conectes desde un nuevo dispositivo.",
enter_password_for_email: "Ingresa la contraseña del correo electrónico",
make_sure_you_save_it: "Asegúrate de guardarlo",
},
wallet_connect: {
no_results_found: "No se encontraron resultados",
search_wallets: "Buscar Carteras",
},
common: {
connect_app: "Conectar aplicación",
unknown_network: "Red Desconocida",
fetching: "Recuperando...",
password: "Contraseña",
reject: "Rechazar",
approve: "Aprobar",
switch_network: "Cambiar Red",
import: "Importar",
username: "Nombre de usuario",
amount: "Cantidad",
send: "Enviar",
receive: "Recibir",
continue: "Continuar",
error_switching_network: "Error al cambiar de red",
or: "O",
from: "de",
to: "a",
next: "Siguiente",
learn_more: "Saber más",
},
};
+113
View File
@@ -0,0 +1,113 @@
export const _ja = {
connect_wallet: {
label: "ウォレットを接続",
},
connect_wallet_details: {
additional_actions: "追加のアクション",
address_copied_clipboard: "アドレスがクリップボードにコピーされました。",
backup_wallet: "ウォレットのバックアップ",
backup_personal_wallet: "個人のウォレットのバックアップ",
import_wallet: "ウォレットのインポート",
connected_to_smart_wallet: "スマートウォレットに接続されています。",
current_network: "現在のネットワーク",
backup: "バックアップ",
connect_to_app: "アプリに接続",
guest: "ゲスト",
connect: "接続",
new_to_wallets: "ウォレットは初めてですか?",
view_transaction_history: "取引履歴を見る",
get_started: "開始する",
receive_funds: "資金を受け取る",
search_or_paste_token: "トークンのアドレスを検索または貼り付け",
connect_a_wallet: "ウォレットを接続",
continue_as_guest: "ゲストとして続行",
tos: "利用規約",
privacy_policy: "プライバシーポリシー",
by_connecting_you_agree: "同意の上、接続してください。",
copy_address_or_scan:
"ウォレットのアドレスをコピーするか、QRコードをスキャンして資金をこのウォレットに送信します。",
request_testnet_funds: "テストネットの資金をリクエスト",
your_address: "あなたのアドレス",
qr_code: "QRコード",
select_token: "トークンを選択",
send_to: "送信先",
send_funds: "資金を送る",
no_tokens_found: "トークンが見つかりません。",
confirm_in_wallet: "ウォレットで確認",
select_network: "ネットワークを選択",
switch_to: "切り替え先",
no_supported_chains_detected:
"サポートされているチェーンが検出されませんでした。",
recommended: "推奨",
network_mismatch:
"あなたのコントラクトとウォレットの間にネットワークの不一致があります。",
},
connecting_wallet: {
creating_encrypting: "デバイスのウォレットを作成、暗号化、保護しています。",
connecting_your_wallet: "ウォレットを接続しています。",
connecting_through_pop_up:
"ウォレットのポップアップを通じてアプリにログインして接続します。",
},
local_wallet: {
guest_wallet: "ゲストウォレット",
backup_your_wallet: "ウォレットをバックアップ",
create_new_wallet: "新しいウォレットを作成",
private_key_mnemonic: "または、秘密鍵またはニーモニック",
private_key_mnemonic_placeholder: "秘密鍵 / ニーモニック",
application_can_authorize_transactions:
"アプリケーションは、承認なしにウォレットを代わりにトランザクションを許可することができます。信頼できるアプリケーションのみに接続することをおすすめします。",
double_check_password: "パスワードまたは秘密鍵を再度確認してください。",
error_accessing_file:
"ファイルにアクセスできませんでしたもう一度試してください。",
wallet_address: "ウォレットのアドレス",
this_will_download_json:
"これにより、パスワードで暗号化されたウォレット情報を含むJSONファイルがデバイスにダウンロードされます。",
this_is_a_temporary_wallet:
"これは一時的なゲストウォレットです。アクセスを失わないようにバックアップをダウンロードしてください。",
},
smart_wallet: {
switch_to_smart: "スマートウォレットに切り替え",
switch_to_personal: "個人のウォレットに切り替え",
network_mismatch: "ネットワークの不一致",
connecting: "接続中",
},
embedded_wallet: {
request_new_code: "新しいコードをリクエスト",
sign_in: "サインイン",
sign_in_google: "Googleでサインイン",
enter_your_email: "メールアドレスを入力",
forgot_password: "パスワードを忘れた場合",
enter_account_recovery_code: "アカウント回復コードを入力",
backup_your_account: "アカウントのバックアップ",
create_password: "パスワードを作成",
enter_password: "パスワードを入力",
set_password_message: "パスワードを設定してアカウントを保護",
enter_password_for_email: "メールアドレスに対するパスワードを入力",
make_sure_you_save_it: "必ず保存してください。",
},
wallet_connect: {
no_results_found: "結果が見つかりません。",
search_wallets: "ウォレットを検索",
},
common: {
connect_app: "アプリに接続",
unknown_network: "不明なネットワーク",
fetching: "取得中...",
password: "パスワード",
reject: "拒否",
approve: "承認",
switch_network: "ネットワークの切り替え",
import: "インポート",
username: "ユーザー名",
amount: "金額",
send: "送信",
receive: "受け取り",
continue: "続行",
error_switching_network: "ネットワークの切り替えエラー",
or: "または",
from: "から",
to: "まで",
next: "次へ",
learn_more: "もっと詳しく",
},
};
+27 -5
View File
@@ -1,13 +1,21 @@
import { DeepPartial, deepMerge } from "../types/deepPartial";
import { Locale, LocaleType, _en } from "./types";
import { _en } from "./en";
import { _es } from "./es";
import { _ja } from "./ja";
import { Locale, LocaleType } from "./types";
export const setLocale = (locale: Locale): LocaleType => {
if (typeof locale === "string") {
if (locale === "en") {
return en();
switch (locale) {
case "en":
return _en;
case "es":
return _es;
case "ja":
return _ja;
default:
throw new Error(`Locale ${locale} not supported`);
}
throw new Error(`Locale ${locale} not supported`);
}
return locale;
};
@@ -18,3 +26,17 @@ export const en = (locale?: DeepPartial<LocaleType>): LocaleType => {
}
return deepMerge(_en, locale);
};
export const ja = (locale?: DeepPartial<LocaleType>): LocaleType => {
if (!locale) {
return _ja;
}
return deepMerge(_ja, locale);
};
export const es = (locale?: DeepPartial<LocaleType>): LocaleType => {
if (!locale) {
return _es;
}
return deepMerge(_es, locale);
};
+3 -109
View File
@@ -1,111 +1,5 @@
export type Locale = LocaleType | "en";
import { _en } from "./en";
export type Locale = LocaleType | "en" | "es" | "ja";
export type LocaleType = typeof _en;
export const _en = {
connect_wallet: {
label: "Connect Wallet",
},
connect_wallet_details: {
additional_actions: "Additional Actions",
address_copied_clipboard: "Address copied to clipboard",
backup_wallet: "Backup Wallet",
backup_personal_wallet: "Backup personal wallet",
import_wallet: "Import Wallet",
connected_to_smart_wallet: "Connected to a Smart Wallet",
current_network: "Current Network",
backup: "Backup",
connect_to_app: "Connect to App",
guest: "Guest",
connect: "Connect",
new_to_wallets: "New to wallets?",
view_transaction_history: "View Transaction History",
get_started: "Get started",
receive_funds: "Receive Funds",
search_or_paste_token: "Search or paste token address",
connect_a_wallet: "Connect a wallet",
continue_as_guest: "Continue as guest",
tos: "Terms of Service",
privacy_policy: "Privacy Policy",
by_connecting_you_agree: "By connecting, you agree to the",
copy_address_or_scan:
"Copy the wallet address or scan the QR code to send funds to this wallet.",
request_testnet_funds: "Request Testnet Funds",
your_address: "Your address",
qr_code: "QR Code",
select_token: "Select Token",
send_to: "Send to",
send_funds: "Send Funds",
no_tokens_found: "No Tokens found",
confirm_in_wallet: "Confirm in your wallet",
select_network: "Select Network",
switch_to: "Switch to",
no_supported_chains_detected: "No supported chains detected",
recommended: "Recommended",
network_mismatch:
"There's a network mismatch between your contract and your wallet",
},
connecting_wallet: {
creating_encrypting:
"Creating, encrypting and securing your device wallet.",
connecting_your_wallet: "Connecting your wallet",
connecting_through_pop_up:
"Login and connect your app through the wallet pop-up",
},
local_wallet: {
guest_wallet: "Guest Wallet",
backup_your_wallet: "Backup your wallet",
create_new_wallet: "Create new wallet",
private_key_mnemonic: "Or Private key or Mnemonic",
private_key_mnemonic_placeholder: "Private key / Mnemonic",
application_can_authorize_transactions:
"The application can authorize any transactions on behalf of the wallet without any approvals. We recommend only connecting to trusted applications.",
double_check_password: "Please, double check your password or private key.",
error_accessing_file: "Error accessing the file. Please try again.",
wallet_address: "Wallet Address",
this_will_download_json:
"This will download a JSON file containing your wallet information onto your device encrypted with the password.",
this_is_a_temporary_wallet:
"This is a temporary guest wallet. Download a backup if you don't want to loose access to it.",
},
smart_wallet: {
switch_to_smart: "Switch to Smart Wallet",
switch_to_personal: "Switch to Personal Wallet",
network_mismatch: "Network Mismatch",
connecting: "Connecting",
},
embedded_wallet: {
request_new_code: "Request new code",
sign_in: "Sign In",
sign_in_google: "Sign in with Google",
enter_your_email: "Enter your email address",
forgot_password: "Forgot password",
enter_account_recovery_code: "Enter account recovery code",
backup_your_account: "Backup your account",
},
wallet_connect: {
no_results_found: "No results found",
search_wallets: "Search Wallets",
},
common: {
connect_app: "Connect app",
unknown_network: "Unknown Network",
fetching: "Fetching...",
password: "Password",
reject: "Reject",
approve: "Approve",
switch_network: "Switch Network",
import: "Import",
username: "Username",
amount: "Amount",
send: "Send",
receive: "Receive",
continue: "Continue",
error_switching_network: "Error switching network",
or: "OR",
from: "from",
to: "from",
next: "Next",
learn_more: "Learn More",
},
};
+4
View File
@@ -30,6 +30,9 @@ export { useCoinbaseWallet } from "./wallets/hooks/useCoinbaseWallet";
export { useMetaMaskWallet } from "./wallets/hooks/useMetaMaskWallet";
export { useRainbowWallet } from "./wallets/hooks/useRainbowWallet";
export { useTrustWallet } from "./wallets/hooks/useTrustWallet";
export { useEmbeddedWallet } from "./wallets/hooks/useEmbeddedWallet";
export { useSmartWallet } from "./wallets/hooks/useSmartWallet";
export { useEmbeddedWalletSendVerificationEmail } from "./wallets/hooks/useEmbeddedWalletSendVerificationEmail";
export {
ConnectWallet,
@@ -46,6 +49,7 @@ export { createSecureStorage } from "../core/SecureStorage";
export { ThirdwebStorage } from "../core/storage/storage";
export * from "./i18n/strings";
export * from "./i18n/types";
// ui components
export * from "./components/base";
@@ -1,5 +1,3 @@
// import { Platform } from "react-native";
export const textVariants = {
defaults: {
fontFamily: undefined as string | undefined,
@@ -1,16 +1,18 @@
import {
AuthOptions,
AuthParams,
AuthResult,
EmbeddedWalletConnectionArgs,
EmbeddedWalletConnectorOptions,
OauthOption,
SendEmailOtpReturnType,
} 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 {
customJwt,
sendEmailOTP,
sendVerificationEmail,
socialLogin,
validateEmailOTP,
} from "./embedded/auth";
@@ -21,7 +23,14 @@ import {
getConnectedEmail,
saveConnectedEmail,
} from "./embedded/helpers/storage/local";
import { AuthProvider } from "@paperxyz/embedded-wallet-service-sdk";
import {
AuthProvider,
Connector,
RecoveryShareManagement,
UserWalletStatus,
normalizeChainId,
} from "@thirdweb-dev/wallets";
import { isValidUserManagedEmailOtp } from "./embedded/helpers/api/fetchers";
export class EmbeddedWalletConnector extends Connector<EmbeddedWalletConnectionArgs> {
private options: EmbeddedWalletConnectorOptions;
@@ -37,142 +46,172 @@ export class EmbeddedWalletConnector extends Connector<EmbeddedWalletConnectionA
this.email = getConnectedEmail();
}
async connect(options?: { chainId?: number } & EmbeddedWalletConnectionArgs) {
const connected = await this.isConnected();
if (connected) {
return this.getAddress();
}
switch (options?.loginType) {
case "headless_google_oauth":
{
await socialLogin(
{
provider: AuthProvider.GOOGLE,
redirectUrl: options.redirectUrl,
},
this.options.clientId,
);
}
break;
case "headless_email_otp_verification": {
await this.validateEmailOtp({ otp: options.otp });
break;
}
case "jwt": {
await this.customJwt({
jwt: options.jwt,
password: options.password,
});
break;
}
default:
throw new Error("Invalid login type");
}
if (options?.chainId) {
this.switchChain(options.chainId);
async connect(options?: EmbeddedWalletConnectionArgs) {
try {
await this.getSigner();
} catch (error) {
throw new Error(`Error fetching the signer: ${error}`);
}
this.setupListeners();
if (options?.chainId) {
await this.switchChain(options.chainId);
}
if (this.email) {
saveConnectedEmail(this.email);
}
return this.getAddress();
}
async validateEmailOtp(options: { otp: string }) {
if (!this.email) {
throw new Error("Email is required to connect");
async authenticate(params: AuthParams): Promise<AuthResult> {
const strategy = params.strategy;
switch (strategy) {
case "email_verification": {
return await this.validateEmailOTP({
email: params.email,
otp: params.verificationCode,
recoveryCode: params.recoveryCode,
});
}
case "google": {
return this.socialLogin({
provider: AuthProvider.GOOGLE,
redirectUrl: params.redirectUrl,
});
}
case "jwt": {
return this.customJwt({
jwt: params.jwt,
password: params.encryptionKey || "",
});
}
default:
assertUnreachable(strategy);
}
}
private async validateEmailOTP(options: {
email: string;
otp: string;
recoveryCode?: string;
}): Promise<AuthResult> {
try {
await validateEmailOTP({
const { storedToken } = await validateEmailOTP({
email: options.email,
clientId: this.options.clientId,
otp: options.otp,
recoveryCode: options.recoveryCode,
});
return {
user: {
status: UserWalletStatus.LOGGED_IN_WALLET_INITIALIZED,
recoveryShareManagement:
storedToken.authDetails.recoveryShareManagement,
},
isNewUser: storedToken.isNewUser,
needsRecoveryCode:
storedToken.authDetails.recoveryShareManagement ===
RecoveryShareManagement.USER_MANAGED,
};
} catch (error) {
console.error(`Error while validating otp: ${error}`);
if (error instanceof Error) {
return { error: error.message };
throw new Error(`Error while validating otp: ${error.message}`);
} else {
return { error: "An unknown error occurred" };
throw new Error("An unknown error occurred while validating otp");
}
}
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 sendEmailOtp(options: { email: string }) {
async isValidUserManagedEmailOTP(options: { otp: string }) {
try {
const result = await isValidUserManagedEmailOtp({
clientId: this.options.clientId,
email: this.email || "",
otp: options.otp,
});
if (result.isValid) {
return result;
} else {
throw new Error("Invalid otp, please try again.");
}
} catch (error) {
throw new Error(`Error validating otp: ${error}`);
}
}
async sendVerificationEmail(options: {
email: string;
}): Promise<SendEmailOtpReturnType> {
this.email = options.email;
saveConnectedEmail(options.email);
return sendEmailOTP({
return sendVerificationEmail({
email: options.email,
clientId: this.options.clientId,
});
}
async socialLogin(oauthOption: OauthOption) {
private async socialLogin(oauthOption: OauthOption): Promise<AuthResult> {
try {
const { email } = await socialLogin(oauthOption, this.options.clientId);
const { storedToken, email } = await socialLogin(
oauthOption,
this.options.clientId,
);
this.email = email;
saveConnectedEmail(email);
return {
user: {
status: UserWalletStatus.LOGGED_IN_WALLET_INITIALIZED,
recoveryShareManagement:
storedToken.authDetails.recoveryShareManagement,
},
isNewUser: storedToken.isNewUser,
needsRecoveryCode:
storedToken.authDetails.recoveryShareManagement ===
RecoveryShareManagement.USER_MANAGED,
};
} catch (error) {
console.error(
`Error while signing in with: ${oauthOption.provider}. ${error}`,
);
if (error instanceof Error) {
return { error: error.message };
throw new Error(
`Error logging in with ${oauthOption.provider}: ${error.message}`,
);
} else {
return { error: "An unknown error occurred" };
throw new Error(
`An unknown error occurred logging in with ${oauthOption.provider}`,
);
}
}
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 customJwt(authOptions: AuthOptions) {
private async customJwt(authOptions: AuthOptions): Promise<AuthResult> {
try {
const resp = await customJwt(authOptions, this.options.clientId);
this.email = resp.email;
const { verifiedToken, email } = await customJwt(
authOptions,
this.options.clientId,
);
this.email = email;
return {
user: {
status: UserWalletStatus.LOGGED_IN_WALLET_INITIALIZED,
recoveryShareManagement:
verifiedToken.authDetails.recoveryShareManagement,
},
isNewUser: verifiedToken.isNewUser,
needsRecoveryCode:
verifiedToken.authDetails.recoveryShareManagement ===
RecoveryShareManagement.USER_MANAGED,
};
} catch (error) {
console.error(`Error while verifying auth: ${error}`);
this.disconnect();
throw error;
}
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> {
@@ -297,3 +336,7 @@ export class EmbeddedWalletConnector extends Connector<EmbeddedWalletConnectionA
return this.email;
}
}
function assertUnreachable(x: never): never {
throw new Error("Invalid param: " + x);
}
@@ -1,14 +1,15 @@
import type { CognitoUser } from "amazon-cognito-identity-js";
import {
AuthProvider,
AuthStoredTokenWithCookieReturnType,
RecoveryShareManagement,
} from "@paperxyz/embedded-wallet-service-sdk";
import type { CognitoUser } from "amazon-cognito-identity-js";
import { Auth } from "aws-amplify";
SendEmailOtpReturnType,
} from "@thirdweb-dev/wallets";
import {
generateAuthTokenFromCognitoEmailOtp,
getEmbeddedWalletUserDetail,
sendUserManagedEmailOtp,
validateUserManagedEmailOtp,
verifyClientId,
} from "./helpers/api/fetchers";
import {
@@ -20,17 +21,17 @@ import {
postPaperAuthUserManaged,
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 { AuthOptions, OauthOption } from "../types";
import { isDeviceSharePresentForUser } from "./helpers/storage/local";
import { Auth } from "aws-amplify";
import {
ROUTE_AUTH_JWT_CALLBACK,
ROUTE_HEADLESS_GOOGLE_LOGIN,
} from "./helpers/constants";
import { AuthOptions, OauthOption, VerifiedTokenResponse } from "../types";
import { InAppBrowser } from "react-native-inappbrowser-reborn";
export async function sendEmailOTP(options: {
export async function sendVerificationEmail(options: {
email: string;
clientId: string;
}): Promise<SendEmailOtpReturnType> {
@@ -41,16 +42,6 @@ export async function sendEmailOTP(options: {
email: options.email,
});
// AWS Auth flow
let cognitoUser: CognitoUser;
try {
cognitoUser = await cognitoEmailSignIn(options.email, options.clientId);
} catch (e) {
await cognitoEmailSignUp(options.email, options.clientId);
cognitoUser = await cognitoEmailSignIn(options.email, options.clientId);
}
setCognitoUser(cognitoUser);
let result: Awaited<ReturnType<typeof getEmbeddedWalletUserDetail>>;
try {
result = await getEmbeddedWalletUserDetail({
@@ -63,11 +54,29 @@ export async function sendEmailOTP(options: {
);
}
if (result.recoveryShareManagement === "USER_MANAGED") {
try {
await sendUserManagedEmailOtp(options.email, options.clientId);
} catch (error) {
throw new Error(`Error sending user managed email otp: ${error}`);
}
} else {
// CLOUD_MANAGED
let cognitoUser: CognitoUser;
try {
cognitoUser = await cognitoEmailSignIn(options.email, options.clientId);
} catch (e) {
await cognitoEmailSignUp(options.email, options.clientId);
cognitoUser = await cognitoEmailSignIn(options.email, options.clientId);
}
setCognitoUser(cognitoUser);
}
return result.isNewUser
? {
isNewUser: result.isNewUser,
isNewDevice: true,
recoveryShareManagement: RecoveryShareManagement.AWS_MANAGED,
recoveryShareManagement: result.recoveryShareManagement,
}
: {
isNewUser: result.isNewUser,
@@ -75,60 +84,88 @@ export async function sendEmailOTP(options: {
options.clientId,
result.walletUserId ?? "",
)),
recoveryShareManagement: RecoveryShareManagement.AWS_MANAGED,
recoveryShareManagement: result.recoveryShareManagement,
};
}
export async function validateEmailOTP({
clientId,
otp,
}: {
export async function validateEmailOTP(options: {
email: string;
otp: string;
clientId: string;
recoveryCode?: string;
}): Promise<AuthStoredTokenWithCookieReturnType> {
let verifiedToken: Awaited<
ReturnType<typeof generateAuthTokenFromCognitoEmailOtp>
>["verifiedToken"];
let verifiedTokenJwtString: string;
let result: Awaited<ReturnType<typeof getEmbeddedWalletUserDetail>>;
try {
let cognitoUser = getCognitoUser();
if (!cognitoUser) {
throw new Error("MISSING COGNITO USER");
}
cognitoUser = await Auth.sendCustomChallengeAnswer(cognitoUser, otp);
// It we get here, the answer was sent successfully,
// but it might have been wrong (1st or 2nd time)
// So we should test if the user is authenticated now
const session = await Auth.currentSession();
({ verifiedToken, verifiedTokenJwtString } =
await generateAuthTokenFromCognitoEmailOtp(session, clientId));
result = await getEmbeddedWalletUserDetail({
email: options.email,
clientId: options.clientId,
});
} catch (e) {
throw new Error(`Invalid OTP ${e}`);
throw new Error(
`Malformed response from the send email OTP API: ${JSON.stringify(e)}`,
);
}
let verifiedTokenResponse: VerifiedTokenResponse;
if (result.recoveryShareManagement === "USER_MANAGED") {
try {
verifiedTokenResponse = await validateUserManagedEmailOtp({
email: options.email,
otp: options.otp,
clientId: options.clientId,
});
} catch (error) {
throw new Error(`Error validating user managed email otp: ${error}`);
}
} else {
try {
let cognitoUser = getCognitoUser();
if (!cognitoUser) {
throw new Error("MISSING COGNITO USER");
}
cognitoUser = await Auth.sendCustomChallengeAnswer(
cognitoUser,
options.otp,
);
// It we get here, the answer was sent successfully,
// but it might have been wrong (1st or 2nd time)
// So we should test if the user is authenticated now
const session = await Auth.currentSession();
verifiedTokenResponse = await generateAuthTokenFromCognitoEmailOtp(
session,
options.clientId,
);
} catch (e) {
throw new Error(`Invalid OTP ${e}`);
}
}
try {
const storedToken: AuthStoredTokenWithCookieReturnType["storedToken"] = {
jwtToken: verifiedToken.jwtToken,
authDetails: verifiedToken.authDetails,
authProvider: verifiedToken.authProvider,
developerClientId: verifiedToken.developerClientId,
cookieString: verifiedTokenJwtString,
jwtToken: verifiedTokenResponse.verifiedToken.jwtToken,
authDetails: verifiedTokenResponse.verifiedToken.authDetails,
authProvider: verifiedTokenResponse.verifiedToken.authProvider,
developerClientId: verifiedTokenResponse.verifiedToken.developerClientId,
cookieString: verifiedTokenResponse.verifiedTokenJwtString,
// we should always store the jwt cookie since there's no concept of cookie in react native
shouldStoreCookieString: true,
isNewUser: verifiedToken.isNewUser,
isNewUser: verifiedTokenResponse.verifiedToken.isNewUser,
};
await postPaperAuth(storedToken, clientId);
await postPaperAuth({
storedToken,
clientId: options.clientId,
recoveryCode: options.recoveryCode,
});
return { storedToken };
} catch (e) {
throw new Error(
`Malformed response from the verify one time password: ${JSON.stringify(
e,
)}`,
`Malformed response from the verify one time password: ${
(e as Error).message
}}`,
);
}
}
@@ -195,7 +232,7 @@ export async function socialLogin(oauthOptions: OauthOption, clientId: string) {
isNewUser: storedToken.isNewUser,
};
await postPaperAuth(toStoreToken, clientId);
await postPaperAuth({ storedToken: toStoreToken, clientId });
return { storedToken, email: storedToken.authDetails.email };
} catch (e) {
@@ -218,7 +255,7 @@ export async function customJwt(authOptions: AuthOptions, clientId: string) {
});
if (!resp.ok) {
const error = await resp.json();
throw new Error(`JWT authentication error: ${error.message} `);
throw new Error(`JWT authentication error: ${error.message}`);
}
try {
@@ -1,16 +1,23 @@
import {
AuthProvider,
RecoveryShareManagement,
} from "@paperxyz/embedded-wallet-service-sdk";
import { CognitoUserSession } from "amazon-cognito-identity-js";
import {
ROUTE_GET_EMBEDDED_WALLET_DETAILS,
ROUTE_STORE_USER_SHARES,
ROUTE_VERIFY_THIRDWEB_CLIENT_ID,
ROUTE_VERIFY_COGNITO_OTP,
ROUTE_USER_MANAGED_OTP,
ROUTE_VALIDATE_USER_MANAGED_OTP,
ROUTE_IS_VALID_USER_MANAGED_OTP,
} from "../constants";
import { getAuthTokenClient } from "../storage/local";
import * as Application from "expo-application";
import {
RecoveryShareManagement,
UserWalletStatus,
} from "@thirdweb-dev/wallets";
import {
IsValidUserManagedEmailOTPResponse,
VerifiedTokenResponse,
} from "../../../types";
const EMBEDDED_WALLET_TOKEN_HEADER = "embedded-wallet-token";
const PAPER_CLIENT_ID_HEADER = "x-thirdweb-client-id";
@@ -91,11 +98,14 @@ export async function getEmbeddedWalletUserDetail(args: {
| {
isNewUser: true;
recoveryShareManagement: RecoveryShareManagement;
status: UserWalletStatus;
walletUserId: string;
}
| {
isNewUser: false;
walletUserId: string;
recoveryShareManagement: RecoveryShareManagement;
status: UserWalletStatus;
};
return result;
}
@@ -125,22 +135,82 @@ export async function generateAuthTokenFromCognitoEmailOtp(
);
}
const respJ = await resp.json();
return respJ as {
verifiedToken: {
jwtToken: string;
authProvider: AuthProvider;
developerClientId: string;
authDetails: {
email?: string;
userWalletId: string;
recoveryCode?: string;
cookieString?: string;
recoveryShareManagement: RecoveryShareManagement;
};
isNewUser: boolean;
};
verifiedTokenJwtString: string;
};
return respJ as VerifiedTokenResponse;
}
export async function sendUserManagedEmailOtp(email: string, clientId: string) {
const resp = await fetch(ROUTE_USER_MANAGED_OTP, {
method: "POST",
headers: {
"Content-Type": "application/json",
[BUNDLE_ID_HEADER]: APP_BUNDLE_ID,
},
body: JSON.stringify({
email,
clientId,
}),
});
if (!resp.ok) {
const error = await resp.json();
throw new Error(
`Something went wrong generating auth token from user cognito email otp. ${error.message}`,
);
}
const respJ = await resp.json();
return respJ;
}
export async function validateUserManagedEmailOtp(options: {
email: string;
otp: string;
clientId: string;
}) {
const resp = await fetch(ROUTE_VALIDATE_USER_MANAGED_OTP, {
method: "POST",
headers: {
"Content-Type": "application/json",
[BUNDLE_ID_HEADER]: APP_BUNDLE_ID,
},
body: JSON.stringify({
email: options.email,
otp: options.otp,
clientId: options.clientId,
}),
});
if (!resp.ok) {
const error = await resp.json();
throw new Error(
`Something went wrong generating auth token from user cognito email otp. ${error.message}`,
);
}
const respJ = await resp.json();
return respJ as VerifiedTokenResponse;
}
export async function isValidUserManagedEmailOtp(options: {
email: string;
otp: string;
clientId: string;
}) {
const resp = await fetch(ROUTE_IS_VALID_USER_MANAGED_OTP, {
method: "POST",
headers: {
"Content-Type": "application/json",
[BUNDLE_ID_HEADER]: APP_BUNDLE_ID,
},
body: JSON.stringify({
email: options.email,
otp: options.otp,
clientId: options.clientId,
}),
});
if (!resp.ok) {
const error = await resp.json();
throw new Error(
`Something went wrong generating auth token from user cognito email otp. ${error.message}`,
);
}
const respJ = await resp.json();
return respJ as IsValidUserManagedEmailOTPResponse;
}
export async function storeUserShares({
@@ -1,7 +1,8 @@
import {
AuthProvider,
AuthStoredTokenWithCookieReturnType,
} from "@paperxyz/embedded-wallet-service-sdk";
RecoveryShareManagement,
} from "@thirdweb-dev/wallets";
import {
getDeviceShare,
setAuthTokenClient,
@@ -10,6 +11,7 @@ import {
import { setUpNewUserWallet } from "../wallet/creation";
import { getCognitoRecoveryPassword } from "../wallet/recoveryCode";
import { setUpShareForNewDevice } from "../wallet/retrieval";
import { ErrorMessages } from "../errors";
export async function prePaperAuth(args: {
authenticationMethod: AuthProvider;
@@ -19,10 +21,15 @@ export async function prePaperAuth(args: {
Promise.resolve(args);
}
export async function postPaperAuth(
storedToken: AuthStoredTokenWithCookieReturnType["storedToken"],
clientId: string,
) {
export async function postPaperAuth({
storedToken,
clientId,
recoveryCode,
}: {
storedToken: AuthStoredTokenWithCookieReturnType["storedToken"];
clientId: string;
recoveryCode?: string;
}) {
if (storedToken.shouldStoreCookieString) {
await setAuthTokenClient(storedToken.cookieString, clientId);
}
@@ -34,23 +41,32 @@ export async function postPaperAuth(
});
if (storedToken.isNewUser) {
console.log("========== New User ==========");
const recoveryCode = await getCognitoRecoveryPassword(clientId);
await setUpNewUserWallet(recoveryCode, clientId);
const _recoveryCode = await getRecoveryCode(
storedToken,
clientId,
recoveryCode,
);
if (!_recoveryCode) {
throw new Error(ErrorMessages.missingRecoveryCode);
}
await setUpNewUserWallet(_recoveryCode, clientId);
} else {
try {
// existing device share
await getDeviceShare(clientId);
console.log("========== Existing user with device share ==========");
} catch (e) {
// trying to recreate device share from recovery code to derive wallet
const recoveryCode = await getCognitoRecoveryPassword(clientId);
console.log("========== Existing user on new device ==========");
const _recoveryCode = await getRecoveryCode(
storedToken,
clientId,
recoveryCode,
);
if (!_recoveryCode) {
throw new Error(ErrorMessages.missingRecoveryCode);
}
try {
await setUpShareForNewDevice({
clientId,
recoveryCode,
recoveryCode: _recoveryCode,
});
} catch (error) {
console.error("Error setting up wallet on device", error);
@@ -67,6 +83,8 @@ export async function postPaperAuthUserManaged(
clientId: string,
password: string,
) {
const _password = await getRecoveryCode(storedToken, clientId, password);
if (storedToken.shouldStoreCookieString) {
await setAuthTokenClient(storedToken.cookieString, clientId);
}
@@ -78,21 +96,17 @@ export async function postPaperAuthUserManaged(
});
if (storedToken.isNewUser) {
console.log("========== New User ==========");
await setUpNewUserWallet(password, clientId);
await setUpNewUserWallet(_password, clientId);
} else {
try {
// existing device share
await getDeviceShare(clientId);
console.log("========== Existing user with device share ==========");
} catch (e) {
// trying to recreate device share from recovery code to derive wallet
console.log("========== Existing user on new device ==========");
try {
await setUpShareForNewDevice({
clientId,
recoveryCode: password,
recoveryCode: _password,
});
} catch (error) {
console.error("Error setting up wallet on device", error);
@@ -103,3 +117,64 @@ export async function postPaperAuthUserManaged(
return storedToken;
}
async function getRecoveryCode(
storedToken: AuthStoredTokenWithCookieReturnType["storedToken"],
clientId: string,
recoveryCode?: string,
): Promise<string> {
if (
storedToken.authDetails.recoveryShareManagement ===
RecoveryShareManagement.CLOUD_MANAGED
) {
if (storedToken.authProvider === AuthProvider.CUSTOM_JWT) {
// derive sub here
const code = extractSubFromJwt(storedToken.jwtToken);
if (!code) {
throw new Error(
`GetRecoveryCode error: ${ErrorMessages.missingRecoveryCode}`,
);
}
return code;
} else {
try {
const code = await getCognitoRecoveryPassword(clientId);
return code;
} catch (e) {
throw new Error("Something went wrong getting cognito recovery code");
}
}
} else if (
storedToken.authDetails.recoveryShareManagement ===
RecoveryShareManagement.USER_MANAGED
) {
if (recoveryCode) {
return recoveryCode;
}
throw new Error(ErrorMessages.missingRecoveryCode);
} else {
throw new Error("Invalid recovery share management option");
}
}
function extractSubFromJwt(jwtToken: string): string | undefined {
const parts = jwtToken.split(".");
if (parts.length !== 3) {
throw new Error("Invalid JWT format.");
}
const encodedPayload = parts[1];
if (!encodedPayload) {
throw new Error("Invalid JWT format.");
}
const decodedPayload = Buffer.from(encodedPayload, "base64").toString("utf8");
try {
const payloadObject = JSON.parse(decodedPayload);
if (payloadObject && payloadObject.sub) {
return payloadObject.sub;
}
} catch (error) {
throw new Error("Error parsing JWT payload as JSON.");
}
}
@@ -25,11 +25,16 @@ const ROUTE_2023_10_20_API_BASE_PATH = `${BASE_URL_2023}api/2023-10-20`;
export const ROUTE_GET_EMBEDDED_WALLET_DETAILS = `${ROUTE_2023_10_20_API_BASE_PATH}/embedded-wallet/embedded-wallet-user-details`;
export const ROUTE_VERIFY_COGNITO_OTP = `${ROUTE_2023_10_20_API_BASE_PATH}/embedded-wallet/validate-cognito-email-otp`;
export const ROUTE_COGNITO_IDENTITY_POOL_URL = `cognito-idp.${AWS_REGION}.amazonaws.com/${COGNITO_USER_POOL_ID}`;
export const ROUTE_STORE_USER_SHARES = `${ROUTE_2023_10_20_API_BASE_PATH}/embedded-wallet/embedded-wallet-shares`;
export const ROUTE_GET_USER_SHARES = `${ROUTE_2023_10_20_API_BASE_PATH}/embedded-wallet/embedded-wallet-shares`;
export const ROUTE_VERIFY_THIRDWEB_CLIENT_ID = `${ROUTE_2023_10_20_API_BASE_PATH}/embedded-wallet/verify-thirdweb-client-id`;
export const ROUTE_AUTH_JWT_CALLBACK = `${ROUTE_2023_10_20_API_BASE_PATH}/embedded-wallet/validate-custom-jwt`;
export const ROUTE_USER_MANAGED_OTP = `${ROUTE_2023_10_20_API_BASE_PATH}/embedded-wallet/send-user-managed-email-otp`;
export const ROUTE_VALIDATE_USER_MANAGED_OTP = `${ROUTE_2023_10_20_API_BASE_PATH}/embedded-wallet/validate-thirdweb-email-otp`;
export const ROUTE_IS_VALID_USER_MANAGED_OTP = `${ROUTE_2023_10_20_API_BASE_PATH}/embedded-wallet/is-thirdweb-email-otp-valid`;
export const ROUTE_HEADLESS_GOOGLE_LOGIN = `${ROUTE_2023_10_20_API_BASE_PATH}/embedded-wallet/headless-oauth-login-link`;
export const ROUTE_AUTH_COGNITO_ID_TOKEN = `${ROUTE_2023_10_20_API_BASE_PATH}/embedded-wallet/cognito-id-token`;
@@ -0,0 +1,5 @@
export const ErrorMessages = {
invalidOtp:
"Your OTP code is invalid or expired. Please request a new code or try again.",
missingRecoveryCode: "Missing encryption key for user",
};
@@ -1,10 +1,6 @@
import { getDefaultProvider } from "ethers";
import { getExistingUserEtherJsWallet } from "./helpers/wallet/retrieval";
export async function getEthersSigner(clientId: string) {
const wallet = await getExistingUserEtherJsWallet(clientId);
const newWallet = wallet.connect(getDefaultProvider());
await newWallet.getChainId();
return newWallet;
return wallet;
}
@@ -1,10 +1,10 @@
import { Chain } from "@thirdweb-dev/chains";
import { WalletOptions } from "@thirdweb-dev/wallets";
import type {
RecoveryShareManagement,
PaperConstructorType,
import {
AuthProvider,
} from "@paperxyz/embedded-wallet-service-sdk";
RecoveryShareManagement,
UserWalletStatus,
WalletOptions,
} from "@thirdweb-dev/wallets";
export type OauthOptions = {
providers: AuthProvider[];
@@ -29,14 +29,7 @@ export type EmbeddedWalletOptions = Omit<
>;
type EmbeddedAdvanceOptions = {
recoveryShareManagement?: "USER_MANAGED" | "AWS_MANAGED";
};
export type EmbeddedWalletAdditionalOptions = {
emailClientId: string;
chain: Pick<Chain, "chainId" | "rpc">;
advancedOptions?: EmbeddedAdvanceOptions;
styles?: PaperConstructorType<RecoveryShareManagement>["styles"];
recoveryShareManagement?: RecoveryShareManagement;
};
export interface EmbeddedWalletConnectorOptions {
@@ -44,7 +37,6 @@ export interface EmbeddedWalletConnectorOptions {
chain: Pick<Chain, "chainId" | "rpc">;
chains: Chain[];
advancedOptions?: EmbeddedAdvanceOptions;
styles?: PaperConstructorType<RecoveryShareManagement>["styles"];
}
export interface AuthOptions {
@@ -52,21 +44,70 @@ export interface AuthOptions {
password: string;
}
export type SendEmailOtpReturnType = {
isNewUser: boolean;
isNewDevice: boolean;
recoveryShareManagement: RecoveryShareManagement;
};
export type EmbeddedWalletConnectionArgs = {
chainId?: number;
} & (
| {
loginType: "headless_google_oauth";
redirectUrl: string;
}
| {
loginType: "headless_email_otp_verification";
email: string;
otp: string;
}
| {
loginType: "jwt";
jwt: string;
password: string;
}
);
authResult: AuthResult;
};
type EmailVerificationAuthParams = {
strategy: "email_verification";
email: string;
verificationCode: string;
recoveryCode?: string;
};
type GoogleAuthParams = {
strategy: "google";
redirectUrl: string;
};
type JwtAuthParams = {
strategy: "jwt";
jwt: string;
encryptionKey?: string;
};
// this is the input to 'authenticate'
export type AuthParams =
| EmailVerificationAuthParams
| GoogleAuthParams
| JwtAuthParams;
// TODO typed based off AuthParams["strategy"]
export type AuthResult = {
user?: InitializedUser;
isNewUser?: boolean;
needsRecoveryCode?: boolean;
};
export type InitializedUser = {
status: UserWalletStatus.LOGGED_IN_WALLET_INITIALIZED;
recoveryShareManagement: RecoveryShareManagement;
};
export type VerifiedTokenResponse = {
verifiedToken: {
jwtToken: string;
authProvider: AuthProvider;
developerClientId: string;
authDetails: {
email?: string;
userWalletId: string;
recoveryCode?: string;
cookieString?: string;
recoveryShareManagement: RecoveryShareManagement;
};
isNewUser: boolean;
};
verifiedTokenJwtString: string;
};
export type IsValidUserManagedEmailOTPResponse = {
isValid: { type: "boolean" };
};
@@ -0,0 +1,72 @@
import {
useCreateWalletInstance,
useSetConnectedWallet,
useSetConnectionStatus,
useWalletContext,
useWallets,
} from "@thirdweb-dev/react-core";
import { useCallback } from "react";
import { AuthParams } from "../connectors/embedded-wallet/types";
import { walletIds } from "@thirdweb-dev/wallets";
import { EmbeddedWallet } from "../wallets/embedded/EmbeddedWallet";
import { embeddedWallet } from "../wallets/embedded/embedded-wallet";
/**
* Hook to authenticate and connect to an embedded wallet
* @returns a function to connect to an embedded wallet
*/
export function useEmbeddedWallet() {
const create = useCreateWalletInstance();
const setStatus = useSetConnectionStatus();
const setWallet = useSetConnectedWallet();
const context = useWalletContext();
const wallets = useWallets();
const connect = useCallback(
async (authParams: AuthParams) => {
setStatus("connecting");
const config = wallets.find((w) => w.id === walletIds.embeddedWallet);
if (
!config ||
(config.personalWallets && config.personalWallets.length > 0)
) {
console.warn(
"Embedded wallet not configured in ThirdwebProvider.supportedWallets - add it to enable autoconnect on page load",
);
}
const wallet = create(embeddedWallet());
try {
const authResult = await wallet.authenticate(authParams);
await wallet.connect({ authResult });
setWallet(wallet);
setStatus("connected");
} catch (e) {
console.error("Error connecting to embedded wallet", e);
setStatus("disconnected");
}
return wallet;
},
[setStatus, wallets, create, setWallet],
);
const sendVerificationEmail = useCallback(
async ({ email }: { email: string }) => {
const clientId = context.clientId;
if (!clientId) {
throw new Error(
"clientId not found, please add it to ThirdwebProvider",
);
}
return EmbeddedWallet.sendVerificationEmail({
email,
clientId,
});
},
[context],
);
return {
connect,
sendVerificationEmail,
};
}
@@ -0,0 +1,22 @@
import { useWalletContext } from "@thirdweb-dev/react-core";
import { useCallback } from "react";
import { EmbeddedWallet } from "../wallets/embedded/EmbeddedWallet";
export const useEmbeddedWalletSendVerificationEmail = () => {
const context = useWalletContext();
const sendVerificationEmail = useCallback(
async (email: string) => {
if (!context.clientId) {
throw new Error("Please, provide a clientId in the ThirdwebProvider");
}
return EmbeddedWallet.sendVerificationEmail({
email,
clientId: context.clientId,
});
},
[context.clientId],
);
return sendVerificationEmail;
};
@@ -0,0 +1,92 @@
import {
type WalletConfig,
WalletInstance,
useCreateWalletInstance,
useSetConnectedWallet,
useSetConnectionStatus,
useWalletContext,
useWallets,
} from "@thirdweb-dev/react-core";
import {
getSmartWalletAddress,
type SmartWalletConnectionArgs,
} from "@thirdweb-dev/wallets/evm/wallets/smart-wallet";
import { useCallback } from "react";
import type { BytesLike } from "ethers";
import { SmartWalletConfig } from "../types/smart-wallet";
export function useSmartWallet<W extends WalletInstance>(
personalWallet: WalletConfig<W>,
options: SmartWalletConfig,
) {
const create = useCreateWalletInstance();
const setStatus = useSetConnectionStatus();
const setWallet = useSetConnectedWallet();
const context = useWalletContext();
const supportedWallets = useWallets();
const predictAddress = useCallback(
async (args: { personalWalletAddress: string; data?: BytesLike }) => {
return getSmartWalletAddress(
context.activeChain,
options.factoryAddress,
args.personalWalletAddress,
args.data,
);
},
[context.activeChain, options.factoryAddress],
);
const connect = useCallback(
async (args?: {
connectPersonalWallet?: (wallet: W) => Promise<void>;
connectionArgs?: Omit<SmartWalletConnectionArgs, "personalWallet">;
}) => {
if (!supportedWallets.find((w) => w.id === personalWallet.id)) {
console.warn(
"Please, add your smart wallet to the supportedWallets prop of the ThirdwebProvider to enjoy auto-connecting to the wallet.",
);
}
const { smartWallet } = await import("../wallets/smart-wallet");
setStatus("connecting");
const pw = create(personalWallet);
const sw = create(smartWallet(personalWallet, options));
try {
if (args?.connectPersonalWallet) {
// if passed in, use custom function to connect personal wallet
await args.connectPersonalWallet(pw);
} else {
// otherwise default to auto-connecting personal wallet with chainId
await pw.connect({
chainId: context.activeChain.chainId,
});
}
await sw.connect({
...args?.connectionArgs,
personalWallet: pw,
});
setStatus("connected");
setWallet(sw);
} catch (e) {
console.error("Error connecting to smart wallet", e);
setStatus("disconnected");
throw e;
}
return sw;
},
[
context.activeChain.chainId,
create,
options,
personalWallet,
setStatus,
setWallet,
supportedWallets,
],
);
return {
connect,
predictAddress,
};
}
@@ -6,6 +6,14 @@ import {
import type { MagicConnector as MagicConnectorType } from "../connectors/magic/magic-connector";
import { MagicConnector } from "../connectors/magic/magic-connector";
/**
* @deprecated We have deprecated magicLink in favor of our embeddedWallet which adds support for more sign in methods.
* To learn more, please see:
*
* Our release notes: https://portal.thirdweb.com/embedded-wallet
*
* Our documentation: https://portal.thirdweb.com/react-native/react-native.embeddedwallet
*/
export class MagicLink extends AbstractClientWallet<
MagicLinkOptions,
MagicConnectorOptions
@@ -1,91 +0,0 @@
import React, { useState } from "react";
import { ActivityIndicator } from "react-native";
import { ConnectWalletHeader } from "../../../components/ConnectWalletFlow/ConnectingWallet/ConnectingWalletHeader";
import { Box, BaseButton, Text, TextInput } from "../../../components/base";
import {
useGlobalTheme,
useLocale,
} from "../../../providers/ui-context-provider";
export type EnterPasswordProps = {
goBack: () => void;
close: () => void;
};
export const AccountRecovery = ({ close, goBack }: EnterPasswordProps) => {
const l = useLocale();
const theme = useGlobalTheme();
const [errorMessage, setErrorMessage] = useState<string>();
const [checkingRecoveryCode, setCheckingRecoveryCode] =
useState<boolean>(false);
const [recoveryCode, setRecoveryCode] = useState<string>("");
const onNextPress = async () => {
setCheckingRecoveryCode(true);
// Call enter recovery code
setErrorMessage("test");
};
return (
<Box marginHorizontal="xl">
<ConnectWalletHeader
middleContent={
<Text variant="header">
{l.embedded_wallet.enter_account_recovery_code}
</Text>
}
subHeaderText={
"You should have a copy of this in the email address associated with your account"
}
onBackPress={goBack}
onClose={close}
/>
<TextInput
textInputProps={{
placeholder: "Enter your recovery code",
placeholderTextColor: theme.colors.textSecondary,
onChangeText: setRecoveryCode,
style: {
fontSize: 14,
color: theme.colors.textPrimary,
lineHeight: 16,
padding: 0,
flex: 1,
},
value: recoveryCode,
returnKeyType: "done",
autoCapitalize: "none",
autoCorrect: false,
autoComplete: "off",
clearTextOnFocus: false,
}}
containerProps={{
paddingHorizontal: "sm",
paddingVertical: "sm",
justifyContent: "flex-start",
}}
/>
{errorMessage ? (
<Text variant="error" numberOfLines={1}>
{errorMessage}
</Text>
) : (
<Box height={20} />
)}
<BaseButton
mt="sm"
alignItems="center"
height={theme.textVariants.bodySmallSecondary.fontSize}
onPress={onNextPress}
>
{checkingRecoveryCode ? (
<ActivityIndicator size={"small"} color={theme.colors.linkPrimary} />
) : (
<Text variant="bodySmallSecondary" color="linkPrimary">
{l.common.next}
</Text>
)}
</BaseButton>
</Box>
);
};
@@ -1,112 +0,0 @@
import React, { useEffect, useState } from "react";
import { ConnectWalletHeader } from "../../../components/ConnectWalletFlow/ConnectingWallet/ConnectingWalletHeader";
import { Box, BaseButton, Text, Toast } from "../../../components/base";
import {
useGlobalTheme,
useLocale,
} from "../../../providers/ui-context-provider";
import CopyIcon from "../../../assets/copy";
import CheckBox from "@react-native-community/checkbox";
import * as Clipboard from "expo-clipboard";
export type EnterPasswordProps = {
goBack: () => void;
close: () => void;
};
export const AccountRecovery = ({ close, goBack }: EnterPasswordProps) => {
const l = useLocale();
const theme = useGlobalTheme();
const [toggleCheckBox, setToggleCheckBox] = useState(false);
const [errorMessage] = useState<string>();
const [codeCopied, setCodeCopied] = useState(false);
useEffect(() => {
const timeout = setTimeout(() => {
if (codeCopied) {
setCodeCopied(false);
}
}, 2000);
return () => clearTimeout(timeout);
}, [codeCopied]);
const onNextPress = async () => {};
const onCodeCopyPress = async (code: string) => {
await Clipboard.setStringAsync(code);
setCodeCopied(true);
};
const codes = ["testing", "testing2", "testing3", "testing4", "testing5"];
return (
<Box marginHorizontal="xl">
<ConnectWalletHeader
middleContent={
<Text variant="header">{l.embedded_wallet.backup_your_account}</Text>
}
subHeaderText={
"Copy or download these codes and keep them safe. You will need these to recover access to your account if you forget your password. These have also been sent to the email address associated with your account"
}
onBackPress={goBack}
onClose={close}
/>
<Box mt="sm" flexDirection="column" marginTop="xl" mb="md">
{codes.map((code) => (
<BaseButton
key={code}
mt="xs"
flexDirection="row"
alignItems="center"
justifyContent="space-between"
borderColor="border"
borderWidth={1}
borderRadius="md"
onPress={() => {
onCodeCopyPress(code);
}}
p="md"
>
<Text variant="bodySmall">{code}</Text>
<CopyIcon width={14} height={14} color={theme.colors.iconPrimary} />
</BaseButton>
))}
</Box>
{errorMessage ? (
<Text variant="error" numberOfLines={1}>
{errorMessage}
</Text>
) : (
<Box height={20} />
)}
<Box flex={1} flexDirection="row" justifyContent="flex-end">
<BaseButton>
<CheckBox
disabled={false}
value={toggleCheckBox}
onValueChange={(newValue) => setToggleCheckBox(newValue)}
/>
<Text variant="bodySmallSecondary" color="linkPrimary">
{l.common.learn_more}
</Text>
</BaseButton>
<BaseButton
mt="sm"
flexDirection="row"
alignItems="center"
height={theme.textVariants.bodySmallSecondary.fontSize}
onPress={onNextPress}
>
<Text variant="bodySmallSecondary" color="linkPrimary">
{l.common.next}
</Text>
</BaseButton>
</Box>
{codeCopied === true ? (
<Toast text={"Address copied to clipboard"} />
) : null}
</Box>
);
};
@@ -5,9 +5,10 @@ import {
useSetConnectionStatus,
} from "@thirdweb-dev/react-core";
import { EmbeddedWallet } from "./EmbeddedWallet";
import React, { useRef, useEffect, useState } from "react";
import React, { useRef, useEffect, useState, useCallback } from "react";
import {
ActivityIndicator,
Keyboard,
NativeSyntheticEvent,
TextInput,
TextInputKeyPressEventData,
@@ -23,8 +24,14 @@ import {
useGlobalTheme,
useLocale,
} from "../../../providers/ui-context-provider";
import { EnterPassword } from "./EnterPassword";
import { RecoveryShareManagement } from "@thirdweb-dev/wallets";
const OTP_LENGTH = 6;
type ScreenToShow =
| "base"
| "create-password"
| "enter-password-or-recovery-code";
export const EmbeddedConnectionUI: React.FC<ConnectUIProps<EmbeddedWallet>> = ({
connected,
@@ -44,10 +51,77 @@ export const EmbeddedConnectionUI: React.FC<ConnectUIProps<EmbeddedWallet>> = ({
const [errorMessage, setErrorMessage] = useState<string>("");
const setConnectedWallet = useSetConnectedWallet();
const setConnectionStatus = useSetConnectionStatus();
const [password, setPassword] = useState("");
const [focusedIndex, setFocusedIndex] = useState<number | undefined>();
const [screen, setScreen] = useState<ScreenToShow>("base"); // TODO change
const address = useAddress();
const onError = useCallback((error: any) => {
clearCode();
console.error("Error validating otp: ", error);
setErrorMessage("Error validating the code");
setCheckingOtp(false);
setFocusedIndex(undefined);
}, []);
const postConnect = useCallback(
async (response: string) => {
if (response) {
if (onLocallyConnected) {
onLocallyConnected(selectionData.emailWallet);
} else {
await setConnectedWallet(selectionData.emailWallet);
setConnectionStatus("connected");
}
} else {
clearCode();
setErrorMessage(response || "Error validating the code");
setCheckingOtp(false);
setFocusedIndex(undefined);
}
},
[
onLocallyConnected,
selectionData.emailWallet,
setConnectedWallet,
setConnectionStatus,
],
);
const connect = useCallback(
async (_password?: string) => {
try {
const emailWallet = selectionData.emailWallet as EmbeddedWallet;
const otp = values.join("");
const authResult = await emailWallet.authenticate({
strategy: "email_verification",
verificationCode: otp,
email: selectionData.email,
recoveryCode: _password,
});
if (!authResult) {
onError(new Error("Failed to verify OTP"));
}
const response = await emailWallet.connect({
authResult,
});
postConnect(response);
} catch (error) {
onError(error);
}
},
[
onError,
postConnect,
selectionData.email,
selectionData.emailWallet,
values,
],
);
useEffect(() => {
if (address) {
setCheckingOtp(false);
@@ -55,50 +129,87 @@ export const EmbeddedConnectionUI: React.FC<ConnectUIProps<EmbeddedWallet>> = ({
}
}, [address, connected]);
useEffect(() => {
const onContinuePress = useCallback(() => {
if (
values.length === OTP_LENGTH &&
values.every((v) => v.length === 1) &&
selectionData
selectionData &&
screen === "base"
) {
setCheckingOtp(true);
setErrorMessage("");
setFocusedIndex(undefined);
const otp = values.join("");
Keyboard.dismiss();
setTimeout(() => {
(selectionData.emailWallet as EmbeddedWallet)
.connect({
loginType: "headless_email_otp_verification",
otp,
email: selectionData.email,
})
.then(async (response) => {
if (response) {
if (onLocallyConnected) {
onLocallyConnected(selectionData.emailWallet);
} else {
await setConnectedWallet(selectionData.emailWallet);
setConnectionStatus("connected");
}
} else {
clearCode();
setErrorMessage(response || "Error validating the code");
setCheckingOtp(false);
setFocusedIndex(undefined);
setTimeout(async () => {
const emailWallet = selectionData.emailWallet as EmbeddedWallet;
const needsRecoveryCode =
selectionData.recoveryShareManagement ===
RecoveryShareManagement.USER_MANAGED &&
(selectionData.isNewUser || selectionData.isNewDevice);
if (needsRecoveryCode) {
if (selectionData.isNewUser) {
console.log("setPassword", setPassword);
try {
const authResult = await emailWallet.authenticate({
strategy: "email_verification",
verificationCode: otp,
email: selectionData.email,
recoveryCode: password,
});
const response = await emailWallet.connect({
authResult,
});
postConnect(response);
} catch (e: any) {
onError(e);
}
})
.catch((error) => {
clearCode();
console.error("Error validating otp: ", error);
setErrorMessage("Error validating the code");
setCheckingOtp(false);
setFocusedIndex(undefined);
});
} else {
try {
await emailWallet.authenticate({
strategy: "email_verification",
verificationCode: otp,
email: selectionData.email,
});
} catch (e: any) {
if (e instanceof Error && e.message.includes("encryption key")) {
setScreen("enter-password-or-recovery-code");
} else {
onError(e);
}
}
}
} else {
// CLOUD_MANAGED
connect();
}
}, 0);
}
}, [connect, onError, password, postConnect, screen, selectionData, values]);
useEffect(() => {
if (
values.length === OTP_LENGTH &&
values.every((v) => v.length === 1) &&
selectionData &&
screen === "base" &&
selectionData.recoveryShareManagement ===
RecoveryShareManagement.CLOUD_MANAGED
) {
onContinuePress();
}
}, [
connect,
onContinuePress,
onError,
onLocallyConnected,
password,
postConnect,
screen,
selectionData,
setConnectedWallet,
setConnectionStatus,
@@ -109,7 +220,7 @@ export const EmbeddedConnectionUI: React.FC<ConnectUIProps<EmbeddedWallet>> = ({
if (value !== "") {
const copiedContent = await Clipboard.getStringAsync();
if (copiedContent.length === 6 && /^\d+$/.test(copiedContent)) {
if (copiedContent.length === 6) {
const newValues = [...copiedContent];
for (let i = 0; i < 6; i++) {
inputRefs.current[i]?.setNativeProps({ text: newValues[i] });
@@ -127,7 +238,7 @@ export const EmbeddedConnectionUI: React.FC<ConnectUIProps<EmbeddedWallet>> = ({
if (value === "" && index > 0) {
inputRefs.current[index - 1]?.focus();
} else if (value.length === 1 && /^[0-9]$/.test(value)) {
} else if (value.length === 1) {
if (index < 5) {
inputRefs.current[index + 1]?.focus();
}
@@ -157,7 +268,7 @@ export const EmbeddedConnectionUI: React.FC<ConnectUIProps<EmbeddedWallet>> = ({
setFocusedIndex(undefined);
setRequestingNewOtp(true);
(selectionData.emailWallet as EmbeddedWallet)
.sendEmailOTP(selectionData.email)
.sendVerificationEmail(selectionData.email)
.then(() => {})
.catch((error) => {
clearCode();
@@ -169,6 +280,37 @@ export const EmbeddedConnectionUI: React.FC<ConnectUIProps<EmbeddedWallet>> = ({
});
};
if (screen === "create-password") {
return (
<EnterPassword
close={goBack}
goBack={() => {
setScreen("base");
}}
email={selectionData.email}
type="create_password"
onPassword={connect}
error={errorMessage}
/>
);
}
if (screen === "enter-password-or-recovery-code") {
return (
<EnterPassword
close={goBack}
goBack={() => {
setScreen("base");
}}
email={selectionData.email}
type="enter_password"
onPassword={connect}
error={errorMessage}
/>
);
}
// TODO (ews) - check actual auth options
if (selectionData?.oauthOptions) {
return (
<EmbeddedSocialConnection
@@ -227,12 +369,15 @@ export const EmbeddedConnectionUI: React.FC<ConnectUIProps<EmbeddedWallet>> = ({
...styles.textInput,
color: theme.colors.textPrimary,
}}
keyboardType="number-pad"
editable={!checkingOtp}
keyboardType="default"
selectTextOnFocus={true}
onFocus={() => setFocusedIndex(index)}
maxLength={1}
returnKeyType={index === 5 ? "done" : "next"}
onSubmitEditing={
index === 5 ? () => onContinuePress() : undefined
}
onChangeText={(value) => handleInputChange(value, index)}
onKeyPress={(e) => handleKeyPress(e, index)}
/>
@@ -1,9 +1,10 @@
import React, { useEffect, useState } from "react";
import { EmbeddedWallet } from "./EmbeddedWallet";
import { ActivityIndicator } from "react-native";
import { ActivityIndicator, Keyboard } from "react-native";
import {
SelectUIProps,
useCreateWalletInstance,
useWallets,
} from "@thirdweb-dev/react-core";
import Box from "../../../components/base/Box";
import Text from "../../../components/base/Text";
@@ -11,22 +12,21 @@ 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,
useLocale,
} from "../../../providers/ui-context-provider";
import { EmbeddedWalletConfig } from "./embedded-wallet";
import { AuthProvider } from "@thirdweb-dev/wallets";
/**
* UI for selecting wallet - this UI is rendered in the wallet selection screen
*/
export const EmailSelectionUI: React.FC<
SelectUIProps<EmbeddedWallet> & {
oauthOptions?: OauthOptions;
email?: boolean;
auth?: EmbeddedWalletConfig["auth"];
}
> = ({ onSelect, walletConfig, oauthOptions, email }) => {
> = ({ onSelect, walletConfig, auth }) => {
const l = useLocale();
const theme = useGlobalTheme();
const [emailInput, setEmailInput] = useState<string>("");
@@ -34,8 +34,10 @@ export const EmailSelectionUI: React.FC<
const [isFetching, setIsFetching] = useState<boolean>(false);
const createWalletInstance = useCreateWalletInstance();
const [emailWallet, setEmailWallet] = useState<EmbeddedWallet | null>(null);
const supportedWallets = useWallets();
const isEmailEnabled = email === false ? false : true;
const isEmailEnabled = auth?.options.includes("email");
const isGoogleEnabled = auth?.options.includes("google");
useEffect(() => {
const emailWalletInstance = createWalletInstance(
@@ -54,15 +56,16 @@ export const EmailSelectionUI: React.FC<
if (validateEmail(emailInput)) {
setErrorMessage("");
setIsFetching(true);
Keyboard.dismiss();
emailWallet
?.sendEmailOTP(emailInput)
?.sendVerificationEmail(emailInput)
.then((response) => {
onSelect({
...response,
email: emailInput,
emailWallet,
oauthOptions: undefined,
oauthOptions: undefined, // TODO (ews) clean up types
});
})
.catch((error) => {
@@ -83,15 +86,15 @@ export const EmailSelectionUI: React.FC<
email: emailInput,
emailWallet,
oauthOptions: {
provider: oauthOptions?.providers[0],
redirectUrl: oauthOptions?.redirectUrl,
provider: AuthProvider.GOOGLE,
redirectUrl: auth?.redirectUrl,
},
});
};
return (
<Box paddingHorizontal="xl" mt="lg">
{oauthOptions?.providers.includes(AuthProvider.GOOGLE) ? (
{isGoogleEnabled ? (
<Box justifyContent="center">
<WalletButton
iconHeight={28}
@@ -106,7 +109,7 @@ export const EmailSelectionUI: React.FC<
walletIconUrl={GOOGLE_ICON}
onPress={onGoogleSignInPress}
/>
{isEmailEnabled ? (
{isEmailEnabled && supportedWallets.length === 1 ? (
<Box
mb="md"
mt="md"
@@ -124,7 +127,9 @@ export const EmailSelectionUI: React.FC<
</Text>
<Box height={1} flex={1} backgroundColor="border" />
</Box>
) : null}
) : (
<Box mt="md" />
)}
</Box>
) : null}
{isEmailEnabled ? (
@@ -142,6 +147,7 @@ export const EmailSelectionUI: React.FC<
padding: 0,
flex: 1,
},
onSubmitEditing: handleNetworkCall,
value: emailInput,
keyboardType: "email-address",
returnKeyType: "done",
@@ -21,30 +21,33 @@ export const EmbeddedSocialConnection: React.FC<
const handleGoogleLogin = useCallback(() => {
setErrorMessage("");
setTimeout(() => {
(selectionData.emailWallet as EmbeddedWallet)
.connect({
loginType: "headless_google_oauth",
setTimeout(async () => {
const emailWallet = selectionData.emailWallet as EmbeddedWallet;
try {
const authResult = await emailWallet.authenticate({
strategy: "google",
redirectUrl: selectionData.oauthOptions?.redirectUrl,
})
.then(async (response) => {
if (response) {
if (onLocallyConnected) {
onLocallyConnected(selectionData.emailWallet);
} else {
await setConnectedWallet(selectionData.emailWallet);
setConnectionStatus("connected");
}
} else {
setErrorMessage(
response || "Error login in. Please try again later.",
);
}
})
.catch((error) => {
console.error("Error logging in with google: ", error);
setErrorMessage("Error login in. Please try again later.");
});
const response = await emailWallet.connect({ authResult });
if (response) {
if (onLocallyConnected) {
onLocallyConnected(selectionData.emailWallet);
} else {
await setConnectedWallet(selectionData.emailWallet);
setConnectionStatus("connected");
}
} else {
setErrorMessage(
response || "Error login in. Please try again later.",
);
}
} catch (error) {
console.error("Error logging in with google: ", error);
setErrorMessage(`Error login in. ${error}`);
}
}, 0);
}, [
onLocallyConnected,
@@ -79,9 +82,7 @@ export const EmbeddedSocialConnection: React.FC<
{!errorMessage ? (
<ActivityIndicator size="large" />
) : (
<Text variant="error" numberOfLines={1}>
{errorMessage}
</Text>
<Text variant="error">{errorMessage}</Text>
)}
</Box>
</Box>
@@ -4,12 +4,11 @@ import {
EmbeddedWalletAdditionalOptions,
walletIds,
} from "@thirdweb-dev/wallets";
import type { EmbeddedWalletConnector as EmbeddedConnectorType } from "../../connectors/embedded-wallet/embedded-connector";
import type { EmbeddedWalletConnector } from "../../connectors/embedded-wallet/embedded-connector";
import {
AuthParams,
EmbeddedWalletConnectionArgs,
OauthOption,
} from "../../connectors/embedded-wallet/types";
import { EmbeddedWalletConnector } from "../../connectors/embedded-wallet/embedded-connector";
import { EMAIL_WALLET_ICON } from "../../../assets/svgs";
export type EmbeddedWalletOptions =
@@ -19,9 +18,19 @@ export class EmbeddedWallet extends AbstractClientWallet<
EmbeddedWalletOptions,
EmbeddedWalletConnectionArgs
> {
connector?: EmbeddedConnectorType;
connector?: EmbeddedWalletConnector;
options: EmbeddedWalletOptions;
static async sendVerificationEmail(options: {
email: string;
clientId: string;
}) {
const { sendVerificationEmail } = await import(
"../../connectors/embedded-wallet/embedded/auth"
);
return sendVerificationEmail(options);
}
static meta = {
name: "Embedded Wallet",
iconURL: EMAIL_WALLET_ICON,
@@ -39,14 +48,17 @@ export class EmbeddedWallet extends AbstractClientWallet<
this.setupListeners();
}
async getConnector(): Promise<EmbeddedConnectorType> {
async getConnector(): Promise<EmbeddedWalletConnector> {
if (!this.connector) {
return await this.initializeConnector();
}
return this.connector;
}
initializeConnector() {
async initializeConnector() {
const { EmbeddedWalletConnector } = await import(
"../../connectors/embedded-wallet/embedded-connector"
);
this.connector = new EmbeddedWalletConnector({
...this.options,
clientId: this.options.clientId,
@@ -56,16 +68,13 @@ export class EmbeddedWallet extends AbstractClientWallet<
return this.connector;
}
async validateEmailOTP(otp: string) {
return this.connector?.validateEmailOtp({ otp });
async sendVerificationEmail(email: string) {
return this.connector?.sendVerificationEmail({ email });
}
async sendEmailOTP(email: string) {
return this.connector?.sendEmailOtp({ email });
}
async socialLogin(oauthOption: OauthOption) {
return this.connector?.socialLogin(oauthOption);
async authenticate(params: AuthParams) {
const connector = (await this.getConnector()) as EmbeddedWalletConnector;
return connector.authenticate(params);
}
onConnected = () => {
@@ -1,4 +1,4 @@
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import { ActivityIndicator } from "react-native";
import { ConnectWalletHeader } from "../../../components/ConnectWalletFlow/ConnectingWallet/ConnectingWalletHeader";
import { Box, BaseButton, Text } from "../../../components/base";
@@ -7,12 +7,15 @@ import {
useLocale,
} from "../../../providers/ui-context-provider";
import { PasswordInput } from "../../../components/PasswordInput";
import Checkbox from "../../../components/base/CheckBox";
export type EnterPasswordProps = {
goBack: () => void;
close: () => void;
email: string;
type: "create_password" | "enter_password";
onPassword: (password: string) => void;
error: string;
};
export const EnterPassword = ({
@@ -20,49 +23,68 @@ export const EnterPassword = ({
goBack,
email,
type,
onPassword,
error,
}: EnterPasswordProps) => {
const l = useLocale();
const theme = useGlobalTheme();
const [errorMessage, setErrorMessage] = useState<string>();
const [checkingPass, setCheckingPass] = useState<boolean>(false);
const [password, setPassword] = useState<string>("");
const [toggleCheckBox, setToggleCheckBox] = useState<boolean>(false);
const isCreatePassword = type === "create_password";
const onNextPress = async () => {
setCheckingPass(true);
if (isCreatePassword) {
// Call create password
console.log("[TODO] Implement", password);
} else {
// Call enter password
setErrorMessage("test");
useEffect(() => {
if (error) {
setCheckingPass(false);
}
}, [error]);
const onNextPress = async () => {
if (!toggleCheckBox) {
return;
}
setCheckingPass(true);
onPassword(password);
};
const onForgotPress = () => {};
const onLearnMorePress = () => {};
const onForgotPress = () => {
// console.log("[TODO] Implement", setErrorMessage);
};
return (
<Box marginHorizontal="xl">
<Box marginHorizontal="xl" mb="sm">
<ConnectWalletHeader
middleContent={
<Text variant="header">
{isCreatePassword ? "Create password" : "Enter password"}
{isCreatePassword
? l.embedded_wallet.create_password
: l.embedded_wallet.enter_password}
</Text>
}
subHeaderText={
isCreatePassword
? "Set a password for your account"
: `Enter the password for email: ${email}`
? l.embedded_wallet.set_password_message
: `${l.embedded_wallet.enter_password_for_email}: ${email}`
}
onBackPress={goBack}
onClose={close}
/>
<Box mt="sm" flexDirection="column" marginTop="xl" mb="md">
<Text variant="bodySmallBold" mt="xs">
{l.embedded_wallet.make_sure_you_save_it}
</Text>
<Box mt="sm" flexDirection="column" marginTop="xl">
<PasswordInput onChangeText={setPassword} />
<BaseButton mt="md">
<Checkbox
label="I have saved my password"
color={theme.colors.linkPrimary}
onToggle={setToggleCheckBox}
/>
</BaseButton>
{isCreatePassword ? null : (
<BaseButton
mt="sm"
@@ -76,40 +98,38 @@ export const EnterPassword = ({
</BaseButton>
)}
</Box>
{errorMessage ? (
<Text variant="error" numberOfLines={1}>
{errorMessage}
{error ? (
<Text variant="error" numberOfLines={1} mt="md">
{error}
</Text>
) : (
<Box height={20} />
)}
<Box flex={1} flexDirection="row" justifyContent="flex-end">
{isCreatePassword ? (
) : null}
{/* {isCreatePassword ? (
<BaseButton onPress={onLearnMorePress}>
<Text variant="bodySmallSecondary" color="linkPrimary">
{l.common.learn_more}
</Text>
</BaseButton>
) : null}
<BaseButton
mt="sm"
flexDirection="row"
alignItems="center"
height={theme.textVariants.bodySmallSecondary.fontSize}
onPress={onNextPress}
>
{checkingPass ? (
<ActivityIndicator
size={"small"}
color={theme.colors.linkPrimary}
/>
) : (
<Text variant="bodySmallSecondary" color="linkPrimary">
{l.common.next}
</Text>
)}
</BaseButton>
</Box>
) : null} */}
<BaseButton
mt="md"
paddingVertical="md"
borderRadius="lg"
borderWidth={1}
borderColor="border"
backgroundColor="accentButtonColor"
onPress={onNextPress}
>
{checkingPass ? (
<ActivityIndicator
size={"small"}
color={theme.colors.accentButtonTextColor}
/>
) : (
<Text variant="bodySmallBold" color="accentButtonTextColor">
{l.common.next}
</Text>
)}
</BaseButton>
</Box>
);
};
@@ -6,20 +6,14 @@ 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
type AuthOption = "email" | "google";
export type EmbeddedWalletConfig = {
// @default true - set false to disable
email?: boolean;
oauthOptions?:
| {
providers: OAuthProvider[];
redirectUrl: string;
}
| false;
auth?: {
options: AuthOption[];
redirectUrl?: string;
};
};
export const embeddedWallet = (
@@ -28,16 +22,11 @@ export const embeddedWallet = (
const selectUI = (props: SelectUIProps<EmbeddedWallet>) => (
<EmailSelectionUI
{...props}
oauthOptions={
config?.oauthOptions
? {
providers: [AuthProvider.GOOGLE],
redirectUrl: config.oauthOptions.redirectUrl,
}
: undefined
auth={
config?.auth || {
options: ["email"],
}
}
// you cannot disable both email and oauth
email={!config?.oauthOptions && !config?.email ? true : config?.email}
/>
);
@@ -21,6 +21,18 @@ import React from "react";
import { ConnectingWallet } from "../../components/ConnectWalletFlow/ConnectingWallet/ConnectingWallet";
import { useGlobalTheme, useLocale } from "../../providers/ui-context-provider";
/**
*
* @param magicLinkOptions Options available to create a magic link wallet
* @returns A WalletConfig<MagicLink> object
*
* @deprecated We have deprecated magicLink in favor of our embeddedWallet which adds support for more sign in methods.
* To learn more, please see:
*
* Our release notes: https://portal.thirdweb.com/embedded-wallet
*
* Our documentation: https://portal.thirdweb.com/react-native/react-native.embeddedwallet
*/
export const magicLink = (
magicLinkOptions: MagicLinkOptions & { recommended?: boolean },
): WalletConfig<MagicLink> => {
+2 -1
View File
@@ -1,9 +1,10 @@
module.exports = {
root: true,
extends: ["thirdweb"],
extends: ["thirdweb", "plugin:i18next/recommended"],
plugins: ["better-tree-shaking"],
rules: {
"@next/next/no-img-element": "off",
"better-tree-shaking/no-top-level-side-effects": "error",
"i18next/no-literal-string": 2,
},
};
+185
View File
@@ -1,5 +1,190 @@
# @thirdweb-dev/react
## 4.1.2
### Patch Changes
- [#1906](https://github.com/thirdweb-dev/js/pull/1906) [`b90d3af5`](https://github.com/thirdweb-dev/js/commit/b90d3af5b8cfb762b38a88d7997879be9b5744bf) Thanks [@kien-ngo](https://github.com/kien-ngo)! - Add `hideSwitchToPersonalWallet` prop on `ConnectWallet` component to hide the "switch to Personal wallet" option in the ConnectWallet dropdown which is shown when wallet is connected to either Smart Wallet or Safe
```tsx
<ConnectWallet hideSwitchToPersonalWallet={true} > // default: false
```
- [#1891](https://github.com/thirdweb-dev/js/pull/1891) [`20d0f53b`](https://github.com/thirdweb-dev/js/commit/20d0f53b46a2975a0a14ddaa4a377f2f758804db) Thanks [@kien-ngo](https://github.com/kien-ngo)! - Add Core wallet (Avalanche)
- [#1847](https://github.com/thirdweb-dev/js/pull/1847) [`c34ac468`](https://github.com/thirdweb-dev/js/commit/c34ac468fbf983a6806cd88828c77eb6d7231e78) Thanks [@MananTank](https://github.com/MananTank)! - Add Localization API to change language used in components `ConnectWallet` and `Web3Button` and/or override the default texts
### Japanese
```tsx
import { ja } from "@thirdweb-dev/react";
const japanese = ja();
<ThirdwebProvider locale={japanese}>
<App />
</ThirdwebProvider>;
```
### Spanish
```tsx
import { es } from "@thirdweb-dev/react";
const spanish = es();
<ThirdwebProvider locale={spanish}>
<App />
</ThirdwebProvider>;
```
### English ( default )
```tsx
import { en } from "@thirdweb-dev/react";
const english = en();
<ThirdwebProvider locale={english}>
<App />
</ThirdwebProvider>;
```
This API also allows overriding the default texts in the locale object. You can override all the texts or just some parts
```tsx
import { en } from "@thirdweb-dev/react";
// override some texts
const english = en({
connectWallet: {
confirmInWallet: "Confirm in your wallet",
},
wallets: {
metamaskWallet: {
connectionScreen: {
inProgress: "Awaiting Confirmation",
instruction: "Accept connection request in your MetaMask wallet",
},
},
},
});
<ThirdwebProvider locale={english}>
<App />
</ThirdwebProvider>;
```
- Updated dependencies [[`0dcb15b7`](https://github.com/thirdweb-dev/js/commit/0dcb15b7e647acd038b0ec8b1a2b200808aae00e), [`fefeaeef`](https://github.com/thirdweb-dev/js/commit/fefeaeef18f52a75462c43da8b85a77975d32e01), [`20d0f53b`](https://github.com/thirdweb-dev/js/commit/20d0f53b46a2975a0a14ddaa4a377f2f758804db), [`91c2352b`](https://github.com/thirdweb-dev/js/commit/91c2352b03d7cf1c3cbbbc98846f248b0f707ea7)]:
- @thirdweb-dev/[email protected]
- @thirdweb-dev/[email protected]
- @thirdweb-dev/[email protected]
- @thirdweb-dev/[email protected]
## 4.1.1
### Patch Changes
- [#1888](https://github.com/thirdweb-dev/js/pull/1888) [`789700cf`](https://github.com/thirdweb-dev/js/commit/789700cf60ac40d4cc2742c8aa735a225089c522) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Fix sign in connect button display logic
- Updated dependencies []:
- @thirdweb-dev/[email protected]
## 4.1.0
### Minor Changes
- [#1846](https://github.com/thirdweb-dev/js/pull/1846) [`0acc530f`](https://github.com/thirdweb-dev/js/commit/0acc530f8bbee59672d9705724edc278bd853d9a) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - New API to configure authentication options on `embeddedWallet`
```ts
// default - google sign in is enabled
embeddedWallet();
// this is same as
embeddedWallet({
auth: {
options: ["email", "google"],
},
});
// only email
embeddedWallet({
auth: {
options: ["email"],
},
});
// only google sign in
embeddedWallet({
auth: {
options: ["google"],
},
});
```
### Patch Changes
- [#1885](https://github.com/thirdweb-dev/js/pull/1885) [`961ef644`](https://github.com/thirdweb-dev/js/commit/961ef644d1b1adaad08c071903cd53aacac50bb4) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Improved `useSmartWallet()` hook
Example with metamask:
```ts
const { connect } = useSmartWallet(metamaskWallet(), {
factoryAddress: factoryAddress,
gasless: true,
});
const onClick = async () => {
// nothing to do here, all handled internally
await connect();
};
```
```ts
Example with localWallet:
const { connect } = useSmartWallet(localWallet(), {
factoryAddress: factoryAddress,
gasless: true,
});
const onClick = async () => {
// function to 'load' the local wallet before using it
await connect({
connectPersonalWallet: async (w) => {
await w.generate();
await w.connect();
}
});
}
```
```ts
Example with embeddedWallet:
const { connect } = useSmartWallet(embeddedWallet(), {
factoryAddress: factoryAddress,
gasless: true,
});
const onClick = async () => {
// function to 'auth' the embedded wallet before using it
await connect({
connectPersonalWallet: async (w) => {
const authResult = await w.authenticate({ strategy: "google" });
await w.connect({ authResult });
}
});
}
```
- [#1886](https://github.com/thirdweb-dev/js/pull/1886) [`6fcd09dd`](https://github.com/thirdweb-dev/js/commit/6fcd09ddbdad2c5e02fad9c3d2c61348ebd987de) Thanks [@edwardysun](https://github.com/edwardysun)! - Fix for user address and active wallet address out of sync
- Updated dependencies [[`a9d32f3c`](https://github.com/thirdweb-dev/js/commit/a9d32f3c90251a459e17a19eca803bbfdeeaeb79), [`0acc530f`](https://github.com/thirdweb-dev/js/commit/0acc530f8bbee59672d9705724edc278bd853d9a)]:
- @thirdweb-dev/[email protected]
- @thirdweb-dev/[email protected]
- @thirdweb-dev/[email protected]
## 4.0.10
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/react",
"version": "4.0.10",
"version": "4.1.2",
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/react",
"author": "thirdweb eng <[email protected]>",
"license": "Apache-2.0",
+6 -3
View File
@@ -12,6 +12,10 @@ export const DragNDrop: React.FC<{
extension: string;
accept: string;
onUpload: (file: File) => void;
locale: {
wrongFileError: string;
uploadedSuccessfully: string;
};
}> = (props) => {
const [error, setError] = useState(false);
const [uploaded, setUploaded] = useState<File | undefined>();
@@ -105,8 +109,7 @@ export const DragNDrop: React.FC<{
<Spacer y="lg" />
{error ? (
<Text color="danger" size="sm">
{" "}
Please upload a {props.extension} file{" "}
{props.locale.wrongFileError}
</Text>
) : (
<Text size="sm"> {props.extension} </Text>
@@ -115,7 +118,7 @@ export const DragNDrop: React.FC<{
) : (
<>
<Text weight={600} color="primaryText" center multiline>
{uploaded.name} uploaded successfully
{uploaded.name} {props.locale.uploadedSuccessfully}
</Text>
<Spacer y="md" />
<Container color="success">
+5 -5
View File
@@ -7,10 +7,10 @@ import {
iconSize,
} from "../design-system";
import {
widemodalMaxHeight,
wideModalMaxHeight,
modalMaxWidthCompact,
modalMaxWidthWide,
compactmodalMaxHeight,
compactModalMaxHeight,
modalCloseFadeOutDuration,
} from "../wallet/ConnectWallet/constants";
import { Overlay } from "./Overlay";
@@ -88,7 +88,7 @@ export const Modal: React.FC<{
? { width: 0, height: 0, overflow: "hidden", opacity: 0 }
: {
height:
props.size === "compact" ? "auto" : widemodalMaxHeight,
props.size === "compact" ? "auto" : wideModalMaxHeight,
maxWidth:
props.size === "compact"
? modalMaxWidthCompact
@@ -97,7 +97,7 @@ export const Modal: React.FC<{
}
>
{props.size === "compact" ? (
<DynamicHeight maxHeight={compactmodalMaxHeight}>
<DynamicHeight maxHeight={compactModalMaxHeight}>
{props.children}{" "}
</DynamicHeight>
) : (
@@ -174,7 +174,7 @@ const DialogContent = styled.div<{ theme?: Theme }>`
box-sizing: border-box;
animation: ${modalAnimationDesktop} 300ms ease;
box-shadow: ${shadow.lg};
line-height: 1;
line-height: normal;
border: 1px solid ${(p) => p.theme.colors.borderColor};
outline: none;
overflow: hidden;
+7 -12
View File
@@ -11,9 +11,9 @@ export function OTPInput(props: {
setValue: (value: string) => void;
onEnter?: () => void;
}) {
const otp = props.value.split("").map(Number);
const otp = props.value.split("");
const setOTP = (newOTP: number[]) => {
const setOTP = (newOTP: string[]) => {
props.setValue(newOTP.join(""));
};
@@ -37,18 +37,13 @@ export function OTPInput(props: {
ref={(e) => (boxEls.current[i] = e)}
key={i}
value={otp[i] ?? ""}
type="number"
pattern="[0-9]*"
type="text"
pattern="[a-zA-Z0-9]*"
variant="outline"
inputMode="numeric"
inputMode="text"
onPaste={(e) => {
const pastedData = e.clipboardData.getData("text/plain");
const newOTP = pastedData
.slice(0, props.digits)
.split("")
.filter((n) => /\d/.test(n))
.map(Number);
const newOTP = pastedData.slice(0, props.digits).split("");
setOTP(newOTP);
e.preventDefault();
}}
@@ -110,7 +105,7 @@ export function OTPInput(props: {
const newOTP = [...otp];
const index = i > inputToFocusIndex - 1 ? inputToFocusIndex : i;
newOTP[index] = Number(value);
newOTP[index] = value;
setOTP(newOTP);
}}
/>
+2
View File
@@ -16,6 +16,8 @@ export const QRCode: React.FC<{
<div
style={{
position: "relative",
display: "flex",
justifyContent: "center",
}}
>
{props.qrCodeUri ? (
+1 -1
View File
@@ -46,7 +46,7 @@ const TooltipContent = /* @__PURE__ */ (() => styled(RadixTooltip.Content)<{
background: ${(p) => p.theme.colors.tooltipBg};
color: ${(p) => p.theme.colors.tooltipText};
border-radius: ${radius.sm};
line-height: 1;
line-height: normal;
padding: ${spacing.sm} ${spacing.md};
font-size: ${fontSize.sm};
box-shadow: ${shadow.sm};
+3 -1
View File
@@ -5,6 +5,7 @@ export const Button = styled.button<{
variant: "primary" | "secondary" | "link" | "accent" | "outline";
theme?: Theme;
fullWidth?: boolean;
gap?: keyof typeof spacing;
}>`
all: unset;
cursor: pointer;
@@ -17,9 +18,10 @@ export const Button = styled.button<{
font-weight: 500;
box-sizing: border-box;
-webkit-tap-highlight-color: transparent;
line-height: 1;
line-height: normal;
flex-shrink: 0;
transition: border 200ms ease;
gap: ${(p) => (p.gap && spacing[p.gap]) || 0};
${(p) => p.fullWidth && `width: 100%;`};
+5 -2
View File
@@ -16,9 +16,12 @@ export const Text = styled.span<{
margin: 0;
display: ${(p) => (p.inline ? "inline" : "block")};
font-weight: ${(p) => p.weight || 500};
line-height: ${(p) => (p.multiline ? 1.5 : 1)};
line-height: ${(p) => (p.multiline ? 1.5 : "normal")};
${(p) => (p.center ? `text-align: center;` : "")};
text-wrap: ${(p) => (p.balance ? "balance" : "inherit")};
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
`;
export const Link = styled.a<{
@@ -38,7 +41,7 @@ export const Link = styled.a<{
text-align: ${(p) => (p.center ? "center" : "left")};
display: ${(p) => (p.inline ? "inline" : "block")};
font-weight: ${(p) => p.weight || 500};
line-height: 1;
line-height: normal;
transition: color 0.2s ease;
&:hover {
@@ -22,6 +22,7 @@ import { PropsWithChildren, useState } from "react";
import invariant from "tiny-invariant";
import { CustomThemeProvider } from "../../../design-system/CustomThemeProvider";
import { useTheme } from "@emotion/react";
import { useTWLocale } from "../../providers/locale-provider";
type ActionFn = (contract: SmartContract) => any;
@@ -48,7 +49,11 @@ interface Web3ButtonProps<TActionFn extends ActionFn> {
style?: React.CSSProperties;
connectWallet?: Omit<
ConnectWalletProps,
"detailsBtn" | "hideTestnetFaucet" | "switchToActiveChain" | "theme"
| "detailsBtn"
| "hideTestnetFaucet"
| "switchToActiveChain"
| "theme"
| "hideSwitchToPersonalWallet"
>;
}
@@ -104,6 +109,8 @@ export const Web3Button = <TAction extends ActionFn>(
const contextTheme = useTheme() as ThemeObjectOrType;
const theme = props.theme || contextTheme || "dark";
const locale = useTWLocale();
const [confirmStatus, setConfirmStatus] = useState<"idle" | "waiting">(
"idle",
);
@@ -185,7 +192,7 @@ export const Web3Button = <TAction extends ActionFn>(
{confirmStatus === "waiting" ? (
<Spinner size="sm" color={"primaryButtonText"} />
) : (
"Switch Network"
locale.connectWallet.switchNetwork
)}
</Button>
);
@@ -193,7 +200,7 @@ export const Web3Button = <TAction extends ActionFn>(
if (requiresConfirmation) {
button = (
<Popover
content={<span>Confirm in Wallet</span>}
content={<span>{locale.connectWallet.confirmInWallet}</span>}
open={confirmStatus === "waiting"}
onOpenChange={(isOpen) => {
if (!isOpen) {
@@ -0,0 +1,22 @@
import { useWalletContext } from "@thirdweb-dev/react-core";
import { EmbeddedWallet } from "@thirdweb-dev/wallets";
import { useCallback } from "react";
export const useEmbeddedWalletSendVerificationEmail = () => {
const context = useWalletContext();
const sendVerificationEmail = useCallback(
async (email: string) => {
if (!context.clientId) {
throw new Error("Please, provide a clientId in the ThirdwebProvider");
}
return EmbeddedWallet.sendVerificationEmail({
email,
clientId: context.clientId,
});
},
[context.clientId],
);
return sendVerificationEmail;
};
@@ -0,0 +1,74 @@
import {
useCreateWalletInstance,
useSetConnectedWallet,
useSetConnectionStatus,
useWalletContext,
useWallets,
} from "@thirdweb-dev/react-core";
import {
walletIds,
type AuthParams,
EmbeddedWallet,
} from "@thirdweb-dev/wallets";
import { useCallback } from "react";
import { embeddedWallet } from "../../../wallet/wallets/embeddedWallet/embeddedWallet";
/**
* Hook to authenticate and connect to an embedded wallet
* @returns a function to connect to an embedded wallet
*/
export function useEmbeddedWallet() {
const create = useCreateWalletInstance();
const setStatus = useSetConnectionStatus();
const setWallet = useSetConnectedWallet();
const context = useWalletContext();
const wallets = useWallets();
const connect = useCallback(
async (authParams: AuthParams) => {
setStatus("connecting");
const config = wallets.find((w) => w.id === walletIds.embeddedWallet);
if (
!config ||
(config.personalWallets && config.personalWallets.length > 0)
) {
console.warn(
"Embedded wallet not configured in ThirdwebProvider.supportedWallets - add it to enable autoconnect on page load",
);
}
const wallet = create(embeddedWallet());
try {
const authResult = await wallet.authenticate(authParams);
await wallet.connect({ authResult });
setWallet(wallet);
setStatus("connected");
} catch (e) {
console.error("Error connecting to embedded wallet", e);
setStatus("disconnected");
}
return wallet;
},
[setStatus, wallets, create, setWallet],
);
const sendVerificationEmail = useCallback(
async ({ email }: { email: string }) => {
const clientId = context.clientId;
if (!clientId) {
throw new Error(
"clientId not found, please add it to ThirdwebProvider",
);
}
return EmbeddedWallet.sendVerificationEmail({
email,
clientId,
});
},
[context],
);
return {
connect,
sendVerificationEmail,
};
}
@@ -1,21 +1,94 @@
import { useConnect } from "@thirdweb-dev/react-core";
import {
type WalletConfig,
WalletInstance,
useCreateWalletInstance,
useSetConnectedWallet,
useSetConnectionStatus,
useWalletContext,
useWallets,
} from "@thirdweb-dev/react-core";
import type { SmartWalletConfigOptions } from "../../../wallet/wallets/smartWallet/types";
import type { SmartWalletConnectionArgs } from "@thirdweb-dev/wallets/evm/wallets/smart-wallet";
import {
getSmartWalletAddress,
type SmartWalletConnectionArgs,
} from "@thirdweb-dev/wallets/evm/wallets/smart-wallet";
import { useCallback } from "react";
import { WalletConfig } from "@thirdweb-dev/react-core/dist/declarations/src";
import type { BytesLike } from "ethers";
export function useSmartWallet() {
const connect = useConnect();
return useCallback(
async (
wallet: WalletConfig<any>,
options: SmartWalletConnectionArgs & SmartWalletConfigOptions,
) => {
export function useSmartWallet<W extends WalletInstance>(
personalWallet: WalletConfig<W>,
options: SmartWalletConfigOptions,
) {
const create = useCreateWalletInstance();
const setStatus = useSetConnectionStatus();
const setWallet = useSetConnectedWallet();
const context = useWalletContext();
const supportedWallets = useWallets();
const predictAddress = useCallback(
async (args: { personalWalletAddress: string; data?: BytesLike }) => {
return getSmartWalletAddress(
context.activeChain,
options.factoryAddress,
args.personalWalletAddress,
args.data,
);
},
[context.activeChain, options.factoryAddress],
);
const connect = useCallback(
async (args?: {
connectPersonalWallet?: (wallet: W) => Promise<void>;
connectionArgs?: Omit<SmartWalletConnectionArgs, "personalWallet">;
}) => {
if (!supportedWallets.find((w) => w.id === personalWallet.id)) {
console.warn(
"Please, add your smart wallet to the supportedWallets prop of the ThirdwebProvider to enjoy auto-connecting to the wallet.",
);
}
const { smartWallet } = await import(
"../../../wallet/wallets/smartWallet/smartWallet"
);
return connect(smartWallet(wallet, options), options);
setStatus("connecting");
const pw = create(personalWallet);
const sw = create(smartWallet(personalWallet, options));
try {
if (args?.connectPersonalWallet) {
// if passed in, use custom function to connect personal wallet
await args.connectPersonalWallet(pw);
} else {
// otherwise default to auto-connecting personal wallet with chainId
await pw.connect({
chainId: context.activeChain.chainId,
});
}
await sw.connect({
...args?.connectionArgs,
personalWallet: pw,
});
setStatus("connected");
setWallet(sw);
} catch (e) {
console.error("Error connecting to smart wallet", e);
setStatus("disconnected");
throw e;
}
return sw;
},
[connect],
[
context.activeChain.chainId,
create,
options,
personalWallet,
setStatus,
setWallet,
supportedWallets,
],
);
return {
connect,
predictAddress,
};
}

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