Skip to content

Commit

Permalink
feat(sdk): simplified EIP-7702 support
Browse files Browse the repository at this point in the history
  • Loading branch information
gregfromstl committed Jan 2, 2025
1 parent 78a163c commit 8d493ca
Show file tree
Hide file tree
Showing 16 changed files with 184 additions and 372 deletions.
44 changes: 11 additions & 33 deletions .changeset/clever-beds-knock.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,23 @@
Feature: Adds beta support for EIP-7702 authorization lists

```ts
import { prepareTransaction, sendTransaction } from "thirdweb";
import { prepareTransaction, sendTransaction, signAuthorization } from "thirdweb";

const authorization = await signAuthorization({
request: {
address: "0x...",
chainId: 911867,
nonce: 100n,
},
account: myAccount,
});

const transaction = prepareTransaction({
chain: ANVIL_CHAIN,
client: TEST_CLIENT,
value: 100n,
to: TEST_WALLET_B,
authorizations: [
{
address: "0x...",
chainId: 1,
nonce: 420n,
},
],
authorizationList: authorization,
});

const res = await sendTransaction({
Expand All @@ -27,28 +30,3 @@ const res = await sendTransaction({
});
```

You can access the underlying authorization signing functions like so:

```ts
import { signAuthorization, signedAuthorizations } from "thirdweb";

const signedAuthorization = await signAuthorization({
authorization: {
address: "0x...",
chainId: 1,
nonce: 420n,
},
account: myAccount,
});

const signedAuthorizations = await signedAuthorizations({
authorizations: [
{
address: "0x...",
chainId: 1,
nonce: 420n,
},
],
account: myAccount,
});
```
9 changes: 9 additions & 0 deletions packages/thirdweb/src/exports/thirdweb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -302,3 +302,12 @@ export {
type VerifyTypedDataParams,
verifyTypedData,
} from "../auth/verify-typed-data.js";

/**
* EIP-7702
*/
export type {
AuthorizationRequest,
SignedAuthorization,
} from "../transaction/actions/eip7702/authorization.js";
export { signAuthorization } from "../transaction/actions/eip7702/authorization.js";
9 changes: 9 additions & 0 deletions packages/thirdweb/src/exports/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,12 @@ export type { GaslessOptions } from "../transaction/actions/gasless/types.js";
export type { EngineOptions } from "../transaction/actions/gasless/providers/engine.js";
export type { OpenZeppelinOptions } from "../transaction/actions/gasless/providers/openzeppelin.js";
export type { BiconomyOptions } from "../transaction/actions/gasless/providers/biconomy.js";

/**
* EIP-7702
*/
export type {
AuthorizationRequest,
SignedAuthorization,
} from "../transaction/actions/eip7702/authorization.js";
export { signAuthorization } from "../transaction/actions/eip7702/authorization.js";
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { describe, expect, it } from "vitest";
import { TEST_WALLET_B } from "~test/addresses.js";
import { TEST_ACCOUNT_A } from "~test/test-wallets.js";
import { signAuthorization } from "./authorization.js";

describe("signAuthorization", () => {
it("should sign an authorization", async () => {
const authorization = await signAuthorization({
account: TEST_ACCOUNT_A,
request: {
address: TEST_WALLET_B,
chainId: 911867,
nonce: 0n,
},
});
expect(authorization).toMatchInlineSnapshot(`
{
"address": "0x0000000000000000000000000000000000000002",
"chainId": 911867,
"nonce": 0n,
"r": 3720526934953059641417422884731844424204826752871127418111522219225437830766n,
"s": 23451045058292828843243765241045958975073226494910356096978666517928790374894n,
"yParity": 1,
}
`);
});
});
48 changes: 36 additions & 12 deletions packages/thirdweb/src/transaction/actions/eip7702/authorization.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,58 @@
import type * as ox__Authorization from "ox/Authorization";
import type { Address } from "../../../utils/address.js";
import type { Account } from "../../../wallets/interfaces/wallet.js";

