From 79e179a0b5df0782417e340faad95942714a61a1 Mon Sep 17 00:00:00 2001 From: o-az Date: Thu, 9 Apr 2026 23:34:12 -0700 Subject: [PATCH 1/3] feat(accounts): document new fee payer APIs --- e2e/no-raw-mermaid.test.ts | 17 +++-- src/pages/accounts/api/provider.mdx | 7 +- .../accounts/server/handler.feePayer.mdx | 69 +++++++++++++++++++ 3 files changed, 85 insertions(+), 8 deletions(-) diff --git a/e2e/no-raw-mermaid.test.ts b/e2e/no-raw-mermaid.test.ts index 16098859..f66a5e2c 100644 --- a/e2e/no-raw-mermaid.test.ts +++ b/e2e/no-raw-mermaid.test.ts @@ -1,7 +1,7 @@ -import { expect, test } from '@playwright/test' -import { readdirSync, readFileSync, statSync } from 'node:fs' +import { readdirSync, readFileSync } from 'node:fs' import { dirname, join } from 'node:path' import { fileURLToPath } from 'node:url' +import { expect, test } from '@playwright/test' const __dirname = dirname(fileURLToPath(import.meta.url)) @@ -26,13 +26,16 @@ test('no raw ```mermaid code blocks in MDX files', () => { for (const file of mdxFiles) { const content = readFileSync(file, 'utf-8') if (/^```mermaid\s*$/m.test(content)) { - const relative = file.replace(join(__dirname, '..') + '/', '') + const relative = file.replace(`${join(__dirname, '..')}/`, '') violations.push(relative) } } - expect(violations, [ - 'Found raw ```mermaid code blocks. Use instead:', - ...violations.map((f) => ` - ${f}`), - ].join('\n')).toHaveLength(0) + expect( + violations, + [ + 'Found raw ```mermaid code blocks. Use instead:', + ...violations.map((f) => ` - ${f}`), + ].join('\n'), + ).toHaveLength(0) }) diff --git a/src/pages/accounts/api/provider.mdx b/src/pages/accounts/api/provider.mdx index f2320044..5f517fb6 100644 --- a/src/pages/accounts/api/provider.mdx +++ b/src/pages/accounts/api/provider.mdx @@ -111,7 +111,12 @@ const provider = Provider.create({ - **Type:** `string | { url: string; precedence?: 'fee-payer-first' | 'user-first' }` - **Optional** -Fee payer configuration for interacting with a service running [`Handler.feePayer`](/accounts/server/handler.feePayer) from `accounts/server`. Pass a URL string, or an object with `url` and optional `precedence` to control whether the fee payer or the user pays first. +Fee payer configuration for interacting with a service running [`Handler.feePayer`](/accounts/server/handler.feePayer) from `accounts/server`. Pass a URL string, or an object with `url` and optional `precedence`. + +The `precedence` option controls the signing order: + +- **`'fee-payer-first'`** (default) — The fee payer service handles `eth_fillTransaction`, preparing the transaction and choosing the fee token before the sender signs. Use this when you want the sponsor to fully control fee parameters. +- **`'user-first'`** — The sender signs first. The fee payer service only counter-signs the already-signed transaction (via `eth_sendRawTransaction` or `eth_sendRawTransactionSync`) when the transaction has an empty fee payer signature. Use this when the sender needs to commit to transaction details before the sponsor gets involved. ```ts twoslash import { Provider } from 'accounts' diff --git a/src/pages/accounts/server/handler.feePayer.mdx b/src/pages/accounts/server/handler.feePayer.mdx index 0ff5cf43..27d761a7 100644 --- a/src/pages/accounts/server/handler.feePayer.mdx +++ b/src/pages/accounts/server/handler.feePayer.mdx @@ -107,6 +107,41 @@ const handler = Handler.feePayer({ }) ``` +### name + +- **Type:** `string` +- **Optional** + +Sponsor display name. Returned in the `sponsor` metadata of `eth_fillTransaction` responses so wallet UIs can show who is sponsoring the transaction. + +```ts twoslash +import { privateKeyToAccount } from 'viem/accounts' +import { Handler } from 'accounts/server' + +const handler = Handler.feePayer({ + account: privateKeyToAccount('0x...'), + name: 'Acme Inc.', // [!code focus] +}) +``` + +### url + +- **Type:** `string` +- **Optional** + +Sponsor URL. Returned alongside `name` in the `sponsor` metadata of `eth_fillTransaction` responses. + +```ts twoslash +import { privateKeyToAccount } from 'viem/accounts' +import { Handler } from 'accounts/server' + +const handler = Handler.feePayer({ + account: privateKeyToAccount('0x...'), + name: 'Acme Inc.', + url: 'https://acme.com', // [!code focus] +}) +``` + ### transports - **Type:** `Record` @@ -127,3 +162,37 @@ const handler = Handler.feePayer({ }, // [!code focus] }) ``` + +## Supported RPC Methods + +The handler accepts JSON-RPC requests and supports the following methods: + +| Method | Description | +| --- | --- | +| `eth_fillTransaction` | Prepares a transaction with fee payer fields and signs the fee payer portion. Returns the prepared transaction and sponsor metadata. | +| `eth_signRawTransaction` | Counter-signs a sender-signed serialized transaction with the fee payer signature and returns the fully signed serialized transaction (without broadcasting). | +| `eth_sendRawTransaction` | Counter-signs and broadcasts a sender-signed serialized transaction. Returns the transaction hash. | +| `eth_sendRawTransactionSync` | Same as `eth_sendRawTransaction` but waits for the transaction to be included before returning. | + +All other methods return a `MethodNotSupported` error. + +:::info +Only Tempo transactions (type `0x76` / `0x78`) are supported. The handler will reject non-Tempo serialized transactions. +::: + +### `eth_fillTransaction` Response + +When using `eth_fillTransaction`, the response includes a `sponsor` object alongside the prepared transaction: + +```json +{ + "sponsor": { + "address": "0x...", + "name": "Acme Inc.", + "url": "https://acme.com" + }, + "tx": { ... } +} +``` + +The `sponsor.name` and `sponsor.url` fields are included when the corresponding handler parameters are set. Wallet UIs can use this metadata to display who is sponsoring the transaction. From ef421120f76e1f4abd02282ceb31fd4394e156b4 Mon Sep 17 00:00:00 2001 From: o-az Date: Thu, 9 Apr 2026 23:54:05 -0700 Subject: [PATCH 2/3] chore: update sponsor demo --- src/pages/guide/payments/sponsor-user-fees.mdx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/pages/guide/payments/sponsor-user-fees.mdx b/src/pages/guide/payments/sponsor-user-fees.mdx index ffc5c159..b25ba442 100644 --- a/src/pages/guide/payments/sponsor-user-fees.mdx +++ b/src/pages/guide/payments/sponsor-user-fees.mdx @@ -77,18 +77,17 @@ export const config = createConfig({ // @noErrors import { webAuthn } from 'accounts/wagmi' import { tempo } from 'viem/chains' -import { withFeePayer } from 'viem/tempo' import { createConfig, http } from 'wagmi' export const config = createConfig({ - connectors: [webAuthn({ authUrl: '/auth' })], + connectors: [webAuthn({ + authUrl: '/auth', + feePayer: 'https://sponsor.moderato.tempo.xyz', // [!code focus] + })], chains: [tempo], multiInjectedProviderDiscovery: false, transports: { - [tempo.id]: withFeePayer( // [!code focus] - http(), // [!code focus] - http('https://sponsor.moderato.tempo.xyz'), // [!code focus] - ), // [!code focus] + [tempo.id]: http(), }, }) ``` From 000067093b28cb5c17bf5ec7541f726ad778b8a0 Mon Sep 17 00:00:00 2001 From: o-az Date: Fri, 10 Apr 2026 05:15:19 -0700 Subject: [PATCH 3/3] chore: discard fmt file --- e2e/no-raw-mermaid.test.ts | 41 -------------------------------------- 1 file changed, 41 deletions(-) delete mode 100644 e2e/no-raw-mermaid.test.ts diff --git a/e2e/no-raw-mermaid.test.ts b/e2e/no-raw-mermaid.test.ts deleted file mode 100644 index f66a5e2c..00000000 --- a/e2e/no-raw-mermaid.test.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { readdirSync, readFileSync } from 'node:fs' -import { dirname, join } from 'node:path' -import { fileURLToPath } from 'node:url' -import { expect, test } from '@playwright/test' - -const __dirname = dirname(fileURLToPath(import.meta.url)) - -function findMdxFiles(dir: string): string[] { - const results: string[] = [] - for (const entry of readdirSync(dir, { withFileTypes: true })) { - const full = join(dir, entry.name) - if (entry.isDirectory() && entry.name !== 'node_modules') { - results.push(...findMdxFiles(full)) - } else if (entry.name.endsWith('.mdx')) { - results.push(full) - } - } - return results -} - -test('no raw ```mermaid code blocks in MDX files', () => { - const pagesDir = join(__dirname, '..', 'src', 'pages') - const mdxFiles = findMdxFiles(pagesDir) - - const violations: string[] = [] - for (const file of mdxFiles) { - const content = readFileSync(file, 'utf-8') - if (/^```mermaid\s*$/m.test(content)) { - const relative = file.replace(`${join(__dirname, '..')}/`, '') - violations.push(relative) - } - } - - expect( - violations, - [ - 'Found raw ```mermaid code blocks. Use instead:', - ...violations.map((f) => ` - ${f}`), - ].join('\n'), - ).toHaveLength(0) -})