Compare commits
24
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4cddbe775f | ||
|
|
a3461114c5 | ||
|
|
51e7adaeb0 | ||
|
|
3f669453d0 | ||
|
|
fe0c4ed00c | ||
|
|
fb8a11654e | ||
|
|
c1e155d04f | ||
|
|
91e658035d | ||
|
|
8491675e2e | ||
|
|
1b2edb4c09 | ||
|
|
874747b3e0 | ||
|
|
01f883f637 | ||
|
|
123275bbe2 | ||
|
|
7bb241f7db | ||
|
|
a1f53a2b9e | ||
|
|
46e9072576 | ||
|
|
501ef9339d | ||
|
|
3dca028a7e | ||
|
|
fd777c71c2 | ||
|
|
fbec24e260 | ||
|
|
bbf65acc80 | ||
|
|
b569eb48ad | ||
|
|
b6b0eb9120 | ||
|
|
7cfe6745e8 |
@@ -73,6 +73,10 @@ pnpm test:dev <YOUR TEST FILE PATH>
|
||||
|
||||
> Specifying your test file path is optional, but will save time by only running specific tests each time. Before opening your PR, run `pnpm test` from the monorepo root (without specifying your test file) to ensure your changes didn't break any existing tests.
|
||||
|
||||
Many of the tests use forked versions of live chains like mainnet. We fork these chains at a specific arbitrary block number so results are consistent across test runs. If you're interacting with on-chain data, you can write tests that interact with real contracts at the same block number by using `FORKED_ETHEREUM_CHAIN`. However, if you don't need to interact with existing contracts, we recommend running your tests against `ANVIL_CHAIN`.
|
||||
|
||||
If you need to use accounts in your tests, use the predefined accounts in `test/src/test-wallets.ts`. These are the default anvil accounts that are pre-funded on the local test forks.
|
||||
|
||||
#### Linting
|
||||
|
||||
We use a linter to maintain best practices across projects. Once your changes are complete (or periodically while making changes), run the linter with the following command from the repo root:
|
||||
|
||||
@@ -90,6 +90,10 @@ jobs:
|
||||
timeout-minutes: 15
|
||||
name: E2E Tests
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
package_manager: [npm, yarn, pnpm, bun]
|
||||
bundler: [vite, webpack, esbuild]
|
||||
steps:
|
||||
- name: Check out the code
|
||||
uses: actions/checkout@v4
|
||||
@@ -97,7 +101,58 @@ jobs:
|
||||
- name: Setup & Install
|
||||
uses: ./.github/composite-actions/install
|
||||
|
||||
# TODO actually run e2e tests
|
||||
- name: Build Packages
|
||||
run: pnpm build:packages
|
||||
|
||||
- name: Install Yarn (if needed)
|
||||
if: matrix.package_manager == 'yarn'
|
||||
run: npm install -g yarn
|
||||
|
||||
- name: Create test project
|
||||
run: |
|
||||
mkdir test-project
|
||||
cd test-project
|
||||
npm init -y
|
||||
${{ matrix.package_manager }} add react react-dom ../packages/thirdweb
|
||||
- name: Create test file
|
||||
run: |
|
||||
cd test-project
|
||||
echo "import { createThirdwebClient } from 'thirdweb'; console.log(createThirdwebClient({clientId: "foo_bar_baz"}));" > index.js
|
||||
|
||||
- name: Bundle with vite
|
||||
if: matrix.bundler == 'vite'
|
||||
run: |
|
||||
cd test-project
|
||||
${{matrix.package_manager}} add vite
|
||||
echo 'import { defineConfig } from "vite"; import {resolve} from "path"; export default defineConfig({ build: { lib: { entry: resolve(__dirname, "index.js"), name: "e2e_test" }, outDir: "dist" }});' > vite.config.js
|
||||
npx vite build
|
||||
|
||||
- name: Bundle with webpack
|
||||
if: matrix.bundler == 'webpack'
|
||||
run: |
|
||||
cd test-project
|
||||
${{matrix.package_manager}} add webpack webpack-cli
|
||||
echo 'const path = require("path"); module.exports = { mode: "production", entry: "./index.js", output: { path: path.resolve(__dirname, "dist"), filename: "bundle.js" }};' > webpack.config.js
|
||||
npx webpack
|
||||
|
||||
- name: Bundle with esbuild
|
||||
if: matrix.bundler == 'esbuild'
|
||||
run: |
|
||||
cd test-project
|
||||
${{matrix.package_manager}} add esbuild
|
||||
npx esbuild index.js --bundle --outdir=dist
|
||||
|
||||
- name: Verify bundle
|
||||
run: |
|
||||
cd test-project/dist
|
||||
file_count=$(find . -type f | wc -l)
|
||||
if [ "$file_count" -gt 0 ]; then
|
||||
echo "Bundling successful"
|
||||
else
|
||||
echo "Bundling failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
benchmark:
|
||||
# cannot run benchmarks on merge group
|
||||
|
||||
@@ -3,6 +3,8 @@ import { Button } from "@/components/ui/button";
|
||||
import { metadataBase } from "@/lib/constants";
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { CodeExample } from "../../../components/code/code-example";
|
||||
import { StyledPayTransaction } from "../../../components/styled-pay-transaction";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
metadataBase,
|
||||
@@ -55,6 +57,46 @@ export default function Page() {
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section className="container px-4 md:px-6 space-y-8">
|
||||
<div className="space-y-2">
|
||||
<h2 className="text-4xl font-semibold tracking-tight">
|
||||
Transaction Button
|
||||
</h2>
|
||||
<p className="max-w-[600px]">
|
||||
Transaction Button is a handy component that handles transactions.
|
||||
<br />
|
||||
If your user doesn't have enough funds for that transaction, a
|
||||
pre-filled pay modal will appear with the exact amount needed.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<CodeExample
|
||||
preview={<StyledPayTransaction />}
|
||||
code={`import { TransactionButton } from "thirdweb/react";
|
||||
|
||||
|
||||
function App() {
|
||||
const account = useActiveAccount();
|
||||
|
||||
return (
|
||||
<TransactionButton
|
||||
transaction={() => {
|
||||
// any transaction works
|
||||
return claimTo({
|
||||
contract,
|
||||
quantity: 1n,
|
||||
tokenId: 0n,
|
||||
to: account?.address,
|
||||
});
|
||||
}}
|
||||
>
|
||||
Buy for 10 MATIC
|
||||
</TransactionButton>
|
||||
);
|
||||
};`}
|
||||
lang="tsx"
|
||||
/>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
"use client";
|
||||
|
||||
import { THIRDWEB_CLIENT } from "@/lib/client";
|
||||
import { useTheme } from "next-themes";
|
||||
import { getContract } from "thirdweb";
|
||||
import { polygon } from "thirdweb/chains";
|
||||
import { claimTo } from "thirdweb/extensions/erc1155";
|
||||
import { TransactionButton, useActiveAccount } from "thirdweb/react";
|
||||
import { StyledConnectButton } from "./styled-connect-button";
|
||||
|
||||
const contract = getContract({
|
||||
address: "0x96B30d36f783c7BC68535De23147e2ce65788e93",
|
||||
chain: polygon,
|
||||
client: THIRDWEB_CLIENT,
|
||||
});
|
||||
|
||||
export function StyledPayTransaction() {
|
||||
const account = useActiveAccount();
|
||||
const { theme } = useTheme();
|
||||
|
||||
return account ? (
|
||||
<TransactionButton
|
||||
transaction={() => {
|
||||
if (!account) throw new Error("No active account");
|
||||
return claimTo({
|
||||
contract,
|
||||
quantity: 1n,
|
||||
tokenId: 0n,
|
||||
to: account?.address,
|
||||
});
|
||||
}}
|
||||
payModal={{
|
||||
theme: theme === "light" ? "light" : "dark",
|
||||
}}
|
||||
>
|
||||
Buy for 10 MATIC
|
||||
</TransactionButton>
|
||||
) : (
|
||||
<StyledConnectButton />
|
||||
);
|
||||
}
|
||||
@@ -1,10 +1,6 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
@@ -22,19 +18,10 @@
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./src/*"
|
||||
]
|
||||
"@/*": ["./src/*"]
|
||||
},
|
||||
"target": "ES2017"
|
||||
"target": "ES2020"
|
||||
},
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
# @thirdweb-dev/auth
|
||||
|
||||
## 4.1.89
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @thirdweb-dev/wallets@2.5.31
|
||||
|
||||
## 4.1.88
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @thirdweb-dev/wallets@2.5.30
|
||||
|
||||
## 4.1.87
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/auth",
|
||||
"version": "4.1.87",
|
||||
"version": "4.1.89",
|
||||
"main": "dist/thirdweb-dev-auth.cjs.js",
|
||||
"module": "dist/thirdweb-dev-auth.esm.js",
|
||||
"browser": {
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
# thirdweb
|
||||
|
||||
## 0.14.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @thirdweb-dev/sdk@4.0.91
|
||||
- @thirdweb-dev/wallets@2.5.31
|
||||
- @thirdweb-dev/auth@4.1.89
|
||||
|
||||
## 0.14.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @thirdweb-dev/sdk@4.0.90
|
||||
- @thirdweb-dev/wallets@2.5.30
|
||||
- @thirdweb-dev/auth@4.1.88
|
||||
|
||||
## 0.14.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/cli",
|
||||
"main": "dist/cli/index.js",
|
||||
"version": "0.14.2",
|
||||
"version": "0.14.4",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/legacy_packages/cli",
|
||||
"author": "thirdweb eng <[email protected]>",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
# @thirdweb-dev/react-core
|
||||
|
||||
## 4.6.30
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @thirdweb-dev/sdk@4.0.91
|
||||
- @thirdweb-dev/wallets@2.5.31
|
||||
- @thirdweb-dev/auth@4.1.89
|
||||
|
||||
## 4.6.29
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @thirdweb-dev/sdk@4.0.90
|
||||
- @thirdweb-dev/wallets@2.5.30
|
||||
- @thirdweb-dev/auth@4.1.88
|
||||
|
||||
## 4.6.28
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/react-core",
|
||||
"version": "4.6.28",
|
||||
"version": "4.6.30",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/legacy_packages/react-core",
|
||||
"author": "thirdweb eng <[email protected]>",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
Binary file not shown.
@@ -1,5 +1,25 @@
|
||||
# @thirdweb-dev/react
|
||||
|
||||
## 4.6.30
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @thirdweb-dev/sdk@4.0.91
|
||||
- @thirdweb-dev/react-core@4.6.30
|
||||
- @thirdweb-dev/wallets@2.5.31
|
||||
|
||||
## 4.6.29
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#3183](https://github.com/thirdweb-dev/js/pull/3183) [`7bb241f`](https://github.com/thirdweb-dev/js/commit/7bb241f7db12e97b7eff7583548638b7ab2e5b77) Thanks [@MananTank](https://github.com/MananTank)! - Improved OTP input using input-otp library
|
||||
|
||||
- Updated dependencies []:
|
||||
- @thirdweb-dev/sdk@4.0.90
|
||||
- @thirdweb-dev/react-core@4.6.29
|
||||
- @thirdweb-dev/wallets@2.5.30
|
||||
|
||||
## 4.6.28
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/react",
|
||||
"version": "4.6.28",
|
||||
"version": "4.6.30",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/legacy_packages/react",
|
||||
"author": "thirdweb eng <[email protected]>",
|
||||
"license": "Apache-2.0",
|
||||
@@ -122,6 +122,7 @@
|
||||
"copy-to-clipboard": "^3.3.3",
|
||||
"detect-browser": "^5.3.0",
|
||||
"fuse.js": "^7.0.0",
|
||||
"input-otp": "^1.2.4",
|
||||
"qrcode": "^1.5.3",
|
||||
"tiny-invariant": "^1.3.3"
|
||||
},
|
||||
|
||||
@@ -1,140 +1,109 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
import { Input } from "./formElements";
|
||||
import { Container } from "./basic";
|
||||
import { media, fontSize, spacing } from "../design-system";
|
||||
import styled from "@emotion/styled";
|
||||
"use client";
|
||||
import { keyframes } from "@emotion/react";
|
||||
import { OTPInput as InputOTP, type SlotProps } from "input-otp";
|
||||
import { fontSize, spacing, radius } from "../design-system";
|
||||
import { useCustomTheme } from "../design-system/CustomThemeProvider";
|
||||
import { StyledDiv } from "../design-system/elements";
|
||||
import { Container } from "./basic";
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export function OTPInput(props: {
|
||||
digits: number;
|
||||
isInvalid?: boolean;
|
||||
value: string;
|
||||
setValue: (value: string) => void;
|
||||
onEnter?: () => void;
|
||||
onEnter: () => void;
|
||||
}) {
|
||||
const otp = props.value.split("").map(Number);
|
||||
|
||||
const setOTP = (newOTP: number[]) => {
|
||||
props.setValue(newOTP.join(""));
|
||||
};
|
||||
|
||||
const inputToFocusIndex = otp.length;
|
||||
const boxEls = useRef<(HTMLInputElement | null)[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (boxEls.current[inputToFocusIndex]) {
|
||||
requestAnimationFrame(() => {
|
||||
boxEls.current[inputToFocusIndex]?.focus();
|
||||
});
|
||||
}
|
||||
}, [inputToFocusIndex]);
|
||||
|
||||
return (
|
||||
<Container center="x" gap="sm" flex="row">
|
||||
{new Array(props.digits).fill(null).map((_, i) => {
|
||||
return (
|
||||
<OTPInputBox
|
||||
data-error={props.isInvalid}
|
||||
ref={(e) => (boxEls.current[i] = e)}
|
||||
key={i}
|
||||
value={otp[i] ?? ""}
|
||||
type="number"
|
||||
pattern="[0-9]*"
|
||||
variant="outline"
|
||||
inputMode="numeric"
|
||||
onPaste={(e) => {
|
||||
const pastedData = e.clipboardData.getData("text/plain");
|
||||
const newOTP = pastedData
|
||||
.slice(0, props.digits)
|
||||
.split("")
|
||||
.filter((n) => /\d/.test(n))
|
||||
.map(Number);
|
||||
|
||||
setOTP(newOTP);
|
||||
e.preventDefault();
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
if (props.onEnter) {
|
||||
props.onEnter();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (e.key === "ArrowLeft") {
|
||||
if (i === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
boxEls.current[i - 1]?.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.key === "ArrowRight") {
|
||||
if (i === props.digits - 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
boxEls.current[i + 1]?.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.key === "e" || e.key === ".") {
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.key === "Backspace") {
|
||||
if (i === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newOTP = otp.slice(0, -1);
|
||||
setOTP(newOTP);
|
||||
}
|
||||
}}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
|
||||
if (value.length > 1) {
|
||||
setOTP(value.split("").filter((n) => /\d/.test(n)).map(Number));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!/\d/.test(value) && value !== "") {
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
const newOTP = [...otp];
|
||||
const index = i > inputToFocusIndex - 1 ? inputToFocusIndex : i;
|
||||
|
||||
newOTP[index] = Number(value);
|
||||
setOTP(newOTP);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Container>
|
||||
<OTPInputContainer>
|
||||
<InputOTP
|
||||
onComplete={() => {
|
||||
props.onEnter();
|
||||
}}
|
||||
maxLength={6}
|
||||
value={props.value}
|
||||
render={({ slots }) => (
|
||||
<Container flex="row" gap="xs" center="both">
|
||||
{slots.map((slot, idx) => (
|
||||
<Slot key={idx} {...slot} isInvalid={props.isInvalid} />
|
||||
))}
|
||||
</Container>
|
||||
)}
|
||||
onChange={(newValue) => {
|
||||
props.setValue(newValue);
|
||||
}}
|
||||
/>
|
||||
</OTPInputContainer>
|
||||
);
|
||||
}
|
||||
|
||||
const OTPInputBox = /* @__PURE__ */ styled(Input)(() => {
|
||||
const OTPInputContainer = /* @__PURE__ */ StyledDiv({
|
||||
"& input": {
|
||||
maxWidth: "100%",
|
||||
},
|
||||
});
|
||||
|
||||
function Slot(props: SlotProps & { isInvalid?: boolean }) {
|
||||
return (
|
||||
<OTPInputBox data-active={props.isActive} data-error={props.isInvalid}>
|
||||
{props.char !== null && <div>{props.char}</div>}
|
||||
{props.hasFakeCaret && <FakeCaret />}
|
||||
</OTPInputBox>
|
||||
);
|
||||
}
|
||||
|
||||
const caretBlink = keyframes`
|
||||
0%, 100% {
|
||||
opacity: 0;
|
||||
},
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
`;
|
||||
|
||||
const FakeCaret = /* @__PURE__ */StyledDiv(() => {
|
||||
const theme = useCustomTheme();
|
||||
return {
|
||||
appearance: "none",
|
||||
WebkitAppearance: "none",
|
||||
position: "absolute",
|
||||
pointerEvents: "none",
|
||||
inset: 0,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
animation: `${caretBlink} 1s infinite`,
|
||||
"&::before": {
|
||||
content: "''",
|
||||
display: "block",
|
||||
width: "1.5px",
|
||||
height: "1em",
|
||||
backgroundColor: theme.colors.primaryText,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const OTPInputBox = /* @__PURE__ */ StyledDiv(() => {
|
||||
const theme = useCustomTheme();
|
||||
return {
|
||||
position: "relative",
|
||||
width: "40px",
|
||||
height: "40px",
|
||||
textAlign: "center",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
fontSize: fontSize.md,
|
||||
padding: spacing.xs,
|
||||
[media.mobile]: {
|
||||
width: "35px",
|
||||
height: "35px",
|
||||
boxSizing: "border-box",
|
||||
transition: "color 200ms ease",
|
||||
border: `2px solid ${theme.colors.borderColor}`,
|
||||
"&[data-active='true']": {
|
||||
borderColor: theme.colors.accentText,
|
||||
},
|
||||
"&[data-verify-status='invalid']": {
|
||||
color: theme.colors.danger,
|
||||
color: theme.colors.primaryText,
|
||||
borderRadius: radius.lg,
|
||||
"&[data-error='true']": {
|
||||
borderColor: theme.colors.danger,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -313,7 +313,6 @@ export const EmbeddedWalletOTPLoginUI: React.FC<
|
||||
setValue={(value) => {
|
||||
setOtpInput(value);
|
||||
setVerifyStatus("idle"); // reset error
|
||||
verify(value);
|
||||
}}
|
||||
onEnter={() => {
|
||||
verify(otpInput);
|
||||
|
||||
Binary file not shown.
@@ -1,5 +1,19 @@
|
||||
# @thirdweb-dev/sdk
|
||||
|
||||
## 4.0.91
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`3f66945`](https://github.com/thirdweb-dev/js/commit/3f669453d086487a217ae9af82745199e26550c6), [`a346111`](https://github.com/thirdweb-dev/js/commit/a3461114c5e9adb8d0ea2f36bc9fb3d96366c5b9), [`51e7ada`](https://github.com/thirdweb-dev/js/commit/51e7adaeb0fe050eb132510291ecac7a12e4c4ad), [`51e7ada`](https://github.com/thirdweb-dev/js/commit/51e7adaeb0fe050eb132510291ecac7a12e4c4ad)]:
|
||||
- thirdweb@5.27.0
|
||||
|
||||
## 4.0.90
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`3dca028`](https://github.com/thirdweb-dev/js/commit/3dca028a7eff926ae406eb29cc82016ae6c994b3), [`874747b`](https://github.com/thirdweb-dev/js/commit/874747b3e00714e9a7336dcfda34e7b92b82e7e1), [`123275b`](https://github.com/thirdweb-dev/js/commit/123275bbe275457b0be5205812ba0ef75b1f0e5b), [`b569eb4`](https://github.com/thirdweb-dev/js/commit/b569eb48ad018619e4589b9efb5aa774c7b77642), [`b569eb4`](https://github.com/thirdweb-dev/js/commit/b569eb48ad018619e4589b9efb5aa774c7b77642)]:
|
||||
- thirdweb@5.26.0
|
||||
|
||||
## 4.0.89
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/sdk",
|
||||
"version": "4.0.89",
|
||||
"version": "4.0.91",
|
||||
"description": "The main thirdweb SDK.",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/legacy_packages/sdk",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,5 +1,23 @@
|
||||
# @thirdweb-dev/unity-js-bridge
|
||||
|
||||
## 0.6.40
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @thirdweb-dev/sdk@4.0.91
|
||||
- @thirdweb-dev/wallets@2.5.31
|
||||
- @thirdweb-dev/auth@4.1.89
|
||||
|
||||
## 0.6.39
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @thirdweb-dev/sdk@4.0.90
|
||||
- @thirdweb-dev/wallets@2.5.30
|
||||
- @thirdweb-dev/auth@4.1.88
|
||||
|
||||
## 0.6.38
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/unity-js-bridge",
|
||||
"version": "0.6.38",
|
||||
"version": "0.6.40",
|
||||
"main": "dist/thirdweb-unity-bridge.js",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/unity-js-bridge",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -171,7 +171,7 @@ class ThirdwebBridge implements TWBridge {
|
||||
// biome-ignore lint/suspicious/noExplicitAny: TODO: fix use of any
|
||||
(globalThis as any).X_SDK_PLATFORM = "unity";
|
||||
// biome-ignore lint/suspicious/noExplicitAny: TODO: fix use of any
|
||||
(globalThis as any).X_SDK_VERSION = "4.13.3";
|
||||
(globalThis as any).X_SDK_VERSION = "4.14.1";
|
||||
// biome-ignore lint/suspicious/noExplicitAny: TODO: fix use of any
|
||||
(globalThis as any).X_SDK_OS = browser?.os ?? "unknown";
|
||||
}
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
# @thirdweb-dev/wallets
|
||||
|
||||
## 2.5.31
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @thirdweb-dev/sdk@4.0.91
|
||||
|
||||
## 2.5.30
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @thirdweb-dev/sdk@4.0.90
|
||||
|
||||
## 2.5.29
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/wallets",
|
||||
"version": "2.5.29",
|
||||
"version": "2.5.31",
|
||||
"main": "dist/thirdweb-dev-wallets.cjs.js",
|
||||
"module": "dist/thirdweb-dev-wallets.esm.js",
|
||||
"browser": {
|
||||
@@ -520,9 +520,9 @@
|
||||
"sideEffects": false,
|
||||
"dependencies": {
|
||||
"@account-abstraction/contracts": "^0.5.0",
|
||||
"@blocto/sdk": "0.5.5",
|
||||
"@coinbase/wallet-sdk": "^3.7.2",
|
||||
"@google-cloud/kms": "4.2.0",
|
||||
"@blocto/sdk": "0.10.2",
|
||||
"@coinbase/wallet-sdk": "^3.9.0",
|
||||
"@google-cloud/kms": "^4.4.0",
|
||||
"@magic-ext/connect": "^6.7.2",
|
||||
"@magic-ext/oauth": "^7.6.2",
|
||||
"@magic-sdk/provider": "^13.6.2",
|
||||
@@ -530,7 +530,7 @@
|
||||
"@paperxyz/embedded-wallet-service-sdk": "^1.2.5",
|
||||
"@paperxyz/sdk-common-utilities": "^0.1.1",
|
||||
"@safe-global/safe-core-sdk": "^3.3.5",
|
||||
"@safe-global/safe-ethers-adapters": "0.1.0-alpha.17",
|
||||
"@safe-global/safe-ethers-adapters": "0.1.0-alpha.19",
|
||||
"@safe-global/safe-ethers-lib": "^1.9.4",
|
||||
"@thirdweb-dev/chains": "workspace:*",
|
||||
"@thirdweb-dev/contracts-js": "workspace:*",
|
||||
|
||||
@@ -114,7 +114,7 @@ export class BloctoConnector extends WagmiConnector<
|
||||
getProvider({ chainId }: { chainId?: number } = {}): Promise<BloctoProvider> {
|
||||
if (!this._provider) {
|
||||
const _chainId =
|
||||
chainId ?? this.options.chainId ?? this.chains[0]?.chainId ?? 1;
|
||||
chainId ?? this.chains[0]?.chainId ?? 1;
|
||||
const _rpc = this.chains.find((x) => x.chainId === _chainId)?.rpc[0];
|
||||
|
||||
this._provider = new BloctoSDK({
|
||||
@@ -145,7 +145,7 @@ export class BloctoConnector extends WagmiConnector<
|
||||
}
|
||||
|
||||
async isAuthorized(): Promise<boolean> {
|
||||
return !!this._provider?._blocto?.sessionKey ?? false;
|
||||
return !!this._provider?._blocto?.sessionKeyEnv ?? false;
|
||||
}
|
||||
|
||||
async switchChain(chainId: number): Promise<Chain> {
|
||||
@@ -157,8 +157,8 @@ export class BloctoConnector extends WagmiConnector<
|
||||
throw new SwitchChainError(new Error("chain not found on connector."));
|
||||
}
|
||||
|
||||
const isBloctoSupportChain =
|
||||
provider._blocto.supportNetworkList[`${chainId}`];
|
||||
const blocktoSupportedChainList = await provider.supportChainList();
|
||||
const isBloctoSupportChain = blocktoSupportedChainList[`${chainId}`];
|
||||
if (!isBloctoSupportChain) {
|
||||
throw new SwitchChainError(new Error(`Blocto unsupported chain: ${id}`));
|
||||
}
|
||||
|
||||
+3
@@ -17,6 +17,9 @@ export enum AuthProvider {
|
||||
CUSTOM_AUTH_ENDPOINT = "CustomAuthEndpoint",
|
||||
FACEBOOK = "Facebook",
|
||||
APPLE = "Apple",
|
||||
PASSKEY = "Passkey",
|
||||
EXTERNAL_WALLET = "ExternalWallet",
|
||||
DISCORD = "Discord",
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Binary file not shown.
@@ -37,7 +37,6 @@
|
||||
"hotlink-init": "node ./scripts/hotlink/hotlink-init.mjs",
|
||||
"hotlink-revert": "node ./scripts/hotlink/hotlink-revert.mjs"
|
||||
},
|
||||
"packageManager": "[email protected]",
|
||||
"dependencies": {
|
||||
"@changesets/changelog-github": "0.5.0",
|
||||
"@changesets/cli": "2.27.1",
|
||||
|
||||
@@ -23,10 +23,7 @@
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"files": [
|
||||
"dist/*",
|
||||
"src/*"
|
||||
],
|
||||
"files": ["dist/*", "src/*"],
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-lambda": "3.577.0",
|
||||
"@aws-sdk/credential-providers": "3.577.0",
|
||||
@@ -43,7 +40,7 @@
|
||||
"expo-application": "^5",
|
||||
"expo-web-browser": "^13",
|
||||
"react-native-aes-gcm-crypto": "^0.2",
|
||||
"react-native-quick-crypto": ">=0.6.1",
|
||||
"react-native-quick-crypto": ">=0.7.0-rc.6 || >=0.7",
|
||||
"shamir-secret-sharing": "^0.0.3",
|
||||
"react-native": ">=0.70",
|
||||
"react-native-get-random-values": "^1"
|
||||
|
||||
@@ -1,5 +1,179 @@
|
||||
# thirdweb
|
||||
|
||||
## 5.27.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#3181](https://github.com/thirdweb-dev/js/pull/3181) [`a346111`](https://github.com/thirdweb-dev/js/commit/a3461114c5e9adb8d0ea2f36bc9fb3d96366c5b9) Thanks [@gregfromstl](https://github.com/gregfromstl)! - Adds headless functions for creating and managing a WalletConnect session with a connected wallet
|
||||
|
||||
### `createWalletConnectClient`
|
||||
|
||||
```ts
|
||||
import { createWalletConnectClient } from "thirdweb/wallets/wallet-connect";
|
||||
|
||||
createWalletConnectClient({
|
||||
client: client,
|
||||
wallet: wallet,
|
||||
onConnect: (session: any) => {
|
||||
alert("Connected");
|
||||
},
|
||||
onDisconnect: (session: any) => {
|
||||
alert("Disconnected");
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### `createWalletConnectSession`
|
||||
|
||||
```ts
|
||||
import { createWalletConnectSession } from "thirdweb/wallets/wallet-connect";
|
||||
|
||||
createWalletConnectSession({
|
||||
walletConnectClient: wcClient,
|
||||
uri: "wc:...",
|
||||
});
|
||||
```
|
||||
|
||||
### `getWalletConnectSessions`
|
||||
|
||||
```ts
|
||||
import {
|
||||
getWalletConnectSession,
|
||||
type WalletConnectSession,
|
||||
} from "thirdweb/wallets/wallet-connect";
|
||||
|
||||
const sessions: WalletConnectSession[] = await getWalletConnectSessions();
|
||||
```
|
||||
|
||||
### `disconnectWalletConnectClient`
|
||||
|
||||
```ts
|
||||
import { disconnectWalletConnectClient } from "thirdweb/wallets/wallet-connect";
|
||||
|
||||
disconnectWalletConnectClient({ session, walletConnectClient: wcClient });
|
||||
```
|
||||
|
||||
- [#3105](https://github.com/thirdweb-dev/js/pull/3105) [`51e7ada`](https://github.com/thirdweb-dev/js/commit/51e7adaeb0fe050eb132510291ecac7a12e4c4ad) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - First party support for zkSync native account abstraction
|
||||
|
||||
You can now use smart accounts on zkSync and zkSync sepolia without any extra setup.
|
||||
|
||||
```ts
|
||||
const wallet = smartWallet({
|
||||
chain: zkSync,
|
||||
sponsorGas: true,
|
||||
});
|
||||
|
||||
const smartAccount = await wallet.connect({
|
||||
client,
|
||||
personalAccount,
|
||||
});
|
||||
|
||||
// now your can perform transactions normally, gas will be sponsored
|
||||
sendTransaction({ transaction, account: smartAccount });
|
||||
```
|
||||
|
||||
- [#3105](https://github.com/thirdweb-dev/js/pull/3105) [`51e7ada`](https://github.com/thirdweb-dev/js/commit/51e7adaeb0fe050eb132510291ecac7a12e4c4ad) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - ZkSync transaction support
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#3195](https://github.com/thirdweb-dev/js/pull/3195) [`3f66945`](https://github.com/thirdweb-dev/js/commit/3f669453d086487a217ae9af82745199e26550c6) Thanks [@gregfromstl](https://github.com/gregfromstl)! - Fix `estimateGas` type resolution
|
||||
|
||||
## 5.26.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#3161](https://github.com/thirdweb-dev/js/pull/3161) [`b569eb4`](https://github.com/thirdweb-dev/js/commit/b569eb48ad018619e4589b9efb5aa774c7b77642) Thanks [@gregfromstl](https://github.com/gregfromstl)! - Adds waitForBundle function to await a sendCalls bundle's complete confirmation.
|
||||
|
||||
### Usage
|
||||
|
||||
```ts
|
||||
import { waitForBundle } from "thirdweb/wallets/eip5792";
|
||||
const result = await waitForBundle({
|
||||
client,
|
||||
chain,
|
||||
wallet,
|
||||
bundleId: "0x123...",
|
||||
});
|
||||
```
|
||||
|
||||
- [#3161](https://github.com/thirdweb-dev/js/pull/3161) [`b569eb4`](https://github.com/thirdweb-dev/js/commit/b569eb48ad018619e4589b9efb5aa774c7b77642) Thanks [@gregfromstl](https://github.com/gregfromstl)! - Adds EIP-5792 react hooks
|
||||
|
||||
## `useSendCalls`
|
||||
|
||||
`useSendCalls` will automatically revalidate all reads from contracts that are interacted with.
|
||||
|
||||
```tsx
|
||||
import { useSendCalls } from "thirdweb/react";
|
||||
|
||||
const sendTx1 = approve({
|
||||
contract: USDT_CONTRACT,
|
||||
amount: 100,
|
||||
spender: "0x33d9B8BEfE81027E2C859EDc84F5636cbb202Ed6",
|
||||
});
|
||||
const sendTx2 = approve({
|
||||
contract: USDT_CONTRACT,
|
||||
amount: 100,
|
||||
spender: "0x2a4f24F935Eb178e3e7BA9B53A5Ee6d8407C0709",
|
||||
});
|
||||
const { mutate: sendCalls, data: bundleId } = useSendCalls({ client });
|
||||
await sendCalls({
|
||||
wallet,
|
||||
client,
|
||||
calls: [sendTx1, sendTx2],
|
||||
});
|
||||
```
|
||||
|
||||
Await the bundle's full confirmation:
|
||||
|
||||
```tsx
|
||||
const { mutate: sendCalls, data: bundleId } = useSendCalls({
|
||||
client,
|
||||
waitForResult: true,
|
||||
});
|
||||
await sendCalls({
|
||||
wallet,
|
||||
client,
|
||||
calls: [sendTx1, sendTx2],
|
||||
});
|
||||
```
|
||||
|
||||
Sponsor transactions with a paymaster:
|
||||
|
||||
```ts
|
||||
const { mutate: sendCalls, data: bundleId } = useSendCalls();
|
||||
await sendCalls({
|
||||
client,
|
||||
calls: [sendTx1, sendTx2],
|
||||
capabilities: {
|
||||
paymasterService: {
|
||||
url: `https://${CHAIN.id}.bundler.thirdweb.com/${client.clientId}`,
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## `useCapabilities`
|
||||
|
||||
```tsx
|
||||
import { useCapabilities } from "thirdweb/react";
|
||||
const { data: capabilities, isLoading } = useCapabilities();
|
||||
```
|
||||
|
||||
## `useCallsStatus`
|
||||
|
||||
```tsx
|
||||
import { useCallsStatus } from "thirdweb/react";
|
||||
const { data: status, isLoading } = useCallsStatus({ bundleId, client });
|
||||
```
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#3178](https://github.com/thirdweb-dev/js/pull/3178) [`3dca028`](https://github.com/thirdweb-dev/js/commit/3dca028a7eff926ae406eb29cc82016ae6c994b3) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Fix gas estimation in transaction button
|
||||
|
||||
- [#3187](https://github.com/thirdweb-dev/js/pull/3187) [`874747b`](https://github.com/thirdweb-dev/js/commit/874747b3e00714e9a7336dcfda34e7b92b82e7e1) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Upgrade to latest coinbase wallet sdk
|
||||
|
||||
- [#3186](https://github.com/thirdweb-dev/js/pull/3186) [`123275b`](https://github.com/thirdweb-dev/js/commit/123275bbe275457b0be5205812ba0ef75b1f0e5b) Thanks [@gregfromstl](https://github.com/gregfromstl)! - Catch any errors thrown while polling block numbers
|
||||
|
||||
## 5.25.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "thirdweb",
|
||||
"version": "5.25.1",
|
||||
"version": "5.27.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/thirdweb-dev/js.git#main"
|
||||
@@ -176,7 +176,7 @@
|
||||
"!tsconfig.build.json"
|
||||
],
|
||||
"dependencies": {
|
||||
"@coinbase/wallet-sdk": "4.0.0-rc.2",
|
||||
"@coinbase/wallet-sdk": "4.0.3",
|
||||
"@emotion/react": "11.11.4",
|
||||
"@emotion/styled": "11.11.0",
|
||||
"@google/model-viewer": "2.1.1",
|
||||
@@ -189,6 +189,7 @@
|
||||
"@radix-ui/react-tooltip": "1.0.7",
|
||||
"@tanstack/react-query": "5.29.2",
|
||||
"@walletconnect/ethereum-provider": "2.12.2",
|
||||
"@walletconnect/sign-client": "^2.13.1",
|
||||
"abitype": "1.0.0",
|
||||
"fast-text-encoding": "^1.0.6",
|
||||
"fuse.js": "7.0.0",
|
||||
@@ -210,7 +211,7 @@
|
||||
"react": ">=18",
|
||||
"react-native": ">=0.70",
|
||||
"react-native-aes-gcm-crypto": "^0.2",
|
||||
"react-native-quick-crypto": ">=0.6.1",
|
||||
"react-native-quick-crypto": ">=0.7.0-rc.6 || >=0.7",
|
||||
"shamir-secret-sharing": "^0.0.3",
|
||||
"typescript": ">=5.0.4"
|
||||
},
|
||||
@@ -293,6 +294,7 @@
|
||||
"@react-native-async-storage/async-storage": "^1.23.1",
|
||||
"@testing-library/jest-dom": "^6.4.5",
|
||||
"@testing-library/react": "^15.0.6",
|
||||
"@testing-library/react-hooks": "^8.0.1",
|
||||
"@types/cross-spawn": "^6.0.6",
|
||||
"@types/react": "18.2.17",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
|
||||
@@ -11,6 +11,29 @@ export type AdapterWalletOptions = {
|
||||
switchChain: (chain: Chain) => Promise<void> | void;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a wallet from the given adapted account. Use this to convert a third party library wallet into a thirdweb wallet.
|
||||
* @param options - The options for the adapter wallet.
|
||||
* @returns a wallet instance.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { createWalletAdapter } from "thirdweb";
|
||||
*
|
||||
* const wallet = createWalletAdapter({
|
||||
* client,
|
||||
* adaptedAccount,
|
||||
* chain,
|
||||
* onDisconnect: () => {
|
||||
* // disconnect logic
|
||||
* },
|
||||
* switchChain: async (chain) => {
|
||||
* // switch chain logic
|
||||
* },
|
||||
* });
|
||||
* ```
|
||||
* @wallet
|
||||
*/
|
||||
export function createWalletAdapter(
|
||||
options: AdapterWalletOptions,
|
||||
): Wallet<"adapter"> {
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import { defineChain } from "../utils.js";
|
||||
|
||||
export const hardhat = /* @__PURE__ */ defineChain({
|
||||
id: 31337,
|
||||
name: "Hardhat",
|
||||
rpc: "http://127.0.0.1:8545",
|
||||
testnet: true,
|
||||
nativeCurrency: {
|
||||
name: "Hardhat Ether",
|
||||
symbol: "ETH",
|
||||
decimals: 18,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
import { defineChain } from "../utils.js";
|
||||
|
||||
export const zkSyncSepolia = /*@__PURE__*/ defineChain({
|
||||
id: 300,
|
||||
name: "ZkSync Sepolia",
|
||||
nativeCurrency: {
|
||||
decimals: 18,
|
||||
name: "Ether",
|
||||
symbol: "ETH",
|
||||
},
|
||||
blockExplorers: [
|
||||
{
|
||||
name: "zkSync Sepolia Block Explorer",
|
||||
url: "https://sepolia.explorer.zksync.io",
|
||||
apiUrl: "https://block-explorer-api.sepolia.zksync.dev/api",
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
import { defineChain } from "../utils.js";
|
||||
|
||||
export const zkSync = /*@__PURE__*/ defineChain({
|
||||
id: 324,
|
||||
name: "ZkSync Era",
|
||||
nativeCurrency: {
|
||||
decimals: 18,
|
||||
name: "Ether",
|
||||
symbol: "ETH",
|
||||
},
|
||||
blockExplorers: [
|
||||
{
|
||||
name: "zkSync Era Block Explorer",
|
||||
url: "https://explorer.zksync.io",
|
||||
apiUrl: "https://block-explorer-api.zksync.dev/api",
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -11,6 +11,7 @@ export {
|
||||
* PRE_DEFINED CHAINS
|
||||
*/
|
||||
export { anvil } from "../chains/chain-definitions/anvil.js";
|
||||
export { hardhat } from "../chains/chain-definitions/hardhat.js";
|
||||
export { arbitrumNova } from "../chains/chain-definitions/arbitrum-nova.js";
|
||||
export { arbitrumSepolia } from "../chains/chain-definitions/arbitrum-sepolia.js";
|
||||
export { arbitrum } from "../chains/chain-definitions/arbitrum.js";
|
||||
@@ -34,3 +35,5 @@ export { zoraSepolia } from "../chains/chain-definitions/zora-sepolia.js";
|
||||
export { zora } from "../chains/chain-definitions/zora.js";
|
||||
export { bsc } from "../chains/chain-definitions/bsc.js";
|
||||
export { bscTestnet } from "../chains/chain-definitions/bsc-testnet.js";
|
||||
export { zkSync } from "../chains/chain-definitions/zksync.js";
|
||||
export { zkSyncSepolia } from "../chains/chain-definitions/zksync-sepolia.js";
|
||||
|
||||
@@ -210,6 +210,7 @@ export {
|
||||
type StringToHexOpts,
|
||||
type ToHexParameters,
|
||||
type Uint8ArrayToHexOpts,
|
||||
type Hex,
|
||||
} from "../utils/encoding/hex.js";
|
||||
export { concatHex } from "../utils/encoding/helpers/concat-hex.js";
|
||||
|
||||
|
||||
@@ -10,8 +10,14 @@ export type {
|
||||
PreparedSendCall,
|
||||
PrepareCallOptions,
|
||||
} from "../../wallets/eip5792/send-calls.js";
|
||||
export { getCallsStatus } from "../../wallets/eip5792/get-calls-status.js";
|
||||
export type { GetCallsStatusOptions } from "../../wallets/eip5792/get-calls-status.js";
|
||||
export {
|
||||
type GetCallsStatusOptions,
|
||||
getCallsStatus,
|
||||
} from "../../wallets/eip5792/get-calls-status.js";
|
||||
export {
|
||||
waitForBundle,
|
||||
type WaitForBundleOptions,
|
||||
} from "../../wallets/eip5792/wait-for-bundle.js";
|
||||
export type {
|
||||
WalletCapabilities,
|
||||
WalletCapabilitiesRecord,
|
||||
@@ -20,3 +26,6 @@ export type {
|
||||
GetCallsStatusResponse,
|
||||
WalletCallReceipt,
|
||||
} from "../../wallets/eip5792/types.js";
|
||||
export { useSendCalls } from "../../react/core/hooks/wallets/useSendCalls.js";
|
||||
export { useCallsStatus } from "../../react/core/hooks/wallets/useCallsStatus.js";
|
||||
export { useCapabilities } from "../../react/core/hooks/wallets/useCapabilities.js";
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
export {
|
||||
createWalletConnectClient,
|
||||
createWalletConnectSession,
|
||||
disconnectWalletConnectSession,
|
||||
getActiveWalletConnectSessions,
|
||||
} from "../../wallets/wallet-connect/consumer/index.js";
|
||||
export type {
|
||||
WalletConnectClient,
|
||||
WalletConnectSession,
|
||||
} from "../../wallets/wallet-connect/consumer/types.js";
|
||||
@@ -0,0 +1,15 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { DOODLES_CONTRACT, DROP1155_CONTRACT } from "~test/test-contracts.js";
|
||||
import { isERC1155 } from "./isERC1155.js";
|
||||
|
||||
describe("isERC1155", () => {
|
||||
it("should return TRUE if contract is an ERC1155 contract", async () => {
|
||||
const result = await isERC1155({ contract: DROP1155_CONTRACT });
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it("should return FALSE if contract is NOT an ERC1155 contract", async () => {
|
||||
const result = await isERC1155({ contract: DOODLES_CONTRACT });
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { DOODLES_CONTRACT } from "~test/test-contracts.js";
|
||||
import { getOwnedNFTs } from "./getOwnedNFTs.js";
|
||||
|
||||
describe.runIf(process.env.TW_SECRET_KEY)("erc721.getOwnedNFTs", () => {
|
||||
it("should return the correct data", async () => {
|
||||
const owner = "0x3010775D16E7B79AF280035c64a1Df5F705CfdDb";
|
||||
const nfts = await getOwnedNFTs({
|
||||
contract: DOODLES_CONTRACT,
|
||||
owner,
|
||||
});
|
||||
|
||||
// The following code is based on the state of the forked chain
|
||||
// so the data should not change
|
||||
expect(nfts.length).toBe(81);
|
||||
for (const item of nfts) {
|
||||
expect(item.owner).toBe(owner);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { DOODLES_CONTRACT } from "~test/test-contracts.js";
|
||||
import { getOwnedTokenIds } from "./getOwnedTokenIds.js";
|
||||
|
||||
describe.runIf(process.env.TW_SECRET_KEY)("erc721.getOwnedTokenIds", () => {
|
||||
it("should return the correct result", async () => {
|
||||
const owner = "0x3010775D16E7B79AF280035c64a1Df5F705CfdDb";
|
||||
const tokenIds = await getOwnedTokenIds({
|
||||
contract: DOODLES_CONTRACT,
|
||||
owner,
|
||||
});
|
||||
|
||||
// The following code is based on the state of the forked chain
|
||||
// so the data should not change
|
||||
expect(tokenIds.length).toBe(81);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,84 @@
|
||||
import { beforeEach, describe, expect, it } from "vitest";
|
||||
import { ANVIL_CHAIN } from "~test/chains.js";
|
||||
import { TEST_CONTRACT_URI } from "~test/ipfs-uris.js";
|
||||
import { TEST_CLIENT } from "~test/test-clients.js";
|
||||
import { TEST_ACCOUNT_A } from "~test/test-wallets.js";
|
||||
import { NATIVE_TOKEN_ADDRESS } from "../../../constants/addresses.js";
|
||||
import {
|
||||
type ThirdwebContract,
|
||||
getContract,
|
||||
} from "../../../contract/contract.js";
|
||||
import { sendAndConfirmTransaction } from "../../../transaction/actions/send-and-confirm-transaction.js";
|
||||
import { deployERC721Contract } from "../../prebuilts/deploy-erc721.js";
|
||||
import { claimTo } from "../drops/write/claimTo.js";
|
||||
import { setClaimConditions } from "../drops/write/setClaimConditions.js";
|
||||
import { lazyMint } from "../write/lazyMint.js";
|
||||
import { getTotalClaimedSupply } from "./getTotalClaimedSupply.js";
|
||||
|
||||
const account = TEST_ACCOUNT_A;
|
||||
let contract: ThirdwebContract;
|
||||
|
||||
describe.runIf(process.env.TW_SECRET_KEY)(
|
||||
"erc721: getTotalClaimedSupply",
|
||||
() => {
|
||||
beforeEach(async () => {
|
||||
const address = await deployERC721Contract({
|
||||
client: TEST_CLIENT,
|
||||
account,
|
||||
type: "DropERC721",
|
||||
chain: ANVIL_CHAIN,
|
||||
params: {
|
||||
name: "",
|
||||
contractURI: TEST_CONTRACT_URI,
|
||||
},
|
||||
});
|
||||
|
||||
contract = getContract({
|
||||
chain: ANVIL_CHAIN,
|
||||
address,
|
||||
client: TEST_CLIENT,
|
||||
});
|
||||
|
||||
const lazyMintTx = lazyMint({
|
||||
contract,
|
||||
// These are the pre-uploaded URIs file - to speed up the test
|
||||
nfts: [
|
||||
"ipfs://QmVQpAEkbzbqm7fxMJ5up68CppbP5fF7bLxnrtPRMuTaYT/0",
|
||||
"ipfs://QmVQpAEkbzbqm7fxMJ5up68CppbP5fF7bLxnrtPRMuTaYT/1",
|
||||
"ipfs://QmVQpAEkbzbqm7fxMJ5up68CppbP5fF7bLxnrtPRMuTaYT/2",
|
||||
],
|
||||
});
|
||||
|
||||
await sendAndConfirmTransaction({ transaction: lazyMintTx, account });
|
||||
|
||||
const setClaimTx = setClaimConditions({
|
||||
contract,
|
||||
phases: [
|
||||
{
|
||||
maxClaimableSupply: 100n,
|
||||
maxClaimablePerWallet: 100n,
|
||||
currencyAddress: NATIVE_TOKEN_ADDRESS,
|
||||
price: 0,
|
||||
startTime: new Date(),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await sendAndConfirmTransaction({
|
||||
transaction: setClaimTx,
|
||||
account,
|
||||
});
|
||||
});
|
||||
|
||||
it("should return the correct claimed amount", async () => {
|
||||
const tx = claimTo({
|
||||
contract,
|
||||
to: account.address,
|
||||
quantity: 1n,
|
||||
});
|
||||
await sendAndConfirmTransaction({ transaction: tx, account });
|
||||
const result = await getTotalClaimedSupply({ contract });
|
||||
expect(result).toBe(1n);
|
||||
});
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,84 @@
|
||||
import { beforeEach, describe, expect, it } from "vitest";
|
||||
import { ANVIL_CHAIN } from "~test/chains.js";
|
||||
import { TEST_CONTRACT_URI } from "~test/ipfs-uris.js";
|
||||
import { TEST_CLIENT } from "~test/test-clients.js";
|
||||
import { TEST_ACCOUNT_A } from "~test/test-wallets.js";
|
||||
import { NATIVE_TOKEN_ADDRESS } from "../../../constants/addresses.js";
|
||||
import {
|
||||
type ThirdwebContract,
|
||||
getContract,
|
||||
} from "../../../contract/contract.js";
|
||||
import { sendAndConfirmTransaction } from "../../../transaction/actions/send-and-confirm-transaction.js";
|
||||
import { deployERC721Contract } from "../../prebuilts/deploy-erc721.js";
|
||||
import { claimTo } from "../drops/write/claimTo.js";
|
||||
import { setClaimConditions } from "../drops/write/setClaimConditions.js";
|
||||
import { lazyMint } from "../write/lazyMint.js";
|
||||
import { getTotalUnclaimedSupply } from "./getTotalUnclaimedSupply.js";
|
||||
|
||||
const account = TEST_ACCOUNT_A;
|
||||
let contract: ThirdwebContract;
|
||||
|
||||
describe.runIf(process.env.TW_SECRET_KEY)(
|
||||
"erc721: getTotalClaimedSupply",
|
||||
() => {
|
||||
beforeEach(async () => {
|
||||
const address = await deployERC721Contract({
|
||||
client: TEST_CLIENT,
|
||||
account,
|
||||
type: "DropERC721",
|
||||
chain: ANVIL_CHAIN,
|
||||
params: {
|
||||
name: "",
|
||||
contractURI: TEST_CONTRACT_URI,
|
||||
},
|
||||
});
|
||||
|
||||
contract = getContract({
|
||||
chain: ANVIL_CHAIN,
|
||||
address,
|
||||
client: TEST_CLIENT,
|
||||
});
|
||||
|
||||
const lazyMintTx = lazyMint({
|
||||
contract,
|
||||
// These are the pre-uploaded URIs file - to speed up the test
|
||||
nfts: [
|
||||
"ipfs://QmVQpAEkbzbqm7fxMJ5up68CppbP5fF7bLxnrtPRMuTaYT/0",
|
||||
"ipfs://QmVQpAEkbzbqm7fxMJ5up68CppbP5fF7bLxnrtPRMuTaYT/1",
|
||||
"ipfs://QmVQpAEkbzbqm7fxMJ5up68CppbP5fF7bLxnrtPRMuTaYT/2",
|
||||
],
|
||||
});
|
||||
|
||||
await sendAndConfirmTransaction({ transaction: lazyMintTx, account });
|
||||
|
||||
const setClaimTx = setClaimConditions({
|
||||
contract,
|
||||
phases: [
|
||||
{
|
||||
maxClaimableSupply: 100n,
|
||||
maxClaimablePerWallet: 100n,
|
||||
currencyAddress: NATIVE_TOKEN_ADDRESS,
|
||||
price: 0,
|
||||
startTime: new Date(),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await sendAndConfirmTransaction({
|
||||
transaction: setClaimTx,
|
||||
account,
|
||||
});
|
||||
});
|
||||
|
||||
it("should return the correct un-claimed amount", async () => {
|
||||
const tx = claimTo({
|
||||
contract,
|
||||
to: account.address,
|
||||
quantity: 1n,
|
||||
});
|
||||
await sendAndConfirmTransaction({ transaction: tx, account });
|
||||
const result = await getTotalUnclaimedSupply({ contract });
|
||||
expect(result).toBe(2n);
|
||||
});
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,15 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { DOODLES_CONTRACT, DROP1155_CONTRACT } from "~test/test-contracts.js";
|
||||
import { isERC721 } from "./isERC721.js";
|
||||
|
||||
describe("isERC721", () => {
|
||||
it("should return TRUE if contract is an ERC721 contract", async () => {
|
||||
const result = await isERC721({ contract: DOODLES_CONTRACT });
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it("should return FALSE if contract is NOT an ERC721 contract", async () => {
|
||||
const result = await isERC721({ contract: DROP1155_CONTRACT });
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -6,6 +6,12 @@ export type SetSharedMetadataParams = {
|
||||
nft: NFTInput;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the shared metadata for a OpenEdition contract.
|
||||
* @param options - The options for the transaction.
|
||||
* @returns The prepared transaction.
|
||||
* @extension ERC721
|
||||
*/
|
||||
export function setSharedMetadata(
|
||||
options: BaseTransactionOptions<SetSharedMetadataParams>,
|
||||
) {
|
||||
|
||||
@@ -41,15 +41,15 @@ export async function getGasOverridesForTransaction(
|
||||
]);
|
||||
|
||||
// Exit early if the user explicitly provided enough options
|
||||
if (gasPrice) {
|
||||
return { gasPrice };
|
||||
}
|
||||
if (maxFeePerGas && maxPriorityFeePerGas) {
|
||||
if (maxFeePerGas !== undefined && maxPriorityFeePerGas !== undefined) {
|
||||
return {
|
||||
maxFeePerGas,
|
||||
maxPriorityFeePerGas,
|
||||
};
|
||||
}
|
||||
if (gasPrice) {
|
||||
return { gasPrice };
|
||||
}
|
||||
|
||||
// If we don't have enough explicit values, get defaults
|
||||
const defaultGasOverrides = await getDefaultGasOverrides(
|
||||
|
||||
@@ -33,7 +33,7 @@ export type BuyWithFiatHistoryParams = {
|
||||
|
||||
/**
|
||||
* The results for [`getBuyWithFiatHistory`](https://portal.thirdweb.com/references/typescript/v5/getBuyWithFiatHistory) function
|
||||
* @buyFiat
|
||||
* @buyCrypto
|
||||
*/
|
||||
export type BuyWithFiatHistoryData = {
|
||||
page: BuyWithFiatStatus[];
|
||||
@@ -60,7 +60,7 @@ export type BuyWithFiatHistoryData = {
|
||||
* })
|
||||
* ```
|
||||
* @returns Object of type [`BuyWithFiatHistoryData`](https://portal.thirdweb.com/references/typescript/v5/BuyWithFiatHistoryData)
|
||||
* @buyFiat
|
||||
* @buyCrypto
|
||||
*/
|
||||
export async function getBuyWithFiatHistory(
|
||||
params: BuyWithFiatHistoryParams,
|
||||
|
||||
@@ -54,7 +54,7 @@ export type GetPostOnRampQuoteParams = {
|
||||
* });
|
||||
* }
|
||||
* ```
|
||||
* @buyFiat
|
||||
* @buyCrypto
|
||||
*/
|
||||
export async function getPostOnRampQuote({
|
||||
client,
|
||||
|
||||
@@ -232,7 +232,7 @@ export type BuyWithFiatQuote = {
|
||||
*
|
||||
* window.open(quote.onRampLink, "_blank");
|
||||
* ```
|
||||
* @buyFiat
|
||||
* @buyCrypto
|
||||
*/
|
||||
export async function getBuyWithFiatQuote(
|
||||
params: GetBuyWithFiatQuoteParams,
|
||||
|
||||
@@ -158,7 +158,7 @@ export type BuyWithFiatStatus =
|
||||
* // when the fiatStatus.status is "ON_RAMP_TRANSFER_COMPLETED" - the process is complete
|
||||
* // when the fiatStatus.status is "CRYPTO_SWAP_REQUIRED" - start the swap process
|
||||
* ```
|
||||
* @buyFiat
|
||||
* @buyCrypto
|
||||
*/
|
||||
export async function getBuyWithFiatStatus(
|
||||
params: GetBuyWithFiatStatusParams,
|
||||
|
||||
@@ -11,7 +11,7 @@ import type { BuyWithFiatQuote } from "./getQuote.js";
|
||||
*
|
||||
* @param buyWithFiatQuote - The quote of type [`BuyWithFiatQuote`](https://portal.thirdweb.com/references/typescript/v5/BuyWithFiatQuote) returned
|
||||
* by the [`getBuyWithFiatQuote`](https://portal.thirdweb.com/references/typescript/v5/getBuyWithFiatQuote) function.
|
||||
* @buyFiat
|
||||
* @buyCrypto
|
||||
*/
|
||||
export function isSwapRequiredPostOnramp(
|
||||
buyWithFiatQuote: Pick<BuyWithFiatQuote, "toToken" | "onRampToken">,
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { createWallet } from "../../../../wallets/create-wallet.js";
|
||||
import { getInstalledWalletProviders } from "../../../../wallets/injected/mipdStore.js";
|
||||
import type { Wallet } from "../../../../wallets/interfaces/wallet.js";
|
||||
import {
|
||||
getLastConnectedChain,
|
||||
@@ -68,8 +70,13 @@ export function useAutoConnect(props: AutoConnectProps) {
|
||||
});
|
||||
}
|
||||
|
||||
const availableWallets = [
|
||||
...wallets,
|
||||
...getInstalledWalletProviders().map((p) => createWallet(p.info.rdns)),
|
||||
];
|
||||
const activeWallet =
|
||||
lastActiveWalletId && wallets.find((w) => w.id === lastActiveWalletId);
|
||||
lastActiveWalletId &&
|
||||
availableWallets.find((w) => w.id === lastActiveWalletId);
|
||||
|
||||
if (activeWallet) {
|
||||
try {
|
||||
|
||||
@@ -5,7 +5,6 @@ import { sendTransaction } from "../../../../transaction/actions/send-transactio
|
||||
import type { WaitForReceiptOptions } from "../../../../transaction/actions/wait-for-tx-receipt.js";
|
||||
import type { PreparedTransaction } from "../../../../transaction/prepare-transaction.js";
|
||||
import { resolvePromisedValue } from "../../../../utils/promise/resolve-promised-value.js";
|
||||
import type { Account } from "../../../../wallets/interfaces/wallet.js";
|
||||
import {
|
||||
type GetWalletBalanceResult,
|
||||
getWalletBalance,
|
||||
@@ -98,15 +97,14 @@ export function useSendTransactionCore(
|
||||
return;
|
||||
}
|
||||
|
||||
// buy supported, check if there is enouch balance - if not show modal to buy tokens
|
||||
|
||||
// buy supported, check if there is enough balance - if not show modal to buy tokens
|
||||
const [walletBalance, totalCostWei] = await Promise.all([
|
||||
getWalletBalance({
|
||||
address: account.address,
|
||||
chain: tx.chain,
|
||||
client: tx.client,
|
||||
}),
|
||||
getTotalTxCostForBuy(tx, account),
|
||||
getTotalTxCostForBuy(tx, account?.address),
|
||||
]);
|
||||
|
||||
const walletBalanceWei = walletBalance.value;
|
||||
@@ -140,18 +138,31 @@ export function useSendTransactionCore(
|
||||
|
||||
export async function getTotalTxCostForBuy(
|
||||
tx: PreparedTransaction,
|
||||
account?: Account,
|
||||
from?: string,
|
||||
) {
|
||||
const gasCost = await estimateGasCost({
|
||||
transaction: tx,
|
||||
account,
|
||||
});
|
||||
try {
|
||||
const gasCost = await estimateGasCost({
|
||||
transaction: tx,
|
||||
from,
|
||||
});
|
||||
|
||||
const bufferCost = gasCost.wei / 10n;
|
||||
const bufferCost = gasCost.wei / 10n;
|
||||
|
||||
// Note: get tx.value AFTER estimateGasCost
|
||||
const txValue = await resolvePromisedValue(tx.value);
|
||||
// Note: get tx.value AFTER estimateGasCost
|
||||
const txValue = await resolvePromisedValue(tx.value);
|
||||
|
||||
// add 10% extra gas cost to the estimate to ensure user buys enough to cover the tx cost
|
||||
return gasCost.wei + bufferCost + (txValue || 0n);
|
||||
// add 10% extra gas cost to the estimate to ensure user buys enough to cover the tx cost
|
||||
return gasCost.wei + bufferCost + (txValue || 0n);
|
||||
} catch (e) {
|
||||
if (from) {
|
||||
// try again without passing from
|
||||
return await getTotalTxCostForBuy(tx);
|
||||
}
|
||||
// fallback if both fail, use the tx value + 1% buffer
|
||||
const value = await resolvePromisedValue(tx.value);
|
||||
if (!value) {
|
||||
return 0n;
|
||||
}
|
||||
return value + value / 100n;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { renderHook, waitFor } from "@testing-library/react";
|
||||
import type { ReactNode } from "react";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { ANVIL_CHAIN } from "~test/chains.js";
|
||||
import { TEST_CONTRACT_URI } from "~test/ipfs-uris.js";
|
||||
import { TEST_CLIENT } from "~test/test-clients.js";
|
||||
import { TEST_ACCOUNT_A } from "~test/test-wallets.js";
|
||||
import { getContract } from "../../../../contract/contract.js";
|
||||
import { mintTo } from "../../../../extensions/erc20/write/mintTo.js";
|
||||
import { deployERC20Contract } from "../../../../extensions/prebuilts/deploy-erc20.js";
|
||||
import { sendAndConfirmTransaction } from "../../../../transaction/actions/send-and-confirm-transaction.js";
|
||||
import { useWalletBalance } from "./useWalletBalance.js";
|
||||
|
||||
const createWrapper = () => {
|
||||
const queryClient = new QueryClient();
|
||||
return ({ children }: { children: ReactNode }) => (
|
||||
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
||||
);
|
||||
};
|
||||
|
||||
describe.runIf(process.env.TW_SECRET_KEY)("useWalletBalance", () => {
|
||||
it("should return the correct balance for erc20", async () => {
|
||||
const erc20Address = await deployERC20Contract({
|
||||
client: TEST_CLIENT,
|
||||
chain: ANVIL_CHAIN,
|
||||
account: TEST_ACCOUNT_A,
|
||||
type: "TokenERC20",
|
||||
params: {
|
||||
name: "",
|
||||
contractURI: TEST_CONTRACT_URI,
|
||||
},
|
||||
});
|
||||
const erc20Contract = getContract({
|
||||
chain: ANVIL_CHAIN,
|
||||
client: TEST_CLIENT,
|
||||
address: erc20Address,
|
||||
});
|
||||
|
||||
const amount = 1000;
|
||||
|
||||
// Mint some tokens
|
||||
const tx = mintTo({
|
||||
contract: erc20Contract,
|
||||
to: TEST_ACCOUNT_A.address,
|
||||
amount,
|
||||
});
|
||||
|
||||
await sendAndConfirmTransaction({
|
||||
transaction: tx,
|
||||
account: TEST_ACCOUNT_A,
|
||||
});
|
||||
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useWalletBalance({
|
||||
chain: ANVIL_CHAIN,
|
||||
address: TEST_ACCOUNT_A.address,
|
||||
client: TEST_CLIENT,
|
||||
tokenAddress: erc20Address,
|
||||
}),
|
||||
{
|
||||
wrapper: createWrapper(),
|
||||
},
|
||||
);
|
||||
|
||||
const defaultDecimal = 18;
|
||||
|
||||
await waitFor(() => expect(result.current.isSuccess).toBe(true));
|
||||
expect(result.current.data).toBeDefined();
|
||||
expect(result.current.data?.decimals).toBe(defaultDecimal);
|
||||
expect(result.current.data?.symbol).toBeDefined();
|
||||
expect(result.current.data?.name).toBeDefined();
|
||||
expect(result.current.data?.displayValue).toBe(amount.toString());
|
||||
expect(result.current.data?.value).toBe(
|
||||
BigInt(amount) * 10n ** BigInt(defaultDecimal),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
*
|
||||
* const { data, isLoading, isError } = useWalletBalance({ chain, address, client, tokenAddress });
|
||||
* ```
|
||||
* @wallet
|
||||
*/
|
||||
export function useWalletBalance(options: Partial<GetWalletBalanceOptions>) {
|
||||
const { chain, address, tokenAddress, client } = options;
|
||||
|
||||
@@ -35,6 +35,7 @@ export type BuyHistoryQueryOptions = Omit<
|
||||
* return <div> ... </div>
|
||||
* }
|
||||
* ```
|
||||
* @buyCrypto
|
||||
*/
|
||||
export function useBuyHistory(
|
||||
params?: BuyHistoryParams,
|
||||
|
||||
@@ -34,7 +34,7 @@ export type BuyWithFiatHistoryQueryOptions = Omit<
|
||||
* return <div> ... </div>
|
||||
* }
|
||||
* ```
|
||||
* @buyFiat
|
||||
* @buyCrypto
|
||||
*/
|
||||
export function useBuyWithFiatHistory(
|
||||
params?: BuyWithFiatHistoryParams,
|
||||
|
||||
@@ -60,7 +60,7 @@ export type BuyWithFiatQuoteQueryOptions = Omit<
|
||||
* );
|
||||
* }
|
||||
* ```
|
||||
* @buyFiat
|
||||
* @buyCrypto
|
||||
*/
|
||||
export function useBuyWithFiatQuote(
|
||||
params?: GetBuyWithFiatQuoteParams,
|
||||
|
||||
@@ -31,7 +31,7 @@ import {
|
||||
* return <div>...</div>;
|
||||
* }
|
||||
* ```
|
||||
* @buyFiat
|
||||
* @buyCrypto
|
||||
*/
|
||||
export function useBuyWithFiatStatus(
|
||||
params?: GetBuyWithFiatStatusParams,
|
||||
|
||||
@@ -32,7 +32,7 @@ export type PostOnRampQuoteQueryOptions = Omit<
|
||||
*
|
||||
* @param params - object of type [`GetPostOnRampQuoteParams`](https://portal.thirdweb.com/references/typescript/v5/GetPostOnRampQuoteParams)
|
||||
* @returns Object of type [`BuyWithCryptoQuote`](https://portal.thirdweb.com/references/typescript/v5/BuyWithCryptoQuote) which contains the information about the quote such as processing fees, estimated time, converted token amounts, etc.
|
||||
* @buyFiat
|
||||
* @buyCrypto
|
||||
*/
|
||||
export function usePostOnRampQuote(
|
||||
params?: GetPostOnRampQuoteParams,
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import {
|
||||
type UseQueryOptions,
|
||||
type UseQueryResult,
|
||||
useQuery,
|
||||
} from "@tanstack/react-query";
|
||||
import type { ThirdwebClient } from "../../../../client/client.js";
|
||||
import { getCallsStatus } from "../../../../wallets/eip5792/get-calls-status.js";
|
||||
import type { GetCallsStatusResponse } from "../../../../wallets/eip5792/types.js";
|
||||
import { useActiveWallet } from "./wallet-hooks.js";
|
||||
|
||||
/**
|
||||
* A hook to get a call bundle's current status according to [EIP-5792](https://eips.ethereum.org/EIPS/eip-5792).
|
||||
*
|
||||
* @note This function is dependent on the wallet's support for EIP-5792 and could throw an error if it's not supported.
|
||||
*
|
||||
* @returns a React Query object.
|
||||
* @beta
|
||||
* @example
|
||||
* ```tsx
|
||||
* import { useCallsStatus } from "thirdweb/react";
|
||||
* const { data: status, isLoading } = useCallsStatus({ bundleId, client });
|
||||
* ```
|
||||
* @extension EIP5792
|
||||
*/
|
||||
export function useCallsStatus(options: {
|
||||
bundleId: string;
|
||||
client: ThirdwebClient;
|
||||
queryOptions?: Pick<UseQueryOptions, "enabled" | "retry">;
|
||||
}): UseQueryResult<GetCallsStatusResponse> {
|
||||
const { client, bundleId } = options;
|
||||
const wallet = useActiveWallet();
|
||||
|
||||
return useQuery({
|
||||
queryKey: [
|
||||
"getCapabilities",
|
||||
wallet?.getChain()?.id || -1,
|
||||
wallet?.id,
|
||||
] as const,
|
||||
queryFn: async () => {
|
||||
if (!wallet) {
|
||||
throw new Error("Failed to get calls status, no wallet connected");
|
||||
}
|
||||
return getCallsStatus({ wallet, client, bundleId });
|
||||
},
|
||||
retry: false,
|
||||
enabled: !!bundleId && wallet !== undefined,
|
||||
...options.queryOptions,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import {
|
||||
type UseQueryOptions,
|
||||
type UseQueryResult,
|
||||
useQuery,
|
||||
} from "@tanstack/react-query";
|
||||
import {
|
||||
type GetCapabilitiesResult,
|
||||
getCapabilities,
|
||||
} from "../../../../wallets/eip5792/get-capabilities.js";
|
||||
import { useActiveWallet } from "./wallet-hooks.js";
|
||||
|
||||
/**
|
||||
* A hook to get the current wallet's capabilities according to [EIP-5792](https://eips.ethereum.org/EIPS/eip-5792).
|
||||
*
|
||||
* @note This function is dependent on the wallet's support for EIP-5792, but will not throw.
|
||||
* **The returned object contains a `message` field detailing any issues with the wallet's support for EIP-5792.**
|
||||
*
|
||||
* @returns a React Query object.
|
||||
* @beta
|
||||
* @example
|
||||
* ```tsx
|
||||
* import { useCapabilities } from "thirdweb/react";
|
||||
* const { data: capabilities, isLoading } = useCapabilities();
|
||||
* ```
|
||||
* @extension EIP5792
|
||||
*/
|
||||
export function useCapabilities(options?: {
|
||||
queryOptions?: Pick<UseQueryOptions, "enabled" | "retry">;
|
||||
}): UseQueryResult<GetCapabilitiesResult> {
|
||||
const wallet = useActiveWallet();
|
||||
|
||||
return useQuery({
|
||||
queryKey: [
|
||||
"getCapabilities",
|
||||
wallet?.getChain()?.id || -1,
|
||||
wallet?.id,
|
||||
] as const,
|
||||
queryFn: async () => {
|
||||
if (!wallet) {
|
||||
return {
|
||||
message: "Can't get capabilities, no wallet connected",
|
||||
} as const;
|
||||
}
|
||||
return getCapabilities({ wallet });
|
||||
},
|
||||
retry: false,
|
||||
...options?.queryOptions,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
import {
|
||||
type UseMutationResult,
|
||||
useMutation,
|
||||
useQueryClient,
|
||||
} from "@tanstack/react-query";
|
||||
import type { ThirdwebClient } from "../../../../client/client.js";
|
||||
import {
|
||||
type SendCallsOptions,
|
||||
sendCalls,
|
||||
} from "../../../../wallets/eip5792/send-calls.js";
|
||||
import type {
|
||||
GetCallsStatusResponse,
|
||||
WalletSendCallsId,
|
||||
} from "../../../../wallets/eip5792/types.js";
|
||||
import { waitForBundle } from "../../../../wallets/eip5792/wait-for-bundle.js";
|
||||
import type { Wallet } from "../../../../wallets/interfaces/wallet.js";
|
||||
import { invalidateWalletBalance } from "../../providers/invalidateWalletBalance.js";
|
||||
import { useActiveWallet } from "../wallets/wallet-hooks.js";
|
||||
|
||||
/**
|
||||
* A hook to send [EIP-5792](https://eips.ethereum.org/EIPS/eip-5792) calls to a wallet.
|
||||
* This hook works with all Thirdweb wallets (in-app and smart) and certain injected wallets that already support EIP-5792.
|
||||
* Transactions will be bundled and sponsored when those capabilities are supported, otherwise they will be sent as individual transactions.
|
||||
*
|
||||
* When calls are sent, all contracts that are interacted with will have their corresponding reads revalidated via React Query.
|
||||
*
|
||||
* @note This hook is dependent on the wallet's support for EIP-5792 and could fail.
|
||||
* @note The mutatuon function will use your currently connected wallet by default, but you can pass it a specific wallet to use if you'd like.
|
||||
*
|
||||
* @returns A React Query mutatuon object to interact with {@link sendCalls}
|
||||
* @throws an error if the wallet does not support EIP-5792.
|
||||
* @returns The ID of the bundle of the calls.
|
||||
*
|
||||
* @beta
|
||||
* @example
|
||||
* ```tsx
|
||||
* import { useSendCalls } from "thirdweb/react";
|
||||
*
|
||||
* const sendTx1 = approve({
|
||||
contract: USDT_CONTRACT,
|
||||
amount: 100,
|
||||
spender: "0x33d9B8BEfE81027E2C859EDc84F5636cbb202Ed6",
|
||||
});
|
||||
* const sendTx2 = approve({
|
||||
contract: USDT_CONTRACT,
|
||||
amount: 100,
|
||||
spender: "0x2a4f24F935Eb178e3e7BA9B53A5Ee6d8407C0709",
|
||||
});
|
||||
* const { mutate: sendCalls, data: bundleId } = useSendCalls({ client });
|
||||
* await sendCalls({
|
||||
* wallet,
|
||||
* client,
|
||||
* calls: [sendTx1, sendTx2],
|
||||
* });
|
||||
* ```
|
||||
* Await the bundle's full confirmation:
|
||||
* ```tsx
|
||||
* const { mutate: sendCalls, data: bundleId } = useSendCalls({ client, waitForResult: true });
|
||||
* await sendCalls({
|
||||
* wallet,
|
||||
* client,
|
||||
* calls: [sendTx1, sendTx2],
|
||||
* });
|
||||
* ```
|
||||
* Sponsor transactions with a paymaster:
|
||||
* ```ts
|
||||
* const { mutate: sendCalls, data: bundleId } = useSendCalls();
|
||||
* await sendCalls({
|
||||
* client,
|
||||
* calls: [sendTx1, sendTx2],
|
||||
* capabilities: {
|
||||
* paymasterService: {
|
||||
* url: `https://${CHAIN.id}.bundler.thirdweb.com/${client.clientId}`
|
||||
* }
|
||||
* }
|
||||
* });
|
||||
* @note We recommend proxying any paymaster calls via an API route you setup and control.
|
||||
* ```
|
||||
* @extension EIP5792
|
||||
*/
|
||||
export function useSendCalls({
|
||||
client,
|
||||
waitForResult = true,
|
||||
}: { client: ThirdwebClient; waitForResult?: boolean }): UseMutationResult<
|
||||
GetCallsStatusResponse | WalletSendCallsId,
|
||||
Error,
|
||||
Omit<SendCallsOptions, "chain" | "wallet"> & { wallet?: Wallet } // Optional wallet override
|
||||
> {
|
||||
const connectedWallet = useActiveWallet();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (options) => {
|
||||
const { wallet = connectedWallet } = options;
|
||||
const chain = wallet?.getChain();
|
||||
if (!wallet || !chain) {
|
||||
throw new Error(
|
||||
"Failed to send transactions, no connected wallet found.",
|
||||
);
|
||||
}
|
||||
|
||||
const callsPromise = sendCalls({ ...options, wallet });
|
||||
if (!waitForResult) {
|
||||
return callsPromise;
|
||||
}
|
||||
|
||||
const result = await waitForBundle({
|
||||
bundleId: await callsPromise,
|
||||
wallet,
|
||||
client,
|
||||
chain,
|
||||
});
|
||||
return result;
|
||||
},
|
||||
onSettled: async (_result, _error, variables) => {
|
||||
// Attempt to invalidate any reads related to the sent transactions
|
||||
const chain = connectedWallet?.getChain();
|
||||
if (!_result || !connectedWallet || !chain) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof _result === "string") {
|
||||
await waitForBundle({
|
||||
bundleId: _result,
|
||||
wallet: connectedWallet,
|
||||
client,
|
||||
chain,
|
||||
}).catch((error) => {
|
||||
console.error(
|
||||
"Failed to confirm sent bundle and invalidate queries",
|
||||
_result,
|
||||
error,
|
||||
);
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
|
||||
for (const call of variables.calls) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [
|
||||
"readContract",
|
||||
call.__contract?.chain.id,
|
||||
call.__contract?.address,
|
||||
],
|
||||
});
|
||||
}
|
||||
invalidateWalletBalance(queryClient, chain.id);
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { QueryClient } from "@tanstack/react-query";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { invalidateWalletBalance } from "./invalidateWalletBalance.js";
|
||||
|
||||
const chainId = 1;
|
||||
|
||||
const queryClient = {
|
||||
invalidateQueries: vi.fn(),
|
||||
} as unknown as QueryClient;
|
||||
|
||||
describe("invalidateWalletBalance", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("should call invalidateQueries when chainId is provided", () => {
|
||||
invalidateWalletBalance(queryClient, chainId);
|
||||
|
||||
expect(queryClient.invalidateQueries).toHaveBeenCalledWith({
|
||||
queryKey: ["walletBalance", chainId] as const,
|
||||
});
|
||||
});
|
||||
|
||||
it("should call invalidateQueries when chainId is not provided", () => {
|
||||
invalidateWalletBalance(queryClient);
|
||||
|
||||
expect(queryClient.invalidateQueries).toHaveBeenCalledWith({
|
||||
queryKey: ["walletBalance", undefined] as const,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { shortenString } from "./addresses.js";
|
||||
|
||||
const testAddress = "0x0000000000000000000000000000000000000000";
|
||||
|
||||
describe("react: addresses test", () => {
|
||||
it("should shorten string <extraShort === true>", () => {
|
||||
expect(shortenString(testAddress, true)).toBe("0x00...000");
|
||||
});
|
||||
|
||||
it("should shorten string <extraShort === false>", () => {
|
||||
expect(shortenString(testAddress, false)).toBe("0x0000...0000");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,65 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { timeoutPromise } from "./timeoutPromise.js";
|
||||
|
||||
const options = {
|
||||
ms: 100,
|
||||
message: "Timeout",
|
||||
};
|
||||
|
||||
describe("timeoutPromise", () => {
|
||||
it("should resolve if the promise resolves before the timeout", async () => {
|
||||
const mockPromise = new Promise((resolve) => {
|
||||
setTimeout(() => resolve("success"), 50);
|
||||
});
|
||||
const result = await timeoutPromise(mockPromise, options);
|
||||
expect(result).toBe("success");
|
||||
});
|
||||
|
||||
it("should reject if the promise rejects before the timeout", async () => {
|
||||
const mockPromise = new Promise((_, reject) => {
|
||||
setTimeout(() => reject(new Error("failure")), 50);
|
||||
});
|
||||
|
||||
try {
|
||||
await timeoutPromise(mockPromise, options);
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
expect((error as Error).message).toBe("failure");
|
||||
}
|
||||
});
|
||||
|
||||
it("rejects with the timeout message if the promise took longer than the allowed time", async () => {
|
||||
const mockPromise = new Promise((resolve) => {
|
||||
setTimeout(() => resolve("success"), 1000);
|
||||
});
|
||||
|
||||
try {
|
||||
await timeoutPromise(mockPromise, options);
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
expect((error as Error).message).toBe("Timeout");
|
||||
}
|
||||
});
|
||||
|
||||
it("cleans up the timeout when the promise resolves", async () => {
|
||||
const clearTimeoutSpy = vi.spyOn(global, "clearTimeout");
|
||||
const mockPromise = new Promise((resolve) => {
|
||||
setTimeout(() => resolve("success"), 50);
|
||||
});
|
||||
await timeoutPromise(mockPromise, options);
|
||||
expect(clearTimeoutSpy).toHaveBeenCalled();
|
||||
clearTimeoutSpy.mockRestore();
|
||||
});
|
||||
|
||||
it("cleans up the timeout when the promise rejects", async () => {
|
||||
const clearTimeoutSpy = vi.spyOn(global, "clearTimeout");
|
||||
const mockPromise = new Promise((_, reject) => {
|
||||
setTimeout(() => reject(new Error("failure")), 50);
|
||||
});
|
||||
try {
|
||||
await timeoutPromise(mockPromise, options);
|
||||
} catch (error) {}
|
||||
expect(clearTimeoutSpy).toHaveBeenCalled();
|
||||
clearTimeoutSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { wait } from "./wait.js";
|
||||
|
||||
describe("wait", () => {
|
||||
it("should wait", async () => {
|
||||
const start = Date.now();
|
||||
const waitTime = 1500;
|
||||
await wait(waitTime);
|
||||
const end = Date.now();
|
||||
const elapsedTime = end - start;
|
||||
expect(elapsedTime).toBeGreaterThanOrEqual(waitTime);
|
||||
});
|
||||
});
|
||||
@@ -38,7 +38,10 @@ export function useBuyTxStates(options: {
|
||||
}
|
||||
|
||||
try {
|
||||
const totalCost = await getTotalTxCostForBuy(buyForTx.tx, account);
|
||||
const totalCost = await getTotalTxCostForBuy(
|
||||
buyForTx.tx,
|
||||
account?.address,
|
||||
);
|
||||
|
||||
if (!mounted) {
|
||||
return;
|
||||
|
||||
@@ -42,7 +42,7 @@ import type { WelcomeScreen } from "./screens/types.js";
|
||||
* The returned `connect` method takes an object of type [UseConnectModalOptions](https://portal.thirdweb.com/references/typescript/v5/ConnectButtonProps)
|
||||
* as an argument to customize the Connect Modal UI. Refer to [UseConnectModalOptions](https://portal.thirdweb.com/references/typescript/v5/ConnectButtonProps) to see the available options.
|
||||
*
|
||||
* @connectWallet
|
||||
* @walletConnection
|
||||
*/
|
||||
export function useConnectModal() {
|
||||
const setRootEl = useContext(SetRootElementContext);
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { WalletId } from "../../../../wallets/wallet-types.js";
|
||||
|
||||
/**
|
||||
* Returns the wallet info for the provided wallet id.
|
||||
* @wallet
|
||||
*/
|
||||
export function useWalletInfo(id: WalletId) {
|
||||
return useQuery<WalletInfo>({
|
||||
@@ -18,6 +19,7 @@ export function useWalletInfo(id: WalletId) {
|
||||
|
||||
/**
|
||||
* Returns the wallet image for the provided wallet id.
|
||||
* @wallet
|
||||
*/
|
||||
export function useWalletImage(id: WalletId) {
|
||||
return useQuery({
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { wideModalScreenThreshold } from "../ui/ConnectWallet/constants.js";
|
||||
import { canFitWideModal } from "./canFitWideModal.js";
|
||||
|
||||
describe("canFitWideModal", () => {
|
||||
it("should return true if window width is >= wideModalScreenThreshold", () => {
|
||||
// Mock the window object and set innerWidth
|
||||
Object.defineProperty(window, "innerWidth", {
|
||||
writable: true,
|
||||
value: wideModalScreenThreshold,
|
||||
});
|
||||
expect(canFitWideModal()).toBe(true);
|
||||
window.innerWidth = wideModalScreenThreshold + 1;
|
||||
expect(canFitWideModal()).toBe(true);
|
||||
});
|
||||
|
||||
it("returns false if window width is < wideModalScreenThreshold", () => {
|
||||
// Mock the window object and set innerWidth
|
||||
Object.defineProperty(window, "innerWidth", {
|
||||
writable: true,
|
||||
value: wideModalScreenThreshold - 1,
|
||||
});
|
||||
expect(canFitWideModal()).toBe(false);
|
||||
});
|
||||
|
||||
it("returns false if window is undefined", () => {
|
||||
// Mock the window object to be undefined
|
||||
const originalWindow = global.window;
|
||||
// @ts-ignore - Not sure if this is the best way to mock the undefined `window` object
|
||||
global.window = undefined;
|
||||
expect(canFitWideModal()).toBe(false);
|
||||
global.window = originalWindow;
|
||||
});
|
||||
});
|
||||
@@ -16,7 +16,7 @@ const injectedWalletLocaleEn = (wallet: string): InjectedWalletLocale => ({
|
||||
scanScreen: {
|
||||
instruction: `Scan the QR code with the ${wallet} app to connect`,
|
||||
},
|
||||
getStartedLink: `Don't have a ${wallet} wallet?`,
|
||||
getStartedLink: `Don't have ${wallet}?`,
|
||||
download: {
|
||||
chrome: "Download Chrome Extension",
|
||||
android: "Download on Google Play",
|
||||
|
||||
@@ -32,6 +32,7 @@ function createBlockNumberPoller(
|
||||
client: ThirdwebClient,
|
||||
chain: Chain,
|
||||
overPollRatio?: number,
|
||||
onError?: (error: Error) => void,
|
||||
) {
|
||||
let subscribers: Array<(blockNumber: bigint) => void> = [];
|
||||
let blockTimesWindow: number[] = [];
|
||||
@@ -51,35 +52,48 @@ function createBlockNumberPoller(
|
||||
if (!isActive) {
|
||||
return;
|
||||
}
|
||||
const blockNumber = await eth_blockNumber(rpcRequest);
|
||||
|
||||
if (!lastBlockNumber || blockNumber > lastBlockNumber) {
|
||||
let newBlockNumbers = [];
|
||||
if (lastBlockNumber) {
|
||||
for (let i = lastBlockNumber + 1n; i <= blockNumber; i++) {
|
||||
newBlockNumbers.push(BigInt(i));
|
||||
try {
|
||||
const blockNumber = await eth_blockNumber(rpcRequest);
|
||||
|
||||
if (!lastBlockNumber || blockNumber > lastBlockNumber) {
|
||||
let newBlockNumbers = [];
|
||||
if (lastBlockNumber) {
|
||||
for (let i = lastBlockNumber + 1n; i <= blockNumber; i++) {
|
||||
newBlockNumbers.push(BigInt(i));
|
||||
}
|
||||
} else {
|
||||
newBlockNumbers = [blockNumber];
|
||||
}
|
||||
lastBlockNumber = blockNumber;
|
||||
const currentTime = new Date().getTime();
|
||||
if (lastBlockAt) {
|
||||
// if we skipped a block we need to adjust the block time down to that level
|
||||
const blockTime =
|
||||
(currentTime - lastBlockAt) / newBlockNumbers.length;
|
||||
|
||||
blockTimesWindow.push(blockTime);
|
||||
blockTimesWindow = blockTimesWindow.slice(-SLIDING_WINDOW_SIZE);
|
||||
}
|
||||
lastBlockAt = currentTime;
|
||||
// for all new blockNumbers...
|
||||
for (const b of newBlockNumbers) {
|
||||
// ... call all current subscribers
|
||||
for (const subscriberCallback of subscribers) {
|
||||
subscriberCallback(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err: unknown) {
|
||||
if (onError) {
|
||||
onError(err as Error);
|
||||
} else {
|
||||
newBlockNumbers = [blockNumber];
|
||||
}
|
||||
lastBlockNumber = blockNumber;
|
||||
const currentTime = new Date().getTime();
|
||||
if (lastBlockAt) {
|
||||
// if we skipped a block we need to adjust the block time down to that level
|
||||
const blockTime = (currentTime - lastBlockAt) / newBlockNumbers.length;
|
||||
|
||||
blockTimesWindow.push(blockTime);
|
||||
blockTimesWindow = blockTimesWindow.slice(-SLIDING_WINDOW_SIZE);
|
||||
}
|
||||
lastBlockAt = currentTime;
|
||||
// for all new blockNumbers...
|
||||
for (const b of newBlockNumbers) {
|
||||
// ... call all current subscribers
|
||||
for (const subscriberCallback of subscribers) {
|
||||
subscriberCallback(b);
|
||||
}
|
||||
console.error(
|
||||
`[watchBlockNumber]: Failed to poll for latest block number: ${err}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const currentApproximateBlockTime = getAverageBlockTime(blockTimesWindow);
|
||||
|
||||
// make sure we never poll faster than our minimum poll delay or slower than our maximum poll delay
|
||||
@@ -141,6 +155,7 @@ export type WatchBlockNumberOptions = {
|
||||
client: ThirdwebClient;
|
||||
chain: Chain;
|
||||
onNewBlockNumber: (blockNumber: bigint) => void;
|
||||
onError?: (error: Error) => void;
|
||||
overPollRatio?: number;
|
||||
latestBlockNumber?: bigint;
|
||||
};
|
||||
@@ -157,7 +172,10 @@ export type WatchBlockNumberOptions = {
|
||||
* chainId,
|
||||
* onNewBlockNumber: (blockNumber) => {
|
||||
* // do something with the block number
|
||||
* },
|
||||
* },
|
||||
* onError: (err) => {
|
||||
* // do something if getting the block number fails
|
||||
* },
|
||||
* });
|
||||
*
|
||||
* // later stop watching
|
||||
@@ -166,14 +184,20 @@ export type WatchBlockNumberOptions = {
|
||||
* @rpc
|
||||
*/
|
||||
export function watchBlockNumber(opts: WatchBlockNumberOptions) {
|
||||
const { client, chain, onNewBlockNumber, overPollRatio, latestBlockNumber } =
|
||||
opts;
|
||||
const {
|
||||
client,
|
||||
chain,
|
||||
onNewBlockNumber,
|
||||
overPollRatio,
|
||||
latestBlockNumber,
|
||||
onError,
|
||||
} = opts;
|
||||
const chainId = chain.id;
|
||||
// if we already have a poller for this chainId -> use it
|
||||
// if we already have a poller for this chainId -> use it.
|
||||
let poller = existingPollers.get(chainId);
|
||||
// otherwise create a new poller
|
||||
if (!poller) {
|
||||
poller = createBlockNumberPoller(client, chain, overPollRatio);
|
||||
poller = createBlockNumberPoller(client, chain, overPollRatio, onError);
|
||||
// and store it for later use
|
||||
existingPollers.set(chainId, poller);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,9 @@ import type { PreparedTransaction } from "../prepare-transaction.js";
|
||||
|
||||
export type EstimateGasOptions = Prettify<
|
||||
{
|
||||
transaction: PreparedTransaction;
|
||||
// TODO: update this to `Transaction<"prepared">` once the type is available to ensure only prepared transactions are accepted
|
||||
// biome-ignore lint/suspicious/noExplicitAny: library function that accepts any prepared transaction type
|
||||
transaction: PreparedTransaction<any>;
|
||||
} & (
|
||||
| {
|
||||
account: Account;
|
||||
@@ -23,7 +25,10 @@ export type EstimateGasOptions = Prettify<
|
||||
|
||||
export type EstimateGasResult = bigint;
|
||||
|
||||
const cache = new WeakMap<PreparedTransaction, Promise<EstimateGasResult>>();
|
||||
const cache = new WeakMap<
|
||||
PreparedTransaction & { from: string | undefined },
|
||||
Promise<EstimateGasResult>
|
||||
>();
|
||||
|
||||
/**
|
||||
* Estimates the gas required to execute a transaction. The gas is returned as a `bigint` and in gwei units.
|
||||
@@ -41,9 +46,15 @@ const cache = new WeakMap<PreparedTransaction, Promise<EstimateGasResult>>();
|
||||
export async function estimateGas(
|
||||
options: EstimateGasOptions,
|
||||
): Promise<EstimateGasResult> {
|
||||
if (cache.has(options.transaction)) {
|
||||
// from is:
|
||||
// 1. the user specified from address
|
||||
// 2. the passed in account address
|
||||
// 3. the passed in wallet's account address
|
||||
const from = options.from ?? options.account?.address ?? undefined;
|
||||
const txWithFrom = { ...options.transaction, from };
|
||||
if (cache.has(txWithFrom)) {
|
||||
// biome-ignore lint/style/noNonNullAssertion: the `has` above ensures that this will always be set
|
||||
return cache.get(options.transaction)!;
|
||||
return cache.get(txWithFrom)!;
|
||||
}
|
||||
const { account } = options;
|
||||
const promise = (async () => {
|
||||
@@ -84,11 +95,6 @@ export async function estimateGas(
|
||||
]);
|
||||
|
||||
const rpcRequest = getRpcClient(options.transaction);
|
||||
// from is:
|
||||
// 1. the user specified from address
|
||||
// 2. the passed in account address
|
||||
// 3. the passed in wallet's account address
|
||||
const from = options.from ?? options.account?.address ?? undefined;
|
||||
try {
|
||||
let gas = await eth_estimateGas(
|
||||
rpcRequest,
|
||||
@@ -110,6 +116,6 @@ export async function estimateGas(
|
||||
});
|
||||
}
|
||||
})();
|
||||
cache.set(options.transaction, promise);
|
||||
cache.set(txWithFrom, promise);
|
||||
return promise;
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ export type SignTransactionOptions = {
|
||||
* privateKey: "0x...",
|
||||
* });
|
||||
* ```
|
||||
* @transaction
|
||||
*/
|
||||
export function signTransaction({
|
||||
transaction,
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import type { TransactionSerializable } from "viem";
|
||||
import type { Address } from "../../../utils/address.js";
|
||||
import type {
|
||||
EIP721SerializedTransaction,
|
||||
EIP721TransactionOptions,
|
||||
} from "../../prepare-transaction.js";
|
||||
|
||||
export type EIP721TransactionSerializable = TransactionSerializable & {
|
||||
from: Address;
|
||||
} & EIP721TransactionOptions;
|
||||
export const gasPerPubdataDefault = 50000n;
|
||||
|
||||
export const getEip712Domain = (transaction: EIP721TransactionSerializable) => {
|
||||
const message = transactionToMessage(transaction);
|
||||
|
||||
return {
|
||||
domain: {
|
||||
name: "zkSync",
|
||||
version: "2",
|
||||
chainId: transaction.chainId,
|
||||
},
|
||||
types: {
|
||||
Transaction: [
|
||||
{ name: "txType", type: "uint256" },
|
||||
{ name: "from", type: "uint256" },
|
||||
{ name: "to", type: "uint256" },
|
||||
{ name: "gasLimit", type: "uint256" },
|
||||
{ name: "gasPerPubdataByteLimit", type: "uint256" },
|
||||
{ name: "maxFeePerGas", type: "uint256" },
|
||||
{ name: "maxPriorityFeePerGas", type: "uint256" },
|
||||
{ name: "paymaster", type: "uint256" },
|
||||
{ name: "nonce", type: "uint256" },
|
||||
{ name: "value", type: "uint256" },
|
||||
{ name: "data", type: "bytes" },
|
||||
{ name: "factoryDeps", type: "bytes32[]" },
|
||||
{ name: "paymasterInput", type: "bytes" },
|
||||
],
|
||||
},
|
||||
primaryType: "Transaction",
|
||||
message: message,
|
||||
};
|
||||
};
|
||||
|
||||
function transactionToMessage(
|
||||
transaction: EIP721TransactionSerializable,
|
||||
): EIP721SerializedTransaction {
|
||||
const {
|
||||
gas,
|
||||
nonce,
|
||||
to,
|
||||
from,
|
||||
value,
|
||||
maxFeePerGas,
|
||||
maxPriorityFeePerGas,
|
||||
paymaster,
|
||||
paymasterInput,
|
||||
gasPerPubdata,
|
||||
data,
|
||||
} = transaction;
|
||||
|
||||
return {
|
||||
txType: 113n,
|
||||
from: BigInt(from),
|
||||
to: to ? BigInt(to) : 0n,
|
||||
gasLimit: gas ?? 0n,
|
||||
gasPerPubdataByteLimit: gasPerPubdata ?? gasPerPubdataDefault,
|
||||
maxFeePerGas: maxFeePerGas ?? 0n,
|
||||
maxPriorityFeePerGas: maxPriorityFeePerGas ?? 0n,
|
||||
paymaster: paymaster ? BigInt(paymaster) : 0n,
|
||||
nonce: nonce ? BigInt(nonce) : 0n,
|
||||
value: value ?? 0n,
|
||||
data: data ? data : "0x0",
|
||||
// TODO suport factoryDeps
|
||||
factoryDeps: [],
|
||||
paymasterInput: paymasterInput ? paymasterInput : "0x",
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { TEST_CLIENT } from "../../../../test/src/test-clients.js";
|
||||
import {
|
||||
TEST_ACCOUNT_A,
|
||||
TEST_ACCOUNT_B,
|
||||
} from "../../../../test/src/test-wallets.js";
|
||||
import { defineChain } from "../../../chains/utils.js";
|
||||
import { prepareTransaction } from "../../prepare-transaction.js";
|
||||
import { sendEip712Transaction } from "./send-eip712-transaction.js";
|
||||
|
||||
describe("sendEip712Transaction", () => {
|
||||
// re-enable for testing, but disable for CI since it requires testnet funds
|
||||
it.skip("should send eip712 transaction", async () => {
|
||||
const transaction = prepareTransaction({
|
||||
chain: defineChain(300),
|
||||
client: TEST_CLIENT,
|
||||
value: 0n,
|
||||
to: TEST_ACCOUNT_B.address,
|
||||
eip712: {
|
||||
paymaster: "0xbA226d47Cbb2731CBAA67C916c57d68484AA269F",
|
||||
paymasterInput:
|
||||
"0x8c5a344500000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000",
|
||||
},
|
||||
});
|
||||
const res = await sendEip712Transaction({
|
||||
account: TEST_ACCOUNT_A,
|
||||
transaction,
|
||||
});
|
||||
expect(res.transactionHash.length).toBe(66);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,200 @@
|
||||
import { toRlp } from "viem";
|
||||
import { eth_sendRawTransaction } from "../../../rpc/actions/eth_sendRawTransaction.js";
|
||||
import { getRpcClient } from "../../../rpc/rpc.js";
|
||||
import { toBigInt } from "../../../utils/bigint.js";
|
||||
import { concatHex } from "../../../utils/encoding/helpers/concat-hex.js";
|
||||
import { type Hex, numberToHex, toHex } from "../../../utils/encoding/hex.js";
|
||||
import { resolvePromisedValue } from "../../../utils/promise/resolve-promised-value.js";
|
||||
import type { Account } from "../../../wallets/interfaces/wallet.js";
|
||||
import type { PreparedTransaction } from "../../prepare-transaction.js";
|
||||
import { encode } from "../encode.js";
|
||||
import { toSerializableTransaction } from "../to-serializable-transaction.js";
|
||||
import type { WaitForReceiptOptions } from "../wait-for-tx-receipt.js";
|
||||
import {
|
||||
type EIP721TransactionSerializable,
|
||||
gasPerPubdataDefault,
|
||||
getEip712Domain,
|
||||
} from "./getEip721Domain.js";
|
||||
|
||||
export type SendEip712TransactionOptions = {
|
||||
account: Account;
|
||||
// TODO: update this to `Transaction<"prepared">` once the type is available to ensure only prepared transactions are accepted
|
||||
// biome-ignore lint/suspicious/noExplicitAny: library function that accepts any prepared transaction type
|
||||
transaction: PreparedTransaction<any>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sends a transaction using the provided wallet.
|
||||
* @param options - The options for sending the transaction.
|
||||
* @returns A promise that resolves to the transaction hash.
|
||||
* @throws An error if the wallet is not connected.
|
||||
* @transaction
|
||||
* @example
|
||||
* ```ts
|
||||
* import { sendTransaction } from "thirdweb";
|
||||
*
|
||||
* const { transactionHash } = await sendTransaction({
|
||||
* account,
|
||||
* transaction
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export async function sendEip712Transaction(
|
||||
options: SendEip712TransactionOptions,
|
||||
): Promise<WaitForReceiptOptions> {
|
||||
const { account, transaction } = options;
|
||||
|
||||
const eip712Transaction = await populateEip712Transaction(options);
|
||||
|
||||
const hash = await signEip712Transaction({
|
||||
account,
|
||||
eip712Transaction,
|
||||
chainId: transaction.chain.id,
|
||||
});
|
||||
|
||||
const rpc = getRpcClient(transaction);
|
||||
const result = await eth_sendRawTransaction(rpc, hash);
|
||||
|
||||
return {
|
||||
transactionHash: result,
|
||||
chain: transaction.chain,
|
||||
client: transaction.client,
|
||||
};
|
||||
}
|
||||
|
||||
export async function signEip712Transaction(options: {
|
||||
account: Account;
|
||||
eip712Transaction: EIP721TransactionSerializable;
|
||||
chainId: number;
|
||||
}): Promise<Hex> {
|
||||
const { account, eip712Transaction, chainId } = options;
|
||||
// EIP712 signing of the serialized tx
|
||||
const eip712Domain = getEip712Domain(eip712Transaction);
|
||||
|
||||
const customSignature = await account.signTypedData({
|
||||
// biome-ignore lint/suspicious/noExplicitAny: TODO type properly
|
||||
...(eip712Domain as any),
|
||||
});
|
||||
|
||||
return serializeTransactionEIP712({
|
||||
...eip712Transaction,
|
||||
chainId,
|
||||
customSignature,
|
||||
});
|
||||
}
|
||||
|
||||
export async function populateEip712Transaction(
|
||||
options: SendEip712TransactionOptions,
|
||||
): Promise<EIP721TransactionSerializable> {
|
||||
const { account, transaction } = options;
|
||||
let [
|
||||
data,
|
||||
to,
|
||||
value,
|
||||
gas,
|
||||
maxFeePerGas,
|
||||
maxPriorityFeePerGas,
|
||||
gasPerPubdata,
|
||||
] = await Promise.all([
|
||||
encode(transaction),
|
||||
resolvePromisedValue(transaction.to),
|
||||
resolvePromisedValue(transaction.value),
|
||||
resolvePromisedValue(transaction.gas),
|
||||
resolvePromisedValue(transaction.maxFeePerGas),
|
||||
resolvePromisedValue(transaction.maxPriorityFeePerGas),
|
||||
resolvePromisedValue(transaction.eip712).then(
|
||||
(eip712) => eip712?.gasPerPubdata,
|
||||
),
|
||||
]);
|
||||
if (!gas || !maxFeePerGas || !maxPriorityFeePerGas) {
|
||||
// fetch fees and gas
|
||||
const rpc = getRpcClient(transaction);
|
||||
const result = (await rpc({
|
||||
// biome-ignore lint/suspicious/noExplicitAny: TODO add to RPC method types
|
||||
method: "zks_estimateFee" as any,
|
||||
params: [
|
||||
{
|
||||
from: account.address,
|
||||
to,
|
||||
data,
|
||||
value: value ? numberToHex(value) : undefined,
|
||||
// biome-ignore lint/suspicious/noExplicitAny: TODO add to RPC method types
|
||||
} as any,
|
||||
],
|
||||
})) as {
|
||||
gas_limit: string;
|
||||
max_fee_per_gas: string;
|
||||
max_priority_fee_per_gas: string;
|
||||
gas_per_pubdata_limit: string;
|
||||
};
|
||||
gas = toBigInt(result.gas_limit);
|
||||
const baseFee = toBigInt(result.max_fee_per_gas);
|
||||
maxFeePerGas = baseFee * 2n; // bumping the base fee per gas to ensure fast inclusion
|
||||
maxPriorityFeePerGas = toBigInt(result.max_priority_fee_per_gas) || 1n;
|
||||
gasPerPubdata = toBigInt(result.gas_per_pubdata_limit);
|
||||
}
|
||||
|
||||
// serialize the transaction (with fees, gas, nonce)
|
||||
const serializableTransaction = await toSerializableTransaction({
|
||||
transaction: {
|
||||
...transaction,
|
||||
gas,
|
||||
maxFeePerGas,
|
||||
maxPriorityFeePerGas,
|
||||
},
|
||||
from: account.address,
|
||||
});
|
||||
|
||||
return {
|
||||
...serializableTransaction,
|
||||
...transaction.eip712,
|
||||
gasPerPubdata,
|
||||
from: account.address as Hex,
|
||||
};
|
||||
}
|
||||
|
||||
function serializeTransactionEIP712(
|
||||
transaction: EIP721TransactionSerializable & {
|
||||
chainId: number;
|
||||
customSignature: Hex;
|
||||
},
|
||||
): Hex {
|
||||
const {
|
||||
chainId,
|
||||
gas,
|
||||
nonce,
|
||||
to,
|
||||
from,
|
||||
value,
|
||||
maxFeePerGas,
|
||||
maxPriorityFeePerGas,
|
||||
customSignature,
|
||||
factoryDeps,
|
||||
paymaster,
|
||||
paymasterInput,
|
||||
gasPerPubdata,
|
||||
data,
|
||||
} = transaction;
|
||||
|
||||
const serializedTransaction = [
|
||||
nonce ? toHex(nonce) : "0x",
|
||||
maxPriorityFeePerGas ? toHex(maxPriorityFeePerGas) : "0x",
|
||||
maxFeePerGas ? toHex(maxFeePerGas) : "0x",
|
||||
gas ? toHex(gas) : "0x",
|
||||
to ?? "0x",
|
||||
value ? toHex(value) : "0x",
|
||||
data ?? "0x0",
|
||||
toHex(chainId),
|
||||
toHex(""),
|
||||
toHex(""),
|
||||
toHex(chainId),
|
||||
from ?? "0x",
|
||||
gasPerPubdata ? toHex(gasPerPubdata) : toHex(gasPerPubdataDefault),
|
||||
factoryDeps ?? [],
|
||||
customSignature ?? "0x", // EIP712 signature
|
||||
paymaster && paymasterInput ? [paymaster, paymasterInput] : [],
|
||||
];
|
||||
|
||||
// @ts-ignore - TODO: fix types
|
||||
return concatHex(["0x71", toRlp(serializedTransaction)]);
|
||||
}
|
||||
@@ -18,11 +18,42 @@ export type StaticPrepareTransactionOptions = {
|
||||
maxFeePerBlobGas?: bigint | undefined;
|
||||
nonce?: number | undefined;
|
||||
extraGas?: bigint | undefined;
|
||||
// zksync specific
|
||||
eip712?: EIP721TransactionOptions | undefined;
|
||||
// tw specific
|
||||
chain: Chain;
|
||||
client: ThirdwebClient;
|
||||
};
|
||||
|
||||
export type EIP721TransactionOptions = {
|
||||
// constant or user input
|
||||
gasPerPubdata?: bigint | undefined;
|
||||
// optional signature, generated
|
||||
customSignature?: Hex | undefined;
|
||||
// optional, used to deploy contracts with the transaction
|
||||
factoryDeps?: Hex[] | undefined;
|
||||
// optional, paymaster contract address to invoke
|
||||
paymaster?: Address | undefined;
|
||||
// optional, paymaster contract input
|
||||
paymasterInput?: Hex | undefined;
|
||||
};
|
||||
|
||||
export type EIP721SerializedTransaction = {
|
||||
txType: bigint;
|
||||
from: bigint;
|
||||
to: bigint;
|
||||
gasLimit: bigint;
|
||||
gasPerPubdataByteLimit: bigint;
|
||||
maxFeePerGas: bigint;
|
||||
maxPriorityFeePerGas: bigint;
|
||||
nonce: bigint;
|
||||
value: bigint;
|
||||
data: Hex;
|
||||
factoryDeps: Hex[];
|
||||
paymaster: bigint;
|
||||
paymasterInput: Hex;
|
||||
};
|
||||
|
||||
export type PrepareTransactionOptions = {
|
||||
chain: Chain;
|
||||
client: ThirdwebClient;
|
||||
|
||||
@@ -32,6 +32,7 @@ import type { FetchDeployMetadataResult } from "./deploy-metadata.js";
|
||||
* });
|
||||
* ```
|
||||
* @returns A promise that resolves to the predicted address of the contract.
|
||||
* @extension DEPLOY
|
||||
*/
|
||||
export async function computePublishedContractAddress(args: {
|
||||
client: ThirdwebClient;
|
||||
|
||||
@@ -15,6 +15,7 @@ export type ResolveArweaveSchemeOptions = {
|
||||
* import { resolveArweaveScheme } from "thirdweb/storage";
|
||||
* const url = resolveArweaveScheme({ uri: "ar://<fileId>" });
|
||||
* ```
|
||||
* @storage
|
||||
*/
|
||||
export function resolveArweaveScheme(options: ResolveArweaveSchemeOptions) {
|
||||
if (options.uri.startsWith("ar://")) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { Hex } from "./is-hex.js";
|
||||
*
|
||||
* @param values - An array of hexadecimal values to concatenate.
|
||||
* @returns The concatenated hexadecimal value.
|
||||
* @utils
|
||||
*/
|
||||
export function concatHex(values: readonly Hex[]): Hex {
|
||||
return `0x${(values as Hex[]).reduce(
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
* const obj = { tokenId: 0n };
|
||||
* const str = stringify(obj); // "{"tokenId":"0"}"
|
||||
* ```
|
||||
* @utils
|
||||
*/
|
||||
export const stringify: typeof JSON.stringify = (value, replacer, space) => {
|
||||
const res = JSON.stringify(
|
||||
|
||||
@@ -28,6 +28,7 @@ export type SignMessageOptions = {
|
||||
* privateKey: "0x...",
|
||||
* });
|
||||
* ```
|
||||
* @utils
|
||||
*/
|
||||
export function signMessage({ message, privateKey }: SignMessageOptions): Hex {
|
||||
const signature = sign({ hash: hashMessage(message), privateKey });
|
||||
|
||||
@@ -25,6 +25,7 @@ export type SignTypedDataOptions<
|
||||
* ...typedData
|
||||
* });
|
||||
* ```
|
||||
* @utils
|
||||
*/
|
||||
export function signTypedData<
|
||||
const typedData extends TypedData | Record<string, unknown>,
|
||||
|
||||
@@ -26,6 +26,7 @@ export type SignOptions = {
|
||||
* privateKey: "0x",
|
||||
* });
|
||||
* ```
|
||||
* @utils
|
||||
*/
|
||||
export function sign({ hash, privateKey }: SignOptions): Signature {
|
||||
const { r, s, recovery } = secp256k1.sign(hash.slice(2), privateKey.slice(2));
|
||||
|
||||
@@ -22,6 +22,8 @@ import { type Hex, hexToBigInt } from "../encoding/hex.js";
|
||||
*
|
||||
* console.log(hex);
|
||||
* // "0x6e100a352ec6ad1b70802290e18aeed190704973570f3b8ed42cb9808e2ea6bf4a90a229a244495b41890987806fcbd2d5d23fc0dbe5f5256c2613c039d76db81c"
|
||||
* ```
|
||||
* @utils
|
||||
*/
|
||||
export function signatureToHex(signature: Signature): Hex {
|
||||
const { r, s, v } = signature;
|
||||
|
||||
@@ -150,7 +150,9 @@ export function createWallet<const ID extends WalletId>(
|
||||
}
|
||||
|
||||
if (options && "client" in options) {
|
||||
const { autoConnectWC } = await import("./wallet-connect/index.js");
|
||||
const { autoConnectWC } = await import(
|
||||
"./wallet-connect/controller.js"
|
||||
);
|
||||
|
||||
const [
|
||||
connectedAccount,
|
||||
@@ -179,7 +181,9 @@ export function createWallet<const ID extends WalletId>(
|
||||
},
|
||||
connect: async (options) => {
|
||||
async function wcConnect(wcOptions: WCConnectOptions) {
|
||||
const { connectWC } = await import("./wallet-connect/index.js");
|
||||
const { connectWC } = await import(
|
||||
"./wallet-connect/controller.js"
|
||||
);
|
||||
|
||||
const [
|
||||
connectedAccount,
|
||||
@@ -295,7 +299,6 @@ export function walletConnect() {
|
||||
* import { smartWallet } from "thirdweb/wallets";
|
||||
*
|
||||
* const wallet = smartWallet({
|
||||
* factoryAddress: "0x1234...",
|
||||
* chain: sepolia,
|
||||
* gasless: true,
|
||||
* });
|
||||
@@ -415,6 +418,7 @@ export function smartWallet(
|
||||
* strategy: "google",
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* Enable smart accounts and sponsor gas for your users:
|
||||
* ```ts
|
||||
* import { inAppWallet } from "thirdweb/wallets";
|
||||
@@ -439,6 +443,7 @@ export function smartWallet(
|
||||
* },
|
||||
* },
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* Hide the ability to export the private key within the Connect Modal
|
||||
* ```ts
|
||||
|
||||
@@ -4,7 +4,7 @@ import { isInAppWallet } from "../in-app/core/wallet/index.js";
|
||||
import { getInjectedProvider } from "../injected/index.js";
|
||||
import type { Wallet } from "../interfaces/wallet.js";
|
||||
import { isSmartWallet } from "../smart/index.js";
|
||||
import { isWalletConnect } from "../wallet-connect/index.js";
|
||||
import { isWalletConnect } from "../wallet-connect/controller.js";
|
||||
import type { GetCallsStatusResponse, WalletSendCallsId } from "./types.js";
|
||||
|
||||
export type GetCallsStatusOptions = {
|
||||
@@ -16,7 +16,7 @@ export type GetCallsStatusOptions = {
|
||||
/**
|
||||
* Get the status of an [EIP-5792](https://eips.ethereum.org/EIPS/eip-5792) bundle.
|
||||
*
|
||||
* Note: This function is dependent on the wallet's support for EIP-5792 and could fail.
|
||||
* @note This function is dependent on the wallet's support for EIP-5792 and could fail.
|
||||
*
|
||||
* @param {GetCallsStatusOptions} options
|
||||
* @param {Wallet} options.wallet - The wallet that send the original calls.
|
||||
@@ -39,7 +39,8 @@ export type GetCallsStatusOptions = {
|
||||
* result = await getCallsStatus({ wallet, client, bundleId });
|
||||
* }
|
||||
* ```
|
||||
* @wallets
|
||||
* @extension EIP5792
|
||||
* @extension EIP5792
|
||||
*/
|
||||
export async function getCallsStatus({
|
||||
wallet,
|
||||
|
||||
@@ -4,7 +4,7 @@ import { isInAppWallet } from "../in-app/core/wallet/index.js";
|
||||
import { getInjectedProvider } from "../injected/index.js";
|
||||
import type { Wallet } from "../interfaces/wallet.js";
|
||||
import { isSmartWallet } from "../smart/index.js";
|
||||
import { isWalletConnect } from "../wallet-connect/index.js";
|
||||
import { isWalletConnect } from "../wallet-connect/controller.js";
|
||||
import type { WalletId } from "../wallet-types.js";
|
||||
import type { WalletCapabilities, WalletCapabilitiesRecord } from "./types.js";
|
||||
|
||||
@@ -19,7 +19,7 @@ export type GetCapabilitiesResult = Prettify<
|
||||
/**
|
||||
* Get the capabilities of a wallet based on the [EIP-5792](https://eips.ethereum.org/EIPS/eip-5792) specification.
|
||||
*
|
||||
* Note: This function is dependent on the wallet's support for EIP-5792, but will not throw.
|
||||
* @note This function is dependent on the wallet's support for EIP-5792, but will not throw.
|
||||
* **The returned object contains a `message` field detailing any issues with the wallet's support for EIP-5792.**
|
||||
*
|
||||
* @param {GetCapabilitiesOptions} options
|
||||
@@ -34,7 +34,7 @@ export type GetCapabilitiesResult = Prettify<
|
||||
* const capabilities = await getCapabilities({ wallet });
|
||||
* ```
|
||||
*
|
||||
* @wallets
|
||||
* @extension EIP5792
|
||||
*/
|
||||
export async function getCapabilities<const ID extends WalletId = WalletId>({
|
||||
wallet,
|
||||
|
||||
@@ -13,7 +13,7 @@ import { isInAppWallet } from "../in-app/core/wallet/index.js";
|
||||
import { getInjectedProvider } from "../injected/index.js";
|
||||
import type { Wallet } from "../interfaces/wallet.js";
|
||||
import { isSmartWallet } from "../smart/index.js";
|
||||
import { isWalletConnect } from "../wallet-connect/index.js";
|
||||
import { isWalletConnect } from "../wallet-connect/controller.js";
|
||||
import type { WalletId } from "../wallet-types.js";
|
||||
import type {
|
||||
EIP5792Call,
|
||||
@@ -56,7 +56,7 @@ export type SendCallsResult = WalletSendCallsId;
|
||||
* This function works with all Thirdweb wallets (in-app and smart) and certain injected wallets that already support EIP-5792.
|
||||
* Transactions will be bundled and sponsored when those capabilities are supported, otherwise they will be sent as individual transactions.
|
||||
*
|
||||
* Note: This function is dependent on the wallet's support for EIP-5792 and could fail.
|
||||
* @note This function is dependent on the wallet's support for EIP-5792 and could fail.
|
||||
*
|
||||
* @param {SendCallsOptions} options
|
||||
* @param {Wallet} options.wallet - The wallet to send the calls to.
|
||||
@@ -106,8 +106,9 @@ export type SendCallsResult = WalletSendCallsId;
|
||||
* }
|
||||
* }
|
||||
* });
|
||||
* @note We recommend proxying any paymaster calls via an API route you setup and control.
|
||||
* ```
|
||||
* @wallets
|
||||
* @extension EIP5792
|
||||
*/
|
||||
export async function sendCalls<const ID extends WalletId>(
|
||||
options: SendCallsOptions<ID>,
|
||||
|
||||
@@ -3,7 +3,7 @@ import { isInAppWallet } from "../in-app/core/wallet/index.js";
|
||||
import { getInjectedProvider } from "../injected/index.js";
|
||||
import type { Wallet } from "../interfaces/wallet.js";
|
||||
import { isSmartWallet } from "../smart/index.js";
|
||||
import { isWalletConnect } from "../wallet-connect/index.js";
|
||||
import { isWalletConnect } from "../wallet-connect/controller.js";
|
||||
import type { WalletSendCallsId } from "./types.js";
|
||||
|
||||
export type ShowCallsStatusOptions = {
|
||||
@@ -32,7 +32,7 @@ export type ShowCallsStatusOptions = {
|
||||
* const bundleId = await sendCalls({ wallet, client, calls });
|
||||
* await showCallsStatus({ wallet, bundleId });
|
||||
* ```
|
||||
* @wallets
|
||||
* @extension EIP5792
|
||||
* @internal
|
||||
*/
|
||||
export async function showCallsStatus({
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
import { afterAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { ANVIL_CHAIN } from "../../../test/src/chains.js";
|
||||
import { TEST_CLIENT } from "../../../test/src/test-clients.js";
|
||||
import { TEST_ACCOUNT_A } from "../../../test/src/test-wallets.js";
|
||||
import * as watchBlockNumberExports from "../../rpc/watchBlockNumber.js";
|
||||
import * as getCallsStatusExports from "../../wallets/eip5792/get-calls-status.js";
|
||||
import { METAMASK } from "../constants.js";
|
||||
import { createWallet } from "../create-wallet.js";
|
||||
import type { GetCallsStatusResponse } from "./types.js";
|
||||
import {
|
||||
DEFAULT_MAX_BLOCKS_WAIT_TIME,
|
||||
waitForBundle,
|
||||
} from "./wait-for-bundle.js";
|
||||
|
||||
const MOCK_BUNDLE_ID = "0x1234567890abcdef";
|
||||
|
||||
const MOCK_SUCCESS_RECEIPT: GetCallsStatusResponse = {
|
||||
status: "CONFIRMED",
|
||||
receipts: [
|
||||
{
|
||||
logs: [],
|
||||
status: "success",
|
||||
blockHash:
|
||||
"0xf19bbafd9fd0124ec110b848e8de4ab4f62bf60c189524e54213285e7f540d4a",
|
||||
blockNumber: 12345n,
|
||||
transactionHash:
|
||||
"0x9b7bb827c2e5e3c1a0a44dc53e573aa0b3af3bd1f9f5ed03071b100bb039eaff",
|
||||
gasUsed: 12345n,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const MOCK_PENDING_RECEIPT: GetCallsStatusResponse = {
|
||||
status: "PENDING",
|
||||
receipts: [],
|
||||
};
|
||||
|
||||
const mockGetCallsStatus = vi.spyOn(getCallsStatusExports, "getCallsStatus");
|
||||
|
||||
const wallet = createWallet(METAMASK);
|
||||
wallet.getAccount = () => TEST_ACCOUNT_A;
|
||||
|
||||
let emitBlockNumber: (blockNumber: bigint) => void;
|
||||
|
||||
vi.spyOn(watchBlockNumberExports, "watchBlockNumber").mockImplementation(
|
||||
({ onNewBlockNumber }) => {
|
||||
emitBlockNumber = (blockNumber: bigint) => {
|
||||
onNewBlockNumber(blockNumber);
|
||||
};
|
||||
return () => {};
|
||||
},
|
||||
);
|
||||
|
||||
describe("waitForBundle", () => {
|
||||
afterAll(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("should resolve with transaction receipt when transactionHash is provided", async () => {
|
||||
mockGetCallsStatus.mockResolvedValueOnce(MOCK_SUCCESS_RECEIPT);
|
||||
|
||||
// can't `await` here because we still need to be able to increment the block number below
|
||||
const res = waitForBundle({
|
||||
chain: ANVIL_CHAIN,
|
||||
client: TEST_CLIENT,
|
||||
bundleId: MOCK_BUNDLE_ID,
|
||||
wallet,
|
||||
});
|
||||
|
||||
emitBlockNumber(1n);
|
||||
|
||||
await expect(res).resolves.toMatchObject(MOCK_SUCCESS_RECEIPT);
|
||||
});
|
||||
|
||||
it("should reject with an error when bundle is not complete after waiting 10 blocks", async () => {
|
||||
mockGetCallsStatus.mockRejectedValue(MOCK_PENDING_RECEIPT);
|
||||
|
||||
const result = waitForBundle({
|
||||
chain: ANVIL_CHAIN,
|
||||
client: TEST_CLIENT,
|
||||
bundleId: MOCK_BUNDLE_ID,
|
||||
wallet,
|
||||
});
|
||||
|
||||
for (let i = 1; i <= DEFAULT_MAX_BLOCKS_WAIT_TIME + 1; i++) {
|
||||
emitBlockNumber(BigInt(i));
|
||||
}
|
||||
|
||||
await expect(result).rejects.toThrow(
|
||||
`Bundle not confirmed after ${DEFAULT_MAX_BLOCKS_WAIT_TIME} blocks`,
|
||||
);
|
||||
expect(mockGetCallsStatus).toHaveBeenCalledTimes(30);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,98 @@
|
||||
import type { Chain } from "../../chains/types.js";
|
||||
import type { ThirdwebClient } from "../../client/client.js";
|
||||
import { watchBlockNumber } from "../../rpc/watchBlockNumber.js";
|
||||
import type { Prettify } from "../../utils/type-utils.js";
|
||||
import type { Wallet } from "../interfaces/wallet.js";
|
||||
import { getCallsStatus } from "./get-calls-status.js";
|
||||
import type { GetCallsStatusResponse, WalletSendCallsId } from "./types.js";
|
||||
|
||||
export const DEFAULT_MAX_BLOCKS_WAIT_TIME = 30;
|
||||
|
||||
const map = new Map<string, Promise<GetCallsStatusResponse>>();
|
||||
|
||||
export type WaitForBundleOptions = Prettify<{
|
||||
bundleId: WalletSendCallsId;
|
||||
wallet: Wallet;
|
||||
client: ThirdwebClient;
|
||||
chain: Chain;
|
||||
maxBlocksWaitTime?: number;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* Waits for the [EIP-5792](https://eips.ethereum.org/EIPS/eip-5792) bundle to be confirmed.
|
||||
*
|
||||
* @note This function is dependent on the wallet's support for EIP-5792 and could fail.
|
||||
*
|
||||
* @param options - The options for waiting for the bundle.
|
||||
* By default, the max wait time is 30 blocks.
|
||||
* @returns A promise that resolves with the final {@link getCallsStatus} result.
|
||||
* @throws an error if the wallet does not support EIP-5792.
|
||||
* @beta
|
||||
* @example
|
||||
* ```ts
|
||||
* import { waitForBundle } from "thirdweb/wallets/eip5792";
|
||||
* const result = await waitForBundle({
|
||||
* client,
|
||||
* chain,
|
||||
* wallet,
|
||||
* bundleId: "0x123...",
|
||||
* });
|
||||
* ```
|
||||
* @extension EIP5792
|
||||
*/
|
||||
export function waitForBundle(
|
||||
options: WaitForBundleOptions,
|
||||
): Promise<GetCallsStatusResponse> {
|
||||
const { bundleId, chain, wallet, client } = options;
|
||||
|
||||
const chainId = chain.id;
|
||||
const key = `${chainId}:calls_${bundleId}`;
|
||||
const maxBlocksWaitTime =
|
||||
options.maxBlocksWaitTime ?? DEFAULT_MAX_BLOCKS_WAIT_TIME;
|
||||
|
||||
if (map.has(key)) {
|
||||
// biome-ignore lint/style/noNonNullAssertion: the `has` above ensures that this will always be set
|
||||
return map.get(key)!;
|
||||
}
|
||||
const promise = new Promise<GetCallsStatusResponse>((resolve, reject) => {
|
||||
// start at -1 because the first block doesn't count
|
||||
let blocksWaited = -1;
|
||||
|
||||
const unwatch = watchBlockNumber({
|
||||
client: client,
|
||||
chain: chain,
|
||||
onNewBlockNumber: async () => {
|
||||
blocksWaited++;
|
||||
if (blocksWaited >= maxBlocksWaitTime) {
|
||||
unwatch();
|
||||
reject(
|
||||
new Error(`Bundle not confirmed after ${maxBlocksWaitTime} blocks`),
|
||||
);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const result = await getCallsStatus({
|
||||
wallet,
|
||||
client,
|
||||
bundleId,
|
||||
});
|
||||
|
||||
if (result.status === "CONFIRMED") {
|
||||
// stop the polling
|
||||
unwatch();
|
||||
// resolve the top level promise with the result
|
||||
resolve(result);
|
||||
}
|
||||
} catch {
|
||||
// noop, we'll try again on the next blocks
|
||||
}
|
||||
},
|
||||
});
|
||||
// remove the promise from the map when it's done (one way or the other)
|
||||
}).finally(() => {
|
||||
map.delete(key);
|
||||
});
|
||||
|
||||
map.set(key, promise);
|
||||
return promise;
|
||||
}
|
||||
@@ -69,6 +69,7 @@ export async function logoutAuthenticatedUser(
|
||||
* console.log(user.walletAddress);
|
||||
* }
|
||||
* ```
|
||||
* @wallet
|
||||
*/
|
||||
export async function getAuthenticatedUser(
|
||||
options: GetAuthenticatedUserParams,
|
||||
@@ -95,6 +96,7 @@ export async function getAuthenticatedUser(
|
||||
* const email = await getUserEmail({ client });
|
||||
* console.log(email);
|
||||
* ```
|
||||
* @wallet
|
||||
*/
|
||||
export async function getUserEmail(options: GetAuthenticatedUserParams) {
|
||||
const user = await getAuthenticatedUser(options);
|
||||
@@ -115,6 +117,7 @@ export async function getUserEmail(options: GetAuthenticatedUserParams) {
|
||||
* const phoneNumber = await getUserPhoneNumber({ client });
|
||||
* console.log(phoneNumber);
|
||||
* ```
|
||||
* @wallet
|
||||
*/
|
||||
export async function getUserPhoneNumber(options: GetAuthenticatedUserParams) {
|
||||
const user = await getAuthenticatedUser(options);
|
||||
@@ -139,6 +142,7 @@ export async function getUserPhoneNumber(options: GetAuthenticatedUserParams) {
|
||||
* email: "[email protected]",
|
||||
* });
|
||||
* ```
|
||||
* @wallet
|
||||
*/
|
||||
export async function preAuthenticate(args: PreAuthArgsType) {
|
||||
const ewSDK = await getInAppWalletConnector(args.client);
|
||||
@@ -160,6 +164,7 @@ export async function preAuthenticate(args: PreAuthArgsType) {
|
||||
* verificationCode: "123456",
|
||||
* });
|
||||
* ```
|
||||
* @wallet
|
||||
*/
|
||||
export async function authenticate(
|
||||
args: AuthArgsType,
|
||||
|
||||
@@ -60,6 +60,12 @@ export const DEVICE_SHARE_LOCAL_STORAGE_NAME = (
|
||||
userId: string,
|
||||
) => `${DEVICE_SHARE_LOCAL_STORAGE_PREFIX}-${clientId}-${userId}`;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export const WALLET_CONNECT_SESSIONS_LOCAL_STORAGE_NAME = (clientId: string) =>
|
||||
`walletConnectSessions-${clientId}`;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
|
||||
@@ -132,6 +132,7 @@ export async function loginWithPasskey(options: {
|
||||
* Returns whether this device has a stored passkey ready to be used for sign-in
|
||||
* @param client - the thirdweb client
|
||||
* @returns whether the device has a stored passkey
|
||||
* @wallet
|
||||
*/
|
||||
export async function hasStoredPasskey(client: ThirdwebClient) {
|
||||
const storage = new LocalStorage({ clientId: client.clientId });
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user