Compare commits

..
Author SHA1 Message Date
github-actions[bot]GitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
7d18c3edea Version Packages (#2132)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-01-09 20:35:38 -08:00
iketwandGitHub 7d07490ed3 [RN] Adds hideDisconnect prop to ConnectWallet comp (#2137) 2024-01-09 23:40:28 +00:00
22571ba0a9 [Wallets] Better userOp estimations (#2098)
Co-authored-by: Aleš Ferlan <[email protected]>
2024-01-09 21:48:22 +00:00
0ff90b8fba feat: add hideDisconnect prop to ConnectWallet (#2122)
Co-authored-by: Manan Tank <[email protected]>
Co-authored-by: Joaquim Verges <[email protected]>
2024-01-08 20:29:55 -08:00
Adam MajmudarandGitHub e66e906650 Add support for signTypedData to AwsKmsWallet (#2125) 2024-01-09 03:56:26 +00:00
Isaac DubuqueandGitHub b3cf65b9db Payments: Minor bug fix package.json import (#2133) 2024-01-09 01:02:10 +00:00
iketwandGitHub f953c72c4e [RN] Fixes custom balance token display in ConnectWallet details button (#2131) 2024-01-08 20:49:58 +00:00
45 changed files with 820 additions and 534 deletions
+7
View File
@@ -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 -1
View File
@@ -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": {
+8
View File
@@ -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 -1
View File
@@ -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",
+2 -2
View File
@@ -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"]
}
+6
View File
@@ -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 -1
View File
@@ -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;
+8
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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",
+16
View File
@@ -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 -1
View File
@@ -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}
@@ -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}
/>
@@ -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>
@@ -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}
+15
View File
@@ -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 -1
View File
@@ -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;
},
+8
View File
@@ -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 -1
View File
@@ -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",
+12
View File
@@ -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
+9
View File
@@ -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",
};
+11 -4
View File
@@ -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;
}
+1
View File
@@ -20,6 +20,7 @@ export type {
AbstractWallet,
WalletData,
WalletEvents,
checkContractWalletSignature,
} from "./wallets/abstract";
export * from "./wallets/paper-wallet";
+4 -1
View File
@@ -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"
);
}
+18 -2
View File
@@ -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
});
});
+2 -2
View File
@@ -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
}
+21 -109
View File
@@ -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}