Skip to content
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
63 changes: 63 additions & 0 deletions .changeset/tender-dolls-tan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
---
"thirdweb": minor
---

Added session keys to smart wallet options

You can now pass a `sessionKey` to the `smartWallet` options function to immediately add a session key to the smart wallet upon connection.

This is great in combination with an engine backend wallet! Let's you act on behalf of the user from your backend, making executing transactions as easy as a REST API call. Also unblocks automations, like renewing a subscription, or paying for a service.

```ts
const wallet = smartWallet({
sessionKey: {
address: "0x...", // the session key address (ex: engine backend wallet)
permissions: {
approvedTargets: ["0x..."], // allowed contract addresses (or * for all)
nativeTokenLimitPerTransaction: 0.1, // max spend per transaction in ETH
permissionEndTimestamp: new Date(Date.now() + 1000 * 60 * 60), // expiration date
},
},
});

// this will connect the user wallet and add the session key if not already added
await wallet.connect({
client: TEST_CLIENT,
personalAccount,
});
```

You can also pass the `sessionKey` to the `ConnectButton`, `ConnectEmbed` components and `useConnect` hook.

```tsx
<ConnectButton
client={client}
accountAbstraction={{
chain,
sponsorGas: true,
sessionKey: {
address: "0x...",
permissions: {
approvedTargets: "*",
},
},
}}
/>
```

Also works for the `inAppWallet` `smartAccount` option!

```ts
const wallet = inAppWallet({
smartAccount: {
chain,
sponsorGas: true,
sessionKey: {
address: "0x...",
permissions: {
approvedTargets: "*",
},
},
},
});
```
28 changes: 28 additions & 0 deletions packages/thirdweb/src/wallets/smart/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import type { ThirdwebClient } from "../../client/client.js";
import { type ThirdwebContract, getContract } from "../../contract/contract.js";
import { allowance } from "../../extensions/erc20/__generated__/IERC20/read/allowance.js";
import { approve } from "../../extensions/erc20/write/approve.js";
import { isActiveSigner } from "../../extensions/erc4337/__generated__/IAccountPermissions/read/isActiveSigner.js";
import { addSessionKey } from "../../extensions/erc4337/account/addSessionKey.js";
import { sendTransaction } from "../../transaction/actions/send-transaction.js";
import { toSerializableTransaction } from "../../transaction/actions/to-serializable-transaction.js";
import type { WaitForReceiptOptions } from "../../transaction/actions/wait-for-tx-receipt.js";
import {
Expand All @@ -16,6 +19,7 @@ import type { PreparedTransaction } from "../../transaction/prepare-transaction.
import { readContract } from "../../transaction/read-contract.js";
import { getAddress } from "../../utils/address.js";
import { isZkSyncChain } from "../../utils/any-evm/zksync/isZkSyncChain.js";
import { isContractDeployed } from "../../utils/bytecode/is-contract-deployed.js";
import type { Hex } from "../../utils/encoding/hex.js";
import { resolvePromisedValue } from "../../utils/promise/resolve-promised-value.js";
import { parseTypedData } from "../../utils/signatures/helpers/parse-typed-data.js";
Expand Down Expand Up @@ -164,6 +168,30 @@ export async function connectSmartAccount(
adminAccountToSmartAccountMap.set(personalAccount, account);
smartAccountToAdminAccountMap.set(account, personalAccount);

if (options.sessionKey) {
let hasSessionKey = false;
// check if already added
const accountDeployed = await isContractDeployed(accountContract);
if (accountDeployed) {
hasSessionKey = await isActiveSigner({
contract: accountContract,
signer: options.sessionKey.address,
});
}
if (!hasSessionKey) {
const transaction = addSessionKey({
account: personalAccount,
contract: accountContract,
permissions: options.sessionKey.permissions,
sessionKeyAddress: options.sessionKey.address,
});
await sendTransaction({
account: account,
transaction,
});
}
}

return [account, chain] as const;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
} from "../../exports/extensions/erc4337.js";
import { balanceOf } from "../../extensions/erc1155/__generated__/IERC1155/read/balanceOf.js";
import { claimTo } from "../../extensions/erc1155/drops/write/claimTo.js";
import { isActiveSigner } from "../../extensions/erc4337/__generated__/IAccountPermissions/read/isActiveSigner.js";
import { setContractURI } from "../../extensions/marketplace/__generated__/IMarketplace/write/setContractURI.js";
import { estimateGasCost } from "../../transaction/actions/estimate-gas-cost.js";
import { sendAndConfirmTransaction } from "../../transaction/actions/send-and-confirm-transaction.js";
Expand Down Expand Up @@ -458,5 +459,29 @@ describe.runIf(process.env.TW_SECRET_KEY).sequential(
}),
).rejects.toThrowError(/AA21 didn't pay prefund/);
});

it("can use a session key right after connecting", async () => {
const sessionKey = await generateAccount({ client });
const wallet = smartWallet({
chain,
gasless: true,
sessionKey: {
address: sessionKey.address,
permissions: {
approvedTargets: "*",
},
},
});
await wallet.connect({
client: TEST_CLIENT,
personalAccount,
});

const isSigner = await isActiveSigner({
contract: accountContract,
signer: sessionKey.address,
});
expect(isSigner).toEqual(true);
});
},
);
5 changes: 5 additions & 0 deletions packages/thirdweb/src/wallets/smart/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type * as ox__TypedData from "ox/TypedData";
import type { Chain } from "../../chains/types.js";
import type { ThirdwebClient } from "../../client/client.js";
import type { ThirdwebContract } from "../../contract/contract.js";
import type { AccountPermissions } from "../../extensions/erc4337/account/types.js";
import type { PreparedTransaction } from "../../transaction/prepare-transaction.js";
import type { TransactionReceipt } from "../../transaction/types.js";
import type { Hex } from "../../utils/encoding/hex.js";
Expand All @@ -21,6 +22,10 @@ export type SmartWalletOptions = Prettify<
{
chain: Chain; // TODO consider making default chain optional
factoryAddress?: string;
sessionKey?: {
address: string;
permissions: AccountPermissions;
};
overrides?: {
bundlerUrl?: string;
accountAddress?: string;
Expand Down
Loading