Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d18c3edea | ||
|
|
7d07490ed3 | ||
|
|
22571ba0a9 | ||
|
|
0ff90b8fba | ||
|
|
e66e906650 | ||
|
|
b3cf65b9db | ||
|
|
f953c72c4e |
@@ -1,5 +1,12 @@
|
||||
# @thirdweb-dev/auth
|
||||
|
||||
## 4.1.22
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`22571ba0`](https://github.com/thirdweb-dev/js/commit/22571ba0a9bd238f9ee16f548bc17ccad2e7ad1f), [`e66e9066`](https://github.com/thirdweb-dev/js/commit/e66e906650e04a63e084bc869a62681bd956e0bb)]:
|
||||
- @thirdweb-dev/wallets@2.4.0
|
||||
|
||||
## 4.1.21
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/auth",
|
||||
"version": "4.1.21",
|
||||
"version": "4.1.22",
|
||||
"main": "dist/thirdweb-dev-auth.cjs.js",
|
||||
"module": "dist/thirdweb-dev-auth.esm.js",
|
||||
"browser": {
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# thirdweb
|
||||
|
||||
## 0.13.30
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`22571ba0`](https://github.com/thirdweb-dev/js/commit/22571ba0a9bd238f9ee16f548bc17ccad2e7ad1f), [`e66e9066`](https://github.com/thirdweb-dev/js/commit/e66e906650e04a63e084bc869a62681bd956e0bb)]:
|
||||
- @thirdweb-dev/wallets@2.4.0
|
||||
- @thirdweb-dev/auth@4.1.22
|
||||
|
||||
## 0.13.29
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "thirdweb",
|
||||
"main": "dist/cli/index.js",
|
||||
"version": "0.13.29",
|
||||
"version": "0.13.30",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/cli",
|
||||
"author": "thirdweb eng <[email protected]>",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"extends": "@thirdweb-dev/tsconfig/sdk.json",
|
||||
"include": ["src", "types"],
|
||||
"exclude": ["dist", "build", "node_modules"]
|
||||
"include": ["src", "types", "test"],
|
||||
"exclude": ["dist", "build", "node_modules", "**/*.test.ts"]
|
||||
}
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @thirdweb-dev/payments
|
||||
|
||||
## 1.0.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#2133](https://github.com/thirdweb-dev/js/pull/2133) [`b3cf65b9`](https://github.com/thirdweb-dev/js/commit/b3cf65b9dbdab22a452d2c597d21a14300b2d174) Thanks [@IDubuque](https://github.com/IDubuque)! - Minor bug fix importing package.json
|
||||
|
||||
## 1.0.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/payments",
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.2",
|
||||
"main": "dist/thirdweb-dev-payments.cjs.js",
|
||||
"module": "dist/thirdweb-dev-payments.esm.js",
|
||||
"types": "dist/thirdweb-dev-payments.cjs.d.ts",
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// CHANGED: packageJson import + version string
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires, better-tree-shaking/no-top-level-side-effects
|
||||
const packageJson = require("../package.json");
|
||||
import packageJson from "../../package.json";
|
||||
|
||||
export interface PaperPaymentElementConstructorArgs {
|
||||
onLoad?: (event?: Event) => void;
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @thirdweb-dev/react-core
|
||||
|
||||
## 4.2.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`22571ba0`](https://github.com/thirdweb-dev/js/commit/22571ba0a9bd238f9ee16f548bc17ccad2e7ad1f), [`e66e9066`](https://github.com/thirdweb-dev/js/commit/e66e906650e04a63e084bc869a62681bd956e0bb)]:
|
||||
- @thirdweb-dev/wallets@2.4.0
|
||||
- @thirdweb-dev/auth@4.1.22
|
||||
|
||||
## 4.2.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/react-core",
|
||||
"version": "4.2.2",
|
||||
"version": "4.2.3",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/react-core",
|
||||
"author": "thirdweb eng <[email protected]>",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# @thirdweb-dev/react-native-compat
|
||||
|
||||
## 0.5.6
|
||||
|
||||
## 0.5.5
|
||||
|
||||
## 0.5.4
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/react-native-compat",
|
||||
"description": "Shims for Thirdweb in React Native Projects",
|
||||
"version": "0.5.5",
|
||||
"version": "0.5.6",
|
||||
"author": "thirdweb eng <[email protected]>",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/react-native-compat",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
# @thirdweb-dev/react-native
|
||||
|
||||
## 0.5.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#2131](https://github.com/thirdweb-dev/js/pull/2131) [`f953c72c`](https://github.com/thirdweb-dev/js/commit/f953c72c4e273feefcbd905876b15e7346b45740) Thanks [@iketw](https://github.com/iketw)! - Fix custom balance token display in details button
|
||||
|
||||
- [#2137](https://github.com/thirdweb-dev/js/pull/2137) [`7d07490e`](https://github.com/thirdweb-dev/js/commit/7d07490ed3ec80aa33a991f38b3831996d49fb09) Thanks [@iketw](https://github.com/iketw)! - Adds hideDisconnect prop to hide the disconnect button
|
||||
|
||||
```ts
|
||||
<ConnectWallet hideDisconnect={true} />
|
||||
```
|
||||
|
||||
- Updated dependencies [[`22571ba0`](https://github.com/thirdweb-dev/js/commit/22571ba0a9bd238f9ee16f548bc17ccad2e7ad1f), [`e66e9066`](https://github.com/thirdweb-dev/js/commit/e66e906650e04a63e084bc869a62681bd956e0bb)]:
|
||||
- @thirdweb-dev/[email protected]
|
||||
- @thirdweb-dev/[email protected]
|
||||
|
||||
## 0.5.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/react-native",
|
||||
"version": "0.5.5",
|
||||
"version": "0.5.6",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/react-native",
|
||||
"author": "thirdweb eng <[email protected]>",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -73,13 +73,17 @@ export type ConnectWalletProps = {
|
||||
supportedTokens?: SupportedTokens;
|
||||
|
||||
/**
|
||||
* Show balance of ERC20 token instead of the native token in the "Connected" button when connected to certain network
|
||||
* Display the balance of a token instead of the native token in ConnectWallet details button.
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* import { Base } from "@thirdweb-dev/chains";
|
||||
*
|
||||
* <ConnectWallet balanceToken={{
|
||||
* 1: "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599" // show USDC balance when connected to Ethereum mainnet
|
||||
* }} />
|
||||
* 1: "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599" // show USDC balance when connected to Ethereum mainnet
|
||||
* [Base.chainId]: "0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb", // show Dai stablecoin token balance when connected to Base mainnet
|
||||
* }}
|
||||
* />
|
||||
* ```
|
||||
*/
|
||||
displayBalanceToken?: Record<number, string>;
|
||||
@@ -90,6 +94,18 @@ export type ConnectWalletProps = {
|
||||
* The default is `false`
|
||||
*/
|
||||
hideSwitchToPersonalWallet?: boolean;
|
||||
|
||||
/**
|
||||
* Hide the "Disconnect Wallet" button in the ConnectWallet Dropdown.
|
||||
*
|
||||
* By default it is `false`
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* <ConnectWallet hideDisconnect={true} />
|
||||
* ```
|
||||
*/
|
||||
hideDisconnect?: boolean;
|
||||
} & ConnectWalletButtonProps;
|
||||
|
||||
export const ConnectWallet = ({
|
||||
@@ -108,6 +124,7 @@ export const ConnectWallet = ({
|
||||
privacyPolicyUrl,
|
||||
supportedTokens,
|
||||
hideSwitchToPersonalWallet,
|
||||
hideDisconnect,
|
||||
}: ConnectWalletProps) => {
|
||||
const globalTheme = useGlobalTheme();
|
||||
const l = useLocale();
|
||||
@@ -187,6 +204,7 @@ export const ConnectWallet = ({
|
||||
hideTestnetFaucet={hideTestnetFaucet}
|
||||
hideReceiveButton={hideReceiveButton}
|
||||
hideSendButton={hideSendButton}
|
||||
hideDisconnect={hideDisconnect}
|
||||
supportedTokens={supportedTokensMemo}
|
||||
displayBalanceToken={displayBalanceToken}
|
||||
hideSwitchToPersonalWallet={hideSwitchToPersonalWallet}
|
||||
|
||||
+5
-7
@@ -43,10 +43,11 @@ export const ConnectWalletDetailsModal = ({
|
||||
address,
|
||||
hideTestnetFaucet,
|
||||
supportedTokens,
|
||||
displayBalanceToken,
|
||||
tokenAddress,
|
||||
hideSwitchToPersonalWallet,
|
||||
hideReceiveButton,
|
||||
hideSendButton,
|
||||
hideDisconnect,
|
||||
}: {
|
||||
isVisible: boolean;
|
||||
onClosePress: () => void;
|
||||
@@ -54,10 +55,11 @@ export const ConnectWalletDetailsModal = ({
|
||||
address?: string;
|
||||
hideTestnetFaucet?: boolean;
|
||||
supportedTokens: SupportedTokens;
|
||||
displayBalanceToken?: Record<number, string>;
|
||||
tokenAddress?: string;
|
||||
hideSwitchToPersonalWallet?: boolean;
|
||||
hideReceiveButton?: boolean;
|
||||
hideSendButton?: boolean;
|
||||
hideDisconnect?: boolean;
|
||||
}) => {
|
||||
const l = useLocale();
|
||||
const [isExportModalVisible, setIsExportModalVisible] = useState(false);
|
||||
@@ -69,11 +71,6 @@ export const ConnectWalletDetailsModal = ({
|
||||
const [isImportModalVisible, setIsImportModalVisible] = useState(false);
|
||||
const setConnectedWallet = useSetConnectedWallet();
|
||||
|
||||
const tokenAddress =
|
||||
chain?.chainId && displayBalanceToken
|
||||
? displayBalanceToken[chain?.chainId]
|
||||
: undefined;
|
||||
|
||||
const [isSmartWalletDeployed, setIsSmartWalletDeployed] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -235,6 +232,7 @@ export const ConnectWalletDetailsModal = ({
|
||||
<WalletDetailsModalHeader
|
||||
tokenAddress={tokenAddress}
|
||||
address={address}
|
||||
hideDisconnect={hideDisconnect}
|
||||
onDisconnectPress={onDisconnectPress}
|
||||
onAddressCopied={onAddressCopied}
|
||||
/>
|
||||
|
||||
+16
-3
@@ -1,7 +1,7 @@
|
||||
import { AddressDisplay } from "../base/AddressDisplay";
|
||||
import BaseButton from "../base/BaseButton";
|
||||
import Text from "../base/Text";
|
||||
import { useENS, useWallet } from "@thirdweb-dev/react-core";
|
||||
import { useChain, useENS, useWallet } from "@thirdweb-dev/react-core";
|
||||
import { StyleSheet } from "react-native";
|
||||
import { LocalWallet, walletIds } from "@thirdweb-dev/wallets";
|
||||
import Box from "../base/Box";
|
||||
@@ -56,6 +56,8 @@ export type ConnectWalletDetailsProps = {
|
||||
* The default is `false`
|
||||
*/
|
||||
hideReceiveButton?: boolean;
|
||||
|
||||
hideDisconnect?: boolean;
|
||||
};
|
||||
|
||||
export const WalletDetailsButton = ({
|
||||
@@ -68,11 +70,13 @@ export const WalletDetailsButton = ({
|
||||
supportedTokens,
|
||||
displayBalanceToken,
|
||||
hideSwitchToPersonalWallet,
|
||||
hideDisconnect,
|
||||
}: ConnectWalletDetailsProps) => {
|
||||
const l = useLocale();
|
||||
const activeWallet = useWallet();
|
||||
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||
const ensQuery = useENS();
|
||||
const chain = useChain();
|
||||
|
||||
const onPress = () => {
|
||||
setIsModalVisible(!isModalVisible);
|
||||
@@ -89,6 +93,11 @@ export const WalletDetailsButton = ({
|
||||
? SMART_WALLET_ICON
|
||||
: activeWallet?.getMeta().iconURL || "";
|
||||
|
||||
const tokenAddress =
|
||||
chain?.chainId && displayBalanceToken
|
||||
? displayBalanceToken[chain?.chainId]
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<>
|
||||
<ConnectWalletDetailsModal
|
||||
@@ -98,7 +107,8 @@ export const WalletDetailsButton = ({
|
||||
address={address}
|
||||
hideTestnetFaucet={hideTestnetFaucet}
|
||||
supportedTokens={supportedTokens}
|
||||
displayBalanceToken={displayBalanceToken}
|
||||
hideDisconnect={hideDisconnect}
|
||||
tokenAddress={tokenAddress}
|
||||
hideSwitchToPersonalWallet={hideSwitchToPersonalWallet}
|
||||
hideReceiveButton={hideReceiveButton}
|
||||
hideSendButton={hideSendButton}
|
||||
@@ -133,7 +143,10 @@ export const WalletDetailsButton = ({
|
||||
address={address}
|
||||
/>
|
||||
)}
|
||||
<TextBalance textVariant="bodySmallSecondary" />
|
||||
<TextBalance
|
||||
textVariant="bodySmallSecondary"
|
||||
tokenAddress={tokenAddress}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</BaseButton>
|
||||
|
||||
+3
-1
@@ -20,6 +20,7 @@ interface WalletDetailsModalHeaderProps {
|
||||
onAddressCopied?: () => void;
|
||||
loading?: boolean;
|
||||
tokenAddress?: string;
|
||||
hideDisconnect?: boolean;
|
||||
}
|
||||
|
||||
export const WalletDetailsModalHeader = ({
|
||||
@@ -27,6 +28,7 @@ export const WalletDetailsModalHeader = ({
|
||||
onDisconnectPress,
|
||||
onAddressCopied,
|
||||
tokenAddress,
|
||||
hideDisconnect,
|
||||
}: WalletDetailsModalHeaderProps) => {
|
||||
const theme = useGlobalTheme();
|
||||
const activeWallet = useWallet();
|
||||
@@ -96,7 +98,7 @@ export const WalletDetailsModalHeader = ({
|
||||
</BaseButton>
|
||||
{showLoading ? (
|
||||
<ActivityIndicator size="small" color={theme.colors.iconHighlight} />
|
||||
) : (
|
||||
) : hideDisconnect ? null : (
|
||||
<Icon
|
||||
type="disconnect"
|
||||
width={18}
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
# @thirdweb-dev/react
|
||||
|
||||
## 4.2.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#2122](https://github.com/thirdweb-dev/js/pull/2122) [`0ff90b8f`](https://github.com/thirdweb-dev/js/commit/0ff90b8fba1a0f657d5727fa1a7789bd933a0ad6) Thanks [@b-bot](https://github.com/b-bot)! - Add `hideDisconnect` prop on `ConnectWallet` component to hide the "Disconnect Wallet" option from the ConnectWallet dropdown
|
||||
|
||||
```tsx
|
||||
<ConnectWallet hideDisconnect={true} />
|
||||
```
|
||||
|
||||
- Updated dependencies [[`b3cf65b9`](https://github.com/thirdweb-dev/js/commit/b3cf65b9dbdab22a452d2c597d21a14300b2d174), [`22571ba0`](https://github.com/thirdweb-dev/js/commit/22571ba0a9bd238f9ee16f548bc17ccad2e7ad1f), [`e66e9066`](https://github.com/thirdweb-dev/js/commit/e66e906650e04a63e084bc869a62681bd956e0bb)]:
|
||||
- @thirdweb-dev/[email protected]
|
||||
- @thirdweb-dev/[email protected]
|
||||
- @thirdweb-dev/[email protected]
|
||||
|
||||
## 4.2.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/react",
|
||||
"version": "4.2.2",
|
||||
"version": "4.2.3",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/react",
|
||||
"author": "thirdweb eng <[email protected]>",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -308,6 +308,18 @@ export type ConnectWalletProps = {
|
||||
* ```
|
||||
*/
|
||||
hideSwitchToPersonalWallet?: boolean;
|
||||
|
||||
/**
|
||||
* Hide the "Disconnect Wallet" button in the ConnectWallet Dropdown.
|
||||
*
|
||||
* By default it is `false`
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* <ConnectWallet hideDisconnect={true} />
|
||||
* ```
|
||||
*/
|
||||
hideDisconnect?: boolean;
|
||||
};
|
||||
|
||||
const TW_CONNECT_WALLET = "tw-connect-wallet";
|
||||
@@ -535,6 +547,14 @@ const TW_CONNECT_WALLET = "tw-connect-wallet";
|
||||
* <ConnectWallet hideSwitchToPersonalWallet={true} />
|
||||
* ```
|
||||
*
|
||||
* ### hideDisconnect
|
||||
* Hide the "Disconnect Wallet" button in the ConnectWallet dropdown
|
||||
*
|
||||
* By default it is `false`
|
||||
*
|
||||
* ```tsx
|
||||
* <ConnectWallet hideDisconnect={true} />
|
||||
* ```
|
||||
*/
|
||||
export function ConnectWallet(props: ConnectWalletProps) {
|
||||
const activeWallet = useWallet();
|
||||
@@ -732,6 +752,7 @@ export function ConnectWallet(props: ConnectWalletProps) {
|
||||
}
|
||||
}}
|
||||
hideSwitchToPersonalWallet={props.hideSwitchToPersonalWallet}
|
||||
hideDisconnect={props.hideDisconnect}
|
||||
/>
|
||||
);
|
||||
})()}
|
||||
|
||||
@@ -97,6 +97,7 @@ export const ConnectedWalletDetails: React.FC<{
|
||||
supportedTokens: SupportedTokens;
|
||||
displayBalanceToken?: Record<number, string>;
|
||||
hideSwitchToPersonalWallet?: boolean;
|
||||
hideDisconnect?: boolean;
|
||||
}> = (props) => {
|
||||
const locale = useTWLocale().connectWallet;
|
||||
const chain = useChain();
|
||||
@@ -331,22 +332,24 @@ export const ConnectedWalletDetails: React.FC<{
|
||||
</IconButton>
|
||||
</div>
|
||||
|
||||
<ToolTip
|
||||
tip={locale.disconnectWallet}
|
||||
side="bottom"
|
||||
align={"end"}
|
||||
sideOffset={10}
|
||||
>
|
||||
<DisconnectIconButton
|
||||
type="button"
|
||||
onClick={() => {
|
||||
disconnect();
|
||||
props.onDisconnect();
|
||||
}}
|
||||
{!props.hideDisconnect && (
|
||||
<ToolTip
|
||||
tip={locale.disconnectWallet}
|
||||
side="bottom"
|
||||
align={"end"}
|
||||
sideOffset={10}
|
||||
>
|
||||
<ExitIcon size={iconSize.md} />
|
||||
</DisconnectIconButton>
|
||||
</ToolTip>
|
||||
<DisconnectIconButton
|
||||
type="button"
|
||||
onClick={() => {
|
||||
disconnect();
|
||||
props.onDisconnect();
|
||||
}}
|
||||
>
|
||||
<ExitIcon size={iconSize.md} />
|
||||
</DisconnectIconButton>
|
||||
</ToolTip>
|
||||
)}
|
||||
</Container>
|
||||
|
||||
{/* row 2 */}
|
||||
|
||||
@@ -39,6 +39,7 @@ export const ConnectModalInline = (
|
||||
| "switchToActiveChain"
|
||||
| "supportedTokens"
|
||||
| "hideSwitchToPersonalWallet"
|
||||
| "hideDisconnect"
|
||||
> & {
|
||||
onModalHide?: () => void;
|
||||
},
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @thirdweb-dev/unity-js-bridge
|
||||
|
||||
## 0.3.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`22571ba0`](https://github.com/thirdweb-dev/js/commit/22571ba0a9bd238f9ee16f548bc17ccad2e7ad1f), [`e66e9066`](https://github.com/thirdweb-dev/js/commit/e66e906650e04a63e084bc869a62681bd956e0bb)]:
|
||||
- @thirdweb-dev/wallets@2.4.0
|
||||
- @thirdweb-dev/auth@4.1.22
|
||||
|
||||
## 0.3.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/unity-js-bridge",
|
||||
"version": "0.3.4",
|
||||
"version": "0.3.5",
|
||||
"main": "dist/thirdweb-unity-bridge.js",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/unity-js-bridge",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# @thirdweb-dev/wallets
|
||||
|
||||
## 2.4.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#2098](https://github.com/thirdweb-dev/js/pull/2098) [`22571ba0`](https://github.com/thirdweb-dev/js/commit/22571ba0a9bd238f9ee16f548bc17ccad2e7ad1f) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - - Add options to override gasless per transaction
|
||||
- Better userOp estimations
|
||||
- Performance optimisations
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#2125](https://github.com/thirdweb-dev/js/pull/2125) [`e66e9066`](https://github.com/thirdweb-dev/js/commit/e66e906650e04a63e084bc869a62681bd956e0bb) Thanks [@adam-maj](https://github.com/adam-maj)! - Add signTypedData to AwsKmsWallet
|
||||
|
||||
## 2.3.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
/* eslint-disable better-tree-shaking/no-top-level-side-effects */
|
||||
require("@nomiclabs/hardhat-ethers");
|
||||
|
||||
/**
|
||||
* @type import('hardhat/config').HardhatUserConfig
|
||||
*/
|
||||
module.exports = {
|
||||
solidity: "0.7.3",
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/wallets",
|
||||
"version": "2.3.4",
|
||||
"version": "2.4.0",
|
||||
"main": "dist/thirdweb-dev-wallets.cjs.js",
|
||||
"module": "dist/thirdweb-dev-wallets.esm.js",
|
||||
"browser": {
|
||||
@@ -478,8 +478,6 @@
|
||||
"sideEffects": false,
|
||||
"dependencies": {
|
||||
"@account-abstraction/contracts": "^0.5.0",
|
||||
"@account-abstraction/sdk": "^0.5.0",
|
||||
"@account-abstraction/utils": "^0.5.0",
|
||||
"@blocto/sdk": "^0.5.4",
|
||||
"@coinbase/wallet-sdk": "^3.7.1",
|
||||
"@google-cloud/kms": "3.0.1",
|
||||
@@ -548,9 +546,11 @@
|
||||
"@babel/plugin-transform-private-methods": "7.22.5",
|
||||
"@microsoft/api-extractor": "^7.36.3",
|
||||
"@noble/ed25519": "^1.7.1",
|
||||
"@nomiclabs/hardhat-ethers": "^2.2.3",
|
||||
"@preconstruct/cli": "2.7.0",
|
||||
"@thirdweb-dev/tsconfig": "workspace:*",
|
||||
"@types/bn.js": "^5.1.1",
|
||||
"@types/jest": "^29.5.3",
|
||||
"abitype": "^0.2.5",
|
||||
"babel-plugin-transform-inline-environment-variables": "^0.4.4",
|
||||
"bs58": "^5.0.0",
|
||||
@@ -561,7 +561,10 @@
|
||||
"ethereum-provider": "^0.7.7",
|
||||
"ethers": "^5.7.2",
|
||||
"ethers-aws-kms-signer": "^1.3.2",
|
||||
"hardhat": "^2.17.0",
|
||||
"jest": "^29.6.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"ts-jest": "^29.1.1",
|
||||
"tweetnacl": "^1.0.3",
|
||||
"typedoc-gen": "workspace:*",
|
||||
"typescript": "^5.1.6"
|
||||
@@ -574,9 +577,13 @@
|
||||
"build": "tsc && cross-env THIRDWEB_EWS_SDK_VERSION=$npm_package_version preconstruct build",
|
||||
"preconstruct:fix": "preconstruct fix",
|
||||
"push": "yalc push",
|
||||
"typedoc": "node scripts/typedoc.mjs"
|
||||
"typedoc": "node scripts/typedoc.mjs",
|
||||
"test": "jest"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"jest": {
|
||||
"testTimeout": 30000
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,11 +9,13 @@ import {
|
||||
ProviderConfig,
|
||||
SmartWalletConfig,
|
||||
SmartWalletConnectionArgs,
|
||||
TransactionOptions,
|
||||
UserOpOptions,
|
||||
} from "./types";
|
||||
import { ENTRYPOINT_ADDRESS } from "./lib/constants";
|
||||
import { EVMWallet } from "../../interfaces";
|
||||
import { ERC4337EthersSigner } from "./lib/erc4337-signer";
|
||||
import { BigNumber, ethers, providers, utils } from "ethers";
|
||||
import { BigNumber, constants, ethers, providers, utils } from "ethers";
|
||||
import {
|
||||
getChainProvider,
|
||||
getGasPrice,
|
||||
@@ -25,9 +27,7 @@ import {
|
||||
TransactionResult,
|
||||
} from "@thirdweb-dev/sdk";
|
||||
import { AccountAPI } from "./lib/account";
|
||||
import { AddressZero } from "@account-abstraction/utils";
|
||||
import { TransactionDetailsForUserOp } from "./lib/transaction-details";
|
||||
import { BatchData } from "./lib/base-api";
|
||||
|
||||
export class SmartWalletConnector extends Connector<SmartWalletConnectionArgs> {
|
||||
protected config: SmartWalletConfig;
|
||||
@@ -52,7 +52,7 @@ export class SmartWalletConnector extends Connector<SmartWalletConnectionArgs> {
|
||||
this.config.bundlerUrl || `https://${this.chainId}.bundler.thirdweb.com`;
|
||||
const paymasterUrl =
|
||||
this.config.paymasterUrl ||
|
||||
`https://${this.chainId}.bundler.thirdweb.com`;
|
||||
`https://${this.chainId}.bundler.thirdweb.com/v2`;
|
||||
const entryPointAddress = config.entryPointAddress || ENTRYPOINT_ADDRESS;
|
||||
const localSigner = await params.personalWallet.getSigner();
|
||||
const providerConfig: ProviderConfig = {
|
||||
@@ -60,16 +60,15 @@ export class SmartWalletConnector extends Connector<SmartWalletConnectionArgs> {
|
||||
localSigner,
|
||||
entryPointAddress,
|
||||
bundlerUrl,
|
||||
paymasterAPI: this.config.gasless
|
||||
paymasterAPI: this.config.paymasterAPI
|
||||
? this.config.paymasterAPI
|
||||
? this.config.paymasterAPI
|
||||
: getVerifyingPaymaster(
|
||||
paymasterUrl,
|
||||
entryPointAddress,
|
||||
this.config.clientId,
|
||||
this.config.secretKey,
|
||||
)
|
||||
: undefined,
|
||||
: getVerifyingPaymaster(
|
||||
paymasterUrl,
|
||||
entryPointAddress,
|
||||
this.config.clientId,
|
||||
this.config.secretKey,
|
||||
),
|
||||
gasless: config.gasless,
|
||||
factoryAddress: config.factoryAddress,
|
||||
accountAddress: params.accountAddress,
|
||||
factoryInfo: config.factoryInfo || this.defaultFactoryInfo(),
|
||||
@@ -172,16 +171,23 @@ export class SmartWalletConnector extends Connector<SmartWalletConnectionArgs> {
|
||||
|
||||
/**
|
||||
* Send a single transaction without waiting for confirmations
|
||||
* @param transaction - The transaction to send
|
||||
* @param transaction - the transaction to send
|
||||
* @param config - optional the transaction configuration
|
||||
* @returns The awaitable transaction
|
||||
*/
|
||||
async send(transaction: Transaction): Promise<providers.TransactionResponse> {
|
||||
async send(
|
||||
transaction: Transaction,
|
||||
options?: TransactionOptions,
|
||||
): Promise<providers.TransactionResponse> {
|
||||
const signer = await this.getSigner();
|
||||
return signer.sendTransaction({
|
||||
to: transaction.getTarget(),
|
||||
data: transaction.encode(),
|
||||
value: await transaction.getValue(),
|
||||
});
|
||||
return signer.sendTransaction(
|
||||
{
|
||||
to: transaction.getTarget(),
|
||||
data: transaction.encode(),
|
||||
value: await transaction.getValue(),
|
||||
},
|
||||
options,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -189,8 +195,11 @@ export class SmartWalletConnector extends Connector<SmartWalletConnectionArgs> {
|
||||
* @param transaction - The transaction to execute
|
||||
* @returns The transaction receipt
|
||||
*/
|
||||
async execute(transaction: Transaction): Promise<TransactionResult> {
|
||||
const tx = await this.send(transaction);
|
||||
async execute(
|
||||
transaction: Transaction,
|
||||
options?: TransactionOptions,
|
||||
): Promise<TransactionResult> {
|
||||
const tx = await this.send(transaction, options);
|
||||
const receipt = await tx.wait();
|
||||
return {
|
||||
receipt,
|
||||
@@ -199,6 +208,7 @@ export class SmartWalletConnector extends Connector<SmartWalletConnectionArgs> {
|
||||
|
||||
async sendBatch(
|
||||
transactions: Transaction<any>[],
|
||||
options?: TransactionOptions,
|
||||
): Promise<providers.TransactionResponse> {
|
||||
if (!this.accountApi) {
|
||||
throw new Error("Personal wallet not connected");
|
||||
@@ -211,7 +221,10 @@ export class SmartWalletConnector extends Connector<SmartWalletConnectionArgs> {
|
||||
data: tx.encode(),
|
||||
value: 0,
|
||||
},
|
||||
batchData,
|
||||
{
|
||||
...options,
|
||||
batchData,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -222,8 +235,9 @@ export class SmartWalletConnector extends Connector<SmartWalletConnectionArgs> {
|
||||
*/
|
||||
async executeBatch(
|
||||
transactions: Transaction<any>[],
|
||||
options?: TransactionOptions,
|
||||
): Promise<TransactionResult> {
|
||||
const tx = await this.sendBatch(transactions);
|
||||
const tx = await this.sendBatch(transactions, options);
|
||||
const receipt = await tx.wait();
|
||||
return {
|
||||
receipt,
|
||||
@@ -234,18 +248,20 @@ export class SmartWalletConnector extends Connector<SmartWalletConnectionArgs> {
|
||||
|
||||
async sendRaw(
|
||||
transaction: utils.Deferrable<providers.TransactionRequest>,
|
||||
options?: TransactionOptions,
|
||||
): Promise<providers.TransactionResponse> {
|
||||
if (!this.accountApi) {
|
||||
throw new Error("Personal wallet not connected");
|
||||
}
|
||||
const signer = await this.getSigner();
|
||||
return signer.sendTransaction(transaction);
|
||||
return signer.sendTransaction(transaction, options);
|
||||
}
|
||||
|
||||
async executeRaw(
|
||||
transaction: utils.Deferrable<providers.TransactionRequest>,
|
||||
options?: TransactionOptions,
|
||||
) {
|
||||
const tx = await this.sendRaw(transaction);
|
||||
const tx = await this.sendRaw(transaction, options);
|
||||
const receipt = await tx.wait();
|
||||
return {
|
||||
receipt,
|
||||
@@ -254,6 +270,7 @@ export class SmartWalletConnector extends Connector<SmartWalletConnectionArgs> {
|
||||
|
||||
async sendBatchRaw(
|
||||
transactions: utils.Deferrable<providers.TransactionRequest>[],
|
||||
options?: TransactionOptions,
|
||||
) {
|
||||
if (!this.accountApi) {
|
||||
throw new Error("Personal wallet not connected");
|
||||
@@ -266,14 +283,18 @@ export class SmartWalletConnector extends Connector<SmartWalletConnectionArgs> {
|
||||
data: batch.tx.encode(),
|
||||
value: 0,
|
||||
},
|
||||
batch.batchData, // batched tx flag
|
||||
{
|
||||
...options,
|
||||
batchData: batch.batchData, // batched tx flag
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
async executeBatchRaw(
|
||||
transactions: utils.Deferrable<providers.TransactionRequest>[],
|
||||
options?: TransactionOptions,
|
||||
) {
|
||||
const tx = await this.sendBatchRaw(transactions);
|
||||
const tx = await this.sendBatchRaw(transactions, options);
|
||||
const receipt = await tx.wait();
|
||||
return {
|
||||
receipt,
|
||||
@@ -282,32 +303,51 @@ export class SmartWalletConnector extends Connector<SmartWalletConnectionArgs> {
|
||||
|
||||
/// ESTIMATION
|
||||
|
||||
async estimate(transaction: Transaction) {
|
||||
async estimate(transaction: Transaction, options?: TransactionOptions) {
|
||||
if (!this.accountApi) {
|
||||
throw new Error("Personal wallet not connected");
|
||||
}
|
||||
return this.estimateTx({
|
||||
target: transaction.getTarget(),
|
||||
data: transaction.encode(),
|
||||
value: await transaction.getValue(),
|
||||
});
|
||||
return this.estimateTx(
|
||||
{
|
||||
target: transaction.getTarget(),
|
||||
data: transaction.encode(),
|
||||
value: await transaction.getValue(),
|
||||
gasLimit: await transaction.getOverrides().gasLimit,
|
||||
maxFeePerGas: await transaction.getOverrides().maxFeePerGas,
|
||||
maxPriorityFeePerGas: await transaction.getOverrides()
|
||||
.maxPriorityFeePerGas,
|
||||
nonce: await transaction.getOverrides().nonce,
|
||||
},
|
||||
options,
|
||||
);
|
||||
}
|
||||
|
||||
async estimateRaw(
|
||||
transaction: utils.Deferrable<providers.TransactionRequest>,
|
||||
options?: TransactionOptions,
|
||||
) {
|
||||
if (!this.accountApi) {
|
||||
throw new Error("Personal wallet not connected");
|
||||
}
|
||||
const tx = await ethers.utils.resolveProperties(transaction);
|
||||
return this.estimateTx({
|
||||
target: tx.to || AddressZero,
|
||||
data: tx.data?.toString() || "",
|
||||
value: tx.value || BigNumber.from(0),
|
||||
});
|
||||
return this.estimateTx(
|
||||
{
|
||||
target: tx.to || constants.AddressZero,
|
||||
data: tx.data?.toString() || "",
|
||||
value: tx.value || BigNumber.from(0),
|
||||
gasLimit: tx.gasLimit,
|
||||
maxFeePerGas: tx.maxFeePerGas,
|
||||
maxPriorityFeePerGas: tx.maxPriorityFeePerGas,
|
||||
nonce: tx.nonce,
|
||||
},
|
||||
options,
|
||||
);
|
||||
}
|
||||
|
||||
async estimateBatch(transactions: Transaction<any>[]) {
|
||||
async estimateBatch(
|
||||
transactions: Transaction<any>[],
|
||||
options?: TransactionOptions,
|
||||
) {
|
||||
if (!this.accountApi) {
|
||||
throw new Error("Personal wallet not connected");
|
||||
}
|
||||
@@ -317,13 +357,21 @@ export class SmartWalletConnector extends Connector<SmartWalletConnectionArgs> {
|
||||
target: tx.getTarget(),
|
||||
data: tx.encode(),
|
||||
value: await tx.getValue(),
|
||||
gasLimit: await tx.getOverrides().gasLimit,
|
||||
maxFeePerGas: await tx.getOverrides().maxFeePerGas,
|
||||
maxPriorityFeePerGas: await tx.getOverrides().maxPriorityFeePerGas,
|
||||
nonce: await tx.getOverrides().nonce,
|
||||
},
|
||||
{
|
||||
...options,
|
||||
batchData,
|
||||
},
|
||||
batchData,
|
||||
);
|
||||
}
|
||||
|
||||
async estimateBatchRaw(
|
||||
transactions: utils.Deferrable<providers.TransactionRequest>[],
|
||||
options?: TransactionOptions,
|
||||
) {
|
||||
if (!this.accountApi) {
|
||||
throw new Error("Personal wallet not connected");
|
||||
@@ -334,8 +382,15 @@ export class SmartWalletConnector extends Connector<SmartWalletConnectionArgs> {
|
||||
target: tx.getTarget(),
|
||||
data: tx.encode(),
|
||||
value: await tx.getValue(),
|
||||
gasLimit: await tx.getOverrides().gasLimit,
|
||||
maxFeePerGas: await tx.getOverrides().maxFeePerGas,
|
||||
maxPriorityFeePerGas: await tx.getOverrides().maxPriorityFeePerGas,
|
||||
nonce: await tx.getOverrides().nonce,
|
||||
},
|
||||
{
|
||||
...options,
|
||||
batchData,
|
||||
},
|
||||
batchData,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -346,7 +401,7 @@ export class SmartWalletConnector extends Connector<SmartWalletConnectionArgs> {
|
||||
* Note that this is not necessary as the smart wallet will be deployed automatically on the first transaction the user makes.
|
||||
* @returns The transaction receipt
|
||||
*/
|
||||
async deploy(): Promise<TransactionResult> {
|
||||
async deploy(options?: TransactionOptions): Promise<TransactionResult> {
|
||||
if (!this.accountApi) {
|
||||
throw new Error("Personal wallet not connected");
|
||||
}
|
||||
@@ -357,10 +412,13 @@ export class SmartWalletConnector extends Connector<SmartWalletConnectionArgs> {
|
||||
data: "0x",
|
||||
},
|
||||
{
|
||||
targets: [],
|
||||
data: [],
|
||||
values: [],
|
||||
}, // batched tx flag to avoid hitting the Router fallback method
|
||||
...options,
|
||||
batchData: {
|
||||
targets: [],
|
||||
data: [],
|
||||
values: [],
|
||||
}, // batched tx flag to avoid hitting the Router fallback method
|
||||
},
|
||||
);
|
||||
const receipt = await tx.wait();
|
||||
return { receipt };
|
||||
@@ -377,10 +435,10 @@ export class SmartWalletConnector extends Connector<SmartWalletConnectionArgs> {
|
||||
return await this.accountApi.isAcountDeployed();
|
||||
}
|
||||
|
||||
async deployIfNeeded(): Promise<void> {
|
||||
async deployIfNeeded(options?: TransactionOptions): Promise<void> {
|
||||
const isDeployed = await this.isDeployed();
|
||||
if (!isDeployed) {
|
||||
await this.deploy();
|
||||
await this.deploy(options);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -523,9 +581,9 @@ export class SmartWalletConnector extends Connector<SmartWalletConnectionArgs> {
|
||||
|
||||
private async estimateTx(
|
||||
tx: TransactionDetailsForUserOp,
|
||||
batchData?: BatchData,
|
||||
options?: UserOpOptions,
|
||||
) {
|
||||
if (!this.accountApi) {
|
||||
if (!this.accountApi || !this.aaProvider) {
|
||||
throw new Error("Personal wallet not connected");
|
||||
}
|
||||
let deployGasLimit = BigNumber.from(0);
|
||||
@@ -536,12 +594,16 @@ export class SmartWalletConnector extends Connector<SmartWalletConnectionArgs> {
|
||||
if (!isDeployed) {
|
||||
deployGasLimit = await this.estimateDeploymentGasLimit();
|
||||
}
|
||||
const [{ callGasLimit: transactionGasLimit }, gasPrice] = await Promise.all(
|
||||
[
|
||||
this.accountApi.encodeUserOpCallDataAndGasLimit(tx, batchData),
|
||||
getGasPrice(provider),
|
||||
],
|
||||
);
|
||||
const [userOp, gasPrice] = await Promise.all([
|
||||
this.accountApi.createUnsignedUserOp(
|
||||
this.aaProvider.httpRpcClient,
|
||||
tx,
|
||||
options,
|
||||
),
|
||||
getGasPrice(provider),
|
||||
]);
|
||||
const resolved = await utils.resolveProperties(userOp);
|
||||
const transactionGasLimit = BigNumber.from(resolved.callGasLimit);
|
||||
const transactionCost = transactionGasLimit.mul(gasPrice);
|
||||
const deployCost = deployGasLimit.mul(gasPrice);
|
||||
const totalCost = deployCost.add(transactionCost);
|
||||
@@ -583,7 +645,7 @@ export class SmartWalletConnector extends Connector<SmartWalletConnectionArgs> {
|
||||
ethers.utils.resolveProperties(transaction),
|
||||
),
|
||||
);
|
||||
const targets = resolvedTxs.map((tx) => tx.to || AddressZero);
|
||||
const targets = resolvedTxs.map((tx) => tx.to || constants.AddressZero);
|
||||
const data = resolvedTxs.map((tx) => tx.data || "0x");
|
||||
const values = resolvedTxs.map((tx) => tx.value || BigNumber.from(0));
|
||||
return {
|
||||
|
||||
@@ -5,18 +5,18 @@ import {
|
||||
Transaction,
|
||||
} from "@thirdweb-dev/sdk";
|
||||
import { BigNumberish, BigNumber, ethers, utils, BytesLike } from "ethers";
|
||||
import { AccountApiParams } from "../types";
|
||||
import { ProviderConfig } from "../types";
|
||||
import { BaseAccountAPI } from "./base-api";
|
||||
import { ACCOUNT_CORE_ABI } from "./constants";
|
||||
|
||||
export class AccountAPI extends BaseAccountAPI {
|
||||
sdk: ThirdwebSDK;
|
||||
params: AccountApiParams;
|
||||
params: ProviderConfig;
|
||||
accountContract?: SmartContract;
|
||||
factoryContract?: SmartContract;
|
||||
|
||||
constructor(
|
||||
params: AccountApiParams,
|
||||
params: ProviderConfig,
|
||||
originalProvider: ethers.providers.Provider,
|
||||
) {
|
||||
super({
|
||||
@@ -30,6 +30,9 @@ export class AccountAPI extends BaseAccountAPI {
|
||||
this.sdk = ThirdwebSDK.fromPrivateKey(LOCAL_NODE_PKEY, params.chain, {
|
||||
clientId: params.clientId,
|
||||
secretKey: params.secretKey,
|
||||
// @ts-expect-error expected chain type error
|
||||
supportedChains:
|
||||
typeof params.chain === "object" ? [params.chain] : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,51 +1,23 @@
|
||||
import {
|
||||
ethers,
|
||||
BigNumber,
|
||||
BigNumberish,
|
||||
providers,
|
||||
utils,
|
||||
BytesLike,
|
||||
} from "ethers";
|
||||
import { ethers, BigNumber, BigNumberish, providers } from "ethers";
|
||||
import {
|
||||
EntryPoint,
|
||||
EntryPoint__factory,
|
||||
UserOperationStruct,
|
||||
} from "@account-abstraction/contracts";
|
||||
|
||||
import { NotPromise, packUserOp } from "@account-abstraction/utils";
|
||||
import {
|
||||
GasOverheads,
|
||||
PaymasterAPI,
|
||||
calcPreVerificationGas,
|
||||
} from "@account-abstraction/sdk";
|
||||
import { TransactionDetailsForUserOp } from "./transaction-details";
|
||||
import { getUserOpHashV06 } from "./utils";
|
||||
import { DUMMY_PAYMASTER_AND_DATA, SIG_SIZE } from "./paymaster";
|
||||
import {
|
||||
CeloAlfajoresTestnet,
|
||||
CeloBaklavaTestnet,
|
||||
Celo,
|
||||
} from "@thirdweb-dev/chains";
|
||||
import { Transaction, getDynamicFeeData } from "@thirdweb-dev/sdk";
|
||||
import { HttpRpcClient } from "./http-rpc-client";
|
||||
import type { BaseApiParams, PaymasterAPI, UserOpOptions } from "../types";
|
||||
|
||||
export type BatchData = {
|
||||
targets: (string | undefined)[];
|
||||
data: BytesLike[];
|
||||
values: BigNumberish[];
|
||||
};
|
||||
|
||||
export interface BaseApiParams {
|
||||
provider: providers.Provider;
|
||||
entryPointAddress: string;
|
||||
accountAddress?: string;
|
||||
overheads?: Partial<GasOverheads>;
|
||||
paymasterAPI?: PaymasterAPI;
|
||||
}
|
||||
|
||||
export interface UserOpResult {
|
||||
transactionHash: string;
|
||||
success: boolean;
|
||||
}
|
||||
const DUMMY_SIGNATURE =
|
||||
"0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c";
|
||||
|
||||
/**
|
||||
* Base class for all Smart Wallet ERC-4337 Clients to implement.
|
||||
@@ -67,10 +39,10 @@ export abstract class BaseAccountAPI {
|
||||
private readonly entryPointView: EntryPoint;
|
||||
|
||||
provider: providers.Provider;
|
||||
overheads?: Partial<GasOverheads>;
|
||||
entryPointAddress: string;
|
||||
paymasterAPI: PaymasterAPI;
|
||||
accountAddress?: string;
|
||||
paymasterAPI?: PaymasterAPI;
|
||||
gasless?: boolean;
|
||||
|
||||
/**
|
||||
* base constructor.
|
||||
@@ -78,10 +50,10 @@ export abstract class BaseAccountAPI {
|
||||
*/
|
||||
protected constructor(params: BaseApiParams) {
|
||||
this.provider = params.provider;
|
||||
this.overheads = params.overheads;
|
||||
this.entryPointAddress = params.entryPointAddress;
|
||||
this.accountAddress = params.accountAddress;
|
||||
this.paymasterAPI = params.paymasterAPI;
|
||||
this.gasless = params.gasless;
|
||||
|
||||
// factory "connect" define the contract address. the contract "connect" defines the "from" address.
|
||||
this.entryPointView = EntryPoint__factory.connect(
|
||||
@@ -160,85 +132,6 @@ export abstract class BaseAccountAPI {
|
||||
return 100000;
|
||||
}
|
||||
|
||||
/**
|
||||
* should cover cost of putting calldata on-chain, and some overhead.
|
||||
* actual overhead depends on the expected bundle size
|
||||
*/
|
||||
async getPreVerificationGas(
|
||||
userOp: Partial<UserOperationStruct>,
|
||||
): Promise<number> {
|
||||
const p = await utils.resolveProperties(userOp);
|
||||
return calcPreVerificationGas(p, this.overheads);
|
||||
}
|
||||
|
||||
/**
|
||||
* ABI-encode a user operation. used for calldata cost estimation
|
||||
*/
|
||||
packUserOp(userOp: NotPromise<UserOperationStruct>): string {
|
||||
return packUserOp(userOp, false);
|
||||
}
|
||||
|
||||
async encodeUserOpCallDataAndGasLimit(
|
||||
detailsForUserOp: TransactionDetailsForUserOp,
|
||||
batchData?: BatchData,
|
||||
): Promise<{ callData: string; callGasLimit: BigNumber }> {
|
||||
const value = parseNumber(detailsForUserOp.value) ?? BigNumber.from(0);
|
||||
const callData = batchData
|
||||
? detailsForUserOp.data
|
||||
: await this.prepareExecute(
|
||||
detailsForUserOp.target,
|
||||
value,
|
||||
detailsForUserOp.data,
|
||||
).then((tx) => tx.encode());
|
||||
|
||||
let callGasLimit: BigNumber;
|
||||
const isPhantom = await this.checkAccountPhantom();
|
||||
if (isPhantom) {
|
||||
// when the account is not deployed yet, we simulate the call to the target contract directly
|
||||
if (batchData) {
|
||||
const limits = await Promise.all(
|
||||
batchData.targets.map((_, i) =>
|
||||
this.provider.estimateGas({
|
||||
from: this.getAccountAddress(),
|
||||
to: batchData.targets[i],
|
||||
data: batchData.data[i],
|
||||
value: batchData.values[i],
|
||||
}),
|
||||
),
|
||||
);
|
||||
callGasLimit = limits.reduce((a, b) => a.add(b), BigNumber.from(0));
|
||||
} else {
|
||||
callGasLimit = await this.provider.estimateGas({
|
||||
from: this.getAccountAddress(),
|
||||
to: detailsForUserOp.target,
|
||||
data: detailsForUserOp.data,
|
||||
value: detailsForUserOp.value,
|
||||
});
|
||||
}
|
||||
|
||||
// add 20% overhead for entrypoint checks
|
||||
callGasLimit = callGasLimit.mul(120).div(100);
|
||||
// if the estimation is too low, we use a fixed value of 500k
|
||||
if (callGasLimit.lt(30000)) {
|
||||
callGasLimit = BigNumber.from(500000);
|
||||
}
|
||||
} else {
|
||||
callGasLimit =
|
||||
parseNumber(detailsForUserOp.gasLimit) ??
|
||||
(await this.provider.estimateGas({
|
||||
from: this.entryPointAddress,
|
||||
to: this.getAccountAddress(),
|
||||
data: callData,
|
||||
value: detailsForUserOp.value,
|
||||
}));
|
||||
}
|
||||
|
||||
return {
|
||||
callData,
|
||||
callGasLimit,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* return userOpHash for signing.
|
||||
* This value matches entryPoint.getUserOpHash (calculated off-chain, to avoid a view call)
|
||||
@@ -276,25 +169,19 @@ export abstract class BaseAccountAPI {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* create a UserOperation, filling all details (except signature)
|
||||
* - if account is not yet created, add initCode to deploy it.
|
||||
* - if gas or nonce are missing, read them from the chain (note that we can't fill gaslimit before the account is created)
|
||||
* @param info - transaction details for the userOp
|
||||
*/
|
||||
async createUnsignedUserOp(
|
||||
httpRpcClient: HttpRpcClient,
|
||||
info: TransactionDetailsForUserOp,
|
||||
batchData?: BatchData,
|
||||
options?: UserOpOptions,
|
||||
): Promise<UserOperationStruct> {
|
||||
const { callData, callGasLimit } =
|
||||
await this.encodeUserOpCallDataAndGasLimit(info, batchData);
|
||||
// construct the userOp without gasLimit or preVerifictaionGas
|
||||
const initCode = await this.getInitCode();
|
||||
|
||||
const initGas = await this.estimateCreationGas(initCode);
|
||||
const verificationGasLimit = BigNumber.from(
|
||||
await this.getVerificationGasLimit(),
|
||||
).add(initGas);
|
||||
|
||||
const value = parseNumber(info.value) ?? BigNumber.from(0);
|
||||
const callData = options?.batchData
|
||||
? info.data
|
||||
: await this.prepareExecute(info.target, value, info.data).then((tx) =>
|
||||
tx.encode(),
|
||||
);
|
||||
let { maxFeePerGas, maxPriorityFeePerGas } = info;
|
||||
if (!maxFeePerGas || !maxPriorityFeePerGas) {
|
||||
const feeData = await getDynamicFeeData(
|
||||
@@ -305,7 +192,6 @@ export abstract class BaseAccountAPI {
|
||||
}
|
||||
if (!maxFeePerGas) {
|
||||
maxFeePerGas = feeData.maxFeePerGas ?? undefined;
|
||||
maxPriorityFeePerGas = feeData.maxPriorityFeePerGas ?? undefined;
|
||||
const network = await this.provider.getNetwork();
|
||||
const chainId = network.chainId;
|
||||
|
||||
@@ -318,69 +204,101 @@ export abstract class BaseAccountAPI {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const partialUserOp: any = {
|
||||
sender: this.getAccountAddress(),
|
||||
nonce: info.nonce ?? this.getNonce(),
|
||||
if (!maxFeePerGas || !maxPriorityFeePerGas) {
|
||||
throw new Error(
|
||||
"maxFeePerGas or maxPriorityFeePerGas could not be calculated, please pass them explicitely",
|
||||
);
|
||||
}
|
||||
const [sender, nonce] = await Promise.all([
|
||||
this.getAccountAddress(),
|
||||
info.nonce ? Promise.resolve(info.nonce) : this.getNonce(),
|
||||
]);
|
||||
const partialOp: UserOperationStruct = {
|
||||
sender,
|
||||
nonce,
|
||||
initCode,
|
||||
callData,
|
||||
callGasLimit,
|
||||
verificationGasLimit,
|
||||
maxFeePerGas,
|
||||
maxPriorityFeePerGas,
|
||||
callGasLimit: BigNumber.from(1000000),
|
||||
verificationGasLimit: BigNumber.from(1000000),
|
||||
preVerificationGas: BigNumber.from(1000000),
|
||||
paymasterAndData: "0x",
|
||||
signature: DUMMY_SIGNATURE,
|
||||
};
|
||||
|
||||
let paymasterAndData: string | undefined;
|
||||
let userOp = partialUserOp;
|
||||
if (this.paymasterAPI) {
|
||||
// fill (partial) preVerificationGas (all except the cost of the generated paymasterAndData)
|
||||
try {
|
||||
// userOp.preVerificationGas contains a promise that will resolve to an error.
|
||||
await ethers.utils.resolveProperties(userOp);
|
||||
// eslint-disable-next-line no-empty
|
||||
} catch (_) {}
|
||||
const pmOp: Partial<UserOperationStruct> = {
|
||||
sender: userOp.sender,
|
||||
nonce: userOp.nonce,
|
||||
initCode: userOp.initCode,
|
||||
callData: userOp.callData,
|
||||
callGasLimit: userOp.callGasLimit,
|
||||
verificationGasLimit: userOp.verificationGasLimit,
|
||||
maxFeePerGas: userOp.maxFeePerGas,
|
||||
maxPriorityFeePerGas: userOp.maxPriorityFeePerGas,
|
||||
// A dummy value here is required in order to calculate a correct preVerificationGas value.
|
||||
paymasterAndData: DUMMY_PAYMASTER_AND_DATA,
|
||||
signature: ethers.utils.hexlify(Buffer.alloc(SIG_SIZE, 1)),
|
||||
};
|
||||
userOp = await ethers.utils.resolveProperties(pmOp);
|
||||
const preVerificationGas = calcPreVerificationGas(userOp);
|
||||
userOp.preVerificationGas = preVerificationGas;
|
||||
paymasterAndData = await this.paymasterAPI.getPaymasterAndData(userOp);
|
||||
if (paymasterAndData === "0x") {
|
||||
paymasterAndData = undefined;
|
||||
// paymaster data + maybe used for estimation as well
|
||||
const gasless =
|
||||
options?.gasless !== undefined ? options.gasless : this.gasless;
|
||||
if (gasless) {
|
||||
const paymasterResult = await this.paymasterAPI.getPaymasterAndData(
|
||||
partialOp,
|
||||
);
|
||||
const paymasterAndData = paymasterResult.paymasterAndData;
|
||||
if (paymasterAndData && paymasterAndData !== "0x") {
|
||||
partialOp.paymasterAndData = paymasterAndData;
|
||||
}
|
||||
// paymaster can have the gas limits in the response
|
||||
if (
|
||||
paymasterResult.callGasLimit &&
|
||||
paymasterResult.verificationGasLimit &&
|
||||
paymasterResult.preVerificationGas
|
||||
) {
|
||||
partialOp.callGasLimit = BigNumber.from(paymasterResult.callGasLimit);
|
||||
partialOp.verificationGasLimit = BigNumber.from(
|
||||
paymasterResult.verificationGasLimit,
|
||||
);
|
||||
partialOp.preVerificationGas = BigNumber.from(
|
||||
paymasterResult.preVerificationGas,
|
||||
);
|
||||
} else {
|
||||
// otherwise fallback to bundler for gas limits
|
||||
let estimates;
|
||||
try {
|
||||
estimates = await httpRpcClient.estimateUserOpGas(partialOp);
|
||||
} catch (error: any) {
|
||||
throw this.unwrapBundlerError(error);
|
||||
}
|
||||
partialOp.callGasLimit = BigNumber.from(estimates.callGasLimit);
|
||||
partialOp.verificationGasLimit = BigNumber.from(
|
||||
estimates.verificationGasLimit,
|
||||
);
|
||||
partialOp.preVerificationGas = BigNumber.from(
|
||||
estimates.preVerificationGas,
|
||||
);
|
||||
// need paymaster to re-sign after estimates
|
||||
const paymasterResult2 = await this.paymasterAPI.getPaymasterAndData(
|
||||
partialOp,
|
||||
);
|
||||
if (
|
||||
paymasterResult2.paymasterAndData &&
|
||||
paymasterResult2.paymasterAndData !== "0x"
|
||||
) {
|
||||
partialOp.paymasterAndData = paymasterResult2.paymasterAndData;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// query bundler for gas limits
|
||||
let estimates;
|
||||
try {
|
||||
estimates = await httpRpcClient.estimateUserOpGas(partialOp);
|
||||
} catch (error: any) {
|
||||
throw this.unwrapBundlerError(error);
|
||||
}
|
||||
|
||||
partialOp.callGasLimit = BigNumber.from(estimates.callGasLimit);
|
||||
partialOp.verificationGasLimit = BigNumber.from(
|
||||
estimates.verificationGasLimit,
|
||||
);
|
||||
partialOp.preVerificationGas = BigNumber.from(
|
||||
estimates.preVerificationGas,
|
||||
);
|
||||
}
|
||||
|
||||
if (paymasterAndData) {
|
||||
userOp.paymasterAndData = paymasterAndData;
|
||||
return {
|
||||
...userOp,
|
||||
signature: "",
|
||||
};
|
||||
} else {
|
||||
const modifiedOp = {
|
||||
...userOp,
|
||||
paymasterAndData: "0x",
|
||||
};
|
||||
modifiedOp.preVerificationGas = await this.getPreVerificationGas(
|
||||
modifiedOp,
|
||||
);
|
||||
return {
|
||||
...modifiedOp,
|
||||
signature: "",
|
||||
};
|
||||
}
|
||||
return {
|
||||
...partialOp,
|
||||
signature: "",
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -396,19 +314,6 @@ export abstract class BaseAccountAPI {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* helper method: create and sign a user operation.
|
||||
* @param info - transaction details for the userOp
|
||||
*/
|
||||
async createSignedUserOp(
|
||||
info: TransactionDetailsForUserOp,
|
||||
batchData?: BatchData,
|
||||
): Promise<UserOperationStruct> {
|
||||
return await this.signUserOp(
|
||||
await this.createUnsignedUserOp(info, batchData),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* get the transaction that has this userOpHash mined, or null if not found
|
||||
* @param userOpHash - returned by sendUserOpToBundler (or by getUserOpHash..)
|
||||
@@ -433,6 +338,12 @@ export abstract class BaseAccountAPI {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private unwrapBundlerError(error: any) {
|
||||
const message =
|
||||
error?.error?.message || error.error || error.message || error;
|
||||
return new Error(message);
|
||||
}
|
||||
}
|
||||
|
||||
function parseNumber(a: any): BigNumber | null {
|
||||
|
||||
@@ -4,18 +4,18 @@ import {
|
||||
EntryPoint,
|
||||
UserOperationStruct,
|
||||
} from "@account-abstraction/contracts";
|
||||
import { ClientConfig } from "@account-abstraction/sdk";
|
||||
import { UserOperationEventListener } from "./userop-event";
|
||||
import { BaseAccountAPI } from "./base-api";
|
||||
import { ERC4337EthersSigner } from "./erc4337-signer";
|
||||
import { HttpRpcClient } from "./http-rpc-client";
|
||||
import { ProviderConfig } from "../types";
|
||||
|
||||
export class ERC4337EthersProvider extends providers.BaseProvider {
|
||||
readonly signer: ERC4337EthersSigner;
|
||||
|
||||
constructor(
|
||||
readonly chainId: number,
|
||||
readonly config: ClientConfig,
|
||||
readonly config: ProviderConfig,
|
||||
readonly originalSigner: Signer,
|
||||
readonly originalProvider: providers.BaseProvider,
|
||||
readonly httpRpcClient: HttpRpcClient,
|
||||
@@ -46,15 +46,8 @@ export class ERC4337EthersProvider extends providers.BaseProvider {
|
||||
throw new Error("Should not get here. Investigate.");
|
||||
}
|
||||
if (method === "estimateGas") {
|
||||
// hijack this to estimate gas from the entrypoint instead
|
||||
const { callGasLimit } =
|
||||
await this.smartAccountAPI.encodeUserOpCallDataAndGasLimit({
|
||||
target: params.transaction.to,
|
||||
data: params.transaction.data,
|
||||
value: params.transaction.value,
|
||||
gasLimit: params.transaction.gasLimit,
|
||||
});
|
||||
return callGasLimit;
|
||||
// gas estimation does nothing at this layer, sendTransaction will do the gas estimation for the userOp
|
||||
return BigNumber.from(500000);
|
||||
}
|
||||
return await this.originalProvider.perform(method, params);
|
||||
}
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import { ethers, providers, utils } from "ethers";
|
||||
|
||||
import { Bytes, Signer } from "ethers";
|
||||
import { ClientConfig } from "@account-abstraction/sdk";
|
||||
import { BaseAccountAPI, BatchData } from "./base-api";
|
||||
import { BaseAccountAPI } from "./base-api";
|
||||
import type { ERC4337EthersProvider } from "./erc4337-provider";
|
||||
import { HttpRpcClient } from "./http-rpc-client";
|
||||
import { randomNonce } from "./utils";
|
||||
import { deepHexlify } from "@account-abstraction/utils";
|
||||
import { hexlifyUserOp, randomNonce } from "./utils";
|
||||
import { ProviderConfig, UserOpOptions } from "../types";
|
||||
|
||||
export class ERC4337EthersSigner extends Signer {
|
||||
config: ClientConfig;
|
||||
config: ProviderConfig;
|
||||
originalSigner: Signer;
|
||||
erc4337provider: ERC4337EthersProvider;
|
||||
httpRpcClient: HttpRpcClient;
|
||||
@@ -17,7 +16,7 @@ export class ERC4337EthersSigner extends Signer {
|
||||
|
||||
// TODO: we have 'erc4337provider', remove shared dependencies or avoid two-way reference
|
||||
constructor(
|
||||
config: ClientConfig,
|
||||
config: ProviderConfig,
|
||||
originalSigner: Signer,
|
||||
erc4337provider: ERC4337EthersProvider,
|
||||
httpRpcClient: HttpRpcClient,
|
||||
@@ -37,22 +36,26 @@ export class ERC4337EthersSigner extends Signer {
|
||||
// This one is called by Contract. It signs the request and passes in to Provider to be sent.
|
||||
async sendTransaction(
|
||||
transaction: utils.Deferrable<providers.TransactionRequest>,
|
||||
batchData?: BatchData,
|
||||
options?: UserOpOptions,
|
||||
): Promise<providers.TransactionResponse> {
|
||||
const tx = await ethers.utils.resolveProperties(transaction);
|
||||
await this.verifyAllNecessaryFields(tx);
|
||||
|
||||
const multidimensionalNonce = randomNonce();
|
||||
const userOperation = await this.smartAccountAPI.createSignedUserOp(
|
||||
const unsigned = await this.smartAccountAPI.createUnsignedUserOp(
|
||||
this.httpRpcClient,
|
||||
{
|
||||
target: tx.to || "",
|
||||
data: tx.data?.toString() || "0x",
|
||||
value: tx.value,
|
||||
gasLimit: tx.gasLimit,
|
||||
nonce: multidimensionalNonce,
|
||||
maxFeePerGas: tx.maxFeePerGas,
|
||||
maxPriorityFeePerGas: tx.maxPriorityFeePerGas,
|
||||
},
|
||||
batchData,
|
||||
options,
|
||||
);
|
||||
const userOperation = await this.smartAccountAPI.signUserOp(unsigned);
|
||||
|
||||
const transactionResponse =
|
||||
await this.erc4337provider.constructUserOpTransactionResponse(
|
||||
@@ -152,13 +155,14 @@ Code: ${errorCode}`;
|
||||
|
||||
async signTransaction(
|
||||
transaction: utils.Deferrable<providers.TransactionRequest>,
|
||||
batchData?: BatchData,
|
||||
options?: UserOpOptions,
|
||||
): Promise<string> {
|
||||
const tx = await ethers.utils.resolveProperties(transaction);
|
||||
await this.verifyAllNecessaryFields(tx);
|
||||
|
||||
const multidimensionalNonce = randomNonce();
|
||||
const userOperation = await this.smartAccountAPI.createSignedUserOp(
|
||||
const unsigned = await this.smartAccountAPI.createUnsignedUserOp(
|
||||
this.httpRpcClient,
|
||||
{
|
||||
target: tx.to || "",
|
||||
data: tx.data?.toString() || "0x",
|
||||
@@ -166,12 +170,11 @@ Code: ${errorCode}`;
|
||||
gasLimit: tx.gasLimit,
|
||||
nonce: multidimensionalNonce,
|
||||
},
|
||||
batchData,
|
||||
options,
|
||||
);
|
||||
const userOperation = await this.smartAccountAPI.signUserOp(unsigned);
|
||||
|
||||
const userOpString = JSON.stringify(
|
||||
deepHexlify(await utils.resolveProperties(userOperation)),
|
||||
);
|
||||
const userOpString = JSON.stringify(await hexlifyUserOp(userOperation));
|
||||
return userOpString;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { providers, utils } from "ethers";
|
||||
import { UserOperationStruct } from "@account-abstraction/contracts";
|
||||
import { deepHexlify } from "@account-abstraction/utils";
|
||||
import { isTwUrl } from "../../../utils/url";
|
||||
import pkg from "../../../../../package.json";
|
||||
import { hexlifyUserOp } from "./utils";
|
||||
|
||||
const DEBUG = false;
|
||||
export const DEBUG = false; // TODO set as public flag
|
||||
function isBrowser() {
|
||||
return typeof window !== "undefined";
|
||||
}
|
||||
@@ -111,7 +111,7 @@ export class HttpRpcClient {
|
||||
*/
|
||||
async sendUserOpToBundler(userOp1: UserOperationStruct): Promise<string> {
|
||||
await this.initializing;
|
||||
const hexifiedUserOp = deepHexlify(await utils.resolveProperties(userOp1));
|
||||
const hexifiedUserOp = await hexlifyUserOp(userOp1);
|
||||
const jsonRequestData: [UserOperationStruct, string] = [
|
||||
hexifiedUserOp,
|
||||
this.entryPointAddress,
|
||||
@@ -123,11 +123,14 @@ export class HttpRpcClient {
|
||||
]);
|
||||
}
|
||||
|
||||
async estimateUserOpGas(
|
||||
userOp1: Partial<UserOperationStruct>,
|
||||
): Promise<string> {
|
||||
async estimateUserOpGas(userOp1: Partial<UserOperationStruct>): Promise<{
|
||||
preVerificationGas: string;
|
||||
verificationGas: string;
|
||||
verificationGasLimit: string;
|
||||
callGasLimit: string;
|
||||
}> {
|
||||
await this.initializing;
|
||||
const hexifiedUserOp = deepHexlify(await utils.resolveProperties(userOp1));
|
||||
const hexifiedUserOp = await hexlifyUserOp(userOp1);
|
||||
const jsonRequestData: [UserOperationStruct, string] = [
|
||||
hexifiedUserOp,
|
||||
this.entryPointAddress,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { PaymasterAPI } from "@account-abstraction/sdk";
|
||||
import { UserOperationStruct } from "@account-abstraction/contracts";
|
||||
import { toJSON } from "./utils";
|
||||
|
||||
import { hexlifyUserOp } from "./utils";
|
||||
import { isTwUrl } from "../../../utils/url";
|
||||
import { PaymasterAPI, PaymasterResult } from "../types";
|
||||
import { DEBUG } from "./http-rpc-client";
|
||||
|
||||
export const SIG_SIZE = 65;
|
||||
export const DUMMY_PAYMASTER_AND_DATA =
|
||||
@@ -28,7 +28,7 @@ class VerifyingPaymasterAPI extends PaymasterAPI {
|
||||
|
||||
async getPaymasterAndData(
|
||||
userOp: Partial<UserOperationStruct>,
|
||||
): Promise<string> {
|
||||
): Promise<PaymasterResult> {
|
||||
const headers: Record<string, string> = {
|
||||
"Content-Type": "application/json",
|
||||
};
|
||||
@@ -85,7 +85,7 @@ class VerifyingPaymasterAPI extends PaymasterAPI {
|
||||
jsonrpc: "2.0",
|
||||
id: 1,
|
||||
method: "pm_sponsorUserOperation",
|
||||
params: [await toJSON(userOp), { entryPoint: this.entryPoint }],
|
||||
params: [await hexlifyUserOp(userOp), this.entryPoint],
|
||||
}),
|
||||
});
|
||||
const res = await response.json();
|
||||
@@ -101,11 +101,26 @@ Code: ${code}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (DEBUG) {
|
||||
console.debug("Paymaster result:", res);
|
||||
}
|
||||
|
||||
if (res.result) {
|
||||
const result = (res.result as any).paymasterAndData || res.result;
|
||||
return result.toString();
|
||||
// some paymasters return a string, some return an object with more data
|
||||
if (typeof res.result === "string") {
|
||||
return {
|
||||
paymasterAndData: res.result,
|
||||
};
|
||||
} else {
|
||||
return res.result as PaymasterResult;
|
||||
}
|
||||
} else {
|
||||
throw new Error(`Paymaster returned no result from ${this.paymasterUrl}`);
|
||||
const error =
|
||||
res.error?.message ||
|
||||
res.error ||
|
||||
response.statusText ||
|
||||
"unknown error";
|
||||
throw new Error(`Paymaster error from ${this.paymasterUrl}: ${error}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +1,31 @@
|
||||
import { ethers, utils } from "ethers";
|
||||
import { BigNumber, utils } from "ethers";
|
||||
import { UserOperationStruct } from "@account-abstraction/contracts";
|
||||
|
||||
export function toJSON(op: Partial<UserOperationStruct>): Promise<any> {
|
||||
return ethers.utils.resolveProperties(op).then((userOp) =>
|
||||
Object.keys(userOp)
|
||||
.map((key) => {
|
||||
let val = (userOp as any)[key];
|
||||
if (typeof val !== "string" || !val.startsWith("0x")) {
|
||||
val = ethers.utils.hexValue(val);
|
||||
}
|
||||
return [key, val];
|
||||
})
|
||||
.reduce(
|
||||
(set, [k, v]) => ({
|
||||
...set,
|
||||
[k]: v,
|
||||
}),
|
||||
{},
|
||||
),
|
||||
);
|
||||
export async function hexlifyUserOp(
|
||||
op: Partial<UserOperationStruct>,
|
||||
): Promise<any> {
|
||||
const userOp = await utils.resolveProperties(op);
|
||||
return Object.keys(userOp)
|
||||
.map((key) => {
|
||||
let val = (userOp as any)[key];
|
||||
if (typeof val !== "string" || !val.startsWith("0x")) {
|
||||
val = utils.hexValue(val);
|
||||
}
|
||||
return [key, val];
|
||||
})
|
||||
.reduce(
|
||||
(set, [k, v]) => ({
|
||||
...set,
|
||||
[k]: v,
|
||||
}),
|
||||
{},
|
||||
);
|
||||
}
|
||||
|
||||
export async function printOp(
|
||||
op: Partial<UserOperationStruct>,
|
||||
): Promise<string> {
|
||||
return toJSON(op).then((userOp) => JSON.stringify(userOp, null, 2));
|
||||
return hexlifyUserOp(op).then((userOp) => JSON.stringify(userOp, null, 2));
|
||||
}
|
||||
|
||||
// v0.6 userOpHash calculation
|
||||
@@ -100,7 +101,5 @@ export const randomNonce = () => {
|
||||
hexString = "0" + hexString;
|
||||
}
|
||||
hexString = "0x" + hexString;
|
||||
return ethers.BigNumber.from(
|
||||
ethers.utils.concat([hexString, "0x0000000000000000"]),
|
||||
);
|
||||
return BigNumber.from(utils.concat([hexString, "0x0000000000000000"]));
|
||||
};
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
import type { PaymasterAPI } from "@account-abstraction/sdk";
|
||||
import type {
|
||||
ChainOrRpcUrl,
|
||||
SmartContract,
|
||||
Transaction,
|
||||
} from "@thirdweb-dev/sdk";
|
||||
import { ChainOrRpcUrl, SmartContract, Transaction } from "@thirdweb-dev/sdk";
|
||||
import type {
|
||||
BigNumber,
|
||||
BigNumberish,
|
||||
BytesLike,
|
||||
ContractInterface,
|
||||
Signer,
|
||||
providers,
|
||||
} from "ethers";
|
||||
import type { WalletConnectReceiverConfig } from "../../../core/types/walletConnect";
|
||||
import { EVMWallet } from "../../interfaces";
|
||||
import { WalletOptions } from "../../wallets/base";
|
||||
import { BaseApiParams } from "./lib/base-api";
|
||||
import { UserOperationStruct } from "@account-abstraction/contracts";
|
||||
|
||||
export type SmartWalletConfig = {
|
||||
chain: ChainOrRpcUrl;
|
||||
@@ -34,17 +31,6 @@ export type SmartWalletConnectionArgs = {
|
||||
};
|
||||
export type SmartWalletOptions = WalletOptions;
|
||||
|
||||
export interface AccountApiParams
|
||||
extends Omit<BaseApiParams, "provider">,
|
||||
ContractInfo {
|
||||
chain: ChainOrRpcUrl;
|
||||
localSigner: Signer;
|
||||
factoryAddress: string;
|
||||
accountAddress?: string;
|
||||
clientId?: string;
|
||||
secretKey?: string;
|
||||
}
|
||||
|
||||
export interface ProviderConfig extends ContractInfo {
|
||||
chain: ChainOrRpcUrl;
|
||||
localSigner: Signer;
|
||||
@@ -54,7 +40,8 @@ export interface ProviderConfig extends ContractInfo {
|
||||
bundlerUrl: string;
|
||||
factoryAddress: string;
|
||||
accountAddress?: string;
|
||||
paymasterAPI?: PaymasterAPI;
|
||||
paymasterAPI: PaymasterAPI;
|
||||
gasless: boolean;
|
||||
}
|
||||
|
||||
export type ContractInfoInput = {
|
||||
@@ -86,3 +73,52 @@ export type FactoryContractInfo = {
|
||||
) => Promise<Transaction>;
|
||||
getAccountAddress: (factory: SmartContract, owner: string) => Promise<string>;
|
||||
};
|
||||
|
||||
export type PaymasterResult = {
|
||||
paymasterAndData: string;
|
||||
preVerificationGas?: BigNumber;
|
||||
verificationGasLimit?: BigNumber;
|
||||
callGasLimit?: BigNumber;
|
||||
};
|
||||
|
||||
/**
|
||||
* an API to external a UserOperation with paymaster info
|
||||
*/
|
||||
export abstract class PaymasterAPI {
|
||||
/**
|
||||
* @param userOp - a partially-filled UserOperation (without signature and paymasterAndData
|
||||
* note that the "preVerificationGas" is incomplete: it can't account for the
|
||||
* paymasterAndData value, which will only be returned by this method..
|
||||
* @returns the value to put into the PaymasterAndData, undefined to leave it empty
|
||||
*/
|
||||
abstract getPaymasterAndData(
|
||||
userOp: Partial<UserOperationStruct>,
|
||||
): Promise<PaymasterResult>;
|
||||
}
|
||||
|
||||
export interface TransactionOptions {
|
||||
gasless?: boolean;
|
||||
}
|
||||
|
||||
export interface UserOpOptions extends TransactionOptions {
|
||||
batchData?: BatchData;
|
||||
}
|
||||
|
||||
export type BatchData = {
|
||||
targets: (string | undefined)[];
|
||||
data: BytesLike[];
|
||||
values: BigNumberish[];
|
||||
};
|
||||
|
||||
export interface BaseApiParams {
|
||||
provider: providers.Provider;
|
||||
entryPointAddress: string;
|
||||
paymasterAPI: PaymasterAPI;
|
||||
accountAddress?: string;
|
||||
gasless: boolean;
|
||||
}
|
||||
|
||||
export interface UserOpResult {
|
||||
transactionHash: string;
|
||||
success: boolean;
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ export type {
|
||||
AbstractWallet,
|
||||
WalletData,
|
||||
WalletEvents,
|
||||
checkContractWalletSignature,
|
||||
} from "./wallets/abstract";
|
||||
|
||||
export * from "./wallets/paper-wallet";
|
||||
|
||||
@@ -3,7 +3,10 @@ import { getValidChainRPCs, type Chain } from "@thirdweb-dev/chains";
|
||||
export function isTwUrl(url: string): boolean {
|
||||
const host = new URL(url).hostname;
|
||||
return (
|
||||
host.endsWith(".thirdweb.com") || host === "localhost" || host === "0.0.0.0"
|
||||
host.endsWith(".thirdweb.com") ||
|
||||
host.endsWith(".thirdweb-dev.com") ||
|
||||
host === "localhost" ||
|
||||
host === "0.0.0.0"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { AbstractWallet } from "./abstract";
|
||||
import type { Signer } from "ethers";
|
||||
import { ethers, TypedDataDomain, type Signer, TypedDataField } from "ethers";
|
||||
import type { AwsKmsSignerCredentials } from "ethers-aws-kms-signer";
|
||||
|
||||
/**
|
||||
@@ -44,7 +44,23 @@ export class AwsKmsWallet extends AbstractWallet {
|
||||
this.#signer = new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const { AwsKmsSigner } = await import("ethers-aws-kms-signer");
|
||||
resolve(new AwsKmsSigner(this.#options));
|
||||
const signer = new AwsKmsSigner(this.#options);
|
||||
|
||||
// Need to add this because ethers-aws-kms-signer doesn't support
|
||||
(signer as any)._signTypedData = async function (
|
||||
domain: TypedDataDomain,
|
||||
types: Record<string, Array<TypedDataField>>,
|
||||
value: Record<string, any>,
|
||||
) {
|
||||
const hash = ethers.utils._TypedDataEncoder.hash(
|
||||
domain,
|
||||
types,
|
||||
value,
|
||||
);
|
||||
return signer._signDigest(hash);
|
||||
};
|
||||
|
||||
resolve(signer);
|
||||
} catch (err) {
|
||||
// remove the cached promise so we can try again
|
||||
this.#signer = undefined;
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { ConnectParams } from "../interfaces/connector";
|
||||
import type {
|
||||
SmartWalletConfig,
|
||||
SmartWalletConnectionArgs,
|
||||
TransactionOptions,
|
||||
} from "../connectors/smart-wallet/types";
|
||||
import type { SmartWalletConnector as SmartWalletConnectorType } from "../connectors/smart-wallet";
|
||||
import {
|
||||
@@ -29,7 +30,7 @@ export {
|
||||
getUserOpReceipt,
|
||||
} from "../connectors/smart-wallet/utils";
|
||||
|
||||
export type { PaymasterAPI } from "@account-abstraction/sdk";
|
||||
export type { UserOperationStruct } from "@account-abstraction/contracts";
|
||||
|
||||
/**
|
||||
* Let your users connect to a [Smart Wallet](/glossary/smart-wallet).
|
||||
@@ -384,12 +385,16 @@ export class SmartWallet extends AbstractClientWallet<
|
||||
|
||||
/**
|
||||
* Send a single transaction without waiting for confirmations
|
||||
* @param transaction - The transaction to send
|
||||
* @param transaction - the transaction to send
|
||||
* @param options - optional transaction options
|
||||
* @returns The transaction result
|
||||
*/
|
||||
async send(transaction: Transaction): Promise<providers.TransactionResponse> {
|
||||
async send(
|
||||
transaction: Transaction,
|
||||
options?: TransactionOptions,
|
||||
): Promise<providers.TransactionResponse> {
|
||||
const connector = await this.getConnector();
|
||||
return connector.send(transaction);
|
||||
return connector.send(transaction, options);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -405,12 +410,15 @@ export class SmartWallet extends AbstractClientWallet<
|
||||
* The transaction to execute. Must be of type `Transaction` from the [`@thirdweb-dev/sdk`](https://www.npmjs.com/package/\@thirdweb-dev/sdk) package.
|
||||
*
|
||||
* Creating these transactions can be done easily using the [Transaction Builder](https://portal.thirdweb.com/typescript/v4/interact#prepare) from the thirdweb SDK.
|
||||
*
|
||||
* @param options - optional transaction options
|
||||
* @returns `TransactionResult` containing the transaction receipt.
|
||||
*/
|
||||
async execute(transaction: Transaction): Promise<TransactionResult> {
|
||||
async execute(
|
||||
transaction: Transaction,
|
||||
options?: TransactionOptions,
|
||||
): Promise<TransactionResult> {
|
||||
const connector = await this.getConnector();
|
||||
return connector.execute(transaction);
|
||||
return connector.execute(transaction, options);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -419,14 +427,15 @@ export class SmartWallet extends AbstractClientWallet<
|
||||
* An array of transactions to send. Must be of type `Transaction[]` from the [`@thirdweb-dev/sdk`](https://www.npmjs.com/package/\@thirdweb-dev/sdk) package.
|
||||
*
|
||||
* Creating these transactions can be done easily using the [Transaction Builder](typescript/sdk.smartcontract.prepare) from the thirdweb SDK.
|
||||
*
|
||||
* @param options - optional transaction options
|
||||
* @returns `TransactionResult` containing the transaction receipt.
|
||||
*/
|
||||
async sendBatch(
|
||||
transactions: Transaction[],
|
||||
options?: TransactionOptions,
|
||||
): Promise<providers.TransactionResponse> {
|
||||
const connector = await this.getConnector();
|
||||
return connector.sendBatch(transactions);
|
||||
return connector.sendBatch(transactions, options);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -447,105 +456,124 @@ export class SmartWallet extends AbstractClientWallet<
|
||||
*
|
||||
* Creating these transactions can be done easily using the [Transaction Builder](typescript/sdk.smartcontract.prepare) from the thirdweb SDK.
|
||||
*
|
||||
* @param options - optional transaction options
|
||||
* @returns `TransactionResult` containing the transaction receipt.
|
||||
*/
|
||||
async executeBatch(
|
||||
transactions: Transaction<any>[],
|
||||
options?: TransactionOptions,
|
||||
): Promise<TransactionResult> {
|
||||
const connector = await this.getConnector();
|
||||
return connector.executeBatch(transactions);
|
||||
return connector.executeBatch(transactions, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a single raw transaction without waiting for confirmations
|
||||
* @param transaction - The transaction to send
|
||||
* @param transaction - the transaction to send
|
||||
* @param options - optional transaction options
|
||||
* @returns The transaction result
|
||||
*/
|
||||
async sendRaw(
|
||||
transaction: utils.Deferrable<providers.TransactionRequest>,
|
||||
options?: TransactionOptions,
|
||||
): Promise<providers.TransactionResponse> {
|
||||
const connector = await this.getConnector();
|
||||
return connector.sendRaw(transaction);
|
||||
return connector.sendRaw(transaction, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a single raw transaction and wait for confirmations
|
||||
* @param transaction - The transaction to execute
|
||||
* @param transaction - the transaction to execute
|
||||
* @param options - optional transaction options
|
||||
* @returns The transaction receipt
|
||||
*/
|
||||
async executeRaw(
|
||||
transaction: utils.Deferrable<providers.TransactionRequest>,
|
||||
options?: TransactionOptions,
|
||||
): Promise<TransactionResult> {
|
||||
const connector = await this.getConnector();
|
||||
return connector.executeRaw(transaction);
|
||||
return connector.executeRaw(transaction, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Estimate the gas cost of a single transaction
|
||||
* @param transaction - The transaction to estimate
|
||||
* @param transaction - the transaction to estimate
|
||||
* @param options - optional transaction options
|
||||
* @returns
|
||||
*/
|
||||
async estimate(transaction: Transaction<any>) {
|
||||
async estimate(transaction: Transaction<any>, options?: TransactionOptions) {
|
||||
const connector = await this.getConnector();
|
||||
return connector.estimate(transaction);
|
||||
return connector.estimate(transaction, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Estimate the gas cost of a batch of transactions
|
||||
* @param transactions - The transactions to estimate
|
||||
* @param transactions - the transactions to estimate
|
||||
* @param options - optional transaction options
|
||||
* @returns
|
||||
*/
|
||||
async estimateBatch(transactions: Transaction<any>[]) {
|
||||
async estimateBatch(
|
||||
transactions: Transaction<any>[],
|
||||
options?: TransactionOptions,
|
||||
) {
|
||||
const connector = await this.getConnector();
|
||||
return connector.estimateBatch(transactions);
|
||||
return connector.estimateBatch(transactions, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Estimate the gas cost of a single raw transaction
|
||||
* @param transactions - The transactions to estimate
|
||||
* @param transactions - the transactions to estimate
|
||||
* @param options - optional transaction options
|
||||
* @returns
|
||||
*/
|
||||
async estimateRaw(
|
||||
transactions: utils.Deferrable<providers.TransactionRequest>,
|
||||
options?: TransactionOptions,
|
||||
) {
|
||||
const connector = await this.getConnector();
|
||||
return connector.estimateRaw(transactions);
|
||||
return connector.estimateRaw(transactions, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Estimate the gas cost of a batch of raw transactions
|
||||
* @param transactions - The transactions to estimate
|
||||
* @param transactions - the transactions to estimate
|
||||
* @param options - optional transaction options
|
||||
* @returns
|
||||
*/
|
||||
async estimateBatchRaw(
|
||||
transactions: utils.Deferrable<providers.TransactionRequest>[],
|
||||
options?: TransactionOptions,
|
||||
) {
|
||||
const connector = await this.getConnector();
|
||||
return connector.estimateBatchRaw(transactions);
|
||||
return connector.estimateBatchRaw(transactions, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send multiple raw transaction in a batch without waiting for confirmations
|
||||
* @param transactions - The transactions to send
|
||||
* @param transactions - the transactions to send
|
||||
* @param options - optional transaction options
|
||||
* @returns The transaction result
|
||||
*/
|
||||
async sendBatchRaw(
|
||||
transactions: utils.Deferrable<providers.TransactionRequest>[],
|
||||
options?: TransactionOptions,
|
||||
): Promise<providers.TransactionResponse> {
|
||||
const connector = await this.getConnector();
|
||||
return connector.sendBatchRaw(transactions);
|
||||
return connector.sendBatchRaw(transactions, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute multiple raw transactions in a single batch and wait for confirmations
|
||||
* @param transactions - The transactions to execute
|
||||
* @param transactions - the transactions to execute
|
||||
* @param options - optional transaction options
|
||||
* @returns The transaction receipt
|
||||
*/
|
||||
async executeBatchRaw(
|
||||
transactions: utils.Deferrable<providers.TransactionRequest>[],
|
||||
options?: TransactionOptions,
|
||||
): Promise<TransactionResult> {
|
||||
const connector = await this.getConnector();
|
||||
return connector.executeBatchRaw(transactions);
|
||||
return connector.executeBatchRaw(transactions, options);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -557,12 +585,12 @@ export class SmartWallet extends AbstractClientWallet<
|
||||
* ```ts
|
||||
* const tx = await wallet.deploy();
|
||||
* ```
|
||||
*
|
||||
* @param options - optional transaction options
|
||||
* @returns The transaction receipt
|
||||
*/
|
||||
async deploy(): Promise<TransactionResult> {
|
||||
async deploy(options?: TransactionOptions): Promise<TransactionResult> {
|
||||
const connector = await this.getConnector();
|
||||
return connector.deploy();
|
||||
return connector.deploy(options);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -573,12 +601,12 @@ export class SmartWallet extends AbstractClientWallet<
|
||||
* ```ts
|
||||
* await wallet.deployIfNeeded();
|
||||
* ```
|
||||
*
|
||||
* @param options - optional transaction options
|
||||
* @returns The transaction receipt
|
||||
*/
|
||||
async deployIfNeeded(): Promise<void> {
|
||||
async deployIfNeeded(options?: TransactionOptions): Promise<void> {
|
||||
const connector = await this.getConnector();
|
||||
return connector.deployIfNeeded();
|
||||
return connector.deployIfNeeded(options);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
/* eslint-disable better-tree-shaking/no-top-level-side-effects */
|
||||
import { SmartWallet } from "../src/evm/wallets/smart-wallet";
|
||||
import { LocalWallet } from "../src/evm/wallets/local-wallet";
|
||||
import { Mumbai } from "@thirdweb-dev/chains";
|
||||
import { ThirdwebSDK, SmartContract } from "@thirdweb-dev/sdk";
|
||||
|
||||
let smartWallet: SmartWallet;
|
||||
let smartWalletAddress: string;
|
||||
let personalWallet: LocalWallet;
|
||||
let contract: SmartContract;
|
||||
const factoryAddress = "0x13947435c2fe6BE51ED82F6f59C38617a323dB9B";
|
||||
const chain = Mumbai;
|
||||
|
||||
beforeAll(async () => {
|
||||
personalWallet = new LocalWallet();
|
||||
await personalWallet.generate();
|
||||
smartWallet = new SmartWallet({
|
||||
chain,
|
||||
factoryAddress,
|
||||
gasless: true,
|
||||
secretKey: process.env.TW_SECRET_KEY,
|
||||
});
|
||||
smartWalletAddress = await smartWallet.connect({ personalWallet });
|
||||
const sdk = await ThirdwebSDK.fromWallet(smartWallet, chain);
|
||||
contract = await sdk.getContract(
|
||||
"0xD170A53dADb19f62C78AB9982236857B71dbc83A", // mumbai edition drop
|
||||
);
|
||||
});
|
||||
|
||||
describe("SmartWallet core tests", () => {
|
||||
it("can connect", async () => {
|
||||
expect(smartWalletAddress).toHaveLength(42);
|
||||
});
|
||||
|
||||
it("can execute a tx via SDK", async () => {
|
||||
const tx = await contract.erc1155.claim(0, 1);
|
||||
expect(tx.receipt.transactionHash).toHaveLength(66);
|
||||
const isDeployed = await smartWallet.isDeployed();
|
||||
expect(isDeployed).toEqual(true);
|
||||
const balance = await contract.erc1155.balance(0);
|
||||
expect(balance.toNumber()).toEqual(1);
|
||||
});
|
||||
|
||||
it("can estimate a tx", async () => {
|
||||
const preparedTx = await contract.erc1155.claim.prepare(0, 1);
|
||||
const estimates = await smartWallet.estimate(preparedTx);
|
||||
expect(estimates.wei.toNumber()).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("can execute a tx", async () => {
|
||||
const preparedTx = await contract.erc1155.claim.prepare(0, 1);
|
||||
const tx = await smartWallet.execute(preparedTx);
|
||||
expect(tx.receipt.transactionHash).toHaveLength(66);
|
||||
const balance = await contract.erc1155.balance(0);
|
||||
expect(balance.toNumber()).toEqual(2);
|
||||
});
|
||||
|
||||
it("can execute a batched tx", async () => {
|
||||
const preparedTx = await contract.erc1155.claim.prepare(0, 1);
|
||||
const tx = await smartWallet.executeBatch([preparedTx, preparedTx]);
|
||||
expect(tx.receipt.transactionHash).toHaveLength(66);
|
||||
const balance = await contract.erc1155.balance(0);
|
||||
expect(balance.toNumber()).toEqual(4);
|
||||
});
|
||||
|
||||
it("can execute a raw tx", async () => {
|
||||
const preparedTx = await contract.erc1155.claim.prepare(0, 1);
|
||||
const tx = await smartWallet.executeRaw({
|
||||
to: preparedTx.getTarget(),
|
||||
data: preparedTx.encode(),
|
||||
});
|
||||
expect(tx.receipt.transactionHash).toHaveLength(66);
|
||||
const balance = await contract.erc1155.balance(0);
|
||||
expect(balance.toNumber()).toEqual(5);
|
||||
});
|
||||
|
||||
it("can execute a batched raw tx", async () => {
|
||||
const preparedTx = await contract.erc1155.claim.prepare(0, 1);
|
||||
const tx = await smartWallet.executeBatchRaw([
|
||||
{
|
||||
to: preparedTx.getTarget(),
|
||||
data: preparedTx.encode(),
|
||||
},
|
||||
{
|
||||
to: preparedTx.getTarget(),
|
||||
data: preparedTx.encode(),
|
||||
},
|
||||
]);
|
||||
expect(tx.receipt.transactionHash).toHaveLength(66);
|
||||
const balance = await contract.erc1155.balance(0);
|
||||
expect(balance.toNumber()).toEqual(7);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,55 @@
|
||||
/* eslint-disable better-tree-shaking/no-top-level-side-effects */
|
||||
import hardhat from "hardhat";
|
||||
import { ChainInfo, ThirdwebSDK } from "@thirdweb-dev/sdk";
|
||||
import { SmartWallet } from "../src/evm/wallets/smart-wallet";
|
||||
import { EthersWallet } from "../src/evm/wallets/ethers";
|
||||
import { EntryPoint__factory } from "@account-abstraction/contracts";
|
||||
import { AbstractWallet } from "../src";
|
||||
|
||||
// TODO make this work with a mock bundler/paymaster
|
||||
|
||||
const hardhatEthers = (hardhat as any).ethers;
|
||||
|
||||
let personalWallet: AbstractWallet;
|
||||
let factoryAddress: string;
|
||||
const chain: ChainInfo = {
|
||||
chainId: 31337,
|
||||
rpc: ["http://localhost:8545"],
|
||||
nativeCurrency: { name: "ETH", symbol: "ETH", decimals: 18 },
|
||||
slug: "hardhat",
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
await hardhatEthers.provider.send("hardhat_reset", []);
|
||||
const [owner] = await hardhatEthers.getSigners();
|
||||
console.log("owner", owner.address);
|
||||
personalWallet = new EthersWallet(owner);
|
||||
// deploy entrypoint and account factory
|
||||
const sdk = ThirdwebSDK.fromSigner(owner);
|
||||
const entrypointAddress = await sdk.deployer.deployContractWithAbi(
|
||||
EntryPoint__factory.abi,
|
||||
EntryPoint__factory.bytecode,
|
||||
[],
|
||||
);
|
||||
console.log("entryPoint", entrypointAddress);
|
||||
const factory = await sdk.deployer.deployPublishedContract(
|
||||
"deployer.thirdweb.eth",
|
||||
"AccountFactory",
|
||||
[owner.address, entrypointAddress],
|
||||
);
|
||||
console.log("factory", factory);
|
||||
factoryAddress = factory;
|
||||
});
|
||||
|
||||
describe("SmartWallet", () => {
|
||||
it("can connect", async () => {
|
||||
const sw = new SmartWallet({
|
||||
chain,
|
||||
factoryAddress,
|
||||
gasless: false,
|
||||
});
|
||||
const addr = await sw.connect({ personalWallet });
|
||||
console.log("addr", addr);
|
||||
// TODO mock bundler / paymaster
|
||||
});
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"extends": "@thirdweb-dev/tsconfig/sdk.json",
|
||||
"include": ["src", "types"],
|
||||
"exclude": ["dist", "build", "node_modules"],
|
||||
"include": ["src", "types", "test"],
|
||||
"exclude": ["dist", "build", "node_modules", "**/*.test.ts"],
|
||||
"compilerOptions": {
|
||||
"noUncheckedIndexedAccess": true
|
||||
}
|
||||
|
||||
Generated
+21
-109
@@ -1490,12 +1490,6 @@ importers:
|
||||
'@account-abstraction/contracts':
|
||||
specifier: ^0.5.0
|
||||
version: 0.5.0
|
||||
'@account-abstraction/sdk':
|
||||
specifier: ^0.5.0
|
||||
version: 0.5.0
|
||||
'@account-abstraction/utils':
|
||||
specifier: ^0.5.0
|
||||
version: 0.5.0
|
||||
'@blocto/sdk':
|
||||
specifier: ^0.5.4
|
||||
version: 0.5.4
|
||||
@@ -1611,6 +1605,9 @@ importers:
|
||||
'@noble/ed25519':
|
||||
specifier: ^1.7.1
|
||||
version: 1.7.3
|
||||
'@nomiclabs/hardhat-ethers':
|
||||
specifier: ^2.2.3
|
||||
version: 2.2.3([email protected])([email protected])
|
||||
'@preconstruct/cli':
|
||||
specifier: 2.7.0
|
||||
version: 2.7.0
|
||||
@@ -1620,6 +1617,9 @@ importers:
|
||||
'@types/bn.js':
|
||||
specifier: ^5.1.1
|
||||
version: 5.1.1
|
||||
'@types/jest':
|
||||
specifier: ^29.5.3
|
||||
version: 29.5.3
|
||||
abitype:
|
||||
specifier: ^0.2.5
|
||||
version: 0.2.5([email protected])([email protected])
|
||||
@@ -1650,9 +1650,18 @@ importers:
|
||||
ethers-aws-kms-signer:
|
||||
specifier: ^1.3.2
|
||||
version: 1.3.2
|
||||
hardhat:
|
||||
specifier: ^2.17.0
|
||||
version: 2.17.0([email protected])([email protected])
|
||||
jest:
|
||||
specifier: ^29.6.2
|
||||
version: 29.6.2(@types/[email protected])([email protected])
|
||||
rimraf:
|
||||
specifier: ^3.0.2
|
||||
version: 3.0.2
|
||||
ts-jest:
|
||||
specifier: ^29.1.1
|
||||
version: 29.1.1(@babel/[email protected])([email protected])([email protected])([email protected])
|
||||
tweetnacl:
|
||||
specifier: ^1.0.3
|
||||
version: 1.0.3
|
||||
@@ -1973,40 +1982,6 @@ packages:
|
||||
resolution: {integrity: sha512-CKyS9Zh5rcYUM+4B6TlaB9+THHzJ+6TY3tWF5QofqvFpqGNvIhF8ddy6wyCmqZw6TB74/yYv7cYD/RarVudfDg==}
|
||||
dev: false
|
||||
|
||||
/@account-abstraction/[email protected]:
|
||||
resolution: {integrity: sha512-KuEG9UVl2kEhamevFmPJfqY5AQH4fRLnFhfWAdoqwxIZIuSyA8wfyzM9WKnDPSCaiApLvSzckjRwbs4dVoOp2Q==}
|
||||
dependencies:
|
||||
'@account-abstraction/contracts': 0.5.0
|
||||
'@account-abstraction/utils': 0.5.0
|
||||
'@ethersproject/abstract-provider': 5.7.0
|
||||
'@ethersproject/abstract-signer': 5.7.0
|
||||
'@ethersproject/networks': 5.7.1
|
||||
'@ethersproject/properties': 5.7.0
|
||||
'@ethersproject/providers': 5.7.2
|
||||
'@types/debug': 4.1.7
|
||||
debug: 4.3.4([email protected])
|
||||
ethers: 5.7.2
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- supports-color
|
||||
- utf-8-validate
|
||||
dev: false
|
||||
|
||||
/@account-abstraction/[email protected]:
|
||||
resolution: {integrity: sha512-dgXguTn5WgFMmr3wQMdLGEoIMDcIJgzAv74YlHeb2D3Nyy1pByPArSb3eLOOcgxCJSJeqTscpO9P57uhNkkC4A==}
|
||||
dependencies:
|
||||
'@account-abstraction/contracts': 0.5.0
|
||||
'@ethersproject/abi': 5.7.0
|
||||
'@ethersproject/providers': 5.7.2
|
||||
'@openzeppelin/contracts': 4.9.3
|
||||
debug: 4.3.4([email protected])
|
||||
ethers: 5.7.2
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- supports-color
|
||||
- utf-8-validate
|
||||
dev: false
|
||||
|
||||
/@adraffy/[email protected]:
|
||||
resolution: {integrity: sha512-UK0bHA7hh9cR39V+4gl2/NnBBjoXIxkuWAPCaY4X7fbH4L/azIi7ilWOCjMUYfpJgraLUAqkRi2BqrjME8Rynw==}
|
||||
dev: true
|
||||
@@ -5233,7 +5208,7 @@ packages:
|
||||
'@babel/helper-plugin-utils': 7.22.5
|
||||
debug: 4.3.4([email protected])
|
||||
lodash.debounce: 4.0.8
|
||||
resolve: 1.22.1
|
||||
resolve: 1.22.8
|
||||
semver: 7.5.4
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
@@ -8523,13 +8498,6 @@ packages:
|
||||
'@types/node': 18.17.1
|
||||
jest-mock: 29.6.2
|
||||
|
||||
/@jest/[email protected]:
|
||||
resolution: {integrity: sha512-fmKzsidoXQT2KwnrwE0SQq3uj8Z763vzR8LnLBwC2qYWEFpjX8daRsk6rHUM1QvNlEW/UJXNXm59ztmJJWs2Mg==}
|
||||
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||
dependencies:
|
||||
jest-get-type: 29.4.3
|
||||
dev: true
|
||||
|
||||
/@jest/[email protected]:
|
||||
resolution: {integrity: sha512-6zIhM8go3RV2IG4aIZaZbxwpOzz3ZiM23oxAlkquOIole+G6TrbeXnykxWYlqF7kz2HlBjdKtca20x9atkEQYg==}
|
||||
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||
@@ -8673,13 +8641,6 @@ packages:
|
||||
'@sinclair/typebox': 0.24.51
|
||||
dev: false
|
||||
|
||||
/@jest/[email protected]:
|
||||
resolution: {integrity: sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg==}
|
||||
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||
dependencies:
|
||||
'@sinclair/typebox': 0.25.24
|
||||
dev: true
|
||||
|
||||
/@jest/[email protected]:
|
||||
resolution: {integrity: sha512-rxLjXyJBTL4LQeJW3aKo0M/+GkCOXsO+8i9Iu7eDb6KwtP65ayoDsitrdPBtujxQ88k4wI2FNYfa6TOGwSn6cQ==}
|
||||
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||
@@ -11503,10 +11464,6 @@ packages:
|
||||
resolution: {integrity: sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==}
|
||||
dev: false
|
||||
|
||||
/@sinclair/[email protected]:
|
||||
resolution: {integrity: sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==}
|
||||
dev: true
|
||||
|
||||
/@sinclair/[email protected]:
|
||||
resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
|
||||
|
||||
@@ -12735,8 +12692,8 @@ packages:
|
||||
/@types/[email protected]:
|
||||
resolution: {integrity: sha512-1Nq7YrO/vJE/FYnqYyw0FS8LdrjExSgIiHyKg7xPpn+yi8Q4huZryKnkJatN1ZRH89Kw2v33/8ZMB7DuZeSLlA==}
|
||||
dependencies:
|
||||
expect: 29.5.0
|
||||
pretty-format: 29.5.0
|
||||
expect: 29.6.2
|
||||
pretty-format: 29.6.2
|
||||
dev: true
|
||||
|
||||
/@types/[email protected]:
|
||||
@@ -18289,7 +18246,7 @@ packages:
|
||||
minimatch: 3.1.2
|
||||
object.values: 1.1.6
|
||||
resolve: 1.22.1
|
||||
semver: 7.5.4
|
||||
semver: 7.5.3
|
||||
tsconfig-paths: 3.14.2
|
||||
transitivePeerDependencies:
|
||||
- eslint-import-resolver-typescript
|
||||
@@ -18998,17 +18955,6 @@ packages:
|
||||
jest-message-util: 27.5.1
|
||||
dev: false
|
||||
|
||||
/[email protected]:
|
||||
resolution: {integrity: sha512-yM7xqUrCO2JdpFo4XpM82t+PJBFybdqoQuJLDGeDX2ij8NZzqRHyu3Hp188/JX7SWqud+7t4MUdvcgGBICMHZg==}
|
||||
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||
dependencies:
|
||||
'@jest/expect-utils': 29.5.0
|
||||
jest-get-type: 29.4.3
|
||||
jest-matcher-utils: 29.5.0
|
||||
jest-message-util: 29.5.0
|
||||
jest-util: 29.5.0
|
||||
dev: true
|
||||
|
||||
/[email protected]:
|
||||
resolution: {integrity: sha512-iAErsLxJ8C+S02QbLAwgSGSezLQK+XXRDt8IuFXFpwCNw2ECmzZSmjKcCaFVp5VRMk+WAvz6h6jokzEzBFZEuA==}
|
||||
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||
@@ -22098,16 +22044,6 @@ packages:
|
||||
pretty-format: 27.5.1
|
||||
dev: false
|
||||
|
||||
/[email protected]:
|
||||
resolution: {integrity: sha512-lecRtgm/rjIK0CQ7LPQwzCs2VwW6WAahA55YBuI+xqmhm7LAaxokSB8C97yJeYyT+HvQkH741StzpU41wohhWw==}
|
||||
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||
dependencies:
|
||||
chalk: 4.1.2
|
||||
jest-diff: 29.6.2
|
||||
jest-get-type: 29.4.3
|
||||
pretty-format: 29.6.2
|
||||
dev: true
|
||||
|
||||
/[email protected]:
|
||||
resolution: {integrity: sha512-4LiAk3hSSobtomeIAzFTe+N8kL6z0JtF3n6I4fg29iIW7tt99R7ZcIFW34QkX+DuVrf+CUe6wuVOpm7ZKFJzZQ==}
|
||||
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||
@@ -22148,21 +22084,6 @@ packages:
|
||||
stack-utils: 2.0.6
|
||||
dev: false
|
||||
|
||||
/[email protected]:
|
||||
resolution: {integrity: sha512-Kijeg9Dag6CKtIDA7O21zNTACqD5MD/8HfIV8pdD94vFyFuer52SigdC3IQMhab3vACxXMiFk+yMHNdbqtyTGA==}
|
||||
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.22.5
|
||||
'@jest/types': 29.6.1
|
||||
'@types/stack-utils': 2.0.1
|
||||
chalk: 4.1.2
|
||||
graceful-fs: 4.2.11
|
||||
micromatch: 4.0.5
|
||||
pretty-format: 29.6.2
|
||||
slash: 3.0.0
|
||||
stack-utils: 2.0.6
|
||||
dev: true
|
||||
|
||||
/[email protected]:
|
||||
resolution: {integrity: sha512-vnIGYEjoPSuRqV8W9t+Wow95SDp6KPX2Uf7EoeG9G99J2OVh7OSwpS4B6J0NfpEIpfkBNHlBZpA2rblEuEFhZQ==}
|
||||
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||
@@ -22278,7 +22199,7 @@ packages:
|
||||
jest-pnp-resolver: 1.2.3([email protected])
|
||||
jest-util: 29.6.2
|
||||
jest-validate: 29.6.2
|
||||
resolve: 1.22.1
|
||||
resolve: 1.22.8
|
||||
resolve.exports: 2.0.1
|
||||
slash: 3.0.0
|
||||
dev: true
|
||||
@@ -25911,7 +25832,7 @@ packages:
|
||||
postcss: 8.4.21
|
||||
postcss-value-parser: 4.2.0
|
||||
read-cache: 1.0.0
|
||||
resolve: 1.22.1
|
||||
resolve: 1.22.8
|
||||
dev: false
|
||||
|
||||
/[email protected]([email protected]):
|
||||
@@ -26543,15 +26464,6 @@ packages:
|
||||
react-is: 18.2.0
|
||||
dev: false
|
||||
|
||||
/[email protected]:
|
||||
resolution: {integrity: sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw==}
|
||||
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||
dependencies:
|
||||
'@jest/schemas': 29.4.3
|
||||
ansi-styles: 5.2.0
|
||||
react-is: 18.2.0
|
||||
dev: true
|
||||
|
||||
/[email protected]:
|
||||
resolution: {integrity: sha512-1q0oC8eRveTg5nnBEWMXAU2qpv65Gnuf2eCQzSjxpWFkPaPARwqZZDGuNE0zPAZfTCHzIk3A8dIjwlQKKLphyg==}
|
||||
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||
|
||||
Reference in New Issue
Block a user