/**
* An EIP-7702 authorization input object.
* An EIP-7702 authorization object fully prepared and ready for signing.
*
* @beta
* @transaction
*/
export type Authorization = {
export type AuthorizationRequest = {
address: Address;
chainId?: number;
nonce?: bigint;
chainId: number;
nonce: bigint;
};

/**
* An EIP-7702 authorization object fully prepared and ready for signing.
* Represents a signed EIP-7702 authorization object.
*
* @beta
* @transaction
*/
export type PreparedAuthorization = {
address: Address;
chainId: number;
nonce: bigint;
};
export type SignedAuthorization = ox__Authorization.ListSigned[number];

/**
* Represents a signed EIP-7702 authorization object.
* Sign the given EIP-7702 authorization object.
* @param options - The options for `signAuthorization`
* Refer to the type [`SignAuthorizationOptions`](https://portal.thirdweb.com/references/typescript/v5/SignAuthorizationOptions)
* @returns The signed authorization object
*
* ```ts
* import { signAuthorization } from "thirdweb";
*
* const authorization = await signAuthorization({
* request: {
* address: "0x...",
* chainId: 911867,
* nonce: 100n,
* },
* account: myAccount,
* });
* ```
*
* @beta
* @transaction
*/
export type SignedAuthorization = ox__Authorization.ListSigned[number];
export async function signAuthorization(options: {
account: Account;
request: AuthorizationRequest;
}): Promise<SignedAuthorization> {
const { account, request } = options;
if (typeof account.signAuthorization === "undefined") {
throw new Error(
"This account type does not yet support signing EIP-7702 authorizations",
);
}
return account.signAuthorization(request);
}
38 changes: 19 additions & 19 deletions packages/thirdweb/src/transaction/actions/estimate-gas.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import * as ox__Hex from "ox/Hex";
import { formatTransactionRequest } from "viem";
import { roundUpGas } from "../../gas/op-gas-fee-reducer.js";
import { resolvePromisedValue } from "../../utils/promise/resolve-promised-value.js";
import type { Prettify } from "../../utils/type-utils.js";
import type { Account } from "../../wallets/interfaces/wallet.js";
import { extractError } from "../extract-error.js";
import type { PreparedTransaction } from "../prepare-transaction.js";
import { resolveAndSignAuthorizations } from "./to-serializable-transaction.js";
import * as ox__Hex from "ox/Hex";

export type EstimateGasOptions = Prettify<
{
Expand All @@ -18,21 +17,21 @@ export type EstimateGasOptions = Prettify<
transaction: PreparedTransaction<any>;
} & (
| {
/**
* The account the transaction would be sent from.
*
* @deprecated Use `from` instead
*/
account: Account;
from?: never;
}
/**
* The account the transaction would be sent from.
*
* @deprecated Use `from` instead
*/
account: Account;
from?: never;
}
| {
account?: never;
/**
* The address the transaction would be sent from.
*/
from?: string | Account;
}
account?: never;
/**
* The address the transaction would be sent from.
*/
from?: string | Account;
}
)
>;

Expand Down Expand Up @@ -66,8 +65,8 @@ export async function estimateGas(
// 3. the passed in wallet's account address
const fromAddress =
typeof options.from === "string"
? options.from ?? undefined
: options.from?.address ?? options.account?.address;
? (options.from ?? undefined)
: (options.from?.address ?? options.account?.address);
const txWithFrom = { ...options.transaction, from: fromAddress };
if (cache.has(txWithFrom)) {
// biome-ignore lint/style/noNonNullAssertion: the `has` above ensures that this will always be set
Expand Down Expand Up @@ -104,7 +103,7 @@ export async function estimateGas(
encode(options.transaction),
resolvePromisedValue(options.transaction.to),
resolvePromisedValue(options.transaction.value),
resolveAndSignAuthorizations(options),
resolvePromisedValue(options.transaction.authorizationList),
]);

// load up the rpc client and the estimateGas function if we need it
Expand Down Expand Up @@ -132,6 +131,7 @@ export async function estimateGas(
})),
}),
);

