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
5 changes: 5 additions & 0 deletions .github/workflows/verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ jobs:
- name: Check types
run: pnpm run check:types

- name: Build
run: pnpm run build
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

e2e:
name: E2E Tests
runs-on: ubuntu-latest
Expand Down
41 changes: 41 additions & 0 deletions src/pages/guide/payments/sponsor-user-fees.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,47 @@ const { receipt } = await client.token.transferSync({
- **Balance checks**: Network verifies fee payer has sufficient balance
- **Signature validation**: Both signatures must be valid

### Signing Hash Behavior

When building fee-sponsored transactions, the sender must indicate sponsorship **before signing**. This is because the transaction's signing hash differs based on whether it will be sponsored:

- **Sponsored transactions**: The `fee_token` field is omitted from the sender's signing payload, and a `0x00` marker is used. This allows the fee payer to choose the fee token.
- **Non-sponsored transactions**: The `fee_token` is included in the sender's signing payload.

The `withFeePayer` transport and `feePayer: true` parameter handle this automatically. If you're building transactions manually with a local account as fee payer, the Viem SDK handles this for you:

```ts twoslash
// @noErrors
import { createClient, http, parseUnits } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { tempoModerato } from 'viem/chains'

const client = createClient({
chain: tempoModerato,
transport: http(),
})

const senderAccount = privateKeyToAccount('0x...')
const feePayerAccount = privateKeyToAccount('0x...')

// When feePayer is an Account object, Viem:
// 1. Has the sender sign the transaction (with feeToken)
// 2. Recovers the sender address from their signature
// 3. Has the fee payer sign a separate hash (includes sender + feeToken)
// 4. Serializes the dual-signed transaction
const { receipt } = await client.token.transferSync({
account: senderAccount,
amount: parseUnits('10.5', 6),
to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb',
token: '0x20c0000000000000000000000000000000000000',
feePayer: feePayerAccount, // [!code hl]
})
```

:::info
When using `feePayer: true` with the `withFeePayer` transport, the SDK deletes the `feeToken` from the transaction before signing, then sends it to the fee payer service which adds their signature.
:::

## Learning Resources

<Cards>
Expand Down
3 changes: 2 additions & 1 deletion vocs.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ const baseUrl = (() => {

export default defineConfig({
changelog: Changelog.github({ prereleases: true, repo: 'tempoxyz/tempo' }),
checkDeadlinks: true,
// TODO: Set back to true once tempoxyz/tempo#tip-1011 dead link is fixed
checkDeadlinks: 'warn',

title: 'Tempo',
titleTemplate: '%s ⋅ Tempo',
Expand Down