Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
882fd3dcbd | ||
|
|
744491f853 | ||
|
|
d3f788471b | ||
|
|
6db81ef68d | ||
|
|
f8b2bdc64b | ||
|
|
c407bd10c7 |
@@ -22,7 +22,7 @@ Send native and ERC-20 currencies
|
||||
|
||||
### Deploy
|
||||
Deploy published contracts
|
||||
- Deploy a Token ERC20 Contract
|
||||
- Deploy a Token ERC20 Contract with name "Hello World" and description "My Hello Contract" on Ethereum.
|
||||
- Deploy a Split contract with two recipients.
|
||||
- Deploy an ERC1155 Contract named 'Hello World' with description 'Hello badges on Ethereum'
|
||||
|
||||
@@ -30,7 +30,7 @@ Deploy published contracts
|
||||
Retrieve information about smart contracts.
|
||||
- What ERC standards are implemented by contract address 0x59325733eb952a92e069C87F0A6168b29E80627f on Ethereum?
|
||||
- What functions can I use to mint more of my contract's NFTs?
|
||||
- What is the total supply of NFTs for my contract?
|
||||
- What is the total supply of NFTs on 0x8a90CAb2b38dba80c64b7734e58Ee1dB38B8992e?
|
||||
|
||||
### Interact
|
||||
Query wallet balances, addresses, and token holdings.
|
||||
|
||||
+1
-1
@@ -79,7 +79,7 @@ export const CredentialForm = ({
|
||||
<DialogTitle>{title}</DialogTitle>
|
||||
<DialogDescription>
|
||||
<TrackedLinkTW
|
||||
href="https://portal.thirdweb.com/infrastructure/engine/features/wallet-credentials"
|
||||
href="https://portal.thirdweb.com/engine/features/wallet-credentials"
|
||||
target="_blank"
|
||||
label="learn-more"
|
||||
category="engine"
|
||||
|
||||
+1
-1
@@ -65,7 +65,7 @@ export const WalletCredentialsSection: React.FC<WalletCredentialsProps> = ({
|
||||
<p className="text-muted-foreground text-sm leading-relaxed">
|
||||
Manage your wallet credentials for different providers.{" "}
|
||||
<Link
|
||||
href="https://portal.thirdweb.com/infrastructure/engine/features/wallet-credentials"
|
||||
href="https://portal.thirdweb.com/engine/features/wallet-credentials"
|
||||
target="_blank"
|
||||
>
|
||||
Learn more about wallet credentials.
|
||||
|
||||
@@ -20,8 +20,8 @@ Maximize the potential of Nebula. Whether you’re new to blockchain development
|
||||
|-----------------------------|----------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **Bridge & Swap** | Bridge and swap native currencies | • "Swap 1 USDC to 1 USDT on the Ethereum Mainnet"<br/>• "Bridge 0.5 ETH from Ethereum Mainnet to Polygon"<br/> |
|
||||
| **Transfer** | Send native and ERC-20 currencies | • "Send 0.1 ETH to vitalik.eth"<br/>• "Transfer 1 USDC to saminacodes.eth on Base"<br/> |
|
||||
| **Deploy** | Deploy published contracts | • "Deploy a Token ERC20 Contract"<br/>• "Deploy a Split contract with two recipients."<br/>• "Deploy an ERC1155 Contract named 'Hello World' with description 'Hello badges on Ethereum'"<br/>|
|
||||
| **Understand** | Retrieve information about smart contracts. | • "What ERC standards are implemented by contract address 0x59325733eb952a92e069C87F0A6168b29E80627f on Ethereum?"<br/>• "What functions can I use to mint more of my contract's NFTs?"<br/>• "What is the total supply of NFTs for my contract?" |
|
||||
| **Deploy** | Deploy published contracts | • "Deploy a Token ERC20 Contract with name "Hello World" and description "My Hello Contract" on Ethereum."<br/>• "Deploy a Split contract with two recipients."<br/>• "Deploy an ERC1155 Contract named 'Hello World' with description 'Hello badges on Ethereum'"<br/>|
|
||||
| **Understand** | Retrieve information about smart contracts. | • "What ERC standards are implemented by contract address 0x59325733eb952a92e069C87F0A6168b29E80627f on Ethereum?"<br/>• "What functions can I use to mint more of my contract's NFTs?"<br/>• "What is the total supply of NFTs on 0x8a90CAb2b38dba80c64b7734e58Ee1dB38B8992e?" |
|
||||
| **Interact** | Query wallet balances, addresses, and token holdings. | • "How much ETH is in my wallet?"<br/>• "What is the wallet address of vitalik.eth?"<br/>• "Does my wallet hold USDC on Base?" |
|
||||
| **Explore** | Access blockchain-specific data. | • "What is the last block on zkSync?"<br/>• "What is the current gas price on Avalanche C-Chain?"<br/>• "Can you show me transaction details for 0xdfc450bb39e44bd37c22e0bfd0e5212edbea571e4e534d87b5cbbf06f10b9e04 on Optimism?" |
|
||||
| **Research** | Obtain details about tokens, their addresses, and current prices. | • "What is the address of USDC on Ethereum?"<br/>• "Is there a UNI token on Arbitrum?"<br/>• "What is the current price of ARB?" |
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# thirdweb
|
||||
|
||||
## 5.93.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#6518](https://github.com/thirdweb-dev/js/pull/6518) [`744491f`](https://github.com/thirdweb-dev/js/commit/744491f85307444f227c14381629cdf27987ee62) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Handle wallet chain mismatch on post onramp flow
|
||||
|
||||
## 5.93.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#6515](https://github.com/thirdweb-dev/js/pull/6515) [`c407bd1`](https://github.com/thirdweb-dev/js/commit/c407bd10c7b16f56c2a08cfdb2ed7e56a1c75f52) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Always fetch fresh quotes before post onramp step in PayEmbed
|
||||
|
||||
## 5.93.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "thirdweb",
|
||||
"version": "5.93.1",
|
||||
"version": "5.93.3",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/thirdweb-dev/js.git#main"
|
||||
|
||||
+50
-36
@@ -6,7 +6,7 @@ import type { ThirdwebClient } from "../../../../../../../client/client.js";
|
||||
import { getContract } from "../../../../../../../contract/contract.js";
|
||||
import { allowance } from "../../../../../../../extensions/erc20/__generated__/IERC20/read/allowance.js";
|
||||
import { approve } from "../../../../../../../extensions/erc20/write/approve.js";
|
||||
import type { BuyWithCryptoQuote } from "../../../../../../../pay/buyWithCrypto/getQuote.js";
|
||||
import { getBuyWithCryptoQuote } from "../../../../../../../pay/buyWithCrypto/getQuote.js";
|
||||
import type { BuyWithCryptoStatus } from "../../../../../../../pay/buyWithCrypto/getStatus.js";
|
||||
import type { BuyWithFiatQuote } from "../../../../../../../pay/buyWithFiat/getQuote.js";
|
||||
import {
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
type OnRampStep,
|
||||
getOnRampSteps,
|
||||
} from "../../../../../../../pay/buyWithFiat/isSwapRequiredPostOnramp.js";
|
||||
import type { PayTokenInfo } from "../../../../../../../pay/utils/commonTypes.js";
|
||||
import { sendBatchTransaction } from "../../../../../../../transaction/actions/send-batch-transaction.js";
|
||||
import { sendTransaction } from "../../../../../../../transaction/actions/send-transaction.js";
|
||||
import type { WaitForReceiptOptions } from "../../../../../../../transaction/actions/wait-for-tx-receipt.js";
|
||||
@@ -28,7 +29,6 @@ import type { Wallet } from "../../../../../../../wallets/interfaces/wallet.js";
|
||||
import { isSmartWallet } from "../../../../../../../wallets/smart/is-smart-wallet.js";
|
||||
import { spacing } from "../../../../../../core/design-system/index.js";
|
||||
import { useChainName } from "../../../../../../core/hooks/others/useChainQuery.js";
|
||||
import { useBuyWithCryptoQuote } from "../../../../../../core/hooks/pay/useBuyWithCryptoQuote.js";
|
||||
import { useBuyWithCryptoStatus } from "../../../../../../core/hooks/pay/useBuyWithCryptoStatus.js";
|
||||
import { useBuyWithFiatStatus } from "../../../../../../core/hooks/pay/useBuyWithFiatStatus.js";
|
||||
import { useConnectedWallets } from "../../../../../../core/hooks/wallets/useConnectedWallets.js";
|
||||
@@ -319,20 +319,6 @@ function useOnRampScreenState(props: {
|
||||
// Get quote for current swap/bridge step if needed
|
||||
const previousStep = onRampSteps[currentStepIndex - 1];
|
||||
const currentStep = onRampSteps[currentStepIndex];
|
||||
const swapQuoteQuery = useBuyWithCryptoQuote(
|
||||
previousStep && currentStep
|
||||
? {
|
||||
fromChainId: previousStep.token.chainId,
|
||||
fromTokenAddress: previousStep.token.tokenAddress,
|
||||
toAmount: currentStep.amount,
|
||||
toChainId: currentStep.token.chainId,
|
||||
toTokenAddress: currentStep.token.tokenAddress,
|
||||
fromAddress: props.payer.account.address,
|
||||
toAddress: props.payer.account.address,
|
||||
client: props.client,
|
||||
}
|
||||
: undefined,
|
||||
);
|
||||
|
||||
// Handle swap execution
|
||||
const swapMutation = useSwapMutation({
|
||||
@@ -375,9 +361,9 @@ function useOnRampScreenState(props: {
|
||||
status = "completed";
|
||||
} else if (index === currentStepIndex) {
|
||||
// Current step - could be swap or bridge
|
||||
if (swapQuoteQuery.isLoading || swapMutation.isPending) {
|
||||
if (swapMutation.isPending) {
|
||||
status = "pending";
|
||||
} else if (swapQuoteQuery.error || swapMutation.error) {
|
||||
} else if (swapMutation.error) {
|
||||
status = "failed";
|
||||
} else if (swapTxHash) {
|
||||
status = swapStatus;
|
||||
@@ -418,11 +404,13 @@ function useOnRampScreenState(props: {
|
||||
type: "fiat",
|
||||
intentId: props.quote.intentId,
|
||||
});
|
||||
} else if (swapQuoteQuery.data && !swapTxHash) {
|
||||
} else if (previousStep && currentStep && !swapTxHash) {
|
||||
// Execute swap/bridge
|
||||
try {
|
||||
const result = await swapMutation.mutateAsync({
|
||||
quote: swapQuoteQuery.data,
|
||||
fromToken: previousStep.token,
|
||||
toToken: currentStep.token,
|
||||
amount: currentStep.amount,
|
||||
});
|
||||
setSwapTxHash({
|
||||
hash: result.transactionHash,
|
||||
@@ -435,23 +423,22 @@ function useOnRampScreenState(props: {
|
||||
// retry the quote step
|
||||
setSwapTxHash(undefined);
|
||||
swapMutation.reset();
|
||||
await swapQuoteQuery.refetch();
|
||||
}
|
||||
}, [
|
||||
isDone,
|
||||
currentStepIndex,
|
||||
swapQuoteQuery.data,
|
||||
swapTxHash,
|
||||
props.quote,
|
||||
props.onDone,
|
||||
swapMutation,
|
||||
props.theme,
|
||||
isFailed,
|
||||
swapQuoteQuery.refetch,
|
||||
swapMutation.reset,
|
||||
props.client,
|
||||
props.payer.account.address,
|
||||
props.payer.wallet.id,
|
||||
currentStep,
|
||||
previousStep,
|
||||
]);
|
||||
|
||||
// Auto-progress effect
|
||||
@@ -467,7 +454,6 @@ function useOnRampScreenState(props: {
|
||||
!isFailed &&
|
||||
currentStepIndex > 0 &&
|
||||
currentStepIndex < onRampSteps.length &&
|
||||
swapQuoteQuery.data &&
|
||||
!swapTxHash
|
||||
) {
|
||||
handleContinue();
|
||||
@@ -475,7 +461,6 @@ function useOnRampScreenState(props: {
|
||||
}, [
|
||||
props.isAutoMode,
|
||||
currentStepIndex,
|
||||
swapQuoteQuery.data,
|
||||
swapTxHash,
|
||||
onRampSteps.length,
|
||||
handleContinue,
|
||||
@@ -643,9 +628,38 @@ function useSwapMutation(props: {
|
||||
}) {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: async (input: { quote: BuyWithCryptoQuote }) => {
|
||||
const { quote } = input;
|
||||
const canBatch = props.payer.account.sendBatchTransaction;
|
||||
mutationFn: async (input: {
|
||||
fromToken: PayTokenInfo;
|
||||
toToken: PayTokenInfo;
|
||||
amount: string;
|
||||
}) => {
|
||||
const { fromToken, toToken, amount } = input;
|
||||
const wallet = props.payer.wallet;
|
||||
|
||||
// in case the wallet is not on the same chain as the fromToken, switch to it
|
||||
if (wallet.getChain()?.id !== fromToken.chainId) {
|
||||
await wallet.switchChain(getCachedChain(fromToken.chainId));
|
||||
}
|
||||
|
||||
const account = wallet.getAccount();
|
||||
|
||||
if (!account) {
|
||||
throw new Error("Payer wallet has no account");
|
||||
}
|
||||
|
||||
// always get a fresh quote before executing
|
||||
const quote = await getBuyWithCryptoQuote({
|
||||
fromChainId: fromToken.chainId,
|
||||
fromTokenAddress: fromToken.tokenAddress,
|
||||
toAmount: amount,
|
||||
toChainId: toToken.chainId,
|
||||
toTokenAddress: toToken.tokenAddress,
|
||||
fromAddress: account.address,
|
||||
toAddress: account.address,
|
||||
client: props.client,
|
||||
});
|
||||
|
||||
const canBatch = account.sendBatchTransaction;
|
||||
const tokenContract = getContract({
|
||||
client: props.client,
|
||||
address: quote.swapDetails.fromToken.tokenAddress,
|
||||
@@ -655,14 +669,14 @@ function useSwapMutation(props: {
|
||||
quote.approvalData &&
|
||||
(await allowance({
|
||||
contract: tokenContract,
|
||||
owner: props.payer.account.address,
|
||||
owner: account.address,
|
||||
spender: quote.approvalData.spenderAddress,
|
||||
})) < BigInt(quote.approvalData.amountWei);
|
||||
if (approveTxRequired && quote.approvalData && !canBatch) {
|
||||
trackPayEvent({
|
||||
event: "prompt_swap_approval",
|
||||
client: props.client,
|
||||
walletAddress: props.payer.account.address,
|
||||
walletAddress: account.address,
|
||||
walletType: props.payer.wallet.id,
|
||||
fromToken: quote.swapDetails.fromToken.tokenAddress,
|
||||
fromAmount: quote.swapDetails.fromAmountWei,
|
||||
@@ -679,7 +693,7 @@ function useSwapMutation(props: {
|
||||
});
|
||||
|
||||
const tx = await sendTransaction({
|
||||
account: props.payer.account,
|
||||
account,
|
||||
transaction,
|
||||
});
|
||||
|
||||
@@ -688,7 +702,7 @@ function useSwapMutation(props: {
|
||||
trackPayEvent({
|
||||
event: "swap_approval_success",
|
||||
client: props.client,
|
||||
walletAddress: props.payer.account.address,
|
||||
walletAddress: account.address,
|
||||
walletType: props.payer.wallet.id,
|
||||
fromToken: quote.swapDetails.fromToken.tokenAddress,
|
||||
fromAmount: quote.swapDetails.fromAmountWei,
|
||||
@@ -702,7 +716,7 @@ function useSwapMutation(props: {
|
||||
trackPayEvent({
|
||||
event: "prompt_swap_execution",
|
||||
client: props.client,
|
||||
walletAddress: props.payer.account.address,
|
||||
walletAddress: account.address,
|
||||
walletType: props.payer.wallet.id,
|
||||
fromToken: quote.swapDetails.fromToken.tokenAddress,
|
||||
fromAmount: quote.swapDetails.fromAmountWei,
|
||||
@@ -722,12 +736,12 @@ function useSwapMutation(props: {
|
||||
});
|
||||
|
||||
_swapTx = await sendBatchTransaction({
|
||||
account: props.payer.account,
|
||||
account,
|
||||
transactions: [approveTx, tx],
|
||||
});
|
||||
} else {
|
||||
_swapTx = await sendTransaction({
|
||||
account: props.payer.account,
|
||||
account,
|
||||
transaction: tx,
|
||||
});
|
||||
}
|
||||
@@ -737,7 +751,7 @@ function useSwapMutation(props: {
|
||||
trackPayEvent({
|
||||
event: "swap_execution_success",
|
||||
client: props.client,
|
||||
walletAddress: props.payer.account.address,
|
||||
walletAddress: account.address,
|
||||
walletType: props.payer.wallet.id,
|
||||
fromToken: quote.swapDetails.fromToken.tokenAddress,
|
||||
fromAmount: quote.swapDetails.fromAmountWei,
|
||||
|
||||
+25
-12
@@ -160,6 +160,19 @@ export function SwapConfirmationScreen(props: {
|
||||
fullWidth
|
||||
disabled={status === "pending"}
|
||||
onClick={async () => {
|
||||
const wallet = props.payer.wallet;
|
||||
|
||||
// in case the wallet is not on the same chain as the fromToken, switch to it
|
||||
if (wallet.getChain()?.id !== props.fromChain.id) {
|
||||
await wallet.switchChain(props.fromChain);
|
||||
}
|
||||
|
||||
const account = wallet.getAccount();
|
||||
|
||||
if (!account) {
|
||||
throw new Error("Payer wallet has no account");
|
||||
}
|
||||
|
||||
if (step === "approval" && props.quote.approvalData) {
|
||||
try {
|
||||
setStatus("pending");
|
||||
@@ -167,8 +180,8 @@ export function SwapConfirmationScreen(props: {
|
||||
trackPayEvent({
|
||||
event: "prompt_swap_approval",
|
||||
client: props.client,
|
||||
walletAddress: props.payer.account.address,
|
||||
walletType: props.payer.wallet.id,
|
||||
walletAddress: account.address,
|
||||
walletType: wallet.id,
|
||||
fromToken: props.quote.swapDetails.fromToken.tokenAddress,
|
||||
fromAmount: props.quote.swapDetails.fromAmountWei,
|
||||
toToken: props.quote.swapDetails.toToken.tokenAddress,
|
||||
@@ -188,7 +201,7 @@ export function SwapConfirmationScreen(props: {
|
||||
});
|
||||
|
||||
const tx = await sendTransaction({
|
||||
account: props.payer.account,
|
||||
account: account,
|
||||
transaction,
|
||||
});
|
||||
|
||||
@@ -197,8 +210,8 @@ export function SwapConfirmationScreen(props: {
|
||||
trackPayEvent({
|
||||
event: "swap_approval_success",
|
||||
client: props.client,
|
||||
walletAddress: props.payer.account.address,
|
||||
walletType: props.payer.wallet.id,
|
||||
walletAddress: account.address,
|
||||
walletType: wallet.id,
|
||||
fromToken: props.quote.swapDetails.fromToken.tokenAddress,
|
||||
fromAmount: props.quote.swapDetails.fromAmountWei,
|
||||
toToken: props.quote.swapDetails.toToken.tokenAddress,
|
||||
@@ -221,8 +234,8 @@ export function SwapConfirmationScreen(props: {
|
||||
trackPayEvent({
|
||||
event: "prompt_swap_execution",
|
||||
client: props.client,
|
||||
walletAddress: props.payer.account.address,
|
||||
walletType: props.payer.wallet.id,
|
||||
walletAddress: account.address,
|
||||
walletType: wallet.id,
|
||||
fromToken: props.quote.swapDetails.fromToken.tokenAddress,
|
||||
fromAmount: props.quote.swapDetails.fromAmountWei,
|
||||
toToken: props.quote.swapDetails.toToken.tokenAddress,
|
||||
@@ -233,7 +246,7 @@ export function SwapConfirmationScreen(props: {
|
||||
const tx = props.quote.transactionRequest;
|
||||
let _swapTx: WaitForReceiptOptions;
|
||||
// check if we can batch approval and swap
|
||||
const canBatch = props.payer.account.sendBatchTransaction;
|
||||
const canBatch = account.sendBatchTransaction;
|
||||
if (
|
||||
canBatch &&
|
||||
props.quote.approvalData &&
|
||||
@@ -250,12 +263,12 @@ export function SwapConfirmationScreen(props: {
|
||||
});
|
||||
|
||||
_swapTx = await sendBatchTransaction({
|
||||
account: props.payer.account,
|
||||
account: account,
|
||||
transactions: [approveTx, tx],
|
||||
});
|
||||
} else {
|
||||
_swapTx = await sendTransaction({
|
||||
account: props.payer.account,
|
||||
account: account,
|
||||
transaction: tx,
|
||||
});
|
||||
}
|
||||
@@ -263,8 +276,8 @@ export function SwapConfirmationScreen(props: {
|
||||
trackPayEvent({
|
||||
event: "swap_execution_success",
|
||||
client: props.client,
|
||||
walletAddress: props.payer.account.address,
|
||||
walletType: props.payer.wallet.id,
|
||||
walletAddress: account.address,
|
||||
walletType: wallet.id,
|
||||
fromToken: props.quote.swapDetails.fromToken.tokenAddress,
|
||||
fromAmount: props.quote.swapDetails.fromAmountWei,
|
||||
toToken: props.quote.swapDetails.toToken.tokenAddress,
|
||||
|
||||
@@ -264,11 +264,12 @@ async function createSmartAccount(
|
||||
executeOverride: options.overrides?.execute,
|
||||
});
|
||||
|
||||
const chain = getCachedChain(transaction.chainId);
|
||||
const result = await _sendUserOp({
|
||||
executeTx,
|
||||
options: {
|
||||
...options,
|
||||
chain: getCachedChain(transaction.chainId),
|
||||
chain,
|
||||
accountContract,
|
||||
overrides: {
|
||||
...options.overrides,
|
||||
@@ -278,7 +279,7 @@ async function createSmartAccount(
|
||||
});
|
||||
trackTransaction({
|
||||
client: options.client,
|
||||
chainId: options.chain.id,
|
||||
chainId: chain.id,
|
||||
transactionHash: result.transactionHash,
|
||||
walletAddress: options.accountContract.address,
|
||||
walletType: "smart",
|
||||
@@ -292,17 +293,25 @@ async function createSmartAccount(
|
||||
transactions,
|
||||
executeBatchOverride: options.overrides?.executeBatch,
|
||||
});
|
||||
if (transactions.length === 0) {
|
||||
throw new Error("No transactions to send");
|
||||
}
|
||||
const firstTx = transactions[0];
|
||||
if (!firstTx) {
|
||||
throw new Error("No transactions to send");
|
||||
}
|
||||
const chain = getCachedChain(firstTx.chainId);
|
||||
const result = await _sendUserOp({
|
||||
executeTx,
|
||||
options: {
|
||||
...options,
|
||||
chain: getCachedChain(transactions[0]?.chainId ?? options.chain.id),
|
||||
chain,
|
||||
accountContract,
|
||||
},
|
||||
});
|
||||
trackTransaction({
|
||||
client: options.client,
|
||||
chainId: options.chain.id,
|
||||
chainId: chain.id,
|
||||
transactionHash: result.transactionHash,
|
||||
walletAddress: options.accountContract.address,
|
||||
walletType: "smart",
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# @thirdweb-dev/wagmi-adapter
|
||||
|
||||
## 0.2.38
|
||||
|
||||
## 0.2.37
|
||||
|
||||
## 0.2.36
|
||||
|
||||
## 0.2.35
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/wagmi-adapter",
|
||||
"version": "0.2.36",
|
||||
"version": "0.2.38",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/thirdweb-dev/js.git#main"
|
||||
|
||||
Reference in New Issue
Block a user