diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index bb2a0522..8b2ddaee 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -36,6 +36,7 @@ jobs: run: pnpm run build env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NODE_OPTIONS: --max-old-space-size=4096 e2e: name: E2E Tests (${{ matrix.shard }}/${{ strategy.job-total }}) diff --git a/README.md b/README.md index 1111acb2..e3a1c82f 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,8 @@

- - tempo combomark + + tempo lockup

diff --git a/SKILL.md b/SKILL.md new file mode 100644 index 00000000..254e8360 --- /dev/null +++ b/SKILL.md @@ -0,0 +1,49 @@ +--- +name: tempo-docs +description: Answer Tempo blockchain questions using official documentation. Use when asked about Tempo protocol, TIP-20 tokens, fees, transactions, stablecoin DEX, or any Tempo-related questions. +--- + +# Tempo Docs + +Skill for navigating Tempo documentation and source code. + +## Quick Context + +Before using MCP tools, try fetching context directly: + +- **llms.txt** – Concise index of all pages: `https://docs.tempo.xyz/llms.txt` +- **Markdown pages** – Append `.md` to any page URL (e.g. `https://docs.tempo.xyz/quickstart/integrate-tempo.md`) + +Use `read_web_page` to fetch these when you need broad context or a quick answer. + +## MCP Tools + +Use these tools for structured exploration: + +| Tool | Description | +| --- | --- | +| `mcp__tempo_mcp__list_pages` | List all documentation pages | +| `mcp__tempo_mcp__read_page` | Read a specific documentation page | +| `mcp__tempo_mcp__search_docs` | Search documentation | +| `mcp__tempo_mcp__list_sources` | List available source repositories | +| `mcp__tempo_mcp__list_source_files` | List files in a directory | +| `mcp__tempo_mcp__read_source_file` | Read a source code file | +| `mcp__tempo_mcp__get_file_tree` | Get recursive file tree | +| `mcp__tempo_mcp__search_source` | Search source code | + +## Available Sources + +- `tempoxyz/tempo` – Tempo node (Rust) +- `tempoxyz/tempo-ts` – TypeScript SDK +- `paradigmxyz/reth` – Reth Ethereum client +- `foundry-rs/foundry` – Foundry toolkit +- `wevm/viem` – TypeScript Ethereum interface +- `wevm/wagmi` – React hooks for Ethereum + +## Workflow + +1. **Quick lookup**: Use `read_web_page` on `https://docs.tempo.xyz/llms.txt` for an overview, or fetch a specific page as Markdown +2. **Search docs**: Use `mcp__tempo_mcp__search_docs` to find relevant pages +3. **Read pages**: Use `mcp__tempo_mcp__read_page` with the page path +4. **Explore source**: Use `mcp__tempo_mcp__search_source` or `mcp__tempo_mcp__get_file_tree` to find implementations +5. **Read code**: Use `mcp__tempo_mcp__read_source_file` to examine specific files \ No newline at end of file diff --git a/package.json b/package.json index 24470774..938d28cc 100644 --- a/package.json +++ b/package.json @@ -37,9 +37,9 @@ "unplugin-auto-import": "^21.0.0", "unplugin-icons": "^23.0.1", "viem": "^2.44.4", - "vocs": "https://pkg.pr.new/wevm/vocs@e5ad67e", + "vocs": "https://pkg.pr.new/vocs@8b55a2c", "wagmi": "3.4.1", - "waku": "1.0.0-alpha.2", + "waku": "1.0.0-alpha.4", "zod": "^4.3.5" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 47067146..abccd96d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -89,14 +89,14 @@ importers: specifier: ^2.44.4 version: 2.44.4(typescript@5.9.3)(zod@4.3.5) vocs: - specifier: https://pkg.pr.new/wevm/vocs@e5ad67e - version: https://pkg.pr.new/wevm/vocs@e5ad67e(@types/react@19.2.9)(mermaid@11.12.2)(react-dom@19.2.3(react@19.2.3))(react-server-dom-webpack@19.2.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.104.1))(react@19.2.3)(rollup@4.56.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(waku@1.0.0-alpha.2(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.3(react@19.2.3))(react-server-dom-webpack@19.2.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.104.1))(react@19.2.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + specifier: https://pkg.pr.new/vocs@8b55a2c + version: https://pkg.pr.new/vocs@8b55a2c(@types/react@19.2.9)(mermaid@11.12.2)(react-dom@19.2.3(react@19.2.3))(react-server-dom-webpack@19.2.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.104.1))(react@19.2.3)(rollup@4.56.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(waku@1.0.0-alpha.4(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.3(react@19.2.3))(react-server-dom-webpack@19.2.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.104.1))(react@19.2.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) wagmi: specifier: 3.4.1 version: 3.4.1(@tanstack/query-core@5.90.19)(@tanstack/react-query@5.90.19(react@19.2.3))(@types/react@19.2.9)(ox@0.11.3(typescript@5.9.3)(zod@4.3.5))(react@19.2.3)(typescript@5.9.3)(viem@2.44.4(typescript@5.9.3)(zod@4.3.5)) waku: - specifier: 1.0.0-alpha.2 - version: 1.0.0-alpha.2(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.3(react@19.2.3))(react-server-dom-webpack@19.2.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.104.1))(react@19.2.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + specifier: 1.0.0-alpha.4 + version: 1.0.0-alpha.4(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.3(react@19.2.3))(react-server-dom-webpack@19.2.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.104.1))(react@19.2.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) zod: specifier: ^4.3.5 version: 4.3.5 @@ -803,6 +803,9 @@ packages: '@rolldown/pluginutils@1.0.0-beta.53': resolution: {integrity: sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==} + '@rolldown/pluginutils@1.0.0-rc.5': + resolution: {integrity: sha512-RxlLX/DPoarZ9PtxVrQgZhPoor987YtKQqCo5zkjX+0S0yLJ7Vv515Wk6+xtTL67VONKJKxETWZwuZjss2idYw==} + '@rollup/pluginutils@5.3.0': resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} engines: {node: '>=14.0.0'} @@ -1478,8 +1481,8 @@ packages: peerDependencies: vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 - '@vitejs/plugin-rsc@0.5.16': - resolution: {integrity: sha512-BopxxopgDQjKIikc6VW1BeC1uRJgY0HOi2uJnF3L+J44oQVjmHIunHEaO85InW6FnFtaMpBeLQ/OJ/sbnwhiEA==} + '@vitejs/plugin-rsc@0.5.21': + resolution: {integrity: sha512-uNayLT8IKvWoznvQyfwKuGiEFV28o7lxUDnw/Av36VCuGpDFZnMmvVCwR37gTvnSmnpul9V0tdJqY3tBKEaDqw==} peerDependencies: react: '*' react-dom: '*' @@ -2052,8 +2055,8 @@ packages: resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} engines: {node: '>=12'} - dotenv@17.2.3: - resolution: {integrity: sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==} + dotenv@17.3.1: + resolution: {integrity: sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==} engines: {node: '>=12'} dunder-proto@1.0.1: @@ -3441,8 +3444,8 @@ packages: resolution: {integrity: sha512-o2yiy7fYXK1HvzA8P6wwj8QSuwG3e/XcpWht/jIxkQX99c0SVPw0OXdLSV9fHASPiYB09HLA0uq8hokGydi/QA==} hasBin: true - srvx@0.10.1: - resolution: {integrity: sha512-A//xtfak4eESMWWydSRFUVvCTQbSwivnGCEf8YGPe2eHU0+Z6znfUTCPF0a7oV3sObSOcrXHlL6Bs9vVctfXdg==} + srvx@0.11.12: + resolution: {integrity: sha512-AQfrGqntqVPXgP03pvBDN1KyevHC+KmYVqb8vVf4N+aomQqdhaZxjvoVp+AOm4u6x+GgNQY3MVzAUIn+TqwkOA==} engines: {node: '>=20.16.0'} hasBin: true @@ -3810,8 +3813,8 @@ packages: vite: optional: true - vocs@https://pkg.pr.new/wevm/vocs@e5ad67e: - resolution: {integrity: sha512-CIZ4KPSeTK3f4V2Ti25OG6VPfMpd/2/j5vzbTlapoqd+cbYIbdbB5oOvlnhtInqvfXFLACVU/bNf8fx/HORVHw==, tarball: https://pkg.pr.new/wevm/vocs@e5ad67e} + vocs@https://pkg.pr.new/vocs@8b55a2c: + resolution: {tarball: https://pkg.pr.new/vocs@8b55a2c} version: 0.0.0 hasBin: true peerDependencies: @@ -3865,14 +3868,14 @@ packages: typescript: optional: true - waku@1.0.0-alpha.2: - resolution: {integrity: sha512-AqS1+jEH+gBeqbxt+Tg8cbDp9Kxqgilr/awifeaWyZsKfsMDfzi0thw/OZlW9KT2W8xJNV1lG4OwSNxgvigJpA==} + waku@1.0.0-alpha.4: + resolution: {integrity: sha512-gZFEaaAL0YWEE55Z5GAggaDkwgpEmkL/ok9uUzq8exykNbXEDAuVd+H/ctXfQqW6VZGZb1aEyEMvjIamDv9igQ==} engines: {node: ^24.0.0 || ^22.12.0 || ^20.19.0} hasBin: true peerDependencies: - react: ~19.2.3 - react-dom: ~19.2.3 - react-server-dom-webpack: ~19.2.3 + react: ~19.2.4 + react-dom: ~19.2.4 + react-server-dom-webpack: ~19.2.4 watchpack@2.5.1: resolution: {integrity: sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==} @@ -4685,6 +4688,8 @@ snapshots: '@rolldown/pluginutils@1.0.0-beta.53': {} + '@rolldown/pluginutils@1.0.0-rc.5': {} + '@rollup/pluginutils@5.3.0(rollup@4.56.0)': dependencies: '@types/estree': 1.0.8 @@ -5288,15 +5293,16 @@ snapshots: transitivePeerDependencies: - supports-color - '@vitejs/plugin-rsc@0.5.16(react-dom@19.2.3(react@19.2.3))(react-server-dom-webpack@19.2.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.104.1))(react@19.2.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))': + '@vitejs/plugin-rsc@0.5.21(react-dom@19.2.3(react@19.2.3))(react-server-dom-webpack@19.2.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.104.1))(react@19.2.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))': dependencies: + '@rolldown/pluginutils': 1.0.0-rc.5 es-module-lexer: 2.0.0 estree-walker: 3.0.3 magic-string: 0.30.21 periscopic: 4.0.2 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) - srvx: 0.10.1 + srvx: 0.11.12 strip-literal: 3.1.0 turbo-stream: 3.1.0 vite: 7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) @@ -5848,7 +5854,7 @@ snapshots: dotenv@16.6.1: {} - dotenv@17.2.3: {} + dotenv@17.3.1: {} dunder-proto@1.0.1: dependencies: @@ -7623,7 +7629,7 @@ snapshots: argparse: 2.0.1 nearley: 2.20.1 - srvx@0.10.1: {} + srvx@0.11.12: {} state-local@1.0.7: {} @@ -7974,7 +7980,7 @@ snapshots: optionalDependencies: vite: 7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) - vocs@https://pkg.pr.new/wevm/vocs@e5ad67e(@types/react@19.2.9)(mermaid@11.12.2)(react-dom@19.2.3(react@19.2.3))(react-server-dom-webpack@19.2.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.104.1))(react@19.2.3)(rollup@4.56.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(waku@1.0.0-alpha.2(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.3(react@19.2.3))(react-server-dom-webpack@19.2.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.104.1))(react@19.2.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)): + vocs@https://pkg.pr.new/vocs@8b55a2c(@types/react@19.2.9)(mermaid@11.12.2)(react-dom@19.2.3(react@19.2.3))(react-server-dom-webpack@19.2.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.104.1))(react@19.2.3)(rollup@4.56.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(waku@1.0.0-alpha.4(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.3(react@19.2.3))(react-server-dom-webpack@19.2.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.104.1))(react@19.2.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)): dependencies: '@base-ui/react': 1.1.0(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@codesandbox/sandpack-react': 2.20.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -7998,7 +8004,7 @@ snapshots: '@takumi-rs/image-response': 0.62.8 '@takumi-rs/wasm': 0.62.8 '@vitejs/plugin-react': 5.1.2(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) - '@vitejs/plugin-rsc': 0.5.16(react-dom@19.2.3(react@19.2.3))(react-server-dom-webpack@19.2.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.104.1))(react@19.2.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + '@vitejs/plugin-rsc': 0.5.21(react-dom@19.2.3(react@19.2.3))(react-server-dom-webpack@19.2.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.104.1))(react@19.2.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) cac: 6.7.14 cva: class-variance-authority@0.7.1 debug: 4.4.3 @@ -8050,7 +8056,7 @@ snapshots: optionalDependencies: mermaid: 11.12.2 vite: 7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) - waku: 1.0.0-alpha.2(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.3(react@19.2.3))(react-server-dom-webpack@19.2.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.104.1))(react@19.2.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + waku: 1.0.0-alpha.4(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.3(react@19.2.3))(react-server-dom-webpack@19.2.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.104.1))(react@19.2.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) transitivePeerDependencies: - '@cfworker/json-schema' - '@remix-run/react' @@ -8112,12 +8118,12 @@ snapshots: - ox - porto - waku@1.0.0-alpha.2(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.3(react@19.2.3))(react-server-dom-webpack@19.2.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.104.1))(react@19.2.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2): + waku@1.0.0-alpha.4(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.3(react@19.2.3))(react-server-dom-webpack@19.2.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.104.1))(react@19.2.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2): dependencies: '@hono/node-server': 1.19.9(hono@4.11.5) '@vitejs/plugin-react': 5.1.2(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) - '@vitejs/plugin-rsc': 0.5.16(react-dom@19.2.3(react@19.2.3))(react-server-dom-webpack@19.2.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.104.1))(react@19.2.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) - dotenv: 17.2.3 + '@vitejs/plugin-rsc': 0.5.21(react-dom@19.2.3(react@19.2.3))(react-server-dom-webpack@19.2.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.104.1))(react@19.2.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + dotenv: 17.3.1 hono: 4.11.5 magic-string: 0.30.21 picocolors: 1.1.1 diff --git a/public/learn/wallet-add-funds.png b/public/learn/wallet-add-funds.png new file mode 100644 index 00000000..2e82115f Binary files /dev/null and b/public/learn/wallet-add-funds.png differ diff --git a/src/components/IndexSupplyQuery.tsx b/src/components/IndexSupplyQuery.tsx index ceac85cb..6df8045c 100644 --- a/src/components/IndexSupplyQuery.tsx +++ b/src/components/IndexSupplyQuery.tsx @@ -1,7 +1,7 @@ 'use client' import * as React from 'react' import { isAddress, isHash } from 'viem' -import { tempoModerato } from 'viem/chains' +import { tempo } from 'viem/chains' import type * as z from 'zod/mini' import LucideExternalLink from '~icons/lucide/external-link' import { Container } from './Container' @@ -68,7 +68,7 @@ function getExplorerHost() { if (VITE_TEMPO_ENV !== 'testnet' && VITE_EXPLORER_OVERRIDE !== undefined) { return VITE_EXPLORER_OVERRIDE } - return tempoModerato.blockExplorers.default.url + return tempo.blockExplorers.default.url } function classifyHash(value: string | number | boolean | null): { diff --git a/src/components/MermaidDiagram.tsx b/src/components/MermaidDiagram.tsx new file mode 100644 index 00000000..8b06449f --- /dev/null +++ b/src/components/MermaidDiagram.tsx @@ -0,0 +1,1077 @@ +'use client' + +import { useCallback, useEffect, useRef, useState } from 'react' + +// --------------------------------------------------------------------------- +// Layout constants +// --------------------------------------------------------------------------- + +export const LAYOUT = { + padding: 20, + actorGap: 260, + actorGap2: 360, + actorBoxH: 36, + actorPadX: 24, + headerGap: 72, + + rowHeight: 72, + blockPadX: 12, + blockPadTop: 28, + blockPadBottom: 10, + labelLineGap: 22, + arrowSize: 8, + badgeR: 10, + noteBoxPadX: 16, + noteBoxPadY: 8, + noteExtraMargin: 28, + fontFamily: + '"Geist Pixel Square", "Geist Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace', + actorFontSize: 14, + actorFontWeight: 600, + labelFontSize: 14, + labelFontWeight: 400, + noteFontSize: 13, + noteFontWeight: 500, + badgeFontSize: 10, + blockLabelFontSize: 11, + blockLabelFontWeight: 600, + messageStroke: 1.2, + lifelineStroke: 0.75, +} + +export interface ThemeColors { + text: string + textMuted: string + line: string + lifeline: string + arrow: string + successArrow: string + errorCode: string + actorFill: string + actorStroke: string + blockStroke: string + blockHeaderBg: string + badgeBg: string + badgeText: string +} + +export const THEMES: Record<'light' | 'dark', ThemeColors> = { + light: { + text: '#27272a', + textMuted: '#3f3f46', + line: '#a1a1aa', + lifeline: '#d4d4d8', + arrow: '#0166ff', + successArrow: '#16a34a', + errorCode: '#dc2626', + actorFill: '#ffffff', + actorStroke: '#e4e4e7', + blockStroke: '#e4e4e7', + blockHeaderBg: '#f4f4f5', + badgeBg: '#e4e4e7', + badgeText: '#52525b', + }, + dark: { + text: '#e4e4e7', + textMuted: '#e4e4e7', + line: '#71717a', + lifeline: '#3f3f46', + arrow: '#60a5fa', + successArrow: '#4ade80', + errorCode: '#f87171', + actorFill: '#27272a', + actorStroke: '#3f3f46', + blockStroke: '#3f3f46', + blockHeaderBg: '#27272a', + badgeBg: '#3f3f46', + badgeText: '#a1a1aa', + }, +} + +// --------------------------------------------------------------------------- +// Parser +// --------------------------------------------------------------------------- + +export interface Participant { + id: string + label: string +} + +export type Step = + | { + type: 'message' + from: string + to: string + label: string + num: string | null + dashed: boolean + } + | { type: 'note'; over: string; text: string; num: string | null } + | { type: 'loop-start'; label: string } + | { type: 'loop-end' } + +export interface ParsedDiagram { + participants: Participant[] + steps: Step[] +} + +export function extractNum(text: string): { num: string | null; rest: string } { + const m = text.match(/^\((\d+)\)\s*(.+)$/) + return m ? { num: m[1], rest: m[2] } : { num: null, rest: text } +} + +export function parse(source: string): ParsedDiagram { + const lines = source + .split('\n') + .map((l) => l.trim()) + .filter((l) => l && !l.startsWith('%%')) + const participants: Participant[] = [] + const steps: Step[] = [] + const seen = new Set() + const ensure = (id: string) => { + if (!seen.has(id)) { + seen.add(id) + participants.push({ id, label: id }) + } + } + + for (const line of lines) { + if (line === 'sequenceDiagram') continue + const mPartAs = line.match(/^participant\s+(\S+)\s+as\s+(.+)$/i) + if (mPartAs) { + seen.add(mPartAs[1]) + participants.push({ id: mPartAs[1], label: mPartAs[2].trim() }) + continue + } + const mPart = line.match(/^participant\s+(\S+)$/i) + if (mPart) { + ensure(mPart[1]) + continue + } + const mNote = line.match(/^Note\s+over\s+(\S+?)\s*:\s*(.+)$/i) + if (mNote) { + ensure(mNote[1]) + const e = extractNum(mNote[2].trim()) + steps.push({ type: 'note', over: mNote[1], text: e.rest, num: e.num }) + continue + } + const mLoop = line.match(/^loop\s+(.+)$/i) + if (mLoop) { + steps.push({ type: 'loop-start', label: mLoop[1].trim() }) + continue + } + if (/^end$/i.test(line)) { + steps.push({ type: 'loop-end' }) + continue + } + const mMsg = line.match(/^(\S+?)(--?>>)(\S+?)\s*:\s*(.+)$/) + if (mMsg) { + ensure(mMsg[1]) + ensure(mMsg[3]) + const e = extractNum(mMsg[4].trim()) + steps.push({ + type: 'message', + from: mMsg[1], + to: mMsg[3], + label: e.rest, + num: e.num, + dashed: mMsg[2] === '-->>', + }) + } + } + return { participants, steps } +} + +// --------------------------------------------------------------------------- +// Layout +// --------------------------------------------------------------------------- + +export interface LMsg { + x1: number + x2: number + y: number + label: string + num: string | null + labelX: number + labelY: number + dashed: boolean + si: number + isLast: boolean +} +export interface LNote { + text: string + num: string | null + x: number + y: number + boxX: number + boxY: number + boxW: number + boxH: number + lines: string[] + si: number +} +export interface LActor { + cx: number + boxX: number + boxY: number + boxW: number + boxH: number + label: string +} +export interface LBlock { + label: string + x: number + y: number + w: number + h: number +} +export interface LLifeline { + x: number + y1: number + y2: number +} +export interface Layout { + w: number + h: number + actors: LActor[] + lifelines: LLifeline[] + messages: LMsg[] + notes: LNote[] + blocks: LBlock[] + msgCount: number +} + +export function doLayout(p: ParsedDiagram): Layout { + const L = LAYOUT + const n = p.participants.length + const gap = n === 2 ? L.actorGap2 : L.actorGap + + const aw = p.participants.map((a) => estW(a.label, L.actorFontSize) + L.actorPadX * 2) + const cx: number[] = [] + let xc = L.padding + aw[0] / 2 + for (let i = 0; i < n; i++) { + if (i > 0) xc += Math.max(gap, (aw[i - 1] + aw[i]) / 2 + 60) + cx.push(xc) + } + + const idx = new Map() + for (let i = 0; i < n; i++) idx.set(p.participants[i].id, i) + + const bY = L.padding + const actors: LActor[] = p.participants.map((a, i) => ({ + cx: cx[i], + boxX: cx[i] - aw[i] / 2, + boxY: bY, + boxW: aw[i], + boxH: L.actorBoxH, + label: a.label, + })) + + let y = bY + L.actorBoxH + L.headerGap + const messages: LMsg[] = [] + const notes: LNote[] = [] + const blocks: LBlock[] = [] + const bStack: { label: string; x: number; y: number }[] = [] + const rightEdge = cx[n - 1] + aw[n - 1] / 2 + const leftEdge = cx[0] - aw[0] / 2 + const midX = (cx[0] + cx[n - 1]) / 2 + + // Count total messages to identify the last one + let totalMsgs = 0 + for (const s of p.steps) { + if (s.type === 'message') totalMsgs++ + } + let msgIdx = 0 + + for (let si = 0; si < p.steps.length; si++) { + const s = p.steps[si] + if (s.type === 'message') { + const fi = idx.get(s.from) ?? 0 + const ti = idx.get(s.to) ?? 0 + msgIdx++ + messages.push({ + x1: cx[fi], + x2: cx[ti], + y, + label: s.label, + num: s.num, + labelX: (cx[fi] + cx[ti]) / 2, + labelY: y - L.labelLineGap, + dashed: s.dashed, + si, + isLast: msgIdx === totalMsgs, + }) + y += L.rowHeight + } else if (s.type === 'note') { + const maxNW = (rightEdge - leftEdge) * 0.8 + const wrapped = wrapText(s.text, maxNW, L.noteFontSize) + const lineH = L.noteFontSize + 4 + const boxW = Math.max(...wrapped.map((t) => estW(t, L.noteFontSize))) + L.noteBoxPadX * 2 + const boxH = wrapped.length * lineH + L.noteBoxPadY * 2 + const boxX = midX - boxW / 2 + const boxY = y - boxH / 2 + notes.push({ + text: s.text, + num: s.num, + x: midX, + y, + boxX, + boxY, + boxW: boxW + (s.num ? L.badgeR * 2 + 6 : 0), + boxH, + lines: wrapped, + si, + }) + y += L.rowHeight + L.noteExtraMargin + } else if (s.type === 'loop-start') { + bStack.push({ + label: s.label, + x: leftEdge - L.blockPadX, + y: y - L.blockPadTop / 2, + }) + y += L.blockPadTop + } else if (s.type === 'loop-end') { + const blk = bStack.pop() + if (blk) { + const bw = rightEdge + L.blockPadX - blk.x + blocks.push({ + label: blk.label, + x: blk.x, + y: blk.y, + w: bw, + h: y - blk.y + L.blockPadBottom, + }) + y += L.blockPadBottom + } + } + } + + const llBot = y - L.rowHeight / 2 + const lifelines: LLifeline[] = cx.map((lx) => ({ + x: lx, + y1: bY + L.actorBoxH, + y2: llBot, + })) + const totalW = rightEdge + L.padding + const totalH = llBot + L.padding + + return { + w: totalW, + h: totalH, + actors, + lifelines, + messages, + notes, + blocks, + msgCount: totalMsgs, + } +} + +export function estW(text: string, fontSize: number): number { + return text.length * fontSize * 0.6 +} + +export function wrapText(text: string, maxW: number, fontSize: number): string[] { + const words = text.split(/\s+/) + const lines: string[] = [] + let cur = '' + for (const word of words) { + const test = cur ? `${cur} ${word}` : word + if (estW(test, fontSize) > maxW && cur) { + lines.push(cur) + cur = word + } else { + cur = test + } + } + if (cur) lines.push(cur) + return lines.length > 0 ? lines : [text] +} + +// --------------------------------------------------------------------------- +// SVG renderer +// --------------------------------------------------------------------------- + +export function render(lo: Layout, th: ThemeColors): string { + const L = LAYOUT + const o: string[] = [] + const sz = L.arrowSize + const br = L.badgeR + + o.push( + '', + ) + o.push(``) + + // Gradient for last (success) message line — use userSpaceOnUse to avoid + // zero-height bounding box issues on horizontal elements. + const lastMsg = lo.messages.find((m) => m.isLast) + if (lastMsg) { + o.push( + '', + ) + } + + // Lifelines + for (const ll of lo.lifelines) { + o.push( + '', + ) + } + + // Blocks + for (const b of lo.blocks) { + const tw = estW(b.label, L.blockLabelFontSize) + 20 + o.push( + '', + ) + o.push( + '', + ) + o.push( + '' + + esc(b.label) + + '', + ) + } + + // Actors + for (const a of lo.actors) { + o.push( + '', + ) + o.push( + '' + + esc(a.label) + + '', + ) + } + + // Messages + for (const m of lo.messages) { + const da = m.dashed ? ' stroke-dasharray="6 4"' : '' + const goingRight = m.x2 > m.x1 + const lineEndX = goingRight ? m.x2 - sz : m.x2 + sz + const lineStroke = m.isLast ? 'url(#grad-success)' : th.line + // Solid arrows (->>): filled triangle; dashed arrows (-->>): outline triangle + const arrowFill = m.isLast + ? m.dashed + ? th.actorFill + : th.successArrow + : m.dashed + ? th.actorFill + : th.line + const arrowStroke = m.isLast ? th.successArrow : th.line + + // Line + o.push( + '', + ) + + // Arrow + const tipX = m.x2 + const baseX = goingRight ? tipX - sz : tipX + sz + o.push( + '', + ) + + // Compute label text width to place badge to its left + const labelW = estW(m.label, L.labelFontSize) + const totalLabelW = labelW + (m.num ? br * 2 + 6 : 0) + const groupLeft = m.labelX - totalLabelW / 2 + + // Badge (subtle bg color, not blue) + if (m.num) { + const bcx = groupLeft + br + const bcy = m.labelY + o.push( + '', + ) + o.push( + '' + + m.num + + '', + ) + } + + // Label text (to the right of badge) + const textX = m.num ? groupLeft + br * 2 + 6 + labelW / 2 : m.labelX + o.push( + '' + + highlightLabel(m.label, th) + + '', + ) + } + + // Notes — rounded box with wrapped text, no italic + for (const nt of lo.notes) { + const lineH = L.noteFontSize + 4 + // Recenter box now that boxW includes badge space + const centeredBoxX = nt.x - nt.boxW / 2 + const textStartY = nt.boxY + L.noteBoxPadY + L.noteFontSize + + o.push( + '', + ) + + if (nt.num) { + const bx = centeredBoxX + L.noteBoxPadX + br + const by = nt.boxY + nt.boxH / 2 + o.push( + '', + ) + o.push( + '' + + nt.num + + '', + ) + } + + const textX = nt.num ? centeredBoxX + L.noteBoxPadX + br * 2 + 6 : centeredBoxX + L.noteBoxPadX + for (let li = 0; li < nt.lines.length; li++) { + o.push( + '' + + esc(nt.lines[li]) + + '', + ) + } + } + + o.push('') + return o.join('\n') +} + +// Syntax highlight HTTP codes and methods in labels +export function highlightLabel(label: string, th: ThemeColors): string { + // Tokenize: split label into segments with optional color overrides + const re = /(GET|POST|PUT|DELETE|PATCH|\b[45]\d{2}\b|\b2\d{2}\s*OK\b|\b2\d{2}\b)/g + let lastIdx = 0 + let result = '' + let match: RegExpExecArray | null = re.exec(label) + while (match !== null) { + // Text before match + if (match.index > lastIdx) { + result += esc(label.slice(lastIdx, match.index)) + } + const tok = match[0] + let color = th.textMuted + if (/^(GET|POST|PUT|DELETE|PATCH)$/.test(tok)) color = th.arrow + else if (/^[45]\d{2}$/.test(tok)) color = th.errorCode + else if (/^2\d{2}/.test(tok)) color = th.successArrow + result += `${esc(tok)}` + lastIdx = match.index + tok.length + match = re.exec(label) + } + // Remaining text + if (lastIdx < label.length) { + result += esc(label.slice(lastIdx)) + } + return result +} + +export function esc(s: string): string { + return s + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') +} + +// --------------------------------------------------------------------------- +// Animation +// --------------------------------------------------------------------------- + +export interface AnimationHandle { + skipToEnd: () => void +} + +export function showAllItems(svg: SVGSVGElement) { + svg.style.opacity = '1' + for (const el of svg.querySelectorAll( + '[data-step],[data-step-arrow],[data-step-label],[data-step-note]', + )) { + el.style.transition = 'none' + el.style.opacity = '1' + el.style.strokeDashoffset = '0' + } +} + +export function animate( + svg: SVGSVGElement, + onComplete: () => void, + onStart: () => void, +): AnimationHandle { + type Item = { + si: number + draw?: SVGElement + arrow?: SVGElement + fade: SVGElement[] + isNote: boolean + } + const map = new Map() + const get = (i: number, isNote = false) => { + if (!map.has(i)) map.set(i, { si: i, fade: [], isNote }) + return map.get(i)! + } + + svg.querySelectorAll('[data-step]').forEach((el) => { + get(+(el.dataset.step ?? 0)).draw = el + }) + svg.querySelectorAll('[data-step-arrow]').forEach((el) => { + get(+(el.dataset.stepArrow ?? 0)).arrow = el + }) + svg.querySelectorAll('[data-step-label]').forEach((el) => { + get(+(el.dataset.stepLabel ?? 0)).fade.push(el) + }) + svg.querySelectorAll('[data-step-note]').forEach((el) => { + const item = get(+(el.dataset.stepNote ?? 0), true) + item.isNote = true + item.fade.push(el) + }) + + const timeline = Array.from(map.values()).sort((a, b) => a.si - b.si) + let skipped = false + const timers: ReturnType[] = [] + + const handle: AnimationHandle = { + skipToEnd() { + if (skipped) return + skipped = true + for (const t of timers) clearTimeout(t) + showAllItems(svg) + onComplete() + }, + } + + if (!timeline.length) { + svg.style.opacity = '1' + onComplete() + return handle + } + + svg.style.opacity = '1' + for (const item of timeline) { + if (item.draw) { + const len = lineLen(item.draw) + item.draw.style.strokeDasharray = `${len}` + item.draw.style.strokeDashoffset = `${len}` + item.draw.style.opacity = '0' + } + if (item.arrow) item.arrow.style.opacity = '0' + for (const el of item.fade) el.style.opacity = '0' + } + + const obs = new IntersectionObserver( + ([e]) => { + if (!e.isIntersecting) return + obs.disconnect() + onStart() + let lastDelay = 0 + let cumDelay = 800 + for (let i = 0; i < timeline.length; i++) { + const item = timeline[i] + const delay = cumDelay + if (delay > lastDelay) lastDelay = delay + cumDelay += item.isNote ? 2000 : 1200 + + const drawEl = item.draw + const arrowEl = item.arrow + timers.push( + setTimeout(() => { + if (skipped) return + if (drawEl) { + drawEl.style.transition = 'opacity 0.3s ease, stroke-dashoffset 1.2s ease-out' + drawEl.style.opacity = '1' + drawEl.style.strokeDashoffset = '0' + } + for (const el of item.fade) { + el.style.transition = drawEl ? 'opacity 0.6s ease' : 'opacity 0.8s ease' + el.style.opacity = '1' + } + if (arrowEl) { + timers.push( + setTimeout(() => { + if (skipped) return + arrowEl.style.transition = 'opacity 0.3s ease' + arrowEl.style.opacity = '1' + }, 1000), + ) + } + }, delay), + ) + } + timers.push( + setTimeout(() => { + if (!skipped) onComplete() + }, lastDelay + 1800), + ) + }, + { threshold: 0.15 }, + ) + obs.observe(svg) + + return handle +} + +export function lineLen(el: SVGElement): number { + const x1 = +(el.getAttribute('x1') || 0) + const x2 = +(el.getAttribute('x2') || 0) + const y1 = +(el.getAttribute('y1') || 0) + const y2 = +(el.getAttribute('y2') || 0) + return Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2) +} + +// --------------------------------------------------------------------------- +// React component +// --------------------------------------------------------------------------- + +export function MermaidDiagram({ chart }: { chart: string }) { + const wrapperRef = useRef(null) + const svgRef = useRef(null) + const animRef = useRef(null) + const [isDark, setIsDark] = useState(false) + const [phase, setPhase] = useState<'idle' | 'playing' | 'done'>('idle') + + useEffect(() => { + const check = () => + setIsDark( + document.documentElement.style.colorScheme === 'dark' || + document.documentElement.classList.contains('dark'), + ) + check() + const obs = new MutationObserver(check) + obs.observe(document.documentElement, { + attributes: true, + attributeFilter: ['class', 'style'], + }) + return () => obs.disconnect() + }, []) + + const renderDiagram = useCallback(() => { + const el = svgRef.current + if (!el || !el.isConnected) return + setPhase('idle') + try { + const parsed = parse(chart) + const lo = doLayout(parsed) + const th = isDark ? THEMES.dark : THEMES.light + el.innerHTML = render(lo, th) + const svg = el.querySelector('svg') + if (!svg) return + svg.style.maxWidth = '100%' + svg.style.height = 'auto' + svg.style.display = 'block' + svg.style.margin = '0 auto' + animRef.current = animate( + svg, + () => setPhase('done'), + () => setPhase('playing'), + ) + } catch (err) { + console.error('MermaidDiagram:', err) + } + }, [chart, isDark]) + + useEffect(() => { + const el = svgRef.current + if (!el) return + let dead = false + const raf = requestAnimationFrame(() => { + if (!dead) renderDiagram() + }) + return () => { + dead = true + cancelAnimationFrame(raf) + el.innerHTML = '' + } + }, [renderDiagram]) + + const th = isDark ? THEMES.dark : THEMES.light + + const btnStyle: React.CSSProperties = { + position: 'absolute', + top: 12, + right: 12, + width: 28, + height: 28, + borderRadius: '50%', + border: `1px solid ${th.actorStroke}`, + background: th.actorFill, + color: th.textMuted, + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + cursor: 'pointer', + padding: 0, + opacity: 0.7, + transition: 'opacity 0.2s', + } + + return ( +
+
+ {phase === 'playing' && ( + + )} + {phase === 'done' && ( + + )} +
+ ) +} diff --git a/src/components/TerminalDemo.tsx b/src/components/TerminalDemo.tsx new file mode 100644 index 00000000..32e1a76a --- /dev/null +++ b/src/components/TerminalDemo.tsx @@ -0,0 +1,430 @@ +'use client' + +import { useEffect, useMemo, useRef, useState } from 'react' + +// --------------------------------------------------------------------------- +// Constants & helpers +// --------------------------------------------------------------------------- + +const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'] + +function randomHex(bytes: number) { + const arr = crypto.getRandomValues(new Uint8Array(bytes)) + return `0x${Array.from(arr, (b) => b.toString(16).padStart(2, '0')).join('')}` +} + +function randomAddress() { + return randomHex(20) +} + +function randomTxHash() { + return randomHex(32) +} + +// --------------------------------------------------------------------------- +// Tiny sub-components +// --------------------------------------------------------------------------- + +function Spinner() { + const [frame, setFrame] = useState(0) + useEffect(() => { + const timer = setInterval(() => setFrame((f) => (f + 1) % SPINNER_FRAMES.length), 80) + return () => clearInterval(timer) + }, []) + return {SPINNER_FRAMES[frame]} +} + +// biome-ignore format: contains unicode ✔︎ +function StepIcon({ spinning }: { spinning: boolean }) { + return ( + + {spinning ? ( + + ) : ( + ✔︎ + )} + + ); +} + +function BlankLine() { + return
+} + +function TruncatedHex({ hash }: { hash: string }) { + return ( + <> + + {hash.slice(0, 6)}…{hash.slice(-4)} + + {hash} + + ) +} + +// --------------------------------------------------------------------------- +// Photo output +// --------------------------------------------------------------------------- + +function PhotoOutput({ url }: { url: string }) { + const [loaded, setLoaded] = useState(false) + + return ( +
+
+ {!loaded && ( +
+ )} + Generated setLoaded(true)} + className="absolute inset-0 h-full w-full object-cover" + style={{ + transition: 'opacity 0.5s', + opacity: loaded ? 1 : 0, + }} + /> +
+
+ ) +} + +// --------------------------------------------------------------------------- +// Simulated charge flow +// --------------------------------------------------------------------------- + +function ChargeSteps({ + endpoint, + output, + address, + onDone, +}: { + endpoint: string + output: string + address: string + onDone: () => void +}) { + const txHash = useMemo(() => randomTxHash(), []) + const doneCalled = useRef(false) + + const steps = useMemo( + () => [ + { key: 'wallet', delay: 600 }, + { key: 'fund', delay: 1500 }, + { key: 'req402', delay: 500 }, + { key: 'pay', delay: 500 }, + { key: 'req200', delay: 500 }, + ], + [], + ) + + const [step, setStep] = useState(0) + const currentKey = steps[step]?.key ?? 'done' + + const pastStep = (key: string) => { + const idx = steps.findIndex((s) => s.key === key) + return idx !== -1 && step > idx + } + const atOrPast = (key: string) => { + const idx = steps.findIndex((s) => s.key === key) + return idx !== -1 && step >= idx + } + const atStep = (key: string) => currentKey === key + + useEffect(() => { + if (currentKey === 'done') { + if (!doneCalled.current) { + doneCalled.current = true + onDone() + } + return + } + const delay = steps[step].delay + const timer = setTimeout(() => setStep((s) => s + 1), delay) + return () => clearTimeout(timer) + }, [step, currentKey, steps, onDone]) + + return ( +
+ + {atOrPast('wallet') && ( +

+ Create a wallet{' '} + {' '} + + + +

+ )} + {atOrPast('fund') && ( +

+ Add test funds{' '} + {' '} + 100 USD +

+ )} + {/* biome-ignore format: contains unicode → */} + {atOrPast('req402') && ( +

+ Call {endpoint} + {pastStep('req402') && ( + <> + {' '} + → 402{' '} + (payment required) + + )} +

+ )} + {atOrPast('pay') && ( +

+ Fulfill payment + {pastStep('pay') && ( + <> + {' '} + {' '} + + {txHash.slice(0, 6)}…{txHash.slice(-4)} + + + )} +

+ )} + {/* biome-ignore format: contains unicode → */} + {atOrPast('req200') && ( +

+ Call {endpoint} + {pastStep('req200') && ( + <> + {' '} + → 200{' '} + (success) + + )} +

+ )} + {pastStep('req200') && ( + <> + + + + + )} +
+ ) +} + +// --------------------------------------------------------------------------- +// CSS triangle for the "Run demo" button +// --------------------------------------------------------------------------- + +function CssTriangle() { + return ( + + ) +} + +// --------------------------------------------------------------------------- +// Main exported component +// --------------------------------------------------------------------------- + +export function TerminalDemo({ className }: { className?: string }) { + const [started, setStarted] = useState(false) + const [done, setDone] = useState(false) + const [key, setKey] = useState(0) + const [address] = useState(() => randomAddress()) + const [photoSeed, setPhotoSeed] = useState(() => Math.random().toString(36).slice(2)) + const photoUrl = `https://picsum.photos/seed/${photoSeed}/400/400` + + const scrollRef = useRef(null) + const contentRef = useRef(null) + + // Auto-scroll when content grows + useEffect(() => { + const scrollEl = scrollRef.current + const contentEl = contentRef.current + if (!scrollEl || !contentEl) return + const observer = new ResizeObserver(() => { + scrollEl.scrollTo({ + top: scrollEl.scrollHeight - scrollEl.clientHeight, + behavior: 'smooth', + }) + }) + observer.observe(contentEl) + return () => observer.disconnect() + }, []) + + const restart = () => { + setStarted(false) + setDone(false) + setPhotoSeed(Math.random().toString(36).slice(2)) + setKey((k) => k + 1) + } + + return ( +
+
+ {/* Title bar */} +
+ + + + + +
+ + {/* Terminal body */} +
+
+
+ + {!started && ( +
+ + +

Press Enter or click to start

+
+ )} + + {started && ( + setDone(true)} + /> + )} + + {done && ( + + )} +
+
+
+
+ ) +} diff --git a/src/components/TokenList.tsx b/src/components/TokenList.tsx index 302090bf..0b1dd429 100644 --- a/src/components/TokenList.tsx +++ b/src/components/TokenList.tsx @@ -4,9 +4,9 @@ import { useQuery } from '@tanstack/react-query' export function TokenListDemo() { const tokenList = useQuery({ - queryKey: ['tokenList', 42431], + queryKey: ['tokenList', 4217], queryFn: async () => { - const response = await fetch('https://tokenlist.tempo.xyz/list/42431') + const response = await fetch('https://tokenlist.tempo.xyz/list/4217') const data = await response.json() if (!Object.hasOwn(data, 'tokens')) throw new Error('Invalid token list') return data.tokens as Array<{ @@ -29,7 +29,7 @@ export function TokenListDemo() { target="_blank" rel="noopener noreferrer" className="flex items-center gap-2 text-content" - href={`https://tokenlist.tempo.xyz/asset/42431/${token.address}`} + href={`https://tokenlist.tempo.xyz/asset/4217/${token.address}`} > {token.name} {token.name} diff --git a/src/pages/_api/api/index-supply.ts b/src/pages/_api/api/index-supply.ts index e8f7c401..b796567e 100644 --- a/src/pages/_api/api/index-supply.ts +++ b/src/pages/_api/api/index-supply.ts @@ -1,4 +1,4 @@ -import { tempoModerato } from 'viem/chains' +import { tempo } from 'viem/chains' type QueryRequest = { query: string @@ -51,7 +51,7 @@ export async function POST(request: Request): Promise { const signatures = body.signatures && body.signatures.length > 0 ? body.signatures : [''] - const chainId = tempoModerato.id + const chainId = tempo.id const chainCursor = `${chainId}-0` const response = await fetch(url.toString(), { diff --git a/src/pages/_root.css b/src/pages/_root.css index 0d130ff5..ceb612e6 100644 --- a/src/pages/_root.css +++ b/src/pages/_root.css @@ -238,3 +238,24 @@ .data-v-mermaid-container { min-height: 200px; } + +/* --------------------------------------------------------------------------- + * Terminal theme — scoped color variables for the embedded terminal demo. + * --------------------------------------------------------------------------- */ + +.terminal-theme { + --term-bg1: light-dark(oklch(0.94 0 0), oklch(0.14 0 0)); + --term-bg2: light-dark(oklch(1 0 0), oklch(0.15 0 0)); + --term-gray1: light-dark(oklch(0.955 0 0), oklch(0.18 0 0)); + --term-gray2: light-dark(oklch(0.933 0 0), oklch(0.2 0 0)); + --term-gray3: light-dark(oklch(0.933 0 0), oklch(0.22 0 0)); + --term-gray4: light-dark(oklch(0.933 0 0), oklch(0.27 0 0)); + --term-gray5: light-dark(oklch(0.549 0 0), oklch(0.45 0 0)); + --term-gray6: light-dark(oklch(0.45 0 0), oklch(0.58 0 0)); + --term-gray10: light-dark(oklch(0.21 0 0), oklch(0.9 0 0)); + --term-blue9: light-dark(hsl(211 100% 42%), hsl(210 100% 70%)); + --term-amber9: light-dark(hsl(30 100% 32%), hsl(39 95% 58%)); + --term-green9: light-dark(hsl(133 50% 32%), hsl(131 50% 60%)); + --term-orange9: light-dark(hsl(25 95% 45%), hsl(25 95% 63%)); + --term-pink9: light-dark(hsl(336 65% 45%), hsl(341 90% 72%)); +} diff --git a/src/pages/cli/index.mdx b/src/pages/cli/index.mdx new file mode 100644 index 00000000..13f8e0f1 --- /dev/null +++ b/src/pages/cli/index.mdx @@ -0,0 +1,73 @@ +--- +title: Tempo CLI +description: A single binary for using Tempo Wallet from the terminal, making paid HTTP requests, and running a Tempo node. +--- + +import { Cards, Card } from 'vocs' + +# Tempo CLI + +The `tempo` binary covers three core workflows: + +- **`tempo wallet`** — use Tempo Wallet from the terminal: balances, funding, access keys, and service discovery +- **`tempo request`** — make paid HTTP requests with automatic [MPP](https://mpp.dev/overview) payment negotiation +- **`tempo node`** / **`tempo download`** — run and sync a Tempo node + +## Install + +:::code-group +```bash [Terminal] +curl -fsSL https://tempo.xyz/install | bash +``` +::: + +To update later, run `tempoup`. + +## Teach your agent to use Tempo + +Paste this into your AI agent to set up Tempo Wallet and start making paid requests: + +:::code-group +```bash [Claude Code] +claude -p "Read https://wallet.tempo.xyz/SKILL.md and set up tempo" +``` +```bash [Amp] +amp -x "Read https://wallet.tempo.xyz/SKILL.md and set up tempo" +``` +```bash [Codex CLI] +codex exec "Read https://wallet.tempo.xyz/SKILL.md and set up tempo" +``` +::: + +All commands support `--help` for documentation and `--describe` for JSON command schemas. For scripts and agents, pass `-t` (`--toon-output`) to get compact, machine-readable output. + +## Commands + +Dive into each command: + + + + + + + diff --git a/src/pages/cli/node.mdx b/src/pages/cli/node.mdx new file mode 100644 index 00000000..52ea6327 --- /dev/null +++ b/src/pages/cli/node.mdx @@ -0,0 +1,94 @@ +--- +title: tempo node +description: Command reference for running a Tempo node. +--- + +import { Cards, Card } from 'vocs' + +# `tempo node` + +Run a Tempo node. For faster initial sync, first download a snapshot with [`tempo download`](/cli/download). For operational setup guides — system requirements, systemd configs, monitoring, validator onboarding — see [Run a Tempo Node](/guide/node). + +Flags grouped by function: + +### Network + +| Flag | Description | +| --- | --- | +| `--follow` | Run as a full node following a trusted RPC endpoint | +| `--chain ` | Target network (`mainnet`, `moderato`) | +| `--datadir ` | Data directory for chain state | + +### RPC server + +| Flag | Description | +| --- | --- | +| `--http` | Enable the JSON-RPC HTTP server | +| `--http.port ` | JSON-RPC port (default: `8545`) | +| `--http.addr ` | JSON-RPC bind address | +| `--http.api ` | Enabled API namespaces (e.g., `eth,net,web3,txpool,trace`) | + +### Consensus (validators) + +| Flag | Description | +| --- | --- | +| `--consensus.signing-key ` | Path to validator signing key | +| `--consensus.fee-recipient ` | Address to receive validator fees | +| `--consensus.datadir ` | Separate volume for consensus data | + +### Observability + +| Flag | Description | +| --- | --- | +| `--telemetry-url ` | Unified metrics and logs export endpoint | +| `--telemetry-metrics-interval ` | Metrics push interval (default: `10s`) | +| `--metrics ` | Enable Prometheus metrics on this port | + +## Examples + +Download a snapshot and start an RPC node: + +```bash +tempo node \ + --follow \ + --http --http.port 8545 \ + --http.api eth,net,web3,txpool,trace +``` + +Start a validator: + +```bash +tempo node --datadir /data/tempo \ + --chain mainnet \ + --consensus.signing-key /etc/tempo/key \ + --consensus.fee-recipient 0x... +``` + +## Learn more + + + + + + + diff --git a/src/pages/cli/request.mdx b/src/pages/cli/request.mdx new file mode 100644 index 00000000..3447de98 --- /dev/null +++ b/src/pages/cli/request.mdx @@ -0,0 +1,80 @@ +--- +title: tempo request +description: A curl-like HTTP client that handles MPP payment negotiation automatically. +--- + +import { Cards, Card } from 'vocs' + +# `tempo request` + +A curl-like HTTP client that handles [Machine Payments Protocol](https://mpp.dev/overview) negotiation transparently. When a server responds with [`402 Payment Required`](https://mpp.dev/protocol/http-402), `tempo request` reads the [challenge](https://mpp.dev/protocol/challenges), signs and submits the payment onchain, then retries with the [credential](https://mpp.dev/protocol/credentials) — all in one command. + +Requires [`tempo wallet login`](/cli/wallet) first. + +## Usage + +| Command | Description | +| --- | --- | +| `tempo request ` | Make an HTTP request with automatic payment | +| `tempo request --dry-run ` | Preview cost without executing payment | +| `tempo request -X POST --json '{...}'` | Send a JSON body | +| `tempo request -H 'Header: Value'` | Add a custom header | + +## Flags + +| Flag | Description | +| --- | --- | +| `-X ` | HTTP method (`GET`, `POST`, etc.) | +| `--json ` | Send a JSON body (implies `-X POST`) | +| `-H
` | Add a custom header | +| `--dry-run` | Preview the payment cost and validate the request without spending | +| `-t` / `--toon-output` | Compact machine-readable output for scripts and agents | + +## Examples + +Preview cost before paying: + +```bash +tempo request --dry-run -X POST \ + --json '{"prompt":"a sunset over the ocean"}' \ + https://fal.mpp.tempo.xyz/fal-ai/flux/dev +``` + +Execute a paid request: + +```bash +tempo request -X POST \ + --json '{"prompt":"a sunset over the ocean"}' \ + https://fal.mpp.tempo.xyz/fal-ai/flux/dev +``` + +Discover the right URL and request schema first with [`tempo wallet services`](/cli/wallet#service-discovery). + +## Learn more + + + + + + + diff --git a/src/pages/cli/wallet.mdx b/src/pages/cli/wallet.mdx new file mode 100644 index 00000000..d7e554be --- /dev/null +++ b/src/pages/cli/wallet.mdx @@ -0,0 +1,201 @@ +--- +title: tempo wallet +description: Use Tempo Wallet from the terminal — authenticate, check balances, manage access keys, and discover services. +--- + +import { Cards, Card } from 'vocs' + +# `tempo wallet` + +CLI interface for [Tempo Wallet](https://wallet.tempo.xyz), Tempo's web-based passkey wallet. Authenticate in your browser, then manage balances, access keys, funding, service discovery, and payment sessions from the terminal. + + + + + + + + +## Download + +```bash +curl -fsSL https://tempo.xyz/install | bash +``` + +To update later, run `tempoup`. Verify with `tempo --version`. + +## Authenticate + +```bash +tempo wallet login +``` + +Opens a browser flow to connect to your [Tempo Wallet](https://wallet.tempo.xyz). If you don't have one, the flow creates it. + +Once logged in, verify everything works: + +```bash +tempo wallet whoami +``` + +If `ready=true`, the wallet is ready for [`tempo request`](/cli/request). + +To disconnect: + +```bash +tempo wallet logout +``` + +## Use it + +### Check balances + +```bash +tempo wallet whoami +``` + +Shows your address, token balances, and key state. + +### Manage access keys + +```bash +tempo wallet keys +``` + +Each wallet can have multiple access keys with independent spending limits. Use these to constrain what an agent or script can spend. + +### Add funds + +```bash +tempo wallet fund +``` + +On testnet, this opens the faucet. On mainnet, it opens bridging and onramp options. + +To transfer tokens to another address: + +```bash +tempo wallet transfer +``` + +For more options, see [Getting Funds on Tempo](/guide/getting-funds). + +### Discover services + +```bash +tempo wallet services +tempo wallet services --search +tempo wallet services +``` + +The [Machine Payments Protocol](https://mpp.dev/overview) (MPP) lets any HTTP endpoint accept payments inline. The service directory indexes MPP-registered providers — each entry shows endpoint URLs, HTTP methods, pricing, and request schemas. Use it to find the right URL and payload for [`tempo request`](/cli/request). + +### Manage payment sessions + +When you use [pay-as-you-go](/guide/machine-payments/pay-as-you-go) services, MPP opens a [session](https://mpp.dev/payment-methods/tempo/session) — a payment channel where your wallet deposits funds into an escrow contract, then pays per request using signed [vouchers](https://mpp.dev/protocol/credentials) off-chain. This avoids an on-chain transaction for every request, giving sub-100ms latency and near-zero per-request fees. + +The CLI tracks session state locally: + +```bash +tempo wallet sessions list +tempo wallet sessions sync +tempo wallet sessions close --all +tempo wallet sessions close --orphaned +``` + +`sync` reconciles local records with onchain state. `close --orphaned` cleans up sessions whose counterparty is unreachable. Use `--dry-run` with any close command to preview before executing. + +## Command reference + +### Authentication + +| Command | Description | +| --- | --- | +| `tempo wallet login` | Connect or create a wallet via browser auth | +| `tempo wallet logout` | Disconnect and clear local credentials | +| `tempo wallet whoami` | Print readiness, address, balances, and key state | + +### Keys + +| Command | Description | +| --- | --- | +| `tempo wallet keys` | List keys and their spending limits | + +### Funds + +| Command | Description | +| --- | --- | +| `tempo wallet fund` | Fund wallet (faucet on testnet, bridge on mainnet) | +| `tempo wallet transfer ` | Transfer tokens to another address | + +### Services + +| Command | Description | +| --- | --- | +| `tempo wallet services` | List all registered services | +| `tempo wallet services --search ` | Filter services by keyword | +| `tempo wallet services ` | Show endpoints, methods, and request schemas | + +### Sessions + +| Command | Description | +| --- | --- | +| `tempo wallet sessions list` | List active payment sessions | +| `tempo wallet sessions sync` | Reconcile local sessions with onchain state | +| `tempo wallet sessions close --all` | Close all sessions | +| `tempo wallet sessions close --orphaned` | Close sessions whose counterparty is unreachable | + +### Advanced + +| Command | Description | +| --- | --- | +| `tempo wallet mpp-sign` | Sign an MPP payment challenge (used internally by `tempo request`) | + +## Learn more + + + + + + + diff --git a/src/pages/ecosystem/block-explorers.mdx b/src/pages/ecosystem/block-explorers.mdx new file mode 100644 index 00000000..643836b0 --- /dev/null +++ b/src/pages/ecosystem/block-explorers.mdx @@ -0,0 +1,18 @@ +--- +title: Block Explorers +description: View transactions, blocks, accounts, and token activity on the Tempo network with block explorers. +--- + +# Block Explorers + +View transactions, blocks, accounts, and token activity on Tempo. + +## Tempo Explorer + +Tempo's official Mainnet block explorer is available at [explore.tempo.xyz](https://explore.tempo.xyz). View transactions, blocks, accounts, and token activity on the Tempo network. Testnet block explorer is available at [explore.testnet.tempo.xyz](https://explore.testnet.tempo.xyz). For more connection information, see [Connect to the Network](/quickstart/connection-details). + +## Tenderly + +[Tenderly](https://tenderly.co) delivers full-stack observability, debugging, and simulation tools for Tempo smart contract development and monitoring. With Tenderly you get real-time error tracking, EVM-level tracing, and off-chain transaction simulation — enabling you to catch bugs, analyze reverts, and inspect gas usage before transactions go live. + +You can enable Tempo in the [Tenderly Dashboard](https://dashboard.tenderly.co/) to use its tracing, alerts, and debugging tools with no infrastructure to manage. diff --git a/src/pages/ecosystem/bridges.mdx b/src/pages/ecosystem/bridges.mdx new file mode 100644 index 00000000..497033ec --- /dev/null +++ b/src/pages/ecosystem/bridges.mdx @@ -0,0 +1,32 @@ +--- +title: Bridges +description: Move assets to and from Tempo with cross-chain bridges including Across, Bungee, Relay, and Squid. +--- + +# Bridges + +Move assets to and from Tempo with cross-chain bridges. + +## Across + +[Across](https://across.to) provides fast, capital-efficient bridging for moving assets to and from Tempo. Across uses an intent-based architecture with optimistic verification, enabling near-instant cross-chain transfers with competitive fees. + +Bridge assets to Tempo through the [Across app](https://app.across.to/) and explore the integration docs at [docs.across.to](https://docs.across.to/). + +## Bungee + +[Bungee](https://bungee.exchange) enables seamless swaps within and between blockchains. Bungee aggregates bridge and DEX liquidity to deliver fast, cost-efficient cross-chain transfers and swaps to and from Tempo — with a simple integration path via widget or API. + +Get started with the [Bungee docs](https://docs.bungee.exchange/) or try the [Bungee app](https://bungee.exchange). + +## Relay + +[Relay](https://relay.link) provides instant cross-chain bridging and transaction execution. Relay enables users and applications to move assets to Tempo from other chains with fast finality and low fees, powered by a network of relayers that fill orders on the destination chain. + +Bridge to Tempo through the [Relay app](https://relay.link) and explore the [Relay docs](https://docs.relay.link/). + +## Squid + +[Squid](https://www.squidrouter.com) enables cross-chain swaps and bridging in a single transaction. Squid's intent-based routing engine aggregates DEXs, bridges, and market makers to find the optimal path for moving assets to and from Tempo — with sub-second execution and zero fees on stablecoin swaps. Developers can integrate cross-chain functionality via a REST API, TypeScript SDK, or drop-in widget. + +Get started with the [Squid docs](https://docs.squidrouter.com/) or try the [bridge app](https://app.squidrouter.com/). diff --git a/src/pages/ecosystem/data-analytics.mdx b/src/pages/ecosystem/data-analytics.mdx new file mode 100644 index 00000000..b394eb8f --- /dev/null +++ b/src/pages/ecosystem/data-analytics.mdx @@ -0,0 +1,89 @@ +--- +title: Data & Analytics +description: Query blockchain data on Tempo with indexers, analytics platforms, oracles, and monitoring tools. +--- + +# Data & Analytics + +Query blockchain data with indexers, analytics platforms, and monitoring tools. + +## Allium + +[Allium](https://www.allium.so) is an enterprise blockchain data platform that delivers real-time, analytics-ready datasets through a unified schema across chains. Developers can fetch wallet, token, and price data in milliseconds without managing infrastructure, decoding raw data, or inferring transactions—making it easy to focus on building Tempo applications. + +Get access to Tempo data through the [Allium App](https://app.allium.so/join), explore the full API in the [Allium docs](https://docs.allium.so/), and browse real examples of production apps built on Allium [here](https://docs.allium.so/api/developer/overview). + +:::tip +Allium has a [ready-to-use recipe](https://github.com/Allium-Science/allium-recipes/tree/main/tempo) for querying Tempo data with SQL. +::: + +## Artemis + +[Artemis](https://www.artemisanalytics.com/products/terminal) provides a unified analytics terminal for monitoring onchain activity across stablecoins, assets, and networks. Developers use Artemis to analyze flows, liquidity, token performance, and ecosystem-level trends through a clean, queryable interface. + +Tempo is already supported within Artemis, with a dedicated analytics page for [Tempo Testnet](https://app.artemisanalytics.com/asset/tempo_moderato). + +Artemis also maintains a cross-chain stablecoin dashboard covering major USD-pegged assets across numerous networks. Stablecoins launched on Tempo will appear in the [Stablecoins dashboard](https://app.artemisanalytics.com/stablecoins). + +## Chainlink + +[Chainlink](https://chain.link) is the industry-standard oracle platform powering the majority of DeFi and bringing capital markets onchain. The Chainlink stack provides the data, interoperability, and security needed for tokenized assets, stablecoins, payments, lending, and other advanced onchain use cases. + +Chainlink supports Tempo through: + +- **Cross-Chain Interoperability Protocol (CCIP):** A secure interoperability layer for sending messages and value across chains, enabling cross-chain user flows and multi-chain architectures. +Explore CCIP in the [Chainlink CCIP docs](https://docs.chain.link/ccip). +- **Data Streams:** Chainlink Data Streams delivers low-latency market data offchain, which can be verified onchain. This pull-based design gives dApps on-demand access to high-frequency market data backed by decentralized, fault-tolerant, and transparent infrastructure—an improvement over traditional push-based oracles that update only at fixed intervals or price thresholds. +View the Chainlink Data Stream deployed on Tempo [here](https://explore.tempo.xyz/address/0x72790f9eB82db492a7DDb6d2af22A270Dcc3Db64?tab=contract). + +Developers can explore CCIP, Data Streams, and the full Chainlink platform through the [Chainlink Developer Docs](https://docs.chain.link/). + +## CoinGecko + +[CoinGecko](https://www.coingecko.com) provides comprehensive cryptocurrency market data, including prices, trading volume, market capitalization, and token metadata. Developers can use the CoinGecko API to access Tempo token data for building dashboards, portfolio trackers, and analytics tools. + +Get started with the [CoinGecko API](https://docs.coingecko.com/reference/introduction). + +## Goldsky + +[Goldsky](https://goldsky.com) makes it easy to access real-time Tempo data with minimal maintenance. Goldsky offers two core products for indexing and streaming onchain data: + +- **[Subgraphs](https://docs.goldsky.com/subgraphs/):** A fully backwards-compatible subgraph indexing solution that handles reorgs, RPC failures, and scaling automatically, with improved reliability and performance over traditional subgraph hosts. +- **[Mirror](https://docs.goldsky.com/mirror/):** A simple way to replicate subgraph or chain-level streams directly into your own databases or message queues, powering flexible front-end and back-end data pipelines. + +Start indexing Tempo [here](https://goldsky.com/chains/tempo). + +## Range + +[Range](https://www.range.org) powers the Stablecoin Explorer, which provides a unified view of major stablecoins across 100+ chains. Tempo is fully supported, allowing developers and users to trace stablecoin flows in a way traditional explorers cannot. + +Range stands out through: +- **Complete cross-chain visibility**, showing the entire lifecycle of a transfer in one place +- **Enriched context**, including bridge routes, verified entities, and risk signals +- **Built-in compliance checks** via global sanctions lists + +Explore Tempo activity in the [Stablecoin Explorer](https://explorer.money/transactions?dn=tempo-testnet&sc=INTRACHAIN&sn=tempo-testnet). + +## Redstone + +[Redstone](https://redstone.finance) delivers modular oracle infrastructure with a push and pull model for onchain price feeds. Redstone's architecture minimizes gas costs by delivering data on-demand, making it well-suited for DeFi applications, lending protocols, and stablecoin systems on Tempo. + +Explore the available data feeds and integration guides in the [Redstone docs](https://docs.redstone.finance/). + +## SonarX + +[SonarX](https://www.sonarx.com) delivers standardized, auditable on-chain data built for institutional confidence and enterprise integration. SonarX provides indexed Tempo data from genesis to tip through instant data shares on Snowflake, Databricks, and BigQuery, as well as real-time streaming and REST APIs — all backed by a robust data quality framework and SOC 2 compliance. + +Start a trial at [sonarx.com](https://www.sonarx.com/trial) and explore the [SonarX docs](https://docs.sonarx.com/). + +## SQD + +[SQD](https://sqd.ai) is a decentralized query engine and high-performance indexing toolkit for extracting and transforming on-chain data. With the Squid SDK, developers can build custom indexers for Tempo that are up to 100x faster than direct RPC indexing, with data served through the SQD Network's decentralized data layer. + +Get started with the [SQD docs](https://docs.sqd.ai/) and deploy indexers via [SQD Cloud](https://app.subsquid.io/). + +## Zerion + +[Zerion](https://zerion.io/api) provides an enterprise-grade wallet data API that delivers portfolio balances, transaction history, DeFi positions, PnL tracking, and real-time webhooks — including Tempo — through a single unified interface. Developers can add comprehensive blockchain data to their applications without running any indexing infrastructure. + +Get a free API key from the [Zerion dashboard](https://dashboard.zerion.io/) and explore the [API documentation](https://developers.zerion.io/reference/authentication). diff --git a/src/pages/ecosystem/index.mdx b/src/pages/ecosystem/index.mdx new file mode 100644 index 00000000..d7484671 --- /dev/null +++ b/src/pages/ecosystem/index.mdx @@ -0,0 +1,67 @@ +--- +title: Tempo Ecosystem Infrastructure +description: Explore Tempo's ecosystem partners providing bridges, wallets, node infrastructure, data analytics, security, and more for building on Tempo. +--- + +import { Cards, Card } from 'vocs' + +# Tempo Ecosystem Infrastructure + +Integrating with Tempo is easy by leveraging services provided by our infrastructure partners. These partners take advantage of Tempo transactions, TIP-20 tokens, and more. Visit their documentation for more information on how to get started. + + + + + + + + + + + + diff --git a/src/pages/ecosystem/node-infrastructure.mdx b/src/pages/ecosystem/node-infrastructure.mdx new file mode 100644 index 00000000..e39fd761 --- /dev/null +++ b/src/pages/ecosystem/node-infrastructure.mdx @@ -0,0 +1,50 @@ +--- +title: Node Infrastructure +description: Connect to Tempo with reliable RPC endpoints and managed node services from infrastructure partners. +--- + +# Node Infrastructure + +Connect to Tempo with reliable RPC endpoints and managed node services. + +## Alchemy + +With [Alchemy](https://alchemy.com), build the fastest and most reliable Tempo applications, powered by industry-leading latency, uptime, and elastic throughput. Alchemy's global RPC infrastructure supports everything from stablecoins to tokenization and large-scale consumer apps. + +Sign up through the [Alchemy dashboard](https://dashboard.alchemy.com) and visit the [Alchemy docs](https://www.alchemy.com/docs/node#tldr) to start building. + +## Blockdaemon + +[Blockdaemon](https://app.blockdaemon.com/) provides institutional-grade node and API infrastructure, along with staking and MPC wallet services. Their globally distributed platform supports enterprise-scale, production workloads with strong reliability and compliance guarantees. + +Sign up through the [Blockdaemon Developer Dashboard](https://app.blockdaemon.com/) and deploy a Tempo node by navigating to **Nodes & RPC → Deploy a Node**. + +## Chainstack + +[Chainstack](https://chainstack.com) provides managed blockchain infrastructure with high-performance, secure RPC nodes. The platform offers reliable Tempo endpoints with built-in monitoring and analytics. + +Create an account through the [Chainstack console](https://console.chainstack.com) to deploy Tempo nodes and access RPC endpoints. + +## Conduit + +[Conduit](https://conduit.xyz) provides high-performance RPC infrastructure for Tempo Testnet. Developers can create API keys and access Tempo Testnet endpoints through the [Conduit app](https://app.conduit.xyz). + +View the Tempo Testnet RPC endpoint in the [Conduit Hub](https://hub.conduit.xyz/tempo-testnet) and get started with the [Tempo RPC Quickstart](https://docs.conduit.xyz/rpc-nodes/getting-started/tempo-rpc-quickstart). + +## dRPC + +[dRPC](https://drpc.org) provides managed Tempo RPC endpoints through NodeCloud, with smart routing, analytics, key control, and front-end protection across 180+ networks. The platform runs on 40 providers in 8 geoclusters, with a free tier and flat-rate plans starting at $10. + +Get started by visiting the [Tempo chain page](https://drpc.org/chainlist/tempo-testnet-rpc), and learn more about NodeCloud on the [dRPC NodeCloud page](https://drpc.org/nodecloud-multichain-rpc-management). + +## Quicknode + +[Quicknode](https://quicknode.com) is the enterprise-grade development platform for building, scaling, and launching onchain applications with speed and reliability. Their globally optimized RPC network makes it easy to run high-performance Tempo workloads from day one. + +Get started on the [Tempo Chain Page](https://www.quicknode.com/chains/tempo) and follow the [QuickStart guide](https://www.quicknode.com/docs/tempo) to create your Tempo RPC endpoint. + +## Validation Cloud + +[Validation Cloud](https://www.validationcloud.io/tempo) provides institutional-grade, full-archive RPC nodes and validator infrastructure for Tempo. With SOC 2 Type II compliance, high performance, and low latency, Validation Cloud is purpose-built for powering real-world payments and stablecoin use cases at scale. + +Get started at [validationcloud.io/tempo](https://www.validationcloud.io/tempo). diff --git a/src/pages/ecosystem/orchestration.mdx b/src/pages/ecosystem/orchestration.mdx new file mode 100644 index 00000000..faa018c2 --- /dev/null +++ b/src/pages/ecosystem/orchestration.mdx @@ -0,0 +1,30 @@ +--- +title: Orchestration +description: Move money globally between local currencies and stablecoins. Issue, transfer, and manage stablecoins on Tempo. +--- + +# Orchestration + +Move money globally between local currencies and stablecoins. Issue, transfer, and manage stablecoins. + +## Brale + +[Brale](https://brale.xyz) provides infrastructure for issuing, transferring, and managing stablecoins across chains. Developers can create new stablecoins or work with existing issued assets using Brale's APIs to support on- and off-ramps, payouts, and cross-ecosystem stablecoin movement. + +Brale exposes two complementary APIs: + +- **[Stablecoin Movement & Account Management](https://docs.brale.xyz/#stablecoin-movement--account-management-apibralexyz):** + An authenticated API for orchestrating stablecoin workflows, including issuance, transfers across accounts or chains, custody management, and integration with financial institutions. + +- **[Stablecoin Market Data](https://docs.brale.xyz/#stablecoin-market-data-databralexyz):** + A public, read-only API that provides token metadata, stablecoin definitions, and price feeds. + +These APIs support common stablecoin workflows such as minting, redemption, swaps, payouts, and treasury operations, making Brale suitable for fintechs, exchanges, and payment platforms building on Tempo. + +Get started by creating an account [here](https://app.brale.xyz/buy/signup/). + +## Bridge + +[Bridge](https://www.bridge.xyz) (a Stripe Company) provides stablecoin orchestration infrastructure for moving money between fiat and crypto rails. Bridge supports Tempo with APIs for issuance, wallets, and cross-border stablecoin transfers — enabling fintechs and platforms to build payment flows that span traditional and onchain systems. + +Get started with [Bridge's Tempo Integration Guide](https://apidocs.bridge.xyz/get-started/guides/move-money/tempo-integration-guide#tempo-integration-guide). diff --git a/src/pages/ecosystem/security-compliance.mdx b/src/pages/ecosystem/security-compliance.mdx new file mode 100644 index 00000000..deaf13e4 --- /dev/null +++ b/src/pages/ecosystem/security-compliance.mdx @@ -0,0 +1,32 @@ +--- +title: Security & Compliance +description: Transaction scanning, threat detection, and compliance infrastructure for Tempo applications. +--- + +# Security & Compliance + +Transaction scanning, threat detection, and compliance infrastructure for Tempo applications. + +## Blockaid + +[Blockaid](https://blockaid.io) provides real-time security infrastructure for Web3 applications. Its transaction scanning and threat detection systems identify malicious activity before users sign transactions, improving safety across wallets and interfaces. + +Learn how Blockaid's transaction scanning improves security by visiting their [overview page](https://www.blockaid.io/transaction-security), and reach out to their team [here](https://www.blockaid.io/contact) to get started. + +## Chainalysis + +[Chainalysis](https://www.chainalysis.com) delivers industry-leading onchain intelligence, compliance, and security infrastructure. Through Hexagate, Chainalysis supports Tempo with real-time monitoring, anomaly detection, and threat insights to help developers and platforms better understand and manage onchain risk as the ecosystem grows. + +Discover how Hexagate supports Tempo [here](https://www.hexagate.com), or request a dedicated walkthrough from the Chainalysis team through their [demo form](https://www.hexagate.com/request-demo). + +## Elliptic + +[Elliptic](https://www.elliptic.co) provides blockchain analytics and compliance solutions for detecting and preventing financial crime. Elliptic supports Tempo with transaction screening, wallet risk scoring, and regulatory compliance tools — helping platforms meet AML obligations while operating on the Tempo network. + +Learn more about Elliptic's compliance solutions at [elliptic.co](https://www.elliptic.co) or explore their [developer docs](https://docs.elliptic.co/). + +## TRM Labs + +[TRM Labs](https://www.trmlabs.com) delivers blockchain intelligence and compliance infrastructure for detecting fraud, money laundering, and financial crime. TRM supports Tempo with transaction monitoring, wallet screening, and risk assessment tools that help platforms operate safely and meet regulatory requirements. + +Get started at [trmlabs.com](https://www.trmlabs.com) or explore their [documentation](https://docs.trmlabs.com/). diff --git a/src/pages/ecosystem/smart-contract-libraries.mdx b/src/pages/ecosystem/smart-contract-libraries.mdx new file mode 100644 index 00000000..b1055fb8 --- /dev/null +++ b/src/pages/ecosystem/smart-contract-libraries.mdx @@ -0,0 +1,26 @@ +--- +title: Smart Contract Libraries +description: Build with account abstraction and programmable smart contract wallets on Tempo. +--- + +# Smart Contract Libraries + +Build with account abstraction and programmable smart contract wallets. + +## Pimlico + +[Pimlico](https://www.pimlico.io) provides smart account infrastructure for Tempo, including ERC-4337 bundlers and paymasters. With Pimlico, developers can sponsor gas fees, accept ERC-20 tokens for gas, and relay smart account transactions — enabling seamless, gasless onchain experiences for end users. + +Get started on the [Pimlico dashboard](https://dashboard.pimlico.io/) and explore the [Pimlico docs](https://docs.pimlico.io/). + +## Safe *(coming soon)* + +[Safe](https://safe.global) provides a modular smart account framework used across leading Web3 applications and institutions. With Safe, developers can build Tempo applications that take advantage of multi-sig controls, programmable permissions, session keys, and automated transaction policies. + +Safe integration for Tempo is coming soon. Stay tuned for updates as support becomes available. + +## ZeroDev + +[ZeroDev](https://zerodev.app) provides a powerful smart account platform for Tempo, supporting both ERC-4337 and EIP-7702. Developers can onboard users with social logins, enable gas sponsorship, and automate transactions while taking advantage of ZeroDev's chain-abstracted workflows. Its modular wallet stack also allows teams to build customized features such as custom transaction policies and tailored approval logic. + +Create a project in the [ZeroDev dashboard](https://dashboard.zerodev.app) and follow the [SDK quickstart](https://docs.zerodev.app/sdk/getting-started/quickstart) to integrate smart accounts into your Tempo application. diff --git a/src/pages/ecosystem/wallets.mdx b/src/pages/ecosystem/wallets.mdx new file mode 100644 index 00000000..7802060a --- /dev/null +++ b/src/pages/ecosystem/wallets.mdx @@ -0,0 +1,79 @@ +--- +title: Wallets +description: Integrate embedded, custodial, and institutional wallet infrastructure into your Tempo application. +--- + +# Wallets + +Integrate user-friendly wallet experiences directly into your application. + +## Embedded + +### Blockradar +[Blockradar](https://blockradar.co) provides non-custodial wallet infrastructure purpose-built for fintechs running stablecoin payments. The platform focuses on real financial use cases, from merchant settlement to cross-border payouts, with tools designed for payments, compliance, treasury operations, and multi-chain liquidity. Explore the full platform in the [Blockradar Docs](https://docs.blockradar.co/). + +**Wallet and Payment Operations:** Through one unified API, teams can issue wallets for users, merchants, or treasury; accept fiat inflows through virtual accounts; enable gasless stablecoin transactions; apply AML checks automatically; consolidate balances through configurable sweeps; and handle cross-chain movement using swap and bridge. Fintechs can start building immediately from our API or [Blockradar Dashboard](https://dashboard.blockradar.co/). For advanced flows or high-volume programs, fintechs can [book a demo](https://www.blockradar.co/contact) to walk through production architectures. + +### Crossmint + +[Crossmint](https://www.crossmint.com) is an all-in-one platform, with unified APIs for [wallets](https://docs.crossmint.com/wallets/), [stablecoin orchestration](https://docs.crossmint.com/stablecoin-orchestration/), [checkout flows](https://docs.crossmint.com/payments), and [tokenization](https://docs.crossmint.com/minting), giving developers a single interface for everything from payments to asset management on Tempo. + +Crossmint delivers a gasless, seed-phrase-free UX backed by bank-grade security and compliance, along with no-code dashboards for managing programs across your team. + +Set up a project in the [Crossmint console](https://crossmint.com/console) and explore the [Solution Guide](https://docs.crossmint.com/solutions/overview#fintech) tailored for payment use-cases. + +### Dynamic + +[Dynamic](https://dynamic.xyz) combines authentication, smart wallets, and key management into a flexible SDK for Tempo developers. Teams can onboard users with familiar login methods and provision Tempo-compatible wallets through Dynamic's secure infrastructure. + +Enable Tempo testnet in the [Dynamic dashboard](https://app.dynamic.xyz/dashboard/chains-and-networks), and create an account [here](https://www.dynamic.xyz/get-started) to start integrating Dynamic into your app. + +### Para + +[Para](https://getpara.com) is a comprehensive wallet and authentication suite for fintech and crypto applications. It provides flexible login methods, secure MPC-backed wallets, fast authentication, and infrastructure for automating onchain activity. Para is adding Tempo chain support so developers can easily build Tempo-enabled wallets and payment flows. + +Get started by signing up through the [Para Dev Portal](https://developer.getpara.com/) and following the quickstart in the [Para docs](https://docs.getpara.com/v2/introduction/welcome). + +### Privy + +[Privy](https://www.privy.io/) builds secure key management and embedded wallets so any developer can easily build secure, scalable wallets into their app. Easily spin up self-custodial wallets for users, manage your treasury wallets and more. + +Privy takes advantage of Tempo-native experiences to enable better stablecoin and payments experiences. Easily enable gas sponsorship, leverage webhooks for onchain events, delegated signatures, simple wallet funding, etc. + +You can get started now. Simply [create](https://docs.privy.io/wallets/wallets/create/create-a-wallet#param-chain-type-1) an ethereum wallet with Privy and pass in `"caip2": "eip155:42431"` when [making transactions](https://docs.privy.io/wallets/using-wallets/ethereum/send-a-transaction#usage-9). + +:::tip +Check out Privy's [example](https://github.com/privy-io/examples/tree/main/examples/privy-next-tempo) peer-to-peer payments app that uses Tempo transaction memos. +::: + +### Turnkey + +[Turnkey](https://www.turnkey.com) provides programmable key management and non-custodial wallet infrastructure for applications that need granular signing policies and automated transaction flows. With Turnkey, developers can securely sign Tempo transactions, automate wallet operations, and build custom logic around how keys are used. + +Turnkey also supports sponsor-style workflows, enabling gasless or subsidized transaction flows through configurable signing policies. + +[Create your Turnkey account](https://app.turnkey.com/dashboard) and follow the [Turnkey Embedded Wallet Kit guide](https://docs.turnkey.com/sdks/react/getting-started) to integrate embedded wallets into your Tempo app. + +:::tip +Turnkey has a [`with-tempo`](https://github.com/tkhq/sdk/tree/main/examples/with-tempo) example in their SDK to get you started quickly. +::: + +## Custodial & Institutional + +### BitGo + +[BitGo](https://www.bitgo.com) provides institutional-grade custody, trading, and wallet infrastructure. BitGo supports Tempo with both custodial and self-custody wallet solutions, enabling enterprises to securely store, manage, and transact with Tempo-based assets under robust security and compliance controls. BitGo is a qualified custodian in the United States and globally [licensed and regulated](https://www.bitgo.com/company/licenses/). + +Get started through the [BitGo platform](https://www.bitgo.com) or explore their [developer docs](https://developers.bitgo.com/). + +### Fireblocks + +[Fireblocks](https://www.fireblocks.com) provides enterprise-grade digital asset infrastructure for custody, transfers, and tokenization. Tempo is supported through Fireblocks' MPC-based signing, policy engine, and transaction API — enabling institutions to securely manage Tempo assets with configurable approval workflows and direct network connectivity. + +Access Tempo through the [Fireblocks console](https://console.fireblocks.io/) and explore the [Fireblocks Developer docs](https://developers.fireblocks.com/). + +### Utila + +[Utila](https://utila.io) provides secure MPC wallet infrastructure and asset-management tooling for teams building with stablecoins and digital assets. Developers can use Utila to manage Tempo-based payments and treasury operations across multiple wallets and blockchains, all within a single policy-driven platform. Utila's MPC technology reduces counterparty risk, while its configurable approval engine gives teams granular control over how funds are moved. + +[Learn more](https://utila.io/product/payments/) about how Utila supports stablecoin operations on Tempo, and [request a demo](https://utila.io/request-a-demo/) if you're interested in secure MPC infrastructure. diff --git a/src/pages/guide/_template.mdx b/src/pages/guide/_template.mdx index 27cb4f21..516907a4 100644 --- a/src/pages/guide/_template.mdx +++ b/src/pages/guide/_template.mdx @@ -42,16 +42,16 @@ export function Component() { ```tsx twoslash [config.ts] filename="config.ts" // @noErrors import { createConfig, http } from 'wagmi' -import { tempoModerato } from 'viem/chains' +import { tempo } from 'viem/chains' import { KeyManager, webAuthn } from 'wagmi/tempo' export const config = createConfig({ - chains: [tempoModerato], + chains: [tempo], connectors: [webAuthn({ keyManager: KeyManager.localStorage(), })], transports: { - [tempoModerato.id]: http(), + [tempo.id]: http(), }, }) ``` @@ -74,16 +74,16 @@ export function Component() { ```tsx twoslash [config.ts] filename="config.ts" // @noErrors import { createConfig, http } from 'wagmi' -import { tempoModerato } from 'viem/chains' +import { tempo } from 'viem/chains' import { KeyManager, webAuthn } from 'wagmi/tempo' export const config = createConfig({ - chains: [tempoModerato], + chains: [tempo], connectors: [webAuthn({ keyManager: KeyManager.localStorage(), })], transports: { - [tempoModerato.id]: http(), + [tempo.id]: http(), }, }) ``` diff --git a/src/pages/guide/building-with-ai.mdx b/src/pages/guide/building-with-ai.mdx deleted file mode 100644 index ab6c87e5..00000000 --- a/src/pages/guide/building-with-ai.mdx +++ /dev/null @@ -1,146 +0,0 @@ ---- -title: Building with AI -description: Tempo documentation is built with AI-first principles, providing llms.txt files, Markdown rendering, and MCP server access for AI assistants. ---- - -# Building with AI - -Tempo documentation is built with AI-first principles, providing multiple features to make documentation accessible to LLMs and AI assistants. - -## llms.txt - -The docs automatically generate [`llms.txt`](https://llmstxt.org/) files for LLM consumption: - -- `/llms.txt` – A concise index of all pages with titles and descriptions -- `/llms-full.txt` – Complete documentation content in a single file - -These files are generated at build time and served at the root of the site. - -## Markdown Rendering for AI Agents - -AI user agents are automatically detected and served raw Markdown instead of rendered HTML. This provides better token efficiency and easier parsing for LLMs. - -### Supported AI Agents - -Popular user agents that are automatically detected: - -- **OpenAI**: `GPTBot`, `ChatGPT-User` -- **Anthropic**: `ClaudeBot`, `claude-web` -- **Google**: `Googlebot` - -See the [full list of supported agents](https://github.com/wevm/vocs/blob/next/src/waku/internal/middleware/md-router.ts#L3) for the most up-to-date list. - -### Manual Markdown Access - -Any page can be accessed as Markdown by appending `.md` to the URL: - -``` -https://docs.tempo.xyz/quickstart/integrate-tempo.md -``` - -This is useful for copying documentation into AI conversations or for custom integrations. - -## Ask AI Menu - -The docs include a built-in "Ask AI" menu (⌘I / Ctrl+I) that provides: - -- **Open in ChatGPT/Claude**: Opens the current page context in popular AI assistants -- **Copy page for AI**: Copies the page content as Markdown to your clipboard -- **View as Markdown**: Opens the raw Markdown version of the current page -- **Copy MCP URL**: Copies the MCP server URL for AI assistant configuration - -## MCP Server - -The docs include a built-in [Model Context Protocol (MCP)](https://modelcontextprotocol.io) server that allows AI assistants to navigate documentation and source code programmatically. - -:::code-group -```bash [Claude Code] -claude mcp add --transport http tempo https://docs.tempo.xyz/api/mcp -``` -```bash [Codex CLI] -codex mcp add vercel --url https://docs.tempo.xyz/api/mcp -``` -```bash [Amp] -amp mcp add tempo https://docs.tempo.xyz/api/mcp -``` -::: - -### Connecting AI Assistants - -Configure your AI assistant to connect to the MCP server: - -```json -{ - "mcpServers": { - "tempo-docs": { - "url": "https://docs.tempo.xyz/api/mcp" - } - } -} -``` - -### Available Tools - -The MCP server exposes these tools to AI assistants: - -| Tool | Description | -| --- | --- | -| `list_pages` | List all documentation pages with their paths | -| `read_page` | Read the content of a specific documentation page | -| `search_docs` | Search documentation for a query string | - -### Source Code Navigation - -The MCP server also provides access to source code repositories: - -| Tool | Description | -| --- | --- | -| `list_sources` | List available source code repositories | -| `list_source_files` | List files in a directory | -| `read_source_file` | Read a source code file | -| `get_file_tree` | Get a recursive file tree | -| `search_source` | Search source code for a pattern | - -Available source repositories: - -- [`tempoxyz/tempo`](https://github.com/tempoxyz/tempo) – Tempo node implementation -- [`tempoxyz/tempo-ts`](https://github.com/tempoxyz/tempo-ts) – TypeScript SDK -- [`paradigmxyz/reth`](https://github.com/paradigmxyz/reth) – Reth Ethereum client -- [`foundry-rs/foundry`](https://github.com/foundry-rs/foundry) – Foundry development toolkit -- [`wevm/viem`](https://github.com/wevm/viem) – TypeScript interface for Ethereum -- [`wevm/wagmi`](https://github.com/wevm/wagmi) – React hooks for Ethereum - -## Agent Skills - -Install the [Tempo Agent Skills](https://github.com/tempoxyz/agent-skills) to give AI coding agents (Amp, Claude Code, etc.) access to Tempo documentation, source code via MCP, and examples. Check out [`tempoxyz/agent-skills`](https://github.com/tempoxyz/agent-skills) for more info on available skills. - -### Installation - -```bash -npx skills add tempoxyz/agent-skills -``` - -Or manually: - -```bash -$ git clone https://github.com/tempoxyz/agent-skills.git -$ cp -r agent-skills/skills/tempo ~/.config/agents/skills/ -``` - -Or add to your project's [`.agents/skills/`](https://github.com/tempoxyz/agent-skills/tree/main/skills) directory for project-specific access. - -### Usage - -Once installed, the skill is automatically available. The agent will use it when relevant tasks are detected. - -**Examples:** - -``` -How do I create a TIP-20 stablecoin? -``` -``` -Show me how fee sponsorship works in Viem -``` -``` -Search the Tempo source for transaction validation -``` diff --git a/src/pages/guide/getting-funds.mdx b/src/pages/guide/getting-funds.mdx new file mode 100644 index 00000000..337f5aa4 --- /dev/null +++ b/src/pages/guide/getting-funds.mdx @@ -0,0 +1,72 @@ +--- +title: Getting Funds on Tempo +description: Bridge assets to Tempo, add funds in Tempo Wallet, or use the faucet on testnet. +--- + +import { Cards, Card } from 'vocs' + +# Getting Funds on Tempo + +You can get funds onto Tempo in three ways: + + + + + + + +## Bridge + +Use one of these supported bridges to move assets to Tempo: + +- **[LayerZero](https://layerzero.network/)**: Bridge supported assets from other chains to Tempo. +- **[Squid](https://app.squidrouter.com/)**: Swap and bridge assets to Tempo in one flow. +- **[Relay](https://relay.link/)**: Bridge assets to Tempo with low fees. + +## Tempo Wallet + +[Tempo Wallet](https://wallet.tempo.xyz) is Tempo's web-based passkey wallet. You can add funds directly in the app. + +### In Tempo Wallet + +1. Sign up or log in to [Tempo Wallet](https://wallet.tempo.xyz) with your passkey. +2. Click **Add funds** on the home screen, then choose an option: + + + +### With the CLI + +Fund your wallet from the terminal using the [Tempo CLI](/cli): + +```bash +tempo wallet fund +``` + +### With an agent + +Paste this into your agent to set up Tempo Wallet and add funds: + +``` +Read https://wallet.tempo.xyz/SKILL.md and fund my Tempo Wallet +``` + +## Testnet Funds + +For development and testing, use the [Tempo Faucet](/quickstart/faucet). + +The faucet provides `pathUSD`, `alphaUSD`, `betaUSD`, and `thetaUSD` test stablecoins. diff --git a/src/pages/guide/issuance/create-a-stablecoin.mdx b/src/pages/guide/issuance/create-a-stablecoin.mdx index 4599ca55..cfe25a5c 100644 --- a/src/pages/guide/issuance/create-a-stablecoin.mdx +++ b/src/pages/guide/issuance/create-a-stablecoin.mdx @@ -68,11 +68,11 @@ export function AddFunds() { ```tsx twoslash [config.ts] filename="config.ts" // @noErrors import { createConfig, http } from 'wagmi' -import { tempoModerato } from 'viem/chains' +import { tempo } from 'viem/chains' import { KeyManager, webAuthn } from 'wagmi/tempo' export const config = createConfig({ - chains: [tempoModerato], + chains: [tempo], connectors: [webAuthn({ keyManager: KeyManager.localStorage(), })], @@ -129,11 +129,11 @@ export function CreateStablecoin() { ```tsx twoslash [config.ts] filename="config.ts" // @noErrors import { createConfig, http } from 'wagmi' -import { tempoModerato } from 'viem/chains' +import { tempo } from 'viem/chains' import { KeyManager, webAuthn } from 'wagmi/tempo' export const config = createConfig({ - chains: [tempoModerato], + chains: [tempo], connectors: [webAuthn({ keyManager: KeyManager.localStorage(), })], @@ -197,11 +197,11 @@ export function CreateStablecoin() { ```tsx twoslash [config.ts] filename="config.ts" // @noErrors import { createConfig, http } from 'wagmi' -import { tempoModerato } from 'viem/chains' +import { tempo } from 'viem/chains' import { KeyManager, webAuthn } from 'wagmi/tempo' export const config = createConfig({ - chains: [tempoModerato], + chains: [tempo], connectors: [webAuthn({ keyManager: KeyManager.localStorage(), })], @@ -265,11 +265,11 @@ export function CreateStablecoin() { ```tsx twoslash [config.ts] filename="config.ts" // @noErrors import { createConfig, http } from 'wagmi' -import { tempoModerato } from 'viem/chains' +import { tempo } from 'viem/chains' import { KeyManager, webAuthn } from 'wagmi/tempo' export const config = createConfig({ - chains: [tempoModerato], + chains: [tempo], connectors: [webAuthn({ keyManager: KeyManager.localStorage(), })], diff --git a/src/pages/guide/issuance/distribute-rewards.mdx b/src/pages/guide/issuance/distribute-rewards.mdx index 0e5bab24..dd9c43af 100644 --- a/src/pages/guide/issuance/distribute-rewards.mdx +++ b/src/pages/guide/issuance/distribute-rewards.mdx @@ -76,11 +76,11 @@ export function OptInToRewards() { ```tsx twoslash [config.ts] filename="config.ts" // @noErrors import { createConfig, http } from 'wagmi' -import { tempoModerato } from 'viem/chains' +import { tempo } from 'viem/chains' import { KeyManager, webAuthn } from 'wagmi/tempo' export const config = createConfig({ - chains: [tempoModerato], + chains: [tempo], connectors: [webAuthn({ keyManager: KeyManager.localStorage(), })], @@ -136,11 +136,11 @@ export function StartReward() { ```tsx twoslash [config.ts] filename="config.ts" // @noErrors import { createConfig, http } from 'wagmi' -import { tempoModerato } from 'viem/chains' +import { tempo } from 'viem/chains' import { KeyManager, webAuthn } from 'wagmi/tempo' export const config = createConfig({ - chains: [tempoModerato], + chains: [tempo], connectors: [webAuthn({ keyManager: KeyManager.localStorage(), })], @@ -189,11 +189,11 @@ export function ClaimReward() { ```tsx twoslash [config.ts] filename="config.ts" // @noErrors import { createConfig, http } from 'wagmi' -import { tempoModerato } from 'viem/chains' +import { tempo } from 'viem/chains' import { KeyManager, webAuthn } from 'wagmi/tempo' export const config = createConfig({ - chains: [tempoModerato], + chains: [tempo], connectors: [webAuthn({ keyManager: KeyManager.localStorage(), })], diff --git a/src/pages/guide/issuance/manage-stablecoin.mdx b/src/pages/guide/issuance/manage-stablecoin.mdx index fdb4560b..4cd91cb0 100644 --- a/src/pages/guide/issuance/manage-stablecoin.mdx +++ b/src/pages/guide/issuance/manage-stablecoin.mdx @@ -73,11 +73,11 @@ export function GrantRoles() { ```tsx twoslash [config.ts] filename="config.ts" // @noErrors import { createConfig, http } from 'wagmi' -import { tempoModerato } from 'viem/chains' +import { tempo } from 'viem/chains' import { KeyManager, webAuthn } from 'wagmi/tempo' export const config = createConfig({ - chains: [tempoModerato], + chains: [tempo], connectors: [webAuthn({ keyManager: KeyManager.localStorage(), })], @@ -152,11 +152,11 @@ export function GrantRoles() { ```tsx twoslash [config.ts] filename="config.ts" // @noErrors import { createConfig, http } from 'wagmi' -import { tempoModerato } from 'viem/chains' +import { tempo } from 'viem/chains' import { KeyManager, webAuthn } from 'wagmi/tempo' export const config = createConfig({ - chains: [tempoModerato], + chains: [tempo], connectors: [webAuthn({ keyManager: KeyManager.localStorage(), })], @@ -265,11 +265,11 @@ export function RevokeRoles() { ```tsx twoslash [config.ts] filename="config.ts" // @noErrors import { createConfig, http } from 'wagmi' -import { tempoModerato } from 'viem/chains' +import { tempo } from 'viem/chains' import { KeyManager, webAuthn } from 'wagmi/tempo' export const config = createConfig({ - chains: [tempoModerato], + chains: [tempo], connectors: [webAuthn({ keyManager: KeyManager.localStorage(), })], @@ -336,11 +336,11 @@ export function SetSupplyCap() { ```tsx twoslash [config.ts] filename="config.ts" // @noErrors import { createConfig, http } from 'wagmi' -import { tempoModerato } from 'viem/chains' +import { tempo } from 'viem/chains' import { KeyManager, webAuthn } from 'wagmi/tempo' export const config = createConfig({ - chains: [tempoModerato], + chains: [tempo], connectors: [webAuthn({ keyManager: KeyManager.localStorage(), })], @@ -445,11 +445,11 @@ export function LinkTokenPolicy() { ```tsx twoslash [config.ts] filename="config.ts" // @noErrors import { createConfig, http } from 'wagmi' -import { tempoModerato } from 'viem/chains' +import { tempo } from 'viem/chains' import { KeyManager, webAuthn } from 'wagmi/tempo' export const config = createConfig({ - chains: [tempoModerato], + chains: [tempo], connectors: [webAuthn({ keyManager: KeyManager.localStorage(), })], @@ -525,11 +525,11 @@ export function PauseUnpauseTransfers() { ```tsx twoslash [config.ts] filename="config.ts" // @noErrors import { createConfig, http } from 'wagmi' -import { tempoModerato } from 'viem/chains' +import { tempo } from 'viem/chains' import { KeyManager, webAuthn } from 'wagmi/tempo' export const config = createConfig({ - chains: [tempoModerato], + chains: [tempo], connectors: [webAuthn({ keyManager: KeyManager.localStorage(), })], @@ -599,11 +599,11 @@ export function BurnBlocked() { ```tsx twoslash [config.ts] filename="config.ts" // @noErrors import { createConfig, http } from 'wagmi' -import { tempoModerato } from 'viem/chains' +import { tempo } from 'viem/chains' import { KeyManager, webAuthn } from 'wagmi/tempo' export const config = createConfig({ - chains: [tempoModerato], + chains: [tempo], connectors: [webAuthn({ keyManager: KeyManager.localStorage(), })], diff --git a/src/pages/guide/issuance/mint-stablecoins.mdx b/src/pages/guide/issuance/mint-stablecoins.mdx index 70621a4b..b4b8caca 100644 --- a/src/pages/guide/issuance/mint-stablecoins.mdx +++ b/src/pages/guide/issuance/mint-stablecoins.mdx @@ -76,11 +76,11 @@ export function GrantIssuerRole() { ```tsx twoslash [config.ts] filename="config.ts" // @noErrors import { createConfig, http } from 'wagmi' -import { tempoModerato } from 'viem/chains' +import { tempo } from 'viem/chains' import { KeyManager, webAuthn } from 'wagmi/tempo' export const config = createConfig({ - chains: [tempoModerato], + chains: [tempo], connectors: [webAuthn({ keyManager: KeyManager.localStorage(), })], @@ -179,11 +179,11 @@ export function MintToken() { ```tsx twoslash [config.ts] filename="config.ts" // @noErrors import { createConfig, http } from 'wagmi' -import { tempoModerato } from 'viem/chains' +import { tempo } from 'viem/chains' import { KeyManager, webAuthn } from 'wagmi/tempo' export const config = createConfig({ - chains: [tempoModerato], + chains: [tempo], connectors: [webAuthn({ keyManager: KeyManager.localStorage(), })], @@ -338,11 +338,11 @@ export function BurnToken() { ```tsx twoslash [config.ts] filename="config.ts" // @noErrors import { createConfig, http } from 'wagmi' -import { tempoModerato } from 'viem/chains' +import { tempo } from 'viem/chains' import { KeyManager, webAuthn } from 'wagmi/tempo' export const config = createConfig({ - chains: [tempoModerato], + chains: [tempo], connectors: [webAuthn({ keyManager: KeyManager.localStorage(), })], diff --git a/src/pages/guide/issuance/use-for-fees.mdx b/src/pages/guide/issuance/use-for-fees.mdx index 0c5cdc36..4ad675c4 100644 --- a/src/pages/guide/issuance/use-for-fees.mdx +++ b/src/pages/guide/issuance/use-for-fees.mdx @@ -200,11 +200,11 @@ export function PayWithIssuedToken() { ```tsx twoslash [config.ts] filename="config.ts" // @noErrors import { createConfig, http } from 'wagmi' -import { tempoModerato } from 'viem/chains' +import { tempo } from 'viem/chains' import { KeyManager, webAuthn } from 'wagmi/tempo' export const config = createConfig({ - chains: [tempoModerato], + chains: [tempo], connectors: [webAuthn({ keyManager: KeyManager.localStorage(), })], diff --git a/src/pages/guide/machine-payments/client.mdx b/src/pages/guide/machine-payments/client.mdx new file mode 100644 index 00000000..4ae816d8 --- /dev/null +++ b/src/pages/guide/machine-payments/client.mdx @@ -0,0 +1,202 @@ +--- +title: Client Quickstart +description: Set up an MPP client on Tempo. Polyfill fetch to automatically pay for 402 responses with TIP-20 stablecoins. +--- + +import { Card, Cards } from 'vocs' + +# Client quickstart + +Polyfill `fetch` to handle `402` responses. Your existing code works unchanged — payments happen in the background. + +::::steps + +### Install dependencies + +:::code-group +```bash [npm] +npm install mppx viem +``` +```bash [pnpm] +pnpm add mppx viem +``` +```bash [bun] +bun add mppx viem +``` +::: + +### Define an account + +```ts +import { privateKeyToAccount } from 'viem/accounts' + +const account = privateKeyToAccount('0xabc…123') +``` + +:::tip +With Tempo, you can also use [Passkey or WebCrypto accounts](https://viem.sh/tempo/accounts). +::: + +### Create payment handler + +Call `Mppx.create` at startup. This polyfills `fetch` to automatically handle `402` payment challenges. + +```ts +import { privateKeyToAccount } from 'viem/accounts' +import { Mppx, tempo } from 'mppx/client' + +const account = privateKeyToAccount('0xabc…123') + +Mppx.create({ + methods: [tempo({ account })], +}) +``` + +:::tip +If you want to avoid polyfilling, use the bound `fetch` instead. + +```ts +const mppx = Mppx.create({ + polyfill: false, + methods: [tempo({ account })] +}) + +const response = await mppx.fetch('https://api.example.com/resource') +``` +::: + +### Request protected resources + +Use `fetch`. Payment happens when a server returns `402`. + +```ts +const response = await fetch('https://api.example.com/resource') +``` + +:::: + +## Learn more + +### Wagmi + +You can inject a [Wagmi](https://wagmi.sh) connector into Mppx by passing the `getConnectorClient` function. + +:::code-group + +```ts [example.ts] +import { Mppx, tempo } from 'mppx/client' +import { getConnectorClient } from 'wagmi/actions' +import { config } from './config' + +Mppx.create({ + methods: [tempo({ + getClient: (parameters) => getConnectorClient(config, parameters), + })], +}) +``` + +```ts [config.ts] +import { createConfig, http } from 'wagmi' +import { webAuthn } from 'wagmi/tempo' +import { tempoModerato } from 'viem/chains' + +export const config = createConfig({ + connectors: [webAuthn()], + chains: [tempoModerato], + transports: { + [tempoModerato.id]: http(), + }, +}) +``` + +::: + +### Per-request accounts + +Pass accounts on individual requests instead of at setup: + +```ts +import { privateKeyToAccount } from 'viem/accounts' +import { Mppx, tempo } from 'mppx/client' + +const mppx = Mppx.create({ + polyfill: false, + methods: [tempo()] +}) + +const response = await mppx.fetch('https://api.example.com/resource', { + context: { + account: privateKeyToAccount('0xabc…123'), + } +}) +``` + +### Manual payment handling + +Use `Mppx.create` for full control over the payment flow: + +- Present payment UI before paying +- Implement custom retry logic +- Handle credentials manually + +```ts +import { Mppx, tempo } from 'mppx/client' +import { privateKeyToAccount } from 'viem/accounts' + +const mppx = Mppx.create({ + polyfill: false, + methods: [tempo()], +}) + +const response = await fetch('https://api.example.com/resource') + +if (response.status === 402) { + const credential = await mppx.createCredential(response, { + account: privateKeyToAccount('0x...'), + }) + + const paidResponse = await fetch('https://api.example.com/resource', { + headers: { Authorization: credential }, + }) +} +``` + +### Payment receipts + +On success, the server returns a `Payment-Receipt` header: + +```ts +import { Receipt } from 'mppx' + +const response = await fetch('https://api.example.com/resource') + +const receipt = Receipt.fromResponse(response) + +console.log(receipt.status) +// success +console.log(receipt.reference) +// 0xtx789abc... +``` + +## Next steps + + + + + + diff --git a/src/pages/guide/machine-payments/index.mdx b/src/pages/guide/machine-payments/index.mdx new file mode 100644 index 00000000..ff1b48d9 --- /dev/null +++ b/src/pages/guide/machine-payments/index.mdx @@ -0,0 +1,114 @@ +--- +title: Machine Payments +description: Charge for APIs, MCP tools, and digital content using the Machine Payments Protocol (MPP) on Tempo with TIP-20 stablecoins. +--- + +import { Card, Cards } from 'vocs' +import { MermaidDiagram } from '../../../components/MermaidDiagram' +import { TerminalDemo } from '../../../components/TerminalDemo' + +# Make Machine Payments + +The [Machine Payments Protocol](https://mpp.dev) (MPP) adds inline payments to any HTTP endpoint. Clients — apps, agents, or humans — pay as part of their request, and the server verifies payment before returning the response. + +## Try it out + +See the full payment flow in action. The terminal creates an ephemeral wallet, funds it with testnet USDC, and makes a paid request to fetch a photo. + +
+ +
+ +## Payment flow + +A client requests a paid resource, the server responds with `402` and a `Challenge` describing the price. The client pays, retries with a `Credential` transaction, and the server returns the resource with a `Receipt`. + +>Server: (1) GET /resource + Server-->>Client: (2) 402 Payment Required + Challenge + Note over Client: (3) Client fulfills payment + Client->>Server: (4) GET /resource + Credential + Note over Server: (5) Server verifies payment + Server-->>Client: (6) 200 OK + Receipt +`} /> + +1. **Request** — Any HTTP method (`GET`, `POST`, etc.) +2. **Challenge** — `402` with `WWW-Authenticate: Payment` header describing amount, currency, and recipient +3. **Pay** — Client signs a transaction or fulfills payment off-chain +4. **Retry** — Client re-sends with `Authorization: Payment` header containing the Credential +5. **Deliver** — Server verifies, returns `200` with `Payment-Receipt` header + +## Why Tempo + +Tempo's transaction model is explicitly designed for inline payments using MPP: + +- **~500ms finality** — Deterministic confirmation fast enough for synchronous request/response flows +- **Sub-cent fees** — Low enough for micropayments and per-request billing +- **Fee sponsorship** — Servers can cover gas on behalf of clients so they only need stablecoins +- **2D and expiring nonces** — Parallel nonce lanes prevent payment transactions from blocking other account activity +- **High throughput** — Supports the on-chain settlement volume that payment channels generate at scale + +## Payment intents + +Two [intents](https://mpp.dev/protocol#payment-intents) are available on Tempo: + +| | **Charge** | **Session** | +|---|---|---| +| **Pattern** | One-time payment per request | Continuous pay-as-you-go | +| **Latency** | ~500ms (on-chain confirmation) | Near-zero (off-chain vouchers) | +| **Best for** | Single API calls, content access, one-off purchases | LLM APIs, metered services, usage-based billing | +| **On-chain cost** | Per request | Amortized across many requests | + +## Use cases + +- **Paid APIs** — Charge per request without API keys, billing accounts, or signup flows. +- **MCP tools** — Monetize tool calls served through the Model Context Protocol. Agents pay per call without OAuth or account setup. +- **Digital content** — Charge per access for articles, data feeds, or media without subscription paywalls. + +## Get started + + + + + + + + +## SDKs and tools + +| Tool | Package | Install | +|-----|---------|---------| +| CLI | [`tempo request`](/cli/request) | `curl -fsSL https://tempo.xyz/install \| bash` | +| TypeScript | [`mppx`](https://github.com/wevm/mppx) | `npm install mppx viem` | +| Python | [`pympp`](https://github.com/tempoxyz/pympp) | `pip install pympp` | +| Rust | [`mpp-rs`](https://github.com/tempoxyz/mpp-rs) | `cargo add mpp` | + +See the [full SDK documentation](https://mpp.dev/sdk) for API reference and advanced usage. + +## Learn more + +- [MPP documentation](https://mpp.dev) — Full protocol docs, SDK reference, and guides +- [IETF specs](https://paymentauth.org/) — Normative protocol specification +- [Protocol overview](https://mpp.dev/protocol) — Challenges, Credentials, Receipts, and transports diff --git a/src/pages/guide/machine-payments/one-time-payments.mdx b/src/pages/guide/machine-payments/one-time-payments.mdx new file mode 100644 index 00000000..3b19af3f --- /dev/null +++ b/src/pages/guide/machine-payments/one-time-payments.mdx @@ -0,0 +1,178 @@ +--- +title: Accept One-Time Payments +description: Charge per request on Tempo using the mppx charge intent. Each request triggers a TIP-20 transfer that settles in ~500ms. +--- + +import { Card, Cards } from 'vocs' + +# Accept one-time payments + +Build a payment-gated API that charges $0.01 per request using `mppx`. The server returns a random photo from [Picsum](https://picsum.photos) behind a paywall. + +## Server setup + +::::steps + +### Install dependencies + +:::code-group +```bash [npm] +npm install mppx viem +``` +```bash [pnpm] +pnpm add mppx viem +``` +```bash [bun] +bun add mppx viem +``` +::: + +### Set up `Mppx` instance + +Set up an `Mppx` instance with the `tempo` method. + +- `recipient` is the address where you receive payments. +- `currency` is the token address for payments (in this case, `pathUSD`). + +```ts +import { Mppx, tempo } from 'mppx/server' + +const mppx = Mppx.create({ + methods: [tempo({ + currency: '0x20c0000000000000000000000000000000000000', + recipient: '0xa726a1CD723409074DF9108A2187cfA19899aCF8', + })], +}) +``` + +### Add a payment-gated route + +Add payment verification using `mppx.charge` as route middleware. The handler only runs after payment is verified. + +:::code-group + +```ts [Next.js] +import { Mppx, tempo } from 'mppx/nextjs' + +const mppx = Mppx.create({ + methods: [tempo({ + currency: '0x20c0000000000000000000000000000000000000', + recipient: '0xa726a1CD723409074DF9108A2187cfA19899aCF8', + })], +}) + +export const GET = + mppx.charge({ amount: '0.01', description: 'Random stock photo' }) + (async () => { + const res = await fetch('https://picsum.photos/1024/1024') + return Response.json({ url: res.url }) + }) +``` + +```ts [Hono] +import { Hono } from 'hono' +import { Mppx, tempo } from 'mppx/hono' + +const app = new Hono() + +const mppx = Mppx.create({ + methods: [tempo({ + currency: '0x20c0000000000000000000000000000000000000', + recipient: '0xa726a1CD723409074DF9108A2187cfA19899aCF8', + })], +}) + +app.get( + '/api/photo', + mppx.charge({ amount: '0.01', description: 'Random stock photo' }), + async (c) => { + const res = await fetch('https://picsum.photos/1024/1024') + return c.json({ url: res.url }) + }, +) +``` + +```ts [Express] +import express from 'express' +import { Mppx, tempo } from 'mppx/express' + +const app = express() + +const mppx = Mppx.create({ + methods: [tempo({ + currency: '0x20c0000000000000000000000000000000000000', + recipient: '0xa726a1CD723409074DF9108A2187cfA19899aCF8', + })], +}) + +app.get( + '/api/photo', + mppx.charge({ amount: '0.01', description: 'Random stock photo' }), + async (req, res) => { + const response = await fetch('https://picsum.photos/1024/1024') + res.json({ url: response.url }) + }, +) +``` + +```ts [Fetch API] +import { Mppx, tempo } from 'mppx/server' + +const mppx = Mppx.create({ + methods: [tempo({ + currency: '0x20c0000000000000000000000000000000000000', + recipient: '0xa726a1CD723409074DF9108A2187cfA19899aCF8', + })], +}) + +Bun.serve({ + async fetch(request) { + const result = await mppx.charge({ + amount: '0.01', + description: 'Random stock photo', + })(request) + + if (result.status === 402) return result.challenge + + const res = await fetch('https://picsum.photos/1024/1024') + return result.withReceipt(Response.json({ url: res.url })) + }, +}) +``` + +::: + +### Test the endpoint + +```bash +# Create account funded with testnet tokens +$ npx mppx account create + +# Make a paid request +$ npx mppx http://localhost:3000/api/photo +``` + +:::: + +## Next steps + + + + + + diff --git a/src/pages/guide/machine-payments/pay-as-you-go.mdx b/src/pages/guide/machine-payments/pay-as-you-go.mdx new file mode 100644 index 00000000..c3845507 --- /dev/null +++ b/src/pages/guide/machine-payments/pay-as-you-go.mdx @@ -0,0 +1,236 @@ +--- +title: Accept Pay-As-You-Go Payments +description: Session-based billing on Tempo with MPP payment channels. Clients deposit funds, sign off-chain vouchers, and pay per request without on-chain latency. +--- + +import { Card, Cards } from 'vocs' +import { MermaidDiagram } from '../../../components/MermaidDiagram' + +# Accept pay-as-you-go payments + +Build a payment-gated photo gallery API that charges $0.01 per photo using `mppx` sessions. The server returns random photos from [Picsum](https://picsum.photos) behind a paywall. + +:::info +Unlike [one-time payments](/guide/machine-payments/one-time-payments), sessions open a payment channel once and use off-chain vouchers for each subsequent request — vouchers are processed in pure CPU-bound signature checks, not bottlenecked by blockchain throughput. +::: + +## How sessions work + +>Tempo: (1) Deposit tokens + Tempo-->>Client: Channel created + Client->>Server: (2) Open credential + Note over Server: Verify on-chain deposit + Server-->>Client: 200 OK (session established) + loop Per request + Client->>Server: (3) Request + voucher + Note over Server: ecrecover only + Server-->>Client: 200 OK + Receipt + end + Note over Server: (4) Periodic settlement + Server->>Tempo: settle(channelId, voucher) + Client->>Server: (5) Close + Server->>Tempo: close(channelId, voucher) + Tempo-->>Client: Refund remaining deposit +`} /> + +1. **Open** — Client deposits funds into an on-chain escrow contract, creating a payment channel +2. **Session** — Client signs EIP-712 vouchers with increasing cumulative amounts as service is consumed +3. **Top up** — If the channel runs low, the client deposits additional tokens without closing the channel +4. **Close** — Either party closes the channel, settling the final balance on-chain and refunding unused deposit + +## Server setup + +::::steps + +### Install dependencies + +:::code-group +```bash [npm] +npm install mppx viem +``` +```bash [pnpm] +pnpm add mppx viem +``` +```bash [bun] +bun add mppx viem +``` +::: + +### Set up `Mppx` instance + +Set up an `Mppx` instance with the `tempo` method. + +- `recipient` is the address where you receive payments. +- `currency` is the token address for payments (in this case, `pathUSD`). + +```ts +import { Mppx, tempo } from 'mppx/server' + +const mppx = Mppx.create({ + methods: [tempo({ + currency: '0x20c0000000000000000000000000000000000000', + recipient: '0xa726a1CD723409074DF9108A2187cfA19899aCF8', + })], +}) +``` + +### Add a session-gated route + +Add payment verification using `mppx.session` as route middleware. The handler only runs after payment is verified. + +:::code-group + +```ts [Next.js] +import { Mppx, tempo } from 'mppx/nextjs' + +const mppx = Mppx.create({ + methods: [tempo({ + currency: '0x20c0000000000000000000000000000000000000', + recipient: '0xa726a1CD723409074DF9108A2187cfA19899aCF8', + })], +}) + +export const GET = + mppx.session({ amount: '0.01', unitType: 'photo' }) + (async () => { + const res = await fetch('https://picsum.photos/200/200') + return Response.json({ url: res.url }) + }) +``` + +```ts [Hono] +import { Hono } from 'hono' +import { Mppx, tempo } from 'mppx/hono' + +const app = new Hono() + +const mppx = Mppx.create({ + methods: [tempo({ + currency: '0x20c0000000000000000000000000000000000000', + recipient: '0xa726a1CD723409074DF9108A2187cfA19899aCF8', + })], +}) + +app.get( + '/api/sessions/photo', + mppx.session({ amount: '0.01', unitType: 'photo' }), + async (c) => { + const res = await fetch('https://picsum.photos/200/200') + return c.json({ url: res.url }) + }, +) +``` + +```ts [Express] +import express from 'express' +import { Mppx, tempo } from 'mppx/express' + +const app = express() + +const mppx = Mppx.create({ + methods: [tempo({ + currency: '0x20c0000000000000000000000000000000000000', + recipient: '0xa726a1CD723409074DF9108A2187cfA19899aCF8', + })], +}) + +app.get( + '/api/sessions/photo', + mppx.session({ amount: '0.01', unitType: 'photo' }), + async (req, res) => { + const response = await fetch('https://picsum.photos/200/200') + res.json({ url: response.url }) + }, +) +``` + +```ts [Fetch API] +import { Mppx, tempo } from 'mppx/server' + +const mppx = Mppx.create({ + methods: [tempo({ + currency: '0x20c0000000000000000000000000000000000000', + recipient: '0xa726a1CD723409074DF9108A2187cfA19899aCF8', + })], +}) + +Bun.serve({ + async fetch(request) { + const result = await mppx.session({ + amount: '0.01', + unitType: 'photo', + })(request) + + if (result.status === 402) return result.challenge + + const res = await fetch('https://picsum.photos/200/200') + return result.withReceipt(Response.json({ url: res.url })) + }, +}) +``` + +::: + +### Test the endpoint + +```bash +# Create account funded with testnet tokens +$ npx mppx account create + +# Make a paid request +$ npx mppx http://localhost:3000/api/sessions/photo +``` + +:::: + +## Client setup + +When using sessions from a client, set `maxDeposit` to enable automatic channel management. This is the maximum amount of tokens the client locks into the payment channel's escrow contract. Any unspent deposit is refunded when the channel closes. + +```ts +import { Mppx, tempo } from 'mppx/client' +import { privateKeyToAccount } from 'viem/accounts' + +const mppx = Mppx.create({ + methods: [tempo({ + account: privateKeyToAccount('0x...'), + maxDeposit: '1', // Lock up to 1 pathUSD per channel + })], +}) + +// Each fetch automatically manages the session lifecycle: +// 1st request: opens channel on-chain, sends initial voucher +// 2nd+ requests: sends off-chain vouchers (no on-chain tx) +const res = await fetch('http://localhost:3000/api/sessions/photo') +``` + +- **`maxDeposit: '1'`** — Locks up to 1 pathUSD into the payment channel. At $0.01/photo, this covers up to 100 requests before the channel runs out. +- The client handles the full session lifecycle automatically: channel open, voucher signing, and retry after `402` responses. +- If the server sets `suggestedDeposit`, the client uses `min(suggestedDeposit, maxDeposit)`. + +## Next steps + + + + + + diff --git a/src/pages/guide/machine-payments/server.mdx b/src/pages/guide/machine-payments/server.mdx new file mode 100644 index 00000000..0271c362 --- /dev/null +++ b/src/pages/guide/machine-payments/server.mdx @@ -0,0 +1,180 @@ +--- +title: Server Quickstart +description: Add payment gating to any HTTP endpoint on Tempo with mppx middleware for Next.js, Hono, Express, and the Fetch API. +--- + +import { Card, Cards } from 'vocs' + +# Server quickstart + +Plug MPP into any server framework to accept payments for protected resources. Use `mppx` middleware for your framework, or call `mppx/server` directly with the Fetch API. + +## Framework middleware + +Use the framework-specific middleware from `mppx` to integrate payment into your server. Each middleware handles the `402` challenge/credential flow and attaches receipts automatically. + +:::code-group + +```ts [Next.js] +import { Mppx, tempo } from 'mppx/nextjs' + +const mppx = Mppx.create({ + methods: [tempo({ + currency: '0x20c0000000000000000000000000000000000000', + recipient: '0xa726a1CD723409074DF9108A2187cfA19899aCF8', + })], +}) + +export const GET = + mppx.charge({ amount: '0.1' }) + (() => Response.json({ data: '...' })) +``` + +```ts [Hono] +import { Hono } from 'hono' +import { Mppx, tempo } from 'mppx/hono' + +const app = new Hono() + +const mppx = Mppx.create({ + methods: [tempo({ + currency: '0x20c0000000000000000000000000000000000000', + recipient: '0xa726a1CD723409074DF9108A2187cfA19899aCF8', + })], +}) + +app.get( + '/resource', + mppx.charge({ amount: '0.1' }), + (c) => c.json({ data: '...' }), +) +``` + +```ts [Express] +import express from 'express' +import { Mppx, tempo } from 'mppx/express' + +const app = express() + +const mppx = Mppx.create({ + methods: [tempo({ + currency: '0x20c0000000000000000000000000000000000000', + recipient: '0xa726a1CD723409074DF9108A2187cfA19899aCF8', + })], +}) + +app.get( + '/resource', + mppx.charge({ amount: '0.1' }), + (req, res) => res.json({ data: '...' })) +``` + +::: + +:::tip +You can override `currency` and `recipient` per call if different routes need different payment configurations. + +```ts +mppx.charge({ + amount: '0.1', + currency: '0x…', + recipient: '0x…', +}) +``` +::: + +## Manual mode + +If you prefer full control over the payment flow, use `mppx/server` directly with the Fetch API. + +```ts +import { Mppx, tempo } from 'mppx/server' + +const mppx = Mppx.create({ + methods: [tempo({ + currency: '0x20c0000000000000000000000000000000000000', + recipient: '0xa726a1CD723409074DF9108A2187cfA19899aCF8', + })], +}) + +export async function handler(request: Request) { + const response = await mppx.charge({ amount: '0.1' })(request) + + // Payment required: send 402 response with challenge + if (response.status === 402) return response.challenge + + // Payment verified: attach receipt and return resource + return response.withReceipt(Response.json({ data: '...' })) +} +``` + +:::info[Currency and recipient values] +`currency` is the TIP-20 token contract address — [`0x20c0…`](https://explore.tempo.xyz/address/0x20c0000000000000000000000000000000000000?live=false) is PathUSD on Tempo. `recipient` is the address that receives payment. See the [Tempo payment method](https://mpp.dev/payment-methods/tempo) for supported tokens. +::: + +## Push & pull modes + +Tempo charges support two transaction submission modes, determined by the client: + +- **`pull` mode (default)**: the client signs the transaction and sends the serialized transaction to the server. The server broadcasts it and verifies on-chain. This enables the server to sponsor gas fees via a `feePayer`. +- **`push` mode**: the client builds, signs, and broadcasts the transaction itself (for example, via a browser wallet). It sends the transaction hash to the server, which verifies the payment by fetching the receipt. + +Your server handles both modes automatically — no configuration required. The server inspects the credential payload type (`transaction` for pull, `hash` for push) and verifies accordingly. + +### Fee sponsorship + +To sponsor gas fees for pull-mode clients, pass a `feePayer` account to `tempo()`: + +```ts +import { Mppx, tempo } from 'mppx/server' +import { privateKeyToAccount } from 'viem/accounts' + +const mppx = Mppx.create({ + methods: [tempo({ + currency: '0x20c0000000000000000000000000000000000000', + feePayer: privateKeyToAccount('0x…'), + recipient: '0xa726a1CD723409074DF9108A2187cfA19899aCF8', + })], +}) +``` + +When a pull-mode client submits a signed transaction, the server co-signs with the fee payer account before broadcasting. Push-mode clients pay their own gas, so `feePayer` is ignored for those requests. + +## Testing your server + +After your server is running, test it with the `mppx` CLI: + +```bash +# Create an account funded with testnet tokens +$ npx mppx account create + +# Make a paid request +$ npx mppx /resource +``` + +:::tip +Use `npx mppx --inspect` to debug your server's Challenge response without making any payments. +::: + +## Next steps + + + + + + diff --git a/src/pages/guide/machine-payments/streamed-payments.mdx b/src/pages/guide/machine-payments/streamed-payments.mdx new file mode 100644 index 00000000..98f9e5db --- /dev/null +++ b/src/pages/guide/machine-payments/streamed-payments.mdx @@ -0,0 +1,314 @@ +--- +title: Accept Streamed Payments +description: Per-token billing over Server-Sent Events on Tempo. Stream content word-by-word and charge per unit using MPP sessions with SSE. +--- + +import { Card, Cards } from 'vocs' +import { MermaidDiagram } from '../../../components/MermaidDiagram' + +# Accept streamed payments + +Build a payment-gated API that streams content word-by-word and charges $0.001 per word using `mppx` sessions with Server-Sent Events (SSE). + +:::info +Streamed payments extend [pay-as-you-go sessions](/guide/machine-payments/pay-as-you-go) with SSE. The server charges per token as content streams — if the channel balance runs out mid-stream, the client automatically sends a new voucher and the stream resumes. +::: + +## How sessions work + +>Tempo: (1) Deposit tokens + Tempo-->>Client: Channel created + Client->>Server: (2) Open credential + Note over Server: Verify on-chain deposit + Server-->>Client: 200 OK (SSE stream begins) + loop Per token streamed + Server-->>Client: (3) SSE data event + charge + Note over Server: ecrecover only + end + alt Channel balance low + Server-->>Client: (4) payment-need-voucher event + Client->>Server: New voucher + Note over Server: Resume streaming + end + Note over Server: (5) Periodic settlement + Server->>Tempo: settle(channelId, voucher) + Client->>Server: (6) Close + Server->>Tempo: close(channelId, voucher) + Tempo-->>Client: Refund remaining deposit +`} /> + +1. **Open** — Client deposits funds into an on-chain escrow contract, creating a payment channel +2. **Stream** — Server streams SSE events, calling `stream.charge()` per token to increment the voucher amount +3. **Top up** — If the channel runs low mid-stream, the server emits a `payment-need-voucher` event and the client automatically signs a new voucher +4. **Close** — Either party closes the channel, settling the final balance on-chain and refunding unused deposit + +## Server setup + +::::steps + +### Install dependencies + +:::code-group +```bash [npm] +npm install mppx viem +``` +```bash [pnpm] +pnpm add mppx viem +``` +```bash [bun] +bun add mppx viem +``` +::: + +### Set up `Mppx` instance with streaming + +Set up an `Mppx` instance with `sse: true` to enable SSE support on the session method. + +```ts +import { Mppx, tempo } from 'mppx/server' + +const mppx = Mppx.create({ + methods: [tempo({ + currency: '0x20c0000000000000000000000000000000000000', + recipient: '0xa726a1CD723409074DF9108A2187cfA19899aCF8', + sse: true, + })], +}) +``` + +### Add a streaming route + +The handler returns an async generator — each yielded value becomes one SSE event and is charged one tick ($0.001). If the channel balance runs out mid-stream, the server emits `event: payment-need-voucher` and pauses until the client sends a new voucher. + +:::code-group + +```ts [Next.js] +import { Mppx, tempo } from 'mppx/nextjs' + +const mppx = Mppx.create({ + methods: [tempo({ + currency: '0x20c0000000000000000000000000000000000000', + recipient: '0xa726a1CD723409074DF9108A2187cfA19899aCF8', + sse: true, + })], +}) + +const poem = { + title: 'The Road Not Taken', + author: 'Robert Frost', + lines: [ + 'Two roads diverged in a yellow wood,', + 'And sorry I could not travel both', + 'And be one traveler, long I stood', + 'And looked down one as far as I could', + 'To where it bent in the undergrowth;', + ], +} + +export const GET = + mppx.session({ amount: '0.001', unitType: 'word' }) + (async () => { + const words = poem.lines.flatMap((line) => [...line.split(' '), '\\n']) + return async function* (stream) { + yield JSON.stringify({ title: poem.title, author: poem.author }) + for (const word of words) { + await stream.charge() + yield word + } + } + }) +``` + +```ts [Hono] +import { Hono } from 'hono' +import { Mppx, tempo } from 'mppx/hono' + +const app = new Hono() + +const mppx = Mppx.create({ + methods: [tempo({ + currency: '0x20c0000000000000000000000000000000000000', + recipient: '0xa726a1CD723409074DF9108A2187cfA19899aCF8', + sse: true, + })], +}) + +const poem = { + title: 'The Road Not Taken', + author: 'Robert Frost', + lines: [ + 'Two roads diverged in a yellow wood,', + 'And sorry I could not travel both', + 'And be one traveler, long I stood', + 'And looked down one as far as I could', + 'To where it bent in the undergrowth;', + ], +} + +app.get( + '/api/sessions/poem', + mppx.session({ amount: '0.001', unitType: 'word' }), + async (c) => { + const words = poem.lines.flatMap((line) => [...line.split(' '), '\\n']) + return async function* (stream) { + yield JSON.stringify({ title: poem.title, author: poem.author }) + for (const word of words) { + await stream.charge() + yield word + } + } + }, +) +``` + +```ts [Express] +import express from 'express' +import { Mppx, tempo } from 'mppx/express' + +const app = express() + +const mppx = Mppx.create({ + methods: [tempo({ + currency: '0x20c0000000000000000000000000000000000000', + recipient: '0xa726a1CD723409074DF9108A2187cfA19899aCF8', + sse: true, + })], +}) + +const poem = { + title: 'The Road Not Taken', + author: 'Robert Frost', + lines: [ + 'Two roads diverged in a yellow wood,', + 'And sorry I could not travel both', + 'And be one traveler, long I stood', + 'And looked down one as far as I could', + 'To where it bent in the undergrowth;', + ], +} + +app.get( + '/api/sessions/poem', + mppx.session({ amount: '0.001', unitType: 'word' }), + async (req, res) => { + const words = poem.lines.flatMap((line) => [...line.split(' '), '\\n']) + return async function* (stream) { + yield JSON.stringify({ title: poem.title, author: poem.author }) + for (const word of words) { + await stream.charge() + yield word + } + } + }, +) +``` + +```ts [Fetch API] +import { Mppx, tempo } from 'mppx/server' + +const mppx = Mppx.create({ + methods: [tempo({ + currency: '0x20c0000000000000000000000000000000000000', + recipient: '0xa726a1CD723409074DF9108A2187cfA19899aCF8', + sse: true, + })], +}) + +const poem = { + title: 'The Road Not Taken', + author: 'Robert Frost', + lines: [ + 'Two roads diverged in a yellow wood,', + 'And sorry I could not travel both', + 'And be one traveler, long I stood', + 'And looked down one as far as I could', + 'To where it bent in the undergrowth;', + ], +} + +Bun.serve({ + async fetch(request) { + const result = await mppx.session({ + amount: '0.001', + unitType: 'word', + })(request) + + if (result.status === 402) return result.challenge + + const words = poem.lines.flatMap((line) => [...line.split(' '), '\\n']) + return result.withReceipt(async function* (stream) { + yield JSON.stringify({ title: poem.title, author: poem.author }) + for (const word of words) { + await stream.charge() + yield word + } + }) + }, +}) +``` + +::: + +### Test the endpoint + +```bash +# Create account funded with testnet tokens +$ npx mppx account create + +# Stream a paid poem +$ npx mppx http://localhost:3000/api/sessions/poem +``` + +:::: + +## Client setup + +Use `tempo.session()` from `mppx/client` to create a session manager. The `.sse()` method connects to the SSE endpoint and handles voucher renewal automatically — if the server requests a new voucher mid-stream, the client signs and sends one without interrupting the stream. + +```ts +import { tempo } from 'mppx/client' +import { privateKeyToAccount } from 'viem/accounts' + +const session = tempo.session({ + account: privateKeyToAccount('0x...'), + maxDeposit: '1', // Lock up to 1 pathUSD per channel +}) + +// .sse() returns an async iterable of SSE data payloads +const stream = await session.sse('http://localhost:3000/api/sessions/poem') + +for await (const word of stream) { + process.stdout.write(word + ' ') +} +``` + +- **`tempo.session()`** — Creates a session manager that handles the full channel lifecycle: open, voucher signing, and close. +- **`.sse()`** — Connects to an SSE endpoint. Automatically sends new vouchers when the server emits `payment-need-voucher` events. +- **`maxDeposit: '1'`** — Locks up to 1 pathUSD. At $0.001/word, this covers ~1,000 words before the channel needs a top-up. + +## Next steps + + + + + + diff --git a/src/pages/guide/node/installation.mdx b/src/pages/guide/node/installation.mdx index ed42fb9c..b9dc6b61 100644 --- a/src/pages/guide/node/installation.mdx +++ b/src/pages/guide/node/installation.mdx @@ -4,7 +4,7 @@ description: Install Tempo node using pre-built binaries, build from source with # Installation -We provide three different installation paths - installing a pre-built binary, building from source or using our provided Docker image. +We provide three different installation paths — installing a pre-built binary, building from source, or using our provided Docker image. For the full CLI command reference, see [`tempo node`](/cli/node). ## Versions diff --git a/src/pages/guide/payments/accept-a-payment.mdx b/src/pages/guide/payments/accept-a-payment.mdx index cdd24714..eb5e974b 100644 --- a/src/pages/guide/payments/accept-a-payment.mdx +++ b/src/pages/guide/payments/accept-a-payment.mdx @@ -5,6 +5,7 @@ description: Accept stablecoin payments in your application. Verify transactions import * as Demo from '../../../components/guides/Demo.tsx' import * as Step from '../../../components/guides/steps' import * as Token from '../../../components/guides/tokens' +import { Tabs, Tab } from 'vocs' # Accept a Payment @@ -27,170 +28,535 @@ Check if a payment has been received by querying the token balance or listening ### Check Balance -:::code-group + + -```ts [TypeScript] -import { client } from './viem.config' + :::code-group -const balance = await client.token.getBalance({ - token: '0x20c0000000000000000000000000000000000001', // AlphaUSD - address: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', -}) + ```ts [example.ts] + import { client } from './viem.config' -console.log('Balance:', balance) -``` + const balance = await client.token.getBalance({ + token: '0x20c0000000000000000000000000000000000001', // AlphaUSD + address: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', + }) -```rust [Rust] -use alloy::{primitives::address, providers::ProviderBuilder}; -use tempo_alloy::{TempoNetwork, contracts::precompiles::ITIP20}; + console.log('Balance:', balance) + ``` -#[tokio::main] -async fn main() -> Result<(), Box> { - let provider = ProviderBuilder::new_with_network::() - .connect(&std::env::var("RPC_URL").expect("No RPC URL set")) - .await?; + ```ts [viem.config.ts] + // [!include ~/snippets/viem.config.ts:setup] + ``` - let balance = ITIP20::new( // [!code focus] - address!("0x20c0000000000000000000000000000000000001"), // Alpha USD // [!code focus] - &provider, // [!code focus] - ) // [!code focus] - .balanceOf(address!("0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb")) // [!code focus] - .call() // [!code focus] - .await?; // [!code focus] + ::: - println!("Balance: {balance:?}"); // [!code focus] + - Ok(()) -} -``` + + + :::code-group + + ```rust [example.rs] + use alloy::{primitives::address, providers::ProviderBuilder}; + use tempo_alloy::{TempoNetwork, contracts::precompiles::ITIP20}; + + #[tokio::main] + async fn main() -> Result<(), Box> { + let provider = ProviderBuilder::new_with_network::() + .connect(&std::env::var("RPC_URL").expect("No RPC URL set")) + .await?; + + let balance = ITIP20::new( // [!code focus] + address!("0x20c0000000000000000000000000000000000001"), // AlphaUSD // [!code focus] + &provider, // [!code focus] + ) // [!code focus] + .balanceOf(address!("0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb")) // [!code focus] + .call() // [!code focus] + .await?; // [!code focus] + + println!("Balance: {balance:?}"); // [!code focus] + + Ok(()) + } + ``` + + ```rust [provider.rs] + // [!include ~/snippets/rust-provider.rs:setup] + ``` + + ::: + + + + + + :::code-group + + ```python [example.py] + from web3 import Web3 + from eth_abi import encode + from provider import w3 + + token_address = "0x20c0000000000000000000000000000000000001" # AlphaUSD + account_address = "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb" + + # balanceOf(address) selector: 0x70a08231 + calldata = "0x70a08231" + encode(["address"], [account_address]).hex() # [!code hl] + result = w3.eth.call({"to": token_address, "data": calldata}) # [!code hl] + balance = int.from_bytes(result, "big") # [!code hl] + + print(f"Balance: {balance}") + ``` + + ```python [provider.py] + from web3 import Web3 + + w3 = Web3(Web3.HTTPProvider("https://rpc.tempo.xyz")) + ``` + + ::: + + + + + + :::code-group + + ```go [main.go] + package main + + import ( + "context" + "fmt" + "log" + "math/big" + "strings" + ) + + func main() { + c := newClient() + ctx := context.Background() + + token := "0x20c0000000000000000000000000000000000001" + account := "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb" + + // balanceOf(address) — ABI-encoded eth_call // [!code hl] + calldata := "0x70a08231" + fmt.Sprintf("%064s", strings.TrimPrefix(account, "0x")) // [!code hl] -::: + resp, err := c.SendRequest(ctx, "eth_call", map[string]interface{}{ // [!code hl] + "to": token, // [!code hl] + "data": calldata, // [!code hl] + }, "latest") // [!code hl] + if err != nil { + log.Fatal(err) + } + + balance := new(big.Int) // [!code hl] + balance.SetString(strings.TrimPrefix(resp.Result.(string), "0x"), 16) // [!code hl] + fmt.Printf("Balance: %s\n", balance) + } + ``` + + ```go [provider.go] + // [!include ~/snippets/go-provider.go:setup] + ``` + + ::: + + + + + + ```bash + $ cast call 0x20c0000000000000000000000000000000000001 \ + "balanceOf(address)(uint256)" \ + 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb \ + --rpc-url $TEMPO_RPC_URL + ``` + + + ### Listen for Transfer Events -:::code-group - -```ts [TypeScript] -import { watchEvent } from 'viem' - -// Watch for incoming transfers -const unwatch = watchEvent(client, { - address: '0x20c0000000000000000000000000000000000001', - event: { - type: 'event', - name: 'Transfer', - inputs: [ - { name: 'from', type: 'address', indexed: true }, - { name: 'to', type: 'address', indexed: true }, - { name: 'value', type: 'uint256' }, - ], - }, - onLogs: (logs) => { - logs.forEach((log) => { - if (log.args.to === yourAddress) { - console.log('Received payment:', { - from: log.args.from, - amount: log.args.value, - }) - } + + + + :::code-group + + ```ts [example.ts] + import { client } from './viem.config' + + // Watch for incoming transfers + const unwatch = client.watchEvent({ + address: '0x20c0000000000000000000000000000000000001', + event: { + type: 'event', + name: 'Transfer', + inputs: [ + { name: 'from', type: 'address', indexed: true }, + { name: 'to', type: 'address', indexed: true }, + { name: 'value', type: 'uint256' }, + ], + }, + onLogs: (logs) => { // [!code focus] + logs.forEach((log) => { // [!code focus] + console.log('Received payment:', { // [!code focus] + from: log.args.from, // [!code focus] + amount: log.args.value, // [!code focus] + }) // [!code focus] + }) // [!code focus] + }, // [!code focus] }) - }, -}) -``` + ``` -```rust [Rust] -use alloy::{primitives::address, providers::ProviderBuilder}; -use futures::StreamExt; -use tempo_alloy::{TempoNetwork, contracts::precompiles::ITIP20}; - -#[tokio::main] -async fn main() -> Result<(), Box> { - let provider = ProviderBuilder::new_with_network::() - .connect(&std::env::var("RPC_URL").expect("No RPC URL set")) - .await?; - - // Watch for incoming transfers // [!code focus] - let mut transfers = ITIP20::new( // [!code focus] - address!("0x20c0000000000000000000000000000000000001"), // [!code focus] - &provider, // [!code focus] - ) // [!code focus] - .Transfer_filter() // [!code focus] - .watch() // [!code focus] - .await? // [!code focus] - .into_stream(); // [!code focus] - - while let Some(Ok((payment, _))) = transfers.next().await { // [!code focus] - println!("Received payment: {payment:?}") // [!code focus] - } // [!code focus] - - Ok(()) -} -``` + ```ts [viem.config.ts] + // [!include ~/snippets/viem.config.ts:setup] + ``` + + ::: + + + + + + :::code-group + + ```rust [example.rs] + use alloy::{primitives::address, providers::ProviderBuilder}; + use futures::StreamExt; + use tempo_alloy::{TempoNetwork, contracts::precompiles::ITIP20}; + + #[tokio::main] + async fn main() -> Result<(), Box> { + let provider = ProviderBuilder::new_with_network::() + .connect(&std::env::var("RPC_URL").expect("No RPC URL set")) + .await?; + + // Watch for incoming transfers // [!code focus] + let mut transfers = ITIP20::new( // [!code focus] + address!("0x20c0000000000000000000000000000000000001"), // [!code focus] + &provider, // [!code focus] + ) // [!code focus] + .Transfer_filter() // [!code focus] + .watch() // [!code focus] + .await? // [!code focus] + .into_stream(); // [!code focus] + + while let Some(Ok((payment, _))) = transfers.next().await { // [!code focus] + println!("Received payment: {payment:?}") // [!code focus] + } // [!code focus] + + Ok(()) + } + ``` + + ```rust [provider.rs] + // [!include ~/snippets/rust-provider.rs:setup] + ``` + + ::: + + + + + + :::code-group + + ```python [example.py] + import json + from web3 import Web3 + from provider import w3 + + token_address = "0x20c0000000000000000000000000000000000001" # AlphaUSD + + transfer_event_abi = { # [!code focus] + "anonymous": False, # [!code focus] + "name": "Transfer", # [!code focus] + "type": "event", # [!code focus] + "inputs": [ # [!code focus] + {"indexed": True, "name": "from", "type": "address"}, # [!code focus] + {"indexed": True, "name": "to", "type": "address"}, # [!code focus] + {"indexed": False, "name": "value", "type": "uint256"}, # [!code focus] + ], # [!code focus] + } # [!code focus] + + contract = w3.eth.contract( # [!code focus] + address=Web3.to_checksum_address(token_address), # [!code focus] + abi=[transfer_event_abi], # [!code focus] + ) # [!code focus] + + # Get historical Transfer events # [!code focus] + events = contract.events.Transfer().get_logs(from_block="latest") # [!code focus] + for event in events: # [!code focus] + print(f"Transfer: {event.args['from']} -> {event.args['to']}: {event.args['value']}") # [!code focus] + ``` -::: + ```python [provider.py] + from web3 import Web3 + + w3 = Web3(Web3.HTTPProvider("https://rpc.tempo.xyz")) + ``` + + ::: + + + + + + :::code-group + + ```go [main.go] + package main + + import ( + "context" + "fmt" + "log" + + "github.com/ethereum/go-ethereum/crypto" + ) + + func main() { + c := newClient() + ctx := context.Background() + + token := "0x20c0000000000000000000000000000000000001" + + // Transfer(address,address,uint256) event topic // [!code focus] + transferTopic := crypto.Keccak256Hash([]byte("Transfer(address,address,uint256)")) // [!code focus] + + resp, err := c.SendRequest(ctx, "eth_getLogs", map[string]interface{}{ // [!code focus] + "fromBlock": "0x0", // [!code focus] + "toBlock": "latest", // [!code focus] + "address": token, // [!code focus] + "topics": []interface{}{transferTopic.Hex()}, // [!code focus] + }) // [!code focus] + if err != nil { + log.Fatal(err) + } + + logs, _ := resp.Result.([]interface{}) // [!code focus] + for _, entry := range logs { // [!code focus] + l := entry.(map[string]interface{}) // [!code focus] + topics := l["topics"].([]interface{}) // [!code focus] + fmt.Printf("Transfer: %s -> %s (data: %s)\n", topics[1], topics[2], l["data"]) // [!code focus] + } // [!code focus] + } + ``` + + ```go [provider.go] + // [!include ~/snippets/go-provider.go:setup] + ``` + + ::: + + + + + + ```bash + $ cast logs \ + --address 0x20c0000000000000000000000000000000000001 \ + "Transfer(address indexed, address indexed, uint256)" \ + --rpc-url $TEMPO_RPC_URL + ``` + + + ## Payment Reconciliation with Memos If payments include memos (invoice IDs, order numbers, etc.), you can reconcile them automatically: -:::code-group - -```ts [TypeScript] -// Watch for TransferWithMemo events -const unwatch = watchEvent(client, { - address: tokenAddress, - event: { - type: 'event', - name: 'TransferWithMemo', - inputs: [ - { name: 'from', type: 'address', indexed: true }, - { name: 'to', type: 'address', indexed: true }, - { name: 'value', type: 'uint256' }, - { name: 'memo', type: 'bytes32', indexed: true }, - ], - }, - onLogs: (logs) => { - logs.forEach((log) => { - if (log.args.to === yourAddress) { - const invoiceId = log.args.memo - // Mark invoice as paid in your database - markInvoiceAsPaid(invoiceId, log.args.value) - } + + + + :::code-group + + ```ts [example.ts] + import { client } from './viem.config' + + // Watch for TransferWithMemo events + const unwatch = client.watchEvent({ + address: '0x20c0000000000000000000000000000000000001', + event: { + type: 'event', + name: 'TransferWithMemo', + inputs: [ + { name: 'from', type: 'address', indexed: true }, + { name: 'to', type: 'address', indexed: true }, + { name: 'value', type: 'uint256' }, + { name: 'memo', type: 'bytes32', indexed: true }, + ], + }, + onLogs: (logs) => { // [!code focus] + logs.forEach((log) => { // [!code focus] + const invoiceId = log.args.memo // [!code focus] + // Mark invoice as paid in your database // [!code focus] + markInvoiceAsPaid(invoiceId, log.args.value) // [!code focus] + }) // [!code focus] + }, // [!code focus] }) - }, -}) -``` + ``` -```rust [Rust] -use alloy::{primitives::address, providers::ProviderBuilder}; -use futures::StreamExt; -use tempo_alloy::{TempoNetwork, contracts::precompiles::ITIP20}; - -#[tokio::main] -async fn main() -> Result<(), Box> { - let provider = ProviderBuilder::new_with_network::() - .connect(&std::env::var("RPC_URL").expect("No RPC URL set")) - .await?; - - let mut transfers = ITIP20::new( // [!code focus] - address!("0x20c0000000000000000000000000000000000001"), // [!code focus] - &provider, // [!code focus] - ) // [!code focus] - .TransferWithMemo_filter() // [!code focus] - .watch() // [!code focus] - .await? // [!code focus] - .into_stream(); // [!code focus] - - while let Some(Ok((transfer, _))) = transfers.next().await { // [!code focus] - let invoice_id = transfer.memo; // [!code focus] - println!("Transfer received with memo: {invoice_id:?}"); // [!code focus] - } // [!code focus] - - Ok(()) -} -``` + ```ts [viem.config.ts] + // [!include ~/snippets/viem.config.ts:setup] + ``` + + ::: + + + + + + :::code-group + + ```rust [example.rs] + use alloy::{primitives::address, providers::ProviderBuilder}; + use futures::StreamExt; + use tempo_alloy::{TempoNetwork, contracts::precompiles::ITIP20}; + + #[tokio::main] + async fn main() -> Result<(), Box> { + let provider = ProviderBuilder::new_with_network::() + .connect(&std::env::var("RPC_URL").expect("No RPC URL set")) + .await?; + + let mut transfers = ITIP20::new( // [!code focus] + address!("0x20c0000000000000000000000000000000000001"), // [!code focus] + &provider, // [!code focus] + ) // [!code focus] + .TransferWithMemo_filter() // [!code focus] + .watch() // [!code focus] + .await? // [!code focus] + .into_stream(); // [!code focus] + + while let Some(Ok((transfer, _))) = transfers.next().await { // [!code focus] + let invoice_id = transfer.memo; // [!code focus] + println!("Transfer received with memo: {invoice_id:?}"); // [!code focus] + } // [!code focus] + + Ok(()) + } + ``` + + ```rust [provider.rs] + // [!include ~/snippets/rust-provider.rs:setup] + ``` + + ::: + + + + + + :::code-group + + ```python [example.py] + from web3 import Web3 + from provider import w3 + + token_address = "0x20c0000000000000000000000000000000000001" # AlphaUSD + + transfer_memo_abi = { # [!code focus] + "anonymous": False, # [!code focus] + "name": "TransferWithMemo", # [!code focus] + "type": "event", # [!code focus] + "inputs": [ # [!code focus] + {"indexed": True, "name": "from", "type": "address"}, # [!code focus] + {"indexed": True, "name": "to", "type": "address"}, # [!code focus] + {"indexed": False, "name": "value", "type": "uint256"}, # [!code focus] + {"indexed": True, "name": "memo", "type": "bytes32"}, # [!code focus] + ], # [!code focus] + } # [!code focus] + + contract = w3.eth.contract( # [!code focus] + address=Web3.to_checksum_address(token_address), # [!code focus] + abi=[transfer_memo_abi], # [!code focus] + ) # [!code focus] + + events = contract.events.TransferWithMemo().get_logs(from_block="latest") # [!code focus] + for event in events: # [!code focus] + invoice_id = event.args["memo"] # [!code focus] + print(f"Transfer with memo {invoice_id.hex()}: {event.args['value']}") # [!code focus] + ``` + + ```python [provider.py] + from web3 import Web3 + + w3 = Web3(Web3.HTTPProvider("https://rpc.tempo.xyz")) + ``` + + ::: + + + + + + :::code-group + + ```go [main.go] + package main + + import ( + "context" + "fmt" + "log" + + "github.com/ethereum/go-ethereum/crypto" + ) + + func main() { + c := newClient() + ctx := context.Background() + + token := "0x20c0000000000000000000000000000000000001" + + // TransferWithMemo(address,address,uint256,bytes32) event topic // [!code focus] + memoTopic := crypto.Keccak256Hash( // [!code focus] + []byte("TransferWithMemo(address,address,uint256,bytes32)"), // [!code focus] + ) // [!code focus] + + resp, err := c.SendRequest(ctx, "eth_getLogs", map[string]interface{}{ // [!code focus] + "fromBlock": "0x0", // [!code focus] + "toBlock": "latest", // [!code focus] + "address": token, // [!code focus] + "topics": []interface{}{memoTopic.Hex()}, // [!code focus] + }) // [!code focus] + if err != nil { + log.Fatal(err) + } + + logs, _ := resp.Result.([]interface{}) // [!code focus] + for _, entry := range logs { // [!code focus] + l := entry.(map[string]interface{}) // [!code focus] + topics := l["topics"].([]interface{}) // [!code focus] + fmt.Printf("Transfer: %s -> %s (memo: %s, data: %s)\n", // [!code focus] + topics[1], topics[2], topics[3], l["data"]) // [!code focus] + } // [!code focus] + } + ``` + + ```go [provider.go] + // [!include ~/snippets/go-provider.go:setup] + ``` + + ::: + + + + + + ```bash + $ cast logs \ + --address 0x20c0000000000000000000000000000000000001 \ + "TransferWithMemo(address indexed, address indexed, uint256, bytes32 indexed)" \ + --rpc-url $TEMPO_RPC_URL + ``` -::: + + ## Smart Contract Integration diff --git a/src/pages/guide/payments/pay-fees-in-any-stablecoin.mdx b/src/pages/guide/payments/pay-fees-in-any-stablecoin.mdx index c3b3dd28..6822a151 100644 --- a/src/pages/guide/payments/pay-fees-in-any-stablecoin.mdx +++ b/src/pages/guide/payments/pay-fees-in-any-stablecoin.mdx @@ -70,6 +70,134 @@ const receipt = await client.token.transferSync({ }) ``` + + + + +:::code-group + +```rust [example.rs] +use alloy::{ + primitives::{address, U256}, + providers::Provider, + sol_types::SolCall, +}; +use tempo_alloy::{ + contracts::precompiles::ITIP20, primitives::transaction::Call, + rpc::TempoTransactionRequest, +}; + +mod provider; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let provider = provider::get_provider().await?; + + let alpha_usd = address!("0x20c0000000000000000000000000000000000001"); + let beta_usd = address!("0x20c0000000000000000000000000000000000002"); + + let calls = vec![Call { + to: alpha_usd.into(), + input: ITIP20::transferCall { + to: address!("0x0000000000000000000000000000000000000000"), + amount: U256::from(100_000_000), + } + .abi_encode() + .into(), + value: U256::ZERO, + }]; + + let pending = provider + .send_transaction(TempoTransactionRequest { + calls, + fee_token: Some(beta_usd), // [!code ++] + ..Default::default() + }) + .await?; + + Ok(()) +} +``` + +```rust [provider.rs] +// [!include ~/snippets/rust-signer-provider.rs:setup] +``` + +::: + + + + + +
+ +```python +from pytempo import TempoTransaction +from pytempo.contracts import TIP20, ALPHA_USD, BETA_USD + +tx = TempoTransaction.create( + chain_id=w3.eth.chain_id, + gas_limit=100_000, + max_fee_per_gas=w3.eth.gas_price * 2, + max_priority_fee_per_gas=w3.eth.gas_price, + nonce=w3.eth.get_transaction_count(account.address), + fee_token=BETA_USD, # [!code ++] + calls=( + TIP20(ALPHA_USD).transfer( + to="0x0000000000000000000000000000000000000000", + amount=100_000_000, + ), + ), +) +``` + + + + + +
+ +```go +alphaUSD := common.HexToAddress("0x20c0000000000000000000000000000000000001") +betaUSD := common.HexToAddress("0x20c0000000000000000000000000000000000002") + +tx := transaction.NewBuilder(big.NewInt(transaction.ChainIdModerato)). + SetNonce(nonce). + SetGas(100_000). + SetMaxFeePerGas(big.NewInt(25_000_000_000)). + SetMaxPriorityFeePerGas(big.NewInt(1_000_000_000)). + SetFeeToken(betaUSD). // [!code ++] + AddCall(alphaUSD, big.NewInt(0), buildTransferData(recipient, big.NewInt(100_000_000))). + Build() +``` + + + + + +
+ +```bash +$ cast send \ + 0x20c0000000000000000000000000000000000001 \ + "transfer(address,uint256)" \ + 0x0000000000000000000000000000000000000000 \ + 100000000 \ + --rpc-url $TEMPO_RPC_URL \ + --private-key $PRIVATE_KEY \ + --tempo.fee-token 0x20c0000000000000000000000000000000000002 # [!code ++] +``` + + + + + +
+ +:::info +The fee token for a given transaction cannot be set from Solidity — it is a transaction-level parameter handled by the signing SDK. However, you can configure a **default** fee token for an account using [`setUserToken`](#set-user-fee-token), which will apply to all future transactions unless explicitly overridden at submission. +::: + @@ -331,11 +459,49 @@ const receipt = await client.token.transferSync({ :::: - -# Recipes + + +:::info +For Rust integration, refer to the [Quick Snippet](#quick-snippet) above and the [Set user fee token](#set-user-fee-token) below. +::: + + + + + +:::info +For Python integration, refer to the [Quick Snippet](#quick-snippet) above and the [Set user fee token](#set-user-fee-token) below. +::: + + + + + +:::info +For Go integration, refer to the [Quick Snippet](#quick-snippet) above and the [Set user fee token](#set-user-fee-token) below. +::: + + + + + +:::info +For Cast integration, refer to the [Quick Snippet](#quick-snippet) above and the [Set user fee token](#set-user-fee-token) below. +::: + + + + + +:::info +For Solidity integration, refer to the [Quick Snippet](#quick-snippet) above and the [Set user fee token](#set-user-fee-token) below. +::: + + + -## Set user fee token +## Set user fee token You can also set a persistent default fee token for an account, so users don't need to specify `feeToken` on every transaction. Learn more about fee token preferences [here](/protocol/fees/spec-fee#fee-token-preferences). @@ -359,6 +525,173 @@ You can also set a persistent default fee token for an account, so users don't n // [!include ~/snippets/unformatted/fee.setUserToken.ts:viem] ``` + + + + +
+ +:::code-group + +```rust [example.rs] +use alloy::primitives::address; +use tempo_alloy::contracts::precompiles::IFeeManager; + +mod provider; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let provider = provider::get_provider().await?; + + let fee_manager = IFeeManager::new( + address!("0xFEEc000000000000000000000000000000000000"), + &provider, + ); + + let receipt = fee_manager + .setUserToken( // [!code hl] + address!("0x20c0000000000000000000000000000000000001"), // [!code hl] + ) // [!code hl] + .send() + .await? + .get_receipt() + .await?; + + println!("Transaction hash: {:?}", receipt.transaction_hash); + + Ok(()) +} +``` + +```rust [provider.rs] +// [!include ~/snippets/rust-signer-provider.rs:setup] +``` + +::: + + + + + +
+ +:::code-group + +```python [example.py] +from pytempo import TempoTransaction +from pytempo.contracts import FeeManager, ALPHA_USD +from provider import w3, account + +tx = TempoTransaction.create( + chain_id=w3.eth.chain_id, + gas_limit=100_000, + max_fee_per_gas=w3.eth.gas_price * 2, + max_priority_fee_per_gas=w3.eth.gas_price, + nonce=w3.eth.get_transaction_count(account.address), + calls=( + FeeManager.set_user_token(ALPHA_USD), # [!code hl] + ), +) + +signed_tx = tx.sign(account.key.hex()) +tx_hash = w3.eth.send_raw_transaction(signed_tx.encode()) +``` + +```python [provider.py] +from web3 import Web3 +from eth_account import Account + +w3 = Web3(Web3.HTTPProvider("https://rpc.presto.tempo.xyz")) +account = Account.from_key("0x...") +``` + +::: + + + + + +
+ +:::code-group + +```go [main.go] +package main + +import ( + "context" + "log" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/tempoxyz/tempo-go/pkg/signer" + "github.com/tempoxyz/tempo-go/pkg/transaction" +) + +func main() { + sgn, _ := signer.NewSigner("0x...") + c := newClient() + ctx := context.Background() + + nonce, _ := c.GetTransactionCount(ctx, sgn.Address().Hex()) + + feeManager := common.HexToAddress("0xFEEc000000000000000000000000000000000000") + token := common.HexToAddress("0x20c0000000000000000000000000000000000001") + + // setUserToken(address) selector: 0xe7897444 + data := make([]byte, 36) // [!code hl] + data[0], data[1], data[2], data[3] = 0xe7, 0x89, 0x74, 0x44 // [!code hl] + copy(data[16:36], token.Bytes()) // [!code hl] + + tx := transaction.NewBuilder(big.NewInt(transaction.ChainIdModerato)). + SetNonce(nonce). + SetGas(100_000). + SetMaxFeePerGas(big.NewInt(25_000_000_000)). + SetMaxPriorityFeePerGas(big.NewInt(1_000_000_000)). + AddCall(feeManager, big.NewInt(0), data). + Build() + + _ = transaction.SignTransaction(tx, sgn) + serialized, _ := transaction.Serialize(tx, nil) + txHash, _ := c.SendRawTransaction(ctx, serialized) + + log.Printf("Transaction hash: %s", txHash) +} +``` + +```go [provider.go] +// [!include ~/snippets/go-provider.go:setup] +``` + +::: + + + + + +
+ +```bash +$ cast send \ + 0xFEEc000000000000000000000000000000000000 \ + "setUserToken(address)" \ + 0x20c0000000000000000000000000000000000001 \ + --rpc-url $TEMPO_RPC_URL \ + --private-key $PRIVATE_KEY # [!code hl] +``` + + + + + +
+ +```solidity +import {StdPrecompiles} from "tempo-std/StdPrecompiles.sol"; + +StdPrecompiles.TIP_FEE_MANAGER.setUserToken(0x20c0000000000000000000000000000000000001); // [!code hl] +``` + diff --git a/src/pages/guide/payments/send-a-payment.mdx b/src/pages/guide/payments/send-a-payment.mdx index 7eaaa891..d25821e4 100644 --- a/src/pages/guide/payments/send-a-payment.mdx +++ b/src/pages/guide/payments/send-a-payment.mdx @@ -5,6 +5,7 @@ description: Send stablecoin payments between accounts on Tempo. Include optiona import * as Demo from '../../../components/guides/Demo.tsx' import * as Step from '../../../components/guides/steps' import { Cards, Card } from 'vocs' +import { Tabs, Tab } from 'vocs' # Send a Payment @@ -183,248 +184,816 @@ Now that you have made a payment you can Send a payment using the standard `transfer` function: -:::code-group + + + + :::code-group + + ```ts twoslash [example.ts] + // @noErrors + import { parseUnits } from 'viem' + import { client } from './viem.config' + + const { receipt } = await client.token.transferSync({ + amount: parseUnits('100', 6), // 100 tokens (6 decimals) // [!code hl] + to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', // [!code hl] + token: '0x20c0000000000000000000000000000000000001', // AlphaUSD // [!code hl] + }) + ``` + + ```ts twoslash [viem.config.ts] + // [!include ~/snippets/viem.config.ts:setup] + ``` + + ::: + + + + + + ```tsx twoslash + // @noErrors + import { Hooks } from 'wagmi/tempo' + import { parseUnits } from 'viem' + + function SendPayment() { + const { mutate, isPending } = Hooks.token.useTransferSync() // [!code hl] + + return ( + + ) + } + ``` -```ts [example.ts] -import { parseUnits } from 'viem' -import { client } from './viem.config' + -const { receipt } = await client.token.transferSync({ - amount: parseUnits('100', 6), // 100 tokens (6 decimals) - to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', - token: '0x20c0000000000000000000000000000000000001', // AlphaUSD -}) -``` + -```ts [viem.config.ts] filename="viem.config.ts" -// [!include ~/snippets/viem.config.ts:setup] -``` + :::code-group -```rs [Rust] -use alloy::{ - primitives::{address, U256}, - providers::ProviderBuilder, -}; -use tempo_alloy::{TempoNetwork, contracts::precompiles::ITIP20}; - -#[tokio::main] -async fn main() -> Result<(), Box> { - let provider = ProviderBuilder::new_with_network::() - .connect(&std::env::var("RPC_URL").expect("No RPC URL set")) - .await?; - - let token = ITIP20::new( // [!code focus] - address!("0x20c0000000000000000000000000000000000001"), // AlphaUSD // [!code focus] - &provider, // [!code focus] - ); // [!code focus] - - let receipt = token // [!code focus] - .transfer( // [!code focus] - address!("0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb"), // [!code focus] - U256::from(100_000_000), // 100 tokens (6 decimals) // [!code focus] - ) // [!code focus] - .send() // [!code focus] - .await? // [!code focus] - .get_receipt() // [!code focus] - .await?; // [!code focus] - - println!("Transfer successful: {:?}", receipt.transaction_hash); // [!code focus] - - Ok(()) -} -``` + ```rust [example.rs] + use alloy::{ + primitives::{address, U256}, + providers::ProviderBuilder, + }; + use tempo_alloy::{TempoNetwork, contracts::precompiles::ITIP20}; -::: + mod provider; + + #[tokio::main] + async fn main() -> Result<(), Box> { + let provider = provider::get_provider().await?; + + let token = ITIP20::new( // [!code hl] + address!("0x20c0000000000000000000000000000000000001"), // AlphaUSD // [!code hl] + &provider, // [!code hl] + ); // [!code hl] + + let receipt = token // [!code hl] + .transfer( // [!code hl] + address!("0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb"), // [!code hl] + U256::from(100_000_000), // 100 tokens (6 decimals) // [!code hl] + ) // [!code hl] + .send() // [!code hl] + .await? // [!code hl] + .get_receipt() // [!code hl] + .await?; // [!code hl] + + println!("Transfer successful: {:?}", receipt.transaction_hash); + + Ok(()) + } + ``` + + ```rust [provider.rs] + // [!include ~/snippets/rust-signer-provider.rs:setup] + ``` + + ::: + + + + + + :::code-group + + ```python [example.py] + from eth_abi import encode + from pytempo import Call, TempoTransaction + from provider import w3, account + + TRANSFER_SELECTOR = bytes.fromhex("a9059cbb") + ALPHA_USD = "0x20c0000000000000000000000000000000000001" + + data = TRANSFER_SELECTOR + encode( # [!code hl] + ["address", "uint256"], # [!code hl] + ["0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb", 100_000_000], # [!code hl] + ) # [!code hl] + + tx = TempoTransaction.create( + chain_id=w3.eth.chain_id, + gas_limit=100_000, + max_fee_per_gas=w3.eth.gas_price * 2, + max_priority_fee_per_gas=w3.eth.gas_price, + nonce=w3.eth.get_transaction_count(account.address), + calls=( + Call.create(to=ALPHA_USD, data="0x" + data.hex()), # [!code hl] + ), + ) + + signed_tx = tx.sign(account.key.hex()) + tx_hash = w3.eth.send_raw_transaction(signed_tx.encode()) + ``` + + ```python [provider.py] + from web3 import Web3 + from eth_account import Account + + w3 = Web3(Web3.HTTPProvider("https://rpc.tempo.xyz")) + account = Account.from_key("0x...") + ``` + + ::: + + + + + + :::code-group + + ```go [main.go] + package main + + import ( + "context" + "log" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/tempoxyz/tempo-go/pkg/signer" + "github.com/tempoxyz/tempo-go/pkg/transaction" + ) + + func buildTransferData(to common.Address, amount *big.Int) []byte { + data := make([]byte, 68) // 4 (selector) + 32 (address) + 32 (uint256) + data[0], data[1], data[2], data[3] = 0xa9, 0x05, 0x9c, 0xbb + copy(data[16:36], to.Bytes()) + amount.FillBytes(data[36:68]) + return data + } + + func main() { + sgn, _ := signer.NewSigner("0x...") + c := newClient() + ctx := context.Background() + + nonce, _ := c.GetTransactionCount(ctx, sgn.Address().Hex()) + + recipient := common.HexToAddress("0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb") + alphaUSD := common.HexToAddress("0x20c0000000000000000000000000000000000001") + + tx := transaction.NewBuilder(big.NewInt(transaction.ChainIdMainnet)). + SetNonce(nonce). + SetGas(100_000). + SetMaxFeePerGas(big.NewInt(25_000_000_000)). + SetMaxPriorityFeePerGas(big.NewInt(1_000_000_000)). + AddCall( // [!code hl] + alphaUSD, // [!code hl] + big.NewInt(0), // [!code hl] + buildTransferData(recipient, big.NewInt(100_000_000)), // [!code hl] + ). // [!code hl] + Build() + + _ = transaction.SignTransaction(tx, sgn) + serialized, _ := transaction.Serialize(tx, nil) + txHash, _ := c.SendRawTransaction(ctx, serialized) + + log.Printf("Transaction hash: %s", txHash) + } + ``` + + ```go [provider.go] + // [!include ~/snippets/go-provider.go:setup] + ``` + + ::: + + + + + + ```bash + $ cast erc20 transfer \ + 0x20c0000000000000000000000000000000000001 \ + 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb \ + 100000000 \ + --rpc-url $TEMPO_RPC_URL \ + --private-key $PRIVATE_KEY # [!code hl] + ``` + + + + + + ```solidity + import {ITIP20} from "tempo-std/interfaces/ITIP20.sol"; + + contract PaymentSender { + ITIP20 public token; + + function sendPayment(address recipient, uint256 amount) external { + token.transfer(recipient, amount); // [!code hl] + } + } + ``` + + + ### Transfer with memo -Include a memo for payment reconciliation and tracking: +Include a memo for payment reconciliation and tracking. The memo is a 32-byte value that can store payment references, invoice IDs, order numbers, or any other metadata. + + + + + :::code-group + + ```ts twoslash [example.ts] + // @noErrors + import { parseUnits, stringToHex, pad } from 'viem' + import { client } from './viem.config' + + const invoiceId = pad(stringToHex('INV-12345'), { size: 32 }) // [!code hl] + + const { receipt } = await client.token.transferSync({ + amount: parseUnits('100', 6), + to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', + token: '0x20c0000000000000000000000000000000000001', + memo: invoiceId, // [!code hl] + }) + ``` + + ```ts twoslash [viem.config.ts] + // [!include ~/snippets/viem.config.ts:setup] + ``` + + ::: + + + + + + ```tsx twoslash + // @noErrors + import { Hooks } from 'wagmi/tempo' + import { parseUnits, stringToHex, pad } from 'viem' + + function SendPaymentWithMemo() { + const { mutate, isPending } = Hooks.token.useTransferSync() + + return ( + + ) + } + ``` -:::code-group + -```ts [example.ts] -import { parseUnits } from 'viem' -import { client } from './viem.config' + -const invoiceId = pad(stringToHex('INV-12345'), { size: 32 }) + :::code-group -const { receipt } = await client.token.transferSync({ - amount: parseUnits('100', 6), - to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', - token: '0x20c0000000000000000000000000000000000001', - memo: invoiceId, -}) -``` -```ts [viem.config.ts] filename="viem.config.ts" -// [!include ~/snippets/viem.config.ts:setup] -``` + ```rust [example.rs] + use alloy::{ + primitives::{address, B256, U256}, + providers::ProviderBuilder, + }; + use tempo_alloy::{TempoNetwork, contracts::precompiles::ITIP20}; -```rs [Rust] -use alloy::{ - primitives::{address, B256, U256}, - providers::ProviderBuilder, -}; -use tempo_alloy::{TempoNetwork, contracts::precompiles::ITIP20}; - -#[tokio::main] -async fn main() -> Result<(), Box> { - let provider = ProviderBuilder::new_with_network::() - .connect(&std::env::var("RPC_URL").expect("No RPC URL set")) - .await?; - - let token = ITIP20::new( // [!code focus] - address!("0x20c0000000000000000000000000000000000001"), // AlphaUSD // [!code focus] - &provider, // [!code focus] - ); // [!code focus] - - let receipt = token // [!code focus] - .transferWithMemo( // [!code focus] - address!("0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb"), // [!code focus] - U256::from(100_000_000), // 100 tokens (6 decimals) // [!code focus] - B256::left_padding_from("INV-12345".as_bytes()), // [!code focus] - ) // [!code focus] - .send() // [!code focus] - .await? // [!code focus] - .get_receipt() // [!code focus] - .await?; // [!code focus] - - println!("Transfer successful: {:?}", receipt.transaction_hash); // [!code focus] - - Ok(()) -} -``` + mod provider; -::: + #[tokio::main] + async fn main() -> Result<(), Box> { + let provider = provider::get_provider().await?; -The memo is a 32-byte value that can store payment references, invoice IDs, order numbers, or any other metadata. + let token = ITIP20::new( + address!("0x20c0000000000000000000000000000000000001"), + &provider, + ); -### Using Solidity + let receipt = token + .transferWithMemo( // [!code hl] + address!("0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb"), + U256::from(100_000_000), + B256::left_padding_from("INV-12345".as_bytes()), // [!code hl] + ) + .send() + .await? + .get_receipt() + .await?; -If you're building a smart contract that sends payments: + println!("Transfer successful: {:?}", receipt.transaction_hash); -```solidity -interface ITIP20 { - function transfer(address to, uint256 amount) external returns (bool); - function transferWithMemo(address to, uint256 amount, bytes32 memo) external; -} + Ok(()) + } + ``` + + ```rust [provider.rs] + // [!include ~/snippets/rust-signer-provider.rs:setup] + ``` + + ::: + + + + + + :::code-group -contract PaymentSender { - ITIP20 public token; - - function sendPayment(address recipient, uint256 amount) external { - token.transfer(recipient, amount); + ```python [example.py] + from eth_abi import encode + from pytempo import Call, TempoTransaction + from provider import w3, account + + TRANSFER_WITH_MEMO_SELECTOR = bytes.fromhex("76a8ee59") + ALPHA_USD = "0x20c0000000000000000000000000000000000001" + + memo = b"INV-12345" + b"\x00" * 23 # right-pad to 32 bytes # [!code hl] + + data = TRANSFER_WITH_MEMO_SELECTOR + encode( # [!code hl] + ["address", "uint256", "bytes32"], # [!code hl] + ["0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb", 100_000_000, memo], # [!code hl] + ) # [!code hl] + + tx = TempoTransaction.create( + chain_id=w3.eth.chain_id, + gas_limit=100_000, + max_fee_per_gas=w3.eth.gas_price * 2, + max_priority_fee_per_gas=w3.eth.gas_price, + nonce=w3.eth.get_transaction_count(account.address), + calls=( + Call.create(to=ALPHA_USD, data="0x" + data.hex()), + ), + ) + + signed_tx = tx.sign(account.key.hex()) + tx_hash = w3.eth.send_raw_transaction(signed_tx.encode()) + ``` + + ```python [provider.py] + from web3 import Web3 + from eth_account import Account + + w3 = Web3(Web3.HTTPProvider("https://rpc.tempo.xyz")) + account = Account.from_key("0x...") + ``` + + ::: + + + + + + :::code-group + + ```go [main.go] + package main + + import ( + "context" + "log" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/tempoxyz/tempo-go/pkg/signer" + "github.com/tempoxyz/tempo-go/pkg/transaction" + ) + + func buildTransferWithMemoData(to common.Address, amount *big.Int, memo [32]byte) []byte { + data := make([]byte, 100) // 4 + 32 + 32 + 32 + data[0], data[1], data[2], data[3] = 0x76, 0xa8, 0xee, 0x59 + copy(data[16:36], to.Bytes()) + amount.FillBytes(data[36:68]) + copy(data[68:100], memo[:]) + return data } - - function sendPaymentWithMemo( - address recipient, - uint256 amount, - bytes32 invoiceId - ) external { - token.transferWithMemo(recipient, amount, invoiceId); + + func main() { + sgn, _ := signer.NewSigner("0x...") + c := newClient() + ctx := context.Background() + + nonce, _ := c.GetTransactionCount(ctx, sgn.Address().Hex()) + + recipient := common.HexToAddress("0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb") + alphaUSD := common.HexToAddress("0x20c0000000000000000000000000000000000001") + + var memo [32]byte // [!code hl] + copy(memo[:], []byte("INV-12345")) // [!code hl] + + tx := transaction.NewBuilder(big.NewInt(transaction.ChainIdMainnet)). + SetNonce(nonce). + SetGas(100_000). + SetMaxFeePerGas(big.NewInt(25_000_000_000)). + SetMaxPriorityFeePerGas(big.NewInt(1_000_000_000)). + AddCall( // [!code hl] + alphaUSD, // [!code hl] + big.NewInt(0), // [!code hl] + buildTransferWithMemoData(recipient, big.NewInt(100_000_000), memo), // [!code hl] + ). // [!code hl] + Build() + + _ = transaction.SignTransaction(tx, sgn) + serialized, _ := transaction.Serialize(tx, nil) + txHash, _ := c.SendRawTransaction(ctx, serialized) + + log.Printf("Transaction hash: %s", txHash) } -} -``` + ``` + + ```go [provider.go] + // [!include ~/snippets/go-provider.go:setup] + ``` + + ::: + + + + + + ```bash + $ cast send \ + 0x20c0000000000000000000000000000000000001 \ + "transferWithMemo(address,uint256,bytes32)" \ + 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb \ + 100000000 \ + $(cast --format-bytes32-string "INV-12345") \ + --rpc-url $TEMPO_RPC_URL \ + --private-key $PRIVATE_KEY # [!code hl] + ``` + + + + + + ```solidity + import {ITIP20} from "tempo-std/interfaces/ITIP20.sol"; + + contract PaymentSender { + ITIP20 public token; + + function sendPaymentWithMemo( + address recipient, + uint256 amount, + bytes32 invoiceId + ) external { + token.transferWithMemo(recipient, amount, invoiceId); // [!code hl] + } + } + ``` + + + ### Batch payment transactions Send multiple payments in a single transaction using batch transactions: -:::code-group + + + + :::code-group + + ```ts twoslash [example.ts] + // @noErrors + import { encodeFunctionData, parseUnits } from 'viem' + import { Abis } from 'viem/tempo' + import { client } from './viem.config' + + const tokenABI = Abis.tip20 + const recipient1 = '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb' + const recipient2 = '0x70997970C51812dc3A010C7d01b50e0d17dc79C8' + + const calls = [ // [!code hl] + { // [!code hl] + to: '0x20c0000000000000000000000000000000000001', // [!code hl] + data: encodeFunctionData({ // [!code hl] + abi: tokenABI, // [!code hl] + functionName: 'transfer', // [!code hl] + args: [recipient1, parseUnits('100', 6)], // [!code hl] + }), // [!code hl] + }, // [!code hl] + { // [!code hl] + to: '0x20c0000000000000000000000000000000000001', // [!code hl] + data: encodeFunctionData({ // [!code hl] + abi: tokenABI, // [!code hl] + functionName: 'transfer', // [!code hl] + args: [recipient2, parseUnits('50', 6)], // [!code hl] + }), // [!code hl] + }, // [!code hl] + ] // [!code hl] + + const hash = await client.sendTransaction({ calls }) + ``` + + ```ts twoslash [viem.config.ts] + // [!include ~/snippets/viem.config.ts:setup] + ``` + + ::: + + + + + + ```tsx twoslash + // @noErrors + import { useSendTransactionSync } from 'wagmi' + import { encodeFunctionData, parseUnits } from 'viem' + import { Abis } from 'viem/tempo' + + function BatchPayment() { + const { sendTransactionSync, isPending } = useSendTransactionSync() + + const tokenABI = Abis.tip20 + const token = '0x20c0000000000000000000000000000000000001' + + return ( + + ) + } + ``` + + + + + + :::code-group + + ```rust [example.rs] + use alloy::{ + primitives::{address, Address, U256}, + providers::{Provider, ProviderBuilder}, + sol_types::SolCall, + }; + use tempo_alloy::{ + TempoNetwork, contracts::precompiles::ITIP20, primitives::transaction::Call, + rpc::TempoTransactionRequest, + }; + + mod provider; + + #[tokio::main] + async fn main() -> Result<(), Box> { + let provider = provider::get_provider().await?; + + let recipient1 = address!("0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb"); + let recipient2 = address!("0x70997970C51812dc3A010C7d01b50e0d17dc79C8"); + let token_address: Address = address!("0x20c0000000000000000000000000000000000001"); + + let calls = vec![ // [!code hl] + Call { // [!code hl] + to: token_address.into(), // [!code hl] + input: ITIP20::transferCall { // [!code hl] + to: recipient1, // [!code hl] + amount: U256::from(100_000_000), // [!code hl] + } // [!code hl] + .abi_encode() // [!code hl] + .into(), // [!code hl] + value: U256::ZERO, // [!code hl] + }, // [!code hl] + Call { // [!code hl] + to: token_address.into(), // [!code hl] + input: ITIP20::transferCall { // [!code hl] + to: recipient2, // [!code hl] + amount: U256::from(50_000_000), // [!code hl] + } // [!code hl] + .abi_encode() // [!code hl] + .into(), // [!code hl] + value: U256::ZERO, // [!code hl] + }, // [!code hl] + ]; // [!code hl] + + let pending = provider + .send_transaction(TempoTransactionRequest { + calls, + ..Default::default() + }) + .await?; + let tx_hash = pending.tx_hash(); + + println!("Batch transaction sent: {tx_hash:?}"); + + Ok(()) + } + ``` + + ```rust [provider.rs] + // [!include ~/snippets/rust-signer-provider.rs:setup] + ``` + + ::: + + + + + + :::code-group + + ```python [example.py] + from eth_abi import encode + from pytempo import Call, TempoTransaction + from provider import w3, account + + TRANSFER_SELECTOR = bytes.fromhex("a9059cbb") + ALPHA_USD = "0x20c0000000000000000000000000000000000001" + + def build_transfer(to: str, amount: int) -> str: + data = TRANSFER_SELECTOR + encode( + ["address", "uint256"], [to, amount] + ) + return "0x" + data.hex() + + tx = TempoTransaction.create( + chain_id=w3.eth.chain_id, + gas_limit=200_000, + max_fee_per_gas=w3.eth.gas_price * 2, + max_priority_fee_per_gas=w3.eth.gas_price, + nonce=w3.eth.get_transaction_count(account.address), + calls=( # [!code hl] + Call.create( # [!code hl] + to=ALPHA_USD, # [!code hl] + data=build_transfer("0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb", 100_000_000), # [!code hl] + ), # [!code hl] + Call.create( # [!code hl] + to=ALPHA_USD, # [!code hl] + data=build_transfer("0x70997970C51812dc3A010C7d01b50e0d17dc79C8", 50_000_000), # [!code hl] + ), # [!code hl] + ), # [!code hl] + ) + + signed_tx = tx.sign(account.key.hex()) + tx_hash = w3.eth.send_raw_transaction(signed_tx.encode()) + ``` + + ```python [provider.py] + from web3 import Web3 + from eth_account import Account + + w3 = Web3(Web3.HTTPProvider("https://rpc.tempo.xyz")) + account = Account.from_key("0x...") + ``` + + ::: + + + + + + :::code-group + + ```go [main.go] + package main + + import ( + "context" + "log" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/tempoxyz/tempo-go/pkg/signer" + "github.com/tempoxyz/tempo-go/pkg/transaction" + ) + + func buildTransferData(to common.Address, amount *big.Int) []byte { + data := make([]byte, 68) + data[0], data[1], data[2], data[3] = 0xa9, 0x05, 0x9c, 0xbb + copy(data[16:36], to.Bytes()) + amount.FillBytes(data[36:68]) + return data + } -```ts [example.ts] -import { encodeFunctionData, parseUnits } from 'viem' -import { Abis } from 'viem/tempo' -import { client } from './viem.config' - -const tokenABI = Abis.tip20 -const recipient1 = '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb' -const recipient2 = '0x70997970C51812dc3A010C7d01b50e0d17dc79C8' - -const calls = [ - { - to: '0x20c0000000000000000000000000000000000001', // AlphaUSD address - data: encodeFunctionData({ - abi: tokenABI, - functionName: 'transfer', - args: [recipient1, parseUnits('100', 6)], - }), - }, - { - to: '0x20c0000000000000000000000000000000000001', - data: encodeFunctionData({ - abi: tokenABI, - functionName: 'transfer', - args: [recipient2, parseUnits('50', 6)], - }), - }, -] - -const hash = await client.sendTransaction({ calls }) -``` + func main() { + sgn, _ := signer.NewSigner("0x...") + c := newClient() + ctx := context.Background() -```ts [viem.config.ts] filename="viem.config.ts" -// [!include ~/snippets/viem.config.ts:setup] -``` + nonce, _ := c.GetTransactionCount(ctx, sgn.Address().Hex()) -```rust [Rust] -use alloy::{ - primitives::{address, Address, U256}, - providers::{Provider, ProviderBuilder}, - sol_types::SolCall, -}; -use tempo_alloy::{ - TempoNetwork, contracts::precompiles::ITIP20, primitives::transaction::Call, - rpc::TempoTransactionRequest, -}; - -#[tokio::main] -async fn main() -> Result<(), Box> { - let provider = ProviderBuilder::new_with_network::() - .connect(&std::env::var("RPC_URL").expect("No RPC URL set")) - .await?; - - let recipient1 = address!("0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb"); // [!code focus] - let recipient2 = address!("0x70997970C51812dc3A010C7d01b50e0d17dc79C8"); // [!code focus] - let token_address: Address = address!("0x20c0000000000000000000000000000000000001"); // [!code focus] - - let calls = vec![ // [!code focus] - Call { // [!code focus] - to: token_address.into(), // [!code focus] - input: ITIP20::transferCall { // [!code focus] - to: recipient1, // [!code focus] - amount: U256::from(100_000_000), // [!code focus] - } // [!code focus] - .abi_encode() // [!code focus] - .into(), // [!code focus] - value: U256::ZERO, // [!code focus] - }, // [!code focus] - Call { // [!code focus] - to: token_address.into(), // [!code focus] - input: ITIP20::transferCall { // [!code focus] - to: recipient2, // [!code focus] - amount: U256::from(50_000_000), // [!code focus] - } // [!code focus] - .abi_encode() // [!code focus] - .into(), // [!code focus] - value: U256::ZERO, // [!code focus] - }, // [!code focus] - ]; // [!code focus] - - let pending = provider // [!code focus] - .send_transaction(TempoTransactionRequest { // [!code focus] - calls, // [!code focus] - ..Default::default() // [!code focus] - }) // [!code focus] - .await?; // [!code focus] - let tx_hash = pending.tx_hash(); // [!code focus] - - println!("Batch transaction sent: {tx_hash:?}"); // [!code focus] - - Ok(()) -} -``` + recipient1 := common.HexToAddress("0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb") + recipient2 := common.HexToAddress("0x70997970C51812dc3A010C7d01b50e0d17dc79C8") + alphaUSD := common.HexToAddress("0x20c0000000000000000000000000000000000001") -::: + tx := transaction.NewBuilder(big.NewInt(transaction.ChainIdMainnet)). + SetNonce(nonce). + SetGas(200_000). + SetMaxFeePerGas(big.NewInt(25_000_000_000)). + SetMaxPriorityFeePerGas(big.NewInt(1_000_000_000)). + AddCall(alphaUSD, big.NewInt(0), buildTransferData(recipient1, big.NewInt(100_000_000))). // [!code hl] + AddCall(alphaUSD, big.NewInt(0), buildTransferData(recipient2, big.NewInt(50_000_000))). // [!code hl] + Build() + + _ = transaction.SignTransaction(tx, sgn) + serialized, _ := transaction.Serialize(tx, nil) + txHash, _ := c.SendRawTransaction(ctx, serialized) + + log.Printf("Batch transaction sent: %s", txHash) + } + ``` + + ```go [provider.go] + // [!include ~/snippets/go-provider.go:setup] + ``` + + ::: + + + + + + ```bash + $ cast batch-send \ + --rpc-url $TEMPO_RPC_URL \ + --private-key $PRIVATE_KEY \ + --call "0x20c0000000000000000000000000000000000001::transfer(address,uint256):0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb,100000000" \ + --call "0x20c0000000000000000000000000000000000001::transfer(address,uint256):0x70997970C51812dc3A010C7d01b50e0d17dc79C8,50000000" # [!code hl] + ``` + + + + + + ```solidity + import {ITIP20} from "tempo-std/interfaces/ITIP20.sol"; + + contract BatchPaymentSender { + ITIP20 public token; + + struct Payment { + address recipient; + uint256 amount; + } + + function batchPay(Payment[] calldata payments) external { + for (uint256 i = 0; i < payments.length; i++) { + token.transfer(payments[i].recipient, payments[i].amount); // [!code hl] + } + } + } + ``` + + + ### Payment events diff --git a/src/pages/guide/payments/sponsor-user-fees.mdx b/src/pages/guide/payments/sponsor-user-fees.mdx index c3c56618..dcdfe8f8 100644 --- a/src/pages/guide/payments/sponsor-user-fees.mdx +++ b/src/pages/guide/payments/sponsor-user-fees.mdx @@ -2,7 +2,7 @@ description: Enable gasless transactions by sponsoring fees for your users. Set up a fee payer service and improve UX by removing friction from payment flows. --- -import { Cards, Card } from 'vocs' +import { Cards, Card, Tabs, Tab } from 'vocs' import * as Demo from '../../../components/guides/Demo.tsx' import * as Step from '../../../components/guides/steps' import PublicTestnetSponsorTip from '../../../snippets/public-testnet-sponsor-tip.mdx' @@ -52,7 +52,7 @@ Use the `withFeePayer` transport provided by Viem ([link](https://viem.sh/tempo/ Now you can sponsor transactions by passing `feePayer: true` in the transaction parameters. For more details on how to send a transaction, see the [Send a payment](/guide/payments/send-a-payment) guide. :::info -You can also sponsor transactions with a local account. See the [recipe below](/guide/payments/sponsor-user-fees#local-account-sponsorship) for more details. +You can also build your own fee paying service. See [Build your own fee paying service](#build-your-own-fee-paying-service) below. ::: :::code-group @@ -108,7 +108,7 @@ function SendSponsoredPayment() { ::: -## Next Steps +### Next Steps Now that you've implemented fee sponsorship, you can: - Learn more about the [Tempo Transaction](/protocol/transactions/spec-tempo-transaction#fee-payer-signature-details) type and fee payer signature details @@ -117,30 +117,229 @@ Now that you've implemented fee sponsorship, you can: :::: -## Recipes +## Build your own fee paying service -### Local account sponsorship +Instead of using the hosted fee payer service, you can build your own. -The example above uses a fee payer server to sign and sponsor transactions. If you want to sponsor transactions locally, you can easily do so by passing a local account to the `feePayer` parameter. +The sender must indicate sponsorship **before signing**, because the transaction's signing hash differs based on whether it will be sponsored: -```ts twoslash [client.ts] -// @noErrors -import { createClient, http } from 'viem' -import { privateKeyToAccount } from 'viem/accounts' -import { tempoModerato } from 'viem/chains' - -const client = createClient({ - chain: tempoModerato, - transport: http(), -}) - -const { receipt } = await client.token.transferSync({ - amount: parseUnits('10.5', 6), - to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', - token: '0x20c0000000000000000000000000000000000000', - feePayer: privateKeyToAccount('0x...'), // [!code hl] -}) -``` +- **Sponsored transactions**: The `fee_token` field is omitted from the sender's signing payload, and a `0x00` marker is used. This allows the fee payer to choose the fee token. +- **Non-sponsored transactions**: The `fee_token` is included in the sender's signing payload. + +The SDKs handle this automatically. Here's how each SDK manages the dual-signing flow: + + + + + In Viem, pass a local account to the `feePayer` parameter to handle both signatures in your Node.js backend. + + ```ts twoslash + // @noErrors + import { createClient, http, parseUnits } from 'viem' + import { privateKeyToAccount } from 'viem/accounts' + import { tempoModerato } from 'viem/chains' + + const client = createClient({ + chain: tempoModerato, + transport: http(), + }) + + const senderAccount = privateKeyToAccount('0x...') + const feePayerAccount = privateKeyToAccount('0x...') // [!code hl] + + // When feePayer is an Account object, Viem: + // 1. Has the sender sign the transaction (with feeToken skipped) + // 2. Recovers the sender address from their signature + // 3. Has the fee payer sign a separate hash (includes sender + feeToken) + // 4. Serializes the dual-signed transaction + const { receipt } = await client.token.transferSync({ + account: senderAccount, + amount: parseUnits('10.5', 6), + to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', + token: '0x20c0000000000000000000000000000000000000', + feePayer: feePayerAccount, // [!code hl] + }) + ``` + + :::info + Alternatively, use `feePayer: true` with the [`withFeePayer`](https://viem.sh/tempo/transports/withFeePayer) transport to delegate signing to a remote fee payer service. See the [Steps section above](#configure-your-client-to-use-the-fee-payer-service) for setup. + ::: + + + + + + :::code-group + + ```rust [example.rs] + use alloy::{ + primitives::{address, U256}, + providers::Provider, + signers::{SignerSync, local::PrivateKeySigner}, + sol_types::SolCall, + }; + use tempo_alloy::{ + contracts::precompiles::ITIP20, primitives::transaction::Call, + rpc::TempoTransactionRequest, + }; + + mod provider; + + #[tokio::main] + async fn main() -> Result<(), Box> { + let provider = provider::get_provider().await?; + + let tx = TempoTransactionRequest { + calls: vec![Call { + to: address!("0x20c0000000000000000000000000000000000000").into(), + input: ITIP20::transferCall { + to: address!("0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb"), + amount: U256::from(10_500_000), + } + .abi_encode() + .into(), + value: U256::ZERO, + }], + ..Default::default() + }; + + // Step 1: Build the transaction (sender signs, fee_token excluded from hash) + let mut tempo_tx = provider.fill(tx).await?.build_aa()?; + let sender_addr = provider.default_signer_address(); + + // Step 2: Compute the fee payer hash (includes sender address + fee_token) + let fee_payer_hash = tempo_tx.fee_payer_signature_hash(sender_addr); // [!code hl] + + // Step 3: Fee payer counter-signs + let fee_payer: PrivateKeySigner = "0x...".parse()?; // [!code hl] + tempo_tx.fee_payer_signature = Some(fee_payer.sign_hash_sync(&fee_payer_hash)?); // [!code hl] + + // Step 4: Broadcast + let pending = provider.send_transaction(tempo_tx).await?; + + println!("Transaction hash: {:?}", pending.tx_hash()); + + Ok(()) + } + ``` + + ```rust [provider.rs] + // [!include ~/snippets/rust-signer-provider.rs:setup] + ``` + + ::: + + + + + + :::code-group + + ```python [example.py] + from pytempo import TempoTransaction + from pytempo.contracts import TIP20 + from provider import w3, account + + TOKEN = "0x20c0000000000000000000000000000000000000" + + # Step 1: Sender creates and signs (fee_token excluded from signing hash) + tx = TempoTransaction.create( + chain_id=w3.eth.chain_id, + gas_limit=100_000, + max_fee_per_gas=w3.eth.gas_price * 2, + max_priority_fee_per_gas=w3.eth.gas_price, + nonce=w3.eth.get_transaction_count(account.address), + awaiting_fee_payer=True, # [!code hl] + calls=( + TIP20(TOKEN).transfer( + to="0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb", + amount=10_500_000, + ), + ), + ) + + signed_by_sender = tx.sign(account.key.hex()) + + # Step 2: Fee payer counter-signs (includes sender address + fee_token) + FEE_PAYER_KEY = "0x..." + fully_signed = signed_by_sender.sign(FEE_PAYER_KEY, for_fee_payer=True) # [!code hl] + + # Step 3: Broadcast + tx_hash = w3.eth.send_raw_transaction(fully_signed.encode()) + ``` + + ```python [provider.py] + from web3 import Web3 + from eth_account import Account + + w3 = Web3(Web3.HTTPProvider("https://rpc.presto.tempo.xyz")) + account = Account.from_key("0x...") + ``` + + ::: + + + + + + :::code-group + + ```go [main.go] + package main + + import ( + "context" + "log" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/tempoxyz/tempo-go/pkg/signer" + "github.com/tempoxyz/tempo-go/pkg/transaction" + ) + + func main() { + senderSigner, _ := signer.NewSigner("0x...") // sender key + feePayerSigner, _ := signer.NewSigner("0x...") // fee payer key + c := newClient() + ctx := context.Background() + + nonce, _ := c.GetTransactionCount(ctx, senderSigner.Address().Hex()) + + token := common.HexToAddress("0x20c0000000000000000000000000000000000000") + recipient := common.HexToAddress("0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb") + + // Step 1: Sender creates tx and signs (fee_token excluded from hash) + tx := transaction.NewBuilder(big.NewInt(transaction.ChainIdModerato)). + SetNonce(nonce). + SetGas(100_000). + SetMaxFeePerGas(big.NewInt(25_000_000_000)). + SetMaxPriorityFeePerGas(big.NewInt(1_000_000_000)). + SetSponsored(true). // [!code hl] + AddCall(token, big.NewInt(0), buildTransferData(recipient, big.NewInt(10_500_000))). + Build() + + _ = transaction.SignTransaction(tx, senderSigner) + + // Step 2: Fee payer sets fee token and counter-signs + tx.FeeToken = common.HexToAddress("0x20c0000000000000000000000000000000000001") // [!code hl] + _ = transaction.AddFeePayerSignature(tx, feePayerSigner) // [!code hl] + + // Step 3: Broadcast + serialized, _ := transaction.Serialize(tx, nil) + txHash, _ := c.SendRawTransaction(ctx, serialized) + + log.Printf("Sponsored transaction hash: %s", txHash) + } + ``` + + ```go [provider.go] + // [!include ~/snippets/go-provider.go:setup] + ``` + + ::: + + + ## Best practices @@ -156,47 +355,6 @@ const { receipt } = await client.token.transferSync({ - **Balance checks**: Network verifies fee payer has sufficient balance - **Signature validation**: Both signatures must be valid -### Signing Hash Behavior - -When building fee-sponsored transactions, the sender must indicate sponsorship **before signing**. This is because the transaction's signing hash differs based on whether it will be sponsored: - -- **Sponsored transactions**: The `fee_token` field is omitted from the sender's signing payload, and a `0x00` marker is used. This allows the fee payer to choose the fee token. -- **Non-sponsored transactions**: The `fee_token` is included in the sender's signing payload. - -The `withFeePayer` transport and `feePayer: true` parameter handle this automatically. If you're building transactions manually with a local account as fee payer, the Viem SDK handles this for you: - -```ts twoslash -// @noErrors -import { createClient, http, parseUnits } from 'viem' -import { privateKeyToAccount } from 'viem/accounts' -import { tempoModerato } from 'viem/chains' - -const client = createClient({ - chain: tempoModerato, - transport: http(), -}) - -const senderAccount = privateKeyToAccount('0x...') -const feePayerAccount = privateKeyToAccount('0x...') - -// When feePayer is an Account object, Viem: -// 1. Has the sender sign the transaction (with feeToken) -// 2. Recovers the sender address from their signature -// 3. Has the fee payer sign a separate hash (includes sender + feeToken) -// 4. Serializes the dual-signed transaction -const { receipt } = await client.token.transferSync({ - account: senderAccount, - amount: parseUnits('10.5', 6), - to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', - token: '0x20c0000000000000000000000000000000000000', - feePayer: feePayerAccount, // [!code hl] -}) -``` - -:::info -When using `feePayer: true` with the `withFeePayer` transport, the SDK deletes the `feeToken` from the transaction before signing, then sends it to the fee payer service which adds their signature. -::: - ## Learning Resources diff --git a/src/pages/guide/stablecoin-dex/managing-fee-liquidity.mdx b/src/pages/guide/stablecoin-dex/managing-fee-liquidity.mdx index e765be52..70fa56f2 100644 --- a/src/pages/guide/stablecoin-dex/managing-fee-liquidity.mdx +++ b/src/pages/guide/stablecoin-dex/managing-fee-liquidity.mdx @@ -54,7 +54,7 @@ function ManageFeeLiquidity() { ```ts twoslash [wagmi.config.ts] // @noErrors -import { tempoModerato } from 'viem/chains' +import { tempo } from 'viem/chains' import { KeyManager, webAuthn } from 'wagmi/tempo' import { createConfig, http } from 'wagmi' @@ -64,7 +64,7 @@ export const config = createConfig({ keyManager: KeyManager.localStorage(), }), ], - chains: [tempoModerato], + chains: [tempo], multiInjectedProviderDiscovery: false, transports: { [tempo.id]: http(), @@ -122,7 +122,7 @@ function ManageFeeLiquidity() { ```ts twoslash [wagmi.config.ts] // @noErrors -import { tempoModerato } from 'viem/chains' +import { tempo } from 'viem/chains' import { KeyManager, webAuthn } from 'wagmi/tempo' import { createConfig, http } from 'wagmi' @@ -132,7 +132,7 @@ export const config = createConfig({ keyManager: KeyManager.localStorage(), }), ], - chains: [tempoModerato], + chains: [tempo], multiInjectedProviderDiscovery: false, transports: { [tempo.id]: http(), @@ -197,7 +197,7 @@ function ManageFeeLiquidity() { ```ts twoslash [wagmi.config.ts] // @noErrors -import { tempoModerato } from 'viem/chains' +import { tempo } from 'viem/chains' import { KeyManager, webAuthn } from 'wagmi/tempo' import { createConfig, http } from 'wagmi' @@ -207,7 +207,7 @@ export const config = createConfig({ keyManager: KeyManager.localStorage(), }), ], - chains: [tempoModerato], + chains: [tempo], multiInjectedProviderDiscovery: false, transports: { [tempo.id]: http(), @@ -284,7 +284,7 @@ function ManageFeeLiquidity() { ```ts twoslash [wagmi.config.ts] // @noErrors -import { tempoModerato } from 'viem/chains' +import { tempo } from 'viem/chains' import { KeyManager, webAuthn } from 'wagmi/tempo' import { createConfig, http } from 'wagmi' @@ -294,7 +294,7 @@ export const config = createConfig({ keyManager: KeyManager.localStorage(), }), ], - chains: [tempoModerato], + chains: [tempo], multiInjectedProviderDiscovery: false, transports: { [tempo.id]: http(), @@ -354,7 +354,7 @@ function MonitorSwaps() { ```ts twoslash [wagmi.config.ts] // @noErrors -import { tempoModerato } from 'viem/chains' +import { tempo } from 'viem/chains' import { KeyManager, webAuthn } from 'wagmi/tempo' import { createConfig, http } from 'wagmi' @@ -364,7 +364,7 @@ export const config = createConfig({ keyManager: KeyManager.localStorage(), }), ], - chains: [tempoModerato], + chains: [tempo], multiInjectedProviderDiscovery: false, transports: { [tempo.id]: http(), @@ -422,7 +422,7 @@ function RebalancePool() { ```ts twoslash [wagmi.config.ts] // @noErrors -import { tempoModerato } from 'viem/chains' +import { tempo } from 'viem/chains' import { KeyManager, webAuthn } from 'wagmi/tempo' import { createConfig, http } from 'wagmi' @@ -432,7 +432,7 @@ export const config = createConfig({ keyManager: KeyManager.localStorage(), }), ], - chains: [tempoModerato], + chains: [tempo], multiInjectedProviderDiscovery: false, transports: { [tempo.id]: http(), diff --git a/src/pages/guide/tempo-transaction/index.mdx b/src/pages/guide/tempo-transaction/index.mdx index 12103b2d..69a6b0ba 100644 --- a/src/pages/guide/tempo-transaction/index.mdx +++ b/src/pages/guide/tempo-transaction/index.mdx @@ -10,7 +10,7 @@ import TempoTxProperties from '../../../snippets/tempo-tx-properties.mdx' Tempo Transactions are a new [EIP-2718](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2718.md) transaction type, exclusively available on Tempo. :::note[SDKs Support] -Transaction [SDKs](#integration-guides) are available for TypeScript, Rust, Go, and Foundry. +Transaction [SDKs](#integration-guides) are available for TypeScript, Rust, Go, Python, and Foundry. ::: If you're integrating with Tempo, we **strongly recommend** using Tempo Transactions, and not regular Ethereum transactions. Learn more about the benefits below, or follow the guide on issuance [here](/guide/issuance). diff --git a/src/pages/guide/use-accounts/add-funds.mdx b/src/pages/guide/use-accounts/add-funds.mdx index fcced800..f1465435 100644 --- a/src/pages/guide/use-accounts/add-funds.mdx +++ b/src/pages/guide/use-accounts/add-funds.mdx @@ -1,5 +1,5 @@ --- -description: Get test stablecoins on Tempo testnet using the faucet. Request pathUSD, AlphaUSD, BetaUSD, and ThetaUSD tokens for development and testing. +description: Get test stablecoins on Tempo Testnet using the faucet. Request pathUSD, AlphaUSD, BetaUSD, and ThetaUSD tokens for development and testing. mipd: true --- @@ -10,7 +10,7 @@ import { Tabs, Tab } from 'vocs' # Add Funds to Your Balance -Get test tokens to start building on Tempo testnet. +Get test tokens to build on Tempo testnet. diff --git a/src/pages/guide/use-accounts/connect-to-wallets.mdx b/src/pages/guide/use-accounts/connect-to-wallets.mdx index f950a0dd..64425465 100644 --- a/src/pages/guide/use-accounts/connect-to-wallets.mdx +++ b/src/pages/guide/use-accounts/connect-to-wallets.mdx @@ -43,11 +43,11 @@ We can also utilize [wallet connectors](https://wagmi.sh/react/api/connectors) f ```tsx twoslash [config.ts] // @noErrors import { createConfig, http } from 'wagmi' -import { tempoModerato } from 'viem/chains' +import { tempo } from 'viem/chains' import { metaMask } from 'wagmi/connectors' // [!code ++] export const config = createConfig({ - chains: [tempoModerato], + chains: [tempo], connectors: [metaMask()], // [!code ++] multiInjectedProviderDiscovery: true, // [!code ++] transports: { @@ -104,11 +104,11 @@ export function Connect() { ```tsx twoslash [config.ts] filename="config.ts" // @noErrors import { createConfig, http } from 'wagmi' -import { tempoModerato } from 'viem/chains' +import { tempo } from 'viem/chains' import { metaMask } from 'wagmi/connectors' // [!code ++] export const config = createConfig({ - chains: [tempoModerato], + chains: [tempo], connectors: [metaMask()], // [!code ++] multiInjectedProviderDiscovery: true, // [!code ++] transports: { @@ -164,11 +164,11 @@ export function Account() { ```tsx twoslash [config.ts] filename="config.ts" // @noErrors import { createConfig, http } from 'wagmi' -import { tempoModerato } from 'viem/chains' +import { tempo } from 'viem/chains' import { metaMask } from 'wagmi/connectors' // [!code ++] export const config = createConfig({ - chains: [tempoModerato], + chains: [tempo], connectors: [metaMask()], // [!code ++] multiInjectedProviderDiscovery: true, // [!code ++] transports: { @@ -200,7 +200,7 @@ If the wallet is not on the Tempo network, we can display a "Add Tempo" button s ```tsx twoslash [Account.tsx] // @noErrors import { useConnection, useDisconnect, useSwitchChain } from 'wagmi' -import { tempoModerato } from 'viem/chains' // [!code ++] +import { tempo } from 'viem/chains' // [!code ++] export function Account() { const account = useConnection() @@ -241,11 +241,11 @@ export function Account() { ```tsx twoslash [config.ts] filename="config.ts" // @noErrors import { createConfig, http } from 'wagmi' -import { tempoModerato } from 'viem/chains' +import { tempo } from 'viem/chains' import { metaMask } from 'wagmi/connectors' // [!code ++] export const config = createConfig({ - chains: [tempoModerato], + chains: [tempo], connectors: [metaMask()], // [!code ++] multiInjectedProviderDiscovery: true, // [!code ++] transports: { @@ -272,7 +272,7 @@ It is worth noting that some wallets that are included in the above libraries ma ## Add to Wallet Manually -You can add Tempo testnet to a wallet that supports custom networks (e.g. MetaMask) manually. +You can add Tempo to a wallet that supports custom networks (e.g. MetaMask) manually. For example, if you are using MetaMask: @@ -280,13 +280,27 @@ For example, if you are using MetaMask: 2. Click "Add a custom network" 3. Enter the network details: -| **Name** | `Tempo Testnet (Moderato)` | +#### Mainnet + +| **Property** | **Value** | |-------------------|-------| +| **Network Name** | Tempo Mainnet | +| **Currency** | `USD` | +| **Chain ID** | `4217` | +| **HTTP URL** | `https://rpc.tempo.xyz` | +| **WebSocket URL** | `wss://rpc.tempo.xyz` | +| **Block Explorer** | [`https://explore.tempo.xyz`](https://explore.tempo.xyz) | + +#### Testnet + +| **Property** | **Value** | +|-------------------|-------| +| **Network Name** | Tempo Testnet (Moderato) | | **Currency** | `USD` | | **Chain ID** | `42431` | | **HTTP URL** | `https://rpc.moderato.tempo.xyz` | | **WebSocket URL** | `wss://rpc.moderato.tempo.xyz` | -| **Block Explorer** | [`https://explore.tempo.xyz`](https://explore.tempo.xyz) | +| **Block Explorer** | [`https://explore.testnet.tempo.xyz`](https://explore.testnet.tempo.xyz) | The official documentation from MetaMask on this process is also available [here](https://support.metamask.io/configure/networks/how-to-add-a-custom-network-rpc#adding-a-network-manually). diff --git a/src/pages/guide/use-accounts/embed-passkeys.mdx b/src/pages/guide/use-accounts/embed-passkeys.mdx index e7097bc4..c5266c9c 100644 --- a/src/pages/guide/use-accounts/embed-passkeys.mdx +++ b/src/pages/guide/use-accounts/embed-passkeys.mdx @@ -48,11 +48,11 @@ Next, we will need to configure the `webAuthn` connector in our Wagmi config. ```tsx twoslash [config.ts] // @noErrors import { createConfig, http } from 'wagmi' -import { tempoModerato } from 'viem/chains' +import { tempo } from 'viem/chains' import { KeyManager, webAuthn } from 'wagmi/tempo' // [!code ++] export const config = createConfig({ - chains: [tempoModerato], + chains: [tempo], connectors: [webAuthn({ // [!code ++] keyManager: KeyManager.localStorage(), // [!code ++] })], // [!code ++] @@ -130,11 +130,11 @@ export function Example() { ```tsx twoslash [config.ts] filename="config.ts" // @noErrors import { createConfig, http } from 'wagmi' -import { tempoModerato } from 'viem/chains' +import { tempo } from 'viem/chains' import { KeyManager, webAuthn } from 'wagmi/tempo' // [!code ++] export const config = createConfig({ - chains: [tempoModerato], + chains: [tempo], connectors: [webAuthn({ // [!code ++] keyManager: KeyManager.localStorage(), // [!code ++] })], // [!code ++] @@ -208,11 +208,11 @@ export function Example() { ```tsx twoslash [config.ts] filename="config.ts" // @noErrors import { createConfig, http } from 'wagmi' -import { tempoModerato } from 'viem/chains' +import { tempo } from 'viem/chains' import { KeyManager, webAuthn } from 'wagmi/tempo' // [!code ++] export const config = createConfig({ - chains: [tempoModerato], + chains: [tempo], connectors: [webAuthn({ // [!code ++] keyManager: KeyManager.localStorage(), // [!code ++] })], // [!code ++] diff --git a/src/pages/guide/using-tempo-with-ai.mdx b/src/pages/guide/using-tempo-with-ai.mdx new file mode 100644 index 00000000..73f6bcb0 --- /dev/null +++ b/src/pages/guide/using-tempo-with-ai.mdx @@ -0,0 +1,108 @@ +--- +title: Using Tempo with AI +description: Tempo is built with AI-first principles, providing neccessary skills and tools for AI assistants. +--- + +import { Cards, Card, Tabs, Tab } from 'vocs' + +# Using Tempo with AI + +Tempo is built with AI-first principles, providing multiple features to make documentation accessible to LLMs and AI assistants. + +Tempo provides AI-assisted skills and tooling for the following: + + + + + + +## Tempo Wallet + +The [Tempo CLI](/cli) gives agents a wallet with built-in spend controls and service discovery. Agents can use [`tempo wallet`](/cli/wallet) to manage keys and balances, and [`tempo request`](/cli/request) to pay for services on demand. + +Paste this into your agent (Claude Code, Amp, Codex, etc.) to set up and start using a Tempo Wallet: + +:::code-group +```bash [Claude Code] +claude -p "Read https://wallet.tempo.xyz/SKILL.md and set up tempo" +``` +```bash [Amp] +amp -x "Read https://wallet.tempo.xyz/SKILL.md and set up tempo" +``` +```bash [Codex CLI] +codex exec "Read https://wallet.tempo.xyz/SKILL.md and set up tempo" +``` +::: + +## Tempo Docs + +### Skills + +Install [Tempo Docs Skills](https://github.com/tempoxyz/agent-skills) to give AI coding agents (Amp, Claude Code, etc.) access to Tempo documentation, source code via MCP, and examples. + +```bash +npx skills add tempoxyz/docs +``` + +Once installed, the skill is automatically available. The agent will use it when relevant tasks are detected. + +**Examples:** + +``` +How do I create a TIP-20 stablecoin? +``` +``` +Show me how fee sponsorship works in Viem +``` +``` +Search the Tempo source for transaction validation +``` + +### Ask AI Menu + +The docs include a built-in "Ask AI" menu (⌘I / Ctrl+I) that provides: + +- **Open in ChatGPT/Claude**: Opens the current page context in popular AI assistants +- **Copy page for AI**: Copies the page content as Markdown to your clipboard +- **View as Markdown**: Opens the raw Markdown version of the current page +- **Copy MCP URL**: Copies the MCP server URL for AI assistant configuration + +### MCP Server + +The docs include a built-in [Model Context Protocol (MCP)](https://modelcontextprotocol.io) server that allows AI assistants to navigate documentation and source code programmatically. + +:::code-group +```bash [Claude Code] +claude mcp add --transport http tempo https://docs.tempo.xyz/api/mcp +``` +```bash [Codex CLI] +codex mcp add vercel --url https://docs.tempo.xyz/api/mcp +``` +```bash [Amp] +amp mcp add tempo https://docs.tempo.xyz/api/mcp +``` +::: + +#### Connecting AI Assistants + +Configure your AI assistant to connect to the MCP server: + +```json +{ + "mcpServers": { + "tempo-docs": { + "url": "https://docs.tempo.xyz/api/mcp" + } + } +} +``` diff --git a/src/pages/index.mdx b/src/pages/index.mdx index f3d5fad4..686293bb 100644 --- a/src/pages/index.mdx +++ b/src/pages/index.mdx @@ -22,10 +22,16 @@ Whether you're new to stablecoins, ready to start building, or looking for partn title="Learn About Stablecoins" /> + @@ -26,12 +26,23 @@ To connect via CLI, we recommend using [`cast`](https://getfoundry.sh/cast/overv ```bash /dev/null/monitor.sh#L1-11 # Check block height (should be steadily increasing) -cast block-number --rpc-url https://rpc.moderato.tempo.xyz +cast block-number --rpc-url https://rpc.tempo.xyz ``` ## Direct Connection Details -If you're manually connecting to Tempo Testnet, you can use the following details: +### Mainnet + +| **Property** | **Value** | +|-------------------|-------| +| **Network Name** | Tempo Mainnet | +| **Currency** | `USD` | +| **Chain ID** | `4217` | +| **HTTP URL** | `https://rpc.tempo.xyz` | +| **WebSocket URL** | `wss://rpc.tempo.xyz` | +| **Block Explorer** | [`https://explore.tempo.xyz`](https://explore.tempo.xyz) | + +### Testnet | **Property** | **Value** | |-------------------|-------| @@ -40,4 +51,4 @@ If you're manually connecting to Tempo Testnet, you can use the following detail | **Chain ID** | `42431` | | **HTTP URL** | `https://rpc.moderato.tempo.xyz` | | **WebSocket URL** | `wss://rpc.moderato.tempo.xyz` | -| **Block Explorer** | [`https://explore.tempo.xyz`](https://explore.tempo.xyz) | +| **Block Explorer** | [`https://explore.testnet.tempo.xyz`](https://explore.testnet.tempo.xyz) | diff --git a/src/pages/quickstart/developer-tools.mdx b/src/pages/quickstart/developer-tools.mdx index 756cfc23..fb65c517 100644 --- a/src/pages/quickstart/developer-tools.mdx +++ b/src/pages/quickstart/developer-tools.mdx @@ -9,6 +9,24 @@ import { Cards, Card } from 'vocs' Integrating with Tempo is easy by leveraging services provided by our infrastructure partners. These partners take advantage of Tempo transactions, TIP-20 tokens, and more. Visit their documentation for more information on how to get started. + + + +## Bridges + +### Across + +[Across](https://across.to) provides fast, capital-efficient bridging for moving assets to and from Tempo. Across uses an intent-based architecture with optimistic verification, enabling near-instant cross-chain transfers with competitive fees. + +Bridge assets to Tempo through the [Across app](https://app.across.to/) and explore the integration docs at [docs.across.to](https://docs.across.to/). + +### Bungee + +[Bungee](https://bungee.exchange) enables seamless swaps within and between blockchains. Bungee aggregates bridge and DEX liquidity to deliver fast, cost-efficient cross-chain transfers and swaps to and from Tempo — with a simple integration path via widget or API. + +Get started with the [Bungee docs](https://docs.bungee.exchange/) or try the [Bungee app](https://bungee.exchange). + +### Relay + +[Relay](https://relay.link) provides instant cross-chain bridging and transaction execution. Relay enables users and applications to move assets to Tempo from other chains with fast finality and low fees, powered by a network of relayers that fill orders on the destination chain. + +Bridge to Tempo through the [Relay app](https://relay.link) and explore the [Relay docs](https://docs.relay.link/). + +### Squid + +[Squid](https://www.squidrouter.com) enables cross-chain swaps and bridging in a single transaction. Squid's intent-based routing engine aggregates DEXs, bridges, and market makers to find the optimal path for moving assets to and from Tempo — with sub-second execution and zero fees on stablecoin swaps. Developers can integrate cross-chain functionality via a REST API, TypeScript SDK, or drop-in widget. + +Get started with the [Squid docs](https://docs.squidrouter.com/) or try the [bridge app](https://app.squidrouter.com/). + ## Data & Analytics ### Allium @@ -80,6 +124,12 @@ View the Chainlink Data Stream deployed on Tempo [here](https://explore.tempo.xy Developers can explore CCIP, Data Streams, and the full Chainlink platform through the [Chainlink Developer Docs](https://docs.chain.link/). +### CoinGecko + +[CoinGecko](https://www.coingecko.com) provides comprehensive cryptocurrency market data, including prices, trading volume, market capitalization, and token metadata. Developers can use the CoinGecko API to access Tempo token data for building dashboards, portfolio trackers, and analytics tools. + +Get started with the [CoinGecko API](https://docs.coingecko.com/reference/introduction). + ### Goldsky [Goldsky](https://goldsky.com) makes it easy to access real-time Tempo data with minimal maintenance. Goldsky offers two core products for indexing and streaming onchain data: @@ -100,12 +150,35 @@ Range stands out through: Explore Tempo activity in the [Stablecoin Explorer](https://explorer.money/transactions?dn=tempo-testnet&sc=INTRACHAIN&sn=tempo-testnet). +### Redstone + +[Redstone](https://redstone.finance) delivers modular oracle infrastructure with a push and pull model for onchain price feeds. Redstone's architecture minimizes gas costs by delivering data on-demand, making it well-suited for DeFi applications, lending protocols, and stablecoin systems on Tempo. + +Explore the available data feeds and integration guides in the [Redstone docs](https://docs.redstone.finance/). + +### SonarX + +[SonarX](https://www.sonarx.com) delivers standardized, auditable on-chain data built for institutional confidence and enterprise integration. SonarX provides indexed Tempo data from genesis to tip through instant data shares on Snowflake, Databricks, and BigQuery, as well as real-time streaming and REST APIs — all backed by a robust data quality framework and SOC 2 compliance. + +Start a trial at [sonarx.com](https://www.sonarx.com/trial) and explore the [SonarX docs](https://docs.sonarx.com/). + +### SQD + +[SQD](https://sqd.ai) is a decentralized query engine and high-performance indexing toolkit for extracting and transforming on-chain data. With the Squid SDK, developers can build custom indexers for Tempo that are up to 100x faster than direct RPC indexing, with data served through the SQD Network's decentralized data layer. + +Get started with the [SQD docs](https://docs.sqd.ai/) and deploy indexers via [SQD Cloud](https://app.subsquid.io/). + +### Zerion + +[Zerion](https://zerion.io/api) provides an enterprise-grade wallet data API that delivers portfolio balances, transaction history, DeFi positions, PnL tracking, and real-time webhooks — including Tempo — through a single unified interface. Developers can add comprehensive blockchain data to their applications without running any indexing infrastructure. + +Get a free API key from the [Zerion dashboard](https://dashboard.zerion.io/) and explore the [API documentation](https://developers.zerion.io/reference/authentication). ## Block Explorers ### Tempo Explorer -Tempo's official block explorer is available at [explore.tempo.xyz](https://explore.tempo.xyz). View transactions, blocks, accounts, and token activity on the Tempo network. +Tempo's official Mainnet block explorer is available at [explore.tempo.xyz](https://explore.tempo.xyz). View transactions, blocks, accounts, and token activity on the Tempo network. Testnet block explorer is available at [explore.testnet.tempo.xyz](https://explore.testnet.tempo.xyz). For more connection information, see [Connect to the Network](/quickstart/connection-details). ### Tenderly @@ -113,14 +186,16 @@ Tempo's official block explorer is available at [explore.tempo.xyz](https://expl You can enable Tempo in the [Tenderly Dashboard](https://dashboard.tenderly.co/) to use its tracing, alerts, and debugging tools with no infrastructure to manage. -## Embedded Wallets +## Wallets + +### Embedded -### Blockradar +#### Blockradar [Blockradar](https://blockradar.co) provides non-custodial wallet infrastructure purpose-built for fintechs running stablecoin payments. The platform focuses on real financial use cases, from merchant settlement to cross-border payouts, with tools designed for payments, compliance, treasury operations, and multi-chain liquidity. Explore the full platform in the [Blockradar Docs](https://docs.blockradar.co/). **Wallet and Payment Operations:** Through one unified API, teams can issue wallets for users, merchants, or treasury; accept fiat inflows through virtual accounts; enable gasless stablecoin transactions; apply AML checks automatically; consolidate balances through configurable sweeps; and handle cross-chain movement using swap and bridge. Fintechs can start building immediately from our API or [Blockradar Dashboard](https://dashboard.blockradar.co/). For advanced flows or high-volume programs, fintechs can [book a demo](https://www.blockradar.co/contact) to walk through production architectures. -### Crossmint +#### Crossmint [Crossmint](https://www.crossmint.com) is an all-in-one platform, with unified APIs for [wallets](https://docs.crossmint.com/wallets/), [stablecoin orchestration](https://docs.crossmint.com/stablecoin-orchestration/), [checkout flows](https://docs.crossmint.com/payments), and [tokenization](https://docs.crossmint.com/minting), giving developers a single interface for everything from payments to asset management on Tempo. @@ -128,19 +203,19 @@ Crossmint delivers a gasless, seed-phrase-free UX backed by bank-grade security Set up a project in the [Crossmint console](https://crossmint.com/console) and explore the [Solution Guide](https://docs.crossmint.com/solutions/overview#fintech) tailored for payment use-cases. -### Dynamic +#### Dynamic [Dynamic](https://dynamic.xyz) combines authentication, smart wallets, and key management into a flexible SDK for Tempo developers. Teams can onboard users with familiar login methods and provision Tempo-compatible wallets through Dynamic's secure infrastructure. Enable Tempo testnet in the [Dynamic dashboard](https://app.dynamic.xyz/dashboard/chains-and-networks), and create an account [here](https://www.dynamic.xyz/get-started) to start integrating Dynamic into your app. -### Para +#### Para [Para](https://getpara.com) is a comprehensive wallet and authentication suite for fintech and crypto applications. It provides flexible login methods, secure MPC-backed wallets, fast authentication, and infrastructure for automating onchain activity. Para is adding Tempo chain support so developers can easily build Tempo-enabled wallets and payment flows. Get started by signing up through the [Para Dev Portal](https://developer.getpara.com/) and following the quickstart in the [Para docs](https://docs.getpara.com/v2/introduction/welcome). -### Privy +#### Privy [Privy](https://www.privy.io/) builds secure key management and embedded wallets so any developer can easily build secure, scalable wallets into their app. Easily spin up self-custodial wallets for users, manage your treasury wallets and more. @@ -161,10 +236,24 @@ Turnkey also supports sponsor-style workflows, enabling gasless or subsidized tr [Create your Turnkey account](https://app.turnkey.com/dashboard) and follow the [Turnkey Embedded Wallet Kit guide](https://docs.turnkey.com/sdks/react/getting-started) to integrate embedded wallets into your Tempo app. :::tip -Turnkey has a [`with-tempo` example](https://github.com/tkhq/sdk/tree/main/examples/with-tempo) in their SDK to get you started quickly. +Turnkey has a [`with-tempo`](https://github.com/tkhq/sdk/tree/main/examples/with-tempo) example in their SDK to get you started quickly. ::: -### Utila +### Custodial & Institutional + +#### BitGo + +[BitGo](https://www.bitgo.com) provides institutional-grade custody, trading, and wallet infrastructure. BitGo supports Tempo with both custodial and self-custody wallet solutions, enabling enterprises to securely store, manage, and transact with Tempo-based assets under robust security and compliance controls. BitGo is a qualified custodian in the United States and globally [licensed and regulated](https://www.bitgo.com/company/licenses/). + +Get started through the [BitGo platform](https://www.bitgo.com) or explore their [developer docs](https://developers.bitgo.com/). + +#### Fireblocks + +[Fireblocks](https://www.fireblocks.com) provides enterprise-grade digital asset infrastructure for custody, transfers, and tokenization. Tempo is supported through Fireblocks' MPC-based signing, policy engine, and transaction API — enabling institutions to securely manage Tempo assets with configurable approval workflows and direct network connectivity. + +Access Tempo through the [Fireblocks console](https://console.fireblocks.io/) and explore the [Fireblocks Developer docs](https://developers.fireblocks.com/). + +#### Utila [Utila](https://utila.io) provides secure MPC wallet infrastructure and asset-management tooling for teams building with stablecoins and digital assets. Developers can use Utila to manage Tempo-based payments and treasury operations across multiple wallets and blockchains, all within a single policy-driven platform. Utila's MPC technology reduces counterparty risk, while its configurable approval engine gives teams granular control over how funds are moved. @@ -172,6 +261,12 @@ Turnkey has a [`with-tempo` example](https://github.com/tkhq/sdk/tree/main/examp ## Smart Contract Libraries +### Pimlico + +[Pimlico](https://www.pimlico.io) provides smart account infrastructure for Tempo, including ERC-4337 bundlers and paymasters. With Pimlico, developers can sponsor gas fees, accept ERC-20 tokens for gas, and relay smart account transactions — enabling seamless, gasless onchain experiences for end users. + +Get started on the [Pimlico dashboard](https://dashboard.pimlico.io/) and explore the [Pimlico docs](https://docs.pimlico.io/). + ### Safe *(coming soon)* [Safe](https://safe.global) provides a modular smart account framework used across leading Web3 applications and institutions. With Safe, developers can build Tempo applications that take advantage of multi-sig controls, programmable permissions, session keys, and automated transaction policies. @@ -222,6 +317,12 @@ Get started by visiting the [Tempo chain page](https://drpc.org/chainlist/tempo- Get started on the [Tempo Chain Page](https://www.quicknode.com/chains/tempo) and follow the [QuickStart guide](https://www.quicknode.com/docs/tempo) to create your Tempo RPC endpoint. +### Validation Cloud + +[Validation Cloud](https://www.validationcloud.io/tempo) provides institutional-grade, full-archive RPC nodes and validator infrastructure for Tempo. With SOC 2 Type II compliance, high performance, and low latency, Validation Cloud is purpose-built for powering real-world payments and stablecoin use cases at scale. + +Get started at [validationcloud.io/tempo](https://www.validationcloud.io/tempo). + ## Security & Compliance ### Blockaid @@ -236,7 +337,19 @@ Learn how Blockaid's transaction scanning improves security by visiting their [o Discover how Hexagate supports Tempo [here](https://www.hexagate.com), or request a dedicated walkthrough from the Chainalysis team through their [demo form](https://www.hexagate.com/request-demo). -## Issuance +### Elliptic + +[Elliptic](https://www.elliptic.co) provides blockchain analytics and compliance solutions for detecting and preventing financial crime. Elliptic supports Tempo with transaction screening, wallet risk scoring, and regulatory compliance tools — helping platforms meet AML obligations while operating on the Tempo network. + +Learn more about Elliptic's compliance solutions at [elliptic.co](https://www.elliptic.co) or explore their [developer docs](https://docs.elliptic.co/). + +### TRM Labs + +[TRM Labs](https://www.trmlabs.com) delivers blockchain intelligence and compliance infrastructure for detecting fraud, money laundering, and financial crime. TRM supports Tempo with transaction monitoring, wallet screening, and risk assessment tools that help platforms operate safely and meet regulatory requirements. + +Get started at [trmlabs.com](https://www.trmlabs.com) or explore their [documentation](https://docs.trmlabs.com/). + +## Orchestration ### Brale [Brale](https://brale.xyz) provides infrastructure for issuing, transferring, and managing stablecoins across chains. Developers can create new stablecoins or work with existing issued assets using Brale's APIs to support on- and off-ramps, payouts, and cross-ecosystem stablecoin movement. @@ -252,3 +365,9 @@ Brale exposes two complementary APIs: These APIs support common stablecoin workflows such as minting, redemption, swaps, payouts, and treasury operations, making Brale suitable for fintechs, exchanges, and payment platforms building on Tempo. Get started by creating an account [here](https://app.brale.xyz/buy/signup/). + +### Bridge + +[Bridge](https://www.bridge.xyz) (a Stripe Company) provides stablecoin orchestration infrastructure for moving money between fiat and crypto rails. Bridge supports Tempo with APIs for issuance, wallets, and cross-border stablecoin transfers — enabling fintechs and platforms to build payment flows that span traditional and onchain systems. + +Get started with [Bridge's Tempo Integration Guide](https://apidocs.bridge.xyz/get-started/guides/move-money/tempo-integration-guide#tempo-integration-guide). diff --git a/src/pages/quickstart/integrate-tempo.mdx b/src/pages/quickstart/integrate-tempo.mdx index 637f4d92..359bbaa6 100644 --- a/src/pages/quickstart/integrate-tempo.mdx +++ b/src/pages/quickstart/integrate-tempo.mdx @@ -1,5 +1,5 @@ --- -description: Start building on Tempo Testnet. Connect to the network, explore SDKs, and follow guides for accounts, payments, and stablecoin issuance. +description: Build on Tempo Testnet. Connect to the network, explore SDKs, and follow guides for accounts, payments, and stablecoin issuance. --- import * as Demo from '../../components/guides/Demo.tsx' @@ -7,7 +7,7 @@ import { ConnectWallet } from '../../components/ConnectWallet.tsx' import TempoTxProperties from '../../snippets/tempo-tx-properties.mdx' import { Cards, Card } from 'vocs' -# Integrate Tempo Testnet +# Integrate Tempo Tempo is fully compatible with the Ethereum Virtual Machine (EVM), targeting the **Osaka** EVM hard fork. So, everything you'd expect to work with Ethereum works on Tempo, with only a few exceptions which we detail on the [EVM Differences](/quickstart/evm-compatibility) page. diff --git a/src/pages/quickstart/tokenlist.mdx b/src/pages/quickstart/tokenlist.mdx index 1ec420fb..99daa1b3 100644 --- a/src/pages/quickstart/tokenlist.mdx +++ b/src/pages/quickstart/tokenlist.mdx @@ -9,7 +9,7 @@ import { TokenListDemo } from '../../components/TokenList.tsx' A [Uniswap Token Lists](https://tokenlists.org)-compatible API for token metadata and icons on Tempo. -As an example, here's Tempo Testnet's tokenlist, fetched from [tokenlist.tempo.xyz/list/42431](https://tokenlist.tempo.xyz/list/42431): +As an example, here's Tempo's tokenlist, fetched from [tokenlist.tempo.xyz/list/4217](https://tokenlist.tempo.xyz/list/4217): @@ -17,10 +17,15 @@ As an example, here's Tempo Testnet's tokenlist, fetched from [tokenlist.tempo.x | Endpoint | Description | |----------|-------------| -[`/list/{chain_id}`](https://tokenlist.tempo.xyz/list/42431) | Token list for a chain | -[`/asset/{chain_id}/{id}`](https://tokenlist.tempo.xyz/asset/42431/pathUSD) | Get a single token by symbol or address​ -[`/icon/{chain_id}`](https://tokenlist.tempo.xyz/icon/42431) | Chain icon (SVG) | -[`/icon/{chain_id}/{address}`](https://tokenlist.tempo.xyz/icon/42431/0x20c0000000000000000000000000000000000000) | Token icon (SVG) | +[`/list/{chain_id}`](https://tokenlist.tempo.xyz/list/4217) | Token list for a chain | +[`/asset/{chain_id}/{id}`](https://tokenlist.tempo.xyz/asset/4217/pathUSD) | Get a single token by symbol or address​ +[`/icon/{chain_id}`](https://tokenlist.tempo.xyz/icon/4217) | Chain icon (SVG) | +[`/icon/{chain_id}/{address}`](https://tokenlist.tempo.xyz/icon/4217/0x20c0000000000000000000000000000000000000) | Token icon (SVG) | + +| Chain | `chain_id` | +|-------|------------| +| Mainnet | `4217` | +| Testnet (Moderato) | `42431` | ## Adding a New Token @@ -32,7 +37,7 @@ As an example, here's Tempo Testnet's tokenlist, fetched from [tokenlist.tempo.x "name": "piUSD", "symbol": "PiUSD", "decimals": 6, - "chainId": 42431, + "chainId": 4217, "address": "0x..." } ``` diff --git a/src/pages/sdk/foundry/index.mdx b/src/pages/sdk/foundry/index.mdx index 885e99df..58ebe4bc 100644 --- a/src/pages/sdk/foundry/index.mdx +++ b/src/pages/sdk/foundry/index.mdx @@ -12,13 +12,13 @@ For general information about Foundry, see the [Foundry documentation](https://g ## Get started with Foundry -### Install using `foundryup` - Tempo's Foundry fork is installed through the standard upstream `foundryup` using the `-n tempo` flag, no separate installer is required. -Getting started is very easy: +::::steps + +## Install `foundryup` -Install regular `foundryup`: +If you don't have `foundryup` installed yet: ```bash curl -L https://foundry.paradigm.xyz | bash @@ -30,21 +30,22 @@ Or if you already have `foundryup` installed: foundryup --update ``` -Next, run: +## Install Tempo's Foundry fork ```bash foundryup -n tempo ``` -It will automatically install the latest `nightly` release of all precompiled binaries: [`forge`](https://getfoundry.sh/forge/overview#forge), [`cast`](https://getfoundry.sh/cast/overview#cast), [`anvil`](https://getfoundry.sh/anvil/overview#anvil), and [`chisel`](https://getfoundry.sh/chisel/overview#chisel). +This will automatically install the latest `nightly` release of all precompiled binaries: [`forge`](https://getfoundry.sh/forge/overview#forge), [`cast`](https://getfoundry.sh/cast/overview#cast), [`anvil`](https://getfoundry.sh/anvil/overview#anvil), and [`chisel`](https://getfoundry.sh/chisel/overview#chisel). +:::tip To install a specific version, replace `` with the desired release tag: - ```bash foundryup -n tempo -i ``` +::: -### Verify Installation +## Verify Installation ```bash forge -V @@ -56,7 +57,7 @@ You should see version information include `-tempo`, indicating you are using th # forge -tempo ( ) ``` -### Create a new Foundry project +## Create a new Foundry project Initialize a new project with Tempo support: @@ -66,6 +67,8 @@ forge init -n tempo my-project && cd my-project Each new project is configured for Tempo out of the box, with [`tempo-std`](https://github.com/tempoxyz/tempo-std), the Tempo standard library installed, containing helpers for Tempo's protocol-level features. +:::: + ## Use Foundry for your workflows All standard Foundry commands are supported out of the box. @@ -87,7 +90,7 @@ forge script script/Mail.s.sol ```bash # Set environment variables -export TEMPO_RPC_URL=https://rpc.moderato.tempo.xyz +export TEMPO_RPC_URL=https://rpc.tempo.xyz export VERIFIER_URL=https://contracts.tempo.xyz # Optional: create a new keypair and request some testnet tokens from the faucet. diff --git a/src/pages/sdk/go/index.mdx b/src/pages/sdk/go/index.mdx index 5986104c..c98fdf77 100644 --- a/src/pages/sdk/go/index.mdx +++ b/src/pages/sdk/go/index.mdx @@ -16,7 +16,7 @@ The Tempo Go SDK can be used to perform common operations with the chain, such a To install the Tempo Go SDK: ```bash [go] -go get github.com/tempoxyz/tempo-go@v0.1.0 +go get github.com/tempoxyz/tempo-go@v0.3.0 ``` :::tip @@ -38,7 +38,7 @@ import ( ) func main() { - c := client.New("https://rpc.testnet.tempo.xyz") + c := client.New("https://rpc.tempo.xyz") ctx := context.Background() blockNum, _ := c.GetBlockNumber(ctx) @@ -49,7 +49,7 @@ func main() { For authenticated RPC endpoints: ```go [main.go] -c := client.New("https://rpc.testnet.tempo.xyz", +c := client.New("https://rpc.tempo.xyz", client.WithAuth("username", "password"), ) ``` @@ -96,7 +96,7 @@ import ( ) func main() { - c := client.New("https://rpc.testnet.tempo.xyz") + c := client.New("https://rpc.tempo.xyz") s, _ := signer.NewSigner("0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80") ctx := context.Background() @@ -105,7 +105,7 @@ func main() { recipient := common.HexToAddress("0x70997970C51812dc3A010C7d01b50e0d17dc79C8") // [!code hl:10] - tx := transaction.NewBuilder(big.NewInt(42431)). // Tempo testnet + tx := transaction.NewBuilder(big.NewInt(4217)). // Tempo mainnet SetNonce(nonce). SetGas(100000). SetMaxFeePerGas(big.NewInt(20000000000)). // 20 gwei base fee @@ -152,7 +152,7 @@ amount := big.NewInt(100_000_000) // 100 tokens (6 decimals) transferData, _ := erc20ABI.Pack("transfer", recipient, amount) -tx := transaction.NewBuilder(big.NewInt(42429)). +tx := transaction.NewBuilder(big.NewInt(4217)). SetNonce(nonce). SetGas(100000). SetMaxFeePerGas(big.NewInt(10000000000)). @@ -175,7 +175,7 @@ copy(memo[:], "INV-12345") memoData, _ := tip20ABI.Pack("transferWithMemo", recipient, amount, memo) -tx := transaction.NewBuilder(big.NewInt(42429)). +tx := transaction.NewBuilder(big.NewInt(4217)). SetNonce(nonce). SetGas(100000). SetMaxFeePerGas(big.NewInt(10000000000)). @@ -189,7 +189,7 @@ tx := transaction.NewBuilder(big.NewInt(42429)). Execute multiple operations atomically in a single transaction: ```go [batch.go] -tx := transaction.NewBuilder(big.NewInt(42429)). +tx := transaction.NewBuilder(big.NewInt(4217)). SetNonce(nonce). SetGas(200000). SetMaxFeePerGas(big.NewInt(10000000000)). @@ -207,7 +207,7 @@ transaction.SignTransaction(tx, s) Send multiple transactions concurrently using different nonce keys: ```go [parallel.go] -tx1 := transaction.NewBuilder(big.NewInt(42429)). +tx1 := transaction.NewBuilder(big.NewInt(4217)). SetNonceKey(big.NewInt(1)). // Sequence A // [!code hl] SetNonce(0). SetGas(100000). @@ -216,7 +216,7 @@ tx1 := transaction.NewBuilder(big.NewInt(42429)). AddCall(recipient1, big.NewInt(0), data1). Build() -tx2 := transaction.NewBuilder(big.NewInt(42429)). +tx2 := transaction.NewBuilder(big.NewInt(4217)). SetNonceKey(big.NewInt(2)). // Sequence B (parallel) // [!code hl] SetNonce(0). SetGas(100000). @@ -238,7 +238,7 @@ go func() { c.SendRawTransaction(ctx, serialize(tx2)) }() Have another account pay for transaction fees: ```go [feepayer.go] -tx := transaction.NewBuilder(big.NewInt(42429)). +tx := transaction.NewBuilder(big.NewInt(4217)). SetNonce(nonce). SetGas(100000). SetMaxFeePerGas(big.NewInt(10000000000)). @@ -258,7 +258,7 @@ Set a time window during which the transaction is valid: ```go [validity.go] now := time.Now() -tx := transaction.NewBuilder(big.NewInt(42429)). +tx := transaction.NewBuilder(big.NewInt(4217)). SetNonce(nonce). SetGas(100000). SetMaxFeePerGas(big.NewInt(10000000000)). diff --git a/src/pages/sdk/python/index.mdx b/src/pages/sdk/python/index.mdx new file mode 100644 index 00000000..67a359f2 --- /dev/null +++ b/src/pages/sdk/python/index.mdx @@ -0,0 +1,223 @@ +--- +description: Build blockchain apps with the Tempo Python SDK. Send transactions, batch calls, and handle fee sponsorship using web3.py. +--- + +# Python + +Tempo distributes a Python SDK as a [web3.py](https://web3py.readthedocs.io/) extension. The SDK adds native support for Tempo transactions, including call batching, fee sponsorship, and access key management. + +The Tempo Python SDK can be used to perform common operations with the chain, such as: sending Tempo transactions, batching multiple calls, fee sponsorship, and more. + +::::steps + +## Install + +To install the Tempo Python SDK: + +```bash [pip] +pip install pytempo +``` + +:::tip +The SDK requires Python 3.9 or higher and web3.py 7.0+. +::: + +## Create a Client + +To interact with Tempo, create a web3.py client connected to a Tempo node: + +```python [main.py] +from web3 import Web3 + +w3 = Web3(Web3.HTTPProvider("https://rpc.tempo.xyz")) # [!code hl] + +block_number = w3.eth.block_number +print(f"Connected to Tempo at block {block_number}") +``` + +## Send a Transaction + +Build and send a transaction using the `TempoTransaction` class: + +```python [main.py] +import os +from web3 import Web3 +from pytempo import Call, TempoTransaction # [!code hl] + +w3 = Web3(Web3.HTTPProvider("https://rpc.tempo.xyz")) +private_key = os.environ["PRIVATE_KEY"] +account = w3.eth.account.from_key(private_key) + +# [!code hl:12] +tx = TempoTransaction.create( + chain_id=w3.eth.chain_id, + gas_limit=100_000, + max_fee_per_gas=w3.eth.gas_price * 2, + max_priority_fee_per_gas=w3.eth.gas_price, + nonce=w3.eth.get_transaction_count(account.address), + calls=( + Call.create(to="0x70997970C51812dc3A010C7d01b50e0d17dc79C8"), + ), +) + +signed_tx = tx.sign(private_key) # [!code hl] +tx_hash = w3.eth.send_raw_transaction(signed_tx.encode()) # [!code hl] +receipt = w3.eth.wait_for_transaction_receipt(tx_hash) +print(f"Transaction hash: {tx_hash.hex()}") +``` + +:::: + +## Examples + +### Token Transfer + +Send a TIP-20 token transfer using pytempo's typed contract helpers: + +```python [transfer.py] +from pytempo import TempoTransaction +from pytempo.contracts import TIP20, ALPHA_USD + +tx = TempoTransaction.create( + chain_id=4217, + gas_limit=100_000, + max_fee_per_gas=10_000_000_000, + max_priority_fee_per_gas=1_000_000_000, + nonce=w3.eth.get_transaction_count(account.address), + calls=( + TIP20(ALPHA_USD).transfer( # [!code hl] + to="0x70997970C51812dc3A010C7d01b50e0d17dc79C8", # [!code hl] + amount=100_000_000, # 100 tokens (6 decimals) # [!code hl] + ), # [!code hl] + ), +) +``` + +### Pay Fees in a Stablecoin + +Use a TIP-20 token to pay for transaction fees instead of the native token: + +```python [fee_token.py] +from pytempo import TempoTransaction, Call +from pytempo.contracts import ALPHA_USD + +tx = TempoTransaction.create( + chain_id=4217, + gas_limit=100_000, + max_fee_per_gas=10_000_000_000, + max_priority_fee_per_gas=1_000_000_000, + nonce=w3.eth.get_transaction_count(account.address), + fee_token=ALPHA_USD, # [!code hl] + calls=( + Call.create(to="0x70997970C51812dc3A010C7d01b50e0d17dc79C8"), + ), +) +``` + +### Batch Multiple Calls + +Execute multiple operations atomically in a single transaction: + +```python [batch.py] +from pytempo import TempoTransaction +from pytempo.contracts import TIP20, ALPHA_USD + +token = TIP20(ALPHA_USD) + +tx = TempoTransaction.create( + chain_id=4217, + gas_limit=300_000, + max_fee_per_gas=10_000_000_000, + max_priority_fee_per_gas=1_000_000_000, + nonce=w3.eth.get_transaction_count(account.address), + calls=( + token.transfer(to="0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb", amount=100_000_000), # [!code hl] + token.transfer(to="0x70997970C51812dc3A010C7d01b50e0d17dc79C8", amount=50_000_000), # [!code hl] + token.transfer(to="0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC", amount=25_000_000), # [!code hl] + ), +) + +signed_tx = tx.sign(private_key) +tx_hash = w3.eth.send_raw_transaction(signed_tx.encode()) +``` + +### Parallel Transactions (2D Nonces) + +Send multiple transactions concurrently using different nonce keys: + +```python [parallel.py] +tx1 = TempoTransaction.create( + chain_id=4217, + gas_limit=100_000, + max_fee_per_gas=10_000_000_000, + max_priority_fee_per_gas=1_000_000_000, + nonce_key=1, # Sequence A // [!code hl] + nonce=0, + calls=(Call.create(to=recipient1, data=data1),), +) + +tx2 = TempoTransaction.create( + chain_id=4217, + gas_limit=100_000, + max_fee_per_gas=10_000_000_000, + max_priority_fee_per_gas=1_000_000_000, + nonce_key=2, # Sequence B (parallel) // [!code hl] + nonce=0, + calls=(Call.create(to=recipient2, data=data2),), +) + +# Sign and send both in parallel +signed_tx1 = tx1.sign(private_key) +signed_tx2 = tx2.sign(private_key) +w3.eth.send_raw_transaction(signed_tx1.encode()) +w3.eth.send_raw_transaction(signed_tx2.encode()) +``` + +### Fee Sponsorship + +Have another account pay for transaction fees: + +```python [fee_payer.py] +# User creates and signs a transaction marked for fee sponsorship +tx = TempoTransaction.create( + chain_id=4217, + gas_limit=100_000, + max_fee_per_gas=10_000_000_000, + max_priority_fee_per_gas=1_000_000_000, + awaiting_fee_payer=True, # [!code hl] + calls=(Call.create(to=recipient, data=data),), +) + +signed_by_user = tx.sign(user_private_key) +final_tx = signed_by_user.sign(fee_payer_private_key, for_fee_payer=True) # [!code hl] +w3.eth.send_raw_transaction(final_tx.encode()) +``` + +### Transaction Validity Window + +Set a time window during which the transaction is valid: + +```python [validity.py] +import time + +now = int(time.time()) + +tx = TempoTransaction.create( + chain_id=4217, + gas_limit=100_000, + max_fee_per_gas=10_000_000_000, + max_priority_fee_per_gas=1_000_000_000, + nonce=nonce, + valid_after=now, # [!code hl] + valid_before=now + 3600, # 1 hour from now // [!code hl] + calls=(Call.create(to=recipient, data=data),), +) +``` + +## Next Steps + +After setting up the Python SDK, you can: + +- Follow a guide on how to [use accounts](/guide/use-accounts), [make payments](/guide/payments), [issue stablecoins](/guide/issuance), [exchange stablecoins](/guide/stablecoin-dex), and [more](/). +- View the [source on GitHub](https://github.com/tempoxyz/pytempo) +- View the [package on PyPI](https://pypi.org/project/pytempo/) diff --git a/src/pages/sdk/rust/index.mdx b/src/pages/sdk/rust/index.mdx index 64cfcc69..689b162e 100644 --- a/src/pages/sdk/rust/index.mdx +++ b/src/pages/sdk/rust/index.mdx @@ -16,7 +16,7 @@ To install the Tempo extension, you will need to install [Alloy](https://alloy.r ```bash [cargo] cargo add alloy tokio -cargo add tempo-alloy --git https://github.com/tempoxyz/tempo +cargo add tempo-alloy --git https://github.com/tempoxyz/tempo --tag v1.4.2 ``` :::tip diff --git a/src/pages/sdk/typescript/server/handler.compose.mdx b/src/pages/sdk/typescript/server/handler.compose.mdx index f72682ac..2f6b2798 100644 --- a/src/pages/sdk/typescript/server/handler.compose.mdx +++ b/src/pages/sdk/typescript/server/handler.compose.mdx @@ -11,7 +11,7 @@ Composes multiple handlers into a single handler. This is useful when you want t ```ts twoslash [server.ts] // @noErrors import { Handler, Kv } from 'tempo.ts/server' -import { tempoModerato } from 'viem/chains' +import { tempo } from 'viem/chains' import { http } from 'viem' import { privateKeyToAccount } from 'viem/accounts' @@ -19,7 +19,7 @@ const handler = Handler.compose([ // Create a fee payer handler Handler.feePayer({ account: privateKeyToAccount('0x...'), - chain: tempoModerato.extend({ + chain: tempo.extend({ feeToken: '0x20c0...0001' }), transport: http(), diff --git a/src/pages/sdk/typescript/server/handler.feePayer.mdx b/src/pages/sdk/typescript/server/handler.feePayer.mdx index 6344d28c..aa897add 100644 --- a/src/pages/sdk/typescript/server/handler.feePayer.mdx +++ b/src/pages/sdk/typescript/server/handler.feePayer.mdx @@ -14,13 +14,13 @@ This enables you to subsidize gas costs for your users by signing transactions w ```ts twoslash [server.ts] // @noErrors import { Handler } from 'tempo.ts/server' -import { tempoModerato } from 'viem/chains' +import { tempo } from 'viem/chains' import { http } from 'viem' import { privateKeyToAccount } from 'viem/accounts' const handler = Handler.feePayer({ account: privateKeyToAccount('0x...'), - chain: tempoModerato.extend({ feeToken: '0x20c0...0001' }), + chain: tempo.extend({ feeToken: '0x20c0...0001' }), path: '/fee-payer', transport: http(), }) @@ -29,11 +29,11 @@ const handler = Handler.feePayer({ ```ts twoslash [example.client.ts] // @noErrors import { createClient, http, walletActions } from 'viem' -import { tempoModerato } from 'viem/chains' +import { tempo } from 'viem/chains' import { tempoActions, withFeePayer } from 'viem/tempo' const client = createClient({ - chain: tempoModerato, + chain: tempo, transport: withFeePayer( http(), // Default transport http('http://localhost:3000/fee-payer'), // Fee payer transport (your server) diff --git a/src/pages/sdk/typescript/server/handler.keyManager.mdx b/src/pages/sdk/typescript/server/handler.keyManager.mdx index f7104ce0..c5f04c6c 100644 --- a/src/pages/sdk/typescript/server/handler.keyManager.mdx +++ b/src/pages/sdk/typescript/server/handler.keyManager.mdx @@ -23,7 +23,7 @@ const handler = Handler.keyManager({ ```ts twoslash [wagmi.config.ts] // @noErrors -import { tempoModerato } from 'viem/chains' +import { tempo } from 'viem/chains' import { KeyManager, webAuthn } from 'wagmi/tempo' import { createConfig, http } from 'wagmi' @@ -34,7 +34,7 @@ export const config = createConfig({ rpId: 'example.com', }), ], - chains: [tempoModerato], + chains: [tempo], transports: { [tempo.id]: http(), }, diff --git a/src/pages/wallet/index.mdx b/src/pages/wallet/index.mdx new file mode 100644 index 00000000..1171c808 --- /dev/null +++ b/src/pages/wallet/index.mdx @@ -0,0 +1,67 @@ +--- +title: Tempo CLI +description: A terminal client for Tempo wallet management, service discovery, and paid HTTP requests via the Machine Payments Protocol. +--- + +# Tempo CLI + +The `tempo` CLI is a terminal client for Tempo. It has two command families: + +- **`tempo wallet`**: manages your onchain identity: authentication, key management, balances, funding, and transfers. It also provides a service directory for discovering [MPP](https://mpp.dev)-compatible endpoints and their request schemas. +- **`tempo request`**: a curl-like HTTP client that handles [Machine Payments Protocol](https://mpp.dev) negotiation automatically. It sends your request, intercepts `402 Payment Required` challenges, signs and submits the payment onchain, then retries with the credential, all in a single command. + +Together they let you browse paid APIs, preview costs, and execute paid requests from a terminal, script, or AI agent without writing any integration code. + +## Install + +```bash +curl -fsSL https://tempo.xyz/install | bash +``` + +## Authenticate + +```bash +tempo wallet login +tempo wallet whoami +``` + +`login` opens a browser flow that creates or connects a Tempo wallet. `whoami` confirms readiness, prints your address, and shows token balances. + +## Discover and call a paid API + +```bash +# Find services +tempo wallet services --search ai + +# Inspect a service's endpoints and request schema +tempo wallet services + +# Preview cost without paying +tempo request --dry-run -X POST \ + --json '{"prompt":"a sunset over the ocean"}' \ + https://fal.mpp.tempo.xyz/fal-ai/flux/dev + +# Execute the paid request +tempo request -X POST \ + --json '{"prompt":"a sunset over the ocean"}' \ + https://fal.mpp.tempo.xyz/fal-ai/flux/dev +``` + +## Scripting and agents + +The CLI is designed for non-interactive use. Two features matter here: + +- **`-t` (TOON output)**: compact, machine-readable output that minimizes token usage when consumed by an LLM or parsed by a script. +- **`--dry-run`**: previews the payment cost and validates the request shape without spending funds. Useful for agents that need to confirm cost before committing. + +To set up an AI agent (Claude Code, Amp, Codex) with wallet and request capabilities: + +``` +Read https://wallet.tempo.xyz/SKILL.md and set up tempo +``` + +This installs `tempo-wallet` and `tempo-request` skills automatically. The agent can then discover services, preview costs, and make paid requests within scoped spending limits. + +## Next + +- [Reference](/wallet/reference): complete command and flag reference diff --git a/src/pages/wallet/recipes.mdx b/src/pages/wallet/recipes.mdx new file mode 100644 index 00000000..40ac24ed --- /dev/null +++ b/src/pages/wallet/recipes.mdx @@ -0,0 +1,134 @@ +--- +title: Tempo Wallet CLI Recipes +description: Use practical Tempo Wallet CLI recipes for service discovery, paid requests, session management, and funding or transfers. +--- + +# Recipes + +## Core Flow + +```bash +tempo wallet -t whoami +tempo wallet services --search ai +tempo wallet services +tempo request --dry-run --json '{"input":"hello"}' +tempo request --json '{"input":"hello"}' +``` + +## Wallet Operations + +```bash +# Wallet readiness and balances +tempo wallet whoami + +# Key and spending-limit state +tempo wallet keys + +# Fund wallet +tempo wallet fund + +# Transfer tokens +tempo wallet transfer +``` + +For machine-readable output: + +```bash +tempo wallet -t whoami +tempo wallet -t keys +``` + +## Service Discovery + +```bash +# Search services by keyword +tempo wallet services --search ai + +# Inspect one service to see exact endpoints +tempo wallet services +``` + +Tip: copy endpoint URL, method, and payload shape directly from service details. + +## Request Execution + +Preview before paying: + +```bash +tempo request --dry-run --json '{"input":"hello"}' +``` + +Execute paid request: + +```bash +tempo request --json '{"input":"hello"}' +``` + +## Session Management + +```bash +# List sessions +tempo wallet sessions list + +# Reconcile local state against on-chain state +tempo wallet sessions sync + +# Preview close operations first +tempo wallet sessions close --dry-run --all + +# Close orphaned sessions +tempo wallet sessions close --orphaned +``` + +## Agent and Script Mode + +Use TOON output (`-t`) when command output is consumed by agents or scripts. + +```bash +tempo wallet -t whoami +tempo wallet -t services --search ai +tempo wallet -t services +``` + +## Failure Recovery Shortcuts + +```bash +# Wallet not ready / auth missing +tempo wallet login +tempo wallet -t whoami + +# Suspected key issue +tempo wallet logout --yes +tempo wallet login +tempo wallet keys + +# Request failing due to payload/path mismatch +tempo wallet services + +# Insufficient funds +tempo wallet fund +``` + +If issues persist, continue with [Troubleshooting](/wallet/troubleshooting). + +## End-to-End Script Pattern + +```bash +# 1) Ensure wallet is ready +tempo wallet -t whoami + +# 2) Discover service details +tempo wallet -t services --search ai + +# 3) Preview cost +tempo request --dry-run --json '{"input":"hello"}' + +# 4) Execute +tempo request --json '{"input":"hello"}' +``` + +## See Also + +1. [Reference](/wallet/reference) +2. [Troubleshooting](/wallet/troubleshooting) +3. [Use with Agents](/wallet/use-with-agents) diff --git a/src/pages/wallet/reference.mdx b/src/pages/wallet/reference.mdx new file mode 100644 index 00000000..5d946948 --- /dev/null +++ b/src/pages/wallet/reference.mdx @@ -0,0 +1,85 @@ +--- +title: Tempo CLI Reference +description: Complete command and flag reference for tempo wallet and tempo request. +--- + +# Reference + +## `tempo wallet` + +Manages your onchain identity and provides service discovery for [MPP](https://mpp.dev) endpoints. + +### Auth + +| Command | Description | +| --- | --- | +| `tempo wallet login` | Connect or create wallet via browser auth | +| `tempo wallet logout` | Disconnect wallet and clear local credentials | +| `tempo wallet whoami` | Print readiness, address, balances, and key state | + +### Keys + +Each wallet can have multiple access keys with independent spending limits. This is how you constrain what an agent or script can spend. + +| Command | Description | +| --- | --- | +| `tempo wallet keys` | List keys and their spending limits | + +### Funds + +| Command | Description | +| --- | --- | +| `tempo wallet fund` | Fund wallet (faucet on testnet, bridge on mainnet) | +| `tempo wallet transfer ` | Transfer tokens to another address | + +### Services + +The service directory indexes [MPP](https://mpp.dev)-registered providers. Each service entry includes endpoint URLs, HTTP methods, and expected request schemas, enough to construct a valid `tempo request` call. + +| Command | Description | +| --- | --- | +| `tempo wallet services` | List all registered services | +| `tempo wallet services --search ` | Filter services by keyword | +| `tempo wallet services ` | Show a service's endpoints, methods, and request schemas | + +### Sessions + +Sessions are the local state for [pay-as-you-go](/guide/machine-payments/pay-as-you-go) payment channels. The CLI tracks them locally and can reconcile against onchain state. + +| Command | Description | +| --- | --- | +| `tempo wallet sessions list` | List active payment sessions | +| `tempo wallet sessions sync` | Reconcile local sessions with onchain state | +| `tempo wallet sessions close --all` | Close all sessions | +| `tempo wallet sessions close --orphaned` | Close sessions whose counterparty is unreachable | + +### Other + +| Command | Description | +| --- | --- | +| `tempo wallet mpp-sign` | Sign an MPP payment challenge (used internally by `tempo request`) | + +## `tempo request` + +A curl-like HTTP client that handles [MPP](https://mpp.dev) payment negotiation transparently. On a `402 Payment Required` response, it reads the challenge, signs and submits the payment, then retries with the credential. + +| Command | Description | +| --- | --- | +| `tempo request ` | Make an HTTP request with automatic payment | +| `tempo request --dry-run ` | Preview cost without executing payment | +| `tempo request --json '{...}'` | Send a JSON body (implies `-X POST`) | +| `tempo request -H 'Header: Value'` | Add a custom header | + +## Global flags + +| Flag | Scope | Description | +| --- | --- | --- | +| `-t` / `--toon-output` | `tempo wallet`, `tempo request` | Compact machine-readable output for scripts and agents | +| `--dry-run` | `tempo request`, `tempo wallet fund`, `tempo wallet sessions close` | Preview the action without executing | +| `--help` | all commands | Show command documentation | +| `--describe` | supported commands | Output command schema as JSON for programmatic tooling | + +## Source + +- Repository: [`tempoxyz/wallet`](https://github.com/tempoxyz/wallet) +- Full help: `tempo wallet --help`, `tempo request --help` diff --git a/src/pages/wallet/use-with-agents.mdx b/src/pages/wallet/use-with-agents.mdx new file mode 100644 index 00000000..643fdf62 --- /dev/null +++ b/src/pages/wallet/use-with-agents.mdx @@ -0,0 +1,36 @@ +--- +title: Use Tempo Wallet CLI with Agents +description: Connect Tempo Wallet CLI to your agent and understand the built-in features that make agent-driven paid requests reliable and safe. +--- + +# Use with Agents + +## Quickstart + +Paste this into your agent to set up Tempo Wallet: + + + +``` +Read https://wallet.tempo.xyz/SKILL.md and set up tempo +``` + +## Auto-installed Skills + +When you run the setup prompt, your agent installs Tempo's built-in skills automatically — no manual skill wiring required. + +- `tempo-wallet`: gives your agent wallet-aware capabilities like readiness checks, balances, service discovery, and session/funding actions. +- `tempo-request`: gives your agent paid HTTP request capabilities with payment preview (`--dry-run`) and execution support. + +This works in supported skill-enabled agents including **Claude Code**, **Amp**, **Codex**, and similar environments. + +## Agent-Ready by Design + +1. **TOON output mode (`-t` / `--toon-output`)** gives compact, machine-readable, token-efficient output so agent tooling can parse command responses reliably. +2. **Built-in service discovery** (`tempo wallet services`) lets agents search providers, inspect endpoint details, and use verified method/path metadata instead of guessing URLs or payload shapes. +3. **`--dry-run` payment previews** let agents validate endpoint reachability, request shape, and expected payment cost before committing funds, which reduces failed paid calls in multi-step workflows. +4. **Scoped access keys and spend controls** let you enforce spending limits per key so agents can only spend within defined budgets, which contains risk if a prompt, endpoint choice, or loop goes wrong. + +## Troubleshooting + +If agent runs fail, continue with [Troubleshooting](/wallet/troubleshooting). diff --git a/src/snippets/go-provider.go b/src/snippets/go-provider.go new file mode 100644 index 00000000..95bb5496 --- /dev/null +++ b/src/snippets/go-provider.go @@ -0,0 +1,13 @@ +// [!region setup] +package main + +import ( + "os" + + "github.com/tempoxyz/tempo-go/pkg/client" +) + +func newClient() *client.Client { + return client.New(os.Getenv("TEMPO_RPC_URL")) +} +// [!endregion setup] diff --git a/src/snippets/rust-provider.rs b/src/snippets/rust-provider.rs new file mode 100644 index 00000000..b27a081c --- /dev/null +++ b/src/snippets/rust-provider.rs @@ -0,0 +1,15 @@ +// [!region setup] +use alloy::providers::ProviderBuilder; +use tempo_alloy::TempoNetwork; + +pub async fn get_provider() -> Result< + impl alloy::providers::Provider, + Box, +> { + let provider = ProviderBuilder::new_with_network::() + .connect(&std::env::var("RPC_URL").expect("RPC_URL not set")) + .await?; + + Ok(provider) +} +// [!endregion setup] diff --git a/src/snippets/rust-signer-provider.rs b/src/snippets/rust-signer-provider.rs new file mode 100644 index 00000000..c44c2b63 --- /dev/null +++ b/src/snippets/rust-signer-provider.rs @@ -0,0 +1,21 @@ +// [!region setup] +use alloy::providers::ProviderBuilder; +use alloy::signers::local::PrivateKeySigner; +use tempo_alloy::TempoNetwork; + +pub async fn get_provider() -> Result< + impl alloy::providers::Provider, + Box, +> { + let signer: PrivateKeySigner = std::env::var("PRIVATE_KEY") + .expect("PRIVATE_KEY not set") + .parse()?; + + let provider = ProviderBuilder::new_with_network::() + .wallet(signer) + .connect(&std::env::var("RPC_URL").expect("RPC_URL not set")) + .await?; + + Ok(provider) +} +// [!endregion setup] diff --git a/src/snippets/setup.ts b/src/snippets/setup.ts index d3fca6ad..119968b4 100644 --- a/src/snippets/setup.ts +++ b/src/snippets/setup.ts @@ -2,12 +2,12 @@ import { createClient, http, publicActions, walletActions } from 'viem' import { privateKeyToAccount } from 'viem/accounts' -import { tempoModerato } from 'viem/chains' +import { tempo } from 'viem/chains' import { tempoActions } from 'viem/tempo' export const client = createClient({ account: privateKeyToAccount('0x...'), - chain: tempoModerato, + chain: tempo, transport: http(), }) .extend(publicActions) diff --git a/src/snippets/tempo-tx-properties.mdx b/src/snippets/tempo-tx-properties.mdx index a03dac51..e078dd0e 100644 --- a/src/snippets/tempo-tx-properties.mdx +++ b/src/snippets/tempo-tx-properties.mdx @@ -65,6 +65,150 @@ user's preferred fee token and the validator's preferred token. + + + :::code-group + + ```rust [example.rs] + use alloy::primitives::{address, bytes}; + use alloy::providers::Provider; + use tempo_alloy::rpc::TempoTransactionRequest; + + mod provider; + + #[tokio::main] + async fn main() -> Result<(), Box> { + let provider = provider::get_provider().await?; + + let alpha_usd = address!("0x20c0000000000000000000000000000000000001"); + + let pending = provider + .send_transaction( + TempoTransactionRequest::default() + .with_fee_token(alpha_usd) // [!code hl] + .with_to(address!("0xcafebabecafebabecafebabecafebabecafebabe")) + .with_input(bytes!("deadbeef")), + ) + .await?; + + Ok(()) + } + ``` + + ```rust [provider.rs] + // [!include ~/snippets/rust-signer-provider.rs:setup] + ``` + + ::: + + + + + + :::code-group + + ```python [example.py] + from pytempo import Call, TempoTransaction + from provider import w3, account + + alpha_usd = "0x20c0000000000000000000000000000000000001" + + tx = TempoTransaction.create( + chain_id=w3.eth.chain_id, + gas_limit=300_000, + max_fee_per_gas=w3.eth.gas_price * 2, + max_priority_fee_per_gas=w3.eth.gas_price, + nonce=w3.eth.get_transaction_count(account.address), + fee_token=alpha_usd, # [!code hl] + calls=( + Call.create( + to="0xcafebabecafebabecafebabecafebabecafebabe", + data="0xdeadbeef", + ), + ), + ) + + signed_tx = tx.sign(account.key.hex()) + tx_hash = w3.eth.send_raw_transaction(signed_tx.encode()) + ``` + + ```python [provider.py] + from web3 import Web3 + from eth_account import Account + + w3 = Web3(Web3.HTTPProvider("https://rpc.tempo.xyz")) + account = Account.from_key("0x...") + ``` + + ::: + + + + + + :::code-group + + ```go [main.go] + package main + + import ( + "context" + "log" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/tempoxyz/tempo-go/pkg/signer" + "github.com/tempoxyz/tempo-go/pkg/transaction" + ) + + func main() { + sgn, _ := signer.NewSigner("0x...") + c := newClient() + ctx := context.Background() + + nonce, _ := c.GetTransactionCount(ctx, sgn.Address().Hex()) + + tx := transaction.NewBuilder(big.NewInt(transaction.ChainIdMainnet)). + SetNonce(nonce). + SetGas(300_000). + SetMaxFeePerGas(big.NewInt(25_000_000_000)). + SetMaxPriorityFeePerGas(big.NewInt(1_000_000_000)). + SetFeeToken(transaction.AlphaUSDAddress). // [!code hl] + AddCall( + common.HexToAddress("0xcafebabecafebabecafebabecafebabecafebabe"), + big.NewInt(0), + common.Hex2Bytes("deadbeef"), + ). + Build() + + _ = transaction.SignTransaction(tx, sgn) + serialized, _ := transaction.Serialize(tx, nil) + txHash, _ := c.SendRawTransaction(ctx, serialized) + + log.Printf("Transaction hash: %s", txHash) + } + ``` + + ```go [provider.go] + // [!include ~/snippets/go-provider.go:setup] + ``` + + ::: + + + + + + ```bash + $ cast send 0xcafebabecafebabecafebabecafebabecafebabe \ + --data 0xdeadbeef \ + --rpc-url $TEMPO_RPC_URL \ + --private-key $PRIVATE_KEY \ + --tempo.fee-token 0x20c0000000000000000000000000000000000001 # [!code hl] + ``` + + + ```tsx @@ -158,6 +302,188 @@ over the transaction with a special "fee payer envelope" to commit to paying fee + + + :::code-group + + ```rust [example.rs] + use alloy::primitives::{U256, address, bytes}; + use alloy::providers::Provider; + use alloy::signers::{SignerSync, local::PrivateKeySigner}; + use tempo_alloy::primitives::transaction::tempo_transaction::Call; + use tempo_alloy::rpc::TempoTransactionRequest; + + mod provider; + + #[tokio::main] + async fn main() -> Result<(), Box> { + let provider = provider::get_provider().await?; + + let tx = TempoTransactionRequest { + calls: vec![Call { + to: address!("0xcafebabecafebabecafebabecafebabecafebabe").into(), + value: U256::ZERO, + input: bytes!("deadbeef"), + }], + ..Default::default() + }; + + // Step 1: Build the transaction + let mut tempo_tx = provider.fill(tx).await?.build_aa()?; + let sender_addr = provider.default_signer_address(); + let fee_payer_hash = tempo_tx.fee_payer_signature_hash(sender_addr); + + // Step 2: Fee payer counter-signs the transaction // [!code hl] + let fee_payer: PrivateKeySigner = "0x...".parse()?; // [!code hl] + tempo_tx.fee_payer_signature = Some(fee_payer.sign_hash_sync(&fee_payer_hash)?); // [!code hl] + + // Step 3: Broadcast + let pending = provider.send_transaction(tempo_tx).await?; + + Ok(()) + } + ``` + + ```rust [provider.rs] + // [!include ~/snippets/rust-signer-provider.rs:setup] + ``` + + ::: + + + + + + :::code-group + + ```python [example.py] + from pytempo import Call, TempoTransaction + from provider import w3, account + + fee_payer_key = "0x..." + + # Sender signs with awaiting_fee_payer flag + tx = TempoTransaction.create( + chain_id=w3.eth.chain_id, + gas_limit=300_000, + max_fee_per_gas=w3.eth.gas_price * 2, + max_priority_fee_per_gas=w3.eth.gas_price, + nonce=w3.eth.get_transaction_count(account.address), + awaiting_fee_payer=True, # [!code hl] + calls=( + Call.create( + to="0xcafebabecafebabecafebabecafebabecafebabe", + data="0xdeadbeef", + ), + ), + ) + sender_signed = tx.sign(account.key.hex()) + + # Fee payer co-signs the transaction // [!code hl] + fully_signed = sender_signed.sign(fee_payer_key, for_fee_payer=True) # [!code hl] + + tx_hash = w3.eth.send_raw_transaction(fully_signed.encode()) + ``` + + ```python [provider.py] + from web3 import Web3 + from eth_account import Account + + w3 = Web3(Web3.HTTPProvider("https://rpc.tempo.xyz")) + account = Account.from_key("0x...") + ``` + + ::: + + + + + + :::code-group + + ```go [main.go] + package main + + import ( + "context" + "log" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/tempoxyz/tempo-go/pkg/signer" + "github.com/tempoxyz/tempo-go/pkg/transaction" + ) + + func main() { + senderSgn, _ := signer.NewSigner("0x...") + sponsorSgn, _ := signer.NewSigner("0x...") + c := newClient() + ctx := context.Background() + + nonce, _ := c.GetTransactionCount(ctx, senderSgn.Address().Hex()) + + // Sender builds and signs a sponsored transaction + tx := transaction.NewBuilder(big.NewInt(transaction.ChainIdMainnet)). + SetNonce(nonce). + SetGas(300_000). + SetMaxFeePerGas(big.NewInt(25_000_000_000)). + SetMaxPriorityFeePerGas(big.NewInt(1_000_000_000)). + SetSponsored(true). // [!code hl] + AddCall( + common.HexToAddress("0xcafebabecafebabecafebabecafebabecafebabe"), + big.NewInt(0), + common.Hex2Bytes("deadbeef"), + ). + Build() + + _ = transaction.SignTransaction(tx, senderSgn) + + // Fee payer co-signs the transaction // [!code hl] + tx.FeeToken = transaction.AlphaUSDAddress // [!code hl] + tx.AwaitingFeePayer = false // [!code hl] + _ = transaction.AddFeePayerSignature(tx, sponsorSgn) // [!code hl] + + serialized, _ := transaction.Serialize(tx, nil) + txHash, _ := c.SendRawTransaction(ctx, serialized) + + log.Printf("Transaction hash: %s", txHash) + } + ``` + + ```go [provider.go] + // [!include ~/snippets/go-provider.go:setup] + ``` + + ::: + + + + + + ```bash + # 1. Get the fee payer signature hash + $ FEE_PAYER_HASH=$(cast mktx 0xcafebabecafebabecafebabecafebabecafebabe \ + --data 0xdeadbeef \ + --rpc-url $TEMPO_RPC_URL \ + --private-key $SENDER_KEY \ + --tempo.print-sponsor-hash) # [!code hl] + + # 2. Sponsor signs the hash + $ SPONSOR_SIG=$(cast wallet sign \ + --private-key $SPONSOR_KEY \ + "$FEE_PAYER_HASH" \ + --no-hash) # [!code hl] + + # 3. Send with sponsor signature + $ cast send 0xcafebabecafebabecafebabecafebabecafebabe \ + --data 0xdeadbeef \ + --rpc-url $TEMPO_RPC_URL \ + --private-key $SENDER_KEY \ + --tempo.sponsor-signature "$SPONSOR_SIG" # [!code hl] + ``` + + + ```tsx @@ -309,6 +635,178 @@ parameter. + + + :::code-group + + ```rust [example.rs] + use alloy::primitives::{U256, address, bytes}; + use alloy::providers::Provider; + use tempo_alloy::primitives::transaction::Call; + use tempo_alloy::rpc::TempoTransactionRequest; + + mod provider; + + #[tokio::main] + async fn main() -> Result<(), Box> { + let provider = provider::get_provider().await?; + + let pending = provider + .send_transaction(TempoTransactionRequest { + calls: vec![ // [!code hl] + Call { // [!code hl] + to: address!("0xcafebabecafebabecafebabecafebabecafebabe").into(), // [!code hl] + value: U256::ZERO, // [!code hl] + input: bytes!("deadbeef0000000000000000000000000000000001"), // [!code hl] + }, // [!code hl] + Call { // [!code hl] + to: address!("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef").into(), // [!code hl] + value: U256::ZERO, // [!code hl] + input: bytes!("cafebabe0000000000000000000000000000000001"), // [!code hl] + }, // [!code hl] + Call { // [!code hl] + to: address!("0xcafebabecafebabecafebabecafebabecafebabe").into(), // [!code hl] + value: U256::ZERO, // [!code hl] + input: bytes!("deadbeef0000000000000000000000000000000001"), // [!code hl] + }, // [!code hl] + ], // [!code hl] + ..Default::default() + }) + .await?; + + Ok(()) + } + ``` + + ```rust [provider.rs] + // [!include ~/snippets/rust-signer-provider.rs:setup] + ``` + + ::: + + + + + + :::code-group + + ```python [example.py] + from pytempo import Call, TempoTransaction + from provider import w3, account + + tx = TempoTransaction.create( + chain_id=w3.eth.chain_id, + gas_limit=600_000, + max_fee_per_gas=w3.eth.gas_price * 2, + max_priority_fee_per_gas=w3.eth.gas_price, + nonce=w3.eth.get_transaction_count(account.address), + calls=( # [!code hl] + Call.create( # [!code hl] + to="0xcafebabecafebabecafebabecafebabecafebabe", # [!code hl] + data="0xdeadbeef0000000000000000000000000000000001", # [!code hl] + ), # [!code hl] + Call.create( # [!code hl] + to="0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef", # [!code hl] + data="0xcafebabe0000000000000000000000000000000001", # [!code hl] + ), # [!code hl] + Call.create( # [!code hl] + to="0xcafebabecafebabecafebabecafebabecafebabe", # [!code hl] + data="0xdeadbeef0000000000000000000000000000000001", # [!code hl] + ), # [!code hl] + ), # [!code hl] + ) + + signed_tx = tx.sign(account.key.hex()) + tx_hash = w3.eth.send_raw_transaction(signed_tx.encode()) + ``` + + ```python [provider.py] + from web3 import Web3 + from eth_account import Account + + w3 = Web3(Web3.HTTPProvider("https://rpc.tempo.xyz")) + account = Account.from_key("0x...") + ``` + + ::: + + + + + + :::code-group + + ```go [main.go] + package main + + import ( + "context" + "log" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/tempoxyz/tempo-go/pkg/signer" + "github.com/tempoxyz/tempo-go/pkg/transaction" + ) + + func main() { + sgn, _ := signer.NewSigner("0x...") + c := newClient() + ctx := context.Background() + + nonce, _ := c.GetTransactionCount(ctx, sgn.Address().Hex()) + + tx := transaction.NewBuilder(big.NewInt(transaction.ChainIdMainnet)). + SetNonce(nonce). + SetGas(600_000). + SetMaxFeePerGas(big.NewInt(25_000_000_000)). + SetMaxPriorityFeePerGas(big.NewInt(1_000_000_000)). + AddCall( // [!code hl] + common.HexToAddress("0xcafebabecafebabecafebabecafebabecafebabe"), // [!code hl] + big.NewInt(0), // [!code hl] + common.Hex2Bytes("deadbeef0000000000000000000000000000000001"), // [!code hl] + ). // [!code hl] + AddCall( // [!code hl] + common.HexToAddress("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"), // [!code hl] + big.NewInt(0), // [!code hl] + common.Hex2Bytes("cafebabe0000000000000000000000000000000001"), // [!code hl] + ). // [!code hl] + AddCall( // [!code hl] + common.HexToAddress("0xcafebabecafebabecafebabecafebabecafebabe"), // [!code hl] + big.NewInt(0), // [!code hl] + common.Hex2Bytes("deadbeef0000000000000000000000000000000001"), // [!code hl] + ). // [!code hl] + Build() + + _ = transaction.SignTransaction(tx, sgn) + serialized, _ := transaction.Serialize(tx, nil) + txHash, _ := c.SendRawTransaction(ctx, serialized) + + log.Printf("Transaction hash: %s", txHash) + } + ``` + + ```go [provider.go] + // [!include ~/snippets/go-provider.go:setup] + ``` + + ::: + + + + + + ```bash + $ cast batch-send \ + --rpc-url $TEMPO_RPC_URL \ + --private-key $PRIVATE_KEY \ + --call "0xcafebabecafebabecafebabecafebabecafebabe::increment()" \ + --call "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef::setNumber(uint256):500" \ + --call "0xcafebabecafebabecafebabecafebabecafebabe::increment()" + ``` + + + ```tsx @@ -387,7 +885,7 @@ transactions thereafter can be signed by the access key. ```tsx twoslash [example.ts] // @noErrors - import { tempoModerato } from 'viem/chains' + import { tempo } from 'viem/chains' import { KeyManager, webAuthn } from 'wagmi/tempo' import { createConfig, http } from 'wagmi' @@ -398,16 +896,297 @@ transactions thereafter can be signed by the access key. keyManager: KeyManager.localStorage(), }), ], - chains: [tempoModerato], + chains: [tempo], multiInjectedProviderDiscovery: false, transports: { - [tempoModerato.id]: http(), + [tempo.id]: http(), }, }) ``` + + + :::code-group + + ```rust [example.rs] + use alloy::primitives::{address, bytes}; + use alloy::providers::Provider; + use alloy::signers::{SignerSync, local::PrivateKeySigner}; + use tempo_alloy::primitives::transaction::key_authorization::{ + KeyAuthorization, SignedKeyAuthorization, + }; + use tempo_alloy::primitives::transaction::tt_signature::{ + KeychainSignature, PrimitiveSignature, SignatureType, TempoSignature, + }; + use tempo_alloy::rpc::TempoTransactionRequest; + + mod provider; + + #[tokio::main] + async fn main() -> Result<(), Box> { + let provider = provider::get_provider().await?; + + let root = PrivateKeySigner::from_str("0x...")?; + let access_key = PrivateKeySigner::random(); + + // Sign key authorization with root account // [!code hl] + let authorization = KeyAuthorization { // [!code hl] + chain_id: 4217, // [!code hl] + key_type: SignatureType::Secp256k1, // [!code hl] + key_id: access_key.address(), // [!code hl] + expiry: None, // [!code hl] + limits: None, // [!code hl] + }; // [!code hl] + let sig = root.sign_hash_sync(&authorization.signature_hash())?; // [!code hl] + let key_authorization = SignedKeyAuthorization { // [!code hl] + authorization, // [!code hl] + signature: sig.into(), // [!code hl] + }; // [!code hl] + + // Attach key authorization to a transaction signed by the root key. // [!code hl] + // This registers the access key on-chain via the Account Keychain. // [!code hl] + provider + .send_transaction( + TempoTransactionRequest { + key_authorization: Some(key_authorization), // [!code hl] + ..Default::default() + } + .with_to(address!("0xcafebabecafebabecafebabecafebabecafebabe")) + .with_input(bytes!("deadbeef")), + ) + .await? + .get_receipt() + .await?; + + // Sign a subsequent transaction with the access key // [!code hl] + let tx = TempoTransactionRequest::default() + .with_to(address!("0xcafebabecafebabecafebabecafebabecafebabe")) + .with_input(bytes!("deadbeef")); + + let filled = provider.fill(tx).await?; + let tempo_tx = filled.build_aa()?; + + // Access key signs a domain-separated hash bound to the root account // [!code hl] + let inner_hash = // [!code hl] + KeychainSignature::signing_hash(tempo_tx.signature_hash(), root.address()); // [!code hl] + let inner_sig = access_key.sign_hash_sync(&inner_hash)?; // [!code hl] + let signature = TempoSignature::Keychain(KeychainSignature::new( // [!code hl] + root.address(), // [!code hl] + PrimitiveSignature::Secp256k1(inner_sig), // [!code hl] + )); // [!code hl] + + let envelope = tempo_tx.into_signed(signature); // [!code hl] + let pending = provider // [!code hl] + .send_raw_transaction(envelope.encoded_2718().as_ref()) // [!code hl] + .await?; // [!code hl] + + Ok(()) + } + ``` + + ```rust [provider.rs] + // [!include ~/snippets/rust-signer-provider.rs:setup] + ``` + + ::: + + + + + + :::code-group + + ```python [example.py] + import time + + from eth_account import Account as EthAccount + from pytempo import ( + Call, KeyAuthorization, SignatureType, TempoTransaction, + sign_tx_access_key, + ) + from provider import w3, account + + access_key = EthAccount.create() + + # Sign key authorization with root account // [!code hl] + auth = KeyAuthorization( # [!code hl] + chain_id=w3.eth.chain_id, # [!code hl] + key_type=SignatureType.SECP256K1, # [!code hl] + key_id=access_key.address, # [!code hl] + expiry=int(time.time()) + 3600, # [!code hl] + limits=None, # [!code hl] + ) # [!code hl] + signed_auth = auth.sign(account.key.hex()) # [!code hl] + + # Attach key authorization to a transaction signed by the access key. // [!code hl] + # This registers the access key on-chain via the Account Keychain. // [!code hl] + tx = TempoTransaction.create( + chain_id=w3.eth.chain_id, + gas_limit=600_000, + max_fee_per_gas=w3.eth.gas_price * 2, + max_priority_fee_per_gas=w3.eth.gas_price, + nonce=0, + nonce_key=201, + key_authorization=signed_auth.rlp_encode(), # [!code hl] + calls=( + Call.create( + to="0xcafebabecafebabecafebabecafebabecafebabe", + data="0xdeadbeef", + ), + ), + ) + + signed_tx = sign_tx_access_key( # [!code hl] + tx, # [!code hl] + access_key_private_key=access_key.key.hex(), # [!code hl] + root_account=account.address, # [!code hl] + ) # [!code hl] + tx_hash = w3.eth.send_raw_transaction(signed_tx.encode()) + ``` + + ```python [provider.py] + from web3 import Web3 + from eth_account import Account + + w3 = Web3(Web3.HTTPProvider("https://rpc.tempo.xyz")) + account = Account.from_key("0x...") + ``` + + ::: + + + + + + :::code-group + + ```go [main.go] + package main + + import ( + "context" + "encoding/hex" + "log" + "math/big" + "strings" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/tempoxyz/tempo-go/pkg/keychain" + "github.com/tempoxyz/tempo-go/pkg/signer" + "github.com/tempoxyz/tempo-go/pkg/transaction" + ) + + func main() { + rootSgn, _ := signer.NewSigner("0x...") + accessKeyPriv, _ := crypto.GenerateKey() + accessKey := signer.NewSignerFromKey(accessKeyPriv) + c := newClient() + ctx := context.Background() + + chainID := big.NewInt(transaction.ChainIdMainnet) + gasPrice := big.NewInt(25_000_000_000) + keychainAddr := common.HexToAddress(keychain.AccountKeychainAddress) + + // Authorize the access key via Account Keychain precompile // [!code hl] + parsed, _ := abi.JSON(strings.NewReader(`[{ + "name": "authorizeKey", + "type": "function", + "inputs": [ + {"name": "keyId", "type": "address"}, + {"name": "sigType", "type": "uint8"}, + {"name": "expiry", "type": "uint64"}, + {"name": "enforceLimits", "type": "bool"}, + {"name": "limits", "type": "tuple[]", "components": [ + {"name": "token", "type": "address"}, + {"name": "amount", "type": "uint256"} + ]} + ] + }]`)) + type TokenLimit struct { + Token common.Address + Amount *big.Int + } + calldata, _ := parsed.Pack("authorizeKey", + accessKey.Address(), + uint8(0), + uint64(1893456000), + false, + []TokenLimit{}, + ) + + nonce, _ := c.GetTransactionCount(ctx, rootSgn.Address().Hex()) + authTx := types.NewTx(&types.DynamicFeeTx{ + ChainID: chainID, + Nonce: nonce, + GasTipCap: gasPrice, + GasFeeCap: gasPrice, + Gas: 600_000, + To: &keychainAddr, + Data: calldata, + }) + signedAuthTx, _ := types.SignTx( + authTx, types.NewLondonSigner(chainID), rootSgn.PrivateKey(), + ) + txBytes, _ := signedAuthTx.MarshalBinary() + authHash, _ := c.SendRawTransaction(ctx, "0x"+hex.EncodeToString(txBytes)) + log.Printf("Authorized access key: %s", authHash) + + // Sign a transaction with the access key // [!code hl] + tx := transaction.NewBuilder(chainID). // [!code hl] + SetNonce(0). // [!code hl] + SetNonceKey(big.NewInt(300)). // [!code hl] + SetGas(500_000). // [!code hl] + SetMaxFeePerGas(gasPrice). // [!code hl] + SetMaxPriorityFeePerGas(gasPrice). // [!code hl] + AddCall( // [!code hl] + common.HexToAddress("0xcafebabecafebabecafebabecafebabecafebabe"), // [!code hl] + big.NewInt(0), // [!code hl] + common.Hex2Bytes("deadbeef"), // [!code hl] + ). // [!code hl] + Build() // [!code hl] + + _ = keychain.SignWithAccessKey(tx, accessKey, rootSgn.Address()) // [!code hl] + + serialized, _ := transaction.Serialize(tx, nil) + txHash, _ := c.SendRawTransaction(ctx, serialized) + + log.Printf("Transaction hash: %s", txHash) + } + ``` + + ```go [provider.go] + // [!include ~/snippets/go-provider.go:setup] + ``` + + ::: + + + + + + ```bash + # 1. Authorize the access key via Account Keychain precompile + $ cast send 0xAAAAAAAA00000000000000000000000000000000 \ + 'authorizeKey(address,uint8,uint64,bool,(address,uint256)[])' \ + $ACCESS_KEY_ADDR 0 1893456000 false "[]" \ + --rpc-url $TEMPO_RPC_URL \ + --private-key $ROOT_PRIVATE_KEY # [!code hl] + + # 2. Send using the access key + $ cast send 0xcafebabecafebabecafebabecafebabecafebabe \ + --data 0xdeadbeef \ + --rpc-url $TEMPO_RPC_URL \ + --tempo.root-account $ROOT_ADDRESS \ + --tempo.access-key $ACCESS_KEY_PRIVATE_KEY # [!code hl] + ``` + + + ```tsx @@ -518,6 +1297,191 @@ In **Viem** and **Wagmi**, expiring nonces are handled automatically. + + + :::code-group + + ```rust [example.rs] + use alloy::primitives::{U256, address, bytes}; + use alloy::providers::Provider; + use tempo_alloy::rpc::TempoTransactionRequest; + + mod provider; + + #[tokio::main] + async fn main() -> Result<(), Box> { + let provider = provider::get_provider().await?; + + // Send three transactions concurrently using different nonce keys + let (r1, r2, r3) = tokio::try_join!( + provider.send_transaction( + TempoTransactionRequest::default() + .with_nonce_key(U256::from(1)) // [!code hl] + .with_to(address!("0xcafebabecafebabecafebabecafebabecafebabe")) + .with_input(bytes!("deadbeef0000000000000000000000000000000001")), + ), + provider.send_transaction( + TempoTransactionRequest::default() + .with_nonce_key(U256::from(2)) // [!code hl] + .with_to(address!("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef")) + .with_input(bytes!("cafebabe0000000000000000000000000000000001")), + ), + provider.send_transaction( + TempoTransactionRequest::default() + .with_nonce_key(U256::from(3)) // [!code hl] + .with_to(address!("0xcafebabecafebabecafebabecafebabecafebabe")) + .with_input(bytes!("deadbeef0000000000000000000000000000000001")), + ), + )?; + + Ok(()) + } + ``` + + ```rust [provider.rs] + // [!include ~/snippets/rust-signer-provider.rs:setup] + ``` + + ::: + + + + + + :::code-group + + ```python [example.py] + from pytempo import Call, TempoTransaction + from provider import w3, account + + # Send three transactions concurrently using different nonce keys + for nonce_key, to, data in [ + (1, "0xcafebabecafebabecafebabecafebabecafebabe", "0xdeadbeef0000000000000000000000000000000001"), + (2, "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef", "0xcafebabe0000000000000000000000000000000001"), + (3, "0xcafebabecafebabecafebabecafebabecafebabe", "0xdeadbeef0000000000000000000000000000000001"), + ]: + tx = TempoTransaction.create( + chain_id=w3.eth.chain_id, + gas_limit=300_000, + max_fee_per_gas=w3.eth.gas_price * 2, + max_priority_fee_per_gas=w3.eth.gas_price, + nonce=0, + nonce_key=nonce_key, # [!code hl] + calls=(Call.create(to=to, data=data),), + ) + signed_tx = tx.sign(account.key.hex()) + w3.eth.send_raw_transaction(signed_tx.encode()) + ``` + + ```python [provider.py] + from web3 import Web3 + from eth_account import Account + + w3 = Web3(Web3.HTTPProvider("https://rpc.tempo.xyz")) + account = Account.from_key("0x...") + ``` + + ::: + + + + + + :::code-group + + ```go [main.go] + package main + + import ( + "context" + "log" + "math/big" + "sync" + + "github.com/ethereum/go-ethereum/common" + "github.com/tempoxyz/tempo-go/pkg/signer" + "github.com/tempoxyz/tempo-go/pkg/transaction" + ) + + func main() { + sgn, _ := signer.NewSigner("0x...") + c := newClient() + ctx := context.Background() + + // Send three transactions concurrently using different nonce keys + type txParams struct { + nonceKey int64 + to string + data string + } + params := []txParams{ + {1, "0xcafebabecafebabecafebabecafebabecafebabe", "deadbeef0000000000000000000000000000000001"}, + {2, "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef", "cafebabe0000000000000000000000000000000001"}, + {3, "0xcafebabecafebabecafebabecafebabecafebabe", "deadbeef0000000000000000000000000000000001"}, + } + + var wg sync.WaitGroup + for _, p := range params { + wg.Add(1) + go func(p txParams) { + defer wg.Done() + tx := transaction.NewBuilder(big.NewInt(transaction.ChainIdMainnet)). + SetNonce(0). + SetNonceKey(big.NewInt(p.nonceKey)). // [!code hl] + SetGas(300_000). + SetMaxFeePerGas(big.NewInt(25_000_000_000)). + SetMaxPriorityFeePerGas(big.NewInt(1_000_000_000)). + AddCall( + common.HexToAddress(p.to), + big.NewInt(0), + common.Hex2Bytes(p.data), + ). + Build() + + _ = transaction.SignTransaction(tx, sgn) + serialized, _ := transaction.Serialize(tx, nil) + txHash, _ := c.SendRawTransaction(ctx, serialized) + + log.Printf("Nonce key %d tx: %s", p.nonceKey, txHash) + }(p) + } + wg.Wait() + } + ``` + + ```go [provider.go] + // [!include ~/snippets/go-provider.go:setup] + ``` + + ::: + + + + + + ```bash + # Send three transactions concurrently using different nonce keys + $ cast send 0xcafebabecafebabecafebabecafebabecafebabe \ + --data 0xdeadbeef0000000000000000000000000000000001 \ + --rpc-url $TEMPO_RPC_URL \ + --private-key $PRIVATE_KEY \ + --async --nonce 0 --tempo.nonce-key 1 # [!code hl] + + $ cast send 0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef \ + --data 0xcafebabe0000000000000000000000000000000001 \ + --rpc-url $TEMPO_RPC_URL \ + --private-key $PRIVATE_KEY \ + --async --nonce 0 --tempo.nonce-key 2 # [!code hl] + + $ cast send 0xcafebabecafebabecafebabecafebabecafebabe \ + --data 0xdeadbeef0000000000000000000000000000000001 \ + --rpc-url $TEMPO_RPC_URL \ + --private-key $PRIVATE_KEY \ + --async --nonce 0 --tempo.nonce-key 3 # [!code hl] + ``` + + + ```tsx @@ -609,14 +1573,156 @@ Expiring nonces can be used by setting `nonceKey` to `maxUint256` and `validBefo - ```rust - let pending = provider - .send_transaction(TempoTransactionRequest { - nonce_key: Some(U256::MAX), // [!code focus] - valid_before: Some(SystemTime::now().duration_since(UNIX_EPOCH).as_secs() + 30), // [!code focus] - ..Default::default(), - }) - .await?; + :::code-group + + ```rust [example.rs] + use std::time::{SystemTime, UNIX_EPOCH}; + + use alloy::primitives::{U256, address, bytes}; + use alloy::providers::Provider; + use tempo_alloy::rpc::TempoTransactionRequest; + + mod provider; + + #[tokio::main] + async fn main() -> Result<(), Box> { + let provider = provider::get_provider().await?; + + let valid_before = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs() + 30; + + let pending = provider + .send_transaction( + TempoTransactionRequest::default() + .with_nonce_key(U256::MAX) // [!code focus] + .with_valid_before(valid_before) // [!code focus] + .with_to(address!("0xcafebabecafebabecafebabecafebabecafebabe")) + .with_input(bytes!("deadbeef0000000000000000000000000000000001")), + ) + .await?; + + Ok(()) + } + ``` + + ```rust [provider.rs] + // [!include ~/snippets/rust-signer-provider.rs:setup] + ``` + + ::: + + + + + + :::code-group + + ```python [example.py] + import time + + from pytempo import Call, TempoTransaction + from provider import w3, account + + # maxUint256: signals an expiring nonce + MAX_UINT256 = 2**256 - 1 + valid_before = int(time.time()) + 20 + + tx = TempoTransaction.create( + chain_id=w3.eth.chain_id, + gas_limit=300_000, + max_fee_per_gas=w3.eth.gas_price * 2, + max_priority_fee_per_gas=w3.eth.gas_price, + nonce_key=MAX_UINT256, # [!code focus] + valid_before=valid_before, # [!code focus] + calls=( + Call.create( + to="0xcafebabecafebabecafebabecafebabecafebabe", + data="0xdeadbeef0000000000000000000000000000000001", + ), + ), + ) + + signed_tx = tx.sign(account.key.hex()) + tx_hash = w3.eth.send_raw_transaction(signed_tx.encode()) + ``` + + ```python [provider.py] + from web3 import Web3 + from eth_account import Account + + w3 = Web3(Web3.HTTPProvider("https://rpc.tempo.xyz")) + account = Account.from_key("0x...") + ``` + + ::: + + + + + + :::code-group + + ```go [main.go] + package main + + import ( + "context" + "log" + "math/big" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/tempoxyz/tempo-go/pkg/signer" + "github.com/tempoxyz/tempo-go/pkg/transaction" + ) + + func main() { + sgn, _ := signer.NewSigner("0x...") + c := newClient() + ctx := context.Background() + + // maxUint256: signals an expiring nonce + maxUint256, _ := new(big.Int).SetString("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 16) + + validBefore := uint64(time.Now().Unix()) + 20 + + tx := transaction.NewBuilder(big.NewInt(transaction.ChainIdMainnet)). + SetGas(300_000). + SetMaxFeePerGas(big.NewInt(25_000_000_000)). + SetMaxPriorityFeePerGas(big.NewInt(1_000_000_000)). + SetNonceKey(maxUint256). // [!code focus] + SetValidBefore(validBefore). // [!code focus] + AddCall( + common.HexToAddress("0xcafebabecafebabecafebabecafebabecafebabe"), + big.NewInt(0), + common.Hex2Bytes("deadbeef0000000000000000000000000000000001"), + ). + Build() + + _ = transaction.SignTransaction(tx, sgn) + serialized, _ := transaction.Serialize(tx, nil) + txHash, _ := c.SendRawTransaction(ctx, serialized) + + log.Printf("Transaction hash: %s", txHash) + } + ``` + + ```go [provider.go] + // [!include ~/snippets/go-provider.go:setup] + ``` + + ::: + + + + + + ```bash + $ VALID_BEFORE=$(($(date +%s) + 20)) + $ cast send 0xcafebabecafebabecafebabecafebabecafebabe \ + --data 0xdeadbeef0000000000000000000000000000000001 \ + --rpc-url $TEMPO_RPC_URL \ + --private-key $PRIVATE_KEY \ + --tempo.expiring-nonce --tempo.valid-before $VALID_BEFORE # [!code hl] ``` @@ -724,6 +1830,187 @@ For cases requiring ordered sequences within a key, Tempo's **2D nonce system** + + + :::code-group + + ```rust [example.rs] + use alloy::primitives::{U256, address, bytes}; + use alloy::providers::Provider; + use tempo_alloy::rpc::TempoTransactionRequest; + + mod provider; + + #[tokio::main] + async fn main() -> Result<(), Box> { + let provider = provider::get_provider().await?; + + let (r1, r2, r3) = tokio::try_join!( + provider.send_transaction( + TempoTransactionRequest::default() + .with_nonce_key(U256::from(1)) // [!code focus] + .with_to(address!("0xcafebabecafebabecafebabecafebabecafebabe")) + .with_input(bytes!("deadbeef0000000000000000000000000000000001")), + ), + provider.send_transaction( + TempoTransactionRequest::default() + .with_nonce_key(U256::from(2)) // [!code focus] + .with_to(address!("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef")) + .with_input(bytes!("cafebabe0000000000000000000000000000000001")), + ), + provider.send_transaction( + TempoTransactionRequest::default() + .with_nonce_key(U256::from(3)) // [!code focus] + .with_to(address!("0xcafebabecafebabecafebabecafebabecafebabe")) + .with_input(bytes!("deadbeef0000000000000000000000000000000001")), + ), + )?; + + Ok(()) + } + ``` + + ```rust [provider.rs] + // [!include ~/snippets/rust-signer-provider.rs:setup] + ``` + + ::: + + + + + + :::code-group + + ```python [example.py] + from pytempo import Call, TempoTransaction + from provider import w3, account + + for nonce_key, to, data in [ + (1, "0xcafebabecafebabecafebabecafebabecafebabe", "0xdeadbeef0000000000000000000000000000000001"), + (2, "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef", "0xcafebabe0000000000000000000000000000000001"), + (3, "0xcafebabecafebabecafebabecafebabecafebabe", "0xdeadbeef0000000000000000000000000000000001"), + ]: + tx = TempoTransaction.create( + chain_id=w3.eth.chain_id, + gas_limit=300_000, + max_fee_per_gas=w3.eth.gas_price * 2, + max_priority_fee_per_gas=w3.eth.gas_price, + nonce=0, + nonce_key=nonce_key, # [!code focus] + calls=(Call.create(to=to, data=data),), + ) + signed_tx = tx.sign(account.key.hex()) + w3.eth.send_raw_transaction(signed_tx.encode()) + ``` + + ```python [provider.py] + from web3 import Web3 + from eth_account import Account + + w3 = Web3(Web3.HTTPProvider("https://rpc.tempo.xyz")) + account = Account.from_key("0x...") + ``` + + ::: + + + + + + :::code-group + + ```go [main.go] + package main + + import ( + "context" + "log" + "math/big" + "sync" + + "github.com/ethereum/go-ethereum/common" + "github.com/tempoxyz/tempo-go/pkg/signer" + "github.com/tempoxyz/tempo-go/pkg/transaction" + ) + + func main() { + sgn, _ := signer.NewSigner("0x...") + c := newClient() + ctx := context.Background() + + type txParams struct { + nonceKey int64 + to string + data string + } + params := []txParams{ + {1, "0xcafebabecafebabecafebabecafebabecafebabe", "deadbeef0000000000000000000000000000000001"}, + {2, "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef", "cafebabe0000000000000000000000000000000001"}, + {3, "0xcafebabecafebabecafebabecafebabecafebabe", "deadbeef0000000000000000000000000000000001"}, + } + + var wg sync.WaitGroup + for _, p := range params { + wg.Add(1) + go func(p txParams) { + defer wg.Done() + tx := transaction.NewBuilder(big.NewInt(transaction.ChainIdMainnet)). + SetNonce(0). + SetNonceKey(big.NewInt(p.nonceKey)). // [!code focus] + SetGas(300_000). + SetMaxFeePerGas(big.NewInt(25_000_000_000)). + SetMaxPriorityFeePerGas(big.NewInt(1_000_000_000)). + AddCall( + common.HexToAddress(p.to), + big.NewInt(0), + common.Hex2Bytes(p.data), + ). + Build() + + _ = transaction.SignTransaction(tx, sgn) + serialized, _ := transaction.Serialize(tx, nil) + txHash, _ := c.SendRawTransaction(ctx, serialized) + + log.Printf("Nonce key %d tx: %s", p.nonceKey, txHash) + }(p) + } + wg.Wait() + } + ``` + + ```go [provider.go] + // [!include ~/snippets/go-provider.go:setup] + ``` + + ::: + + + + + + ```bash + $ cast send 0xcafebabecafebabecafebabecafebabecafebabe \ + --data 0xdeadbeef0000000000000000000000000000000001 \ + --rpc-url $TEMPO_RPC_URL \ + --private-key $PRIVATE_KEY \ + --nonce 0 --tempo.nonce-key 1 # [!code hl] + + $ cast send 0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef \ + --data 0xcafebabe0000000000000000000000000000000001 \ + --rpc-url $TEMPO_RPC_URL \ + --private-key $PRIVATE_KEY \ + --nonce 0 --tempo.nonce-key 2 # [!code hl] + + $ cast send 0xcafebabecafebabecafebabecafebabecafebabe \ + --data 0xdeadbeef0000000000000000000000000000000001 \ + --rpc-url $TEMPO_RPC_URL \ + --private-key $PRIVATE_KEY \ + --nonce 0 --tempo.nonce-key 3 # [!code hl] + ``` + + + ```tsx @@ -811,6 +2098,172 @@ the transaction can be included in a block. + + + :::code-group + + ```rust [example.rs] + use alloy::primitives::{address, bytes}; + use alloy::providers::Provider; + use tempo_alloy::rpc::TempoTransactionRequest; + + mod provider; + + #[tokio::main] + async fn main() -> Result<(), Box> { + let provider = provider::get_provider().await?; + + // 2026-01-01 00:00:00 UTC + let valid_after = 1_767_225_600; + // 2026-01-02 00:00:00 UTC + let valid_before = 1_767_312_000; + + let pending = provider + .send_transaction( + TempoTransactionRequest::default() + .with_valid_after(valid_after) // [!code hl] + .with_valid_before(valid_before) // [!code hl] + .with_to(address!("0xcafebabecafebabecafebabecafebabecafebabe")) + .with_input(bytes!("deadbeef0000000000000000000000000000000001")), + ) + .await?; + + Ok(()) + } + ``` + + ```rust [provider.rs] + // [!include ~/snippets/rust-signer-provider.rs:setup] + ``` + + ::: + + + + + + :::code-group + + ```python [example.py] + from datetime import datetime, timezone + + from pytempo import Call, TempoTransaction + from provider import w3, account + + # 2026-01-01 00:00:00 UTC + valid_after = int(datetime(2026, 1, 1, tzinfo=timezone.utc).timestamp()) + # 2026-01-02 00:00:00 UTC + valid_before = int(datetime(2026, 1, 2, tzinfo=timezone.utc).timestamp()) + + tx = TempoTransaction.create( + chain_id=w3.eth.chain_id, + gas_limit=300_000, + max_fee_per_gas=w3.eth.gas_price * 2, + max_priority_fee_per_gas=w3.eth.gas_price, + nonce=w3.eth.get_transaction_count(account.address), + valid_after=valid_after, # [!code hl] + valid_before=valid_before, # [!code hl] + calls=( + Call.create( + to="0xcafebabecafebabecafebabecafebabecafebabe", + data="0xdeadbeef0000000000000000000000000000000001", + ), + ), + ) + + # Sign now, submit to the network for later execution + signed_tx = tx.sign(account.key.hex()) + tx_hash = w3.eth.send_raw_transaction(signed_tx.encode()) + ``` + + ```python [provider.py] + from web3 import Web3 + from eth_account import Account + + w3 = Web3(Web3.HTTPProvider("https://rpc.tempo.xyz")) + account = Account.from_key("0x...") + ``` + + ::: + + + + + + :::code-group + + ```go [main.go] + package main + + import ( + "context" + "log" + "math/big" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/tempoxyz/tempo-go/pkg/signer" + "github.com/tempoxyz/tempo-go/pkg/transaction" + ) + + func main() { + sgn, _ := signer.NewSigner("0x...") + c := newClient() + ctx := context.Background() + + nonce, _ := c.GetTransactionCount(ctx, sgn.Address().Hex()) + + // 2026-01-01 00:00:00 UTC + validAfter := uint64(time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC).Unix()) + // 2026-01-02 00:00:00 UTC + validBefore := uint64(time.Date(2026, 1, 2, 0, 0, 0, 0, time.UTC).Unix()) + + tx := transaction.NewBuilder(big.NewInt(transaction.ChainIdMainnet)). + SetNonce(nonce). + SetGas(300_000). + SetMaxFeePerGas(big.NewInt(25_000_000_000)). + SetMaxPriorityFeePerGas(big.NewInt(1_000_000_000)). + SetValidAfter(validAfter). // [!code hl] + SetValidBefore(validBefore). // [!code hl] + AddCall( + common.HexToAddress("0xcafebabecafebabecafebabecafebabecafebabe"), + big.NewInt(0), + common.Hex2Bytes("deadbeef0000000000000000000000000000000001"), + ). + Build() + + // Sign now, submit to the network for later execution + _ = transaction.SignTransaction(tx, sgn) + serialized, _ := transaction.Serialize(tx, nil) + txHash, _ := c.SendRawTransaction(ctx, serialized) + + log.Printf("Transaction hash: %s", txHash) + } + ``` + + ```go [provider.go] + // [!include ~/snippets/go-provider.go:setup] + ``` + + ::: + + + + + + ```bash + $ VALID_AFTER=$(date -d '2026-01-01' +%s) + $ VALID_BEFORE=$(date -d '2026-01-02' +%s) + $ cast mktx 0xcafebabecafebabecafebabecafebabecafebabe \ + --data 0xdeadbeef0000000000000000000000000000000001 \ + --rpc-url $TEMPO_RPC_URL \ + --private-key $PRIVATE_KEY \ + --tempo.valid-after $VALID_AFTER \ + --tempo.valid-before $VALID_BEFORE # [!code hl] + ``` + + + ```tsx diff --git a/src/snippets/unformatted/withFeePayer.ts b/src/snippets/unformatted/withFeePayer.ts index 55d533cf..2ec49c4e 100644 --- a/src/snippets/unformatted/withFeePayer.ts +++ b/src/snippets/unformatted/withFeePayer.ts @@ -5,7 +5,7 @@ import { withFeePayer } from 'viem/tempo' const _client = createClient({ account: privateKeyToAccount('0x...'), - chain: tempoModerato, + chain: tempo, transport: withFeePayer( // [!code hl] http(), // [!code hl] @@ -34,10 +34,10 @@ import { Handler } from 'tempo.ts/server' // [!region server] import { createClient, http } from 'viem' import { privateKeyToAccount } from 'viem/accounts' -import { tempoModerato } from 'viem/chains' +import { tempo } from 'viem/chains' const client = createClient({ - chain: tempoModerato.extend({ + chain: tempo.extend({ feeToken: '0x20c0000000000000000000000000000000000001', }), transport: http(), diff --git a/src/snippets/viem.config.ts b/src/snippets/viem.config.ts index 8b92be10..5404b57c 100644 --- a/src/snippets/viem.config.ts +++ b/src/snippets/viem.config.ts @@ -1,12 +1,12 @@ // [!region setup] import { createClient, http, publicActions, walletActions } from 'viem' import { privateKeyToAccount } from 'viem/accounts' -import { tempoModerato } from 'viem/chains' +import { tempo } from 'viem/chains' import { tempoActions } from 'viem/tempo' export const client = createClient({ account: privateKeyToAccount('0x...'), - chain: tempoModerato, + chain: tempo, transport: http(), }) .extend(publicActions) diff --git a/src/snippets/wagmi.config.ts b/src/snippets/wagmi.config.ts index 2620b8b1..0fce1d1f 100644 --- a/src/snippets/wagmi.config.ts +++ b/src/snippets/wagmi.config.ts @@ -4,7 +4,7 @@ import { KeyManager, webAuthn } from 'tempo.ts/wagmi' // [!region setup] -import { tempoModerato } from 'viem/chains' +import { tempo } from 'viem/chains' import { createConfig, http } from 'wagmi' import { KeyManager, webAuthn } from 'wagmi/tempo' @@ -14,10 +14,10 @@ export const config = createConfig({ keyManager: KeyManager.http('https://keys.tempo.xyz'), }), ], - chains: [tempoModerato], + chains: [tempo], multiInjectedProviderDiscovery: false, transports: { - [tempoModerato.id]: http(), + [tempo.id]: http(), }, }) @@ -25,7 +25,7 @@ export const config = createConfig({ import { KeyManager, webAuthn } from 'tempo.ts/wagmi' // [!region withFeePayer] -import { tempoModerato } from 'viem/chains' +import { tempo } from 'viem/chains' import { withFeePayer } from 'viem/tempo' import { createConfig, http } from 'wagmi' import { KeyManager, webAuthn } from 'wagmi/tempo' @@ -36,10 +36,10 @@ export const config = createConfig({ keyManager: KeyManager.http('https://keys.tempo.xyz'), }), ], - chains: [tempoModerato], + chains: [tempo], multiInjectedProviderDiscovery: false, transports: { - [tempoModerato.id]: withFeePayer(http(), http('https://sponsor.moderato.tempo.xyz')), + [tempo.id]: withFeePayer(http(), http('https://sponsor.moderato.tempo.xyz')), }, }) // [!endregion withFeePayer] diff --git a/vercel.json b/vercel.json index 2c687922..4bc4289f 100644 --- a/vercel.json +++ b/vercel.json @@ -8,7 +8,7 @@ "value": "faucet.tempo.xyz" } ], - "destination": "https://docs.tempo.xyz/quickstart/faucet", + "destination": "https://docs.tempo.xyz/guide/getting-funds", "permanent": false }, { @@ -19,7 +19,7 @@ "value": "faucet.tempo.xyz" } ], - "destination": "https://docs.tempo.xyz/quickstart/faucet", + "destination": "https://docs.tempo.xyz/guide/getting-funds", "permanent": false } ], diff --git a/vocs.config.ts b/vocs.config.ts index 9ebdd607..ab5a5340 100644 --- a/vocs.config.ts +++ b/vocs.config.ts @@ -40,9 +40,11 @@ export default defineConfig({ path === '/' ? `${baseUrl}/og-docs.png` : `${baseUrl}/api/og?title=%title&description=%description`, + // TODO: Change back to file paths (`/lockup-light.svg`, `/lockup-dark.svg`) once password protection is removed logoUrl: { - light: '/lockup-light.svg', - dark: '/lockup-dark.svg', + light: + 'data:image/svg+xml,%3Csvg%20width%3D%22184%22%20height%3D%2241%22%20viewBox%3D%220%200%20184%2041%22%20fill%3D%22none%22%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%3E%0A%3Cpath%20d%3D%22M13.6424%2040.3635H2.80251L12.8492%209.60026H0L2.80251%200.58344H38.6006L35.7981%209.60026H23.6362L13.6424%2040.3635Z%22%20fill%3D%22black%22/%3E%0A%3Cpath%20d%3D%22M53.9809%2040.3635H28.2824L41.1846%200.58344H66.7773L64.3449%208.16818H49.4863L46.7896%2016.7076H61.1723L58.7399%2024.1863H44.3043L41.6076%2032.7788H56.3604L53.9809%2040.3635Z%22%20fill%3D%22black%22/%3E%0A%3Cpath%20d%3D%22M65.6123%2040.3635H56.9933L69.9483%200.58344H84.331L83.8551%2022.0647L97.8676%200.58344H113.625L100.723%2040.3635H89.936L98.5021%2013.6313H98.3435L80.7353%2040.3635H74.3371L74.6015%2013.3131H74.4957L65.6123%2040.3635Z%22%20fill%3D%22black%22/%3E%0A%3Cpath%20d%3D%22M125.758%207.95602L121.581%2020.7917H122.744C125.388%2020.7917%20127.592%2020.1729%20129.354%2018.9353C131.117%2017.6624%20132.262%2015.859%20132.791%2013.5252C133.249%2011.5097%20133.003%2010.0776%20132.051%209.22898C131.099%208.38034%20129.513%207.95602%20127.292%207.95602H125.758ZM115.289%2040.3635H104.449L117.351%200.58344H130.517C133.549%200.58344%20136.158%201.07848%20138.343%202.06856C140.564%203.02328%20142.186%204.40233%20143.208%206.20569C144.266%207.97369%20144.618%2010.0423%20144.266%2012.4114C143.807%2015.5231%20142.609%2018.2635%20140.67%2020.6326C138.731%2023.0017%20136.211%2024.8405%20133.108%2026.1488C130.042%2027.4217%20126.604%2028.0582%20122.797%2028.0582H119.255L115.289%2040.3635Z%22%20fill%3D%22black%22/%3E%0A%3Cpath%20d%3D%22M170.103%2037.8176C166.507%2039.9392%20162.682%2041%20158.628%2041H158.523C154.927%2041%20151.895%2040.2044%20149.428%2038.6132C146.995%2036.9866%20145.25%2034.7943%20144.193%2032.0362C143.171%2029.2781%20142.924%2026.2549%20143.453%2022.9664C144.122%2018.8292%20145.656%2015.0103%20148.053%2011.5097C150.45%208.00906%20153.446%205.21561%20157.042%203.12937C160.638%201.04312%20164.48%200%20168.569%200H168.675C172.412%200%20175.496%200.795602%20177.929%202.38681C180.396%203.97801%20182.106%206.15265%20183.058%208.91074C184.045%2011.6335%20184.256%2014.6921%20183.692%2018.0867C183.023%2022.0824%20181.489%2025.8482%20179.092%2029.3842C176.695%2032.8849%20173.699%2035.696%20170.103%2037.8176ZM155.138%2030.9754C156.09%2032.7788%20157.747%2033.6805%20160.109%2033.6805H160.215C162.154%2033.6805%20163.951%2032.9556%20165.608%2031.5058C167.3%2030.0207%20168.728%2028.0405%20169.891%2025.5653C171.09%2023.0901%20171.971%2020.332%20172.535%2017.2911C173.064%2014.3208%20172.852%2011.934%20171.901%2010.1307C170.949%208.29194%20169.31%207.37257%20166.983%207.37257H166.877C165.079%207.37257%20163.335%208.11514%20161.642%209.60026C159.986%2011.0854%20158.54%2013.0832%20157.306%2015.5938C156.073%2018.1044%20155.174%2020.8271%20154.61%2023.762C154.046%2026.7322%20154.222%2029.1367%20155.138%2030.9754Z%22%20fill%3D%22black%22/%3E%0A%3C/svg%3E', + dark: 'data:image/svg+xml,%3Csvg%20width%3D%22184%22%20height%3D%2241%22%20viewBox%3D%220%200%20184%2041%22%20fill%3D%22none%22%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%3E%0A%3Cpath%20d%3D%22M13.6424%2040.3635H2.80251L12.8492%209.60026H0L2.80251%200.58344H38.6006L35.7981%209.60026H23.6362L13.6424%2040.3635Z%22%20fill%3D%22white%22/%3E%0A%3Cpath%20d%3D%22M53.9809%2040.3635H28.2824L41.1846%200.58344H66.7773L64.3449%208.16818H49.4863L46.7896%2016.7076H61.1723L58.7399%2024.1863H44.3043L41.6076%2032.7788H56.3604L53.9809%2040.3635Z%22%20fill%3D%22white%22/%3E%0A%3Cpath%20d%3D%22M65.6123%2040.3635H56.9933L69.9483%200.58344H84.331L83.8551%2022.0647L97.8676%200.58344H113.625L100.723%2040.3635H89.936L98.5021%2013.6313H98.3435L80.7353%2040.3635H74.3371L74.6015%2013.3131H74.4957L65.6123%2040.3635Z%22%20fill%3D%22white%22/%3E%0A%3Cpath%20d%3D%22M125.758%207.95602L121.581%2020.7917H122.744C125.388%2020.7917%20127.592%2020.1729%20129.354%2018.9353C131.117%2017.6624%20132.262%2015.859%20132.791%2013.5252C133.249%2011.5097%20133.003%2010.0776%20132.051%209.22898C131.099%208.38034%20129.513%207.95602%20127.292%207.95602H125.758ZM115.289%2040.3635H104.449L117.351%200.58344H130.517C133.549%200.58344%20136.158%201.07848%20138.343%202.06856C140.564%203.02328%20142.186%204.40233%20143.208%206.20569C144.266%207.97369%20144.618%2010.0423%20144.266%2012.4114C143.807%2015.5231%20142.609%2018.2635%20140.67%2020.6326C138.731%2023.0017%20136.211%2024.8405%20133.108%2026.1488C130.042%2027.4217%20126.604%2028.0582%20122.797%2028.0582H119.255L115.289%2040.3635Z%22%20fill%3D%22white%22/%3E%0A%3Cpath%20d%3D%22M170.103%2037.8176C166.507%2039.9392%20162.682%2041%20158.628%2041H158.523C154.927%2041%20151.895%2040.2044%20149.428%2038.6132C146.995%2036.9866%20145.25%2034.7943%20144.193%2032.0362C143.171%2029.2781%20142.924%2026.2549%20143.453%2022.9664C144.122%2018.8292%20145.656%2015.0103%20148.053%2011.5097C150.45%208.00906%20153.446%205.21561%20157.042%203.12937C160.638%201.04312%20164.48%200%20168.569%200H168.675C172.412%200%20175.496%200.795602%20177.929%202.38681C180.396%203.97801%20182.106%206.15265%20183.058%208.91074C184.045%2011.6335%20184.256%2014.6921%20183.692%2018.0867C183.023%2022.0824%20181.489%2025.8482%20179.092%2029.3842C176.695%2032.8849%20173.699%2035.696%20170.103%2037.8176ZM155.138%2030.9754C156.09%2032.7788%20157.747%2033.6805%20160.109%2033.6805H160.215C162.154%2033.6805%20163.951%2032.9556%20165.608%2031.5058C167.3%2030.0207%20168.728%2028.0405%20169.891%2025.5653C171.09%2023.0901%20171.971%2020.332%20172.535%2017.2911C173.064%2014.3208%20172.852%2011.934%20171.901%2010.1307C170.949%208.29194%20169.31%207.37257%20166.983%207.37257H166.877C165.079%207.37257%20163.335%208.11514%20161.642%209.60026C159.986%2011.0854%20158.54%2013.0832%20157.306%2015.5938C156.073%2018.1044%20155.174%2020.8271%20154.61%2023.762C154.046%2026.7322%20154.222%2029.1367%20155.138%2030.9754Z%22%20fill%3D%22white%22/%3E%0A%3C/svg%3E', }, iconUrl: { light: '/icon-light.png', @@ -70,53 +72,16 @@ export default defineConfig({ link: '/changelog', }, { - text: 'Integrate Tempo Testnet', - items: [ - { - text: 'Overview', - link: '/quickstart/integrate-tempo', - }, - { - text: 'Connect to the Network', - link: '/quickstart/connection-details', - }, - { - text: 'Get Faucet Funds', - link: '/quickstart/faucet', - }, - { - text: 'Developer Tools', - link: '/quickstart/developer-tools', - }, - { - text: 'EVM Differences', - link: '/quickstart/evm-compatibility', - }, - { - text: 'Predeployed Contracts', - link: '/quickstart/predeployed-contracts', - }, - { - text: 'Token List Registry', - link: '/quickstart/tokenlist', - }, - { - text: 'Wallet Developers', - link: '/quickstart/wallet-developers', - }, - { - text: 'Contract Verification', - link: '/quickstart/verify-contracts', - }, - { - text: 'Building with AI', - link: '/guide/building-with-ai', - }, - ], + text: 'Using Tempo with AI', + link: '/guide/using-tempo-with-ai', }, { - text: 'Start Building on Tempo', + text: 'Build on Tempo', items: [ + { + text: 'Getting Funds on Tempo', + link: '/guide/getting-funds', + }, { text: 'Use Tempo Transactions', link: '/guide/tempo-transaction', @@ -243,6 +208,119 @@ export default defineConfig({ }, ], }, + { + text: 'Make Machine Payments', + collapsed: true, + items: [ + { + text: 'Overview', + link: '/guide/machine-payments', + }, + { + text: 'Client quickstart', + link: '/guide/machine-payments/client', + }, + { + text: 'Server quickstart', + link: '/guide/machine-payments/server', + }, + { + text: 'Accept one-time payments', + link: '/guide/machine-payments/one-time-payments', + }, + { + text: 'Accept pay-as-you-go payments', + link: '/guide/machine-payments/pay-as-you-go', + }, + { + text: 'Accept streamed payments', + link: '/guide/machine-payments/streamed-payments', + }, + ], + }, + ], + }, + { + text: 'Integrate Tempo', + items: [ + { + text: 'Overview', + link: '/quickstart/integrate-tempo', + }, + { + text: 'Connect to the Network', + link: '/quickstart/connection-details', + }, + { + text: 'Get Faucet Funds', + link: '/quickstart/faucet', + }, + { + text: 'Developer Tools', + link: '/ecosystem', + }, + { + text: 'EVM Differences', + link: '/quickstart/evm-compatibility', + }, + { + text: 'Predeployed Contracts', + link: '/quickstart/predeployed-contracts', + }, + { + text: 'Token List Registry', + link: '/quickstart/tokenlist', + }, + { + text: 'Wallet Developers', + link: '/quickstart/wallet-developers', + }, + { + text: 'Contract Verification', + link: '/quickstart/verify-contracts', + }, + ], + }, + { + text: 'Tempo Ecosystem Infrastructure', + collapsed: true, + items: [ + { + text: 'Overview', + link: '/ecosystem', + }, + { + text: 'Bridges', + link: '/ecosystem/bridges', + }, + { + text: 'Data & Analytics', + link: '/ecosystem/data-analytics', + }, + { + text: 'Block Explorers', + link: '/ecosystem/block-explorers', + }, + { + text: 'Wallets', + link: '/ecosystem/wallets', + }, + { + text: 'Smart Contract Libraries', + link: '/ecosystem/smart-contract-libraries', + }, + { + text: 'Node Infrastructure', + link: '/ecosystem/node-infrastructure', + }, + { + text: 'Security & Compliance', + link: '/ecosystem/security-compliance', + }, + { + text: 'Orchestration', + link: '/ecosystem/orchestration', + }, ], }, { @@ -443,78 +521,114 @@ export default defineConfig({ ], }, { - text: 'Tempo SDKs', + text: 'Tempo Developer Tools', collapsed: true, items: [ { - text: 'Overview', - link: '/sdk', - }, - { - text: 'TypeScript', + text: 'CLI', collapsed: true, items: [ { text: 'Overview', - link: '/sdk/typescript', + link: '/cli', + }, + { + text: 'Wallet', + link: '/cli/wallet', }, { - text: 'Viem Reference', - link: 'https://viem.sh/tempo', + text: 'Request', + link: '/cli/request', }, { - text: 'Wagmi Reference', - link: 'https://wagmi.sh/tempo', + text: 'Download', + link: '/cli/download', }, { - text: 'Server Reference', + text: 'Node', + link: '/cli/node', + }, + ], + }, + { + text: 'SDKs', + collapsed: true, + items: [ + { + text: 'Overview', + link: '/sdk', + }, + { + text: 'TypeScript', + collapsed: true, items: [ { - text: 'Handlers', + text: 'Overview', + link: '/sdk/typescript', + }, + { + text: 'Viem Reference', + link: 'https://viem.sh/tempo', + }, + { + text: 'Wagmi Reference', + link: 'https://wagmi.sh/tempo', + }, + { + text: 'Server Reference', items: [ { - text: 'Overview', - link: '/sdk/typescript/server/handlers', - }, - { - text: 'compose', - link: '/sdk/typescript/server/handler.compose', - }, - { - text: 'feePayer', - link: '/sdk/typescript/server/handler.feePayer', + text: 'Handlers', + items: [ + { + text: 'Overview', + link: '/sdk/typescript/server/handlers', + }, + { + text: 'compose', + link: '/sdk/typescript/server/handler.compose', + }, + { + text: 'feePayer', + link: '/sdk/typescript/server/handler.feePayer', + }, + { + text: 'keyManager', + link: '/sdk/typescript/server/handler.keyManager', + }, + ], }, + ], + }, + { + text: 'Prool Reference', + items: [ { - text: 'keyManager', - link: '/sdk/typescript/server/handler.keyManager', + text: 'Setup', + link: '/sdk/typescript/prool/setup', }, ], }, ], }, { - text: 'Prool Reference', - items: [ - { - text: 'Setup', - link: '/sdk/typescript/prool/setup', - }, - ], + text: 'Go', + link: '/sdk/go', + }, + { + text: 'Foundry', + link: '/sdk/foundry', + }, + { + text: 'Python', + link: '/sdk/python', + }, + { + text: 'Rust', + link: '/sdk/rust', }, ], }, - { - text: 'Go', - link: '/sdk/go', - }, - { - text: 'Foundry', - link: '/sdk/foundry', - }, - { - text: 'Rust', - link: '/sdk/rust', - }, ], }, { @@ -678,10 +792,19 @@ export default defineConfig({ destination: '/stablecoin-dex/:path*', status: 301, }, + { + source: '/quickstart/developer-tools', + destination: '/ecosystem', + status: 301, + }, { source: '/guide/ai-support', destination: '/guide/building-with-ai', }, + { + source: '/guide/building-with-ai', + destination: '/guide/using-tempo-with-ai', + }, { source: '/guide', destination: '/quickstart/integrate-tempo', @@ -718,6 +841,26 @@ export default defineConfig({ source: '/sdk/typescript/prool', destination: '/sdk/typescript/prool/setup', }, + { + source: '/wallet', + destination: '/cli', + status: 301, + }, + { + source: '/wallet/reference', + destination: '/cli/wallet', + status: 301, + }, + { + source: '/wallet/:path*', + destination: '/cli/:path*', + status: 301, + }, + { + source: '/cli/reference', + destination: '/cli/wallet', + status: 301, + }, { source: '/guide/use-accounts/fee-sponsorship', destination: '/guide/payments/sponsor-user-fees',