From 65c88a44bf3c6d0b5c0c8d1593e1b8c5a57ef160 Mon Sep 17 00:00:00 2001 From: Emma Jamieson-Hoare Date: Fri, 10 Jul 2026 13:34:29 +0100 Subject: [PATCH 01/10] feat: Add Tempo Account docs --- src/lib/og-sections.ts | 1 + src/marketing/app/_components/Header.tsx | 9 ++- src/pages/docs/protocol/accounts/index.mdx | 76 ++++++++++++++++++++++ src/pages/docs/protocol/index.mdx | 10 ++- vocs.config.ts | 20 ++++-- 5 files changed, 107 insertions(+), 9 deletions(-) create mode 100644 src/pages/docs/protocol/accounts/index.mdx diff --git a/src/lib/og-sections.ts b/src/lib/og-sections.ts index dd55c6f3..8b5c0b90 100644 --- a/src/lib/og-sections.ts +++ b/src/lib/og-sections.ts @@ -35,6 +35,7 @@ export const ogSectionMap: Record = { /** Second path segment → subsection label (only applied to 3+ segment routes). */ export const ogSubsectionMap: Record = { + accounts: 'ACCOUNTS', blockspace: 'BLOCKSPACE', exchange: 'DEX', fees: 'FEES', diff --git a/src/marketing/app/_components/Header.tsx b/src/marketing/app/_components/Header.tsx index 8861b0d8..072ea406 100644 --- a/src/marketing/app/_components/Header.tsx +++ b/src/marketing/app/_components/Header.tsx @@ -18,6 +18,7 @@ import { ExplorerIcon, FaucetIcon, McpIcon, + PoliciesIcon, TerminalIcon, TokensIcon, TransactionsIcon, @@ -32,8 +33,14 @@ const protocolMenu: MegaMenuData = { variant: 'vertical', columns: [ { - title: 'Transactions', + title: 'Protocol', items: [ + { + label: 'Tempo Accounts', + desc: 'Account model for keys, permissions, and policies', + href: developersPath('/docs/protocol/accounts'), + icon: , + }, { label: 'Tempo Transactions', desc: 'Flexible transactions for batching, fee sponsorship, scheduling, and more', diff --git a/src/pages/docs/protocol/accounts/index.mdx b/src/pages/docs/protocol/accounts/index.mdx new file mode 100644 index 00000000..7870a7fb --- /dev/null +++ b/src/pages/docs/protocol/accounts/index.mdx @@ -0,0 +1,76 @@ +--- +title: Tempo Accounts +description: Understand Tempo Accounts as the account, key, permission, and policy primitives that wallets and applications compose. +--- + +import { Cards, Card } from "vocs"; + +# Tempo Accounts + +A Tempo account is the address that sends transactions, receives TIP-20 tokens, pays fees, and authorizes keys on Tempo. + +Tempo accounts use ordinary EVM addresses. Tempo adds native rules for keys, fee token preferences, nonces, and receive policies through precompiles and transaction validation, rather than requiring each account to be a smart contract. + +## Account model + +These protocol features define account behavior: + +| Feature | Account state | What it controls | +| ---------------- | --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | +| Address | The 20-byte EVM address used as `caller`, `sender`, `receiver`, or `feePayer` | Where funds are received, which account submits a transaction, and which account pays fees | +| Signatures | secp256k1, P256, or WebAuthn signature type | How the account authorizes Tempo Transactions | +| Nonces | `nonceKey` and `nonce` on the Tempo Transaction | Replay protection and parallel transaction lanes | +| Account Keychain | `keys[account][keyId]`, spending limits, call scopes, and authorization witnesses | Delegated keys for devices, apps, agents, sessions, and recovery workflows | +| Fee preferences | `feeToken` on the transaction or a stored fee token preference for the fee payer | Which supported stablecoin pays transaction fees | +| Receive policies | Per address sender policy, token filter, and recovery authority | Which inbound TIP-20 transfers or mints the account accepts | + +## Key model + +Tempo accounts have a small authorization hierarchy: + +1. **Root keys** have full control of the account and can authorize or revoke other keys. +2. **Admin access keys** can manage account keys, making device recovery, passkey rotation, and operational administration possible without using the root key for every action. +3. **Limited access keys** can transact only within their configured restrictions, making them useful for connected apps, agents, subscriptions, and session key style flows. + +The [Account Keychain precompile](/docs/protocol/transactions/AccountKeychain) stores authorized keys and enforces expiry, revocation, spending limits, and call scopes at the protocol layer. Tempo Transactions can also carry a `keyAuthorization` payload that registers a new key before the transaction signature is verified. + +## Related account docs + + + + + + + + + diff --git a/src/pages/docs/protocol/index.mdx b/src/pages/docs/protocol/index.mdx index b046c82a..28cbf212 100644 --- a/src/pages/docs/protocol/index.mdx +++ b/src/pages/docs/protocol/index.mdx @@ -1,9 +1,9 @@ --- title: Tempo Protocol -description: Read Tempo protocol references for tokens, policies, fees, transactions, blockspace, exchange, zones, network upgrades, TIP specs, and implementation details. +description: Read Tempo protocol references for accounts, tokens, policies, fees, transactions, blockspace, exchange, zones, network upgrades, TIP specs, and implementation details. --- -import { Cards, Card } from 'vocs' +import { Cards, Card } from "vocs"; # Tempo Protocol @@ -16,6 +16,12 @@ For private execution internals, start with Tempo Zones and the [zone accounts s ## Core Primitives + { - if (path === '/docs') return 'Tempo %s ⋅ Tempo Docs' - if (path === '/docs' || path.startsWith('/docs/')) return '%s ⋅ Tempo Docs' + const pagePath = typeof path === 'string' ? path : '/' + if (pagePath === '/docs') return 'Tempo %s ⋅ Tempo Docs' + if (pagePath === '/docs' || pagePath.startsWith('/docs/')) return '%s ⋅ Tempo Docs' if (title?.includes('Tempo')) return undefined return '%s ⋅ Tempo' }, @@ -71,7 +72,8 @@ export default defineConfig({ renderStrategy: 'partial-static', feedback: createFeedbackAdapter(), head(path) { - if (path === '/docs' || path.startsWith('/docs/')) + const pagePath = typeof path === 'string' ? path : '/' + if (pagePath === '/docs' || pagePath.startsWith('/docs/')) return { meta: { articleModifiedTime: false } } return undefined }, @@ -112,7 +114,8 @@ export default defineConfig({ }, sitemap: { lastmod: (path, { lastmod }) => { - if (path === '/docs' || path.startsWith('/docs/')) return false + const pagePath = typeof path === 'string' ? path : '/' + if (pagePath === '/docs' || pagePath.startsWith('/docs/')) return false return lastmod }, }, @@ -135,7 +138,7 @@ export default defineConfig({ // src/lib/og-sections.test.ts fails if the two drift. ogImageUrl: (path, options = {}) => { const urlBase = options.baseUrl?.replace(/\/$/, '') ?? '' - const docsPath = path.replace(/^\/docs(?=\/|$)/, '') || '/' + const docsPath = String(path ?? '').replace(/^\/docs(?=\/|$)/, '') || '/' const landingPaths = ['/', '/changelog'] if (landingPaths.includes(docsPath)) return `${urlBase}/og-docs.png` @@ -158,6 +161,7 @@ export default defineConfig({ } const subsectionMap: Record = { + accounts: 'ACCOUNTS', blockspace: 'BLOCKSPACE', exchange: 'DEX', fees: 'FEES', @@ -640,6 +644,10 @@ export default defineConfig({ text: 'Overview', link: '/docs/protocol', }, + { + text: 'Tempo Accounts', + link: '/docs/protocol/accounts', + }, { text: 'TIP-20 Tokens', collapsed: false, @@ -838,7 +846,7 @@ export default defineConfig({ items: [ { text: 'T8', - badge: { text: 'Planned', variant: 'note' }, + badge: { text: 'Planned', variant: 'note' as const }, link: '/docs/protocol/upgrades/t8', }, { From 11278e0dd724a0aa0761c2f8e24c407011b9a006 Mon Sep 17 00:00:00 2001 From: Emma Jamieson-Hoare Date: Fri, 10 Jul 2026 15:06:18 +0100 Subject: [PATCH 02/10] feat: expand sections, tighten --- src/pages/docs/build.mdx | 8 +++- src/pages/docs/guide/accounts/index.mdx | 45 ++++++++++++++++++++++ src/pages/docs/protocol/accounts/index.mdx | 39 +++++++------------ vocs.config.ts | 27 +++++++++---- 4 files changed, 85 insertions(+), 34 deletions(-) create mode 100644 src/pages/docs/guide/accounts/index.mdx diff --git a/src/pages/docs/build.mdx b/src/pages/docs/build.mdx index 8e7e1e08..47cde854 100644 --- a/src/pages/docs/build.mdx +++ b/src/pages/docs/build.mdx @@ -7,7 +7,7 @@ import { Cards, Card } from 'vocs' # Build on Tempo -Use this section when you are designing payment experiences or payment infrastructure on Tempo. The guides here focus on what your product needs to do: move stablecoins, issue assets, exchange liquidity, isolate activity in private zones, or let agents pay for services. +Use this section when you are designing payment experiences or payment infrastructure on Tempo. The guides here focus on what your product needs to do: move stablecoins, manage accounts, issue assets, exchange liquidity, isolate activity in private zones, or let agents pay for services. Start with **Make Payments** if you are not sure where to begin. It covers the core transfer flow and introduces the payment primitives used throughout the rest of the docs. @@ -22,6 +22,12 @@ Use [Agentic Payments](/docs/guide/machine-payments) when agents, APIs, or servi to="/docs/guide/payments" icon="lucide:send" /> + + + + + + + diff --git a/src/pages/docs/protocol/accounts/index.mdx b/src/pages/docs/protocol/accounts/index.mdx index 7870a7fb..61b5b628 100644 --- a/src/pages/docs/protocol/accounts/index.mdx +++ b/src/pages/docs/protocol/accounts/index.mdx @@ -23,6 +23,7 @@ These protocol features define account behavior: | Account Keychain | `keys[account][keyId]`, spending limits, call scopes, and authorization witnesses | Delegated keys for devices, apps, agents, sessions, and recovery workflows | | Fee preferences | `feeToken` on the transaction or a stored fee token preference for the fee payer | Which supported stablecoin pays transaction fees | | Receive policies | Per address sender policy, token filter, and recovery authority | Which inbound TIP-20 transfers or mints the account accepts | +| Virtual addresses | Master wallet registration and derived TIP-20 recipient addresses | Deposit attribution and TIP-20 forwarding to the registered account | ## Key model @@ -38,39 +39,27 @@ The [Account Keychain precompile](/docs/protocol/transactions/AccountKeychain) s - - diff --git a/vocs.config.ts b/vocs.config.ts index dffe3b5e..70b74d47 100644 --- a/vocs.config.ts +++ b/vocs.config.ts @@ -304,6 +304,24 @@ export default defineConfig({ text: 'Getting Funds on Tempo', link: '/docs/guide/getting-funds', }, + { + text: 'Manage Accounts', + collapsed: false, + items: [ + { + text: 'Overview', + link: '/docs/guide/accounts', + }, + { + text: 'Configure receive policies', + link: '/docs/guide/payments/configure-receive-policies', + }, + { + text: 'Use virtual addresses', + link: '/docs/guide/payments/virtual-addresses', + }, + ], + }, { text: 'Make Payments', collapsed: false, @@ -320,18 +338,10 @@ export default defineConfig({ text: 'Accept a payment', link: '/docs/guide/payments/accept-a-payment', }, - { - text: 'Configure receive policies', - link: '/docs/guide/payments/configure-receive-policies', - }, { text: 'Attach a transfer memo', link: '/docs/guide/payments/transfer-memos', }, - { - text: 'Use virtual addresses', - link: '/docs/guide/payments/virtual-addresses', - }, { text: 'Pay fees in any stablecoin', link: '/docs/guide/payments/pay-fees-in-any-stablecoin', @@ -1175,6 +1185,7 @@ export default defineConfig({ '/docs': docsHomeSidebar, '/docs/build': buildSidebar, '/docs/guide/getting-funds': buildSidebar, + '/docs/guide/accounts': buildSidebar, '/docs/guide/payments': buildSidebar, '/docs/guide/issuance': buildSidebar, '/docs/guide/stablecoin-dex': buildSidebar, From 00ee7a43dcd4fb467c090c9dcafc84347984f643 Mon Sep 17 00:00:00 2001 From: Emma Jamieson-Hoare Date: Mon, 13 Jul 2026 19:12:36 +0100 Subject: [PATCH 03/10] fix: route accounts menu link to docs page --- src/marketing/app/_components/Header.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/marketing/app/_components/Header.tsx b/src/marketing/app/_components/Header.tsx index 072ea406..dcde792c 100644 --- a/src/marketing/app/_components/Header.tsx +++ b/src/marketing/app/_components/Header.tsx @@ -38,7 +38,7 @@ const protocolMenu: MegaMenuData = { { label: 'Tempo Accounts', desc: 'Account model for keys, permissions, and policies', - href: developersPath('/docs/protocol/accounts'), + href: '/docs/protocol/accounts', icon: , }, { From 0a1d92482b0e1efa3d61d58e5a4262a4c2eeefb2 Mon Sep 17 00:00:00 2001 From: Emma Jamieson-Hoare Date: Mon, 13 Jul 2026 20:47:50 +0100 Subject: [PATCH 04/10] feat: add accounts landing page --- src/marketing/app/_components/Footer.tsx | 1 + src/marketing/app/_components/Header.tsx | 2 +- src/marketing/app/_components/features.tsx | 98 ++++- src/marketing/app/_lib/featurePaths.ts | 1 + src/marketing/app/features/[slug]/page.tsx | 5 +- .../features/_components/AccountsSections.tsx | 381 ++++++++++++++++++ src/marketing/main.tsx | 1 + src/marketing/routeMetadata.ts | 5 + src/pages/build/tempo-accounts.tsx | 12 + 9 files changed, 502 insertions(+), 4 deletions(-) create mode 100644 src/marketing/app/features/_components/AccountsSections.tsx create mode 100644 src/pages/build/tempo-accounts.tsx diff --git a/src/marketing/app/_components/Footer.tsx b/src/marketing/app/_components/Footer.tsx index 9d64f17d..9d0960a1 100644 --- a/src/marketing/app/_components/Footer.tsx +++ b/src/marketing/app/_components/Footer.tsx @@ -38,6 +38,7 @@ const columns: FooterColumn[] = [ { header: 'Protocol', links: [ + { label: 'Accounts', href: featurePath('accounts') }, { label: 'Transactions', href: featurePath('transactions') }, { label: 'TIP-20 tokens', href: featurePath('tokens') }, ], diff --git a/src/marketing/app/_components/Header.tsx b/src/marketing/app/_components/Header.tsx index dcde792c..83679151 100644 --- a/src/marketing/app/_components/Header.tsx +++ b/src/marketing/app/_components/Header.tsx @@ -38,7 +38,7 @@ const protocolMenu: MegaMenuData = { { label: 'Tempo Accounts', desc: 'Account model for keys, permissions, and policies', - href: '/docs/protocol/accounts', + href: featurePath('accounts'), icon: , }, { diff --git a/src/marketing/app/_components/features.tsx b/src/marketing/app/_components/features.tsx index 4068b385..d558f28b 100644 --- a/src/marketing/app/_components/features.tsx +++ b/src/marketing/app/_components/features.tsx @@ -30,11 +30,105 @@ export type Feature = { } const featurePrecedence: Record = { - tokens: 0, - transactions: 1, + accounts: 0, + tokens: 1, + transactions: 2, } export const features: Feature[] = [ + { + slug: 'accounts', + title: 'Tempo Accounts', + description: ( + <> + Account infrastructure for passkeys,{' '} + scoped keys,{' '} + receive controls, and{' '} + deposit attribution. + + ), + items: [ + { + label: 'Root keys and passkeys', + desc: 'Use modern account signing without wrapping every account in a contract.', + code: [ + 'import { Account } from "viem/tempo";', + '', + '// A Tempo account can be controlled by root keys', + '// including passkey-backed signing schemes.', + 'const account = Account.fromWebAuthnCredential({', + ' id: credential.id,', + ' publicKey: credential.publicKey,', + '});', + ], + highlight: ['Account.fromWebAuthnCredential'], + }, + { + label: 'Scoped access keys', + desc: 'Delegate limited signing to apps, agents, sessions, or devices.', + code: [ + 'import { Account, Actions, Expiry } from "viem/tempo";', + '', + 'const accessKey = Account.fromP256(sessionKey, {', + ' access: rootAccount,', + '});', + '', + 'await Actions.accessKey.signAuthorization(client, {', + ' accessKey,', + ' expiry: Expiry.days(7),', + ' spendingLimit: { token: usdc, amount: 100_000_000n },', + '});', + ], + highlight: ['accessKey', 'Expiry.days(7)', 'spendingLimit'], + }, + { + label: 'Virtual addresses', + desc: 'Attribute deposits per customer without creating new state accounts.', + code: [ + 'import { getVirtualAddress } from "viem/tempo";', + '', + 'const depositAddress = getVirtualAddress({', + ' master: treasuryAccount,', + ' salt: customerId,', + '});', + '', + '// Incoming TIP-20 funds resolve to treasuryAccount', + '// while preserving customer-level attribution.', + ], + highlight: ['getVirtualAddress', 'treasuryAccount', 'customerId'], + }, + { + label: 'Receive policies', + desc: 'Control which tokens and senders can deliver funds to an account.', + code: [ + 'await client.receivePolicy.setPolicy({', + ' account: merchant,', + ' acceptedTokens: [usdc, usdt],', + ' allowedSenders: [processor],', + ' recovery: complianceOps,', + '});', + ], + highlight: ['acceptedTokens', 'allowedSenders', 'recovery'], + }, + ], + readLabel: 'Read account docs', + readHref: '/docs/guide/accounts', + heroActions: [ + { + label: 'Create and manage accounts', + href: '/docs/guide/accounts', + primary: true, + }, + { + label: 'Access keys', + href: '/docs/guide/tempo-transaction#access-keys', + }, + { + label: 'Receive policies', + href: '/docs/guide/payments/configure-receive-policies', + }, + ], + }, { slug: 'transactions', title: 'Tempo Transactions', diff --git a/src/marketing/app/_lib/featurePaths.ts b/src/marketing/app/_lib/featurePaths.ts index 83418e7a..6b49ad18 100644 --- a/src/marketing/app/_lib/featurePaths.ts +++ b/src/marketing/app/_lib/featurePaths.ts @@ -1,6 +1,7 @@ import { developersPath } from './developersPaths' const featurePaths: Record = { + accounts: developersPath('/build/tempo-accounts'), transactions: developersPath('/build/tempo-transactions'), tokens: developersPath('/build/tip20-tokens'), } diff --git a/src/marketing/app/features/[slug]/page.tsx b/src/marketing/app/features/[slug]/page.tsx index 42e42583..eee93936 100644 --- a/src/marketing/app/features/[slug]/page.tsx +++ b/src/marketing/app/features/[slug]/page.tsx @@ -6,6 +6,7 @@ import { features } from '../../_components/features' import Header from '../../_components/Header' import HeroDots from '../../_components/HeroDots' import Reveal from '../../_components/Reveal' +import AccountsSections from '../_components/AccountsSections' import TokensSections from '../_components/TokensSections' import TransactionsSections from '../_components/TransactionsSections' @@ -80,7 +81,9 @@ export default function FeaturePage({ params }: { params: FeatureParams }) { {/* Every snippet expanded — no select-to-reveal on the dedicated page. Rows run full-bleed so their borders meet the shell's side borders; the content is inset to match the section intros. */} - {feature.slug === 'transactions' ? ( + {feature.slug === 'accounts' ? ( + + ) : feature.slug === 'transactions' ? ( ) : feature.slug === 'tokens' ? ( diff --git a/src/marketing/app/features/_components/AccountsSections.tsx b/src/marketing/app/features/_components/AccountsSections.tsx new file mode 100644 index 00000000..905171fc --- /dev/null +++ b/src/marketing/app/features/_components/AccountsSections.tsx @@ -0,0 +1,381 @@ +'use client' + +import Button from '../../_components/Button' +import CodeWindow, { type CodeVariant } from '../../_components/CodeWindow' +import EdgeMarkers from '../../_components/EdgeMarkers' +import Reveal from '../../_components/Reveal' +import FeatureFaq, { type FaqItem } from './FeatureFaq' + +type Capability = { + title: string + desc: string + href: string + accent: string +} + +const accountCodeVariants: CodeVariant[] = [ + { + lang: 'Access key', + code: [ + 'import { Account, Actions, Expiry } from "viem/tempo";', + 'import { generatePrivateKey } from "viem/accounts";', + '', + 'const sessionKey = Account.fromP256(generatePrivateKey(), {', + ' access: rootAccount,', + '});', + '', + 'const authorization = await Actions.accessKey.signAuthorization(client, {', + ' accessKey: sessionKey,', + ' expiry: Expiry.days(7),', + ' spendingLimit: { token: usdc, amount: 100_000_000n },', + '});', + ], + highlight: ['Account.fromP256', 'Expiry.days(7)', 'spendingLimit'], + }, + { + lang: 'Virtual address', + code: [ + 'import { getVirtualAddress } from "viem/tempo";', + '', + 'const depositAddress = getVirtualAddress({', + ' master: treasuryAccount,', + ' salt: customerId,', + '});', + '', + '// Customer funds arrive at a unique address.', + '// TIP-20 resolution forwards them to treasuryAccount.', + ], + highlight: ['getVirtualAddress', 'treasuryAccount', 'customerId'], + }, + { + lang: 'Receive policy', + code: [ + 'await client.receivePolicy.setPolicy({', + ' account: merchant,', + ' acceptedTokens: [usdc, usdt],', + ' allowedSenders: [processor],', + ' recovery: complianceOps,', + '});', + ], + highlight: ['acceptedTokens', 'allowedSenders', 'recovery'], + }, +] + +const capabilities: Capability[] = [ + { + title: 'Root keys and passkeys', + desc: 'Keep ordinary EVM addresses while adding modern signing paths for wallets, devices, and recovery.', + href: '/docs/protocol/accounts#key-model', + accent: 'bg-[#7C5CFF]', + }, + { + title: 'Scoped access keys', + desc: 'Authorize sessions, agents, and apps with spending limits, expiry, and call scopes.', + href: '/docs/guide/tempo-transaction#access-keys', + accent: 'bg-[#16A34A]', + }, + { + title: 'Virtual addresses', + desc: 'Issue per-customer deposit addresses that resolve back to a master account.', + href: '/docs/guide/payments/virtual-addresses', + accent: 'bg-[#F97316]', + }, + { + title: 'Receive policies', + desc: 'Define token and sender controls for inbound transfers, mints, and recovery flows.', + href: '/docs/guide/payments/configure-receive-policies', + accent: 'bg-[#0891B2]', + }, +] + +const ACCOUNT_FAQS: FaqItem[] = [ + { + question: 'Are Tempo accounts smart contracts?', + answer: [ + 'No. Tempo accounts use ordinary EVM addresses. Account behavior is extended by protocol rules, precompiles, and Tempo Transaction validation rather than requiring every account to deploy contract code. Read the ', + { text: 'protocol account model', href: '/docs/protocol/accounts' }, + ' for the exact mechanics.', + ], + }, + { + question: 'What are access keys for?', + answer: [ + 'Access keys let an account delegate constrained signing authority to a device, app, agent, or session. They can carry expiry, spending limits, and call scopes, so products can avoid using a root key for every action.', + ], + }, + { + question: 'When should I use virtual addresses?', + answer: [ + 'Use virtual addresses when you need many deposit addresses for users, customers, or invoices but want funds to resolve to one registered account without creating permanent state for every deposit address.', + ], + }, + { + question: 'How do receive policies differ from token controls?', + answer: [ + 'Receive policies are account-level controls for what an address accepts. Token roles and token policies are issuer-level controls. Many products use both: issuer controls for asset governance, receive policies for account-specific risk and routing.', + ], + }, +] + +function AccountModelDiagram() { + const nodes = [ + { label: 'ROOT KEY', detail: 'FULL CONTROL', className: 'left-[8%] top-[12%]' }, + { label: 'PASSKEY', detail: 'USER DEVICE', className: 'left-[8%] top-[38%]' }, + { label: 'ACCESS KEY', detail: 'SCOPED SESSION', className: 'left-[8%] top-[64%]' }, + { label: 'VIRTUAL ADDRESS', detail: 'DEPOSIT ALIAS', className: 'right-[7%] top-[16%]' }, + { label: 'RECEIVE POLICY', detail: 'TOKEN + SENDER', className: 'right-[7%] top-[46%]' }, + { label: 'FEE TOKEN', detail: 'STABLECOIN FEES', className: 'right-[7%] top-[70%]' }, + ] + + return ( +
+
+ +
+
+
+
+ ACCOUNT +
+
+ 0xA11c...09 +
+
+
+
+
+ SIG +
+
P256
+
+
+
+ FEE +
+
USDC
+
+
+
+
+ {nodes.map((node) => ( +
+
+ {node.detail} +
+
+ {node.label} +
+
+ ))} +
+
+ + + + + + + + + + +
+
+
+ ACCOUNT +
+
+ 0xA11c...09 +
+
+
+
+
SIG
+
P256
+
+
+
FEE
+
USDC
+
+
+
+ + {nodes.map((node) => ( + + ))} +
+ ) +} + +function IntroSection() { + return ( +
+ + +
+ +

+ ACCOUNT MODEL +

+

+ One address, multiple controls. +

+

+ Tempo accounts keep the operational simplicity of EVM addresses while adding native + account controls for signing, delegation, inbound funds, deposit attribution, and fee + payment. +

+
+ + +
+
+
+
+ ) +} + +function CapabilitySection() { + return ( +
+ +
+

+ CAPABILITIES +

+

+ Account primitives for payment products. +

+

+ Use the same account surface for consumer wallets, merchant accounts, treasury flows, + hosted products, and agent-driven payments. +

+
+
+ {capabilities.map((item) => ( + + +

+ {item.title} +

+

+ {item.desc} +

+ + Read more + +
+ ))} +
+
+
+ ) +} + +function CodeSection() { + return ( +
+ + +
+
+

+ IMPLEMENTATION +

+

+ Compose keys, aliases, and policies. +

+

+ Accounts become a reusable infrastructure layer: sign with root keys, delegate to + scoped keys, route deposits through virtual addresses, and enforce receive rules. +

+
+ + +
+
+
+ +
+
+
+
+ ) +} + +export default function AccountsSections() { + return ( + <> + + + + + + ) +} diff --git a/src/marketing/main.tsx b/src/marketing/main.tsx index 4724d903..bba4170f 100644 --- a/src/marketing/main.tsx +++ b/src/marketing/main.tsx @@ -153,6 +153,7 @@ function applyRouteMetadata(path: string) { function renderRoute(path: string): ReactNode { if (path === '/' || path === '/build') return + if (path === '/build/tempo-accounts') return if (path === '/build/tempo-transactions') return if (path === '/build/tip20-tokens') return if (path === '/performance') return diff --git a/src/marketing/routeMetadata.ts b/src/marketing/routeMetadata.ts index 9d9ca9ce..1a3a5ca7 100644 --- a/src/marketing/routeMetadata.ts +++ b/src/marketing/routeMetadata.ts @@ -15,6 +15,11 @@ export const routeMetadata: Record = { description: 'Build payment products on Tempo with stablecoins, fast settlement, and predictable fees.', }, + '/build/tempo-accounts': { + title: 'Tempo Accounts', + description: + 'Account infrastructure for passkeys, scoped keys, receive controls, and deposit attribution.', + }, '/build/tempo-transactions': { title: 'Tempo Transactions', description: 'Batch, sponsor, schedule, and parallelize payments with Tempo Transactions.', diff --git a/src/pages/build/tempo-accounts.tsx b/src/pages/build/tempo-accounts.tsx new file mode 100644 index 00000000..2550397e --- /dev/null +++ b/src/pages/build/tempo-accounts.tsx @@ -0,0 +1,12 @@ +'use client' + +import FeaturePage from '../../marketing/FeaturePage' +import MarketingRoute from '../../marketing/MarketingRoute' + +export default function Page() { + return ( + + + + ) +} From c7e5d475bda7b5ae8bc64efef2b358e0903b0c2a Mon Sep 17 00:00:00 2001 From: Emma Jamieson-Hoare Date: Mon, 13 Jul 2026 20:50:29 +0100 Subject: [PATCH 05/10] fix: satisfy accounts page check --- .../app/_lib/developersPaths.test.ts | 4 +-- .../features/_components/AccountsSections.tsx | 35 +++++++++---------- 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/src/marketing/app/_lib/developersPaths.test.ts b/src/marketing/app/_lib/developersPaths.test.ts index 690fc83a..79d8f2c8 100644 --- a/src/marketing/app/_lib/developersPaths.test.ts +++ b/src/marketing/app/_lib/developersPaths.test.ts @@ -21,8 +21,6 @@ describe('developersPath', () => { expect(developersPath('/')).toBe('/developers') expect(developersPath('/docs')).toBe('/developers/docs') - expect(developersPath('/build/tempo-transactions')).toBe( - '/developers/build/tempo-transactions', - ) + expect(developersPath('/build/tempo-transactions')).toBe('/developers/build/tempo-transactions') }) }) diff --git a/src/marketing/app/features/_components/AccountsSections.tsx b/src/marketing/app/features/_components/AccountsSections.tsx index 905171fc..6f87191d 100644 --- a/src/marketing/app/features/_components/AccountsSections.tsx +++ b/src/marketing/app/features/_components/AccountsSections.tsx @@ -143,22 +143,21 @@ function AccountModelDiagram() {
-
- SIG -
+
SIG
P256
-
- FEE -
+
FEE
USDC
{nodes.map((node) => ( -
+
{node.detail}
@@ -170,7 +169,13 @@ function AccountModelDiagram() {
- + + Account model relationships -
+
-
- ACCOUNT -
-
- 0xA11c...09 -
+
ACCOUNT
+
0xA11c...09
@@ -290,9 +291,7 @@ function CapabilitySection() {
-

- CAPABILITIES -

+

CAPABILITIES

Account primitives for payment products.

From 84fab49d7874787bf6bee99f965904f5e1a6f81e Mon Sep 17 00:00:00 2001 From: Emma Jamieson-Hoare Date: Mon, 13 Jul 2026 20:54:59 +0100 Subject: [PATCH 06/10] fix: route accounts docs nav to guide --- vocs.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vocs.config.ts b/vocs.config.ts index 70b74d47..47eee4bb 100644 --- a/vocs.config.ts +++ b/vocs.config.ts @@ -656,7 +656,7 @@ export default defineConfig({ }, { text: 'Tempo Accounts', - link: '/docs/protocol/accounts', + link: '/docs/guide/accounts', }, { text: 'TIP-20 Tokens', From a8e7c18adaa3412ed846cedcb45eb19152bfefd6 Mon Sep 17 00:00:00 2001 From: Emma Jamieson-Hoare Date: Tue, 14 Jul 2026 13:48:38 +0100 Subject: [PATCH 07/10] feat: refine accounts landing page --- src/marketing/app/features/[slug]/page.tsx | 8 +- .../features/_components/AccountsSections.tsx | 786 ++++++++++++------ 2 files changed, 551 insertions(+), 243 deletions(-) diff --git a/src/marketing/app/features/[slug]/page.tsx b/src/marketing/app/features/[slug]/page.tsx index eee93936..f68f38ea 100644 --- a/src/marketing/app/features/[slug]/page.tsx +++ b/src/marketing/app/features/[slug]/page.tsx @@ -43,7 +43,13 @@ export default function FeaturePage({ params }: { params: FeatureParams }) {
- +

{feature.title} diff --git a/src/marketing/app/features/_components/AccountsSections.tsx b/src/marketing/app/features/_components/AccountsSections.tsx index 6f87191d..cde2c8ce 100644 --- a/src/marketing/app/features/_components/AccountsSections.tsx +++ b/src/marketing/app/features/_components/AccountsSections.tsx @@ -1,90 +1,196 @@ 'use client' +import { useState } from 'react' import Button from '../../_components/Button' import CodeWindow, { type CodeVariant } from '../../_components/CodeWindow' import EdgeMarkers from '../../_components/EdgeMarkers' +import ModeToggle, { type ShowcaseMode } from '../../_components/ModeToggle' import Reveal from '../../_components/Reveal' +import { accessKeyCodeVariants } from '../../_components/transactionCodeVariants' import FeatureFaq, { type FaqItem } from './FeatureFaq' -type Capability = { - title: string - desc: string - href: string - accent: string -} +const CODE_WINDOW_HEIGHT = 'h-[420px] max-h-[420px] lg:h-[460px] lg:max-h-[460px]' -const accountCodeVariants: CodeVariant[] = [ +const receivePolicyCodeVariants: CodeVariant[] = [ { - lang: 'Access key', + lang: 'TypeScript', code: [ - 'import { Account, Actions, Expiry } from "viem/tempo";', - 'import { generatePrivateKey } from "viem/accounts";', + 'import { client } from "./viem.config";', '', - 'const sessionKey = Account.fromP256(generatePrivateKey(), {', - ' access: rootAccount,', + 'const policy = await client.receivePolicy.create({', + ' account: merchantAccount,', + ' allowedHolders: [customerAllowlist],', + ' approvedTransfers: [checkoutPolicy],', + ' blockedAddresses: [sanctionsList],', '});', '', - 'const authorization = await Actions.accessKey.signAuthorization(client, {', - ' accessKey: sessionKey,', - ' expiry: Expiry.days(7),', - ' spendingLimit: { token: usdc, amount: 100_000_000n },', - '});', + 'await client.account.setReceivePolicy(policy.id);', + ], + highlight: [ + 'client.receivePolicy.create({', + 'allowedHolders: [customerAllowlist]', + 'approvedTransfers: [checkoutPolicy]', + 'blockedAddresses: [sanctionsList]', + 'client.account.setReceivePolicy(policy.id)', ], - highlight: ['Account.fromP256', 'Expiry.days(7)', 'spendingLimit'], }, { - lang: 'Virtual address', + lang: 'CLI', code: [ - 'import { getVirtualAddress } from "viem/tempo";', - '', - 'const depositAddress = getVirtualAddress({', - ' master: treasuryAccount,', - ' salt: customerId,', - '});', + 'cast send $RECEIVE_POLICY_REGISTRY \\', + ' "setPolicy(address,uint64)" \\', + ' "$MERCHANT_ACCOUNT" \\', + ' "$POLICY_ID" \\', + ' --rpc-url "$TEMPO_RPC_URL" \\', + ' --private-key "$ADMIN_KEY"', '', - '// Customer funds arrive at a unique address.', - '// TIP-20 resolution forwards them to treasuryAccount.', + 'cast call $RECEIVE_POLICY_REGISTRY \\', + ' "check(address,address,address)(bool)" \\', + ' "$TOKEN" "$SENDER" "$MERCHANT_ACCOUNT"', ], - highlight: ['getVirtualAddress', 'treasuryAccount', 'customerId'], - }, - { - lang: 'Receive policy', - code: [ - 'await client.receivePolicy.setPolicy({', - ' account: merchant,', - ' acceptedTokens: [usdc, usdt],', - ' allowedSenders: [processor],', - ' recovery: complianceOps,', - '});', + highlight: [ + 'setPolicy(address,uint64)', + '"$MERCHANT_ACCOUNT"', + '"$POLICY_ID"', + 'check(address,address,address)(bool)', ], - highlight: ['acceptedTokens', 'allowedSenders', 'recovery'], }, ] -const capabilities: Capability[] = [ +type Capability = { + title: string + desc: string + href: string + accent: string +} + +type AccountFeature = { + id: string + kicker: string + title: string + desc: string + href: string + cta: string + points: Capability[] + diagram: 'keys' | 'access' | 'policies' | 'virtual' +} + +const accountFeatures: AccountFeature[] = [ { - title: 'Root keys and passkeys', - desc: 'Keep ordinary EVM addresses while adding modern signing paths for wallets, devices, and recovery.', - href: '/docs/protocol/accounts#key-model', - accent: 'bg-[#7C5CFF]', + id: 'key-types', + kicker: 'KEY TYPES', + title: 'Flexible auth without changing the address.', + desc: 'Tempo accounts keep ordinary EVM addresses while adding native signing paths for root keys, passkeys, recovery, and app-owned infrastructure.', + href: '/docs/guide/accounts', + cta: 'Account guide', + diagram: 'keys', + points: [ + { + title: 'Root keys', + desc: 'Full account control stays anchored to the address.', + href: '/docs/protocol/accounts#key-model', + accent: 'bg-[#73A0FF]', + }, + { + title: 'Passkeys', + desc: 'Use device-native signing for wallet UX and recovery flows.', + href: '/docs/guide/accounts', + accent: 'bg-[#C084FC]', + }, + { + title: 'Privacy by default', + desc: 'Use aliases and delegated keys without exposing every operational relationship.', + href: '/docs/guide/payments/virtual-addresses', + accent: 'bg-[#6EE7B7]', + }, + ], }, { - title: 'Scoped access keys', - desc: 'Authorize sessions, agents, and apps with spending limits, expiry, and call scopes.', + id: 'access-keys', + kicker: 'ACCESS KEYS', + title: 'Delegate only the permissions a product needs.', + desc: 'Access keys let wallets, apps, and agents sign constrained actions with expiry, revocation, and periodic spend limits enforced before execution.', href: '/docs/guide/tempo-transaction#access-keys', - accent: 'bg-[#16A34A]', + cta: 'Access keys', + diagram: 'access', + points: [ + { + title: 'Periodic spend limits', + desc: 'Cap a key by day, week, or session window before it ever signs.', + href: '/docs/protocol/transactions/AccountKeychain#spending-limit-enforcement', + accent: 'bg-[#C084FC]', + }, + { + title: 'Call scopes', + desc: 'Constrain a key to payment, payout, checkout, or app-specific calls.', + href: '/docs/protocol/transactions/AccountKeychain#call-scope-enforcement', + accent: 'bg-[#6EE7B7]', + }, + { + title: 'Instant revocation', + desc: 'Disable an app or old device without rotating the root key.', + href: '/docs/protocol/transactions/AccountKeychain#key-revocation', + accent: 'bg-[#737373]', + }, + ], }, { - title: 'Virtual addresses', - desc: 'Issue per-customer deposit addresses that resolve back to a master account.', - href: '/docs/guide/payments/virtual-addresses', - accent: 'bg-[#F97316]', + id: 'receive-policies', + kicker: 'RECEIVE POLICIES', + title: 'Use TIP-403 policies at the account boundary.', + desc: 'Receive policies give accounts token, sender, and content controls for inbound transfers, deposits, and recovery-sensitive flows.', + href: '/docs/guide/payments/configure-receive-policies', + cta: 'Receive policies', + diagram: 'policies', + points: [ + { + title: 'Token rules', + desc: 'Accept only the stablecoins or assets the account is configured to receive.', + href: '/docs/guide/payments/configure-receive-policies', + accent: 'bg-[#6EE7B7]', + }, + { + title: 'Sender rules', + desc: 'Limit inbound funds to known processors, users, or settlement partners.', + href: '/docs/protocol/tip403/receive-policies', + accent: 'bg-[#73A0FF]', + }, + { + title: 'Content policies', + desc: 'Attach TIP-403 policy IDs to account flows instead of baking controls into every app.', + href: '/docs/protocol/tip403/spec', + accent: 'bg-[#C084FC]', + }, + ], }, { - title: 'Receive policies', - desc: 'Define token and sender controls for inbound transfers, mints, and recovery flows.', - href: '/docs/guide/payments/configure-receive-policies', - accent: 'bg-[#0891B2]', + id: 'virtual-addresses', + kicker: 'VIRTUAL ADDRESSES', + title: 'Private deposit attribution without new accounts.', + desc: 'Virtual addresses let products issue per-customer or per-invoice deposit addresses while funds resolve back to the master account.', + href: '/docs/guide/payments/virtual-addresses', + cta: 'Virtual addresses', + diagram: 'virtual', + points: [ + { + title: 'Deposit aliases', + desc: 'Generate many addresses for routing without creating permanent account state.', + href: '/docs/guide/payments/virtual-addresses', + accent: 'bg-[#F97316]', + }, + { + title: 'Privacy-preserving routing', + desc: 'Separate public deposit handles from treasury and settlement accounts.', + href: '/docs/protocol/tip20/virtual-addresses', + accent: 'bg-[#C084FC]', + }, + { + title: 'Master settlement', + desc: 'Attribute deposits while keeping balances and policy decisions on the master account.', + href: '/docs/guide/payments/virtual-addresses', + accent: 'bg-[#6EE7B7]', + }, + ], }, ] @@ -117,247 +223,443 @@ const ACCOUNT_FAQS: FaqItem[] = [ }, ] -function AccountModelDiagram() { - const nodes = [ - { label: 'ROOT KEY', detail: 'FULL CONTROL', className: 'left-[8%] top-[12%]' }, - { label: 'PASSKEY', detail: 'USER DEVICE', className: 'left-[8%] top-[38%]' }, - { label: 'ACCESS KEY', detail: 'SCOPED SESSION', className: 'left-[8%] top-[64%]' }, - { label: 'VIRTUAL ADDRESS', detail: 'DEPOSIT ALIAS', className: 'right-[7%] top-[16%]' }, - { label: 'RECEIVE POLICY', detail: 'TOKEN + SENDER', className: 'right-[7%] top-[46%]' }, - { label: 'FEE TOKEN', detail: 'STABLECOIN FEES', className: 'right-[7%] top-[70%]' }, - ] - +function AccessKeyDiagram() { return ( -
-
- -
-
-
-
- ACCOUNT -
-
- 0xA11c...09 -
-
-
-
-
SIG
-
P256
-
-
-
FEE
-
USDC
-
-
+
+ + Scoped access key relationships + + + + + +
+ +
ACCOUNT
+
+ ROOT · PASSKEY
-
- {nodes.map((node) => ( -
-
- {node.detail} -
-
- {node.label} -
-
- ))} +
+ +
+
APP KEY
+
+
+
+
+ PAYMENTS · ≤ $500/DAY · 7D +
+
+ +
+
AGENT KEY
+
+
+
+
+ CHECKOUT · ≤ $100/DAY
+
+
OLD KEY
+
+
+ REVOKED · INSTANTLY +
+
+ +
+ REVOKED + + × + +
+
+ ) +} + +function AccessKeysPanel() { + const [mode, setMode] = useState('visual') + + return ( +
+
+ +
+ {mode === 'visual' ? ( + + ) : ( +
+
+ +
+
+ )} +
+ ) +} + +function KeyTypesDiagram() { + return ( +
+
- Account model relationships + Root, passkey, and recovery auth paths + +
+
ACCOUNT
+
0xA11c...09
+
+ {[ + ['ROOT KEY', 'FULL CONTROL', 'top-[13%] left-[7%]', 'bg-[#73A0FF]'], + ['PASSKEY', 'USER DEVICE', 'top-[40%] left-[7%]', 'bg-[#C084FC]'], + ['RECOVERY', 'BACKUP PATH', 'bottom-[13%] left-[7%]', 'bg-[#6EE7B7]'], + ].map(([label, detail, position, accent]) => ( +
+ +
+ {detail} +
+
{label}
+
+ ))} +
+ FLEXIBLE AUTH · SAME ADDRESS +
+
+ ) +} + +function PoliciesDiagram() { + return ( +
+ + Receive policy check flow + -
-
-
ACCOUNT
-
0xA11c...09
+
+
+ ALLOWED HOLDER
-
-
-
SIG
-
P256
-
-
-
FEE
-
USDC
-
+
+ ON WHITELIST
- {nodes.map((node) => ( - ) } -function IntroSection() { +function ReceivePoliciesPanel() { + const [mode, setMode] = useState('visual') + return ( -
- - -
- -

- ACCOUNT MODEL -

-

- One address, multiple controls. -

-

- Tempo accounts keep the operational simplicity of EVM addresses while adding native - account controls for signing, delegation, inbound funds, deposit attribution, and fee - payment. -

-
- - +
+
+ +
+ {mode === 'visual' ? ( + + ) : ( +
+
+
- -
+ )} +
) } -function CapabilitySection() { +function VirtualAddressDiagram() { return ( -
- -
-

CAPABILITIES

-

- Account primitives for payment products. -

-

- Use the same account surface for consumer wallets, merchant accounts, treasury flows, - hosted products, and agent-driven payments. -

+
+
+ + + Virtual addresses resolving to a master account + + + + + + {[ + ['INV-1042', 'DEPOSIT ALIAS', 'top-[18%] left-[8%]'], + ['USER-83', 'CUSTOMER TAG', 'top-[45%] left-[8%]'], + ['PAYOUT-7', 'ROUTING KEY', 'bottom-[18%] left-[8%]'], + ].map(([label, detail, position]) => ( +
+
{detail}
+
{label}
-
- {capabilities.map((item) => ( - - -

- {item.title} -

-

- {item.desc} -

- - Read more - -
- ))} + ))} +
+
+ MASTER ACCOUNT
- -
+
0xA11c...09
+
+ BALANCE · POLICY · SETTLEMENT +
+
+
) } -function CodeSection() { +function AccountFeatureDiagram({ kind }: { kind: AccountFeature['diagram'] }) { + if (kind === 'keys') return + if (kind === 'access') return + if (kind === 'policies') return + return +} + +function AccountFeatureSection({ feature, index }: { feature: AccountFeature; index: number }) { + const visualFirst = index % 2 === 1 + + const copy = ( +
+

{feature.kicker}

+

+ {feature.title} +

+

+ {feature.desc} +

+
+ +
+
+ ) + + const diagram = + feature.diagram === 'access' ? ( + + ) : feature.diagram === 'policies' ? ( + + ) : ( + + ) + return ( -
- +
+ -
-
-

- IMPLEMENTATION -

-

- Compose keys, aliases, and policies. -

-

- Accounts become a reusable infrastructure layer: sign with root keys, delegate to - scoped keys, route deposits through virtual addresses, and enforce receive rules. -

-
- - -
-
-
- -
+
+ {visualFirst ? ( + <> + {diagram} + {copy} + + ) : ( + <> + {copy} + {diagram} + + )} +
+
+ {feature.points.map((point) => ( + + +

+ {point.title} +

+

+ {point.desc} +

+
+ ))}
@@ -367,9 +669,9 @@ function CodeSection() { export default function AccountsSections() { return ( <> - - - + {accountFeatures.map((feature, index) => ( + + ))} Date: Tue, 14 Jul 2026 14:56:37 +0100 Subject: [PATCH 08/10] fix: improve accounts mobile diagrams --- src/marketing/app/_components/CodePanel.tsx | 2 +- src/marketing/app/_components/CodeWindow.tsx | 6 +- .../features/_components/AccountsSections.tsx | 75 ++++++++----------- 3 files changed, 37 insertions(+), 46 deletions(-) diff --git a/src/marketing/app/_components/CodePanel.tsx b/src/marketing/app/_components/CodePanel.tsx index c7f009cc..18493570 100644 --- a/src/marketing/app/_components/CodePanel.tsx +++ b/src/marketing/app/_components/CodePanel.tsx @@ -248,7 +248,7 @@ export default function CodePanel({ if (inline) { return (
diff --git a/src/marketing/app/_components/CodeWindow.tsx b/src/marketing/app/_components/CodeWindow.tsx index 07c903fc..1e2d2e6e 100644 --- a/src/marketing/app/_components/CodeWindow.tsx +++ b/src/marketing/app/_components/CodeWindow.tsx @@ -42,7 +42,7 @@ export default function CodeWindow({ return (
@@ -71,13 +71,13 @@ export default function CodeWindow({ ))}
) : null} -
+
{panels.map((panel, index) => (
diff --git a/src/marketing/app/features/_components/AccountsSections.tsx b/src/marketing/app/features/_components/AccountsSections.tsx index cde2c8ce..bdb17f49 100644 --- a/src/marketing/app/features/_components/AccountsSections.tsx +++ b/src/marketing/app/features/_components/AccountsSections.tsx @@ -225,10 +225,10 @@ const ACCOUNT_FAQS: FaqItem[] = [ function AccessKeyDiagram() { return ( -
+
@@ -262,7 +262,7 @@ function AccessKeyDiagram() { /> -
+
ACCOUNT
@@ -270,7 +270,7 @@ function AccessKeyDiagram() {
-
+
APP KEY
@@ -280,7 +280,7 @@ function AccessKeyDiagram() {
-
+
AGENT KEY
@@ -290,7 +290,7 @@ function AccessKeyDiagram() {
-
+
OLD KEY
@@ -298,7 +298,7 @@ function AccessKeyDiagram() {
-
+
REVOKED × @@ -335,14 +335,14 @@ function AccessKeysPanel() { function KeyTypesDiagram() { return ( -
+
@@ -373,18 +373,18 @@ function KeyTypesDiagram() { className="text-[#6EE7B7]/70" /> -
+
ACCOUNT
0xA11c...09
{[ - ['ROOT KEY', 'FULL CONTROL', 'top-[13%] left-[7%]', 'bg-[#73A0FF]'], - ['PASSKEY', 'USER DEVICE', 'top-[40%] left-[7%]', 'bg-[#C084FC]'], - ['RECOVERY', 'BACKUP PATH', 'bottom-[13%] left-[7%]', 'bg-[#6EE7B7]'], + ['ROOT KEY', 'FULL CONTROL', 'lg:top-[13%] lg:left-[7%]', 'bg-[#73A0FF]'], + ['PASSKEY', 'USER DEVICE', 'lg:top-[40%] lg:left-[7%]', 'bg-[#C084FC]'], + ['RECOVERY', 'BACKUP PATH', 'lg:bottom-[13%] lg:left-[7%]', 'bg-[#6EE7B7]'], ].map(([label, detail, position, accent]) => (
@@ -402,10 +402,10 @@ function KeyTypesDiagram() { function PoliciesDiagram() { return ( -
+
@@ -447,7 +447,7 @@ function PoliciesDiagram() { /> -
+
ALLOWED HOLDER
@@ -456,7 +456,7 @@ function PoliciesDiagram() {
-
+
APPROVED TRANSFER
@@ -465,7 +465,7 @@ function PoliciesDiagram() {
-
+
BLOCKED ADDRESS
@@ -474,18 +474,18 @@ function PoliciesDiagram() {
-
+
POLICY CHECK
-
+
× BLOCKED
-
+
TOKEN
@@ -523,14 +523,14 @@ function ReceivePoliciesPanel() { function VirtualAddressDiagram() { return ( -
+
@@ -566,19 +566,19 @@ function VirtualAddressDiagram() { /> {[ - ['INV-1042', 'DEPOSIT ALIAS', 'top-[18%] left-[8%]'], - ['USER-83', 'CUSTOMER TAG', 'top-[45%] left-[8%]'], - ['PAYOUT-7', 'ROUTING KEY', 'bottom-[18%] left-[8%]'], + ['INV-1042', 'DEPOSIT ALIAS', 'lg:top-[18%] lg:left-[8%]'], + ['USER-83', 'CUSTOMER TAG', 'lg:top-[45%] lg:left-[8%]'], + ['PAYOUT-7', 'ROUTING KEY', 'lg:bottom-[18%] lg:left-[8%]'], ].map(([label, detail, position]) => (
{detail}
{label}
))} -
+
MASTER ACCOUNT
@@ -632,17 +632,8 @@ function AccountFeatureSection({ feature, index }: { feature: AccountFeature; in
- {visualFirst ? ( - <> - {diagram} - {copy} - - ) : ( - <> - {copy} - {diagram} - - )} +
{copy}
+
{diagram}
{feature.points.map((point) => ( From e9cf64356242c8acb3cefb24e057916c0b21704e Mon Sep 17 00:00:00 2001 From: Emma Jamieson-Hoare Date: Wed, 15 Jul 2026 14:02:23 +0100 Subject: [PATCH 09/10] docs: add account key guides --- .../accounts/account-keychain-precompile.mdx | 83 +++++++++++++++++++ src/pages/docs/guide/accounts/index.mdx | 12 +-- .../docs/guide/accounts/use-access-keys.mdx | 77 +++++++++++++++++ vocs.config.ts | 10 ++- 4 files changed, 172 insertions(+), 10 deletions(-) create mode 100644 src/pages/docs/guide/accounts/account-keychain-precompile.mdx create mode 100644 src/pages/docs/guide/accounts/use-access-keys.mdx diff --git a/src/pages/docs/guide/accounts/account-keychain-precompile.mdx b/src/pages/docs/guide/accounts/account-keychain-precompile.mdx new file mode 100644 index 00000000..614a209f --- /dev/null +++ b/src/pages/docs/guide/accounts/account-keychain-precompile.mdx @@ -0,0 +1,83 @@ +--- +title: Use the Account Keychain Precompile +description: Use Tempo's Account Keychain precompile to authorize, inspect, update, and revoke access keys with expiry, spending limits, and call scopes for an account. +--- + +import { Cards, Card } from 'vocs' + +# Use the Account Keychain Precompile + +The Account Keychain precompile stores the access keys authorized for a Tempo account and enforces their permissions. It is deployed at `0xAAAAAAAA00000000000000000000000000000000` on Tempo networks. + +Most applications should use an SDK to encode keychain calls. Use the precompile directly when you are building wallet infrastructure, account recovery, key-management interfaces, or tooling that needs to inspect and update keys. + +## Choose an authorization path + +There are two ways to register a limited access key: + +| Path | Best for | What happens | +|---|---|---| +| Transaction-level `keyAuthorization` | First-use and connect flows | The first transaction registers the key and uses it in one atomic flow | +| `authorizeKey(...)` on the precompile | Dedicated account-management flows | A root key or admin access key registers the key before it is used | + +Both paths create the same account-keychain state. The direct precompile ABI represents a non-expiring key with `type(uint64).max`; transaction-level authorization represents it by omitting `expiry`. Do not pass `0` as a direct-call expiry. + +Admin access keys use `authorizeAdminKey(...)`. They can manage other keys, but cannot have expiry, spending limits, or call scopes. + +## Manage the key lifecycle + +Call the precompile from the account being managed. Key-management writes must be signed by the root key or an active admin access key. + +| Operation | Use it to | +|---|---| +| `authorizeKey(...)` | Register a limited key with expiry, TIP-20 limits, and call scopes | +| `authorizeAdminKey(...)` | Register a key that can administer the account keychain | +| `updateSpendingLimit(...)` | Reset a limited key's allowance for one TIP-20 token | +| `setAllowedCalls(...)` | Add or replace target, selector, and recipient scopes | +| `removeAllowedCalls(...)` | Remove the scope stored for one target | +| `revokeKey(...)` | Permanently disable an authorized key ID for the account | + +Limited access keys cannot call these mutating functions. Revocation is permanent: once an account revokes a key ID, that same key ID cannot be authorized again for the account. + +## Read key state + +Use the view functions to build account-management and policy checks: + +- `getKey(...)` returns the key's signature type, expiry, limit-enforcement flag, and revocation state. +- `getRemainingLimitWithPeriod(...)` returns the effective remaining TIP-20 allowance and the current period end. +- `getAllowedCalls(...)` distinguishes unrestricted keys from scoped keys and returns their call allowlist. +- `isAdminKey(...)` checks whether a signer has account-administration authority. +- `getTransactionKey()` returns the key used by the current transaction; `address(0)` means the root key. + +Treat missing, expired, and revoked keys as deny-all. For those keys, limit reads return zero and call-scope reads return a scoped empty allowlist. + +## Enforce account boundaries + +- Keep each access key specific to one app and device. +- Combine call scopes with spending limits; the two controls cover different risks. +- Remember that limited access keys cannot deploy contracts. +- Index keychain events instead of trying to enumerate all keys from contract storage. +- Validate the exact ABI and network behavior before hand-encoding calls. + +## Implement the keychain + + + + + + diff --git a/src/pages/docs/guide/accounts/index.mdx b/src/pages/docs/guide/accounts/index.mdx index cc782a80..fdddc27d 100644 --- a/src/pages/docs/guide/accounts/index.mdx +++ b/src/pages/docs/guide/accounts/index.mdx @@ -27,19 +27,13 @@ See [Tempo Accounts](/docs/protocol/accounts) for the protocol model behind thes - diff --git a/src/pages/docs/guide/accounts/use-access-keys.mdx b/src/pages/docs/guide/accounts/use-access-keys.mdx new file mode 100644 index 00000000..9e3ff38c --- /dev/null +++ b/src/pages/docs/guide/accounts/use-access-keys.mdx @@ -0,0 +1,77 @@ +--- +title: Use Access Keys +description: Use scoped access keys to sign for a Tempo account without repeated root-key prompts, with expiry, spending limits, call restrictions, and revocation. +--- + +import { Cards, Card } from 'vocs' + +# Use Access Keys + +Access keys are secondary signing keys authorized by a Tempo account. They let an app, device, agent, or session submit transactions without asking the account's root key to approve every action. + +Use a limited access key for routine transactions. Give it only the permissions it needs, then keep the root key or an admin access key for account administration. + +## Choose the key type + +Tempo accounts have three levels of signing authority: + +| Key | Use it for | Restrictions | +|---|---|---| +| Root key | Initial authorization, recovery, and unrestricted account control | None | +| Admin access key | Provisioning and revoking keys from another trusted device or recovery flow | Cannot have expiry, spending limits, or call scopes | +| Limited access key | Payments, connected apps, agents, subscriptions, and sessions | Can have expiry, per-token limits, and call scopes | + +Most applications should provision a limited access key after the user connects. Use an admin key only when the product needs delegated key management. + +## Set the restrictions + +Treat every limited access key as a capability. Configure all three controls deliberately: + +- **Expiry** sets when the key stops being valid. Prefer short-lived keys for sessions and connected apps. +- **Spending limits** cap how much of each TIP-20 token the key may spend. Limits can be one-time or recurring. +- **Call scopes** restrict the contracts, functions, and optional recipients the key may call. + +An unrestricted key has no call allowlist. A scoped key with an empty allowlist is deny-all. Access-key-signed transactions cannot create contracts, even when the key is otherwise unrestricted. + +:::warning +TIP-20 spending limits cover `transfer`, `transferWithMemo`, and increases made through `approve`. They do not limit native value, NFTs, or a direct `transferFrom` call. Use call scopes as well as spending limits when a key must be tightly constrained. +::: + +## Authorize and use the key + +The first transaction can carry both a root-key-signed `keyAuthorization` and the access-key signature. Tempo registers the key, validates its restrictions, and then executes the transaction without requiring a separate setup transaction. + +After that first use, the access key signs subsequent Tempo Transactions directly. The protocol looks up the key for the account and enforces its expiry, spending limits, and call scopes during validation and execution. + +Use the [Account Keychain precompile guide](/docs/guide/accounts/account-keychain-precompile) when you need to authorize a key in a dedicated transaction, update its restrictions, inspect its remaining allowance, or revoke it. + +## Operate access keys safely + +- Generate a separate key for each app and device. Do not share one access key across clients. +- Store browser keys as non-extractable WebCrypto keys rather than raw private-key material. +- Index `KeyAuthorized`, `AdminKeyAuthorized`, `KeyRevoked`, and `SpendingLimitUpdated` events for account-management state. +- Revoke a key when a device is lost or an integration is disconnected. A revoked key ID cannot be authorized again for the same account. +- Use the root key for contract deployment; access-key-signed transactions cannot use `CREATE` or `CREATE2`. + +## Implement access keys + + + + + + diff --git a/vocs.config.ts b/vocs.config.ts index 47eee4bb..7979e578 100644 --- a/vocs.config.ts +++ b/vocs.config.ts @@ -320,6 +320,14 @@ export default defineConfig({ text: 'Use virtual addresses', link: '/docs/guide/payments/virtual-addresses', }, + { + text: 'Use access keys', + link: '/docs/guide/accounts/use-access-keys', + }, + { + text: 'Account keychain precompile', + link: '/docs/guide/accounts/account-keychain-precompile', + }, ], }, { @@ -656,7 +664,7 @@ export default defineConfig({ }, { text: 'Tempo Accounts', - link: '/docs/guide/accounts', + link: '/docs/protocol/accounts', }, { text: 'TIP-20 Tokens', From 05588188c6aab1e0d72b1633dd287896e50c8f45 Mon Sep 17 00:00:00 2001 From: Emma Jamieson-Hoare Date: Wed, 15 Jul 2026 16:28:16 +0100 Subject: [PATCH 10/10] docs: refine account access key guides --- src/marketing/app/_components/features.tsx | 6 +- .../_components/transactionCodeVariants.ts | 47 ++++++----- .../features/_components/AccountsSections.tsx | 24 +++--- .../accounts/account-keychain-precompile.mdx | 83 ------------------- src/pages/docs/guide/accounts/index.mdx | 10 +-- .../docs/guide/accounts/use-access-keys.mdx | 71 +++++++--------- src/pages/docs/protocol/accounts/index.mdx | 4 +- src/pages/docs/sdk/rust/index.mdx | 50 ++++++++++- vocs.config.ts | 4 - 9 files changed, 122 insertions(+), 177 deletions(-) delete mode 100644 src/pages/docs/guide/accounts/account-keychain-precompile.mdx diff --git a/src/marketing/app/_components/features.tsx b/src/marketing/app/_components/features.tsx index d558f28b..9e428b43 100644 --- a/src/marketing/app/_components/features.tsx +++ b/src/marketing/app/_components/features.tsx @@ -120,11 +120,11 @@ export const features: Feature[] = [ primary: true, }, { - label: 'Access keys', - href: '/docs/guide/tempo-transaction#access-keys', + label: 'Use access keys', + href: '/docs/guide/accounts/use-access-keys', }, { - label: 'Receive policies', + label: 'Configure receive policies', href: '/docs/guide/payments/configure-receive-policies', }, ], diff --git a/src/marketing/app/_components/transactionCodeVariants.ts b/src/marketing/app/_components/transactionCodeVariants.ts index c01a17e2..92da0903 100644 --- a/src/marketing/app/_components/transactionCodeVariants.ts +++ b/src/marketing/app/_components/transactionCodeVariants.ts @@ -352,33 +352,36 @@ export const accessKeyCodeVariants: CodeVariant[] = [ { lang: 'Rust', code: [ - 'use alloy::primitives::{Address, U256};', - 'use tempo_alloy::contracts::precompiles::{', - ' IAccountKeychain, TokenLimit, ACCOUNT_KEYCHAIN,', + 'use alloy::primitives::U256;', + 'use tempo_alloy::{', + ' primitives::{SignatureType, transaction::TokenLimit},', + ' provider::{', + ' KeyRestrictions,', + ' keychain::authorize_key,', + ' },', '};', '', - 'let keychain = IAccountKeychain::new(ACCOUNT_KEYCHAIN, provider.clone());', - 'let expiry = now + 7 * 24 * 60 * 60;', - '', - 'keychain', - ' .authorizeKey(', - ' access_key,', - ' 1u8,', - ' expiry,', - ' true,', - ' vec![TokenLimit {', - ' token: alpha_usd,', - ' amount: U256::from(500_000_000),', - ' }],', - ' )', - ' .send()', - ' .await?;', + 'let restrictions = KeyRestrictions::default()', + ' .with_expiry(now + 7 * 24 * 60 * 60)', + ' .with_limits(vec![TokenLimit {', + ' token: alpha_usd,', + ' limit: U256::from(500_000_000),', + ' period: 0,', + ' }]);', + '', + 'let authorize = authorize_key(', + ' access_key,', + ' SignatureType::P256,', + ' restrictions,', + ');', + '', + '// Include `authorize` in a root-key-signed Tempo Transaction.', ], highlight: [ - 'IAccountKeychain::new(ACCOUNT_KEYCHAIN, provider.clone())', - '.authorizeKey(', + 'let restrictions = KeyRestrictions::default()', 'TokenLimit {', - 'amount: U256::from(500_000_000)', + 'let authorize = authorize_key(', + 'SignatureType::P256', ], }, { diff --git a/src/marketing/app/features/_components/AccountsSections.tsx b/src/marketing/app/features/_components/AccountsSections.tsx index bdb17f49..9f523227 100644 --- a/src/marketing/app/features/_components/AccountsSections.tsx +++ b/src/marketing/app/features/_components/AccountsSections.tsx @@ -80,9 +80,9 @@ const accountFeatures: AccountFeature[] = [ id: 'key-types', kicker: 'KEY TYPES', title: 'Flexible auth without changing the address.', - desc: 'Tempo accounts keep ordinary EVM addresses while adding native signing paths for root keys, passkeys, recovery, and app-owned infrastructure.', + desc: 'Tempo accounts keep ordinary EVM addresses while adding native signing paths for root keys, passkeys, admin access keys, and app-owned infrastructure.', href: '/docs/guide/accounts', - cta: 'Account guide', + cta: 'Read the account guide', diagram: 'keys', points: [ { @@ -93,14 +93,14 @@ const accountFeatures: AccountFeature[] = [ }, { title: 'Passkeys', - desc: 'Use device-native signing for wallet UX and recovery flows.', + desc: 'Use device-native signing for passwordless wallet experiences.', href: '/docs/guide/accounts', accent: 'bg-[#C084FC]', }, { - title: 'Privacy by default', - desc: 'Use aliases and delegated keys without exposing every operational relationship.', - href: '/docs/guide/payments/virtual-addresses', + title: 'Admin access keys', + desc: 'Let a previously authorized device add or revoke account keys.', + href: '/docs/guide/accounts/use-access-keys', accent: 'bg-[#6EE7B7]', }, ], @@ -110,8 +110,8 @@ const accountFeatures: AccountFeature[] = [ kicker: 'ACCESS KEYS', title: 'Delegate only the permissions a product needs.', desc: 'Access keys let wallets, apps, and agents sign constrained actions with expiry, revocation, and periodic spend limits enforced before execution.', - href: '/docs/guide/tempo-transaction#access-keys', - cta: 'Access keys', + href: '/docs/guide/accounts/use-access-keys', + cta: 'Use access keys', diagram: 'access', points: [ { @@ -140,7 +140,7 @@ const accountFeatures: AccountFeature[] = [ title: 'Use TIP-403 policies at the account boundary.', desc: 'Receive policies give accounts token, sender, and content controls for inbound transfers, deposits, and recovery-sensitive flows.', href: '/docs/guide/payments/configure-receive-policies', - cta: 'Receive policies', + cta: 'Configure receive policies', diagram: 'policies', points: [ { @@ -169,7 +169,7 @@ const accountFeatures: AccountFeature[] = [ title: 'Private deposit attribution without new accounts.', desc: 'Virtual addresses let products issue per-customer or per-invoice deposit addresses while funds resolve back to the master account.', href: '/docs/guide/payments/virtual-addresses', - cta: 'Virtual addresses', + cta: 'Use virtual addresses', diagram: 'virtual', points: [ { @@ -346,7 +346,7 @@ function KeyTypesDiagram() { role="img" viewBox="0 0 720 520" > - Root, passkey, and recovery auth paths + Root, passkey, and admin access key paths (
- - - - diff --git a/src/pages/docs/guide/accounts/index.mdx b/src/pages/docs/guide/accounts/index.mdx index fdddc27d..b3b99820 100644 --- a/src/pages/docs/guide/accounts/index.mdx +++ b/src/pages/docs/guide/accounts/index.mdx @@ -3,7 +3,7 @@ title: Manage Accounts description: Configure Tempo accounts with receive policies, virtual addresses, access keys, and protocol account controls. --- -import { Cards, Card } from 'vocs' +import { Cards, Card } from "vocs"; # Manage Accounts @@ -26,14 +26,8 @@ See [Tempo Accounts](/docs/protocol/accounts) for the protocol model behind thes /> - diff --git a/src/pages/docs/guide/accounts/use-access-keys.mdx b/src/pages/docs/guide/accounts/use-access-keys.mdx index 9e3ff38c..e1471cdb 100644 --- a/src/pages/docs/guide/accounts/use-access-keys.mdx +++ b/src/pages/docs/guide/accounts/use-access-keys.mdx @@ -1,6 +1,6 @@ --- title: Use Access Keys -description: Use scoped access keys to sign for a Tempo account without repeated root-key prompts, with expiry, spending limits, call restrictions, and revocation. +description: Use and manage scoped access keys for routine app, device, agent, and session transactions, with guidance for permissions, storage, and key lifecycle. --- import { Cards, Card } from 'vocs' @@ -9,49 +9,32 @@ import { Cards, Card } from 'vocs' Access keys are secondary signing keys authorized by a Tempo account. They let an app, device, agent, or session submit transactions without asking the account's root key to approve every action. -Use a limited access key for routine transactions. Give it only the permissions it needs, then keep the root key or an admin access key for account administration. +Use a limited access key for routine transactions and give it only the permissions the product needs. -## Choose the key type +## Decide when to use an access key -Tempo accounts have three levels of signing authority: +Access keys are a good fit for: -| Key | Use it for | Restrictions | -|---|---|---| -| Root key | Initial authorization, recovery, and unrestricted account control | None | -| Admin access key | Provisioning and revoking keys from another trusted device or recovery flow | Cannot have expiry, spending limits, or call scopes | -| Limited access key | Payments, connected apps, agents, subscriptions, and sessions | Can have expiry, per-token limits, and call scopes | +- Connected apps that need to submit transactions after the user connects +- Devices that should not hold the account's root key +- Agents, subscriptions, and sessions with a narrow set of permissions -Most applications should provision a limited access key after the user connects. Use an admin key only when the product needs delegated key management. +## Configure it for the product -## Set the restrictions - -Treat every limited access key as a capability. Configure all three controls deliberately: - -- **Expiry** sets when the key stops being valid. Prefer short-lived keys for sessions and connected apps. -- **Spending limits** cap how much of each TIP-20 token the key may spend. Limits can be one-time or recurring. -- **Call scopes** restrict the contracts, functions, and optional recipients the key may call. - -An unrestricted key has no call allowlist. A scoped key with an empty allowlist is deny-all. Access-key-signed transactions cannot create contracts, even when the key is otherwise unrestricted. - -:::warning -TIP-20 spending limits cover `transfer`, `transferWithMemo`, and increases made through `approve`. They do not limit native value, NFTs, or a direct `transferFrom` call. Use call scopes as well as spending limits when a key must be tightly constrained. -::: - -## Authorize and use the key - -The first transaction can carry both a root-key-signed `keyAuthorization` and the access-key signature. Tempo registers the key, validates its restrictions, and then executes the transaction without requiring a separate setup transaction. - -After that first use, the access key signs subsequent Tempo Transactions directly. The protocol looks up the key for the account and enforces its expiry, spending limits, and call scopes during validation and execution. +- Generate a separate key for each app and device instead of sharing one key across clients. +- Prefer short-lived keys for sessions and connected apps. +- Set spending limits around the product's expected transaction size and frequency. +- Restrict calls to the targets, actions, and recipients the product needs. +- Store browser keys as non-extractable WebCrypto keys rather than raw private-key material. -Use the [Account Keychain precompile guide](/docs/guide/accounts/account-keychain-precompile) when you need to authorize a key in a dedicated transaction, update its restrictions, inspect its remaining allowance, or revoke it. +## Manage the key lifecycle -## Operate access keys safely +- Authorize the key after the user connects, either during its first transaction or in a separate account-management action. +- Show the key's purpose, expiry, and permissions in the account interface. +- Update permissions when the product's needs change. +- Revoke keys for disconnected apps and retired devices. -- Generate a separate key for each app and device. Do not share one access key across clients. -- Store browser keys as non-extractable WebCrypto keys rather than raw private-key material. -- Index `KeyAuthorized`, `AdminKeyAuthorized`, `KeyRevoked`, and `SpendingLimitUpdated` events for account-management state. -- Revoke a key when a device is lost or an integration is disconnected. A revoked key ID cannot be authorized again for the same account. -- Use the root key for contract deployment; access-key-signed transactions cannot use `CREATE` or `CREATE2`. +Use an SDK for these workflows rather than encoding Account Keychain calls directly. Read the [Account Keychain Precompile specification](/docs/protocol/transactions/AccountKeychain) for the authorization model, exact restriction semantics, ABI, validation behavior, and security considerations. ## Implement access keys @@ -63,13 +46,19 @@ Use the [Account Keychain precompile guide](/docs/guide/accounts/account-keychai icon="lucide:blocks" /> + (Call, Call, Call) { + let restrictions = KeyRestrictions::default() + .with_expiry(expiry) + .with_limits(vec![TokenLimit { + token, + limit: U256::from(500_000_000u64), + period: 0, + }]); + + let authorize_call = authorize_key( + access_key, + SignatureType::P256, + restrictions, + ); + + let update_limit_call = update_spending_limit( + access_key, + token, + U256::from(1_000_000_000u64), + ); + + let revoke_call = revoke_key(access_key); + (authorize_call, update_limit_call, revoke_call) +} +``` + +See [Use Access Keys](/docs/guide/accounts/use-access-keys) for the builder workflow and the [Account Keychain Precompile specification](/docs/protocol/transactions/AccountKeychain) for exact authorization and validation behavior. + :::: diff --git a/vocs.config.ts b/vocs.config.ts index 7979e578..6d827957 100644 --- a/vocs.config.ts +++ b/vocs.config.ts @@ -324,10 +324,6 @@ export default defineConfig({ text: 'Use access keys', link: '/docs/guide/accounts/use-access-keys', }, - { - text: 'Account keychain precompile', - link: '/docs/guide/accounts/account-keychain-precompile', - }, ], }, {