if (options.transaction.chain.experimental?.increaseZeroByteCount) {
gas = roundUpGas(gas);
}
Expand Down
109 changes: 1 addition & 108 deletions packages/thirdweb/src/transaction/actions/send-transaction.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,9 @@ import { describe, expect, it, vi } from "vitest";
import { TEST_WALLET_B } from "../../../test/src/addresses.js";
import { ANVIL_CHAIN } from "../../../test/src/chains.js";
import { TEST_CLIENT } from "../../../test/src/test-clients.js";
import {
TEST_ACCOUNT_A,
TEST_ACCOUNT_B,
} from "../../../test/src/test-wallets.js";
import { defineChain } from "../../chains/utils.js";
import { getContract } from "../../contract/contract.js";
import { claimTo } from "../../extensions/erc20/drops/write/claimTo.js";
import { getWalletBalance } from "../../wallets/utils/getWalletBalance.js";
import { prepareContractCall } from "../prepare-contract-call.js";
import { TEST_ACCOUNT_A } from "../../../test/src/test-wallets.js";
import { prepareTransaction } from "../prepare-transaction.js";
import * as TransactionStore from "../transaction-store.js";
import { encode } from "./encode.js";
import { sendAndConfirmTransaction } from "./send-and-confirm-transaction.js";
import { sendTransaction } from "./send-transaction.js";

const addTransactionToStore = vi.spyOn(
Expand All @@ -38,103 +28,6 @@ describe("sendTransaction", () => {
expect(res.transactionHash.length).toBe(66);
});

it.only("should send an eip7702 transaction", async () => {
const account = TEST_ACCOUNT_A;
const executor = TEST_ACCOUNT_B;

console.log("ACCOUNT", account.address);
console.log("DELEGATE", executor.address);

const chain = defineChain(911867);
const tokenContract = getContract({
address: "0xAA462a5BE0fc5214507FDB4fB2474a7d5c69065b",
chain,
client: TEST_CLIENT,
});

const claimTx = claimTo({
contract: tokenContract,
quantityInWei: 1000n,
to: account.address,
});

// const transferTx = transfer({
// contract: tokenContract,
// amountWei: 1000n,
// to: "0x2247d5d238d0f9d37184d8332aE0289d1aD9991b",
// });

console.log(
"BALANCE BEFORE",
await getWalletBalance({
address: account.address,
chain,
client: TEST_CLIENT,
tokenAddress: tokenContract.address,
}),
);

// const nonce = await (async () => {
// const rpcRequest = getRpcClient({
// chain,
// client: TEST_CLIENT,
// });
// const { eth_getTransactionCount } = await import(
// "../../rpc/actions/eth_getTransactionCount.js"
// );
// return await eth_getTransactionCount(rpcRequest, {
// address: account.address,
// blockTag: "pending",
// });
// })();

// const signedAuthorization = await account.signAuthorization!({
// address: "0x654F42b74885EE6803F403f077bc0409f1066c58",
// chainId: chain.id,
// nonce: BigInt(nonce),
// });

// console.log("SIGNED AUTHORIZATION", signedAuthorization);

const batchSend = prepareContractCall({
contract: getContract({
address: account.address,
chain,
client: TEST_CLIENT,
}),
method:
"function execute((bytes data, address to, uint256 value)[] calldata calls) external payable",
// authorizationList: [signedAuthorization],
params: [
[
{
data: await encode(claimTx),
to: tokenContract.address,
value: 0n,
},
],
],
});

const batchSendResult = await sendAndConfirmTransaction({
account: executor,
transaction: batchSend,
});
console.log("BATCH SEND RESULT", batchSendResult.transactionHash);

expect(batchSendResult.transactionHash.length).toBe(66);

console.log(
"BALANCE AFTER",
await getWalletBalance({
address: account.address,
chain,
client: TEST_CLIENT,
tokenAddress: tokenContract.address,
}),
);
});

it("should add transaction to session", async () => {
const transaction = prepareTransaction({
chain: ANVIL_CHAIN,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ export function signTransaction({
privateKey,
}: SignTransactionOptions): Hex {
const serializedTransaction = serializeTransaction({ transaction });
console.log("serializedTransaction", serializedTransaction);

const signature = ox__Secp256k1.sign({
payload: ox__Hash.keccak256(serializedTransaction),
Expand Down
Loading

0 comments on commit 8d493ca

Please sign in to comment.