Compare commits

...
Author SHA1 Message Date
github-actions[bot]GitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
b6b52ea56b Version Packages (#1360)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-07-18 18:36:39 -07:00
Tair AsimandGitHub da95dce580 [service-utils] handle port in the origin check (#1365) 2023-07-18 21:43:35 +00:00
iketwandGitHub 44dbf283c9 [RN/Storage] Use storage.downloader in RN and pass the clientId (#1362) 2023-07-18 21:04:00 +00:00
+1 d844714609 [All Packages] Pass keys in all packages (#1316)
Co-authored-by: Jonas Daniels <[email protected]>
Co-authored-by: Manan Tank <[email protected]>
Co-authored-by: Joaquim Verges <[email protected]>
Co-authored-by: Mariano Fuentes <[email protected]>
Co-authored-by: Mariano Fuentes <[email protected]>
Co-authored-by: Adam Majmudar <[email protected]>
Co-authored-by: Farhan Khwaja <[email protected]>
Co-authored-by: Nacho Iacovino <[email protected]>
Co-authored-by: 0xFirekeeper <[email protected]>
2023-07-17 23:54:41 +00:00
github-actions[bot]GitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
5cea8436c1 Version Packages (#1341)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-07-17 15:10:05 +02:00
Joaquim VergesandGitHub dac8fa7d98 [SDK] Fix getOwned missing last owner for OpenEdition contracts (#1357) 2023-07-15 22:13:58 +00:00
Joaquim VergesandGitHub 7a6534d1a9 [React] Fix wrong enabled logic in useOwnedNFTs (#1356) 2023-07-15 05:02:06 +00:00
Tair AsimandGitHub d5aee62bf4 [service-utils] remove unused type (#1355) 2023-07-14 23:33:28 +00:00
Tair AsimandGitHub 0bfdedd4ba [service-utils] fix enforce auth param (#1354) 2023-07-14 23:18:10 +00:00
Joaquim VergesandGitHub c5761b99e9 [Wallets] Switch local wallet to seed based wallets (#1352) 2023-07-14 23:07:59 +00:00
Tair AsimandGitHub 42d9ab68d9 [service-utils] allow to skip auth for backwards compat (#1353) 2023-07-14 22:27:14 +00:00
farhanW3andGitHub fd0650dcac Farhan/service utils fix (#1351) 2023-07-14 20:52:30 +00:00
Tair AsimandGitHub ddb3dabe69 [service-utils] allow target addresses and api url stringy (#1349) 2023-07-14 19:17:28 +00:00
Manan TankandGitHub b96a228273 [wallets, react] Various improvements (#1350) 2023-07-14 18:26:26 +00:00
Tair AsimandGitHub 781e26d896 [service-utils] authorize bundle (#1347) 2023-07-14 16:47:22 +00:00
Tair AsimandGitHub f7392138ee [service-utils] api url fix (#1346) 2023-07-14 09:52:23 +00:00
Jonas DanielsandGitHub 5eb6fc1bf3 [service-utils] - rewrite (#1345) 2023-07-14 08:21:03 +00:00
Danny FridayandGitHub 7dc685bbec Add creatorWalletAddress to ApiKey type (#1343) 2023-07-14 04:11:26 +00:00
Jonas DanielsandGitHub 4915ac50f8 [service-utils] add relayer service definition (#1342) 2023-07-14 04:02:47 +00:00
Jonas DanielsandGitHub d724ad85c5 [service-utils] - fix use endpoint (#1340) 2023-07-14 00:27:25 +00:00
github-actions[bot]GitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
1d32b299a0 Version Packages (#1337)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-07-13 16:02:54 -07:00
Tair AsimandGitHub cfa7ecbc9a service-utils: remove crypt + unfetch (#1339) 2023-07-13 22:20:42 +00:00
Jonas DanielsandGitHub 8dd7540c45 [storage] - remove HEAD request pre upload (#1338) 2023-07-13 21:57:30 +00:00
07c7d97c28 [service-utils] - initially add package (#1333)
Co-authored-by: Tair Asim <[email protected]>
2023-07-13 20:51:55 +00:00
259 changed files with 7805 additions and 2013 deletions
+1
View File
@@ -131,3 +131,4 @@ jobs:
run: pnpm e2e
env:
NODE_OPTIONS: "--max_old_space_size=4096"
CLI_E2E_API_KEY: ${{ secrets.CLI_E2E_API_KEY }}
+4 -4
View File
@@ -38,15 +38,15 @@
"@changesets/get-dependents-graph": "^1.3.6",
"@changesets/get-release-plan": "^3.0.17",
"@changesets/parse": "^0.3.16",
"@manypkg/cli": "^0.20.0",
"@manypkg/cli": "^0.21.0",
"@manypkg/get-packages": "^1.1.3",
"@playwright/test": "1.31.2",
"@trivago/prettier-plugin-sort-imports": "^4.1.1",
"@types/node": "^18.16.18",
"eslint": "^8.43.0",
"eslint": "^8.45.0",
"mocha": "^10.2.0",
"prettier": "^2.8.8",
"turbo": "^1.10.6",
"prettier": "^3.0.0",
"turbo": "^1.10.7",
"yalc": "1.0.0-pre.53"
},
"manypkg": {
+21
View File
@@ -1,5 +1,26 @@
# @thirdweb-dev/auth
## 3.2.17
### Patch Changes
- Updated dependencies [[`d8447146`](https://github.com/thirdweb-dev/js/commit/d8447146092c1962f410155ab2047225453aaa2b)]:
- @thirdweb-dev/wallets@1.1.0
## 3.2.16
### Patch Changes
- Updated dependencies [[`c5761b99`](https://github.com/thirdweb-dev/js/commit/c5761b99e9797772481506e90cbfa5c35a05bd1d), [`b96a2282`](https://github.com/thirdweb-dev/js/commit/b96a2282730582b684cc96802649a96851af5220)]:
- @thirdweb-dev/wallets@1.0.12
## 3.2.15
### Patch Changes
- Updated dependencies []:
- @thirdweb-dev/wallets@1.0.11
## 3.2.14
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/auth",
"version": "3.2.14",
"version": "3.2.17",
"main": "dist/thirdweb-dev-auth.cjs.js",
"module": "dist/thirdweb-dev-auth.esm.js",
"browser": {
@@ -101,7 +101,7 @@
"@typescript-eslint/parser": "^5.60.1",
"chai": "^4.3.6",
"cookie-parser": "^1.4.6",
"eslint": "^8.43.0",
"eslint": "^8.45.0",
"eslint-config-prettier": "^8.8.0",
"eslint-config-thirdweb": "workspace:*",
"eslint-plugin-import": "^2.26.0",
@@ -113,7 +113,7 @@
"mocha": "^10.2.0",
"next": "^12.3.4",
"next-auth": "^4.22.1",
"prettier": "^2.8.8",
"prettier": "^3.0.0",
"typescript": "^5.1.6"
},
"peerDependencies": {
+12
View File
@@ -1,5 +1,17 @@
# @thirdweb-dev/chains
## 0.1.35
### Patch Changes
- [#1316](https://github.com/thirdweb-dev/js/pull/1316) [`d8447146`](https://github.com/thirdweb-dev/js/commit/d8447146092c1962f410155ab2047225453aaa2b) Thanks [@iketw](https://github.com/iketw)! - Adds support for clientId / secretKey
## 0.1.34
### Patch Changes
- [#1338](https://github.com/thirdweb-dev/js/pull/1338) [`8dd7540c`](https://github.com/thirdweb-dev/js/commit/8dd7540c455aa70534f6d29986537592fd12169b) Thanks [@jnsdls](https://github.com/jnsdls)! - update chains
## 0.1.33
### Patch Changes
+4 -4
View File
@@ -1,9 +1,9 @@
import type { Chain } from "../src/types";
export default {
"name": "ChainX-EVM",
"name": "BEVM",
"chain": "ChainX",
"rpc": [
"https://chainx-evm.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://bevm.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://mainnet.chainx.org/rpc",
"https://mainnet2.chainx.org/rpc"
],
@@ -19,11 +19,11 @@ export default {
"networkId": 1501,
"explorers": [
{
"name": "chainx-evm scan",
"name": "bevm scan",
"url": "https://evm.chainx.org",
"standard": "none"
}
],
"testnet": false,
"slug": "chainx-evm"
"slug": "bevm"
} as const satisfies Chain;
+36
View File
@@ -0,0 +1,36 @@
import type { Chain } from "../src/types";
export default {
"name": "Antofy Mainnet",
"chain": "ABN",
"icon": {
"url": "ipfs://QmdTfku81ohnG9ECU1Xswmeumt678cBhwHWuFYZ7i1Qsto",
"width": 400,
"height": 400,
"format": "png"
},
"rpc": [
"https://antofy.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://rpc.antofy.io"
],
"faucets": [
"https://faucet.antofy.io"
],
"nativeCurrency": {
"name": "Antofy",
"symbol": "ABN",
"decimals": 18
},
"infoURL": "https://antofy.io",
"shortName": "ABNm",
"chainId": 2202,
"networkId": 2202,
"explorers": [
{
"name": "Antofy Mainnet",
"url": "https://antofyscan.com",
"standard": "EIP3091"
}
],
"testnet": false,
"slug": "antofy"
} as const satisfies Chain;
+36
View File
@@ -0,0 +1,36 @@
import type { Chain } from "../src/types";
export default {
"name": "Antofy Testnet",
"chain": "ABN",
"icon": {
"url": "ipfs://QmdTfku81ohnG9ECU1Xswmeumt678cBhwHWuFYZ7i1Qsto",
"width": 400,
"height": 400,
"format": "png"
},
"rpc": [
"https://antofy-testnet.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://testnet-rpc.antofy.io"
],
"faucets": [
"https://faucet.antofy.io"
],
"nativeCurrency": {
"name": "Antofy",
"symbol": "ABN",
"decimals": 18
},
"infoURL": "https://antofy.io",
"shortName": "ABNt",
"chainId": 23006,
"networkId": 23006,
"explorers": [
{
"name": "Antofy Testnet",
"url": "https://test.antofyscan.com",
"standard": "EIP3091"
}
],
"testnet": true,
"slug": "antofy-testnet"
} as const satisfies Chain;
@@ -2,10 +2,10 @@ import type { Chain } from "../src/types";
export default {
"name": "Kroma Sepolia",
"title": "Kroma Testnet Sepolia",
"chainId": 2357,
"chainId": 2358,
"shortName": "kroma-sepolia",
"chain": "ETH",
"networkId": 2357,
"networkId": 2358,
"nativeCurrency": {
"name": "Sepolia Ether",
"symbol": "ETH",
+1 -4
View File
@@ -2,10 +2,7 @@ import type { Chain } from "../src/types";
export default {
"name": "Neon EVM MainNet",
"chain": "Solana",
"rpc": [
"https://neon-evm.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://mainnet.neonevm.org"
],
"rpc": [],
"faucets": [],
"icon": {
"url": "ipfs://Qmcxevb3v8PEvnvfYgcG3bCBuPhe5YAdsHeaufDChSSR3Q",
+36
View File
@@ -0,0 +1,36 @@
import type { Chain } from "../src/types";
export default {
"name": "EgonCoin Mainnet",
"chain": "EGON",
"icon": {
"url": "ipfs://QmNZiMmzMQYjyGtNSghtzLg4UooYhDgMQsa677DAP5KsBg",
"width": 512,
"height": 512,
"format": "png"
},
"rpc": [
"https://egoncoin.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://rpc.egcscan.com"
],
"faucets": [
"https://faucet.egcscan.com"
],
"nativeCurrency": {
"name": "EgonCoin",
"symbol": "EGON",
"decimals": 18
},
"infoURL": "https://egcscan.com",
"shortName": "EGONm",
"chainId": 271,
"networkId": 271,
"explorers": [
{
"name": "EgonCoin Mainnet",
"url": "https://egcscan.com",
"standard": "EIP3091"
}
],
"testnet": false,
"slug": "egoncoin"
} as const satisfies Chain;
+36
View File
@@ -0,0 +1,36 @@
import type { Chain } from "../src/types";
export default {
"name": "EgonCoin Testnet",
"chain": "EGON",
"icon": {
"url": "ipfs://QmNZiMmzMQYjyGtNSghtzLg4UooYhDgMQsa677DAP5KsBg",
"width": 512,
"height": 512,
"format": "png"
},
"rpc": [
"https://egoncoin-testnet.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://rpctest.egcscan.com"
],
"faucets": [
"https://faucet.egcscan.com"
],
"nativeCurrency": {
"name": "EgonCoin",
"symbol": "EGON",
"decimals": 18
},
"infoURL": "https://egcscan.com",
"shortName": "EGONt",
"chainId": 271271,
"networkId": 271271,
"explorers": [
{
"name": "EgonCoin Testnet",
"url": "https://testnet.egcscan.com",
"standard": "EIP3091"
}
],
"testnet": true,
"slug": "egoncoin-testnet"
} as const satisfies Chain;
+36
View File
@@ -0,0 +1,36 @@
import type { Chain } from "../src/types";
export default {
"name": "SenjePowers Mainnet",
"chain": "SPC",
"icon": {
"url": "ipfs://QmcpyTj4hUyHJZ2VmSdkXFpPpRcNKRP1VxMs7Cp1anymNy",
"width": 504,
"height": 495,
"format": "png"
},
"rpc": [
"https://senjepowers.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://rpc.senjepowersscan.com"
],
"faucets": [
"https://faucet.senjepowersscan.com"
],
"nativeCurrency": {
"name": "SenjePowers",
"symbol": "SPC",
"decimals": 18
},
"infoURL": "https://senjepowersscan.com",
"shortName": "SPCm",
"chainId": 3699,
"networkId": 3699,
"explorers": [
{
"name": "SenjePowers",
"url": "https://senjepowersscan.com",
"standard": "EIP3091"
}
],
"testnet": false,
"slug": "senjepowers"
} as const satisfies Chain;
+37
View File
@@ -0,0 +1,37 @@
import type { Chain } from "../src/types";
export default {
"name": "Toronet Testnet",
"chain": "Toronet",
"icon": {
"url": "ipfs://QmciSvgLatP6jhgdazuiyD3fSrhipfAN7wC943v1qxcrpv",
"width": 846,
"height": 733,
"format": "png"
},
"rpc": [
"https://toronet-testnet.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"http://testnet.toronet.org/rpc"
],
"faucets": [],
"nativeCurrency": {
"name": "Toro",
"symbol": "TORO",
"decimals": 18
},
"infoURL": "https://toronet.org",
"shortName": "ToronetTestnet",
"chainId": 54321,
"networkId": 54321,
"ens": {
"registry": "0x059C474f26D65B0458F9da10A649a7322aB02C09"
},
"explorers": [
{
"name": "toronet_explorer",
"url": "https://testnet.toronet.org",
"standard": "none"
}
],
"testnet": true,
"slug": "toronet-testnet"
} as const satisfies Chain;
+34
View File
@@ -0,0 +1,34 @@
import type { Chain } from "../src/types";
export default {
"name": "Bitrock Mainnet",
"chain": "Bitrock",
"icon": {
"url": "ipfs://QmfXZCAh3HWS2bJroUStN9TieL4QA9QArMotie3X4pwBfj",
"width": 72,
"height": 72,
"format": "svg"
},
"rpc": [
"https://bitrock.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://connect.bit-rock.io"
],
"faucets": [],
"nativeCurrency": {
"name": "BITROCK",
"symbol": "BROCK",
"decimals": 18
},
"infoURL": "https://bit-rock.io",
"shortName": "bitrock",
"chainId": 7171,
"networkId": 7171,
"explorers": [
{
"name": "Bitrock Explorer",
"url": "https://scan.bit-rock.io",
"standard": "EIP3091"
}
],
"testnet": false,
"slug": "bitrock"
} as const satisfies Chain;
+36
View File
@@ -0,0 +1,36 @@
import type { Chain } from "../src/types";
export default {
"name": "Bitrock Testnet",
"chain": "Bitrock",
"icon": {
"url": "ipfs://QmfXZCAh3HWS2bJroUStN9TieL4QA9QArMotie3X4pwBfj",
"width": 72,
"height": 72,
"format": "svg"
},
"rpc": [
"https://bitrock-testnet.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://testnet.bit-rock.io"
],
"faucets": [
"https://faucet.bit-rock.io"
],
"nativeCurrency": {
"name": "BITROCK",
"symbol": "BROCK",
"decimals": 18
},
"infoURL": "https://bit-rock.io",
"shortName": "tbitrock",
"chainId": 7771,
"networkId": 7771,
"explorers": [
{
"name": "Bitrock Testnet Explorer",
"url": "https://testnetscan.bit-rock.io",
"standard": "EIP3091"
}
],
"testnet": true,
"slug": "bitrock-testnet"
} as const satisfies Chain;
+37
View File
@@ -0,0 +1,37 @@
import type { Chain } from "../src/types";
export default {
"name": "Toronet Mainnet",
"chain": "Toronet",
"icon": {
"url": "ipfs://QmciSvgLatP6jhgdazuiyD3fSrhipfAN7wC943v1qxcrpv",
"width": 846,
"height": 733,
"format": "png"
},
"rpc": [
"https://toronet.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"http://toronet.org/rpc"
],
"faucets": [],
"nativeCurrency": {
"name": "Toro",
"symbol": "TORO",
"decimals": 18
},
"infoURL": "https://toronet.org",
"shortName": "Toronet",
"chainId": 77777,
"networkId": 77777,
"ens": {
"registry": "0x1f45a71f4aAD769E27c969c4359E0e250C67165c"
},
"explorers": [
{
"name": "toronet_explorer",
"url": "https://toronet.org/explorer",
"standard": "none"
}
],
"testnet": false,
"slug": "toronet"
} as const satisfies Chain;
+42
View File
@@ -0,0 +1,42 @@
import type { Chain } from "../src/types";
export default {
"name": "ARDENIUM Athena",
"chain": "ATHENA",
"rpc": [
"https://ardenium-athena.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://rpc-athena.ardescan.com/"
],
"faucets": [
"https://faucet-athena.ardescan.com/"
],
"nativeCurrency": {
"name": "ARD",
"symbol": "tARD",
"decimals": 18
},
"infoURL": "https://ardenium.org",
"shortName": "ard",
"chainId": 7895,
"networkId": 7895,
"icon": {
"url": "ipfs://QmdwifhejRfF8QfyzYrNdFVhfhCR6iuzWMmppK4eL7kttG",
"width": 120,
"height": 120,
"format": "png"
},
"explorers": [
{
"name": "ARDENIUM Athena Explorer",
"icon": {
"url": "ipfs://QmdwifhejRfF8QfyzYrNdFVhfhCR6iuzWMmppK4eL7kttG",
"width": 120,
"height": 120,
"format": "png"
},
"url": "https://testnet.ardscan.com",
"standard": "none"
}
],
"testnet": true,
"slug": "ardenium-athena"
} as const satisfies Chain;
+15 -2
View File
@@ -3,7 +3,8 @@ export default {
"name": "Base",
"chain": "ETH",
"rpc": [
"https://base.rpc.thirdweb.com/${THIRDWEB_API_KEY}"
"https://base.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
"https://developer-access-mainnet.base.org/"
],
"faucets": [],
"nativeCurrency": {
@@ -15,13 +16,25 @@ export default {
"shortName": "base",
"chainId": 8453,
"networkId": 8453,
"status": "active",
"icon": {
"url": "ipfs://QmW5Vn15HeRkScMfPcW12ZdZcC2yUASpu6eCsECRdEmjjj/base-512.png",
"height": 512,
"width": 512,
"format": "png"
},
"explorers": [
{
"name": "basescout",
"url": "https://base.blockscout.com",
"standard": "none"
},
{
"name": "basescan",
"url": "https://basescan.org",
"standard": "none"
}
],
"status": "active",
"testnet": false,
"slug": "base"
} as const satisfies Chain;
+6 -6
View File
@@ -18,6 +18,12 @@ export default {
"shortName": "basegor",
"chainId": 84531,
"networkId": 84531,
"icon": {
"url": "ipfs://QmW5Vn15HeRkScMfPcW12ZdZcC2yUASpu6eCsECRdEmjjj/base-512.png",
"height": 512,
"width": 512,
"format": "png"
},
"explorers": [
{
"name": "basescout",
@@ -31,11 +37,5 @@ export default {
}
],
"testnet": true,
"icon": {
"url": "ipfs://QmW5Vn15HeRkScMfPcW12ZdZcC2yUASpu6eCsECRdEmjjj/base-512.png",
"height": 512,
"width": 512,
"format": "png"
},
"slug": "base-goerli"
} as const satisfies Chain;
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/chains",
"version": "0.1.33",
"version": "0.1.35",
"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
+73
View File
@@ -69,6 +69,64 @@ export function getChainRPCs(
return processedRPCs;
}
/**
* Construct the list of RPC URLs given a specific chain config. Format any RPC URLs
* with necessary clientId/secretKey and returns the first valid one.
*
* @param chain - The chain config to assemble RPC URLs from
* @param clientId - The client id to use for the RPC URL
* @param secretKey - The secret key to use for the RPC URL
* @returns The first valid RPC URL for the chain
*/
export function getValidChainRPCs(
chain: Pick<Chain, "rpc" | "chainId">,
clientId?: string,
mode: "http" | "ws" = "http",
): string[] {
const processedRPCs: string[] = [];
chain.rpc.forEach((rpc) => {
// exclude RPC if mode mismatch
if (mode === "http" && !rpc.startsWith("http")) {
return;
}
if (mode === "ws" && !rpc.startsWith("ws")) {
return;
}
// Replace API_KEY placeholder with value
if (rpc.includes("${THIRDWEB_API_KEY}")) {
if (clientId) {
processedRPCs.push(rpc.replace("${THIRDWEB_API_KEY}", clientId));
} else {
// if no client id, let it through with empty string
// if secretKey is present, it will be used in header
// if none are passed, will have reduced access
processedRPCs.push(rpc.replace("${THIRDWEB_API_KEY}", ""));
}
}
// exclude RPCs with unknown placeholder
else if (rpc.includes("${")) {
return;
}
// add as is
else {
processedRPCs.push(rpc);
}
});
if (processedRPCs.length === 0) {
throw new Error(
`No RPC available for chainId "${chain.chainId}" with mode ${mode}`,
);
}
return processedRPCs;
}
/**
* Get the highest priority RPC URL for a specific chain
*
@@ -118,3 +176,18 @@ export function configureChain(
// prepend additional RPCs to the chain's RPCs
return { ...chain, rpc: [...additionalRPCs, ...chain.rpc] };
}
/**
*
* use the clientId and return a new chain object with updated RPCs
*/
export function updateChainRPCs(chain: Chain, clientId?: string) {
try {
return {
...chain,
rpc: getValidChainRPCs(chain, clientId),
};
} catch (error) {
return chain;
}
}
+45
View File
@@ -1,5 +1,50 @@
# thirdweb
## 0.11.0
### Minor Changes
- [#1316](https://github.com/thirdweb-dev/js/pull/1316) [`d8447146`](https://github.com/thirdweb-dev/js/commit/d8447146092c1962f410155ab2047225453aaa2b) Thanks [@iketw](https://github.com/iketw)! - Introducing API secret key restrictions for certain commands. Get your API secret key from [here](https://thirdweb.com/dashboard/settings/api-keys).
The following commands will require you to have logged in with your API secret key:
- `thirdweb deploy`
- `thirdweb release`
- `thirdweb publish`
- `thirdweb upload`
Implemented 2 new commands for managing your session with the CLI:
- thirdweb login
- thirdweb logout
Note: If you try to call a command that requires you to be logged in, you will be prompted to login.
Updated chains.
### Patch Changes
- Updated dependencies [[`44dbf283`](https://github.com/thirdweb-dev/js/commit/44dbf283c985f3e2ecbeb1cfdf09fe1d84e26298), [`d8447146`](https://github.com/thirdweb-dev/js/commit/d8447146092c1962f410155ab2047225453aaa2b), [`d8447146`](https://github.com/thirdweb-dev/js/commit/d8447146092c1962f410155ab2047225453aaa2b), [`d8447146`](https://github.com/thirdweb-dev/js/commit/d8447146092c1962f410155ab2047225453aaa2b)]:
- @thirdweb-dev/storage@1.2.0
- @thirdweb-dev/sdk@3.10.37
- @thirdweb-dev/chains@0.1.35
## 0.10.37
### Patch Changes
- Updated dependencies [[`dac8fa7d`](https://github.com/thirdweb-dev/js/commit/dac8fa7d98b6952acf8d13e173099889c1d47da8)]:
- @thirdweb-dev/sdk@3.10.36
## 0.10.36
### Patch Changes
- Updated dependencies [[`8dd7540c`](https://github.com/thirdweb-dev/js/commit/8dd7540c455aa70534f6d29986537592fd12169b), [`8dd7540c`](https://github.com/thirdweb-dev/js/commit/8dd7540c455aa70534f6d29986537592fd12169b)]:
- @thirdweb-dev/storage@1.1.9
- @thirdweb-dev/chains@0.1.34
- @thirdweb-dev/sdk@3.10.35
## 0.10.35
### Patch Changes
+13 -2
View File
@@ -1,7 +1,7 @@
import { ERROR_MESSAGES } from "../constants/constants";
import { prepareEnvironment } from "@gmrchk/cli-testing-library";
import { copyFile } from "fs/promises";
import { resolve } from "path";
import { ERROR_MESSAGES } from "../constants/constants";
// this creates an app, can take some time that's fine
jest.setTimeout(120_000);
@@ -9,6 +9,14 @@ jest.setTimeout(120_000);
describe("npx thirdweb deploy", () => {
it("should return deploy page url", async () => {
const { spawn, cleanup, exists, path } = await prepareEnvironment();
// eslint-disable-next-line turbo/no-undeclared-env-vars
const apiSecretKey = process.env.CLI_E2E_API_KEY as string;
if (!apiSecretKey) {
throw new Error(
"CLI_E2E_API_KEY is not set in the environment variables",
);
}
await copyFile(
resolve("./e2e/files/BasicContract.sol"),
@@ -16,7 +24,10 @@ describe("npx thirdweb deploy", () => {
);
const { waitForText, waitForFinish, getExitCode, writeText, getStderr } =
await spawn("node", "./dist/cli/index.js deploy --contract");
await spawn(
"node",
`./dist/cli/index.js deploy --contract -k ${apiSecretKey}`,
);
expect(await exists("BasicContract.sol")).toEqual(true);
+12 -3
View File
@@ -1,14 +1,23 @@
import { ERROR_MESSAGES } from "../constants/constants";
import { prepareEnvironment } from "@gmrchk/cli-testing-library";
import { copyFile } from "fs/promises";
import { resolve } from "path";
import { ERROR_MESSAGES } from "../constants/constants";
// this creates an app, can take some time that's fine
jest.setTimeout(120_000);
describe("npx thirdweb publish", () => {
it("should return publish page url", async () => {
// TOOD: turn this test back on when we figure out why it's failing
it.skip("should return publish page url", async () => {
const { spawn, cleanup, exists, path } = await prepareEnvironment();
// eslint-disable-next-line turbo/no-undeclared-env-vars
const apiSecretKey = process.env.CLI_E2E_API_KEY as string;
if (!apiSecretKey) {
throw new Error(
"CLI_E2E_API_KEY is not set in the environment variables",
);
}
await copyFile(
resolve("./e2e/files/BasicContract.sol"),
@@ -16,7 +25,7 @@ describe("npx thirdweb publish", () => {
);
const { waitForText, waitForFinish, getExitCode, writeText, getStderr } =
await spawn("node", "./dist/cli/index.js publish");
await spawn("node", `./dist/cli/index.js publish -k ${apiSecretKey}}`);
expect(await exists("BasicContract.sol")).toEqual(true);
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "thirdweb",
"main": "dist/cli/index.js",
"version": "0.10.35",
"version": "0.11.0",
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/cli",
"author": "thirdweb eng <[email protected]>",
"license": "Apache-2.0",
@@ -28,7 +28,7 @@
"hardhat": "^2.16.1",
"jest": "^29.4.3",
"ora": "^6.3.1",
"prettier": "^2.8.8",
"prettier": "^3.0.0",
"ts-jest": "^29.1.0",
"ts-node": "^10.7.0",
"tsup": "^6.7.0",
+86
View File
@@ -0,0 +1,86 @@
import chalk from "chalk";
import prompts from "prompts";
import Cache, { CacheEntry } from "sync-disk-cache";
import { ApiResponse } from "../lib/types";
export async function loginUser(
cache: Cache,
options?: { new: boolean },
showLogs?: boolean,
) {
const keyFound = getSession(cache);
if (keyFound && !options?.new) {
if (showLogs) {
console.log(chalk.green("You are already logged in"));
}
return keyFound;
} else {
const apiKey = await createSession(cache);
return apiKey;
}
}
export async function logoutUser(cache: Cache) {
try {
cache.remove("api-secret-key");
console.log(chalk.green("You have been logged out"));
} catch (error) {
console.log(chalk.red("Something went wrong", error));
}
}
export function getSession(cache: Cache) {
try {
const apiKey: CacheEntry = cache.get("api-secret-key");
return apiKey.value;
} catch (error) {
console.log(error);
}
}
export async function createSession(cache: Cache) {
try {
const response = await prompts({
type: "text",
name: "apiSecretKey",
message: `Please enter your API secret key, you can find or create it on ${chalk.blue(
"https://thirdweb.com/create-api-key",
)}`,
});
try {
await validateKey(response.apiSecretKey);
} catch (error) {
console.log(error);
process.exit(1);
}
cache.set("api-secret-key", response.apiSecretKey);
return response.apiSecretKey;
} catch (error) {
console.log(error);
}
}
export async function validateKey(apiSecretKey: string) {
const fetch = (await import("node-fetch")).default;
// TODO: CHANGE THIS TO PROD BEFORE MERGING!!!
const response = await fetch(
`https://api.thirdweb.com/v1/keys/use?scope=storage`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
"x-secret-key": apiSecretKey,
},
},
);
const apiResponse = (await response.json()) as ApiResponse;
if (!response.ok) {
const { error } = apiResponse;
throw new Error(chalk.red(error.message));
} else {
return apiSecretKey;
}
}
+94 -24
View File
@@ -1,30 +1,34 @@
#!/usr/bin/env node
import { detectExtensions } from "../common/feature-detector";
import { detectProject } from "../common/project-detector";
import { processProject } from "../common/processor";
import { cliVersion, pkg } from "../constants/urls";
import { info, logger, spinner } from "../core/helpers/logger";
import { CacheEntry } from "../core/types/cache";
import { twCreate } from "../create/command";
import { deploy } from "../deploy";
import { generate } from "../generate/command";
import { findPackageInstallation } from "../helpers/detect-local-packages";
import { upload } from "../storage/command";
import { ThirdwebStorage } from "@thirdweb-dev/storage";
import chalk from "chalk";
import { exec, spawn } from "child_process";
import { Command } from "commander";
import open from "open";
import os from "os";
import path from "path";
import prompts from "prompts";
import Cache from "sync-disk-cache";
import Cache, { CacheEntry } from "sync-disk-cache";
import { loginUser, logoutUser, validateKey } from "../auth";
import { detectExtensions } from "../common/feature-detector";
import { processProject } from "../common/processor";
import { detectProject } from "../common/project-detector";
import { cliVersion, pkg } from "../constants/urls";
import { info, logger, spinner } from "../core/helpers/logger";
import { twCreate } from "../create/command";
import { deploy } from "../deploy";
import { generate } from "../generate/command";
import { findPackageInstallation } from "../helpers/detect-local-packages";
import { install } from "../install";
import { upload } from "../storage/command";
const main = async () => {
// eslint-disable-next-line turbo/no-undeclared-env-vars
const skipIntro = process.env.THIRDWEB_CLI_SKIP_INTRO === "true";
const program = new Command();
const cache = new Cache("thirdweb:cli");
const cache = new Cache("thirdweb:cli", {
location: path.join(os.homedir(), ".thirdweb", "creds"),
});
// yes this has to look like this, eliminates whitespace
if (!skipIntro) {
@@ -205,8 +209,8 @@ const main = async () => {
.option("--nightly", "Install the nightly version of packages.")
.option("--dev", "Install the dev version of packages")
.option("-d, --debug", "show debug logs")
.action(async (path, options) => {
await install(path, options);
.action(async (_path, options) => {
await install(_path, options);
});
program
@@ -246,8 +250,8 @@ const main = async () => {
"-c, --contract-name [name]",
"Name of the new smart contract to create",
)
.action(async (type, path, options) => {
await twCreate(type, path, options);
.action(async (type, _path, options) => {
await twCreate(type, _path, options);
});
program
@@ -296,8 +300,17 @@ const main = async () => {
"Deploy a dynamic smart contract made up of extensions to blockchains",
)
.option("--zksync", "Deploy on ZKSync")
.option("-k, --key <key>", "API key to authorize usage")
.action(async (options) => {
const url = await deploy(options);
let apiSecretKey = "";
// If no key is passed in, prompt the user to login. If it is passed in, use it.
if (!options.key) {
apiSecretKey = await loginUser(cache);
} else {
await validateKey(options.key);
apiSecretKey = options.key;
}
const url = await deploy(options, apiSecretKey);
if (url) {
await open(url);
}
@@ -319,11 +332,20 @@ const main = async () => {
.option("--dry-run", "dry run (skip actually publishing)")
.option("-d, --debug", "show debug logs")
.option("--ci", "Continuous Integration mode")
.option("-k, --key <key>", "API key to authorize usage")
.action(async (options) => {
let apiSecretKey = "";
// If no key is passed in, prompt the user to login. If it is passed in, use it.
if (!options.key) {
apiSecretKey = await loginUser(cache);
} else {
await validateKey(options.key);
apiSecretKey = options.key;
}
logger.warn(
"'release' is deprecated and will be removed in a future update. Please use 'publish' instead.",
);
const url = await processProject(options, "publish");
const url = await processProject(options, "publish", apiSecretKey);
info(
`Open this link to publish your contracts: ${chalk.blueBright(
url.toString(),
@@ -350,8 +372,17 @@ const main = async () => {
.option("--dry-run", "dry run (skip actually publishing)")
.option("-d, --debug", "show debug logs")
.option("--ci", "Continuous Integration mode")
.option("-k, --key <key>", "API key to authorize usage")
.action(async (options) => {
const url = await processProject(options, "publish");
let apiSecretKey = "";
// If no key is passed in, prompt the user to login. If it is passed in, use it.
if (!options.key) {
apiSecretKey = await loginUser(cache);
} else {
await validateKey(options.key);
apiSecretKey = options.key;
}
const url = await processProject(options, "publish", apiSecretKey);
info(
`Open this link to publish your contracts: ${chalk.blueBright(
url.toString(),
@@ -364,10 +395,21 @@ const main = async () => {
.command("upload")
.description("Upload any file or directory to decentralized storage (IPFS)")
.argument("[upload]", "path to file or directory to upload")
.action(async (path) => {
const storage = new ThirdwebStorage();
.option("-k, --key <key>", "API key to authorize usage")
.action(async (_path, options) => {
let apiSecretKey = "";
// If no key is passed in, prompt the user to login. If it is passed in, use it.
if (!options.key) {
apiSecretKey = await loginUser(cache);
} else {
await validateKey(options.key);
apiSecretKey = options.key;
}
const storage = new ThirdwebStorage({
secretKey: apiSecretKey,
});
try {
const uri = await upload(storage, path);
const uri = await upload(storage, _path);
info(
`Files stored at the following IPFS URI: ${chalk.blueBright(
uri.toString(),
@@ -414,8 +456,36 @@ const main = async () => {
"Preload ABIs and generate types for your smart contract to strongly type the thirdweb SDK",
)
.option("-p, --path <project-path>", "path to project", ".")
.option("-k, --key <key>", "API key to authorize usage")
.action(async (options) => {
await generate(options);
let apiSecretKey = "";
// If no key is passed in, prompt the user to login. If it is passed in, use it.
if (!options.key) {
apiSecretKey = await loginUser(cache);
} else {
await validateKey(options.key);
apiSecretKey = options.key;
}
await generate(options, apiSecretKey);
});
program
.command("login")
.description(
"Authenticate with the thirdweb CLI using your API secret key or replace an existing API secret key",
)
.option("-n, --new", "Login with a new API secret key", false)
.action(async (options) => {
await loginUser(cache, options, true);
});
program
.command("logout")
.description(
"Logout of the thirdweb CLI, effectively removing your API secret key from your machine",
)
.action(async () => {
await logoutUser(cache);
});
await program.parseAsync();
+23 -14
View File
@@ -1,11 +1,18 @@
import { detectFeatures } from "@thirdweb-dev/sdk";
import { ThirdwebStorage } from "@thirdweb-dev/storage";
import chalk from "chalk";
import { ethers } from "ethers";
import { readFileSync, writeFileSync } from "fs";
import ora from "ora";
import path from "path";
import {
EXTENSION_REGISTRY_ADDRESS,
ROUTER_CONTRACTS,
PreDeployedRouter,
TWRouterParams,
ROUTER_CONTRACTS,
RouterParams,
TWRouterParams,
} from "../constants/router-contracts";
import { cliVersion, THIRDWEB_URL } from "../constants/urls";
import { THIRDWEB_URL, cliVersion } from "../constants/urls";
import build from "../core/builder/build";
import detect from "../core/detection/detect";
import { execute } from "../core/helpers/exec";
@@ -17,24 +24,20 @@ import {
import { ContractPayload } from "../core/interfaces/ContractPayload";
import {
Extension,
ExtensionDeployArgs,
ExtensionFunction,
ExtensionMetadata,
ExtensionDeployArgs,
} from "../core/interfaces/Extension";
import { detectFeatures } from "@thirdweb-dev/sdk";
import { ThirdwebStorage } from "@thirdweb-dev/storage";
import chalk from "chalk";
import { ethers } from "ethers";
import { readFileSync, writeFileSync } from "fs";
import ora from "ora";
import path from "path";
export async function processProject(
options: any,
command: "deploy" | "publish",
apiSecretKey: string,
) {
// TODO: allow overriding the default storage
const storage = new ThirdwebStorage();
const storage = new ThirdwebStorage({
secretKey: apiSecretKey,
});
logger.setSettings({
minLevel: options.debug ? "debug" : "info",
@@ -237,6 +240,7 @@ export async function processProject(
} else {
const deployArgs: RouterParams = await formatToExtensions(
selectedContracts,
apiSecretKey,
);
const outputDeployArgs = JSON.stringify(deployArgs, undefined, 2);
@@ -364,11 +368,16 @@ export function getUrl(hashes: string[], command: string) {
return url;
}
async function formatToExtensions(contracts: ContractPayload[]): Promise<{
async function formatToExtensions(
contracts: ContractPayload[],
apiSecretKey: string,
): Promise<{
extensions: Extension[];
extensionDeployArgs: ExtensionDeployArgs[];
}> {
const storage = new ThirdwebStorage();
const storage = new ThirdwebStorage({
secretKey: apiSecretKey,
});
const extensions: Extension[] = [];
const extensionDeployArgs: ExtensionDeployArgs[] = [];
+22 -5
View File
@@ -4,7 +4,10 @@ import detectLibrary from "../core/detection/detectLibrary";
import detectLanguage from "../core/detection/detectLanguage";
import { logger, spinner } from "../core/helpers/logger";
import path from "path";
import { ContractLibrariesType, contractLibraries } from "../core/types/ProjectType";
import {
ContractLibrariesType,
contractLibraries,
} from "../core/types/ProjectType";
import { FeatureWithEnabled, detectFeatures } from "@thirdweb-dev/sdk";
import chalk from "chalk";
import ora from "ora";
@@ -48,8 +51,16 @@ export async function detectProject(options: any) {
options,
detectedPackageManager,
);
const detectedAppType = detectedFramework !== "none" ? contractLibraries.includes(detectedFramework as ContractLibrariesType) ? "contract" : "app" : "app";
const detectedContractLibrary = detectedAppType === "contract" ? detectedFramework as ContractLibrariesType : "none";
const detectedAppType =
detectedFramework !== "none"
? contractLibraries.includes(detectedFramework as ContractLibrariesType)
? "contract"
: "app"
: "app";
const detectedContractLibrary =
detectedAppType === "contract"
? (detectedFramework as ContractLibrariesType)
: "none";
logger.info("Detected package manager: " + detectedPackageManager);
logger.info("Detected library: " + detectedLibrary);
@@ -61,7 +72,11 @@ export async function detectProject(options: any) {
let compiledResult;
const compileLoader = spinner("Compiling project...");
try {
compiledResult = await build(projectPath, detectedContractLibrary, options);
compiledResult = await build(
projectPath,
detectedContractLibrary,
options,
);
} catch (e) {
compileLoader.fail("Compilation failed");
logger.error(e);
@@ -127,7 +142,9 @@ export async function detectProject(options: any) {
).stopAndPersist({ symbol: "🔎" });
} else {
ora(
`Detected extension on ${chalk.blueBright(contractWithFeatures.name)}`,
`Detected extension on ${chalk.blueBright(
contractWithFeatures.name,
)}`,
).stopAndPersist({ symbol: "🔎" });
contractWithFeatures.enabledFeatures.map((feature) => {
logger.info(`✔️ ${chalk.green(feature.name)}`);
+8 -5
View File
@@ -16,11 +16,14 @@ export class SolcBuilder extends BaseBuilder {
const inputPaths: string[] = [];
findFiles(options.projectPath, /^.*\.sol$/, inputPaths);
const sources = inputPaths.reduce((acc, curr) => {
const source = readFileSync(curr, "utf-8");
acc[basename(curr)] = { content: source };
return acc;
}, {} as Record<string, { content: string }>);
const sources = inputPaths.reduce(
(acc, curr) => {
const source = readFileSync(curr, "utf-8");
acc[basename(curr)] = { content: source };
return acc;
},
{} as Record<string, { content: string }>,
);
const input = {
language: "Solidity",
@@ -67,7 +67,10 @@ export default async function detect(
return possibleFrameworks[0];
}
if (possibleFrameworks.includes("expo") && possibleFrameworks.includes("react-native-cli")) {
if (
possibleFrameworks.includes("expo") &&
possibleFrameworks.includes("react-native-cli")
) {
return "expo";
}
@@ -30,7 +30,10 @@ export default async function detect(
return possibleLanguages[0];
}
if (possibleLanguages.includes("typescript") && possibleLanguages.includes("javascript")) {
if (
possibleLanguages.includes("typescript") &&
possibleLanguages.includes("javascript")
) {
return "typescript";
}
@@ -34,7 +34,10 @@ export default async function detect(
return possibleLibraries[0];
}
if (possibleLibraries.includes("react-native") && possibleLibraries.includes("react")) {
if (
possibleLibraries.includes("react-native") &&
possibleLibraries.includes("react")
) {
return "react-native";
}
@@ -42,7 +42,10 @@ export default async function detect(
return possiblePackageManagers[0];
}
if (possiblePackageManagers.includes("brownie") && possiblePackageManagers.includes("pip")) {
if (
possiblePackageManagers.includes("brownie") &&
possiblePackageManagers.includes("pip")
) {
return "brownie";
}
@@ -21,13 +21,15 @@ export default class BrownieDetector implements FrameworkDetector {
);
const additionalDirectoriesToCheck = ["/build", "/contracts", "/reports"];
const additionalDirectoriesExist = additionalDirectoriesToCheck.every((dir) => {
return existsSync(path + dir);
});
const additionalDirectoriesExist = additionalDirectoriesToCheck.every(
(dir) => {
return existsSync(path + dir);
},
);
return (
!!dependencies.find(dep => dep.includes("brownie")) ||
!!devDependencies.find(dep => dep.includes("brownie")) ||
!!dependencies.find((dep) => dep.includes("brownie")) ||
!!devDependencies.find((dep) => dep.includes("brownie")) ||
additionalFilesExist ||
additionalDirectoriesExist ||
false
@@ -18,8 +18,8 @@ export default class DjangoDetector implements FrameworkDetector {
);
return (
!!dependencies.find(dep => dep.includes("Django")) ||
!!devDependencies.find(dep => dep.includes("Django")) ||
!!dependencies.find((dep) => dep.includes("Django")) ||
!!devDependencies.find((dep) => dep.includes("Django")) ||
additionalFilesExist ||
false
);
@@ -18,8 +18,8 @@ export default class FastAPIDetector implements FrameworkDetector {
);
return (
!!dependencies.find(dep => dep.includes("fastapi")) ||
!!devDependencies.find(dep => dep.includes("fastapi")) ||
!!dependencies.find((dep) => dep.includes("fastapi")) ||
!!devDependencies.find((dep) => dep.includes("fastapi")) ||
additionalFilesExist ||
false
);
@@ -18,8 +18,8 @@ export default class FlaskDetector implements FrameworkDetector {
);
return (
!!dependencies.find(dep => dep.includes("Flask")) ||
!!devDependencies.find(dep => dep.includes("Flask")) ||
!!dependencies.find((dep) => dep.includes("Flask")) ||
!!devDependencies.find((dep) => dep.includes("Flask")) ||
additionalFilesExist ||
false
);
@@ -18,8 +18,8 @@ export default class PopulusDetector implements FrameworkDetector {
);
return (
!!dependencies.find(dep => dep === "populus") ||
!!devDependencies.find(dep => dep === "populus") ||
!!dependencies.find((dep) => dep === "populus") ||
!!devDependencies.find((dep) => dep === "populus") ||
additionalFilesExist ||
false
);
@@ -16,10 +16,7 @@ export default class TruffleDetector implements FrameworkDetector {
const packageJson = readFileSync(packageJsonPath);
const { dependencies, devDependencies } = parsePackageJson(packageJson);
const additionalFilesToCheck = [
"/truffle.config.js",
"/truffle.js",
];
const additionalFilesToCheck = ["/truffle.config.js", "/truffle.js"];
const additionalFilesExist = additionalFilesToCheck.some((file) =>
existsSync(path + file),
);
@@ -12,7 +12,9 @@ export default class Web3PyDetector implements LibraryDetector {
);
return (
!!dependencies.find(dep => dep.includes("web3")) || !!devDependencies.find(dep => dep.includes("web3")) || false
!!dependencies.find((dep) => dep.includes("web3")) ||
!!devDependencies.find((dep) => dep.includes("web3")) ||
false
);
}
}
@@ -6,7 +6,6 @@ export default class BrownieDetector implements PackageManagerDetector {
public packageManagerType: PackageManagerType = "brownie";
public matches(path: string): boolean {
const additionalFilesToCheck = [
"/brownie-config.yaml",
"/brownie-config.json",
@@ -17,14 +16,12 @@ export default class BrownieDetector implements PackageManagerDetector {
// Check that all of these exist.
const additionalDirectoriesToCheck = ["/build", "/contracts", "/reports"];
const additionalDirectoriesExist = additionalDirectoriesToCheck.every((dir) => {
return existsSync(path + dir);
});
return (
additionalFilesExist ||
additionalDirectoriesExist ||
false
const additionalDirectoriesExist = additionalDirectoriesToCheck.every(
(dir) => {
return existsSync(path + dir);
},
);
return additionalFilesExist || additionalDirectoriesExist || false;
}
}
+1 -2
View File
@@ -76,7 +76,6 @@ export type ContractLibrariesType =
| "solc"
| "none";
export const contractLibraries: readonly ContractLibrariesType[] = [
"brownie",
"foundry",
@@ -84,4 +83,4 @@ export const contractLibraries: readonly ContractLibrariesType[] = [
"truffle",
"solc",
"none",
] as const;
] as const;
-5
View File
@@ -1,5 +0,0 @@
export type CacheEntry = {
isCached: boolean;
key: string;
value: string;
};
-2
View File
@@ -1,2 +0,0 @@
declare module "sync-disk-cache";
declare module "solc";
+21
View File
@@ -0,0 +1,21 @@
declare module 'sync-disk-cache' {
export type CacheEntry = {
isCached: boolean;
key: string;
value: string;
};
export default class Cache {
constructor(key: string, options?: { location?: string; compression?: "gzip" | "deflate" | "deflateRaw"; });
clear(): void;
has(key: string): boolean;
get(key: string): CacheEntry;
set(key: string, value: string): string;
remove(key: string): boolean;
pathFor(key: string): string;
decompress(value: string): string;
compress(value: string): string;
}
}
declare module "solc"
+4 -1
View File
@@ -321,7 +321,10 @@ export async function twCreate(
const res = await prompts({
type: "text",
name: "path",
message: projectType === "extension" ? CREATE_MESSAGES.extensionName : CREATE_MESSAGES.contractName,
message:
projectType === "extension"
? CREATE_MESSAGES.extensionName
: CREATE_MESSAGES.contractName,
initial: defaultName,
validate: (name: string) => {
const isValid = /(^[a-z0-9A-Z]+$)|(^[a-z0-9A-Z]+\.sol$)/.test(name);
@@ -9,7 +9,7 @@ export async function runCommand(
return new Promise<void>((resolve, reject) => {
/**
* Spawn the installation process.
*/
*/
const child = spawn(command, args);
child.stdout?.on("data", (data) => {
@@ -28,7 +28,7 @@ export async function runCommand(
});
child.on("error", (err) => {
console.log("Spawn error", err)
console.log("Spawn error", err);
});
child.on("close", (code) => {
@@ -39,4 +39,4 @@ export async function runCommand(
resolve();
});
});
}
}
+17 -4
View File
@@ -5,10 +5,20 @@ import { logger } from "../core/helpers/logger";
import { runCommand } from "../create/helpers/run-command";
import { upload } from "../storage/command";
export async function deployApp(distPath = "dist", projectPath = ".") {
const storage = new ThirdwebStorage();
export async function deployApp(
distPath = "dist",
projectPath = ".",
apiSecretKey: string,
) {
const storage = new ThirdwebStorage({
secretKey: apiSecretKey,
});
const detectedPackageManager = await detectPackageManager(projectPath, {});
const detectedFramework = await detectFramework(projectPath, {}, detectedPackageManager);
const detectedFramework = await detectFramework(
projectPath,
{},
detectedPackageManager,
);
if (detectedFramework === "none") {
throw new Error("No supported project detected");
@@ -60,7 +70,10 @@ export async function deployApp(distPath = "dist", projectPath = ".") {
try {
const uri = await upload(storage, distPath);
return `${uri.replace("ipfs://", "https://ipfs-public.thirdwebcdn.com/ipfs/")}`;
return `${uri.replace(
"ipfs://",
"https://ipfs-public.thirdwebcdn.com/ipfs/",
)}`;
} catch (err) {
console.error("Can't upload project", err);
return Promise.reject("Can't upload project");
+4 -4
View File
@@ -1,8 +1,8 @@
import chalk from "chalk";
import { processProject } from "../common/processor";
import { info, logger } from "../core/helpers/logger";
import generateDashboardUrl from "../helpers/generate-dashboard-url";
import { deployApp } from "./app";
import chalk from "chalk";
type DeployOptions = {
name: string;
@@ -12,7 +12,7 @@ type DeployOptions = {
distPath?: string;
};
export async function deploy(options: DeployOptions) {
export async function deploy(options: DeployOptions, apiSecretKey: string) {
if (options.name) {
const url = generateDashboardUrl(options.name, options.contractVersion);
@@ -33,7 +33,7 @@ export async function deploy(options: DeployOptions) {
if (options.app) {
try {
let url = await deployApp(options.distPath, options.path);
let url = await deployApp(options.distPath, options.path, apiSecretKey);
info(`Here is the link to your app: ${chalk.blueBright(url.toString())}`);
return url.toString();
} catch (err) {
@@ -44,7 +44,7 @@ export async function deploy(options: DeployOptions) {
}
}
const url = await processProject(options, "deploy");
const url = await processProject(options, "deploy", apiSecretKey);
info(
`Open this link to deploy your contracts: ${chalk.blueBright(
url.toString(),
+23 -15
View File
@@ -1,20 +1,20 @@
import { GENERATE_MESSAGES } from "../../constants/constants";
import { allChains } from "@thirdweb-dev/chains";
import {
DeployedContract,
fetchContractMetadataFromAddress,
getChainProvider,
} from "@thirdweb-dev/sdk";
import { ThirdwebStorage } from "@thirdweb-dev/storage";
import { info } from "../core/helpers/logger";
import { allChains } from "@thirdweb-dev/chains";
import fs from "fs";
import prompts from "prompts";
import { findMatches } from "../common/file-helper";
import ora from "ora";
import prompts from "prompts";
import { GENERATE_MESSAGES } from "../../constants/constants";
import { findMatches } from "../common/file-helper";
import { info } from "../core/helpers/logger";
import { GenerateOptions, ThirdwebConfig } from "./types";
import { CHAIN_OPTIONS, getContractsForAddresses } from "./utils";
export async function generate(options: GenerateOptions) {
export async function generate(options: GenerateOptions, apiSecretKey: string) {
let projectPath: string = options.path?.replace(/\/$/, "") || ".";
let contracts: DeployedContract[] = [];
@@ -96,7 +96,9 @@ export async function generate(options: GenerateOptions) {
// Attempt to download the ABI for each contract
ora(`Downloading ABIs for contracts configured in 'thirdweb.json'`).info();
const storage = new ThirdwebStorage();
const storage = new ThirdwebStorage({
secretKey: apiSecretKey,
});
const metadata: {
address: string;
metadata: Awaited<ReturnType<typeof fetchContractMetadataFromAddress>>;
@@ -147,10 +149,13 @@ export async function generate(options: GenerateOptions) {
const file = fs.readFileSync(filePath, "utf-8");
const abiRegex = /GENERATED_ABI = \{.*\};\n\n/s;
const contractAbis = metadata.reduce((acc, contract) => {
acc[contract.address] = contract.metadata.abi;
return acc;
}, {} as Record<string, any>);
const contractAbis = metadata.reduce(
(acc, contract) => {
acc[contract.address] = contract.metadata.abi;
return acc;
},
{} as Record<string, any>,
);
const updatedAbis = JSON.stringify(contractAbis, null, 2);
const updatedFile = file.replace(
abiRegex,
@@ -169,10 +174,13 @@ export async function generate(options: GenerateOptions) {
const file = fs.readFileSync(typeFilePath, "utf-8");
const abiRegex = /GENERATED_ABI: \{.*\}/s;
const contractAbis = metadata.reduce((acc, contract) => {
acc[contract.address] = contract.metadata.abi;
return acc;
}, {} as Record<string, any>);
const contractAbis = metadata.reduce(
(acc, contract) => {
acc[contract.address] = contract.metadata.abi;
return acc;
},
{} as Record<string, any>,
);
const updatedAbis = JSON.stringify(contractAbis, null, 2);
const updatedFile = file.replace(abiRegex, `GENERATED_ABI: ${updatedAbis}`);
fs.writeFileSync(typeFilePath, updatedFile);
+295 -235
View File
@@ -1,235 +1,295 @@
// Import required dependencies
import { existsSync, readFileSync } from "fs";
import ora from "ora";
import inquirer from "inquirer";
// Import project dependencies
import detectFramework from "../core/detection/detectFramework";
import detectPackageManager from "../core/detection/detectPackageManager";
import detectLibrary from "../core/detection/detectLibrary";
import { ContractLibrariesType, contractLibraries } from "../core/types/ProjectType";
import { checkIfBrowniePackageIsInstalled, getDependenciesForFoundry, getDependenciesForGo, getDependenciesForPython, installOrUpdate, parsePackageJson, processAppType, processContractAppType } from "../lib/utils";
import { logger } from "../core/helpers/logger";
// Define excluded dependencies
const EXCLUDED_DEPENDENCIES = ["@thirdweb-dev/chain-icons"];
export async function install(projectPath = ".", options: any) {
const debug = options.debug || false;
let installer;
let updater;
// Initialize all dependencies.
let jsDependencies: { [key: string]: string } | string[] = {};
let jsDevDependencies: { [key: string]: string } | string[] = {};
let pythonDependencies: string[] = [];
let pythonDevDependencies: string[] = [];
let peerDependencies: { [key: string]: string } = {};
let goDependencies: { name: string; version: string }[] = [];
const thirdwebDepsToInstall = new Set<string>();
const otherDeps = new Set<string>();
// Initialize brownie and foundry checks
let thirdwebExistsInBrownie = { package: "", exists: false };
let thirdwebExistsInFoundry = false;
// Detect the package manager, and framework used in the project
const detectedPackageManager = await detectPackageManager(projectPath, options);
const detectedFramework = await detectFramework(projectPath, options, detectedPackageManager);
const detectedLibrary = await detectLibrary(projectPath, options, detectedPackageManager);
// Determine project type
const detectedAppType = detectedFramework !== "none" ? contractLibraries.includes(detectedFramework as ContractLibrariesType) ? "contract" : "app" : "app";
// Identify which specific package manager is used
const isBrowniePackageManager = detectedPackageManager === "brownie";
const isFoundryPackageManager = detectedPackageManager === "foundry";
const isGoPackageManager = detectedPackageManager === "go-modules";
const isPythonPackageManager = ["pip", "pipenv", "poetry"].includes(detectedPackageManager);
const isJSPackageManager = ["yarn", "pnpm", "npm"].includes(detectedPackageManager);
// Processing JS/TS dependencies
if (isJSPackageManager) {
let packageJson;
const packageJsonPath = projectPath + "/package.json";
// Check if package.json exists
if (!existsSync(packageJsonPath)) {
console.error(
"No package.json file found. Please run this command in the root of your project."
);
return;
}
// Parse package.json
packageJson = readFileSync(packageJsonPath);
const parsedJSDeps = parsePackageJson(packageJson);
jsDependencies = parsedJSDeps.dependencies;
jsDevDependencies = parsedJSDeps.devDependencies;
peerDependencies = parsedJSDeps.peerDependencies;
}
// Processing Python dependencies
if (isPythonPackageManager) {
const pythonDeps = getDependenciesForPython(projectPath, detectedPackageManager);
pythonDependencies = pythonDeps.dependencies;
pythonDevDependencies = pythonDeps.devDependencies;
}
// Processing Brownie dependencies
if (isBrowniePackageManager) {
const regex = /\s*thirdweb-dev\/contracts@\d+\.\d+\.\d+/g;
const res = await checkIfBrowniePackageIsInstalled(regex);
thirdwebExistsInBrownie = res;
}
// Processing Foundry dependencies
if (isFoundryPackageManager) {
const exists = await getDependenciesForFoundry(projectPath, "thirdweb-contracts");
thirdwebExistsInFoundry = exists;
}
// Processing Go dependencies
if (isGoPackageManager) {
const deps = getDependenciesForGo(projectPath);
goDependencies = deps;
}
const hasEthers =
!!jsDependencies?.ethers ||
!!jsDevDependencies?.ethers;
const version = !isJSPackageManager ? "" :
options.dev
? "@dev"
: options.nightly
? "@nightly" : "";
const thirdwebDepsToUpdate = new Set<string>(
[
...Object.keys(jsDependencies).filter((dep) =>
dep.startsWith("@thirdweb-dev/"),
),
...Object.keys(jsDevDependencies).filter((dep) =>
dep.startsWith("@thirdweb-dev/"),
),
...Object.keys(peerDependencies).filter((dep) =>
dep.startsWith("@thirdweb-dev/"),
),
...pythonDependencies
.filter((dep) => dep.startsWith("thirdweb-sdk"))
.map((dep) => dep.split('==')[0]),
...pythonDevDependencies
.filter((dep) => dep.startsWith("thirdweb-sdk"))
.map((dep) => dep.split('==')[0]),
...goDependencies
.filter((dep) => {
console.log('dep', dep)
return dep.name === "github.com/thirdweb-dev/go-sdk/v2"
})
.map((dep) => dep.name),
].concat(thirdwebExistsInBrownie.exists ? [thirdwebExistsInBrownie.package] : [])
.concat(thirdwebExistsInFoundry ? ["lib/thirdweb-contracts"] : [])
);
// Figure out which dependencies to install and which to update.
if (detectedAppType === "contract") {
processContractAppType({ detectedPackageManager, thirdwebDepsToUpdate, thirdwebDepsToInstall, isJSPackageManager })
} else if (detectedAppType === "app") {
processAppType({
detectedLibrary,
detectedFramework,
hasEthers,
isGoPackageManager,
isJSPackageManager,
isPythonPackageManager,
otherDeps,
thirdwebDepsToInstall,
thirdwebDepsToUpdate,
})
}
// remove exluded dependencies
EXCLUDED_DEPENDENCIES.forEach((excludedDep) => {
thirdwebDepsToUpdate.delete(excludedDep);
thirdwebDepsToInstall.delete(excludedDep);
});
// Ask which dependencies to install
if (thirdwebDepsToInstall.size !== 0) {
const answer = await inquirer.prompt({
type: "checkbox",
choices: [...thirdwebDepsToInstall],
message: "Which thirdweb dependencies would you like to install?",
name: "dependencies",
default: [...thirdwebDepsToInstall],
});
if (answer.dependencies.length !== thirdwebDepsToInstall.size) {
thirdwebDepsToInstall.clear();
answer.dependencies.forEach((dep: string) => thirdwebDepsToInstall.add(dep));
}
}
// Ask which dependencies to update
if (thirdwebDepsToUpdate.size !== 0) {
const answer = await inquirer.prompt({
type: "checkbox",
choices: [...thirdwebDepsToUpdate],
message: "Which thirdweb dependencies would you like to update?",
name: "dependencies",
default: [...thirdwebDepsToUpdate],
});
if (answer.dependencies.length !== thirdwebDepsToUpdate.size) {
thirdwebDepsToUpdate.clear();
answer.dependencies.forEach((dep: string) => thirdwebDepsToUpdate.add(dep));
}
}
if (thirdwebDepsToInstall.size === 0 && thirdwebDepsToUpdate.size === 0) {
console.log("No thirdweb dependencies to install or update.");
return;
}
if (debug) {
logger.info("Detected package manager: " + detectedPackageManager);
logger.info("Detected library: " + detectedLibrary);
logger.info("Detected framework: " + detectedFramework);
logger.info("Detected app type: " + detectedAppType);
}
try {
const dependenciesToAdd = [
...[...thirdwebDepsToInstall].map((dep) => `${dep}${version}`),
...otherDeps,
];
const dependenciesToUpdate = [...thirdwebDepsToUpdate].map(
(dep) => `${dep}${version}`,
)
if (thirdwebDepsToInstall.size !== 0) {
installer = ora(
`Installing: "${[...thirdwebDepsToInstall].join('", "')}"`,
).start();
await installOrUpdate(detectedPackageManager, dependenciesToAdd, [], "install", { oldVersion: thirdwebExistsInBrownie.package, debug });
installer?.succeed(`Installed: "${dependenciesToAdd.join('", "')}"`);
}
if (thirdwebDepsToUpdate.size !== 0) {
updater = ora(
`Updating: "${[...thirdwebDepsToUpdate].join('", "')}"`,
).start();
await installOrUpdate(detectedPackageManager, [], dependenciesToUpdate, "update", {
debug,
});
updater?.succeed(`Updated: "${dependenciesToUpdate.join('", "')}"`);
}
} catch (err) {
console.error("Can't install within project: ", err);
return Promise.reject("Can't install within project");
}
}
// Import required dependencies
import { existsSync, readFileSync } from "fs";
import ora from "ora";
import inquirer from "inquirer";
// Import project dependencies
import detectFramework from "../core/detection/detectFramework";
import detectPackageManager from "../core/detection/detectPackageManager";
import detectLibrary from "../core/detection/detectLibrary";
import {
ContractLibrariesType,
contractLibraries,
} from "../core/types/ProjectType";
import {
checkIfBrowniePackageIsInstalled,
getDependenciesForFoundry,
getDependenciesForGo,
getDependenciesForPython,
installOrUpdate,
parsePackageJson,
processAppType,
processContractAppType,
} from "../lib/utils";
import { logger } from "../core/helpers/logger";
// Define excluded dependencies
const EXCLUDED_DEPENDENCIES = ["@thirdweb-dev/chain-icons"];
export async function install(projectPath = ".", options: any) {
const debug = options.debug || false;
let installer;
let updater;
// Initialize all dependencies.
let jsDependencies: { [key: string]: string } | string[] = {};
let jsDevDependencies: { [key: string]: string } | string[] = {};
let pythonDependencies: string[] = [];
let pythonDevDependencies: string[] = [];
let peerDependencies: { [key: string]: string } = {};
let goDependencies: { name: string; version: string }[] = [];
const thirdwebDepsToInstall = new Set<string>();
const otherDeps = new Set<string>();
// Initialize brownie and foundry checks
let thirdwebExistsInBrownie = { package: "", exists: false };
let thirdwebExistsInFoundry = false;
// Detect the package manager, and framework used in the project
const detectedPackageManager = await detectPackageManager(
projectPath,
options,
);
const detectedFramework = await detectFramework(
projectPath,
options,
detectedPackageManager,
);
const detectedLibrary = await detectLibrary(
projectPath,
options,
detectedPackageManager,
);
// Determine project type
const detectedAppType =
detectedFramework !== "none"
? contractLibraries.includes(detectedFramework as ContractLibrariesType)
? "contract"
: "app"
: "app";
// Identify which specific package manager is used
const isBrowniePackageManager = detectedPackageManager === "brownie";
const isFoundryPackageManager = detectedPackageManager === "foundry";
const isGoPackageManager = detectedPackageManager === "go-modules";
const isPythonPackageManager = ["pip", "pipenv", "poetry"].includes(
detectedPackageManager,
);
const isJSPackageManager = ["yarn", "pnpm", "npm"].includes(
detectedPackageManager,
);
// Processing JS/TS dependencies
if (isJSPackageManager) {
let packageJson;
const packageJsonPath = projectPath + "/package.json";
// Check if package.json exists
if (!existsSync(packageJsonPath)) {
console.error(
"No package.json file found. Please run this command in the root of your project.",
);
return;
}
// Parse package.json
packageJson = readFileSync(packageJsonPath);
const parsedJSDeps = parsePackageJson(packageJson);
jsDependencies = parsedJSDeps.dependencies;
jsDevDependencies = parsedJSDeps.devDependencies;
peerDependencies = parsedJSDeps.peerDependencies;
}
// Processing Python dependencies
if (isPythonPackageManager) {
const pythonDeps = getDependenciesForPython(
projectPath,
detectedPackageManager,
);
pythonDependencies = pythonDeps.dependencies;
pythonDevDependencies = pythonDeps.devDependencies;
}
// Processing Brownie dependencies
if (isBrowniePackageManager) {
const regex = /\s*thirdweb-dev\/contracts@\d+\.\d+\.\d+/g;
const res = await checkIfBrowniePackageIsInstalled(regex);
thirdwebExistsInBrownie = res;
}
// Processing Foundry dependencies
if (isFoundryPackageManager) {
const exists = await getDependenciesForFoundry(
projectPath,
"thirdweb-contracts",
);
thirdwebExistsInFoundry = exists;
}
// Processing Go dependencies
if (isGoPackageManager) {
const deps = getDependenciesForGo(projectPath);
goDependencies = deps;
}
const hasEthers = !!jsDependencies?.ethers || !!jsDevDependencies?.ethers;
const version = !isJSPackageManager
? ""
: options.dev
? "@dev"
: options.nightly
? "@nightly"
: "";
const thirdwebDepsToUpdate = new Set<string>(
[
...Object.keys(jsDependencies).filter((dep) =>
dep.startsWith("@thirdweb-dev/"),
),
...Object.keys(jsDevDependencies).filter((dep) =>
dep.startsWith("@thirdweb-dev/"),
),
...Object.keys(peerDependencies).filter((dep) =>
dep.startsWith("@thirdweb-dev/"),
),
...pythonDependencies
.filter((dep) => dep.startsWith("thirdweb-sdk"))
.map((dep) => dep.split("==")[0]),
...pythonDevDependencies
.filter((dep) => dep.startsWith("thirdweb-sdk"))
.map((dep) => dep.split("==")[0]),
...goDependencies
.filter((dep) => {
console.log("dep", dep);
return dep.name === "github.com/thirdweb-dev/go-sdk/v2";
})
.map((dep) => dep.name),
]
.concat(
thirdwebExistsInBrownie.exists ? [thirdwebExistsInBrownie.package] : [],
)
.concat(thirdwebExistsInFoundry ? ["lib/thirdweb-contracts"] : []),
);
// Figure out which dependencies to install and which to update.
if (detectedAppType === "contract") {
processContractAppType({
detectedPackageManager,
thirdwebDepsToUpdate,
thirdwebDepsToInstall,
isJSPackageManager,
});
} else if (detectedAppType === "app") {
processAppType({
detectedLibrary,
detectedFramework,
hasEthers,
isGoPackageManager,
isJSPackageManager,
isPythonPackageManager,
otherDeps,
thirdwebDepsToInstall,
thirdwebDepsToUpdate,
});
}
// remove exluded dependencies
EXCLUDED_DEPENDENCIES.forEach((excludedDep) => {
thirdwebDepsToUpdate.delete(excludedDep);
thirdwebDepsToInstall.delete(excludedDep);
});
// Ask which dependencies to install
if (thirdwebDepsToInstall.size !== 0) {
const answer = await inquirer.prompt({
type: "checkbox",
choices: [...thirdwebDepsToInstall],
message: "Which thirdweb dependencies would you like to install?",
name: "dependencies",
default: [...thirdwebDepsToInstall],
});
if (answer.dependencies.length !== thirdwebDepsToInstall.size) {
thirdwebDepsToInstall.clear();
answer.dependencies.forEach((dep: string) =>
thirdwebDepsToInstall.add(dep),
);
}
}
// Ask which dependencies to update
if (thirdwebDepsToUpdate.size !== 0) {
const answer = await inquirer.prompt({
type: "checkbox",
choices: [...thirdwebDepsToUpdate],
message: "Which thirdweb dependencies would you like to update?",
name: "dependencies",
default: [...thirdwebDepsToUpdate],
});
if (answer.dependencies.length !== thirdwebDepsToUpdate.size) {
thirdwebDepsToUpdate.clear();
answer.dependencies.forEach((dep: string) =>
thirdwebDepsToUpdate.add(dep),
);
}
}
if (thirdwebDepsToInstall.size === 0 && thirdwebDepsToUpdate.size === 0) {
console.log("No thirdweb dependencies to install or update.");
return;
}
if (debug) {
logger.info("Detected package manager: " + detectedPackageManager);
logger.info("Detected library: " + detectedLibrary);
logger.info("Detected framework: " + detectedFramework);
logger.info("Detected app type: " + detectedAppType);
}
try {
const dependenciesToAdd = [
...[...thirdwebDepsToInstall].map((dep) => `${dep}${version}`),
...otherDeps,
];
const dependenciesToUpdate = [...thirdwebDepsToUpdate].map(
(dep) => `${dep}${version}`,
);
if (thirdwebDepsToInstall.size !== 0) {
installer = ora(
`Installing: "${[...thirdwebDepsToInstall].join('", "')}"`,
).start();
await installOrUpdate(
detectedPackageManager,
dependenciesToAdd,
[],
"install",
{ oldVersion: thirdwebExistsInBrownie.package, debug },
);
installer?.succeed(`Installed: "${dependenciesToAdd.join('", "')}"`);
}
if (thirdwebDepsToUpdate.size !== 0) {
updater = ora(
`Updating: "${[...thirdwebDepsToUpdate].join('", "')}"`,
).start();
await installOrUpdate(
detectedPackageManager,
[],
dependenciesToUpdate,
"update",
{
debug,
},
);
updater?.succeed(`Updated: "${dependenciesToUpdate.join('", "')}"`);
}
} catch (err) {
console.error("Can't install within project: ", err);
return Promise.reject("Can't install within project");
}
}
+29 -3
View File
@@ -1,11 +1,15 @@
import { FrameworkType, LibraryType, PackageManagerType } from "../core/types/ProjectType";
import {
FrameworkType,
LibraryType,
PackageManagerType,
} from "../core/types/ProjectType";
export type IProcessContractAppTypeArgs = {
detectedPackageManager: PackageManagerType;
thirdwebDepsToUpdate: Set<string>;
thirdwebDepsToInstall: Set<string>;
isJSPackageManager: boolean;
}
};
export type IProcessAppTypeArgs = {
detectedLibrary: LibraryType;
@@ -17,4 +21,26 @@ export type IProcessAppTypeArgs = {
isGoPackageManager: boolean;
hasEthers: boolean;
otherDeps: Set<string>;
}
};
export type ApiKey = {
id: string;
key: string;
walletAddresses: string[];
domains: string[];
services?: [
{
name: string;
targetAddresses: string[];
},
];
};
export type ApiResponse = {
data: ApiKey | null;
error: {
code: string;
statusCode: number;
message: string;
};
};
+107 -43
View File
@@ -7,19 +7,21 @@ import { IProcessAppTypeArgs, IProcessContractAppTypeArgs } from "./types";
export function convertDependenciesToStringFormat(dependenciesObject: object) {
return Object.entries(dependenciesObject).map(([pkg, versionDetails]) => {
let version = '';
if (typeof versionDetails === 'string') {
let version = "";
if (typeof versionDetails === "string") {
version = versionDetails;
} else if (typeof versionDetails === 'object') {
} else if (typeof versionDetails === "object") {
version = versionDetails.version;
}
// Replace '^' with nothing as '^' is not a valid version specifier in Python
version = version.replace('^', '');
version = version.replace("^", "");
return `${pkg}==${version}`;
});
}
export const parsePackageJson = (packageJson: Buffer): {
export const parsePackageJson = (
packageJson: Buffer,
): {
dependencies: {
[key: string]: string;
};
@@ -55,10 +57,9 @@ export const parsePipFile = (PipFile: string) => {
export const parsePyProjectToml = (pyProjectToml: string) => {
const pyProjectFile = toml.parse(pyProjectToml) as any;
const dependencies =
(pyProjectFile["tool"]["poetry"]["dependencies"]) || [];
const dependencies = pyProjectFile["tool"]["poetry"]["dependencies"] || [];
const devDependencies =
(pyProjectFile["tool"]["poetry"]["dev-dependencies"]) || [];
pyProjectFile["tool"]["poetry"]["dev-dependencies"] || [];
return {
dependencies: convertDependenciesToStringFormat(dependencies),
@@ -150,20 +151,26 @@ export const getDependenciesForPython = (
export const getLatestVersion = async (packageName: string): Promise<any> => {
try {
const fetch = (await import('node-fetch')).default;
const url = `https://registry.npmjs.org/${packageName}`
const fetch = (await import("node-fetch")).default;
const url = `https://registry.npmjs.org/${packageName}`;
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data: any = await response.json();
return data['dist-tags']['latest'];
return data["dist-tags"]["latest"];
} catch (error) {
console.error(`Failed to get latest version of ${packageName}:`, error);
}
}
};
export const installOrUpdate = async (packageManager: PackageManagerType, dependenciesToAdd: string[], dependenciesToUpdate: string[], typeOfAction: "install" | "update", options?: { oldVersion?: string, debug?: boolean }) => {
export const installOrUpdate = async (
packageManager: PackageManagerType,
dependenciesToAdd: string[],
dependenciesToUpdate: string[],
typeOfAction: "install" | "update",
options?: { oldVersion?: string; debug?: boolean },
) => {
let runner: string = "";
let installCommand: string[] = [];
let updateCommand: string[] = [];
@@ -174,7 +181,7 @@ export const installOrUpdate = async (packageManager: PackageManagerType, depend
case "npm":
runner = "npm";
installCommand = ["install"];
updateCommand = ["install", "--legacy-peer-deps"]
updateCommand = ["install", "--legacy-peer-deps"];
break;
case "yarn":
runner = "yarn";
@@ -208,9 +215,9 @@ export const installOrUpdate = async (packageManager: PackageManagerType, depend
break;
case "brownie":
runner = "brownie";
installCommand = ["pm", "install"]
updateCommand = ["pm", "update"]
deleteCommand = ["pm", "delete"]
installCommand = ["pm", "install"];
updateCommand = ["pm", "update"];
deleteCommand = ["pm", "delete"];
break;
case "foundry":
runner = "forge";
@@ -223,17 +230,25 @@ export const installOrUpdate = async (packageManager: PackageManagerType, depend
}
if (typeOfAction === "install") {
if (!dependenciesToAdd.length) { return; }
if (!dependenciesToAdd.length) {
return;
}
const commands = [...installCommand, ...dependenciesToAdd];
await runCommand(runner, commands, printLogs);
if (options?.oldVersion) {
await runCommand(runner, [...deleteCommand, options.oldVersion], printLogs);
await runCommand(
runner,
[...deleteCommand, options.oldVersion],
printLogs,
);
}
}
if (typeOfAction === "update") {
if (!dependenciesToUpdate.length) { return; }
if (!dependenciesToUpdate.length) {
return;
}
const commands = [...updateCommand, ...dependenciesToUpdate];
await runCommand(runner, commands, printLogs);
}
@@ -259,13 +274,16 @@ export const getDependenciesForBrownie = async () => {
let deps = "";
await runCommand("brownie", ["pm", "list"], true, (data) => {
deps += data.toString();
})
});
// strip out colors from output
deps = deps.replace(/\u001b\[\d+(;\d+)*m/g, '');
deps = deps.replace(/\u001b\[\d+(;\d+)*m/g, "");
return deps;
};
export const getDependenciesForFoundry = async (filePath: string, libName: string) => {
export const getDependenciesForFoundry = async (
filePath: string,
libName: string,
) => {
const fullPath = path.join(filePath, "lib", libName);
return existsSync(fullPath);
};
@@ -275,15 +293,14 @@ type GoModule = {
version: string;
};
export const getDependenciesForGo = (filePath: string): GoModule[] => {
const content = readFileSync(filePath + "/go.mod", "utf-8");
const lines = content.split("\n");
const modules: GoModule[] = [];
for (const line of lines) {
if (line.includes('github.com/thirdweb-dev')) {
const words = line.split(/\s+/); // split on any amount of whitespace
if (line.includes("github.com/thirdweb-dev")) {
const words = line.split(/\s+/); // split on any amount of whitespace
const name = words[1].trim();
const version = words[2];
modules.push({ name, version });
@@ -291,10 +308,17 @@ export const getDependenciesForGo = (filePath: string): GoModule[] => {
}
return modules;
}
};
export const processContractAppType = async (args: IProcessContractAppTypeArgs): Promise<void> => {
const { detectedPackageManager, thirdwebDepsToUpdate, thirdwebDepsToInstall, isJSPackageManager } = args;
export const processContractAppType = async (
args: IProcessContractAppTypeArgs,
): Promise<void> => {
const {
detectedPackageManager,
thirdwebDepsToUpdate,
thirdwebDepsToInstall,
isJSPackageManager,
} = args;
if (isJSPackageManager) {
if (!thirdwebDepsToUpdate.has(`@thirdweb-dev/contracts`)) {
@@ -303,15 +327,29 @@ export const processContractAppType = async (args: IProcessContractAppTypeArgs):
}
if (detectedPackageManager === "brownie") {
const latestThirdwebContractVersion = await getLatestVersion("@thirdweb-dev/contracts");
const latestThirdwebContractVersion = await getLatestVersion(
"@thirdweb-dev/contracts",
);
// Exit out early if the user already has the latest version of thirdweb installed.
if (thirdwebDepsToUpdate.has(`thirdweb-dev/contracts@${latestThirdwebContractVersion}`)) {
return Promise.reject("You already have the latest version of thirdweb-dev/contracts installed.");
if (
thirdwebDepsToUpdate.has(
`thirdweb-dev/contracts@${latestThirdwebContractVersion}`,
)
) {
return Promise.reject(
"You already have the latest version of thirdweb-dev/contracts installed.",
);
}
if (!thirdwebDepsToUpdate.has(`thirdweb-dev/contracts@${latestThirdwebContractVersion}`)) {
thirdwebDepsToInstall.add(`thirdweb-dev/contracts@${latestThirdwebContractVersion}`);
if (
!thirdwebDepsToUpdate.has(
`thirdweb-dev/contracts@${latestThirdwebContractVersion}`,
)
) {
thirdwebDepsToInstall.add(
`thirdweb-dev/contracts@${latestThirdwebContractVersion}`,
);
}
}
@@ -322,8 +360,20 @@ export const processContractAppType = async (args: IProcessContractAppTypeArgs):
}
};
export const processAppType = async (args: IProcessAppTypeArgs): Promise<void> => {
const { detectedFramework, thirdwebDepsToUpdate, thirdwebDepsToInstall, hasEthers, isJSPackageManager, otherDeps, isPythonPackageManager, isGoPackageManager, detectedLibrary } = args;
export const processAppType = async (
args: IProcessAppTypeArgs,
): Promise<void> => {
const {
detectedFramework,
thirdwebDepsToUpdate,
thirdwebDepsToInstall,
hasEthers,
isJSPackageManager,
otherDeps,
isPythonPackageManager,
isGoPackageManager,
detectedLibrary,
} = args;
const reactlibs = [
"react",
@@ -335,21 +385,35 @@ export const processAppType = async (args: IProcessAppTypeArgs): Promise<void> =
"vue",
"vite",
"svelte",
]
const shouldInstallThirdwebReact = reactlibs.includes(detectedFramework as string);
];
const shouldInstallThirdwebReact = reactlibs.includes(
detectedFramework as string,
);
const shouldInstallThirdwebReactNative = detectedLibrary === "react-native";
if (isJSPackageManager) {
if (!thirdwebDepsToUpdate.has(`@thirdweb-dev/react`) && shouldInstallThirdwebReact) {
if (
!thirdwebDepsToUpdate.has(`@thirdweb-dev/react`) &&
shouldInstallThirdwebReact
) {
thirdwebDepsToInstall.add(`@thirdweb-dev/react`);
}
if (!thirdwebDepsToUpdate.has(`@thirdweb-dev/sdk`) && !shouldInstallThirdwebReactNative) {
if (
!thirdwebDepsToUpdate.has(`@thirdweb-dev/sdk`) &&
!shouldInstallThirdwebReactNative
) {
thirdwebDepsToInstall.add(`@thirdweb-dev/sdk`);
}
if (!thirdwebDepsToUpdate.has(`@thirdweb-dev/react-native`) && shouldInstallThirdwebReactNative) {
if (
!thirdwebDepsToUpdate.has(`@thirdweb-dev/react-native`) &&
shouldInstallThirdwebReactNative
) {
thirdwebDepsToInstall.add(`@thirdweb-dev/react-native`);
}
if (!thirdwebDepsToUpdate.has(`@thirdweb-dev/react-native-compat`) && shouldInstallThirdwebReactNative) {
if (
!thirdwebDepsToUpdate.has(`@thirdweb-dev/react-native-compat`) &&
shouldInstallThirdwebReactNative
) {
thirdwebDepsToInstall.add(`@thirdweb-dev/react-native-compat`);
}
if (!hasEthers) {
@@ -368,4 +432,4 @@ export const processAppType = async (args: IProcessAppTypeArgs): Promise<void> =
thirdwebDepsToInstall.add("github.com/thirdweb-dev/go-sdk/v2");
}
}
};
};
+14 -4
View File
@@ -51,9 +51,14 @@ export async function upload(
const spin = spinner(
"Uploading directory to IPFS. This may take a while depending on file sizes.",
);
const uris = await storage.uploadBatch(files);
spin.succeed("Successfully uploaded directory to IPFS");
uri = uris[0].substring(0, uris[0].lastIndexOf("/"));
try {
const uris = await storage.uploadBatch(files);
spin.succeed("Successfully uploaded directory to IPFS");
uri = uris[0].substring(0, uris[0].lastIndexOf("/"));
} catch (err) {
spin.fail("Failed to upload directory to IPFS.");
return Promise.reject(err);
}
} else if (fileType.isFile()) {
const fileName = path.parse(uploadPath).base;
const fileData = fs.readFileSync(uploadPath);
@@ -62,7 +67,12 @@ export async function upload(
const spin = spinner(
"Uploading file to IPFS. This may take a while depending on file sizes.",
);
uri = await storage.upload(file, { uploadWithoutDirectory: true });
try {
uri = await storage.upload(file, { uploadWithoutDirectory: true });
} catch (err) {
spin.fail("Failed to upload file to IPFS.");
return Promise.reject(err);
}
spin.succeed("Successfully uploaded file to IPFS.");
} else {
return Promise.reject("Invalid path provided");
+38
View File
@@ -1,5 +1,43 @@
# @thirdweb-dev/react-core
## 3.14.17
### Patch Changes
- [#1316](https://github.com/thirdweb-dev/js/pull/1316) [`d8447146`](https://github.com/thirdweb-dev/js/commit/d8447146092c1962f410155ab2047225453aaa2b) Thanks [@iketw](https://github.com/iketw)! - Adds new `clientId` prop to access thirdweb's services.
You can create a _free_ `clientId` [on the thirdweb Dashboard](https://thirdweb.com/dashboard)
```javascript
<ThirdwebProvider clientId="your-client-id" />
```
- Updated dependencies [[`44dbf283`](https://github.com/thirdweb-dev/js/commit/44dbf283c985f3e2ecbeb1cfdf09fe1d84e26298), [`d8447146`](https://github.com/thirdweb-dev/js/commit/d8447146092c1962f410155ab2047225453aaa2b), [`d8447146`](https://github.com/thirdweb-dev/js/commit/d8447146092c1962f410155ab2047225453aaa2b), [`d8447146`](https://github.com/thirdweb-dev/js/commit/d8447146092c1962f410155ab2047225453aaa2b), [`d8447146`](https://github.com/thirdweb-dev/js/commit/d8447146092c1962f410155ab2047225453aaa2b)]:
- @thirdweb-dev/storage@1.2.0
- @thirdweb-dev/sdk@3.10.37
- @thirdweb-dev/chains@0.1.35
- @thirdweb-dev/wallets@1.1.0
## 3.14.16
### Patch Changes
- [#1356](https://github.com/thirdweb-dev/js/pull/1356) [`7a6534d1`](https://github.com/thirdweb-dev/js/commit/7a6534d1a98cab1eb47cf88d13e2b7ec04037e42) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Fix useOwnedNFT enabled state
- Updated dependencies [[`dac8fa7d`](https://github.com/thirdweb-dev/js/commit/dac8fa7d98b6952acf8d13e173099889c1d47da8), [`c5761b99`](https://github.com/thirdweb-dev/js/commit/c5761b99e9797772481506e90cbfa5c35a05bd1d), [`b96a2282`](https://github.com/thirdweb-dev/js/commit/b96a2282730582b684cc96802649a96851af5220)]:
- @thirdweb-dev/sdk@3.10.36
- @thirdweb-dev/wallets@1.0.12
## 3.14.15
### Patch Changes
- Updated dependencies [[`8dd7540c`](https://github.com/thirdweb-dev/js/commit/8dd7540c455aa70534f6d29986537592fd12169b), [`8dd7540c`](https://github.com/thirdweb-dev/js/commit/8dd7540c455aa70534f6d29986537592fd12169b)]:
- @thirdweb-dev/storage@1.1.9
- @thirdweb-dev/chains@0.1.34
- @thirdweb-dev/sdk@3.10.35
- @thirdweb-dev/wallets@1.0.11
## 3.14.14
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/react-core",
"version": "3.14.14",
"version": "3.14.17",
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/react-core",
"author": "thirdweb eng <[email protected]>",
"license": "Apache-2.0",
@@ -84,7 +84,7 @@
"@types/react": "^18.2.14",
"@typescript-eslint/eslint-plugin": "^5.60.1",
"@typescript-eslint/parser": "^5.60.1",
"eslint": "^8.43.0",
"eslint": "^8.45.0",
"eslint-config-prettier": "^8.8.0",
"eslint-config-thirdweb": "workspace:*",
"eslint-plugin-better-tree-shaking": "0.0.3",
@@ -93,7 +93,7 @@
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-tsdoc": "^0.2.16",
"ethers": "^5.7.2",
"prettier": "^2.8.8",
"prettier": "^3.0.0",
"react": "^18.2.0",
"typescript": "^5.1.6"
},
@@ -1 +1 @@
export const AUTH_TOKEN_STORAGE_KEY = "auth_token_storage_key";
export const AUTH_TOKEN_STORAGE_KEY = "auth_token_storage_key";
@@ -1,2 +0,0 @@
export const DEFAULT_API_KEY =
"fb52f0ca630bdd02f0d5210d9e8e3568272ac765dd0709272918eefb29d3ac9e";
@@ -1,5 +1,5 @@
import { ThirdwebAuthProvider } from "../../evm/contexts/ThirdwebAuthProvider";
import { useUpdateChainsWithApiKeys } from "../../evm/hooks/chain-hooks";
import { useUpdateChainsWithClientId } from "../../evm/hooks/chain-hooks";
import { ThirdwebSDKProvider } from "../../evm/providers/thirdweb-sdk-provider";
import { ThirdwebSDKProviderProps } from "../../evm/providers/types";
import { WalletConfig } from "../types/wallet";
@@ -106,12 +106,10 @@ export const ThirdwebProviderCore = <TChains extends Chain[]>({
}, [props.supportedChains, activeChain]);
const [supportedChainsWithKey, activeChainIdOrObjWithKey] =
useUpdateChainsWithApiKeys(
useUpdateChainsWithClientId(
supportedChainsNonNull,
props.activeChain || supportedChainsNonNull[0],
props.thirdwebApiKey,
props.alchemyApiKey,
props.infuraApiKey,
props.clientId,
);
const activeChainWithKey = useMemo(() => {
@@ -155,6 +153,7 @@ export const ThirdwebProviderCore = <TChains extends Chain[]>({
activeChain={activeChainWithKey}
autoSwitch={props.autoSwitch}
autoConnectTimeout={props.autoConnectTimeout}
clientId={props.clientId}
>
<ThirdwebSDKProviderWrapper
queryClient={props.queryClient}
@@ -163,9 +162,7 @@ export const ThirdwebProviderCore = <TChains extends Chain[]>({
activeChain={activeChainWithKey}
storageInterface={props.storageInterface}
authConfig={props.authConfig}
thirdwebApiKey={props.thirdwebApiKey}
alchemyApiKey={props.alchemyApiKey}
infuraApiKey={props.infuraApiKey}
clientId={props.clientId}
>
<ThirdwebAuthProvider value={props.authConfig}>
{props.children}
@@ -1,6 +1,9 @@
import { DAppMetaData } from "../types/dAppMeta";
import type { WalletConfig, WalletInstance } from "../types/wallet";
import { ThirdwebThemeContext } from "./theme-context";
import type {
WalletConfig,
WalletInstance,
WalletOptions,
} from "../types/wallet";
import { Chain } from "@thirdweb-dev/chains";
import {
AbstractClientWallet,
@@ -102,6 +105,7 @@ export function ThirdwebWalletProvider(
chains: Chain[];
autoSwitch?: boolean;
autoConnectTimeout?: number;
clientId?: string;
}>,
) {
const [signer, setSigner] = useState<Signer | undefined>(undefined);
@@ -130,21 +134,14 @@ export function ThirdwebWalletProvider(
// if autoSwitch is enabled - enforce connection to activeChain
const chainToConnect = props.autoSwitch ? props.activeChain : undefined;
const theme = useContext(ThirdwebThemeContext);
const walletParams = useMemo(() => {
const walletChains = props.chains;
const walletOptions = {
chains: walletChains,
dappMetadata: props.dAppMeta,
};
const walletParams: WalletOptions = useMemo(() => {
return {
...walletOptions,
chains: props.chains,
dappMetadata: props.dAppMeta,
chain: props.activeChain || props.chains[0],
theme: theme || "dark",
clientId: props.clientId,
};
}, [props.chains, props.dAppMeta, props.activeChain, theme]);
}, [props.chains, props.dAppMeta, props.activeChain, props.clientId]);
const createWalletInstance = useCallback(
<I extends WalletInstance>(walletConfig: WalletConfig<I>): I => {
@@ -494,7 +491,14 @@ async function getLastConnectedWalletInfo() {
return null;
}
return JSON.parse(str) as LastConnectedWalletInfo;
try {
return JSON.parse(str) as LastConnectedWalletInfo;
} catch {
await lastConnectedWalletStorage.removeItem(
LAST_CONNECTED_WALLET_STORAGE_KEY,
);
return null;
}
}
async function saveLastConnectedWalletInfo(
+3 -10
View File
@@ -1,19 +1,12 @@
import type {
AbstractClientWallet,
Chain,
DAppMetaData,
} from "@thirdweb-dev/wallets";
import type { AbstractClientWallet, Chain } from "@thirdweb-dev/wallets";
import { WalletOptions as WalletOptions_ } from "@thirdweb-dev/wallets";
// these are extra options provided by the react-core package
export type ExtraCoreWalletOptions = {
chain: Chain;
};
export type WalletOptions = {
chains: Chain[];
walletId?: string;
dappMetadata?: DAppMetaData;
} & ExtraCoreWalletOptions;
export type WalletOptions = WalletOptions_<ExtraCoreWalletOptions>;
export type WalletInstance = AbstractClientWallet;
@@ -1,16 +0,0 @@
import { getChainRPCs, ChainRPCOptions, Chain } from "@thirdweb-dev/chains";
/**
*
* use the keys and return a new chain object with updated RPCs
*/
export const updateChainRPCs = (chain: Chain, options: ChainRPCOptions) => {
try {
return {
...chain,
rpc: getChainRPCs(chain, options),
};
} catch (error) {
return chain;
}
};
@@ -3,9 +3,7 @@ import React, { createContext, PropsWithChildren, useContext } from "react";
interface ThirdwebConfigContext {
chains: Chain[];
thirdwebApiKey?: string;
alchemyApiKey?: string;
infuraApiKey?: string;
clientId?: string;
}
const ThirdwebConfigContext =
@@ -0,0 +1,11 @@
import { ThirdwebSDK } from "@thirdweb-dev/sdk";
import { createContext } from "react";
export interface TWSDKContext {
sdk?: ThirdwebSDK;
_inProvider?: true;
}
export const ThirdwebSDKContext = /* @__PURE__ */ createContext<TWSDKContext>(
{},
);
@@ -1,6 +1,6 @@
import { __DEV__ } from "../../core/constants/runtime";
import { useThirdwebConfigContext } from "./thirdweb-config";
import { getChainRPC } from "@thirdweb-dev/chains";
import { getValidChainRPCs } from "@thirdweb-dev/chains";
import { UserWallet } from "@thirdweb-dev/sdk";
import { Signer } from "ethers";
import React, {
@@ -10,6 +10,7 @@ import React, {
useEffect,
useState,
} from "react";
import { useSDK } from "../hooks/useSDK";
interface ThirdwebConnectedWalletContext {
wallet: UserWallet | undefined;
@@ -33,8 +34,8 @@ const ThirdwebConnectedWalletContext =
export const ThirdwebConnectedWalletProvider: React.FC<
PropsWithChildren<{ signer?: Signer }>
> = ({ signer, children }) => {
const { chains, thirdwebApiKey, alchemyApiKey, infuraApiKey } =
useThirdwebConfigContext();
const { chains, clientId } = useThirdwebConfigContext();
const storage = useSDK()?.storage;
const [contextValue, setContextValue] =
useState<ThirdwebConnectedWalletContext>({
@@ -50,6 +51,10 @@ export const ThirdwebConnectedWalletProvider: React.FC<
}, [signer]);
useEffect(() => {
if (!storage) {
return;
}
let s = signer;
if (signer) {
// just get both of these up front and keep them around with the context
@@ -59,11 +64,7 @@ export const ThirdwebConnectedWalletProvider: React.FC<
let rpcUrl = undefined;
if (chain) {
try {
rpcUrl = getChainRPC(chain, {
thirdwebApiKey,
alchemyApiKey,
infuraApiKey,
});
rpcUrl = getValidChainRPCs(chain, clientId)[0];
} catch (e) {
// failed to get a viable rpc url, nothing we can do
console.error(e);
@@ -72,14 +73,18 @@ export const ThirdwebConnectedWalletProvider: React.FC<
// only if the signer is still the same!
if (signer === s) {
const wallet = new UserWallet(signer, {
readonlySettings: rpcUrl
? {
rpcUrl,
chainId,
}
: undefined,
});
const wallet = new UserWallet(
signer,
{
readonlySettings: rpcUrl
? {
rpcUrl,
chainId,
}
: undefined,
},
storage,
);
setContextValue({ wallet, address, chainId, signer });
}
})
@@ -100,7 +105,7 @@ export const ThirdwebConnectedWalletProvider: React.FC<
s = undefined;
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [signer, thirdwebApiKey, alchemyApiKey, infuraApiKey]);
}, [signer, clientId]);
return (
<ThirdwebConnectedWalletContext.Provider value={contextValue}>
@@ -2,7 +2,7 @@ import {
RequiredParam,
requiredParamInvariant,
} from "../../../core/query-utils/required-param";
import { useSDKChainId } from "../../providers/thirdweb-sdk-provider";
import { useSDKChainId } from "../useSDK";
import { WalletAddress } from "../../types";
import {
cacheKeys,
@@ -3,7 +3,7 @@ import {
RequiredParam,
requiredParamInvariant,
} from "../../../core/query-utils/required-param";
import { useSDKChainId } from "../../providers/thirdweb-sdk-provider";
import { useSDKChainId } from "../useSDK";
import {
cacheKeys,
invalidateContractAndBalances,
@@ -2,7 +2,7 @@ import {
RequiredParam,
requiredParamInvariant,
} from "../../../core/query-utils/required-param";
import { useSDKChainId } from "../../providers/thirdweb-sdk-provider";
import { useSDKChainId } from "../useSDK";
import {
cacheKeys,
invalidateContractAndBalances,
@@ -2,7 +2,7 @@ import {
RequiredParam,
requiredParamInvariant,
} from "../../../core/query-utils/required-param";
import { useSDK, useSDKChainId } from "../../providers/thirdweb-sdk-provider";
import { useSDK, useSDKChainId } from "../useSDK";
import { getErcs, DropContract, WalletAddress } from "../../types";
import {
cacheKeys,
@@ -2,7 +2,7 @@ import {
RequiredParam,
requiredParamInvariant,
} from "../../../core/query-utils/required-param";
import { useSDKChainId } from "../../providers/thirdweb-sdk-provider";
import { useSDKChainId } from "../useSDK";
import { WalletAddress } from "../../types";
import {
cacheKeys,
@@ -3,7 +3,7 @@ import {
RequiredParam,
requiredParamInvariant,
} from "../../../core/query-utils/required-param";
import { useSDK, useSDKChainId } from "../../providers/thirdweb-sdk-provider";
import { useSDK, useSDKChainId } from "../useSDK";
import { ContractAddress } from "../../types";
import {
cacheKeys,
@@ -2,7 +2,7 @@ import {
RequiredParam,
requiredParamInvariant,
} from "../../../core/query-utils/required-param";
import { useSDKChainId } from "../../providers/thirdweb-sdk-provider";
import { useSDKChainId } from "../useSDK";
import {
ClaimNFTParams,
ClaimNFTReturnType,
@@ -2,7 +2,7 @@ import {
RequiredParam,
requiredParamInvariant,
} from "../../../core/query-utils/required-param";
import { useSDKChainId } from "../../providers/thirdweb-sdk-provider";
import { useSDKChainId } from "../useSDK";
import {
AcceptDirectOffer,
BuyFromListingParams,
@@ -596,7 +596,10 @@ export function useBidBuffer(
) {
const contractAddress = contract?.getAddress();
return useQueryWithNetwork(
cacheKeys.contract.marketplace.auction.getBidBufferBps(contractAddress, listingId),
cacheKeys.contract.marketplace.auction.getBidBufferBps(
contractAddress,
listingId,
),
() => {
requiredParamInvariant(contract, "No Contract instance provided");
@@ -1426,13 +1429,13 @@ export function useBuyDirectListing(contract: RequiredParam<MarketplaceV3>) {
requiredParamInvariant(contract, "No Contract instance provided");
invariant(
contract.directListings.buyFromListing,
"contract does not support directListings.buyFromListing"
"contract does not support directListings.buyFromListing",
);
return await contract.directListings.buyFromListing(
data.listingId,
data.quantity,
data.buyer
data.buyer,
);
},
{
@@ -3,7 +3,7 @@ import {
requiredParamInvariant,
RequiredParam,
} from "../../../core/query-utils/required-param";
import { useSDKChainId } from "../../providers/thirdweb-sdk-provider";
import { useSDKChainId } from "../useSDK";
import {
AirdropNFTParams,
BurnNFTParams,
@@ -256,7 +256,7 @@ export function useOwnedNFTs<TContract extends NFTContract>(
invariant(false, "Unknown NFT type");
},
{
enabled: !!erc721 || (!!erc1155 && !!ownerWalletAddress),
enabled: (!!erc721 || !!erc1155) && !!ownerWalletAddress,
},
);
}
@@ -2,7 +2,7 @@ import {
requiredParamInvariant,
RequiredParam,
} from "../../../core/query-utils/required-param";
import { useSDKChainId } from "../../providers/thirdweb-sdk-provider";
import { useSDKChainId } from "../useSDK";
import { WalletAddress } from "../../types";
import {
cacheKeys,
@@ -2,7 +2,7 @@ import {
requiredParamInvariant,
RequiredParam,
} from "../../../core/query-utils/required-param";
import { useSDKChainId } from "../../providers/thirdweb-sdk-provider";
import { useSDKChainId } from "../useSDK";
import {
ClaimTokenParams,
getErc20,
@@ -1,8 +1,7 @@
import { updateChainRPCs } from "../../core/utils/updateChainRpcs";
import { useMemo } from "react";
import type { Chain } from "@thirdweb-dev/chains";
import { updateChainRPCs, type Chain } from "@thirdweb-dev/chains";
export function useUpdateChainsWithApiKeys<
export function useUpdateChainsWithClientId<
TChains extends Chain[],
TActiveChain extends
| Chain
@@ -11,22 +10,11 @@ export function useUpdateChainsWithApiKeys<
>(
supportedChains: NonNullable<TChains>,
activeChain: TActiveChain,
thirdwebApiKey?: string,
alchemyApiKey?: string,
infuraApiKey?: string,
clientId?: string,
) {
const keys = useMemo(
() => ({
thirdwebApiKey: thirdwebApiKey,
alchemyApiKey: alchemyApiKey,
infuraApiKey: infuraApiKey,
}),
[thirdwebApiKey, alchemyApiKey, infuraApiKey],
);
const supportedChainsWithKey = useMemo(() => {
return supportedChains.map((chain) => updateChainRPCs(chain, keys));
}, [supportedChains, keys]);
return supportedChains.map((chain) => updateChainRPCs(chain, clientId));
}, [supportedChains, clientId]);
const activeChainIdOrObjWithKey = useMemo(() => {
if (
@@ -37,8 +25,8 @@ export function useUpdateChainsWithApiKeys<
return activeChain;
}
return updateChainRPCs(activeChain, keys);
}, [activeChain, keys]);
return updateChainRPCs(activeChain, clientId);
}, [activeChain, clientId]);
return [supportedChainsWithKey, activeChainIdOrObjWithKey] as const;
}
@@ -1,4 +1,4 @@
import { useSDKChainId } from "../../providers/thirdweb-sdk-provider";
import { useSDKChainId } from "../useSDK";
import { createCacheKeyWithNetwork } from "../../utils/cache-keys";
import {
QueryFunction,
@@ -1,4 +1,4 @@
import { useSDK } from "../../providers/thirdweb-sdk-provider";
import { useSDK } from "../useSDK";
export { useStorageUpload } from "./useStorageUpload";
@@ -1,4 +1,4 @@
import { useSDK } from "../../providers/thirdweb-sdk-provider";
import { useSDK } from "../useSDK";
import { useMutation } from "@tanstack/react-query";
import { IpfsUploadBatchOptions, UploadOptions } from "@thirdweb-dev/storage";
import invariant from "tiny-invariant";
@@ -1,4 +1,4 @@
import { useSDKChainId } from "../providers/thirdweb-sdk-provider";
import { useSDKChainId } from "./useSDK";
import { useChainId } from "./wallet";
/**
@@ -0,0 +1,39 @@
import { ThirdwebSDK } from "@thirdweb-dev/sdk";
import { useContext } from "react";
import invariant from "tiny-invariant";
import { TWSDKContext, ThirdwebSDKContext } from "../contexts/thirdweb-sdk";
/**
* @internal
*/
export function useSDKContext(): TWSDKContext {
const ctx = useContext(ThirdwebSDKContext);
invariant(
ctx._inProvider,
"useSDK must be called from within a ThirdwebProvider, did you forget to wrap your app in a <ThirdwebProvider />?",
);
return ctx;
}
/**
*
* @returns {@link ThirdwebSDK}
* Access the instance of the thirdweb SDK created by the ThirdwebProvider
* to call methods using the connected wallet on the desiredChainId.
* @example
* ```javascript
* const sdk = useSDK();
* ```
*/
export function useSDK(): ThirdwebSDK | undefined {
const { sdk } = useSDKContext();
return sdk;
}
/**
* @internal
*/
export function useSDKChainId(): number | undefined {
const sdk = useSDK();
return (sdk as any)?._chainId;
}
+2 -5
View File
@@ -242,11 +242,8 @@ export { useSupportedWallet } from "./hooks/useSupportedWallet";
export { useAppURI, useSetAppURI } from "./hooks/async/app";
// sdk provider
export {
ThirdwebSDKProvider,
useSDK,
useSDKChainId,
} from "./providers/thirdweb-sdk-provider";
export { ThirdwebSDKProvider } from "./providers/thirdweb-sdk-provider";
export { useSDK, useSDKChainId } from "./hooks/useSDK";
export type { ThirdwebSDKProviderProps } from "./providers/types";
// types
@@ -1,20 +1,13 @@
import { DEFAULT_API_KEY } from "../../core/constants/rpc";
import { useMemo, useEffect } from "react";
import { QueryClientProviderWithDefault } from "../../core/providers/query-client";
import { ThirdwebConfigProvider } from "../contexts/thirdweb-config";
import { ThirdwebSDKContext } from "../contexts/thirdweb-sdk";
import { ThirdwebConnectedWalletProvider } from "../contexts/thirdweb-wallet";
import { useUpdateChainsWithApiKeys } from "../hooks/chain-hooks";
import { useUpdateChainsWithClientId } from "../hooks/chain-hooks";
import { ThirdwebSDKProviderProps } from "./types";
import { Chain, defaultChains, getChainRPC } from "@thirdweb-dev/chains";
import { Chain, defaultChains, getValidChainRPCs } from "@thirdweb-dev/chains";
import { SDKOptionsOutput, ThirdwebSDK } from "@thirdweb-dev/sdk/evm";
import { createContext, useContext, useEffect, useMemo } from "react";
import invariant from "tiny-invariant";
interface TWSDKContext {
sdk?: ThirdwebSDK;
_inProvider?: true;
}
const ThirdwebSDKContext = /* @__PURE__ */ createContext<TWSDKContext>({});
import { checkClientIdOrSecretKey } from "@thirdweb-dev/sdk";
/**
*
@@ -27,9 +20,7 @@ const WrappedThirdwebSDKProvider = <TChains extends Chain[]>({
activeChain,
signer,
children,
thirdwebApiKey,
infuraApiKey,
alchemyApiKey,
clientId,
}: React.PropsWithChildren<
{ supportedChains: Readonly<TChains> } & Omit<
ThirdwebSDKProviderProps<TChains>,
@@ -70,11 +61,7 @@ const WrappedThirdwebSDKProvider = <TChains extends Chain[]>({
if (supportedChain && supportedChain.rpc.length > 0) {
try {
const rpcUrl = getChainRPC(supportedChain, {
thirdwebApiKey,
infuraApiKey,
alchemyApiKey,
});
const rpcUrl = getValidChainRPCs(supportedChain, clientId)[0];
readonlySettings = {
chainId: supportedChain.chainId,
@@ -105,7 +92,7 @@ const WrappedThirdwebSDKProvider = <TChains extends Chain[]>({
// sdk from chainId
sdk_ = new ThirdwebSDK(
chainId,
{ ...mergedOptions, infuraApiKey, alchemyApiKey, thirdwebApiKey },
{ ...mergedOptions, clientId },
storageInterface,
);
}
@@ -126,15 +113,7 @@ const WrappedThirdwebSDKProvider = <TChains extends Chain[]>({
(sdk_ as any)._chainId = chainId;
return sdk_;
}, [
activeChainId,
alchemyApiKey,
infuraApiKey,
supportedChains,
sdkOptions,
storageInterface,
thirdwebApiKey,
]);
}, [activeChainId, supportedChains, sdkOptions, storageInterface, clientId]);
useEffect(() => {
// if we have an sdk and a signer update the signer
@@ -179,21 +158,24 @@ export const ThirdwebSDKProvider = <TChains extends Chain[]>({
queryClient,
supportedChains,
activeChain,
thirdwebApiKey = DEFAULT_API_KEY,
alchemyApiKey,
infuraApiKey,
clientId,
...restProps
}: React.PropsWithChildren<ThirdwebSDKProviderProps<TChains>>) => {
if (!clientId) {
checkClientIdOrSecretKey(
"No clientId provided in ThirdwebSDK. You will have limited access to thirdweb's services for storage, RPC, and account abstraction. You can get a clientId from https://thirdweb.com/create-api-key",
clientId,
undefined,
);
}
const supportedChainsNonNull = useMemo(() => {
return supportedChains || (defaultChains as any as TChains);
}, [supportedChains]);
const [supportedChainsWithKey, activeChainIdOrObjWithKey] =
useUpdateChainsWithApiKeys(
useUpdateChainsWithClientId(
supportedChainsNonNull,
activeChain || supportedChainsNonNull[0],
thirdwebApiKey,
alchemyApiKey,
infuraApiKey,
clientId,
);
const mergedChains = useMemo(() => {
@@ -222,61 +204,22 @@ export const ThirdwebSDKProvider = <TChains extends Chain[]>({
<ThirdwebConfigProvider
value={{
chains: mergedChains as Chain[],
thirdwebApiKey,
alchemyApiKey,
infuraApiKey,
clientId,
}}
>
<ThirdwebConnectedWalletProvider signer={signer}>
<QueryClientProviderWithDefault queryClient={queryClient}>
<WrappedThirdwebSDKProvider
signer={signer}
supportedChains={mergedChains}
thirdwebApiKey={thirdwebApiKey}
alchemyApiKey={alchemyApiKey}
infuraApiKey={infuraApiKey}
activeChain={activeChainIdOrObjWithKey}
{...restProps}
>
<QueryClientProviderWithDefault queryClient={queryClient}>
<WrappedThirdwebSDKProvider
signer={signer}
supportedChains={mergedChains}
clientId={clientId}
activeChain={activeChainIdOrObjWithKey}
{...restProps}
>
<ThirdwebConnectedWalletProvider signer={signer}>
{children}
</WrappedThirdwebSDKProvider>
</QueryClientProviderWithDefault>
</ThirdwebConnectedWalletProvider>
</ThirdwebConnectedWalletProvider>
</WrappedThirdwebSDKProvider>
</QueryClientProviderWithDefault>
</ThirdwebConfigProvider>
);
};
/**
* @internal
*/
function useSDKContext(): TWSDKContext {
const ctx = useContext(ThirdwebSDKContext);
invariant(
ctx._inProvider,
"useSDK must be called from within a ThirdwebProvider, did you forget to wrap your app in a <ThirdwebProvider />?",
);
return ctx;
}
/**
*
* @returns {@link ThirdwebSDK}
* Access the instance of the thirdweb SDK created by the ThirdwebProvider
* to call methods using the connected wallet on the desiredChainId.
* @example
* ```javascript
* const sdk = useSDK();
* ```
*/
export function useSDK(): ThirdwebSDK | undefined {
const { sdk } = useSDKContext();
return sdk;
}
/**
* @internal
*/
export function useSDKChainId(): number | undefined {
const sdk = useSDK();
return (sdk as any)?._chainId;
}
@@ -2,7 +2,7 @@ import type { QueryClientProviderProps } from "../../core/providers/query-client
import type { ThirdwebAuthConfig } from "../contexts/thirdweb-auth";
import type { Chain } from "@thirdweb-dev/chains";
import type { SDKOptions } from "@thirdweb-dev/sdk";
import type { ThirdwebStorage } from "@thirdweb-dev/storage";
import type { IThirdwebStorage } from "@thirdweb-dev/storage";
import type { Signer } from "ethers";
export interface ThirdwebSDKProviderProps<TChains extends Chain[]>
@@ -22,7 +22,7 @@ export interface ThirdwebSDKProviderProps<TChains extends Chain[]>
/**
* The storage interface to use with the sdk.
*/
storageInterface?: ThirdwebStorage;
storageInterface?: IThirdwebStorage;
/**
* The configuration used for thirdweb auth usage. Enables users to login
* to backends with their wallet.
@@ -41,8 +41,6 @@ export interface ThirdwebSDKProviderProps<TChains extends Chain[]>
| (string & {})
| Chain;
// api keys that can be passed
thirdwebApiKey?: string;
alchemyApiKey?: string;
infuraApiKey?: string;
// client Id for thirdweb services
clientId?: string;
}
@@ -74,7 +74,7 @@ export const ThirdwebSDKProvider: ComponentWithChildren<
sdk,
desiredNetwork: network || "unknown",
_inProvider: true,
} as const),
}) as const,
[sdk, network],
);
+10
View File
@@ -1,5 +1,15 @@
# @thirdweb-dev/react-native-compat
## 0.2.36
### Patch Changes
- [#1316](https://github.com/thirdweb-dev/js/pull/1316) [`d8447146`](https://github.com/thirdweb-dev/js/commit/d8447146092c1962f410155ab2047225453aaa2b) Thanks [@iketw](https://github.com/iketw)! - Adds support for clientId / secretKey
## 0.2.35
## 0.2.34
## 0.2.33
## 0.2.32
+11 -1
View File
@@ -1,3 +1,13 @@
module.exports = {
presets: ['module:metro-react-native-babel-preset'],
presets: ["module:metro-react-native-babel-preset"],
plugins: [
[
"module-resolver",
{
alias: {
crypto: "react-native-crypto",
},
},
],
],
};
+4 -2
View File
@@ -1,7 +1,7 @@
{
"name": "@thirdweb-dev/react-native-compat",
"description": "Shims for Thirdweb in React Native Projects",
"version": "0.2.33",
"version": "0.2.36",
"author": "thirdweb eng <[email protected]>",
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/react-native-compat",
"license": "Apache-2.0",
@@ -25,7 +25,8 @@
"@walletconnect/react-native-compat": "^2.8.6",
"base-64": "^1.0.0",
"big-integer": "^1.6.51",
"react-native-get-random-values": "^1.9.0"
"react-native-get-random-values": "^1.9.0",
"react-native-url-polyfill": "^2.0.0"
},
"peerDependencies": {
"ethers": "^5.5.1",
@@ -33,6 +34,7 @@
"react-native": ">=0.70.0"
},
"devDependencies": {
"@babel/preset-env": "^7.22.5",
"@thirdweb-dev/tsconfig": "workspace:*",
"@types/node": "^18.16.18",
"babel-plugin-module-resolver": "^5.0.0",
@@ -2,4 +2,7 @@
// prettier-ignore
import "react-native-get-random-values";
// URL polyfill for React Native
import 'react-native-url-polyfill/auto';
import "./shims";
+41
View File
@@ -1,5 +1,46 @@
# @thirdweb-dev/react-native
## 0.2.36
### Patch Changes
- [#1362](https://github.com/thirdweb-dev/js/pull/1362) [`44dbf283`](https://github.com/thirdweb-dev/js/commit/44dbf283c985f3e2ecbeb1cfdf09fe1d84e26298) Thanks [@iketw](https://github.com/iketw)! - Correctly pass clientId in storage
- [#1316](https://github.com/thirdweb-dev/js/pull/1316) [`d8447146`](https://github.com/thirdweb-dev/js/commit/d8447146092c1962f410155ab2047225453aaa2b) Thanks [@iketw](https://github.com/iketw)! - Adds new `clientId` prop to access thirdweb's services.
You can create a _free_ `clientId` [on the thirdweb Dashboard](https://thirdweb.com/dashboard)
```javascript
<ThirdwebProvider clientId="your-client-id" />
```
- Updated dependencies [[`44dbf283`](https://github.com/thirdweb-dev/js/commit/44dbf283c985f3e2ecbeb1cfdf09fe1d84e26298), [`d8447146`](https://github.com/thirdweb-dev/js/commit/d8447146092c1962f410155ab2047225453aaa2b), [`d8447146`](https://github.com/thirdweb-dev/js/commit/d8447146092c1962f410155ab2047225453aaa2b), [`d8447146`](https://github.com/thirdweb-dev/js/commit/d8447146092c1962f410155ab2047225453aaa2b), [`d8447146`](https://github.com/thirdweb-dev/js/commit/d8447146092c1962f410155ab2047225453aaa2b), [`d8447146`](https://github.com/thirdweb-dev/js/commit/d8447146092c1962f410155ab2047225453aaa2b)]:
- @thirdweb-dev/storage@1.2.0
- @thirdweb-dev/sdk@3.10.37
- @thirdweb-dev/react-core@3.14.17
- @thirdweb-dev/chains@0.1.35
- @thirdweb-dev/wallets@1.1.0
## 0.2.35
### Patch Changes
- Updated dependencies [[`7a6534d1`](https://github.com/thirdweb-dev/js/commit/7a6534d1a98cab1eb47cf88d13e2b7ec04037e42), [`dac8fa7d`](https://github.com/thirdweb-dev/js/commit/dac8fa7d98b6952acf8d13e173099889c1d47da8), [`c5761b99`](https://github.com/thirdweb-dev/js/commit/c5761b99e9797772481506e90cbfa5c35a05bd1d), [`b96a2282`](https://github.com/thirdweb-dev/js/commit/b96a2282730582b684cc96802649a96851af5220)]:
- @thirdweb-dev/react-core@3.14.16
- @thirdweb-dev/sdk@3.10.36
- @thirdweb-dev/wallets@1.0.12
## 0.2.34
### Patch Changes
- Updated dependencies [[`8dd7540c`](https://github.com/thirdweb-dev/js/commit/8dd7540c455aa70534f6d29986537592fd12169b), [`8dd7540c`](https://github.com/thirdweb-dev/js/commit/8dd7540c455aa70534f6d29986537592fd12169b)]:
- @thirdweb-dev/storage@1.1.9
- @thirdweb-dev/chains@0.1.34
- @thirdweb-dev/react-core@3.14.15
- @thirdweb-dev/sdk@3.10.35
- @thirdweb-dev/wallets@1.0.11
## 0.2.33
### Patch Changes
+4 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/react-native",
"version": "0.2.33",
"version": "0.2.36",
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/react-native",
"author": "thirdweb eng <[email protected]>",
"license": "Apache-2.0",
@@ -35,6 +35,7 @@
"@thirdweb-dev/storage": "workspace:*",
"@thirdweb-dev/wallets": "workspace:*",
"abitype": "^0.2.5",
"expo-application": "^5.3.0",
"expo-clipboard": "^4.1.2",
"expo-document-picker": "^11.2.2",
"expo-file-system": "^15.2.2",
@@ -56,7 +57,7 @@
"@typescript-eslint/eslint-plugin": "^5.60.1",
"@typescript-eslint/parser": "^5.60.1",
"babel-jest": "^29.2.1",
"eslint": "^8.43.0",
"eslint": "^8.45.0",
"eslint-config-prettier": "^8.8.0",
"eslint-config-thirdweb": "workspace:*",
"eslint-plugin-import": "^2.26.0",
@@ -65,7 +66,7 @@
"ethers": "^5.7.2",
"expo": "^47.0.14",
"jest": "^29.4.3",
"prettier": "^2.8.8",
"prettier": "^3.0.0",
"react": "^18.2.0",
"react-native": "^0.71.11",
"react-native-camera": "^4.2.1",
@@ -0,0 +1,181 @@
import {
GatewayUrls,
IStorageUploader,
IpfsUploadBatchOptions,
ThirdwebStorageOptions,
UploadOptions,
parseGatewayUrls,
replaceObjectSchemesWithGatewayUrls,
replaceSchemeWithGatewayUrl,
IThirdwebStorage,
IStorageDownloader,
StorageDownloader,
} from "@thirdweb-dev/storage";
import { prepareGatewayUrls } from "./utils";
import { IpfsUploader } from "./uploader";
import { UploadDataValue } from "./types";
export class ThirdwebStorage<T extends UploadOptions = IpfsUploadBatchOptions>
implements IThirdwebStorage
{
private uploader: IStorageUploader<T>;
private downloader: IStorageDownloader;
private gatewayUrls: GatewayUrls;
constructor(options?: ThirdwebStorageOptions<T>) {
this.uploader =
options?.uploader ||
new IpfsUploader({
clientId: options?.clientId,
});
this.downloader =
options?.downloader ||
new StorageDownloader({
clientId: options?.clientId,
});
this.gatewayUrls = prepareGatewayUrls(
parseGatewayUrls(options?.gatewayUrls),
options?.clientId,
);
}
/**
* Resolve any scheme on a URL to get a retrievable URL for the data
*
* @param url - The URL to resolve the scheme of
* @returns The URL with its scheme resolved
*
* @example
* ```jsx
* const uri = "ipfs://example";
* const url = storage.resolveScheme(uri);
* console.log(url);
* ```
*/
resolveScheme(url: string): string {
return replaceSchemeWithGatewayUrl(url, this.gatewayUrls) as string;
}
/**
* Downloads arbitrary data from any URL scheme.
*
* @param url - The URL of the data to download
* @returns The response object fetched from the resolved URL
*
* @example
* ```jsx
* const uri = "ipfs://example";
* const data = await storage.download(uri);
* ```
*/
async download(url: string): Promise<Response> {
return this.downloader.download(url, this.gatewayUrls);
}
/**
* Downloads JSON data from any URL scheme.
* Resolves any URLs with schemes to retrievable gateway URLs.
*
* @param url - The URL of the JSON data to download
* @returns The JSON data fetched from the resolved URL
*
* @example
* ```jsx
* const uri = "ipfs://example";
* const json = await storage.downloadJSON(uri);
* ```
*/
async downloadJSON<TJSON = any>(url: string): Promise<TJSON> {
const res = await this.download(url);
// If we get a JSON object, recursively replace any schemes with gatewayUrls
const json = await res.json();
return replaceObjectSchemesWithGatewayUrls(json, this.gatewayUrls) as TJSON;
}
/**
* Upload arbitrary file or JSON data using the configured decentralized storage system.
* Automatically uploads any file data within JSON objects and replaces them with hashes.
*
* @param data - Arbitrary file or JSON data to upload
* @param options - Options to pass through to the storage uploader class
* @returns - The URI of the uploaded data
*
* @example
* ```jsx
* // Upload an image
* launchImageLibrary({mediaType: 'photo'}, async response => {
* if (response.assets?.[0]) {
* const {fileName, type, uri} = response.assets[0];
* if (!uri) {
* throw new Error('No uri');
* }
* const resp = await storage.upload({
* uri,
* type,
* name: fileName,
* });
* }
* });
* const jsonUri = await storage.upload(json);
* ```
*/
async upload(data: UploadDataValue, options?: T): Promise<string> {
const [uri] = await this.uploadBatch([data], options);
return uri;
}
/**
* Batch upload arbitrary file or JSON data using the configured decentralized storage system.
* Automatically uploads any file data within JSON objects and replaces them with hashes.
*
* @param data - Array of arbitrary file or JSON data to upload
* @param options - Options to pass through to the storage uploader class
* @returns - The URIs of the uploaded data
*
* @example
* ```jsx
* // Upload an image
* launchImageLibrary({mediaType: 'photo'}, async response => {
* if (response.assets?.[0]) {
* const {fileName, type, uri} = response.assets[0];
* if (!uri) {
* throw new Error('No uri');
* }
* const resp = await storage.upload({
* uri,
* type,
* name: fileName,
* });
* }
* });
*
* // Upload an array of JSON objects
* const objects = [
* { name: "JSON 1", text: "Hello World" },
* { name: "JSON 2", trait: "Awesome" },
* ];
* const jsonUris = await storage.uploadBatch(objects);
* ```
*/
async uploadBatch(data: UploadDataValue[], options?: T): Promise<string[]> {
data = data.filter((item) => item !== undefined);
if (!data.length) {
return [];
}
const uris: string[] = await this.uploader.uploadBatch(data, options);
if (options?.uploadWithGatewayUrl || this.uploader.uploadWithGatewayUrl) {
return uris.map((uri) => this.resolveScheme(uri));
} else {
return uris;
}
}
getGatewayUrls(): GatewayUrls {
return this.gatewayUrls;
}
}
@@ -0,0 +1,29 @@
/**
* @public
*/
export type IpfsDownloaderOptions = {
/**
* Optional clientId to associate with the IpfsDownloader - when used from the frontend.
* You can get a clientId here: https://thirdweb.com/create-api-key
*/
clientId?: string;
};
/**
* @public
*/
export type IpfsUploaderOptions = {
/**
* Whether or not to replace any URLs with schemes with resolved URLs before upload
*/
uploadWithGatewayUrl?: boolean;
/**
* Optional clientId to associate with the IpfsUploader.
* You can get an clientId here: https://thirdweb.com/create-api-key
*/
clientId?: string;
};
export type UploadFile = { name?: string; type?: string; uri: string };
export type UploadDataValue = UploadFile | any;
@@ -0,0 +1,256 @@
import {
IStorageUploader,
IpfsUploadBatchOptions,
isFileBufferOrStringEqual,
TW_UPLOAD_SERVER_URL,
} from "@thirdweb-dev/storage";
import { IpfsUploaderOptions, UploadDataValue } from "./types";
import * as Application from "expo-application";
const APP_BUNDLE_ID = Application.applicationId;
const METADATA_NAME = "Storage React Native SDK";
export class IpfsUploader implements IStorageUploader<IpfsUploadBatchOptions> {
public uploadWithGatewayUrl?: boolean | undefined;
private clientId?: string;
constructor(options?: IpfsUploaderOptions) {
this.uploadWithGatewayUrl = options?.uploadWithGatewayUrl || false;
this.clientId = options?.clientId;
}
async uploadBatch(
data: UploadDataValue[],
options?: IpfsUploadBatchOptions | undefined,
): Promise<string[]> {
if (data.length === 0) {
throw new Error("[UPLOAD_BATCH_ERROR] No files or objects to upload.");
}
// if (options?.uploadWithoutDirectory && data.length > 1) {
// throw new Error(
// "[UPLOAD_WITHOUT_DIRECTORY_ERROR] Cannot upload more than one file without directory!",
// );
// }
const metadata = {
name: METADATA_NAME,
keyvalues: { ...options?.metadata },
};
if ("uri" in data[0] && "type" in data[0] && "name" in data[0]) {
// then it's an array of files
return new Promise(async (resolve, reject) => {
const formData = new FormData();
const { form, fileNames } = this.buildFormData(
formData,
data as { name?: string; type?: string; uri: string }[],
options,
);
const xhr = new XMLHttpRequest();
let timer = setTimeout(() => {
xhr.abort();
reject(
new Error(
"Request to upload timed out! No upload progress received in 30s",
),
);
}, 30000);
xhr.upload.addEventListener("loadstart", () => {
console.log(`[${Date.now()}] [IPFS] Started`);
});
xhr.upload.addEventListener("progress", (event) => {
console.log(`[IPFS] Progress Event ${event.loaded}/${event.total}`);
clearTimeout(timer);
if (event.loaded < event.total) {
timer = setTimeout(() => {
xhr.abort();
reject(
new Error(
"Request to upload timed out! No upload progress received in 30s",
),
);
}, 30000);
} else {
console.log(
`[${Date.now()}] [IPFS] Uploaded files. Waiting for response.`,
);
}
});
xhr.addEventListener("load", () => {
console.log(`[${Date.now()}] [IPFS] Load`);
clearTimeout(timer);
if (xhr.status >= 200 && xhr.status < 300) {
let body;
try {
body = JSON.parse(xhr.responseText);
} catch (err) {
return reject(
new Error("Failed to parse JSON from upload response"),
);
}
const cid = body.IpfsHash;
if (!cid) {
throw new Error("Failed to get IPFS hash from upload response");
}
if (options?.uploadWithoutDirectory) {
return resolve([`ipfs://${cid}`]);
} else {
return resolve(fileNames.map((name) => `ipfs://${cid}/${name}`));
}
}
return reject(
new Error(
`Upload failed with status ${xhr.status} - ${xhr.responseText}`,
),
);
});
xhr.addEventListener("error", () => {
console.log("[IPFS] Load");
clearTimeout(timer);
if (
(xhr.readyState !== 0 && xhr.readyState !== 4) ||
xhr.status === 0
) {
return reject(
new Error(
`This looks like a network error, the endpoint might be blocked by an internet provider or a firewall. ${xhr.responseText}`,
),
);
}
return reject(new Error("Unknown upload error occured"));
});
xhr.open("POST", `${TW_UPLOAD_SERVER_URL}/ipfs/upload`);
xhr.setRequestHeader("x-bundle-id", APP_BUNDLE_ID || ""); // only empty on web
if (this.clientId) {
xhr.setRequestHeader("x-client-id", this.clientId);
}
xhr.send(form);
});
} else {
// assume an array of things
return new Promise(async (resolve, reject) => {
const fetchBody = JSON.stringify({
metadata: metadata,
content: data,
});
try {
const res = await fetch(`${TW_UPLOAD_SERVER_URL}/ipfs/pin-json`, {
method: "POST",
headers: {
"x-bundle-id": APP_BUNDLE_ID || "", // only empty on web
...(this.clientId ? { "x-client-id": this.clientId } : {}),
"Content-Type": "application/json",
},
body: fetchBody,
});
if (res.ok) {
const ipfs = await res.json();
const cid = ipfs.IpfsHash;
return resolve([`ipfs://${cid}`]);
}
} catch (error) {
reject(error);
}
});
}
}
private buildFormData(
form: FormData,
files: { name?: string; type?: string; uri: string }[],
options?: IpfsUploadBatchOptions,
) {
const fileNameToFileMap = new Map<
string,
{ name?: string; type?: string; uri: string }
>();
const fileNames: string[] = [];
for (let i = 0; i < files.length; i++) {
const file = files[i];
let fileName = "";
if (options?.rewriteFileNames) {
let extensions = "";
if (file.name) {
const extensionStartIndex = file.name.lastIndexOf(".");
if (extensionStartIndex > -1) {
extensions = file.name.substring(extensionStartIndex);
}
}
fileName = `${
i + options.rewriteFileNames.fileStartNumber
}${extensions}`;
} else {
fileName = `${file.name}`;
}
// TODO: If we don't want to wrap with directory, adjust the filepath
// const filepath = options?.uploadWithoutDirectory
// ? 'files'
// : `files/${fileName}`;
if (fileNameToFileMap.has(fileName)) {
// if the file in the map is the same as the file we are already looking at then just skip and continue
if (isFileBufferOrStringEqual(fileNameToFileMap.get(fileName), file)) {
// we add it to the filenames array so that we can return the correct number of urls,
fileNames.push(fileName);
// but then we skip because we don't need to upload it multiple times
continue;
}
// otherwise if file names are the same but they are not the same file then we should throw an error (trying to upload to differnt files but with the same names)
throw new Error(
`[DUPLICATE_FILE_NAME_ERROR] File name ${fileName} was passed for more than one different file.`,
);
}
// add it to the map so that we can check for duplicates
fileNameToFileMap.set(fileName, file);
// add it to the filenames array so that we can return the correct number of urls
fileNames.push(fileName);
form.append("file", file);
}
const metadata = {
name: METADATA_NAME,
keyvalues: { ...options?.metadata },
};
form.append("pinataMetadata", JSON.stringify(metadata));
if (options?.uploadWithoutDirectory) {
form.append(
"pinataOptions",
JSON.stringify({
wrapWithDirectory: false,
}),
);
}
return {
form,
// encode the file names on the way out (which is what the upload backend expects)
fileNames: fileNames.map((fName) => encodeURIComponent(fName)),
};
}
}
@@ -0,0 +1,34 @@
import { DEFAULT_GATEWAY_URLS, GatewayUrls } from "@thirdweb-dev/storage";
/**
* @internal
*/
export function prepareGatewayUrls(
gatewayUrls: GatewayUrls,
clientId?: string,
): GatewayUrls {
const allGatewayUrls = {
...DEFAULT_GATEWAY_URLS,
...gatewayUrls,
};
for (const key of Object.keys(allGatewayUrls)) {
const cleanedGatewayUrls = allGatewayUrls[key]
.map((url) => {
// inject clientId when present
if (clientId && url.includes("{clientId}")) {
return url.replace("{clientId}", clientId);
} else if (url.includes("{clientId}")) {
// if no client id passed, filter out the url
return undefined;
} else {
return url;
}
})
.filter((url) => url !== undefined) as string[];
allGatewayUrls[key] = cleanedGatewayUrls;
}
return allGatewayUrls;
}
@@ -165,7 +165,7 @@ export const ConnectWalletDetailsModal = ({
/>
{activeWallet?.walletId === LocalWallet.id ? (
<Text variant="error">
<Text variant="error" textAlign="left">
{
"This is a temporary guest wallet. Download a backup if you don't want to lose access to it."
}

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