diff --git a/src/lib/og-sections.ts b/src/lib/og-sections.ts index ab191f45..3d158714 100644 --- a/src/lib/og-sections.ts +++ b/src/lib/og-sections.ts @@ -34,6 +34,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/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/_components/Footer.tsx b/src/marketing/app/_components/Footer.tsx index b50462f9..ccc06734 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 8f8e1d19..1146ece0 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: featurePath('accounts'), + icon: , + }, { label: 'Tempo Transactions', desc: 'Flexible transactions for batching, fee sponsorship, scheduling, and more', diff --git a/src/marketing/app/_components/features.tsx b/src/marketing/app/_components/features.tsx index 4068b385..9e428b43 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: 'Use access keys', + href: '/docs/guide/accounts/use-access-keys', + }, + { + label: 'Configure receive policies', + href: '/docs/guide/payments/configure-receive-policies', + }, + ], + }, { slug: 'transactions', title: 'Tempo Transactions', 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/_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/_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..f68f38ea 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' @@ -42,7 +43,13 @@ export default function FeaturePage({ params }: { params: FeatureParams }) {
- +

{feature.title} @@ -80,7 +87,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..9f523227 --- /dev/null +++ b/src/marketing/app/features/_components/AccountsSections.tsx @@ -0,0 +1,673 @@ +'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' + +const CODE_WINDOW_HEIGHT = 'h-[420px] max-h-[420px] lg:h-[460px] lg:max-h-[460px]' + +const receivePolicyCodeVariants: CodeVariant[] = [ + { + lang: 'TypeScript', + code: [ + 'import { client } from "./viem.config";', + '', + 'const policy = await client.receivePolicy.create({', + ' account: merchantAccount,', + ' allowedHolders: [customerAllowlist],', + ' approvedTransfers: [checkoutPolicy],', + ' blockedAddresses: [sanctionsList],', + '});', + '', + 'await client.account.setReceivePolicy(policy.id);', + ], + highlight: [ + 'client.receivePolicy.create({', + 'allowedHolders: [customerAllowlist]', + 'approvedTransfers: [checkoutPolicy]', + 'blockedAddresses: [sanctionsList]', + 'client.account.setReceivePolicy(policy.id)', + ], + }, + { + lang: 'CLI', + code: [ + 'cast send $RECEIVE_POLICY_REGISTRY \\', + ' "setPolicy(address,uint64)" \\', + ' "$MERCHANT_ACCOUNT" \\', + ' "$POLICY_ID" \\', + ' --rpc-url "$TEMPO_RPC_URL" \\', + ' --private-key "$ADMIN_KEY"', + '', + 'cast call $RECEIVE_POLICY_REGISTRY \\', + ' "check(address,address,address)(bool)" \\', + ' "$TOKEN" "$SENDER" "$MERCHANT_ACCOUNT"', + ], + highlight: [ + 'setPolicy(address,uint64)', + '"$MERCHANT_ACCOUNT"', + '"$POLICY_ID"', + 'check(address,address,address)(bool)', + ], + }, +] + +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[] = [ + { + 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, admin access keys, and app-owned infrastructure.', + href: '/docs/guide/accounts', + cta: 'Read the 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 passwordless wallet experiences.', + href: '/docs/guide/accounts', + accent: 'bg-[#C084FC]', + }, + { + 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]', + }, + ], + }, + { + 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/accounts/use-access-keys', + cta: 'Use 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]', + }, + ], + }, + { + 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: 'Configure 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]', + }, + ], + }, + { + 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: 'Use 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]', + }, + ], + }, +] + +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 AccessKeyDiagram() { + return ( +
+ + Scoped access key relationships + + + + + +
+ +
ACCOUNT
+
+ ROOT · PASSKEY +
+
+ +
+
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 ( +
+
+ + Root, passkey, and admin access key paths + + + + +
+
ACCOUNT
+
0xA11c...09
+
+ {[ + ['ROOT KEY', 'FULL CONTROL', 'lg:top-[13%] lg:left-[7%]', 'bg-[#73A0FF]'], + ['PASSKEY', 'USER DEVICE', 'lg:top-[40%] lg:left-[7%]', 'bg-[#C084FC]'], + ['ADMIN KEY', 'KEY MANAGEMENT', 'lg:bottom-[13%] lg:left-[7%]', 'bg-[#6EE7B7]'], + ].map(([label, detail, position, accent]) => ( +
+ +
+ {detail} +
+
{label}
+
+ ))} +
+ FLEXIBLE AUTH · SAME ADDRESS +
+
+ ) +} + +function PoliciesDiagram() { + return ( +
+ + Receive policy check flow + + + + + + +
+
+ ALLOWED HOLDER +
+
+ ON WHITELIST +
+
+ +
+
+ APPROVED TRANSFER +
+
+ POLICY OK +
+
+ +
+
+ BLOCKED ADDRESS +
+
+ ON BLACKLIST +
+
+ +
+ POLICY CHECK +
+ +
+ + × + + BLOCKED +
+ +
+ +
TOKEN
+
+ ISSUER POLICY +
+
+
+ ) +} + +function ReceivePoliciesPanel() { + const [mode, setMode] = useState('visual') + + return ( +
+
+ +
+ {mode === 'visual' ? ( + + ) : ( +
+
+ +
+
+ )} +
+ ) +} + +function VirtualAddressDiagram() { + return ( +
+
+ + + Virtual addresses resolving to a master account + + + + + + {[ + ['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 +
+
0xA11c...09
+
+ BALANCE · POLICY · SETTLEMENT +
+
+
+ ) +} + +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 ( +
+ + +
+
{copy}
+
{diagram}
+
+
+ {feature.points.map((point) => ( + + +

+ {point.title} +

+

+ {point.desc} +

+
+ ))} +
+
+
+ ) +} + +export default function AccountsSections() { + return ( + <> + {accountFeatures.map((feature, index) => ( + + ))} + + + ) +} 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 ( + + + + ) +} 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/guide/accounts/use-access-keys.mdx b/src/pages/docs/guide/accounts/use-access-keys.mdx new file mode 100644 index 00000000..e1471cdb --- /dev/null +++ b/src/pages/docs/guide/accounts/use-access-keys.mdx @@ -0,0 +1,66 @@ +--- +title: Use Access Keys +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' + +# 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 and give it only the permissions the product needs. + +## Decide when to use an access key + +Access keys are a good fit for: + +- 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 + +## Configure it for the product + +- 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. + +## Manage the key lifecycle + +- 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. + +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 + + + + + + + diff --git a/src/pages/docs/protocol/accounts/index.mdx b/src/pages/docs/protocol/accounts/index.mdx new file mode 100644 index 00000000..be77cd1e --- /dev/null +++ b/src/pages/docs/protocol/accounts/index.mdx @@ -0,0 +1,65 @@ +--- +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, device replacement, and key management | +| 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 + +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, including adding or revoking keys from another trusted device. +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 + (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 f399037f..e74f14b7 100644 --- a/vocs.config.ts +++ b/vocs.config.ts @@ -62,8 +62,9 @@ export default defineConfig({ }, title: 'Tempo Docs', titleTemplate: (path, { title }) => { - if (path === '/docs') return 'Tempo %s ⋅ Tempo Docs' - if (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` @@ -157,6 +160,7 @@ export default defineConfig({ } const subsectionMap: Record = { + accounts: 'ACCOUNTS', blockspace: 'BLOCKSPACE', exchange: 'DEX', fees: 'FEES', @@ -329,6 +333,28 @@ 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: 'Use access keys', + link: '/docs/guide/accounts/use-access-keys', + }, + ], + }, { text: 'Make Payments', collapsed: false, @@ -345,18 +371,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', @@ -669,6 +687,10 @@ export default defineConfig({ text: 'Overview', link: '/docs/protocol', }, + { + text: 'Tempo Accounts', + link: '/docs/protocol/accounts', + }, { text: 'TIP-20 Tokens', collapsed: false, @@ -867,7 +889,7 @@ export default defineConfig({ items: [ { text: 'T8', - badge: { text: 'Planned', variant: 'note' }, + badge: { text: 'Planned', variant: 'note' as const }, link: '/docs/protocol/upgrades/t8', }, { @@ -1183,6 +1205,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,