Skip to content

[SmartWallet] expose new send/execute smart wallet functions #1800

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Oct 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/new-books-notice.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@thirdweb-dev/sdk": patch
---

Expose getCompositeABI for dynamic contracts
19 changes: 19 additions & 0 deletions .changeset/olive-impalas-knock.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
"@thirdweb-dev/wallets": patch
---

Expose new Smart Wallet transaction functions

Snding raw transactions:

```
smartWallet.sendRaw(tx);
smartWallet.executeRaw(tx); // waits for confirmations
```

Sending raw batched transactions

```
smartWallet.sendBatchRaw(tx);
smartWallet.executeBatchRaw(tx); // waits for confirmations
```
1 change: 1 addition & 0 deletions packages/sdk/src/evm/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export * from "./feature-detection/assertEnabled";
export * from "./feature-detection/detectContractFeature";
export * from "./feature-detection/hasFunction";
export * from "./plugin/joinABIs";
export * from "./plugin/getCompositePluginABI";

export * from "./version-checker";
export * from "./fetchSourceFilesFromMetadata";
Expand Down
8 changes: 4 additions & 4 deletions packages/sdk/src/evm/common/plugin/getCompositePluginABI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
import { ContractWrapper } from "../../core/classes/contract-wrapper";
import { Abi, AbiSchema } from "../../schema/contracts/custom";
import { SDKOptions } from "../../schema/sdk-options";
import { isFeatureEnabled } from "../feature-detection/isFeatureEnabled";
import { isExtensionEnabled } from "../feature-detection/isFeatureEnabled";
import { ThirdwebStorage } from "@thirdweb-dev/storage";
import { providers } from "ethers";
import { joinABIs } from "./joinABIs";
Expand All @@ -14,7 +14,7 @@ import { getPluginABI } from "./getPluginABI";
/**
* @internal
*/
export async function getCompositePluginABI(
export async function getCompositeABI(
address: string,
abi: Abi,
provider: providers.Provider,
Expand All @@ -25,11 +25,11 @@ export async function getCompositePluginABI(

try {
// check if contract is plugin-pattern
const isPluginRouter: boolean = isFeatureEnabled(
const isPluginRouter: boolean = isExtensionEnabled(
AbiSchema.parse(abi),
"PluginRouter",
);
const isbaseRouter: boolean = isFeatureEnabled(
const isbaseRouter: boolean = isExtensionEnabled(
AbiSchema.parse(abi),
"DynamicContract",
);
Expand Down
6 changes: 3 additions & 3 deletions packages/sdk/src/evm/contracts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
import { getPrebuiltInfo } from "../common/legacy";
import { fetchAbiFromAddress } from "../common/metadata-resolver";
import { getCompositeABIfromRelease } from "../common/plugin/getCompositeABIfromRelease";
import { getCompositePluginABI } from "../common/plugin/getCompositePluginABI";
import { getCompositeABI } from "../common/plugin/getCompositePluginABI";
import { ALL_ROLES } from "../common/role";
import { getSignerAndProvider } from "../constants/urls";
import type { NetworkInput } from "../core/types";
Expand Down Expand Up @@ -239,14 +239,14 @@ export const MarketplaceV3Initializer = {

const abi = await fetchAbiFromAddress(address, provider, storage);
if (abi) {
return await getCompositePluginABI(address, abi, provider, {}, storage);
return await getCompositeABI(address, abi, provider, {}, storage);
}

// Deprecated - only needed for backwards compatibility with non-published contracts - should remove in v4
const localAbi = (
await import("@thirdweb-dev/contracts-js/dist/abis/MarketplaceV3.json")
).default;
return await getCompositePluginABI(
return await getCompositeABI(
address,
AbiSchema.parse(localAbi || []),
provider,
Expand Down
4 changes: 2 additions & 2 deletions packages/sdk/src/evm/core/sdk.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { getAllDetectedExtensionNames } from "../common/feature-detection/getAllDetectedFeatureNames";
import { resolveAddress } from "../common/ens/resolveAddress";
import { getCompositePluginABI } from "../common/plugin/getCompositePluginABI";
import { getCompositeABI } from "../common/plugin/getCompositePluginABI";
import { createStorage } from "../common/storage";
import { getChainProvider, isChainConfig } from "../constants/urls";
import { setSupportedChains } from "../constants/chains/supportedChains";
Expand Down Expand Up @@ -842,7 +842,7 @@ export class ThirdwebSDK extends RPCConnectionHandler {
const contract = new SmartContract(
this.getSignerOrProvider(),
resolvedAddress,
await getCompositePluginABI(
await getCompositeABI(
resolvedAddress,
AbiSchema.parse(parsedABI),
provider,
Expand Down
4 changes: 2 additions & 2 deletions packages/sdk/src/evm/functions/getContractFromAbi.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { resolveAddress } from "../common/ens/resolveAddress";
import { getCompositePluginABI } from "../common/plugin/getCompositePluginABI";
import { getCompositeABI } from "../common/plugin/getCompositePluginABI";
import { SmartContract } from "../contracts/smart-contract";
import { NetworkInput } from "../core/types";
import { AddressOrEns } from "../schema/shared/AddressOrEnsSchema";
Expand Down Expand Up @@ -44,7 +44,7 @@ export async function getContractFromAbi(
const contract = new SmartContract(
signer || provider,
resolvedAddress,
await getCompositePluginABI(
await getCompositeABI(
resolvedAddress,
AbiSchema.parse(parsedAbi),
provider,
Expand Down
94 changes: 85 additions & 9 deletions packages/wallets/src/evm/connectors/smart-wallet/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
import { ENTRYPOINT_ADDRESS } from "./lib/constants";
import { EVMWallet } from "../../interfaces";
import { ERC4337EthersSigner } from "./lib/erc4337-signer";
import { BigNumber, ethers, providers } from "ethers";
import { BigNumber, ethers, providers, utils } from "ethers";
import {
ChainOrRpcUrl,
getChainProvider,
Expand All @@ -25,6 +25,7 @@ import {
TransactionResult,
} from "@thirdweb-dev/sdk";
import { AccountAPI } from "./lib/account";
import { AddressZero } from "@account-abstraction/utils";

export class SmartWalletConnector extends Connector<SmartWalletConnectionArgs> {
private config: SmartWalletConfig;
Expand Down Expand Up @@ -164,23 +165,57 @@ export class SmartWalletConnector extends Connector<SmartWalletConnectionArgs> {
}

/**
* Execute a single transaction
* Send a single transaction without waiting for confirmations
* @param transactions
* @returns the transaction receipt
* @returns the awaitable transaction
*/
async execute(transaction: Transaction): Promise<TransactionResult> {
async send(transaction: Transaction): Promise<providers.TransactionResponse> {
const signer = await this.getSigner();
const tx = await signer.sendTransaction({
return signer.sendTransaction({
to: transaction.getTarget(),
data: transaction.encode(),
value: await transaction.getValue(),
});
}

/**
* Execute a single transaction (waiting for confirmations)
* @param transactions
* @returns the transaction receipt
*/
async execute(transaction: Transaction): Promise<TransactionResult> {
const tx = await this.send(transaction);
const receipt = await tx.wait();
return {
receipt,
};
}

async sendBatch(
transactions: Transaction<any>[],
): Promise<providers.TransactionResponse> {
if (!this.accountApi) {
throw new Error("Personal wallet not connected");
}
const signer = await this.getSigner();
const targets = transactions.map((tx) => tx.getTarget());
const data = transactions.map((tx) => tx.encode());
const values = await Promise.all(transactions.map((tx) => tx.getValue()));
const callData = await this.accountApi.encodeExecuteBatch(
targets,
values,
data,
);
return await signer.sendTransaction(
{
to: await signer.getAddress(),
data: callData,
value: 0,
},
true, // batched tx flag
);
}

/**
* Execute multiple transactions in a single batch
* @param transactions
Expand All @@ -189,26 +224,67 @@ export class SmartWalletConnector extends Connector<SmartWalletConnectionArgs> {
async executeBatch(
transactions: Transaction<any>[],
): Promise<TransactionResult> {
const tx = await this.sendBatch(transactions);
const receipt = await tx.wait();
return {
receipt,
};
}

async sendRaw(
transaction: utils.Deferrable<providers.TransactionRequest>,
): Promise<providers.TransactionResponse> {
if (!this.accountApi) {
throw new Error("Personal wallet not connected");
}
const signer = await this.getSigner();
const targets = transactions.map((tx) => tx.getTarget());
const data = transactions.map((tx) => tx.encode());
const values = transactions.map(() => BigNumber.from(0)); // TODO check if we can handle multiple values
return signer.sendTransaction(transaction);
}

async executeRaw(
transaction: utils.Deferrable<providers.TransactionRequest>,
) {
const tx = await this.sendRaw(transaction);
const receipt = await tx.wait();
return {
receipt,
};
}

async sendBatchRaw(
transactions: utils.Deferrable<providers.TransactionRequest>[],
) {
if (!this.accountApi) {
throw new Error("Personal wallet not connected");
}
const signer = await this.getSigner();
const resolvedTxs = await Promise.all(
transactions.map((transaction) =>
ethers.utils.resolveProperties(transaction),
),
);
const targets = resolvedTxs.map((tx) => tx.to || AddressZero);
const data = resolvedTxs.map((tx) => tx.data || "0x");
const values = resolvedTxs.map((tx) => tx.value || BigNumber.from(0));
const callData = await this.accountApi.encodeExecuteBatch(
targets,
values,
data,
);
const tx = await signer.sendTransaction(
return signer.sendTransaction(
{
to: await signer.getAddress(),
data: callData,
value: 0,
},
true, // batched tx flag
);
}

async executeBatchRaw(
transactions: utils.Deferrable<providers.TransactionRequest>[],
) {
const tx = await this.sendBatchRaw(transactions);
const receipt = await tx.wait();
return {
receipt,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { LOCAL_NODE_PKEY, SmartContract, ThirdwebSDK } from "@thirdweb-dev/sdk";
import { BigNumberish, BigNumber, ethers, utils } from "ethers";
import { BigNumberish, BigNumber, ethers, utils, BytesLike } from "ethers";
import { AccountApiParams } from "../types";
import { BaseAccountAPI } from "./base-api";
import { ACCOUNT_CORE_ABI } from "./constants";
Expand Down Expand Up @@ -121,10 +121,10 @@ export class AccountAPI extends BaseAccountAPI {
async encodeExecuteBatch(
targets: string[],
values: BigNumberish[],
datas: string[],
datas: BytesLike[],
): Promise<string> {
const accountContract = await this.getAccountContract();
const tx = await accountContract.prepare("executeBatch", [
const tx = accountContract.prepare("executeBatch", [
targets,
values,
datas,
Expand Down
75 changes: 73 additions & 2 deletions packages/wallets/src/evm/wallets/smart-wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
import { WalletConnectV2Handler } from "../../core/WalletConnect/WalletConnectV2Handler";
import { NoOpWalletConnectHandler } from "../../core/WalletConnect/constants";
import { getValidChainRPCs } from "@thirdweb-dev/chains";
import { providers, utils } from "ethers";

// export types and utils for convenience
export * from "../connectors/smart-wallet/types";
Expand Down Expand Up @@ -106,7 +107,17 @@ export class SmartWallet
}

/**
* Execute a single transaction
* Send a single transaction without waiting for confirmations
* @param transactions
* @returns the transaction result
*/
async send(transaction: Transaction): Promise<providers.TransactionResponse> {
const connector = await this.getConnector();
return connector.send(transaction);
}

/**
* Execute a single transaction and wait for confirmations
* @param transactions
* @returns the transaction receipt
*/
Expand All @@ -116,7 +127,19 @@ export class SmartWallet
}

/**
* Execute multiple transactions in a single batch
* Send a multiple transaction in a batch without waiting for confirmations
* @param transactions
* @returns the transaction result
*/
async sendBatch(
transactions: Transaction[],
): Promise<providers.TransactionResponse> {
const connector = await this.getConnector();
return connector.sendBatch(transactions);
}

/**
* Execute multiple transactions in a single batch and wait for confirmations
* @param transactions
* @returns the transaction receipt
*/
Expand All @@ -127,6 +150,54 @@ export class SmartWallet
return connector.executeBatch(transactions);
}

/**
* Send a single raw transaction without waiting for confirmations
* @param transaction
* @returns the transaction result
*/
async sendRaw(
transaction: utils.Deferrable<providers.TransactionRequest>,
): Promise<providers.TransactionResponse> {
const connector = await this.getConnector();
return connector.sendRaw(transaction);
}

/**
* Execute a single raw transaction and wait for confirmations
* @param transaction
* @returns the transaction receipt
*/
async executeRaw(
transaction: utils.Deferrable<providers.TransactionRequest>,
): Promise<TransactionResult> {
const connector = await this.getConnector();
return connector.executeRaw(transaction);
}

/**
* Send multiple raw transaction in a batch without waiting for confirmations
* @param transaction
* @returns the transaction result
*/
async sendBatchRaw(
transactions: utils.Deferrable<providers.TransactionRequest>[],
): Promise<providers.TransactionResponse> {
const connector = await this.getConnector();
return connector.sendBatchRaw(transactions);
}

/**
* Execute multiple raw transactions in a single batch and wait for confirmations
* @param transaction
* @returns the transaction receipt
*/
async executeBatchRaw(
transactions: utils.Deferrable<providers.TransactionRequest>[],
): Promise<TransactionResult> {
const connector = await this.getConnector();
return connector.executeBatchRaw(transactions);
}

/**
* Manually deploy the smart wallet contract. If already deployed this will throw an error.
* Note that this is not necessary as the smart wallet will be deployed automatically on the first transaction the user makes.
Expand Down