diff --git a/.changeset/neat-knives-cheat.md b/.changeset/neat-knives-cheat.md new file mode 100644 index 0000000000..dca366664f --- /dev/null +++ b/.changeset/neat-knives-cheat.md @@ -0,0 +1,5 @@ +--- +"viem": patch +--- + +Tweaked EIP-7702 types. diff --git a/site/pages/experimental/eip7702/contract-writes.md b/site/pages/experimental/eip7702/contract-writes.md index 67d5b6c25c..efe5df0ddc 100644 --- a/site/pages/experimental/eip7702/contract-writes.md +++ b/site/pages/experimental/eip7702/contract-writes.md @@ -441,7 +441,7 @@ const batchCallInvoker = getContract({ address: walletClient.account.address, walletClient, }) - + const delegate = privateKeyToAccount('0x...') // [!code ++] const authorization = await walletClient.signAuthorization({ @@ -449,7 +449,6 @@ const authorization = await walletClient.signAuthorization({ delegate, // [!code ++] }) - const hash = await batchCallInvoker.write.execute([[{ data: '0x', to: '0xcb98643b8786950F0461f3B0edf99D88F274574D', diff --git a/site/pages/experimental/eip7702/sending-transactions.md b/site/pages/experimental/eip7702/sending-transactions.md index 8bc4149f7d..2901cb5005 100644 --- a/site/pages/experimental/eip7702/sending-transactions.md +++ b/site/pages/experimental/eip7702/sending-transactions.md @@ -341,7 +341,7 @@ We can also utilize an Delegate Account to execute a call on behalf of the autho import { encodeFunctionData{ parseEther } from 'viem' import { walletClient } from './config' import { contractAddress } from './contract' - + const delegate = privateKeyToAccount('0x...') // [!code ++] const authorization = await walletClient.signAuthorization({ @@ -349,7 +349,6 @@ const authorization = await walletClient.signAuthorization({ delegate, // [!code ++] }) - const hash = await walletClient.sendTransaction({ account: delegate, // [!code ++] authorizationList: [authorization], diff --git a/site/pages/experimental/eip7702/signAuthorization.md b/site/pages/experimental/eip7702/signAuthorization.md index 07eb35f5a4..ff7f563cdb 100644 --- a/site/pages/experimental/eip7702/signAuthorization.md +++ b/site/pages/experimental/eip7702/signAuthorization.md @@ -154,6 +154,25 @@ const authorization = await walletClient.signAuthorization({ }) ``` +### delegate (optional) + +- **Type:** `true | Address | Account` + +Whether the EIP-7702 Transaction will be executed by another Account. + +If not specified, it will be assumed that the EIP-7702 Transaction will be executed by the Account that signed the Authorization. + +```ts twoslash +import { privateKeyToAccount } from 'viem/accounts' +import { walletClient } from './client' + +const authorization = await walletClient.signAuthorization({ + account: privateKeyToAccount('0x...'), + contractAddress: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + delegate: true, // [!code focus] +}) +``` + ### nonce (optional) - **Type:** `Address` diff --git a/src/experimental/eip7702/actions/signAuthorization.test.ts b/src/experimental/eip7702/actions/signAuthorization.test.ts index c27bf414dc..56fd8bec2e 100644 --- a/src/experimental/eip7702/actions/signAuthorization.test.ts +++ b/src/experimental/eip7702/actions/signAuthorization.test.ts @@ -206,7 +206,7 @@ test('behavior: partial authorization: no chainId', async () => { ).toBe(true) }) -test('behavior: delegate', async () => { +test('behavior: delegate is address', async () => { const authorization = await signAuthorization(client, { account, contractAddress: wagmiContractConfig.address, @@ -222,6 +222,22 @@ test('behavior: delegate', async () => { ).toBe(true) }) +test('behavior: delegate is truthy', async () => { + const authorization = await signAuthorization(client, { + account, + contractAddress: wagmiContractConfig.address, + delegate: true, + }) + + expect(authorization.nonce).toBe(663) + expect( + await verifyAuthorization({ + address: account.address, + authorization, + }), + ).toBe(true) +}) + test('behavior: account as delegate', async () => { const authorization = await signAuthorization(client, { account, diff --git a/src/experimental/eip7702/actions/signAuthorization.ts b/src/experimental/eip7702/actions/signAuthorization.ts index 39715cefa3..80fd4af5ae 100644 --- a/src/experimental/eip7702/actions/signAuthorization.ts +++ b/src/experimental/eip7702/actions/signAuthorization.ts @@ -31,8 +31,13 @@ export type SignAuthorizationParameters< account extends Account | undefined = Account | undefined, > = GetAccountParameter & PartialBy & { - /** Delegate of the EIP-7702 Authorization Transaction. @default `account` */ - delegate?: Address | Account | undefined + /** + * Whether the EIP-7702 Transaction will be executed by another Account. + * + * If not specified, it will be assumed that the EIP-7702 Transaction will + * be executed by the Account that signed the Authorization. + */ + delegate?: true | Address | Account | undefined } export type SignAuthorizationReturnType = SignAuthorizationReturnType_account @@ -108,7 +113,11 @@ export async function signAuthorization< }) const account = parseAccount(account_) - const delegate = delegate_ ? parseAccount(delegate_) : undefined + const delegate = (() => { + if (typeof delegate_ === 'boolean') return delegate_ + if (delegate_) return parseAccount(delegate_) + return undefined + })() if (!account.experimental_signAuthorization) throw new AccountTypeNotSupportedError({ @@ -139,7 +148,10 @@ export async function signAuthorization< address: account.address, blockTag: 'pending', }) - if (!delegate || isAddressEqual(account.address, delegate.address)) + if ( + !delegate || + (delegate !== true && isAddressEqual(account.address, delegate.address)) + ) authorization.nonce += 1 } diff --git a/src/experimental/eip7702/types/rpc.ts b/src/experimental/eip7702/types/rpc.ts index 7767f3554d..44984f4de5 100644 --- a/src/experimental/eip7702/types/rpc.ts +++ b/src/experimental/eip7702/types/rpc.ts @@ -1,5 +1,5 @@ import type { Address } from 'abitype' -import type { Hex, Signature } from '../../../types/misc.js' +import type { Hex } from '../../../types/misc.js' export type RpcAuthorization = { /** Address of the contract to set as code for the Authority. */ @@ -8,5 +8,11 @@ export type RpcAuthorization = { chainId: Hex /** Nonce of the Authority to authorize. */ nonce: Hex -} & Signature + /** ECDSA r value. */ + r: Hex + /** ECDSA s value. */ + s: Hex + /** y parity. */ + yParity: Hex +} export type RpcAuthorizationList = readonly RpcAuthorization[] diff --git a/src/types/eip1193.ts b/src/types/eip1193.ts index d45779a1b3..9a12dbb633 100644 --- a/src/types/eip1193.ts +++ b/src/types/eip1193.ts @@ -1780,6 +1780,18 @@ export type WalletRpcSchema = [ Parameters?: WalletSendCallsParameters ReturnType: string }, + /** + * @description Creates, signs, and sends a new transaction to the network + * @link https://eips.ethereum.org/EIPS/eip-1474 + * @example + * provider.request({ method: 'wallet_sendTransaction', params: [{ from: '0x...', to: '0x...', value: '0x...' }] }) + * // => '0x...' + */ + { + Method: 'wallet_sendTransaction' + Parameters: [transaction: TransactionRequest] + ReturnType: Hash + }, /** * @description Requests for the wallet to show information about a call batch * that was sent via `wallet_sendCalls`. diff --git a/src/types/transaction.ts b/src/types/transaction.ts index d4b0f4aad7..7880e25472 100644 --- a/src/types/transaction.ts +++ b/src/types/transaction.ts @@ -275,7 +275,7 @@ export type TransactionRequestEIP7702< > = TransactionRequestBase & ExactPartial> & { accessList?: AccessList | undefined - authorizationList: AuthorizationList + authorizationList?: AuthorizationList | undefined } export type TransactionRequest = OneOf< diff --git a/src/utils/formatters/transaction.ts b/src/utils/formatters/transaction.ts index 8321792b29..34974c04ea 100644 --- a/src/utils/formatters/transaction.ts +++ b/src/utils/formatters/transaction.ts @@ -127,21 +127,12 @@ export const defineTransaction = /*#__PURE__*/ defineFormatter( function formatAuthorizationList( authorizationList: RpcAuthorizationList, ): SignedAuthorizationList { - return authorizationList.map( - (authorization) => - ({ - contractAddress: (authorization as any).address, - r: authorization.r, - s: authorization.s, - chainId: Number(authorization.chainId), - nonce: Number(authorization.nonce), - ...(typeof authorization.yParity !== 'undefined' - ? { yParity: Number(authorization.yParity) } - : {}), - ...(typeof authorization.v !== 'undefined' && - typeof authorization.yParity === 'undefined' - ? { v: Number(authorization.v) } - : {}), - }) as any, - ) as SignedAuthorizationList + return authorizationList.map((authorization) => ({ + contractAddress: (authorization as any).address, + chainId: Number(authorization.chainId), + nonce: Number(authorization.nonce), + r: authorization.r, + s: authorization.s, + yParity: Number(authorization.yParity), + })) as SignedAuthorizationList }