# Documentation ⋅ Tempo > Documentation for Tempo testnet and protocol specifications import LucideCode from '~icons/lucide/code' import LucideHammer from '~icons/lucide/hammer' import LucideCog from '~icons/lucide/cog' import LucideBox from '~icons/lucide/box' import * as Card from "../../components/Card.tsx" ### SDKs Tempo is building clients in multiple languages to make integration as easy as possible.

Get in touch with us to request language support or contribute directly.

import LucideWorkflow from '~icons/lucide/workflow' import LucideNetwork from '~icons/lucide/network' import LucideSquareStack from '~icons/lucide/square-stack' import LucideSendToBack from '~icons/lucide/send-to-back' import * as Card from "../../../components/Card.tsx" ## TypeScript SDKs :::note **Note:** `tempo.ts/chains` & `tempo.ts/viem` have been upstreamed into [Viem](https://viem.sh/tempo) as of `viem@2.43.0`. If you are using either entrypoint, please update to use `viem/chains` or `viem/tempo` instead. [Migration Guide](https://github.com/tempoxyz/tempo-ts/releases/tag/tempo.ts%400.12.0) ::: Tempo distributes TypeScript SDKs for: * [Viem](https://viem.sh): TypeScript interface for EVM blockchains * [Wagmi](https://wagmi.sh): React Hooks (and reactive primitives) for EVM blockchains The Tempo extensions can be used to perform common operations with the chain, such as: querying the chain, sending Tempo transactions, managing tokens & their AMM pools, and more. ( <> Viem Viem )} title="Viem Setup" /> Wagmi } title="Wagmi Setup" /> :::tip **When should I use Wagmi vs. Viem?** * **Viem** is best suited for **libraries, tooling, servers, scripting, etc** – a low-level and stateless interface for the EVM * **Wagmi** is best suited for **applications & wallets** – a high-level and stateful interface for the EVM (React Hooks, Vanilla JS, etc) ::: ### Viem ### Wagmi ## Setup Setup the Tempo extension for Wagmi by following the steps below. ::::steps ### Install To add Wagmi to your project, install the required packages: :::code-group ```bash [npm] npm i tempo.ts wagmi viem @tanstack/react-query ``` ```bash [pnpm] pnpm i tempo.ts wagmi viem @tanstack/react-query ``` ::: * [Viem](https://viem.sh) is a TypeScript interface for the EVM that performs blockchain operations. * [TanStack Query](https://tanstack.com/query/v5) is an async state manager that handles requests, caching, and more. ### Create Config Create and export a new Wagmi config using `createConfig`. ```tsx twoslash // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` In this example, Wagmi is configured to use the Tempo chain and `webAuthn` connector to enable Passkeys. Check out the [`createConfig` docs](https://wagmi.sh/react/api/createConfig) for more configuration options. :::tip It is highly recommended to set a `feeToken` on the `tempo` chain, which will be used as the default fee token for all transactions. If you do not wish to use a default fee token, you can set it to `null` to opt in to Tempo's [default fee token preferences](/protocol/fees/spec-fee#fee-token-preferences). ::: ### Wrap App in Context Provider Wrap your app in the `WagmiProvider` React Context Provider and pass the `config` you created earlier to the `config` property. :::code-group ```tsx [app.tsx] import { WagmiProvider } from 'wagmi' // [!code focus] import { config } from './config' // [!code focus] function App() { return ( {/* [!code focus] */} {/** ... */} // [!code focus] ) } ``` ```tsx [config.ts] // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: Check out the [`WagmiProvider` docs](https://wagmi.sh/react/api/WagmiProvider) to learn more about React Context in Wagmi. ### Setup TanStack Query Inside the `WagmiProvider`, wrap your app in a TanStack Query React Context Provider, e.g. `QueryClientProvider`, and pass a new `QueryClient` instance to the `client` property. :::code-group ```tsx [app.tsx] import { QueryClient, QueryClientProvider } from '@tanstack/react-query' // [!code focus] import { WagmiProvider } from 'wagmi' import { config } from './config' const queryClient = new QueryClient() // [!code focus] function App() { return ( {/* [!code focus] */} {/** ... */} {/* [!code focus] */} ) } ``` ```tsx [config.ts] // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: Check out the [TanStack Query docs](https://tanstack.com/query/latest/docs/framework/react/overview) to learn about the library, APIs, and more. ### Use Wagmi Hooks Now that everything is set up, we can now use regular Wagmi Hooks (e.g. `useSendTransactionSync`) that are decorated with [Tempo properties](/protocol/transactions/spec-tempo-transaction) like `calls` (batch transactions), `feePayer` (fee sponsorship), `nonceKey` (concurrent transactions) and more! :::code-group ```tsx [balance.tsx] import { useAccount, useSendTransactionSync } from 'wagmi' export function TokenMetadata() { const { sendTransactionSync } = useSendTransactionSync() return ( ) } ``` ```tsx [app.tsx] import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { WagmiProvider } from 'wagmi' import { config } from './config' import { Profile } from './profile' const queryClient = new QueryClient() function App() { return ( ) } ``` ```tsx [config.ts] // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Use Tempo Hooks You can also use [Tempo-specific Hooks](/sdk/typescript/wagmi/hooks): :::code-group ```tsx [balance.tsx] import { useAccount } from 'wagmi' import { Hooks } from 'tempo.ts/wagmi' const alphaUsd = '0x20c0000000000000000000000000000000000001' export function TokenMetadata() { const { data: metadata, ...metadataQuery } = Hooks.token.useGetMetadata({ token: alphaUsd }) if (metadataQuery.isError) return
Error fetching metadata: {metadataQuery.error.message}
if (metadataQuery.isLoading) return
Loading metadata...
return
{metadata.name} ({metadata.symbol})
} ``` ```tsx [app.tsx] import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { WagmiProvider } from 'wagmi' import { config } from './config' import { Profile } from './profile' const queryClient = new QueryClient() function App() { return ( ) } ``` ```tsx [config.ts] // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Next Steps After you have set up your Tempo with Wagmi, you can now: * Follow a guide on how to [use accounts](/guide/use-accounts), [make payments](/guide/payments), [issue stablecoins](/guide/issuance), [exchange stablecoins](/guide/stablecoin-exchange), and [more](/). * Use the [suite of Tempo Hooks](/sdk/typescript/wagmi/hooks) :::: ## `KeyManager.http` Manages public key registrations remotely on a server. ### Usage ```ts twoslash [wagmi.config.ts] // @noErrors import { tempoTestnet } from 'viem/chains' import { KeyManager, webAuthn } from 'tempo.ts/wagmi' // [!code focus] import { createConfig, http } from 'wagmi' export const config = createConfig({ connectors: [webAuthn({ keyManager: KeyManager.http('/keys'), // [!code focus] })], chains: [tempoTestnet], multiInjectedProviderDiscovery: false, transports: { [tempoTestnet.id]: http(), }, }) ``` :::tip In order for the above code snippet to work, you need to have a server running at the provided URL. Below is an example using a [Cloudflare Worker](https://developers.cloudflare.com/workers/) (but can work with any server runtime). [See more](/sdk/typescript/server/handler.keyManager) ```ts twoslash // @noErrors import { env } from 'cloudflare:workers' import { Handler, Kv } from 'tempo.ts/server' export default { fetch(request) { return Handler.keyManager({ kv: Kv.cloudflare(env.KEY_STORE), path: '/keys', }).fetch(request) }, } satisfies ExportedHandler ``` ::: ### Parameters #### url * **Type:** `string` URL to the key manager server. #### options ##### options.fetch * **Type:** `typeof globalThis.fetch` * **Default:** `globalThis.fetch` Fetch function to use for the key manager server. import LucideServer from '~icons/lucide/server' import LucideHardDrive from '~icons/lucide/hard-drive' import * as Card from "../../../../../components/Card.tsx" ## Overview WebAuthn-based accounts in Tempo require the public key to be attached to transactions and other protocol features. However, **it is not possible to extract a public key from a WebAuthn credential after its registration**. To solve this, we maintain a `credentialId → publicKey` mapping that stores the public key when the credential is first created. Key Managers are responsible for managing this mapping, allowing users to access their accounts from any device without losing their public key. ## `KeyManager.localStorage` Manages public key registrations in local storage on the client device. :::warning The `KeyManager.localStorage()` implementation is not recommended for production use as it stores public keys on the client device, meaning it cannot be re-extracted when the user's storage is cleared or if the user is on another device. For production, you should opt for a remote key manager such as [`KeyManager.http`](/sdk/typescript/wagmi/keyManagers/http). ::: ### Usage ```ts twoslash [wagmi.config.ts] // @noErrors import { tempoTestnet } from 'viem/chains' import { KeyManager, webAuthn } from 'tempo.ts/wagmi' // [!code focus] import { createConfig, http } from 'wagmi' export const config = createConfig({ connectors: [webAuthn({ keyManager: KeyManager.localStorage(), // [!code focus] })], chains: [tempoTestnet], multiInjectedProviderDiscovery: false, transports: { [tempoTestnet.id]: http(), }, }) ``` ## `amm.useBurn` Burns liquidity tokens and receives the underlying token pair. [Learn more about the Fee AMM](/protocol/fees/spec-fee-amm) ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' import { parseUnits } from 'viem' const { data: result, mutate } = Hooks.amm.useBurnSync() // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ liquidity: parseUnits('10.5', 18), to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', userToken: '0x20c0000000000000000000000000000000000000', validatorToken: '0x20c0000000000000000000000000000000000001', }) console.log('Received user tokens:', result.amountUserToken) // @log: Received user tokens: 5250000000000000000n console.log('Received validator tokens:', result.amountValidatorToken) // @log: Received validator tokens: 5250000000000000000n ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `amm.burn` action and wait for inclusion manually: ```ts twoslash // @errors: 2322 declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' import { Actions } from 'viem/tempo' import { parseUnits } from 'viem' import { useWaitForTransactionReceipt } from 'wagmi' import { config } from './wagmi.config' const { data: hash, mutate } = Hooks.amm.useBurn() const { data: receipt } = useWaitForTransactionReceipt({ hash }) // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ liquidity: parseUnits('10.5', 18), to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', userToken: '0x20c0000000000000000000000000000000000000', validatorToken: '0x20c0000000000000000000000000000000000001', }) if (receipt) { const { args: { amountUserToken, amountValidatorToken } } = Actions.amm.burn.extractEvent(receipt.logs) } ``` ### Return Type See [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook return types. #### data See [Wagmi Action `amm.burn` Return Type](/sdk/typescript/wagmi/actions/amm.burn#return-type) #### mutate/mutateAsync See [Wagmi Action `amm.burn` Parameters](/sdk/typescript/wagmi/actions/amm.burn#parameters) ### Parameters #### config `Config | undefined` [`Config`](https://wagmi.sh/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](https://wagmi.sh/react/api/WagmiProvider). #### mutation See the [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook parameters. ## `amm.useLiquidityBalance` Gets the liquidity balance for an address in a specific pool. ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' const { data: balance } = Hooks.amm.useLiquidityBalance({ address: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', userToken: '0x20c0000000000000000000000000000000000000', validatorToken: '0x20c0000000000000000000000000000000000001', }) console.log('Liquidity balance:', balance) // @log: Liquidity balance: 10500000000000000000n ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Return Type See [TanStack Query query docs](https://tanstack.com/query/v5/docs/framework/react/reference/useQuery) for more info hook return types. #### data See [Wagmi Action `amm.getLiquidityBalance` Return Type](/sdk/typescript/wagmi/actions/amm.getLiquidityBalance#return-type) ### Parameters See [Wagmi Action `amm.getLiquidityBalance` Parameters](/sdk/typescript/wagmi/actions/amm.getLiquidityBalance#parameters) #### query See the [TanStack Query query docs](https://tanstack.com/query/v5/docs/framework/react/reference/useQuery) for more info hook parameters. ## `amm.useMint` Mints liquidity tokens by providing a token pair. [Learn more about the Fee AMM](/protocol/fees/spec-fee-amm) ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' import { parseUnits } from 'viem' const { data: result, mutate } = Hooks.amm.useMintSync() // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', userToken: '0x20c0000000000000000000000000000000000000', validatorToken: '0x20c0000000000000000000000000000000000001', validatorTokenAmount: parseUnits('100', 6), }) console.log('Liquidity minted:', result.liquidity) // @log: Liquidity minted: 100000000n ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `amm.mint` action and wait for inclusion manually: ```ts twoslash // @errors: 2322 declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' import { Actions } from 'viem/tempo' import { parseUnits } from 'viem' import { useWaitForTransactionReceipt } from 'wagmi' import { config } from './wagmi.config' const { data: hash, mutate } = Hooks.amm.useMint() const { data: receipt } = useWaitForTransactionReceipt({ hash }) // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', userToken: '0x20c0000000000000000000000000000000000000', validatorToken: '0x20c0000000000000000000000000000000000001', validatorTokenAmount: parseUnits('100', 6), }) if (receipt) { const { args: { liquidity } } = Actions.amm.mint.extractEvent(receipt.logs) } ``` ### Return Type See [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook return types. #### data See [Wagmi Action `amm.mint` Return Type](/sdk/typescript/wagmi/actions/amm.mint#return-type) #### mutate/mutateAsync See [Wagmi Action `amm.mint` Parameters](/sdk/typescript/wagmi/actions/amm.mint#parameters) ### Parameters #### config `Config | undefined` [`Config`](https://wagmi.sh/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](https://wagmi.sh/react/api/WagmiProvider). #### mutation See the [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook parameters. ## `amm.usePool` Gets the reserves for a liquidity pool. ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' const { data: pool } = Hooks.amm.usePool({ userToken: '0x20c0000000000000000000000000000000000000', validatorToken: '0x20c0000000000000000000000000000000000001', }) console.log('User token reserve:', pool.reserveUserToken) // @log: User token reserve: 1000000000000000000000n console.log('Validator token reserve:', pool.reserveValidatorToken) // @log: Validator token reserve: 1000000000000000000000n console.log('Total supply:', pool.totalSupply) // @log: Total supply: 1000000000000000000000n ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Return Type See [TanStack Query query docs](https://tanstack.com/query/v5/docs/framework/react/reference/useQuery) for more info hook return types. #### data See [Wagmi Action `amm.getPool` Return Type](/sdk/typescript/wagmi/actions/amm.getPool#return-type) ### Parameters See [Wagmi Action `amm.getPool` Parameters](/sdk/typescript/wagmi/actions/amm.getPool#parameters) #### query See the [TanStack Query query docs](https://tanstack.com/query/v5/docs/framework/react/reference/useQuery) for more info hook parameters. ## `amm.useRebalanceSwap` Performs a rebalance swap between user and validator tokens. [Learn more about the Fee AMM](/protocol/fees/spec-fee-amm) ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' import { parseUnits } from 'viem' const { data: result, mutate } = Hooks.amm.useRebalanceSwapSync() // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ amountOut: parseUnits('10.5', 6), to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', userToken: '0x20c0000000000000000000000000000000000000', validatorToken: '0x20c0000000000000000000000000000000000001', }) console.log('Amount in:', result.amountIn) // @log: 10605000n ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `amm.rebalanceSwap` action and wait for inclusion manually: ```ts twoslash // @errors: 2322 declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' import { Actions } from 'viem/tempo' import { parseUnits } from 'viem' import { useWaitForTransactionReceipt } from 'wagmi' import { config } from './wagmi.config' const { data: hash, mutate } = Hooks.amm.useRebalanceSwap() const { data: receipt } = useWaitForTransactionReceipt({ hash }) // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ amountOut: parseUnits('10.5', 6), to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', userToken: '0x20c0000000000000000000000000000000000000', validatorToken: '0x20c0000000000000000000000000000000000001', }) if (receipt) { const { args: { amountIn } } = Actions.amm.rebalanceSwap.extractEvent(receipt.logs) } ``` ### Return Type See [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook return types. #### data See [Wagmi Action `amm.rebalanceSwap` Return Type](/sdk/typescript/wagmi/actions/amm.rebalanceSwap#return-type) #### mutate/mutateAsync See [Wagmi Action `amm.rebalanceSwap` Parameters](/sdk/typescript/wagmi/actions/amm.rebalanceSwap#parameters) ### Parameters #### config `Config | undefined` [`Config`](https://wagmi.sh/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](https://wagmi.sh/react/api/WagmiProvider). #### mutation See the [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook parameters. ## `amm.useWatchBurn` Watches for liquidity burn events on the Fee AMM. ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' Hooks.amm.useWatchBurn({ onBurn: (args, log) => { console.log('User token amount:', args.amountUserToken) console.log('Validator token amount:', args.amountValidatorToken) console.log('Liquidity burned:', args.liquidity) console.log('Sender:', args.sender) console.log('Recipient:', args.to) console.log('User token:', args.userToken) console.log('Validator token:', args.validatorToken) }, }) ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Parameters See [Wagmi Action `amm.watchBurn` Parameters](/sdk/typescript/wagmi/actions/amm.watchBurn#parameters) #### config `Config | undefined` [`Config`](https://wagmi.sh/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](https://wagmi.sh/react/api/WagmiProvider). ## `amm.useWatchFeeSwap` Watches for fee swap events on the Fee AMM. ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' Hooks.amm.useWatchFeeSwap({ onFeeSwap: (args, log) => { console.log('Amount in:', args.amountIn) console.log('Amount out:', args.amountOut) console.log('Recipient:', args.to) console.log('User token:', args.userToken) console.log('Validator token:', args.validatorToken) }, }) ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Parameters See [Wagmi Action `amm.watchFeeSwap` Parameters](/sdk/typescript/wagmi/actions/amm.watchFeeSwap#parameters) #### config `Config | undefined` [`Config`](https://wagmi.sh/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](https://wagmi.sh/react/api/WagmiProvider). ## `amm.useWatchMint` Watches for liquidity mint events on the Fee AMM. ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' Hooks.amm.useWatchMint({ onMint: (args, log) => { console.log('Liquidity minted:', args.liquidity) console.log('Sender:', args.sender) console.log('User token amount:', args.userToken.amount) console.log('Validator token amount:', args.validatorToken.amount) }, }) ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Parameters See [Wagmi Action `amm.watchMint` Parameters](/sdk/typescript/wagmi/actions/amm.watchMint#parameters) #### config `Config | undefined` [`Config`](https://wagmi.sh/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](https://wagmi.sh/react/api/WagmiProvider). ## `amm.useWatchRebalanceSwap` Watches for rebalance swap events on the Fee AMM. ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' Hooks.amm.useWatchRebalanceSwap({ onRebalanceSwap: (args, log) => { console.log('Amount in:', args.amountIn) console.log('Amount out:', args.amountOut) console.log('Recipient:', args.to) console.log('User token:', args.userToken) console.log('Validator token:', args.validatorToken) }, }) ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Parameters See [Wagmi Action `amm.watchRebalanceSwap` Parameters](/sdk/typescript/wagmi/actions/amm.watchRebalanceSwap#parameters) #### config `Config | undefined` [`Config`](https://wagmi.sh/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](https://wagmi.sh/react/api/WagmiProvider). ## `dex.useBalance` Gets a user's token balance on the Stablecoin DEX. ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' const { data: balance } = Hooks.dex.useBalance({ account: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', token: '0x20c0000000000000000000000000000000000001', }) console.log('DEX balance:', balance) // @log: DEX balance: 1000000000n ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Return Type See [TanStack Query query docs](https://tanstack.com/query/v5/docs/framework/react/reference/useQuery) for more info hook return types. #### data See [Wagmi Action `dex.getBalance` Return Type](/sdk/typescript/wagmi/actions/dex.getBalance#return-type) ### Parameters See [Wagmi Action `dex.getBalance` Parameters](/sdk/typescript/wagmi/actions/dex.getBalance#parameters) #### query See the [TanStack Query query docs](https://tanstack.com/query/v5/docs/framework/react/reference/useQuery) for more info hook parameters. ## `dex.useBuy` Buys a specific amount of tokens from the Stablecoin DEX orderbook. ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' import { parseUnits } from 'viem' const { data: result, mutate } = Hooks.dex.useBuySync() // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ amountOut: parseUnits('100', 6), maxAmountIn: parseUnits('105', 6), tokenIn: '0x20c0000000000000000000000000000000000001', tokenOut: '0x20c0000000000000000000000000000000000002', }) console.log('Transaction hash:', result.receipt.transactionHash) ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `dex.buy` action and wait for inclusion manually: ```ts twoslash // @errors: 2322 declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' import { parseUnits } from 'viem' import { useWaitForTransactionReceipt } from 'wagmi' import { config } from './wagmi.config' const { data: hash, mutate } = Hooks.dex.useBuy() const { data: receipt } = useWaitForTransactionReceipt({ hash }) // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ amountOut: parseUnits('100', 6), maxAmountIn: parseUnits('105', 6), tokenIn: '0x20c0000000000000000000000000000000000001', tokenOut: '0x20c0000000000000000000000000000000000002', }) ``` ### Return Type See [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook return types. #### data See [Wagmi Action `dex.buy` Return Type](/sdk/typescript/wagmi/actions/dex.buy#return-type) #### mutate/mutateAsync See [Wagmi Action `dex.buy` Parameters](/sdk/typescript/wagmi/actions/dex.buy#parameters) ### Parameters #### config `Config | undefined` [`Config`](https://wagmi.sh/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](https://wagmi.sh/react/api/WagmiProvider). #### mutation See the [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook parameters. ## `dex.useBuyQuote` Gets the quote for buying a specific amount of tokens. ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' import { parseUnits } from 'viem' const { data: quote } = Hooks.dex.useBuyQuote({ amountOut: parseUnits('100', 6), tokenIn: '0x20c0000000000000000000000000000000000001', tokenOut: '0x20c0000000000000000000000000000000000002', }) console.log('Amount needed:', quote) // @log: Amount needed: 100300000n ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Return Type See [TanStack Query query docs](https://tanstack.com/query/v5/docs/framework/react/reference/useQuery) for more info hook return types. #### data See [Wagmi Action `dex.getBuyQuote` Return Type](/sdk/typescript/wagmi/actions/dex.getBuyQuote#return-type) ### Parameters See [Wagmi Action `dex.getBuyQuote` Parameters](/sdk/typescript/wagmi/actions/dex.getBuyQuote#parameters) #### query See the [TanStack Query query docs](https://tanstack.com/query/v5/docs/framework/react/reference/useQuery) for more info hook parameters. ## `dex.useCancel` Cancels an order from the Stablecoin DEX orderbook. ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' const { data: result, mutate } = Hooks.dex.useCancelSync() // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ orderId: 123n, }) console.log('Cancelled order ID:', result.orderId) // @log: Cancelled order ID: 123n ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `dex.cancel` action and wait for inclusion manually: ```ts twoslash // @errors: 2322 declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' import { Actions } from 'viem/tempo' import { useWaitForTransactionReceipt } from 'wagmi' import { config } from './wagmi.config' const { data: hash, mutate } = Hooks.dex.useCancel() const { data: receipt } = useWaitForTransactionReceipt({ hash }) // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ orderId: 123n, }) if (receipt) { const { args: { orderId } } = Actions.dex.cancel.extractEvent(receipt.logs) } ``` ### Return Type See [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook return types. #### data See [Wagmi Action `dex.cancel` Return Type](/sdk/typescript/wagmi/actions/dex.cancel#return-type) #### mutate/mutateAsync See [Wagmi Action `dex.cancel` Parameters](/sdk/typescript/wagmi/actions/dex.cancel#parameters) ### Parameters #### config `Config | undefined` [`Config`](https://wagmi.sh/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](https://wagmi.sh/react/api/WagmiProvider). #### mutation See the [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook parameters. ## `dex.useCreatePair` Creates a new trading pair on the Stablecoin DEX. ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' const { data: result, mutate } = Hooks.dex.useCreatePairSync() // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ base: '0x20c0000000000000000000000000000000000001', }) console.log('Base token:', result.base) console.log('Quote token:', result.quote) ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `dex.createPair` action and wait for inclusion manually: ```ts twoslash // @errors: 2322 declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' import { Actions } from 'viem/tempo' import { useWaitForTransactionReceipt } from 'wagmi' import { config } from './wagmi.config' const { data: hash, mutate } = Hooks.dex.useCreatePair() const { data: receipt } = useWaitForTransactionReceipt({ hash }) // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ base: '0x20c0000000000000000000000000000000000001', }) if (receipt) { const { args: { base, quote } } = Actions.dex.createPair.extractEvent(receipt.logs) } ``` ### Return Type See [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook return types. #### data See [Wagmi Action `dex.createPair` Return Type](/sdk/typescript/wagmi/actions/dex.createPair#return-type) #### mutate/mutateAsync See [Wagmi Action `dex.createPair` Parameters](/sdk/typescript/wagmi/actions/dex.createPair#parameters) ### Parameters #### config `Config | undefined` [`Config`](https://wagmi.sh/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](https://wagmi.sh/react/api/WagmiProvider). #### mutation See the [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook parameters. ## `dex.useOrder` Gets an order's details from the Stablecoin DEX orderbook. ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' const { data: order } = Hooks.dex.useOrder({ orderId: 123n, }) console.log('Order details:', order) // @log: Order details: { amount: 100000000n, maker: '0x...', isBid: true, ... } ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Return Type See [TanStack Query query docs](https://tanstack.com/query/v5/docs/framework/react/reference/useQuery) for more info hook return types. #### data See [Wagmi Action `dex.getOrder` Return Type](/sdk/typescript/wagmi/actions/dex.getOrder#return-type) ### Parameters See [Wagmi Action `dex.getOrder` Parameters](/sdk/typescript/wagmi/actions/dex.getOrder#parameters) #### query See the [TanStack Query query docs](https://tanstack.com/query/v5/docs/framework/react/reference/useQuery) for more info hook parameters. ## `dex.usePlace` Places a limit order on the Stablecoin DEX orderbook. ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' import { parseUnits } from 'viem' import { Tick } from 'viem/tempo' const { data: result, mutate } = Hooks.dex.usePlaceSync() // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ amount: parseUnits('100', 6), tick: Tick.fromPrice('0.99'), token: '0x20c0000000000000000000000000000000000001', type: 'buy', }) console.log('Order ID:', result.orderId) // @log: Order ID: 123n ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `dex.place` action and wait for inclusion manually: ```ts twoslash // @errors: 2322 declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' import { Actions } from 'viem/tempo' import { parseUnits } from 'viem' import { Tick } from 'viem/tempo' import { useWaitForTransactionReceipt } from 'wagmi' import { config } from './wagmi.config' const { data: hash, mutate } = Hooks.dex.usePlace() const { data: receipt } = useWaitForTransactionReceipt({ hash }) // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ amount: parseUnits('100', 6), tick: Tick.fromPrice('0.99'), token: '0x20c0000000000000000000000000000000000001', type: 'buy', }) if (receipt) { const { args: { orderId } } = Actions.dex.place.extractEvent(receipt.logs) } ``` ### Return Type See [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook return types. #### data See [Wagmi Action `dex.place` Return Type](/sdk/typescript/wagmi/actions/dex.place#return-type) #### mutate/mutateAsync See [Wagmi Action `dex.place` Parameters](/sdk/typescript/wagmi/actions/dex.place#parameters) ### Parameters #### config `Config | undefined` [`Config`](https://wagmi.sh/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](https://wagmi.sh/react/api/WagmiProvider). #### mutation See the [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook parameters. ## `dex.usePlaceFlip` Places a flip order that automatically flips to the opposite side when filled. ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' import { parseUnits } from 'viem' import { Tick } from 'viem/tempo' const { data: result, mutate } = Hooks.dex.usePlaceFlipSync() // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ amount: parseUnits('100', 6), flipTick: Tick.fromPrice('1.01'), tick: Tick.fromPrice('0.99'), token: '0x20c0000000000000000000000000000000000001', type: 'buy', }) console.log('Flip order ID:', result.orderId) // @log: Flip order ID: 456n ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `dex.placeFlip` action and wait for inclusion manually: ```ts twoslash // @errors: 2322 declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' import { Actions } from 'viem/tempo' import { parseUnits } from 'viem' import { Tick } from 'viem/tempo' import { useWaitForTransactionReceipt } from 'wagmi' import { config } from './wagmi.config' const { data: hash, mutate } = Hooks.dex.usePlaceFlip() const { data: receipt } = useWaitForTransactionReceipt({ hash }) // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ amount: parseUnits('100', 6), flipTick: Tick.fromPrice('1.01'), tick: Tick.fromPrice('0.99'), token: '0x20c0000000000000000000000000000000000001', type: 'buy', }) if (receipt) { const { args: { orderId } } = Actions.dex.placeFlip.extractEvent(receipt.logs) } ``` ### Return Type See [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook return types. #### data See [Wagmi Action `dex.placeFlip` Return Type](/sdk/typescript/wagmi/actions/dex.placeFlip#return-type) #### mutate/mutateAsync See [Wagmi Action `dex.placeFlip` Parameters](/sdk/typescript/wagmi/actions/dex.placeFlip#parameters) ### Parameters #### config `Config | undefined` [`Config`](https://wagmi.sh/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](https://wagmi.sh/react/api/WagmiProvider). #### mutation See the [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook parameters. ## `dex.useSell` Sells a specific amount of tokens on the Stablecoin DEX orderbook. ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' import { parseUnits } from 'viem' const { data: result, mutate } = Hooks.dex.useSellSync() // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ amountIn: parseUnits('100', 6), minAmountOut: parseUnits('95', 6), tokenIn: '0x20c0000000000000000000000000000000000001', tokenOut: '0x20c0000000000000000000000000000000000002', }) console.log('Transaction hash:', result.receipt.transactionHash) ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `dex.sell` action and wait for inclusion manually: ```ts twoslash // @errors: 2322 declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' import { parseUnits } from 'viem' import { useWaitForTransactionReceipt } from 'wagmi' import { config } from './wagmi.config' const { data: hash, mutate } = Hooks.dex.useSell() const { data: receipt } = useWaitForTransactionReceipt({ hash }) // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ amountIn: parseUnits('100', 6), minAmountOut: parseUnits('95', 6), tokenIn: '0x20c0000000000000000000000000000000000001', tokenOut: '0x20c0000000000000000000000000000000000002', }) ``` ### Return Type See [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook return types. #### data See [Wagmi Action `dex.sell` Return Type](/sdk/typescript/wagmi/actions/dex.sell#return-type) #### mutate/mutateAsync See [Wagmi Action `dex.sell` Parameters](/sdk/typescript/wagmi/actions/dex.sell#parameters) ### Parameters #### config `Config | undefined` [`Config`](https://wagmi.sh/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](https://wagmi.sh/react/api/WagmiProvider). #### mutation See the [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook parameters. ## `dex.useSellQuote` Gets the quote for selling a specific amount of tokens. ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' import { parseUnits } from 'viem' const { data: quote } = Hooks.dex.useSellQuote({ amountIn: parseUnits('100', 6), tokenIn: '0x20c0000000000000000000000000000000000001', tokenOut: '0x20c0000000000000000000000000000000000002', }) console.log('Amount received:', quote) // @log: Amount received: 99700000n ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Return Type See [TanStack Query query docs](https://tanstack.com/query/v5/docs/framework/react/reference/useQuery) for more info hook return types. #### data See [Wagmi Action `dex.getSellQuote` Return Type](/sdk/typescript/wagmi/actions/dex.getSellQuote#return-type) ### Parameters See [Wagmi Action `dex.getSellQuote` Parameters](/sdk/typescript/wagmi/actions/dex.getSellQuote#parameters) #### query See the [TanStack Query query docs](https://tanstack.com/query/v5/docs/framework/react/reference/useQuery) for more info hook parameters. ## `dex.useTickLevel` Gets the tick level information at a specific tick on the Stablecoin DEX orderbook. ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' import { Tick } from 'viem/tempo' const { data: level } = Hooks.dex.useTickLevel({ base: '0x20c0000000000000000000000000000000000001', tick: Tick.fromPrice('1.001'), isBid: true, }) console.log('Tick level:', level) // @log: Tick level: { head: 1n, tail: 5n, totalLiquidity: 1000000000n } ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Return Type See [TanStack Query query docs](https://tanstack.com/query/v5/docs/framework/react/reference/useQuery) for more info hook return types. #### data See [Wagmi Action `dex.getTickLevel` Return Type](/sdk/typescript/wagmi/actions/dex.getTickLevel#return-type) ### Parameters See [Wagmi Action `dex.getTickLevel` Parameters](/sdk/typescript/wagmi/actions/dex.getTickLevel#parameters) #### query See the [TanStack Query query docs](https://tanstack.com/query/v5/docs/framework/react/reference/useQuery) for more info hook parameters. ## `dex.useWatchFlipOrderPlaced` Watches for flip order placed events on the Stablecoin DEX. ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' Hooks.dex.useWatchFlipOrderPlaced({ onFlipOrderPlaced: (args, log) => { console.log('Flip order placed:', args.orderId) console.log('Maker:', args.maker) console.log('Amount:', args.amount) console.log('Tick:', args.tick) console.log('Flip tick:', args.flipTick) }, }) ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Parameters See [Wagmi Action `dex.watchFlipOrderPlaced` Parameters](/sdk/typescript/wagmi/actions/dex.watchFlipOrderPlaced#parameters) #### config `Config | undefined` [`Config`](https://wagmi.sh/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](https://wagmi.sh/react/api/WagmiProvider). ## `dex.useWatchOrderCancelled` Watches for order cancelled events on the Stablecoin DEX. ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' Hooks.dex.useWatchOrderCancelled({ onOrderCancelled: (args, log) => { console.log('Order cancelled:', args.orderId) console.log('Maker:', args.maker) }, }) ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Parameters See [Wagmi Action `dex.watchOrderCancelled` Parameters](/sdk/typescript/wagmi/actions/dex.watchOrderCancelled#parameters) #### config `Config | undefined` [`Config`](https://wagmi.sh/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](https://wagmi.sh/react/api/WagmiProvider). ## `dex.useWatchOrderFilled` Watches for order filled events on the Stablecoin DEX. ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' Hooks.dex.useWatchOrderFilled({ onOrderFilled: (args, log) => { console.log('Order filled:', args.orderId) console.log('Maker:', args.maker) console.log('Taker:', args.taker) console.log('Amount:', args.amount) }, }) ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Parameters See [Wagmi Action `dex.watchOrderFilled` Parameters](/sdk/typescript/wagmi/actions/dex.watchOrderFilled#parameters) #### config `Config | undefined` [`Config`](https://wagmi.sh/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](https://wagmi.sh/react/api/WagmiProvider). ## `dex.useWatchOrderPlaced` Watches for order placed events on the Stablecoin DEX. ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' Hooks.dex.useWatchOrderPlaced({ onOrderPlaced: (args, log) => { console.log('Order placed:', args.orderId) console.log('Maker:', args.maker) console.log('Token:', args.token) console.log('Amount:', args.amount) console.log('Tick:', args.tick) }, }) ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Parameters See [Wagmi Action `dex.watchOrderPlaced` Parameters](/sdk/typescript/wagmi/actions/dex.watchOrderPlaced#parameters) #### config `Config | undefined` [`Config`](https://wagmi.sh/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](https://wagmi.sh/react/api/WagmiProvider). ## `dex.useWithdraw` Withdraws tokens from the Stablecoin DEX to your wallet. ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' import { parseUnits } from 'viem' const { data: result, mutate } = Hooks.dex.useWithdrawSync() // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ amount: parseUnits('100', 6), token: '0x20c0000000000000000000000000000000000001', }) console.log('Transaction hash:', result.receipt.transactionHash) ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `dex.withdraw` action and wait for inclusion manually: ```ts twoslash // @errors: 2322 declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' import { parseUnits } from 'viem' import { useWaitForTransactionReceipt } from 'wagmi' import { config } from './wagmi.config' const { data: hash, mutate } = Hooks.dex.useWithdraw() const { data: receipt } = useWaitForTransactionReceipt({ hash }) // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ amount: parseUnits('100', 6), token: '0x20c0000000000000000000000000000000000001', }) ``` ### Return Type See [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook return types. #### data See [Wagmi Action `dex.withdraw` Return Type](/sdk/typescript/wagmi/actions/dex.withdraw#return-type) #### mutate/mutateAsync See [Wagmi Action `dex.withdraw` Parameters](/sdk/typescript/wagmi/actions/dex.withdraw#parameters) ### Parameters #### config `Config | undefined` [`Config`](https://wagmi.sh/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](https://wagmi.sh/react/api/WagmiProvider). #### mutation See the [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook parameters. ## `faucet.useFund` Hook for funding an account with testnet tokens on Tempo's testnet. ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' const { data: hashes, mutate } = Hooks.faucet.useFund() // Call `mutate` in response to user action (e.g. button click) mutate({ account: '0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef', }) console.log('Transaction hashes:', hashes) // @log: Transaction hashes: ['0x...', '0x...'] ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Synchronous Usage Use `useFundSync` to wait for the transactions to be included on a block before returning: ```ts twoslash // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' const { data: receipts, mutate } = Hooks.faucet.useFundSync() // Call `mutate` in response to user action mutate({ account: '0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef', }) console.log('Receipts:', receipts) // @log: Receipts: [{ blockNumber: 123n, ... }, { blockNumber: 123n, ... }] ``` ### Return Type See [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook return types. #### data See [Wagmi Action `faucet.fund` Return Type](/sdk/typescript/wagmi/actions/faucet.fund#return-type) #### mutate/mutateAsync See [Wagmi Action `faucet.fund` Parameters](/sdk/typescript/wagmi/actions/faucet.fund#parameters) ### Parameters #### config `Config | undefined` [`Config`](https://wagmi.sh/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](https://wagmi.sh/react/api/WagmiProvider). #### mutation See the [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook parameters. ## `fee.useSetUserToken` Sets the user's default fee token preference. [Learn more about fees](/protocol/fees) ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- // [!include ~/snippets/unformatted/fee.setUserToken.ts:wagmi-hooks] ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `fee.setUserToken` action and wait for inclusion manually: ```ts twoslash // @errors: 2322 declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' import { Actions } from 'viem/tempo' import { useWaitForTransactionReceipt } from 'wagmi' import { config } from './wagmi.config' const { data: hash, mutate } = Hooks.fee.useSetUserToken() const { data: receipt } = useWaitForTransactionReceipt({ hash }) // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ token: '0x20c0000000000000000000000000000000000001', }) if (receipt) { const { args } = Actions.fee.setUserToken.extractEvent(receipt.logs) } ``` ### Return Type See [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook return types. #### data See [Wagmi Action `fee.setUserToken` Return Type](/sdk/typescript/wagmi/actions/fee.setUserToken#return-type) #### mutate/mutateAsync See [Wagmi Action `fee.setUserToken` Parameters](/sdk/typescript/wagmi/actions/fee.setUserToken#parameters) ### Parameters #### config `Config | undefined` [`Config`](https://wagmi.sh/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](https://wagmi.sh/react/api/WagmiProvider). #### mutation See the [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook parameters. ## `fee.useUserToken` Gets the user's default fee token preference. [Learn more about fees](/protocol/fees) ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' const { data: userToken } = Hooks.fee.useUserToken({ account: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', }) console.log('Fee token address:', userToken.address) // @log: Fee token address: 0x20c0000000000000000000000000000000000000 console.log('Fee token ID:', userToken.id) // @log: Fee token ID: 0n ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Return Type See [TanStack Query query docs](https://tanstack.com/query/v5/docs/framework/react/reference/useQuery) for more info hook return types. #### data See [Wagmi Action `fee.getUserToken` Return Type](/sdk/typescript/wagmi/actions/fee.getUserToken#return-type) ### Parameters See [Wagmi Action `fee.getUserToken` Parameters](/sdk/typescript/wagmi/actions/fee.getUserToken#parameters) #### query See the [TanStack Query query docs](https://tanstack.com/query/v5/docs/framework/react/reference/useQuery) for more info hook parameters. ## `fee.useWatchSetUserToken` Watches for user token set events on the Fee Manager. ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' Hooks.fee.useWatchSetUserToken({ onUserTokenSet: (args, log) => { console.log('User:', args.user) console.log('New fee token:', args.userToken) }, }) ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Parameters See [Wagmi Action `fee.watchSetUserToken` Parameters](/sdk/typescript/wagmi/actions/fee.watchSetUserToken#parameters) #### config `Config | undefined` [`Config`](https://wagmi.sh/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](https://wagmi.sh/react/api/WagmiProvider). ## Overview | Hook | Description | | -------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | | **AMM Hooks** | | | [`amm.useBurn`](/sdk/typescript/wagmi/hooks/amm.useBurn) | Hook for burning liquidity tokens and receiving the underlying token pair | | [`amm.useLiquidityBalance`](/sdk/typescript/wagmi/hooks/amm.useLiquidityBalance) | Hook for getting the liquidity balance for an address in a specific pool | | [`amm.useMint`](/sdk/typescript/wagmi/hooks/amm.useMint) | Hook for minting liquidity tokens by providing a token pair | | [`amm.usePool`](/sdk/typescript/wagmi/hooks/amm.usePool) | Hook for getting the reserves for a liquidity pool | | [`amm.useRebalanceSwap`](/sdk/typescript/wagmi/hooks/amm.useRebalanceSwap) | Hook for performing a rebalance swap between user and validator tokens | | [`amm.useWatchBurn`](/sdk/typescript/wagmi/hooks/amm.useWatchBurn) | Hook for watching liquidity burn events | | [`amm.useWatchFeeSwap`](/sdk/typescript/wagmi/hooks/amm.useWatchFeeSwap) | Hook for watching fee swap events | | [`amm.useWatchMint`](/sdk/typescript/wagmi/hooks/amm.useWatchMint) | Hook for watching liquidity mint events | | [`amm.useWatchRebalanceSwap`](/sdk/typescript/wagmi/hooks/amm.useWatchRebalanceSwap) | Hook for watching rebalance swap events | | **Faucet Hooks** | | | [`faucet.useFund`](/sdk/typescript/wagmi/hooks/faucet.useFund) | Hook for funding an account with testnet tokens | | **Fee Hooks** | | | [`fee.useSetUserToken`](/sdk/typescript/wagmi/hooks/fee.useSetUserToken) | Hook for setting the user's default fee token preference | | [`fee.useUserToken`](/sdk/typescript/wagmi/hooks/fee.useUserToken) | Hook for getting the user's default fee token preference | | [`fee.useWatchSetUserToken`](/sdk/typescript/wagmi/hooks/fee.useWatchSetUserToken) | Hook for watching user token set events | | **Nonce Hooks** | | | [`nonce.useNonce`](/sdk/typescript/wagmi/hooks/nonce.useNonce) | Hook for getting the nonce for an account and nonce key | | [`nonce.useNonceKeyCount`](/sdk/typescript/wagmi/hooks/nonce.useNonceKeyCount) | Hook for getting the number of active nonce keys for an account | | [`nonce.useWatchActiveKeyCountChanged`](/sdk/typescript/wagmi/hooks/nonce.useWatchActiveKeyCountChanged) | Hook for watching active key count changed events | | [`nonce.useWatchNonceIncremented`](/sdk/typescript/wagmi/hooks/nonce.useWatchNonceIncremented) | Hook for watching nonce incremented events | | **Policy Hooks** | | | [`policy.useCreate`](#TODO) | Hook for creating a new transfer policy for token access control | | [`policy.useData`](#TODO) | Hook for getting the data for a transfer policy, including its type and admin address | | [`policy.useIsAuthorized`](#TODO) | Hook for checking if an address is authorized by a transfer policy | | [`policy.useModifyBlacklist`](#TODO) | Hook for modifying the blacklist for a blacklist-type transfer policy | | [`policy.useModifyWhitelist`](#TODO) | Hook for modifying the whitelist for a whitelist-type transfer policy | | [`policy.useSetAdmin`](#TODO) | Hook for setting the admin for a transfer policy | | [`policy.useWatchAdminUpdated`](#TODO) | Hook for watching policy admin update events | | [`policy.useWatchBlacklistUpdated`](#TODO) | Hook for watching blacklist update events | | [`policy.useWatchCreate`](#TODO) | Hook for watching policy creation events | | [`policy.useWatchWhitelistUpdated`](#TODO) | Hook for watching whitelist update events | | **Reward Hooks** | | | [`reward.useClaim`](/sdk/typescript/wagmi/hooks/reward.useClaim) | Hook for claiming accumulated rewards | | [`reward.useGetTotalPerSecond`](/sdk/typescript/wagmi/hooks/reward.useGetTotalPerSecond) | Hook for getting the total reward per second rate for all active streams | | [`reward.useSetRecipient`](/sdk/typescript/wagmi/hooks/reward.useSetRecipient) | Hook for setting or changing the reward recipient for a token holder | | [`reward.useStart`](/sdk/typescript/wagmi/hooks/reward.useStart) | Hook for starting a new reward stream that distributes tokens to opted-in holders | | [`reward.useUserRewardInfo`](/sdk/typescript/wagmi/hooks/reward.useUserRewardInfo) | Hook for getting reward information for a specific account | | [`reward.useWatchRewardRecipientSet`](/sdk/typescript/wagmi/hooks/reward.useWatchRewardRecipientSet) | Hook for watching reward recipient set events | | [`reward.useWatchRewardScheduled`](/sdk/typescript/wagmi/hooks/reward.useWatchRewardScheduled) | Hook for watching reward scheduled events | | **Stablecoin DEX Hooks** | | | [`dex.useBalance`](/sdk/typescript/wagmi/hooks/dex.useBalance) | Hook for getting a user's token balance on the Stablecoin DEX | | [`dex.useBuy`](/sdk/typescript/wagmi/hooks/dex.useBuy) | Hook for buying a specific amount of tokens from the Stablecoin DEX orderbook | | [`dex.useBuyQuote`](/sdk/typescript/wagmi/hooks/dex.useBuyQuote) | Hook for getting the quote for buying a specific amount of tokens | | [`dex.useCancel`](/sdk/typescript/wagmi/hooks/dex.useCancel) | Hook for canceling an order from the orderbook | | [`dex.useCreatePair`](/sdk/typescript/wagmi/hooks/dex.useCreatePair) | Hook for creating a new trading pair on the DEX | | [`dex.useOrder`](/sdk/typescript/wagmi/hooks/dex.useOrder) | Hook for getting an order's details from the orderbook | | [`dex.usePlace`](/sdk/typescript/wagmi/hooks/dex.usePlace) | Hook for placing a limit order on the orderbook | | [`dex.usePlaceFlip`](/sdk/typescript/wagmi/hooks/dex.usePlaceFlip) | Hook for placing a flip order that automatically flips when filled | | [`dex.useTickLevel`](/sdk/typescript/wagmi/hooks/dex.useTickLevel) | Hook for getting the price level information at a specific tick | | [`dex.useSell`](/sdk/typescript/wagmi/hooks/dex.useSell) | Hook for selling a specific amount of tokens from the Stablecoin DEX orderbook | | [`dex.useSellQuote`](/sdk/typescript/wagmi/hooks/dex.useSellQuote) | Hook for getting the quote for selling a specific amount of tokens | | [`dex.useWatchFlipOrderPlaced`](/sdk/typescript/wagmi/hooks/dex.useWatchFlipOrderPlaced) | Hook for watching flip order placed events | | [`dex.useWatchOrderCancelled`](/sdk/typescript/wagmi/hooks/dex.useWatchOrderCancelled) | Hook for watching order cancelled events | | [`dex.useWatchOrderFilled`](/sdk/typescript/wagmi/hooks/dex.useWatchOrderFilled) | Hook for watching order filled events | | [`dex.useWatchOrderPlaced`](/sdk/typescript/wagmi/hooks/dex.useWatchOrderPlaced) | Hook for watching order placed events | | [`dex.useWithdraw`](/sdk/typescript/wagmi/hooks/dex.useWithdraw) | Hook for withdrawing tokens from the DEX to the caller's wallet | | **Token Hooks** | | | [`token.useAllowance`](/sdk/typescript/wagmi/hooks/token.useGetAllowance) | Hook for getting the amount of tokens that a spender is approved to transfer on behalf of an owner | | [`token.useApprove`](/sdk/typescript/wagmi/hooks/token.useApprove) | Hook for approving a spender to transfer TIP-20 tokens on behalf of the caller | | [`token.useBalance`](/sdk/typescript/wagmi/hooks/token.useGetBalance) | Hook for getting the token balance of an address | | [`token.useBurn`](/sdk/typescript/wagmi/hooks/token.useBurn) | Hook for burning TIP-20 tokens from the caller's balance | | [`token.useBurnBlocked`](/sdk/typescript/wagmi/hooks/token.useBurnBlocked) | Hook for burning TIP-20 tokens from a blocked address | | [`token.useChangeTransferPolicy`](/sdk/typescript/wagmi/hooks/token.useChangeTransferPolicy) | Hook for changing the transfer policy for a TIP-20 token | | [`token.useCreate`](/sdk/typescript/wagmi/hooks/token.useCreate) | Hook for creating a new TIP-20 token and assigning the admin role to the calling account | | [`token.useGrantRoles`](/sdk/typescript/wagmi/hooks/token.useGrantRoles) | Hook for granting one or more roles to an address | | [`token.useHasRole`](/sdk/typescript/wagmi/hooks/token.useHasRole) | Hook for checking if an address has a specific role | | [`token.useMetadata`](/sdk/typescript/wagmi/hooks/token.useGetMetadata) | Hook for getting the metadata for a TIP-20 token, including name, symbol, decimals, currency, and total supply | | [`token.useMint`](/sdk/typescript/wagmi/hooks/token.useMint) | Hook for minting new TIP-20 tokens to a recipient | | [`token.usePause`](/sdk/typescript/wagmi/hooks/token.usePause) | Hook for pausing a TIP-20 token, preventing all transfers | | [`token.useRenounceRoles`](/sdk/typescript/wagmi/hooks/token.useRenounceRoles) | Hook for renouncing one or more roles from the caller's address | | [`token.useRevokeRoles`](/sdk/typescript/wagmi/hooks/token.useRevokeRoles) | Hook for revoking one or more roles from an address | | [`token.useSetRoleAdmin`](/sdk/typescript/wagmi/hooks/token.useSetRoleAdmin) | Hook for setting the admin role for another role | | [`token.useSetSupplyCap`](/sdk/typescript/wagmi/hooks/token.useSetSupplyCap) | Hook for setting the supply cap for a TIP-20 token | | [`token.useTransfer`](/sdk/typescript/wagmi/hooks/token.useTransfer) | Hook for transferring TIP-20 tokens from the caller to a recipient | | [`token.useUnpause`](/sdk/typescript/wagmi/hooks/token.useUnpause) | Hook for unpausing a TIP-20 token, allowing transfers to resume | | [`token.useWatchAdminRole`](/sdk/typescript/wagmi/hooks/token.useWatchAdminRole) | Hook for watching role admin update events | | [`token.useWatchApprove`](/sdk/typescript/wagmi/hooks/token.useWatchApprove) | Hook for watching token approval events | | [`token.useWatchBurn`](/sdk/typescript/wagmi/hooks/token.useWatchBurn) | Hook for watching token burn events | | [`token.useWatchCreate`](/sdk/typescript/wagmi/hooks/token.useWatchCreate) | Hook for watching new token creation events | | [`token.useWatchMint`](/sdk/typescript/wagmi/hooks/token.useWatchMint) | Hook for watching token mint events | | [`token.useWatchRole`](/sdk/typescript/wagmi/hooks/token.useWatchRole) | Hook for watching role membership update events | | [`token.useWatchTransfer`](/sdk/typescript/wagmi/hooks/token.useWatchTransfer) | Hook for watching token transfer events | ## `nonce.useNonce` Hook for getting the nonce for an account and nonce key. This is useful for managing multiple nonce lanes for parallel transaction submission. ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' const { data: nonce } = Hooks.nonce.useNonce({ account: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', nonceKey: 1n, }) console.log('Nonce:', nonce) // @log: Nonce: 42n ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Return Type See [TanStack Query query docs](https://tanstack.com/query/v5/docs/framework/react/reference/useQuery) for more info hook return types. #### data See [Wagmi Action `nonce.getNonce` Return Type](/sdk/typescript/wagmi/actions/nonce.getNonce#return-type) ### Parameters See [Wagmi Action `nonce.getNonce` Parameters](/sdk/typescript/wagmi/actions/nonce.getNonce#parameters) #### query See the [TanStack Query query docs](https://tanstack.com/query/v5/docs/framework/react/reference/useQuery) for more info hook parameters. ## `nonce.useNonceKeyCount` Hook for getting the number of active nonce keys for an account. Active nonce keys are keys that have been used at least once. ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' const { data: count } = Hooks.nonce.useNonceKeyCount({ account: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', }) console.log('Active nonce keys:', count) // @log: Active nonce keys: 3n ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Return Type See [TanStack Query query docs](https://tanstack.com/query/v5/docs/framework/react/reference/useQuery) for more info hook return types. #### data See [Wagmi Action `nonce.getNonceKeyCount` Return Type](/sdk/typescript/wagmi/actions/nonce.getNonceKeyCount#return-type) ### Parameters See [Wagmi Action `nonce.getNonceKeyCount` Parameters](/sdk/typescript/wagmi/actions/nonce.getNonceKeyCount#parameters) #### query See the [TanStack Query query docs](https://tanstack.com/query/v5/docs/framework/react/reference/useQuery) for more info hook parameters. ## `nonce.useWatchActiveKeyCountChanged` Hook for watching active key count changed events. This event is emitted when an account starts using a new nonce key for the first time. ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' Hooks.nonce.useWatchActiveKeyCountChanged({ onActiveKeyCountChanged: (args, log) => { console.log('Account:', args.account) console.log('New active key count:', args.newCount) }, }) ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Parameters See [Wagmi Action `nonce.watchActiveKeyCountChanged` Parameters](/sdk/typescript/wagmi/actions/nonce.watchActiveKeyCountChanged#parameters) #### config `Config | undefined` [`Config`](https://wagmi.sh/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](https://wagmi.sh/react/api/WagmiProvider). ## `nonce.useWatchNonceIncremented` Hook for watching nonce incremented events. This event is emitted whenever a transaction is executed using a specific nonce key. ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' Hooks.nonce.useWatchNonceIncremented({ onNonceIncremented: (args, log) => { console.log('Account:', args.account) console.log('Nonce key:', args.nonceKey) console.log('New nonce:', args.newNonce) }, }) ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Parameters See [Wagmi Action `nonce.watchNonceIncremented` Parameters](/sdk/typescript/wagmi/actions/nonce.watchNonceIncremented#parameters) #### config `Config | undefined` [`Config`](https://wagmi.sh/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](https://wagmi.sh/react/api/WagmiProvider). ## `policy.useCreate` Creates a new transfer policy for token access control. [Learn more about transfer policies](/protocol/tip403/overview) ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' const { data: result, mutate } = Hooks.policy.useCreateSync() // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ addresses: [ '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', '0x70997970C51812dc3A010C7d01b50e0d17dc79C8', ], type: 'whitelist', }) console.log('Policy ID:', result.policyId) // @log: Policy ID: 1n ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `policy.create` action and wait for inclusion manually: ```ts twoslash // @errors: 2322 declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' import { Actions } from 'viem/tempo' import { useWaitForTransactionReceipt } from 'wagmi' import { config } from './wagmi.config' const { data: hash, mutate } = Hooks.policy.useCreate() const { data: receipt } = useWaitForTransactionReceipt({ hash }) // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ addresses: [ '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', '0x70997970C51812dc3A010C7d01b50e0d17dc79C8', ], type: 'whitelist', }) if (receipt) { const { args: { policyId } } = Actions.policy.create.extractEvent(receipt.logs) } ``` ### Return Type See [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook return types. #### data See [Wagmi Action `policy.create` Return Type](/sdk/typescript/wagmi/actions/policy.create#return-type) #### mutate/mutateAsync See [Wagmi Action `policy.create` Parameters](/sdk/typescript/wagmi/actions/policy.create#parameters) ### Parameters #### config `Config | undefined` [`Config`](https://wagmi.sh/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](https://wagmi.sh/react/api/WagmiProvider). #### mutation See the [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook parameters. ## `policy.useData` Gets the data for a transfer policy, including its type and admin address. ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' const { data: policyData } = Hooks.policy.useData({ policyId: 1n, }) console.log('Policy admin:', policyData?.admin) // @log: Policy admin: 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb console.log('Policy type:', policyData?.type) // @log: Policy type: whitelist ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Return Type See [TanStack Query query docs](https://tanstack.com/query/v5/docs/framework/react/reference/useQuery) for more info hook return types. #### data See [Wagmi Action `policy.getData` Return Type](/sdk/typescript/wagmi/actions/policy.getData#return-type) ### Parameters See [Wagmi Action `policy.getData` Parameters](/sdk/typescript/wagmi/actions/policy.getData#parameters) #### query See the [TanStack Query query docs](https://tanstack.com/query/v5/docs/framework/react/reference/useQuery) for more info hook parameters. ## `policy.useIsAuthorized` Checks if an address is authorized by a transfer policy. ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' const { data: isAuthorized } = Hooks.policy.useIsAuthorized({ address: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', policyId: 1n, }) console.log('Is authorized:', isAuthorized) // @log: Is authorized: true ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Return Type See [TanStack Query query docs](https://tanstack.com/query/v5/docs/framework/react/reference/useQuery) for more info hook return types. #### data See [Wagmi Action `policy.isAuthorized` Return Type](/sdk/typescript/wagmi/actions/policy.isAuthorized#return-type) ### Parameters See [Wagmi Action `policy.isAuthorized` Parameters](/sdk/typescript/wagmi/actions/policy.isAuthorized#parameters) #### query See the [TanStack Query query docs](https://tanstack.com/query/v5/docs/framework/react/reference/useQuery) for more info hook parameters. ## `policy.useModifyBlacklist` Modifies the blacklist for a blacklist-type transfer policy. Requires policy admin role. ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' const { data: result, mutate } = Hooks.policy.useModifyBlacklistSync() // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ account: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', policyId: 1n, restricted: true, }) console.log('Transaction hash:', result.receipt.transactionHash) // @log: Transaction hash: 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `policy.modifyBlacklist` action and wait for inclusion manually: ```ts twoslash // @errors: 2322 declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' import { Actions } from 'viem/tempo' import { useWaitForTransactionReceipt } from 'wagmi' import { config } from './wagmi.config' const { data: hash, mutate } = Hooks.policy.useModifyBlacklist() const { data: receipt } = useWaitForTransactionReceipt({ hash }) // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ account: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', policyId: 1n, restricted: true, }) if (receipt) { const { args } = Actions.policy.modifyBlacklist.extractEvent(receipt.logs) } ``` ### Return Type See [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook return types. #### data See [Wagmi Action `policy.modifyBlacklist` Return Type](/sdk/typescript/wagmi/actions/policy.modifyBlacklist#return-type) #### mutate/mutateAsync See [Wagmi Action `policy.modifyBlacklist` Parameters](/sdk/typescript/wagmi/actions/policy.modifyBlacklist#parameters) ### Parameters #### config `Config | undefined` [`Config`](https://wagmi.sh/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](https://wagmi.sh/react/api/WagmiProvider). #### mutation See the [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook parameters. ## `policy.useModifyWhitelist` Modifies the whitelist for a whitelist-type transfer policy. Requires policy admin role. ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' const { data: result, mutate } = Hooks.policy.useModifyWhitelistSync() // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ account: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', allowed: true, policyId: 1n, }) console.log('Transaction hash:', result.receipt.transactionHash) // @log: Transaction hash: 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `policy.modifyWhitelist` action and wait for inclusion manually: ```ts twoslash // @errors: 2322 declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' import { Actions } from 'viem/tempo' import { useWaitForTransactionReceipt } from 'wagmi' import { config } from './wagmi.config' const { data: hash, mutate } = Hooks.policy.useModifyWhitelist() const { data: receipt } = useWaitForTransactionReceipt({ hash }) // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ account: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', allowed: true, policyId: 1n, }) if (receipt) { const { args } = Actions.policy.modifyWhitelist.extractEvent(receipt.logs) } ``` ### Return Type See [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook return types. #### data See [Wagmi Action `policy.modifyWhitelist` Return Type](/sdk/typescript/wagmi/actions/policy.modifyWhitelist#return-type) #### mutate/mutateAsync See [Wagmi Action `policy.modifyWhitelist` Parameters](/sdk/typescript/wagmi/actions/policy.modifyWhitelist#parameters) ### Parameters #### config `Config | undefined` [`Config`](https://wagmi.sh/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](https://wagmi.sh/react/api/WagmiProvider). #### mutation See the [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook parameters. ## `policy.useSetAdmin` Sets the admin for a transfer policy. Requires current policy admin role. ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' const { data: result, mutate } = Hooks.policy.useSetAdminSync() // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ admin: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', policyId: 1n, }) console.log('Transaction hash:', result.receipt.transactionHash) // @log: Transaction hash: 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `policy.setAdmin` action and wait for inclusion manually: ```ts twoslash // @errors: 2322 declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' import { Actions } from 'viem/tempo' import { useWaitForTransactionReceipt } from 'wagmi' import { config } from './wagmi.config' const { data: hash, mutate } = Hooks.policy.useSetAdmin() const { data: receipt } = useWaitForTransactionReceipt({ hash }) // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ admin: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', policyId: 1n, }) if (receipt) { const { args } = Actions.policy.setAdmin.extractEvent(receipt.logs) } ``` ### Return Type See [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook return types. #### data See [Wagmi Action `policy.setAdmin` Return Type](/sdk/typescript/wagmi/actions/policy.setAdmin#return-type) #### mutate/mutateAsync See [Wagmi Action `policy.setAdmin` Parameters](/sdk/typescript/wagmi/actions/policy.setAdmin#parameters) ### Parameters #### config `Config | undefined` [`Config`](https://wagmi.sh/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](https://wagmi.sh/react/api/WagmiProvider). #### mutation See the [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook parameters. ## `policy.useWatchAdminUpdated` Watches for policy admin update events on the TIP403 Registry. ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' Hooks.policy.useWatchAdminUpdated({ onAdminUpdated: (args, log) => { console.log('New admin:', args.newAdmin) console.log('Policy ID:', args.policyId) console.log('Updater:', args.updater) }, }) ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Parameters See [Wagmi Action `policy.watchAdminUpdated` Parameters](/sdk/typescript/wagmi/actions/policy.watchAdminUpdated#parameters) #### config `Config | undefined` [`Config`](https://wagmi.sh/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](https://wagmi.sh/react/api/WagmiProvider). ## `policy.useWatchBlacklistUpdated` Watches for blacklist update events on the TIP403 Registry. ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' Hooks.policy.useWatchBlacklistUpdated({ onBlacklistUpdated: (args, log) => { console.log('Account:', args.account) console.log('Policy ID:', args.policyId) console.log('Restricted:', args.restricted) console.log('Updater:', args.updater) }, }) ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Parameters See [Wagmi Action `policy.watchBlacklistUpdated` Parameters](/sdk/typescript/wagmi/actions/policy.watchBlacklistUpdated#parameters) #### config `Config | undefined` [`Config`](https://wagmi.sh/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](https://wagmi.sh/react/api/WagmiProvider). ## `policy.useWatchCreate` Watches for policy creation events on the TIP403 Registry. ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' Hooks.policy.useWatchCreate({ onPolicyCreated: (args, log) => { console.log('Policy ID:', args.policyId) console.log('Type:', args.type) console.log('Updater:', args.updater) }, }) ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Parameters See [Wagmi Action `policy.watchCreate` Parameters](/sdk/typescript/wagmi/actions/policy.watchCreate#parameters) #### config `Config | undefined` [`Config`](https://wagmi.sh/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](https://wagmi.sh/react/api/WagmiProvider). ## `policy.useWatchWhitelistUpdated` Watches for whitelist update events on the TIP403 Registry. ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' Hooks.policy.useWatchWhitelistUpdated({ onWhitelistUpdated: (args, log) => { console.log('Account:', args.account) console.log('Allowed:', args.allowed) console.log('Policy ID:', args.policyId) console.log('Updater:', args.updater) }, }) ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Parameters See [Wagmi Action `policy.watchWhitelistUpdated` Parameters](/sdk/typescript/wagmi/actions/policy.watchWhitelistUpdated#parameters) #### config `Config | undefined` [`Config`](https://wagmi.sh/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](https://wagmi.sh/react/api/WagmiProvider). ## `reward.useClaim` Claims accumulated rewards for the caller. ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' const { data: result, mutate } = Hooks.reward.useClaimSync() // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ token: '0x20c0000000000000000000000000000000000000', }) console.log('Transaction hash:', result.receipt.transactionHash) // @log: Transaction hash: 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `reward.claim` action and wait for inclusion manually: ```ts twoslash // @errors: 2322 declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' import { useWaitForTransactionReceipt } from 'wagmi' import { config } from './wagmi.config' const { data: hash, mutate } = Hooks.reward.useClaim() const { data: receipt } = useWaitForTransactionReceipt({ hash }) // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ token: '0x20c0000000000000000000000000000000000000', }) ``` ### Return Type See [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook return types. #### data See [Wagmi Action `reward.claim` Return Type](/sdk/typescript/wagmi/actions/reward.claim#return-type) #### mutate/mutateAsync See [Wagmi Action `reward.claim` Parameters](/sdk/typescript/wagmi/actions/reward.claim#parameters) ### Parameters #### config `Config | undefined` [`Config`](https://wagmi.sh/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](https://wagmi.sh/react/api/WagmiProvider). #### mutation See the [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook parameters. ## `reward.useGetTotalPerSecond` Gets the total reward per second rate for all active streams. ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' const { data: rate } = Hooks.reward.useGetTotalPerSecond({ token: '0x20c0000000000000000000000000000000000000', }) console.log('Total rate per second:', rate) // @log: Total rate per second: 385802469135802469135n ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Return Type See [TanStack Query query docs](https://tanstack.com/query/v5/docs/framework/react/reference/useQuery) for more info hook return types. #### data See [Wagmi Action `reward.getTotalPerSecond` Return Type](/sdk/typescript/wagmi/actions/reward.getTotalPerSecond#return-type) ### Parameters See [Wagmi Action `reward.getTotalPerSecond` Parameters](/sdk/typescript/wagmi/actions/reward.getTotalPerSecond#parameters) #### query See the [TanStack Query query docs](https://tanstack.com/query/v5/docs/framework/react/reference/useQuery) for more info hook parameters. ## `reward.useSetRecipient` Sets or changes the reward recipient for a token holder. ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' const { data: result, mutate } = Hooks.reward.useSetRecipientSync() // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ recipient: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', token: '0x20c0000000000000000000000000000000000000', }) console.log('Holder:', result.holder) // @log: Holder: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 console.log('Recipient:', result.recipient) // @log: Recipient: 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `reward.setRecipient` action and wait for inclusion manually: ```ts twoslash // @errors: 2322 declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' import { Actions } from 'viem/tempo' import { useWaitForTransactionReceipt } from 'wagmi' import { config } from './wagmi.config' const { data: hash, mutate } = Hooks.reward.useSetRecipient() const { data: receipt } = useWaitForTransactionReceipt({ hash }) // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ recipient: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', token: '0x20c0000000000000000000000000000000000000', }) if (receipt) { const { args: { holder, recipient } } = Actions.reward.setRecipient.extractEvent(receipt.logs) } ``` ### Return Type See [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook return types. #### data See [Wagmi Action `reward.setRecipient` Return Type](/sdk/typescript/wagmi/actions/reward.setRecipient#return-type) #### mutate/mutateAsync See [Wagmi Action `reward.setRecipient` Parameters](/sdk/typescript/wagmi/actions/reward.setRecipient#parameters) ### Parameters #### config `Config | undefined` [`Config`](https://wagmi.sh/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](https://wagmi.sh/react/api/WagmiProvider). #### mutation See the [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook parameters. ## `reward.useStart` Starts a new reward stream that distributes tokens to opted-in holders. ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' import { parseEther } from 'viem' const { data: result, mutate } = Hooks.reward.useStartSync() // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ amount: parseEther('1000'), seconds: 2_592_000, // 30 days token: '0x20c0000000000000000000000000000000000000', }) console.log('Stream ID:', result.id) // @log: Stream ID: 1n console.log('Amount:', result.amount) // @log: Amount: 1000000000000000000000n console.log('Duration:', result.durationSeconds, 'seconds') // @log: Duration: 2592000 seconds ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `reward.start` action and wait for inclusion manually: ```ts twoslash // @errors: 2322 declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' import { Actions } from 'viem/tempo' import { parseEther } from 'viem' import { useWaitForTransactionReceipt } from 'wagmi' import { config } from './wagmi.config' const { data: hash, mutate } = Hooks.reward.useStart() const { data: receipt } = useWaitForTransactionReceipt({ hash }) // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ amount: parseEther('1000'), seconds: 2_592_000, token: '0x20c0000000000000000000000000000000000000', }) if (receipt) { const { args: { id, funder, amount, durationSeconds } } = Actions.reward.start.extractEvent(receipt.logs) } ``` ### Return Type See [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook return types. #### data See [Wagmi Action `reward.start` Return Type](/sdk/typescript/wagmi/actions/reward.start#return-type) #### mutate/mutateAsync See [Wagmi Action `reward.start` Parameters](/sdk/typescript/wagmi/actions/reward.start#parameters) ### Parameters #### config `Config | undefined` [`Config`](https://wagmi.sh/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](https://wagmi.sh/react/api/WagmiProvider). #### mutation See the [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook parameters. ## `reward.useUserRewardInfo` Gets reward information for a specific account. ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' const { data } = Hooks.reward.useUserRewardInfo({ account: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', token: '0x20c0000000000000000000000000000000000000', }) console.log('Reward recipient:', data.rewardRecipient) // @log: Reward recipient: 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb console.log('Reward balance:', data.rewardBalance) // @log: Reward balance: 1000000000000000000n console.log('Reward per token:', data.rewardPerToken) // @log: Reward per token: 385802469135802469135n ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Return Type See [TanStack Query query docs](https://tanstack.com/query/v5/docs/framework/react/reference/useQuery) for more info hook return types. #### data See [Wagmi Action `reward.getUserRewardInfo` Return Type](/sdk/typescript/wagmi/actions/reward.getUserRewardInfo#return-type) ### Parameters See [Wagmi Action `reward.getUserRewardInfo` Parameters](/sdk/typescript/wagmi/actions/reward.getUserRewardInfo#parameters) #### query See the [TanStack Query query docs](https://tanstack.com/query/v5/docs/framework/react/reference/useQuery) for more info hook parameters. ## `reward.useWatchRewardRecipientSet` Watches for reward recipient set events when token holders change their reward recipient. ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' Hooks.reward.useWatchRewardRecipientSet({ onRewardRecipientSet: (args, log) => { console.log('Holder:', args.holder) console.log('Recipient:', args.recipient) }, token: '0x20c0000000000000000000000000000000000000', }) ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Parameters See [Wagmi Action `reward.watchRewardRecipientSet` Parameters](/sdk/typescript/wagmi/actions/reward.watchRewardRecipientSet#parameters) #### config `Config | undefined` [`Config`](https://wagmi.sh/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](https://wagmi.sh/react/api/WagmiProvider). ## `reward.useWatchRewardScheduled` Watches for reward scheduled events when new reward streams are started. ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' Hooks.reward.useWatchRewardScheduled({ onRewardScheduled: (args, log) => { console.log('Stream ID:', args.id) console.log('Funder:', args.funder) console.log('Amount:', args.amount) console.log('Duration:', args.durationSeconds, 'seconds') }, token: '0x20c0000000000000000000000000000000000000', }) ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Parameters See [Wagmi Action `reward.watchRewardScheduled` Parameters](/sdk/typescript/wagmi/actions/reward.watchRewardScheduled#parameters) #### config `Config | undefined` [`Config`](https://wagmi.sh/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](https://wagmi.sh/react/api/WagmiProvider). ## `token.useApprove` Approves a spender to transfer TIP-20 tokens on behalf of the caller. ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' import { parseUnits } from 'viem' const { data: result, mutate } = Hooks.token.useApproveSync() // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ amount: parseUnits('10.5', 6), spender: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', token: '0x20c0000000000000000000000000000000000000', }) console.log('Approved amount:', result.amount) // @log: Approved amount: 10500000n console.log('Owner:', result.owner) // @log: Owner: 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb console.log('Spender:', result.spender) // @log: Spender: 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `token.approve` action and wait for inclusion manually: ```ts twoslash // @errors: 2322 declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' import { Actions } from 'viem/tempo' import { parseUnits } from 'viem' import { useWaitForTransactionReceipt } from 'wagmi' import { config } from './wagmi.config' const { data: hash, mutate } = Hooks.token.useApprove() const { data: receipt } = useWaitForTransactionReceipt({ hash }) // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ amount: parseUnits('10.5', 6), spender: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', token: '0x20c0000000000000000000000000000000000000', }) if (receipt) { const { args: { amount, owner, spender } } = Actions.token.approve.extractEvent(receipt.logs) } ``` ### Return Type See [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook return types. #### data See [Wagmi Action `token.approve` Return Type](/sdk/typescript/wagmi/actions/token.approve#return-type) #### mutate/mutateAsync See [Wagmi Action `token.approve` Parameters](/sdk/typescript/wagmi/actions/token.approve#parameters) ### Parameters #### config `Config | undefined` [`Config`](https://wagmi.sh/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](https://wagmi.sh/react/api/WagmiProvider). #### mutation See the [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook parameters. ## `token.useBurn` Burns TIP-20 tokens from the caller's balance. ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' import { parseUnits } from 'viem' const { data: result, mutate } = Hooks.token.useBurnSync() // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ amount: parseUnits('10.5', 6), token: '0x20c0000000000000000000000000000000000000', }) console.log('Burned amount:', result.amount) // @log: Burned amount: 10500000n console.log('From:', result.from) // @log: From: 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `token.burn` action and wait for inclusion manually: ```ts twoslash // @errors: 2322 declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' import { Actions } from 'viem/tempo' import { parseUnits } from 'viem' import { useWaitForTransactionReceipt } from 'wagmi' import { config } from './wagmi.config' const { data: hash, mutate } = Hooks.token.useBurn() const { data: receipt } = useWaitForTransactionReceipt({ hash }) // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ amount: parseUnits('10.5', 6), token: '0x20c0000000000000000000000000000000000000', }) if (receipt) { const { args: { amount, from } } = Actions.token.burn.extractEvent(receipt.logs) } ``` ### Return Type See [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook return types. #### data See [Wagmi Action `token.burn` Return Type](/sdk/typescript/wagmi/actions/token.burn#return-type) #### mutate/mutateAsync See [Wagmi Action `token.burn` Parameters](/sdk/typescript/wagmi/actions/token.burn#parameters) ### Parameters #### config `Config | undefined` [`Config`](https://wagmi.sh/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](https://wagmi.sh/react/api/WagmiProvider). #### mutation See the [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook parameters. ## `token.useBurnBlocked` Burns blocked TIP-20 tokens from a specific address. Requires appropriate permissions. ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' import { parseUnits } from 'viem' const { data: result, mutate } = Hooks.token.useBurnBlockedSync() // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ amount: parseUnits('10.5', 6), from: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', token: '0x20c0000000000000000000000000000000000000', }) console.log('Transaction hash:', result.receipt.transactionHash) // @log: Transaction hash: 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `token.burnBlocked` action and wait for inclusion manually: ```ts twoslash // @errors: 2322 declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' import { Actions } from 'viem/tempo' import { parseUnits } from 'viem' import { useWaitForTransactionReceipt } from 'wagmi' import { config } from './wagmi.config' const { data: hash, mutate } = Hooks.token.useBurnBlocked() const { data: receipt } = useWaitForTransactionReceipt({ hash }) // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ amount: parseUnits('10.5', 6), from: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', token: '0x20c0000000000000000000000000000000000000', }) if (receipt) { const { args } = Actions.token.burnBlocked.extractEvent(receipt.logs) } ``` ### Return Type See [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook return types. #### data See [Wagmi Action `token.burnBlocked` Return Type](/sdk/typescript/wagmi/actions/token.burnBlocked#return-type) #### mutate/mutateAsync See [Wagmi Action `token.burnBlocked` Parameters](/sdk/typescript/wagmi/actions/token.burnBlocked#parameters) ### Parameters #### config `Config | undefined` [`Config`](https://wagmi.sh/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](https://wagmi.sh/react/api/WagmiProvider). #### mutation See the [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook parameters. ## `token.useChangeTransferPolicy` Changes the transfer policy for a TIP-20 token. Requires appropriate permissions. ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' const { data: result, mutate } = Hooks.token.useChangeTransferPolicySync() // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ policy: 'allowlist', token: '0x20c0000000000000000000000000000000000000', }) console.log('Transaction hash:', result.receipt.transactionHash) // @log: Transaction hash: 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `token.changeTransferPolicy` action and wait for inclusion manually: ```ts twoslash // @errors: 2322 declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' import { Actions } from 'viem/tempo' import { useWaitForTransactionReceipt } from 'wagmi' import { config } from './wagmi.config' const { data: hash, mutate } = Hooks.token.useChangeTransferPolicy() const { data: receipt } = useWaitForTransactionReceipt({ hash }) // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ policy: 'allowlist', token: '0x20c0000000000000000000000000000000000000', }) if (receipt) { const { args } = Actions.token.changeTransferPolicy.extractEvent(receipt.logs) } ``` ### Return Type See [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook return types. #### data See [Wagmi Action `token.changeTransferPolicy` Return Type](/sdk/typescript/wagmi/actions/token.changeTransferPolicy#return-type) #### mutate/mutateAsync See [Wagmi Action `token.changeTransferPolicy` Parameters](/sdk/typescript/wagmi/actions/token.changeTransferPolicy#parameters) ### Parameters #### config `Config | undefined` [`Config`](https://wagmi.sh/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](https://wagmi.sh/react/api/WagmiProvider). #### mutation See the [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook parameters. ## `token.useCreate` Creates a new TIP-20 token, and assigns the admin role to the calling account. [Learn more](/protocol/tip20/overview) ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' const { data: result, mutate } = Hooks.token.useCreateSync() // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ currency: 'USD', name: 'My Company USD', symbol: 'CUSD', }) console.log('Token address:', result.token) // @log: Token address: 0x20c0000000000000000000000000000000000004 console.log('Token ID:', result.tokenId) // @log: Token ID: 4n console.log('Admin:', result.admin) // @log: Admin: 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `token.create` action and wait for inclusion manually: ```ts twoslash // @errors: 2322 declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' import { Actions } from 'viem/tempo' import { useWaitForTransactionReceipt } from 'wagmi' import { config } from './wagmi.config' const { data: hash, mutate } = Hooks.token.useCreate() const { data: receipt } = useWaitForTransactionReceipt({ hash }) // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ currency: 'USD', name: 'My Company USD', symbol: 'CUSD', }) if (receipt) { const { args: { token, tokenId, admin } } = Actions.token.create.extractEvent(receipt.logs) } ``` ### Return Type See [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook return types. #### data See [Wagmi Action `token.create` Return Type](/sdk/typescript/wagmi/actions/token.create#return-type) #### mutate/mutateAsync See [Wagmi Action `token.create` Parameters](/sdk/typescript/wagmi/actions/token.create#parameters) ### Parameters #### config `Config | undefined` [`Config`](https://wagmi.sh/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](https://wagmi.sh/react/api/WagmiProvider). #### mutation See the [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook parameters. ## `token.useGetAllowance` Gets the amount of tokens that a spender is approved to transfer on behalf of an owner. ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' const { data: allowance } = Hooks.token.useGetAllowance({ owner: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', spender: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8', token: '0x20c0000000000000000000000000000000000000', }) console.log('Allowance:', allowance) // @log: Allowance: 10500000n ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Return Type See [TanStack Query query docs](https://tanstack.com/query/v5/docs/framework/react/reference/useQuery) for more info hook return types. #### data See [Wagmi Action `token.getAllowance` Return Type](/sdk/typescript/wagmi/actions/token.getAllowance#return-type) ### Parameters See [Wagmi Action `token.getAllowance` Parameters](/sdk/typescript/wagmi/actions/token.getAllowance#parameters) #### query See the [TanStack Query query docs](https://tanstack.com/query/v5/docs/framework/react/reference/useQuery) for more info hook parameters. ## `token.useGetBalance` Gets the token balance of an address. ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' const { data: balance } = Hooks.token.useGetBalance({ account: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', token: '0x20c0000000000000000000000000000000000000', }) console.log('Balance:', balance) // @log: Balance: 10500000n ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Return Type See [TanStack Query query docs](https://tanstack.com/query/v5/docs/framework/react/reference/useQuery) for more info hook return types. #### data See [Wagmi Action `token.getBalance` Return Type](/sdk/typescript/wagmi/actions/token.getBalance#return-type) ### Parameters See [Wagmi Action `token.getBalance` Parameters](/sdk/typescript/wagmi/actions/token.getBalance#parameters) #### query See the [TanStack Query query docs](https://tanstack.com/query/v5/docs/framework/react/reference/useQuery) for more info hook parameters. ## `token.useGetMetadata` Gets the metadata for a TIP-20 token, including name, symbol, decimals, currency, and total supply. ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' const { data: metadata } = Hooks.token.useGetMetadata({ token: '0x20c0000000000000000000000000000000000000', }) console.log('Currency:', metadata?.currency) // @log: Currency: USD console.log('Name:', metadata?.name) // @log: Name: United States Dollar console.log('Symbol:', metadata?.symbol) // @log: Symbol: USD console.log('Decimals:', metadata?.decimals) // @log: Decimals: 18 console.log('Total Supply:', metadata?.totalSupply) // @log: Total Supply: 1000000000000000000000n ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Return Type See [TanStack Query query docs](https://tanstack.com/query/v5/docs/framework/react/reference/useQuery) for more info hook return types. #### data See [Wagmi Action `token.getMetadata` Return Type](/sdk/typescript/wagmi/actions/token.getMetadata#return-type) ### Parameters See [Wagmi Action `token.getMetadata` Parameters](/sdk/typescript/wagmi/actions/token.getMetadata#parameters) #### query See the [TanStack Query query docs](https://tanstack.com/query/v5/docs/framework/react/reference/useQuery) for more info hook parameters. ## `token.useGrantRoles` Grants roles to an address for a TIP-20 token. Requires appropriate permissions. [Learn more about token roles](/protocol/tip403/spec) ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' const { data: result, mutate } = Hooks.token.useGrantRolesSync() // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ account: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', roles: ['issuer', 'blocker'], token: '0x20c0000000000000000000000000000000000000', }) console.log('Transaction hash:', result.receipt.transactionHash) // @log: Transaction hash: 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `token.grantRoles` action and wait for inclusion manually: ```ts twoslash // @errors: 2322 declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' import { Actions } from 'viem/tempo' import { useWaitForTransactionReceipt } from 'wagmi' import { config } from './wagmi.config' const { data: hash, mutate } = Hooks.token.useGrantRoles() const { data: receipt } = useWaitForTransactionReceipt({ hash }) // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ account: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', roles: ['issuer', 'blocker'], token: '0x20c0000000000000000000000000000000000000', }) if (receipt) { const { args } = Actions.token.grantRoles.extractEvent(receipt.logs) } ``` ### Return Type See [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook return types. #### data See [Wagmi Action `token.grantRoles` Return Type](/sdk/typescript/wagmi/actions/token.grantRoles#return-type) #### mutate/mutateAsync See [Wagmi Action `token.grantRoles` Parameters](/sdk/typescript/wagmi/actions/token.grantRoles#parameters) ### Parameters #### config `Config | undefined` [`Config`](https://wagmi.sh/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](https://wagmi.sh/react/api/WagmiProvider). #### mutation See the [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook parameters. ## `token.useHasRole` Checks if an address has a specific role for a TIP-20 token. [Learn more about token roles](/protocol/tip403/spec) ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' const { data: hasRole } = Hooks.token.useHasRole({ account: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', role: 'issuer', token: '0x20c0000000000000000000000000000000000011', }) console.log('Has issuer role:', hasRole) // @log: Has issuer role: true ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Return Type See [TanStack Query query docs](https://tanstack.com/query/v5/docs/framework/react/reference/useQuery) for more info hook return types. #### data See [Wagmi Action `token.hasRole` Return Type](/sdk/typescript/wagmi/actions/token.hasRole#return-type) ### Parameters See [Wagmi Action `token.hasRole` Parameters](/sdk/typescript/wagmi/actions/token.hasRole#parameters) #### query See the [TanStack Query query docs](https://tanstack.com/query/v5/docs/framework/react/reference/useQuery) for more info hook parameters. ## `token.useMint` Mints new TIP-20 tokens to a recipient. Requires the `ISSUER` role. [Learn more about roles](/protocol/tip20/spec#role-based-access-control) ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' import { parseUnits } from 'viem' const { data: result, mutate } = Hooks.token.useMintSync() // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ amount: parseUnits('10.5', 6), to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', token: '0x20c0000000000000000000000000000000000000', }) console.log('Minted amount:', result.amount) // @log: Minted amount: 10500000n console.log('Recipient:', result.to) // @log: Recipient: 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `token.mint` action and wait for inclusion manually: ```ts twoslash // @errors: 2322 declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' import { Actions } from 'viem/tempo' import { parseUnits } from 'viem' import { useWaitForTransactionReceipt } from 'wagmi' import { config } from './wagmi.config' const { data: hash, mutate } = Hooks.token.useMint() const { data: receipt } = useWaitForTransactionReceipt({ hash }) // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ amount: parseUnits('10.5', 6), to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', token: '0x20c0000000000000000000000000000000000000', }) if (receipt) { const { args: { amount, to } } = Actions.token.mint.extractEvent(receipt.logs) } ``` ### Return Type See [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook return types. #### data See [Wagmi Action `token.mint` Return Type](/sdk/typescript/wagmi/actions/token.mint#return-type) #### mutate/mutateAsync See [Wagmi Action `token.mint` Parameters](/sdk/typescript/wagmi/actions/token.mint#parameters) ### Parameters #### config `Config | undefined` [`Config`](https://wagmi.sh/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](https://wagmi.sh/react/api/WagmiProvider). #### mutation See the [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook parameters. ## `token.usePause` Pauses a TIP-20 token, preventing transfers. Requires appropriate permissions. ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' const { data: result, mutate } = Hooks.token.usePauseSync() // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ token: '0x20c0000000000000000000000000000000000000', }) console.log('Transaction hash:', result.receipt.transactionHash) // @log: Transaction hash: 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `token.pause` action and wait for inclusion manually: ```ts twoslash // @errors: 2322 declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' import { Actions } from 'viem/tempo' import { useWaitForTransactionReceipt } from 'wagmi' import { config } from './wagmi.config' const { data: hash, mutate } = Hooks.token.usePause() const { data: receipt } = useWaitForTransactionReceipt({ hash }) // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ token: '0x20c0000000000000000000000000000000000000', }) if (receipt) { const { args } = Actions.token.pause.extractEvent(receipt.logs) } ``` ### Return Type See [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook return types. #### data See [Wagmi Action `token.pause` Return Type](/sdk/typescript/wagmi/actions/token.pause#return-type) #### mutate/mutateAsync See [Wagmi Action `token.pause` Parameters](/sdk/typescript/wagmi/actions/token.pause#parameters) ### Parameters #### config `Config | undefined` [`Config`](https://wagmi.sh/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](https://wagmi.sh/react/api/WagmiProvider). #### mutation See the [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook parameters. ## `token.useRenounceRoles` Renounces roles from the caller for a TIP-20 token. [Learn more about token roles](/protocol/tip403/spec) ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' const { data: result, mutate } = Hooks.token.useRenounceRolesSync() // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ roles: ['issuer', 'blocker'], token: '0x20c0000000000000000000000000000000000000', }) console.log('Transaction hash:', result.receipt.transactionHash) // @log: Transaction hash: 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `token.renounceRoles` action and wait for inclusion manually: ```ts twoslash // @errors: 2322 declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' import { Actions } from 'viem/tempo' import { useWaitForTransactionReceipt } from 'wagmi' import { config } from './wagmi.config' const { data: hash, mutate } = Hooks.token.useRenounceRoles() const { data: receipt } = useWaitForTransactionReceipt({ hash }) // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ roles: ['issuer', 'blocker'], token: '0x20c0000000000000000000000000000000000000', }) if (receipt) { const { args } = Actions.token.renounceRoles.extractEvent(receipt.logs) } ``` ### Return Type See [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook return types. #### data See [Wagmi Action `token.renounceRoles` Return Type](/sdk/typescript/wagmi/actions/token.renounceRoles#return-type) #### mutate/mutateAsync See [Wagmi Action `token.renounceRoles` Parameters](/sdk/typescript/wagmi/actions/token.renounceRoles#parameters) ### Parameters #### config `Config | undefined` [`Config`](https://wagmi.sh/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](https://wagmi.sh/react/api/WagmiProvider). #### mutation See the [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook parameters. ## `token.useRevokeRoles` Revokes roles from an address for a TIP-20 token. Requires appropriate permissions. [Learn more about token roles](/protocol/tip403/spec) ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' const { data: result, mutate } = Hooks.token.useRevokeRolesSync() // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ account: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', roles: ['issuer', 'blocker'], token: '0x20c0000000000000000000000000000000000000', }) console.log('Transaction hash:', result.receipt.transactionHash) // @log: Transaction hash: 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `token.revokeRoles` action and wait for inclusion manually: ```ts twoslash // @errors: 2322 declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' import { Actions } from 'viem/tempo' import { useWaitForTransactionReceipt } from 'wagmi' import { config } from './wagmi.config' const { data: hash, mutate } = Hooks.token.useRevokeRoles() const { data: receipt } = useWaitForTransactionReceipt({ hash }) // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ account: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', roles: ['issuer', 'blocker'], token: '0x20c0000000000000000000000000000000000000', }) if (receipt) { const { args } = Actions.token.revokeRoles.extractEvent(receipt.logs) } ``` ### Return Type See [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook return types. #### data See [Wagmi Action `token.revokeRoles` Return Type](/sdk/typescript/wagmi/actions/token.revokeRoles#return-type) #### mutate/mutateAsync See [Wagmi Action `token.revokeRoles` Parameters](/sdk/typescript/wagmi/actions/token.revokeRoles#parameters) ### Parameters #### config `Config | undefined` [`Config`](https://wagmi.sh/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](https://wagmi.sh/react/api/WagmiProvider). #### mutation See the [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook parameters. ## `token.useSetRoleAdmin` Sets the admin role for a specific role on a TIP-20 token. Requires appropriate permissions. [Learn more about token roles](/protocol/tip403/spec) ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' const { data: result, mutate } = Hooks.token.useSetRoleAdminSync() // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ adminRole: 'admin', role: 'issuer', token: '0x20c0000000000000000000000000000000000000', }) console.log('Transaction hash:', result.receipt.transactionHash) // @log: Transaction hash: 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `token.setRoleAdmin` action and wait for inclusion manually: ```ts twoslash // @errors: 2322 declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' import { Actions } from 'viem/tempo' import { useWaitForTransactionReceipt } from 'wagmi' import { config } from './wagmi.config' const { data: hash, mutate } = Hooks.token.useSetRoleAdmin() const { data: receipt } = useWaitForTransactionReceipt({ hash }) // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ adminRole: 'admin', role: 'issuer', token: '0x20c0000000000000000000000000000000000000', }) if (receipt) { const { args } = Actions.token.setRoleAdmin.extractEvent(receipt.logs) } ``` ### Return Type See [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook return types. #### data See [Wagmi Action `token.setRoleAdmin` Return Type](/sdk/typescript/wagmi/actions/token.setRoleAdmin#return-type) #### mutate/mutateAsync See [Wagmi Action `token.setRoleAdmin` Parameters](/sdk/typescript/wagmi/actions/token.setRoleAdmin#parameters) ### Parameters #### config `Config | undefined` [`Config`](https://wagmi.sh/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](https://wagmi.sh/react/api/WagmiProvider). #### mutation See the [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook parameters. ## `token.useSetSupplyCap` Sets the supply cap for a TIP-20 token. Requires appropriate permissions. [Learn more about roles](/protocol/tip20/spec#role-based-access-control) ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' import { parseUnits } from 'viem' const { data: result, mutate } = Hooks.token.useSetSupplyCapSync() // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ supplyCap: parseUnits('1000000', 6), token: '0x20c0000000000000000000000000000000000000', }) console.log('Transaction hash:', result.receipt.transactionHash) // @log: Transaction hash: 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `token.setSupplyCap` action and wait for inclusion manually: ```ts twoslash // @errors: 2322 declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' import { Actions } from 'viem/tempo' import { parseUnits } from 'viem' import { useWaitForTransactionReceipt } from 'wagmi' import { config } from './wagmi.config' const { data: hash, mutate } = Hooks.token.useSetSupplyCap() const { data: receipt } = useWaitForTransactionReceipt({ hash }) // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ supplyCap: parseUnits('1000000', 6), token: '0x20c0000000000000000000000000000000000000', }) if (receipt) { const { args } = Actions.token.setSupplyCap.extractEvent(receipt.logs) } ``` ### Return Type See [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook return types. #### data See [Wagmi Action `token.setSupplyCap` Return Type](/sdk/typescript/wagmi/actions/token.setSupplyCap#return-type) #### mutate/mutateAsync See [Wagmi Action `token.setSupplyCap` Parameters](/sdk/typescript/wagmi/actions/token.setSupplyCap#parameters) ### Parameters #### config `Config | undefined` [`Config`](https://wagmi.sh/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](https://wagmi.sh/react/api/WagmiProvider). #### mutation See the [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook parameters. ## `token.useTransfer` Transfers TIP-20 tokens from the caller to a recipient. ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' import { parseUnits } from 'viem' const { data: result, mutate } = Hooks.token.useTransferSync() // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ amount: parseUnits('10.5', 6), to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', token: '0x20c0000000000000000000000000000000000000', }) console.log('Transfer amount:', result.amount) // @log: Transfer amount: 10500000n console.log('From:', result.from) // @log: From: 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb console.log('To:', result.to) // @log: To: 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `token.transfer` action and wait for inclusion manually: ```ts twoslash // @errors: 2322 declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' import { Actions } from 'viem/tempo' import { parseUnits } from 'viem' import { useWaitForTransactionReceipt } from 'wagmi' import { config } from './wagmi.config' const { data: hash, mutate } = Hooks.token.useTransfer() const { data: receipt } = useWaitForTransactionReceipt({ hash }) // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ amount: parseUnits('10.5', 6), to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', token: '0x20c0000000000000000000000000000000000000', }) if (receipt) { const { args: { amount, from, to } } = Actions.token.transfer.extractEvent(receipt.logs) } ``` ### Return Type See [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook return types. #### data See [Wagmi Action `token.transfer` Return Type](/sdk/typescript/wagmi/actions/token.transfer#return-type) #### mutate/mutateAsync See [Wagmi Action `token.transfer` Parameters](/sdk/typescript/wagmi/actions/token.transfer#parameters) ### Parameters #### config `Config | undefined` [`Config`](https://wagmi.sh/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](https://wagmi.sh/react/api/WagmiProvider). #### mutation See the [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook parameters. ## `token.useUnpause` Unpauses a TIP-20 token, allowing transfers to resume. Requires appropriate permissions. ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' const { data: result, mutate } = Hooks.token.useUnpauseSync() // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ token: '0x20c0000000000000000000000000000000000000', }) console.log('Transaction hash:', result.receipt.transactionHash) // @log: Transaction hash: 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `token.unpause` action and wait for inclusion manually: ```ts twoslash // @errors: 2322 declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' import { Actions } from 'viem/tempo' import { useWaitForTransactionReceipt } from 'wagmi' import { config } from './wagmi.config' const { data: hash, mutate } = Hooks.token.useUnpause() const { data: receipt } = useWaitForTransactionReceipt({ hash }) // Call `mutate` in response to user action (e.g. button click, form submission) mutate({ token: '0x20c0000000000000000000000000000000000000', }) if (receipt) { const { args } = Actions.token.unpause.extractEvent(receipt.logs) } ``` ### Return Type See [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook return types. #### data See [Wagmi Action `token.unpause` Return Type](/sdk/typescript/wagmi/actions/token.unpause#return-type) #### mutate/mutateAsync See [Wagmi Action `token.unpause` Parameters](/sdk/typescript/wagmi/actions/token.unpause#parameters) ### Parameters #### config `Config | undefined` [`Config`](https://wagmi.sh/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](https://wagmi.sh/react/api/WagmiProvider). #### mutation See the [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation) for more info hook parameters. ## `token.useWatchAdminRole` Watches for role admin update events on TIP20 tokens. ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' Hooks.token.useWatchAdminRole({ onRoleAdminUpdated: (args, log) => { console.log('New admin role:', args.newAdminRole) console.log('Previous admin role:', args.previousAdminRole) console.log('Role:', args.role) }, token: '0x20c0000000000000000000000000000000000000', }) ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Parameters See [Wagmi Action `token.watchAdminRole` Parameters](/sdk/typescript/wagmi/actions/token.watchAdminRole#parameters) #### config `Config | undefined` [`Config`](https://wagmi.sh/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](https://wagmi.sh/react/api/WagmiProvider). ## `token.useWatchApprove` Watches for token approval events on TIP20 tokens. ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' Hooks.token.useWatchApprove({ onApproval: (args, log) => { console.log('Amount:', args.amount) console.log('Owner:', args.owner) console.log('Spender:', args.spender) }, token: '0x20c0000000000000000000000000000000000000', }) ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Parameters See [Wagmi Action `token.watchApprove` Parameters](/sdk/typescript/wagmi/actions/token.watchApprove#parameters) #### config `Config | undefined` [`Config`](https://wagmi.sh/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](https://wagmi.sh/react/api/WagmiProvider). ## `token.useWatchBurn` Watches for token burn events on TIP20 tokens. ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' Hooks.token.useWatchBurn({ onBurn: (args, log) => { console.log('Amount:', args.amount) console.log('Burner:', args.burner) console.log('From:', args.from) }, token: '0x20c0000000000000000000000000000000000000', }) ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Parameters See [Wagmi Action `token.watchBurn` Parameters](/sdk/typescript/wagmi/actions/token.watchBurn#parameters) #### config `Config | undefined` [`Config`](https://wagmi.sh/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](https://wagmi.sh/react/api/WagmiProvider). ## `token.useWatchCreate` Watches for new TIP20 token creation events on the TIP20 Factory. ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' Hooks.token.useWatchCreate({ onTokenCreated: (args, log) => { console.log('Creator:', args.creator) console.log('Name:', args.name) console.log('Symbol:', args.symbol) console.log('Token address:', args.tokenAddress) console.log('Token ID:', args.tokenId) }, }) ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Parameters See [Wagmi Action `token.watchCreate` Parameters](/sdk/typescript/wagmi/actions/token.watchCreate#parameters) #### config `Config | undefined` [`Config`](https://wagmi.sh/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](https://wagmi.sh/react/api/WagmiProvider). ## `token.useWatchMint` Watches for token mint events on TIP20 tokens. ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' Hooks.token.useWatchMint({ onMint: (args, log) => { console.log('Amount:', args.amount) console.log('Minter:', args.minter) console.log('To:', args.to) }, token: '0x20c0000000000000000000000000000000000000', }) ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Parameters See [Wagmi Action `token.watchMint` Parameters](/sdk/typescript/wagmi/actions/token.watchMint#parameters) #### config `Config | undefined` [`Config`](https://wagmi.sh/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](https://wagmi.sh/react/api/WagmiProvider). ## `token.useWatchRole` Watches for role membership update events on TIP20 tokens. ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' Hooks.token.useWatchRole({ onRoleUpdated: (args, log) => { console.log('Account:', args.account) console.log('Has role:', args.hasRole) console.log('Role:', args.role) console.log('Type:', args.type) // 'granted' or 'revoked' console.log('Updater:', args.updater) }, token: '0x20c0000000000000000000000000000000000000', }) ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Parameters See [Wagmi Action `token.watchRole` Parameters](/sdk/typescript/wagmi/actions/token.watchRole#parameters) #### config `Config | undefined` [`Config`](https://wagmi.sh/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](https://wagmi.sh/react/api/WagmiProvider). ## `token.useWatchTransfer` Watches for token transfer events on TIP20 tokens. ### Usage :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' Hooks.token.useWatchTransfer({ onTransfer: (args, log) => { console.log('Amount:', args.amount) console.log('From:', args.from) console.log('To:', args.to) }, token: '0x20c0000000000000000000000000000000000000', }) ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Parameters See [Wagmi Action `token.watchTransfer` Parameters](/sdk/typescript/wagmi/actions/token.watchTransfer#parameters) #### config `Config | undefined` [`Config`](https://wagmi.sh/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](https://wagmi.sh/react/api/WagmiProvider). ## `dangerous_secp256k1` Connector for a Secp256k1 EOA. :::warning NOT RECOMMENDED FOR PRODUCTION USAGE. This connector stores private keys in clear text, and are bound to the session length of the storage used. Instead, use this connector for testing workflows, like end-to-end tests. ::: ### Usage ```ts twoslash [wagmi.config.ts] // @noErrors import { tempoTestnet } from 'viem/chains' import { dangerous_secp256k1 } from 'tempo.ts/wagmi' // [!code focus] import { createConfig, http } from 'wagmi' export const config = createConfig({ connectors: [dangerous_secp256k1()], // [!code focus] chains: [tempoTestnet], multiInjectedProviderDiscovery: false, transports: { [tempo.id]: http(), }, }) ``` ### Parameters #### account * **Type:** `LocalAccount` Optional account to use with connector. If not provided, one is created internally for you. import LucideFingerprint from '~icons/lucide/fingerprint' import LucideKeyRound from '~icons/lucide/key-round' import * as Card from "../../../../../components/Card.tsx" ## Transports ## `webAuthn` Connector for a WebAuthn EOA. ### Usage ```ts twoslash [wagmi.config.ts] // @noErrors import { tempoTestnet } from 'viem/chains' import { KeyManager, webAuthn } from 'tempo.ts/wagmi' // [!code focus] import { createConfig, http } from 'wagmi' export const config = createConfig({ connectors: [ webAuthn({ // [!code focus] keyManager: KeyManager.localStorage(), // [!code focus] }), // [!code focus] ], chains: [tempoTestnet], multiInjectedProviderDiscovery: false, transports: { [tempoTestnet.id]: http(), }, }) ``` :::warning The `KeyManager.localStorage()` implementation is not recommended for production use as it stores public keys on the client device, meaning it cannot be re-extracted when the user's storage is cleared or if the user is on another device. For production, you should opt for a remote key manager such as [`KeyManager.http`](/sdk/typescript/wagmi/keyManagers/http). ::: ### Parameters #### keyManager * **Type:** `KeyManager` Public key manager that handles credential storage and retrieval. This is required for managing WebAuthn credentials. The `KeyManager` interface provides: * `getChallenge()`: Optional function to fetch a challenge for registration * `getPublicKey(parameters)`: Function to retrieve the public key for a credential * `setPublicKey(parameters)`: Function to store the public key for a credential See [`KeyManager`](/sdk/typescript/wagmi/keyManagers) for built-in implementations. #### createOptions (optional) Options for WebAuthn registration. ##### createOptions.createFn * **Type:** `(options?: CredentialCreationOptions | undefined) => Promise` * **Default:** `window.navigator.credentials.create` Credential creation function. Useful for environments that do not support the WebAuthn API natively (i.e. React Native or testing environments). ##### createOptions.label * **Type:** `string` Label associated with the WebAuthn registration. ##### createOptions.timeout * **Type:** `number` A numerical hint, in milliseconds, which indicates the time the calling web app is willing to wait for the creation operation to complete. ##### createOptions.userId * **Type:** `Bytes.Bytes` User ID associated with the WebAuthn registration. #### getOptions (optional) Options for WebAuthn authentication. ##### getOptions.getFn * **Type:** `(options?: CredentialRequestOptions) => Promise` * **Default:** `window.navigator.credentials.get` Credential request function. Useful for environments that do not support the WebAuthn API natively (i.e. React Native or testing environments). #### rpId (optional) * **Type:** `string` The default RP ID to use for WebAuthn operations. Can be overridden by `createOptions.rpId` or `getOptions.rpId`. import WriteParameters from '../../../../../snippets/write-parameters.mdx' ## `amm.burn` Burns liquidity tokens and receives the underlying token pair. [Learn more about the Fee AMM](/protocol/fees/spec-fee-amm) ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { parseUnits } from 'viem' import { config } from './wagmi.config' const { amountUserToken, amountValidatorToken, receipt } = await Actions.amm.burnSync(config, { liquidity: parseUnits('10.5', 18), to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', userToken: '0x20c0000000000000000000000000000000000000', validatorToken: '0x20c0000000000000000000000000000000000001', }) console.log('Received user tokens:', amountUserToken) // @log: Received user tokens: 5250000000000000000n console.log('Received validator tokens:', amountValidatorToken) // @log: Received validator tokens: 5250000000000000000n ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `amm.burn` action and wait for inclusion manually: ```ts twoslash import { Actions } from 'tempo.ts/wagmi' import { Actions as viem_Actions } from 'viem/tempo' import { waitForTransactionReceipt } from 'wagmi/actions' import { parseUnits } from 'viem' import { config } from './wagmi.config' const hash = await Actions.amm.burn(config, { liquidity: parseUnits('10.5', 18), to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', userToken: '0x20c0000000000000000000000000000000000000', validatorToken: '0x20c0000000000000000000000000000000000001', }) const receipt = await waitForTransactionReceipt(config, { hash }) const { args: { amountUserToken, amountValidatorToken } } = viem_Actions.amm.burn.extractEvent(receipt.logs) ``` ### Return Type ```ts type ReturnType = { /** Amount of user tokens received */ amountUserToken: bigint /** Amount of validator tokens received */ amountValidatorToken: bigint /** Amount of liquidity tokens burned */ liquidity: bigint /** Transaction receipt */ receipt: TransactionReceipt /** Address that initiated the burn */ sender: Address /** Address that received the underlying tokens */ to: Address /** Address of the user token */ userToken: Address /** Address of the validator token */ validatorToken: Address } ``` ### Parameters #### liquidity * **Type:** `bigint` Amount of LP tokens to burn. #### to * **Type:** `Address` Address to send tokens to. #### userToken * **Type:** `Address | bigint` Address or ID of the user token. #### validatorToken * **Type:** `Address | bigint` Address or ID of the validator token. import ReadParameters from '../../../../../snippets/read-parameters.mdx' ## `amm.getLiquidityBalance` Gets the liquidity balance for an address in a specific pool. ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { config } from './wagmi.config' const balance = await Actions.amm.getLiquidityBalance(config, { address: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', userToken: '0x20c0000000000000000000000000000000000000', validatorToken: '0x20c0000000000000000000000000000000000001', }) console.log('Liquidity balance:', balance) // @log: Liquidity balance: 10500000000000000000n ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Return Type ```ts type ReturnType = bigint // Liquidity balance ``` ### Parameters #### address * **Type:** `Address` Address to check balance for. #### poolId (optional) * **Type:** `Hex` Pool ID. #### userToken (optional) * **Type:** `Address | bigint` User token. #### validatorToken (optional) * **Type:** `Address | bigint` Validator token. import ReadParameters from '../../../../../snippets/read-parameters.mdx' ## `amm.getPool` Gets the reserves for a liquidity pool. ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { config } from './wagmi.config' const pool = await Actions.amm.getPool(config, { userToken: '0x20c0000000000000000000000000000000000000', validatorToken: '0x20c0000000000000000000000000000000000001', }) console.log('User token reserve:', pool.reserveUserToken) // @log: User token reserve: 1000000000000000000000n console.log('Validator token reserve:', pool.reserveValidatorToken) // @log: Validator token reserve: 1000000000000000000000n console.log('Total supply:', pool.totalSupply) // @log: Total supply: 1000000000000000000000n ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Return Type ```ts type ReturnType = { /** Reserve of user token */ reserveUserToken: bigint /** Reserve of validator token */ reserveValidatorToken: bigint /** Total supply of LP tokens */ totalSupply: bigint } ``` ### Parameters #### userToken * **Type:** `Address | bigint` Address or ID of the user token. #### validatorToken * **Type:** `Address | bigint` Address or ID of the validator token. import WriteParameters from '../../../../../snippets/write-parameters.mdx' ## `amm.mint` Mints liquidity tokens by providing a token pair. [Learn more about the Fee AMM](/protocol/fees/spec-fee-amm) ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { parseUnits } from 'viem' import { config } from './wagmi.config' const { liquidity, receipt } = await Actions.amm.mintSync(config, { to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', userTokenAddress: '0x20c0000000000000000000000000000000000000', validatorTokenAddress: '0x20c0000000000000000000000000000000000001', validatorTokenAmount: parseUnits('100', 6), }) console.log('Liquidity minted:', liquidity) // @log: Liquidity minted: 100000000n ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `amm.mint` action and wait for inclusion manually: ```ts twoslash import { Actions } from 'tempo.ts/wagmi' import { Actions as viem_Actions } from 'viem/tempo' import { waitForTransactionReceipt } from 'wagmi/actions' import { parseUnits } from 'viem' import { config } from './wagmi.config' const hash = await Actions.amm.mint(config, { to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', userTokenAddress: '0x20c0000000000000000000000000000000000000', validatorTokenAddress: '0x20c0000000000000000000000000000000000001', validatorTokenAmount: parseUnits('100', 6), }) const receipt = await waitForTransactionReceipt(config, { hash }) const { args: { liquidity } } = viem_Actions.amm.mint.extractEvent(receipt.logs) ``` ### Return Type ```ts type ReturnType = { /** Amount of user tokens provided */ amountUserToken: bigint /** Amount of validator tokens provided */ amountValidatorToken: bigint /** Amount of liquidity tokens minted */ liquidity: bigint /** Transaction receipt */ receipt: TransactionReceipt /** Address that initiated the mint */ sender: Address /** Address of the user token */ userToken: Address /** Address of the validator token */ validatorToken: Address } ``` ### Parameters #### to * **Type:** `Address` Address to mint the liquidity tokens to. #### userTokenAddress * **Type:** `Address | bigint` User token address. #### validatorTokenAddress * **Type:** `Address | bigint` Validator token address. #### validatorTokenAmount * **Type:** `bigint` Amount of validator tokens to provide. import WriteParameters from '../../../../../snippets/write-parameters.mdx' ## `amm.rebalanceSwap` Performs a rebalance swap between user and validator tokens. [Learn more about the Fee AMM](/protocol/fees/spec-fee-amm) ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { parseUnits } from 'viem' import { config } from './wagmi.config' const { amountIn, receipt } = await Actions.amm.rebalanceSwapSync(config, { amountOut: parseUnits('10.5', 6), to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', userToken: '0x20c0000000000000000000000000000000000000', validatorToken: '0x20c0000000000000000000000000000000000001', }) console.log('Amount in:', amountIn) // @log: 10605000n ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `amm.rebalanceSwap` action and wait for inclusion manually: ```ts twoslash import { Actions } from 'tempo.ts/wagmi' import { Actions as viem_Actions } from 'viem/tempo' import { waitForTransactionReceipt } from 'wagmi/actions' import { parseUnits } from 'viem' import { config } from './wagmi.config' const hash = await Actions.amm.rebalanceSwap(config, { amountOut: parseUnits('10.5', 6), to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', userToken: '0x20c0000000000000000000000000000000000000', validatorToken: '0x20c0000000000000000000000000000000000001', }) const receipt = await waitForTransactionReceipt(config, { hash }) const { args: { amountIn } } = viem_Actions.amm.rebalanceSwap.extractEvent(receipt.logs) ``` ### Return Type ```ts type ReturnType = { /** Amount of tokens required for the swap */ amountIn: bigint /** Amount of output tokens received */ amountOut: bigint /** Transaction receipt */ receipt: TransactionReceipt /** Address that initiated the swap */ swapper: Address /** Address of the user token */ userToken: Address /** Address of the validator token */ validatorToken: Address } ``` ### Parameters #### amountOut * **Type:** `bigint` Amount of user token to receive. #### to * **Type:** `Address` Address to send the user token to. #### userToken * **Type:** `Address | bigint` Address or ID of the user token. #### validatorToken * **Type:** `Address | bigint` Address or ID of the validator token. ## `amm.watchBurn` Watches for liquidity burn events on the Fee AMM. ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { config } from './wagmi.config' const unwatch = Actions.amm.watchBurn(config, { onBurn: (args, log) => { console.log('User token amount:', args.amountUserToken) console.log('Validator token amount:', args.amountValidatorToken) console.log('Liquidity burned:', args.liquidity) console.log('Sender:', args.sender) console.log('Recipient:', args.to) console.log('User token:', args.userToken) console.log('Validator token:', args.validatorToken) }, }) // Later, stop watching unwatch() ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Return Type ```ts type ReturnType = () => void ``` Returns a function to unsubscribe from the event. ### Parameters #### onBurn * **Type:** `function` ```ts declare function onBurn(args: Args, log: Log): void type Args = { /** Amount of user token received */ amountUserToken: bigint /** Amount of validator token received */ amountValidatorToken: bigint /** Amount of LP tokens burned */ liquidity: bigint /** Address that removed liquidity */ sender: Address /** Address that received the tokens */ to: Address /** Address of the user token */ userToken: Address /** Address of the validator token */ validatorToken: Address } ``` Callback to invoke when liquidity is removed. #### args (optional) * **Type:** `object` ```ts type Args = { /** Filter by sender address */ sender?: Address | Address[] | null /** Filter by user token address */ userToken?: Address | Address[] | null /** Filter by validator token address */ validatorToken?: Address | Address[] | null } ``` Filter events by indexed parameters. #### userToken (optional) * **Type:** `Address | bigint` Address or ID of the user token to filter events. #### validatorToken (optional) * **Type:** `Address | bigint` Address or ID of the validator token to filter events. #### fromBlock (optional) * **Type:** `bigint` Block to start listening from. #### onError (optional) * **Type:** `function` ```ts declare function onError(error: Error): void ``` The callback to call when an error occurred when trying to get for a new block. #### poll (optional) * **Type:** `true` Enable polling mode. #### pollingInterval (optional) * **Type:** `number` Polling frequency (in ms). Defaults to Client's pollingInterval config. ## `amm.watchFeeSwap` Watches for fee swap events on the Fee AMM. ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { config } from './wagmi.config' const unwatch = Actions.amm.watchFeeSwap(config, { onFeeSwap: (args, log) => { console.log('User token:', args.userToken) console.log('Validator token:', args.validatorToken) console.log('Amount in:', args.amountIn) console.log('Amount out:', args.amountOut) }, }) // Later, stop watching unwatch() ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Return Type ```ts type ReturnType = () => void ``` Returns a function to unsubscribe from the event. ### Parameters #### onFeeSwap * **Type:** `function` ```ts declare function onFeeSwap(args: Args, log: Log): void type Args = { /** Address of the user token */ userToken: Address /** Address of the validator token */ validatorToken: Address /** Amount of user token swapped in */ amountIn: bigint /** Amount of validator token received */ amountOut: bigint } ``` Callback to invoke when a fee swap occurs. #### userToken (optional) * **Type:** `Address | bigint` Address or ID of the user token to filter events. #### validatorToken (optional) * **Type:** `Address | bigint` Address or ID of the validator token to filter events. #### fromBlock (optional) * **Type:** `bigint` Block to start listening from. #### onError (optional) * **Type:** `(error: Error) => void` The callback to call when an error occurred when trying to get for a new block. #### poll (optional) * **Type:** `true` Whether to use polling. #### pollingInterval (optional) * **Type:** `number` Polling frequency (in ms). Defaults to Client's pollingInterval config. ## `amm.watchMint` Watches for liquidity mint events on the Fee AMM. ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { config } from './wagmi.config' const unwatch = Actions.amm.watchMint(config, { onMint: (args, log) => { console.log('Liquidity minted:', args.liquidity) console.log('Sender:', args.sender) console.log('User token amount:', args.userToken.amount) console.log('Validator token amount:', args.validatorToken.amount) }, }) // Later, stop watching unwatch() ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Return Type ```ts type ReturnType = () => void ``` Returns a function to unsubscribe from the event. ### Parameters #### onMint * **Type:** ```ts declare function onMint(args: Args, log: Log): void type Args = { /** Amount of LP tokens minted */ liquidity: bigint /** Address that added liquidity */ sender: Address /** User token details */ userToken: { /** Address of the user token */ address: Address /** Amount of user token added */ amount: bigint } /** Validator token details */ validatorToken: { /** Address of the validator token */ address: Address /** Amount of validator token added */ amount: bigint } } ``` Callback to invoke when liquidity is added. #### sender (optional) * **Type:** `Address | bigint` Address or ID of the sender to filter events. #### userToken (optional) * **Type:** `Address | bigint` Address or ID of the user token to filter events. #### validatorToken (optional) * **Type:** `Address | bigint` Address or ID of the validator token to filter events. #### fromBlock (optional) * **Type:** `bigint` Block to start listening from. #### onError (optional) * **Type:** `(error: Error) => void` The callback to call when an error occurred when trying to get for a new block. #### poll (optional) * **Type:** `true` Enable polling mode for watching events. #### pollingInterval (optional) * **Type:** `number` Polling frequency (in ms). Defaults to Client's pollingInterval config. ## `amm.watchRebalanceSwap` Watches for rebalance swap events on the Fee AMM. ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { config } from './wagmi.config' const unwatch = Actions.amm.watchRebalanceSwap(config, { onRebalanceSwap: (args, log) => { console.log('Amount in:', args.amountIn) console.log('Amount out:', args.amountOut) console.log('Swapper:', args.swapper) console.log('User token:', args.userToken) console.log('Validator token:', args.validatorToken) }, }) // Later, stop watching unwatch() ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Return Type ```ts type ReturnType = () => void ``` Returns a function to unsubscribe from the event. ### Parameters #### onRebalanceSwap * **Type:** `function` ```ts declare function onRebalanceSwap(args: Args, log: Log): void type Args = { /** Address of the user token */ userToken: Address /** Address of the validator token */ validatorToken: Address /** Address of the swapper */ swapper: Address /** Amount of validator token swapped in */ amountIn: bigint /** Amount of user token received */ amountOut: bigint } ``` Callback to invoke when a rebalance swap occurs. #### userToken (optional) * **Type:** `Address | bigint` Address or ID of the user token to filter events. #### validatorToken (optional) * **Type:** `Address | bigint` Address or ID of the validator token to filter events. #### args (optional) * **Type:** `object` ```ts type Args = { /** Filter by user token address */ userToken?: Address | Address[] | null /** Filter by validator token address */ validatorToken?: Address | Address[] | null /** Filter by swapper address */ swapper?: Address | Address[] | null } ``` Filter parameters for the event. #### fromBlock (optional) * **Type:** `bigint` Block to start listening from. #### onError (optional) * **Type:** `function` ```ts declare function onError(error: Error): void ``` The callback to call when an error occurred when trying to get for a new block. #### poll (optional) * **Type:** `true` Whether to use polling. #### pollingInterval (optional) * **Type:** `number` Polling frequency (in ms). Defaults to Client's pollingInterval config. import WriteParameters from '../../../../../snippets/write-parameters.mdx' ## `dex.buy` Buys a specific amount of tokens from the Stablecoin DEX orderbook. ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { parseUnits } from 'viem' import { config } from './wagmi.config' const { receipt } = await Actions.dex.buySync(config, { amountOut: parseUnits('100', 6), maxAmountIn: parseUnits('105', 6), tokenIn: '0x20c0000000000000000000000000000000000001', tokenOut: '0x20c0000000000000000000000000000000000002', }) console.log('Transaction hash:', receipt.transactionHash) ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `dex.buy` action and wait for inclusion manually: ```ts twoslash import { Actions } from 'tempo.ts/wagmi' import { waitForTransactionReceipt } from 'wagmi/actions' import { parseUnits } from 'viem' import { config } from './wagmi.config' const hash = await Actions.dex.buy(config, { amountOut: parseUnits('100', 6), maxAmountIn: parseUnits('105', 6), tokenIn: '0x20c0000000000000000000000000000000000001', tokenOut: '0x20c0000000000000000000000000000000000002', }) const receipt = await waitForTransactionReceipt(config, { hash }) ``` ### Return Type ```ts type ReturnType = { /** Transaction receipt */ receipt: TransactionReceipt } ``` ### Parameters #### amountOut * **Type:** `bigint` Amount of tokenOut to buy. #### maxAmountIn * **Type:** `bigint` Maximum amount of tokenIn to spend. #### tokenIn * **Type:** `Address` Address of the token to spend. #### tokenOut * **Type:** `Address` Address of the token to buy. import WriteParameters from '../../../../../snippets/write-parameters.mdx' ## `dex.cancel` Cancels an order from the Stablecoin DEX orderbook. ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { config } from './wagmi.config' const { orderId, receipt } = await Actions.dex.cancelSync(config, { orderId: 123n, }) console.log('Cancelled order ID:', orderId) // @log: Cancelled order ID: 123n ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `dex.cancel` action and wait for inclusion manually: ```ts twoslash import { Actions } from 'tempo.ts/wagmi' import { Actions as viem_Actions } from 'viem/tempo' import { waitForTransactionReceipt } from 'wagmi/actions' import { config } from './wagmi.config' const hash = await Actions.dex.cancel(config, { orderId: 123n, }) const receipt = await waitForTransactionReceipt(config, { hash }) const { args: { orderId } } = viem_Actions.dex.cancel.extractEvent(receipt.logs) ``` ### Return Type ```ts type ReturnType = { /** ID of the cancelled order */ orderId: bigint /** Transaction receipt */ receipt: TransactionReceipt } ``` ### Parameters #### orderId * **Type:** `bigint` ID of the order to cancel. import WriteParameters from '../../../../../snippets/write-parameters.mdx' ## `dex.createPair` Creates a new trading pair on the Stablecoin DEX. ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { config } from './wagmi.config' const { key, base, quote, receipt } = await Actions.dex.createPairSync(config, { base: '0x20c0000000000000000000000000000000000001', }) console.log('Pair key:', key) console.log('Base token:', base) console.log('Quote token:', quote) ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `dex.createPair` action and wait for inclusion manually: ```ts twoslash import { Actions } from 'tempo.ts/wagmi' import { Actions as viem_Actions } from 'viem/tempo' import { waitForTransactionReceipt } from 'wagmi/actions' import { config } from './wagmi.config' const hash = await Actions.dex.createPair(config, { base: '0x20c0000000000000000000000000000000000001', }) const receipt = await waitForTransactionReceipt(config, { hash }) const { args: { base, quote } } = viem_Actions.dex.createPair.extractEvent(receipt.logs) ``` ### Return Type ```ts type ReturnType = { /** Key of the trading pair */ key: Hex /** Address of the base token */ base: Address /** Address of the quote token */ quote: Address /** Transaction receipt */ receipt: TransactionReceipt } ``` ### Parameters #### base * **Type:** `Address` Address of the base token for the pair. The quote token is determined by the base token's quote token. import ReadAccountParameters from '../../../../../snippets/read-account-parameters.mdx' ## `dex.getBalance` Gets a user's token balance on the Stablecoin DEX. ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { config } from './wagmi.config' const balance = await Actions.dex.getBalance(config, { account: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', token: '0x20c0000000000000000000000000000000000001', }) console.log('DEX balance:', balance) // @log: DEX balance: 1000000000n ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Return Type ```ts type ReturnType = bigint ``` ### Parameters #### account * **Type:** `Address` Address of the account. #### token * **Type:** `Address` Address of the token. import ReadParameters from '../../../../../snippets/read-parameters.mdx' ## `dex.getBuyQuote` Gets the quote for buying a specific amount of tokens. ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { parseUnits } from 'viem' import { config } from './wagmi.config' const amountIn = await Actions.dex.getBuyQuote(config, { amountOut: parseUnits('100', 6), tokenIn: '0x20c0000000000000000000000000000000000001', tokenOut: '0x20c0000000000000000000000000000000000002', }) console.log('Amount needed:', amountIn) // @log: Amount needed: 100300000n ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Return Type ```ts type ReturnType = bigint ``` Returns the amount of `tokenIn` needed to buy the specified `amountOut` of `tokenOut`. ### Parameters #### amountOut * **Type:** `bigint` Amount of tokenOut to buy. #### tokenIn * **Type:** `Address` Address of the token to spend. #### tokenOut * **Type:** `Address` Address of the token to buy. import ReadParameters from '../../../../../snippets/read-parameters.mdx' ## `dex.getOrder` Gets an order's details from the Stablecoin DEX orderbook. ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { config } from './wagmi.config' const order = await Actions.dex.getOrder(config, { orderId: 123n, }) console.log('Order details:', order) // @log: Order details: { amount: 100000000n, maker: '0x...', isBid: true, ... } ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Return Type ```ts type ReturnType = { /** Original order amount */ amount: bigint /** Orderbook key (identifies the trading pair) */ bookKey: Hex /** Tick to flip to when fully filled (for flip orders). For bid flips: must be > tick. For ask flips: must be < tick */ flipTick: number /** Whether this is a bid (true) or ask (false) order */ isBid: boolean /** Whether this is a flip order */ isFlip: boolean /** Address of the user who placed this order */ maker: Address /** Next order ID in the doubly linked list (0 if tail) */ next: bigint /** The order ID */ orderId: bigint /** Previous order ID in the doubly linked list (0 if head) */ prev: bigint /** Remaining amount to be filled */ remaining: bigint /** Price tick */ tick: number } ``` Returns the complete order details including the maker's address, order amounts, price tick, linked list pointers, and flip order information. ### Parameters #### orderId * **Type:** `bigint` Order ID to query. import ReadParameters from '../../../../../snippets/read-parameters.mdx' ## `dex.getSellQuote` Gets the quote for selling a specific amount of tokens. ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { parseUnits } from 'viem' import { config } from './wagmi.config' const amountOut = await Actions.dex.getSellQuote(config, { amountIn: parseUnits('100', 6), tokenIn: '0x20c0000000000000000000000000000000000001', tokenOut: '0x20c0000000000000000000000000000000000002', }) console.log('Amount received:', amountOut) // @log: Amount received: 99700000n ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Return Type ```ts type ReturnType = bigint ``` Returns the amount of `tokenOut` received for selling the specified `amountIn` of `tokenIn`. ### Parameters #### amountIn * **Type:** `bigint` Amount of tokenIn to sell. #### tokenIn * **Type:** `Address` Address of the token to sell. #### tokenOut * **Type:** `Address` Address of the token to receive. import ReadParameters from '../../../../../snippets/read-parameters.mdx' ## `dex.getTickLevel` Gets the tick level information at a specific tick on the Stablecoin DEX orderbook. ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { Tick } from 'viem/tempo' import { config } from './wagmi.config' const level = await Actions.dex.getTickLevel(config, { base: '0x20c0000000000000000000000000000000000001', tick: Tick.fromPrice('1.001'), isBid: true, }) console.log('Tick level:', level) // @log: Tick level: { head: 1n, tail: 5n, totalLiquidity: 1000000000n } ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Return Type ```ts type ReturnType = { /** Order ID of the first order at this tick (0 if empty) */ head: bigint /** Order ID of the last order at this tick (0 if empty) */ tail: bigint /** Total liquidity available at this tick level */ totalLiquidity: bigint } ``` Returns the price level information including the order IDs for the head and tail of the FIFO queue at this price level, and the total liquidity available. ### Parameters #### base * **Type:** `Address` Address of the base token. #### isBid * **Type:** `boolean` Whether to query the bid side (`true`) or ask side (`false`). #### tick * **Type:** `number` Price tick to query. Can be created using `Tick.fromPrice()`. import WriteParameters from '../../../../../snippets/write-parameters.mdx' ## `dex.place` Places a limit order on the Stablecoin DEX orderbook. ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { parseUnits } from 'viem' import { Tick } from 'viem/tempo' import { config } from './wagmi.config' const { orderId, receipt } = await Actions.dex.placeSync(config, { amount: parseUnits('100', 6), tick: Tick.fromPrice('0.99'), token: '0x20c0000000000000000000000000000000000001', type: 'buy', }) console.log('Order ID:', orderId) // @log: Order ID: 123n ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `dex.place` action and wait for inclusion manually: ```ts twoslash import { Actions } from 'tempo.ts/wagmi' import { Actions as viem_Actions } from 'viem/tempo' import { waitForTransactionReceipt } from 'wagmi/actions' import { parseUnits } from 'viem' import { Tick } from 'viem/tempo' import { config } from './wagmi.config' const hash = await Actions.dex.place(config, { amount: parseUnits('100', 6), tick: Tick.fromPrice('0.99'), token: '0x20c0000000000000000000000000000000000001', type: 'buy', }) const receipt = await waitForTransactionReceipt(config, { hash }) const { args: { orderId } } = viem_Actions.dex.place.extractEvent(receipt.logs) ``` ### Return Type ```ts type ReturnType = { /** ID of the placed order */ orderId: bigint /** Address of the order maker */ maker: Address /** Address of the base token */ token: Address /** Amount of tokens in the order */ amount: bigint /** Whether this is a buy order */ isBid: boolean /** Price tick for the order */ tick: number /** Transaction receipt */ receipt: TransactionReceipt } ``` ### Parameters #### amount * **Type:** `bigint` Amount of tokens to place in the order. #### tick * **Type:** `number` Price tick for the order. Use `Tick.fromPrice()` to convert from a price string. #### token * **Type:** `Address` Address of the base token. #### type * **Type:** `OrderType` Order type - `'buy'` to buy the token, `'sell'` to sell it. import WriteParameters from '../../../../../snippets/write-parameters.mdx' ## `dex.placeFlip` Places a flip order that automatically flips to the opposite side when filled. ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { parseUnits } from 'viem' import { Tick } from 'viem/tempo' import { config } from './wagmi.config' const { orderId, receipt } = await Actions.dex.placeFlipSync(config, { amount: parseUnits('100', 6), flipTick: Tick.fromPrice('1.01'), tick: Tick.fromPrice('0.99'), token: '0x20c0000000000000000000000000000000000001', type: 'buy', }) console.log('Flip order ID:', orderId) // @log: Flip order ID: 456n ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `dex.placeFlip` action and wait for inclusion manually: ```ts twoslash import { Actions } from 'tempo.ts/wagmi' import { Actions as viem_Actions } from 'viem/tempo' import { waitForTransactionReceipt } from 'wagmi/actions' import { parseUnits } from 'viem' import { Tick } from 'viem/tempo' import { config } from './wagmi.config' const hash = await Actions.dex.placeFlip(config, { amount: parseUnits('100', 6), flipTick: Tick.fromPrice('1.01'), tick: Tick.fromPrice('0.99'), token: '0x20c0000000000000000000000000000000000001', type: 'buy', }) const receipt = await waitForTransactionReceipt(config, { hash }) const { args: { orderId } } = viem_Actions.dex.placeFlip.extractEvent(receipt.logs) ``` ### Return Type ```ts type ReturnType = { /** ID of the placed flip order */ orderId: bigint /** Address of the order maker */ maker: Address /** Address of the base token */ token: Address /** Amount of tokens in the order */ amount: bigint /** Whether this is a buy order */ isBid: boolean /** Price tick for the order */ tick: number /** Target tick to flip to when order is filled */ flipTick: number /** Transaction receipt */ receipt: TransactionReceipt } ``` ### Parameters #### amount * **Type:** `bigint` Amount of tokens to place in the order. #### flipTick * **Type:** `number` Target tick to flip to when order is filled. Must be greater than `tick` for buy orders, less than `tick` for sell orders. #### tick * **Type:** `number` Price tick for the order. Use `Tick.fromPrice()` to convert from a price string. #### token * **Type:** `Address` Address of the base token. #### type * **Type:** `'buy' | 'sell'` Order type - `'buy'` to buy the token, `'sell'` to sell it. import WriteParameters from '../../../../../snippets/write-parameters.mdx' ## `dex.sell` Sells a specific amount of tokens on the Stablecoin DEX orderbook. ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { parseUnits } from 'viem' import { config } from './wagmi.config' const { receipt } = await Actions.dex.sellSync(config, { amountIn: parseUnits('100', 6), minAmountOut: parseUnits('95', 6), tokenIn: '0x20c0000000000000000000000000000000000001', tokenOut: '0x20c0000000000000000000000000000000000002', }) console.log('Transaction hash:', receipt.transactionHash) ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `dex.sell` action and wait for inclusion manually: ```ts twoslash import { Actions } from 'tempo.ts/wagmi' import { waitForTransactionReceipt } from 'wagmi/actions' import { parseUnits } from 'viem' import { config } from './wagmi.config' const hash = await Actions.dex.sell(config, { amountIn: parseUnits('100', 6), minAmountOut: parseUnits('95', 6), tokenIn: '0x20c0000000000000000000000000000000000001', tokenOut: '0x20c0000000000000000000000000000000000002', }) const receipt = await waitForTransactionReceipt(config, { hash }) ``` ### Return Type ```ts type ReturnType = { /** Transaction receipt */ receipt: TransactionReceipt } ``` ### Parameters #### amountIn * **Type:** `bigint` Amount of tokenIn to sell. #### minAmountOut * **Type:** `bigint` Minimum amount of tokenOut to receive. #### tokenIn * **Type:** `Address` Address of the token to sell. #### tokenOut * **Type:** `Address` Address of the token to receive. ## `dex.watchFlipOrderPlaced` Watches for flip order placed events on the Stablecoin DEX. ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { config } from './wagmi.config' const unwatch = Actions.dex.watchFlipOrderPlaced(config, { onFlipOrderPlaced: (args, log) => { console.log('Flip order placed:', args.orderId) console.log('Maker:', args.maker) console.log('Amount:', args.amount) }, }) // Later, stop watching unwatch() ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Return Type ```ts type ReturnType = () => void ``` Returns a function to unsubscribe from the event. ### Parameters #### onFlipOrderPlaced * **Type:** `function` ```ts declare function onFlipOrderPlaced(args: Args, log: Log): void type Args = { /** ID of the placed order */ orderId: bigint /** Address that placed the order */ maker: Address /** Address of the base token */ token: Address /** Amount of tokens in the order */ amount: bigint /** Whether this is a buy order */ isBid: boolean /** Price tick for the order */ tick: number /** Target tick to flip to when filled */ flipTick: number } ``` Callback to invoke when a flip order is placed. #### args (optional) * **Type:** `object` ```ts type Args = { /** Filter by order ID */ orderId?: bigint | bigint[] | null /** Filter by maker address */ maker?: Address | Address[] | null /** Filter by token address */ token?: Address | Address[] | null } ``` Filter parameters for the event subscription. #### maker (optional) * **Type:** `Address` Address of the maker to filter events. #### token (optional) * **Type:** `Address` Address of the token to filter events. #### fromBlock (optional) * **Type:** `bigint` Block to start listening from. #### onError (optional) * **Type:** `(error: Error) => void` The callback to call when an error occurred when trying to get for a new block. #### poll (optional) * **Type:** `true` Whether to use polling. #### pollingInterval (optional) * **Type:** `number` Polling frequency (in ms). Defaults to Client's pollingInterval config. ## `dex.watchOrderCancelled` Watches for order cancelled events on the Stablecoin DEX. ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { config } from './wagmi.config' const unwatch = Actions.dex.watchOrderCancelled(config, { onOrderCancelled: (args, log) => { console.log('Order cancelled:', args.orderId) }, }) // Later, stop watching unwatch() ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Return Type ```ts type ReturnType = () => void ``` Returns a function to unsubscribe from the event. ### Parameters #### onOrderCancelled * **Type:** `function` ```ts declare function onOrderCancelled(args: Args, log: Log): void type Args = { /** ID of the cancelled order */ orderId: bigint } ``` Callback to invoke when an order is cancelled. #### args (optional) * **Type:** `object` ```ts type Args = { /** Order ID to filter events */ orderId?: bigint | bigint[] | null } ``` Filter options for the event. #### orderId (optional) * **Type:** `bigint` Order ID to filter events. #### fromBlock (optional) * **Type:** `bigint` Block to start listening from. #### onError (optional) * **Type:** `function` ```ts declare function onError(error: Error): void ``` The callback to call when an error occurred when trying to get for a new block. #### poll (optional) * **Type:** `true` Enable polling mode. #### pollingInterval (optional) * **Type:** `number` Polling frequency (in ms). Defaults to Client's pollingInterval config. ## `dex.watchOrderFilled` Watches for order filled events on the Stablecoin DEX. ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { config } from './wagmi.config' const unwatch = Actions.dex.watchOrderFilled(config, { onOrderFilled: (args, log) => { console.log('Order filled:', args.orderId) console.log('Maker:', args.maker) console.log('Amount filled:', args.amountFilled) console.log('Partial fill:', args.partialFill) }, }) // Later, stop watching unwatch() ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Return Type ```ts type ReturnType = () => void ``` Returns a function to unsubscribe from the event. ### Parameters #### onOrderFilled * **Type:** `function` ```ts declare function onOrderFilled(args: Args, log: Log): void type Args = { /** ID of the filled order */ orderId: bigint /** Address that placed the order */ maker: Address /** Amount of tokens filled */ amountFilled: bigint /** Whether the order was partially filled */ partialFill: boolean } ``` Callback to invoke when an order is filled. #### maker (optional) * **Type:** `Address` Address of the maker to filter events. #### orderId (optional) * **Type:** `bigint` Order ID to filter events. #### taker (optional) * **Type:** `Address` Address of the taker to filter events. #### fromBlock (optional) * **Type:** `bigint` Block to start listening from. #### onError (optional) * **Type:** `function` ```ts declare function onError(error: Error): void ``` The callback to call when an error occurred when trying to get for a new block. #### poll (optional) * **Type:** `true` Whether to use polling. #### pollingInterval (optional) * **Type:** `number` Polling frequency (in ms). Defaults to Client's pollingInterval config. ## `dex.watchOrderPlaced` Watches for order placed events on the Stablecoin DEX. ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { config } from './wagmi.config' const unwatch = Actions.dex.watchOrderPlaced(config, { onOrderPlaced: (args, log) => { console.log('Order placed:', args.orderId) console.log('Maker:', args.maker) console.log('Token:', args.token) console.log('Amount:', args.amount) }, }) // Later, stop watching unwatch() ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Return Type ```ts type ReturnType = () => void ``` Returns a function to unsubscribe from the event. ### Parameters #### onOrderPlaced * **Type:** `function` ```ts declare function onOrderPlaced(args: Args, log: Log): void type Args = { /** ID of the placed order */ orderId: bigint /** Address that placed the order */ maker: Address /** Address of the base token */ token: Address /** Amount of tokens in the order */ amount: bigint /** Whether this is a buy order */ isBid: boolean /** Price tick for the order */ tick: number } ``` Callback to invoke when an order is placed. #### args (optional) * **Type:** `object` ```ts type Args = { /** Filter by order ID */ orderId?: bigint | bigint[] | null /** Filter by maker address */ maker?: Address | Address[] | null /** Filter by token address */ token?: Address | Address[] | null } ``` Filter parameters for the watch subscription. #### maker (optional) * **Type:** `Address` Address of the maker to filter events. #### token (optional) * **Type:** `Address` Address of the token to filter events. #### fromBlock (optional) * **Type:** `bigint` Block to start listening from. #### onError (optional) * **Type:** `(error: Error) => void` The callback to call when an error occurred when trying to get for a new block. #### poll (optional) * **Type:** `true` Enable polling mode. #### pollingInterval (optional) * **Type:** `number` Polling frequency (in ms). Defaults to Client's pollingInterval config. import WriteParameters from '../../../../../snippets/write-parameters.mdx' ## `dex.withdraw` Withdraws tokens from the Stablecoin DEX to your wallet. ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { parseUnits } from 'viem' import { config } from './wagmi.config' const { receipt } = await Actions.dex.withdrawSync(config, { amount: parseUnits('100', 6), token: '0x20c0000000000000000000000000000000000001', }) console.log('Transaction hash:', receipt.transactionHash) ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `dex.withdraw` action and wait for inclusion manually: ```ts twoslash import { Actions } from 'tempo.ts/wagmi' import { waitForTransactionReceipt } from 'wagmi/actions' import { parseUnits } from 'viem' import { config } from './wagmi.config' const hash = await Actions.dex.withdraw(config, { amount: parseUnits('100', 6), token: '0x20c0000000000000000000000000000000000001', }) const receipt = await waitForTransactionReceipt(config, { hash }) ``` ### Return Type ```ts type ReturnType = { /** Transaction receipt */ receipt: TransactionReceipt } ``` ### Parameters #### amount * **Type:** `bigint` Amount of tokens to withdraw. #### token * **Type:** `Address` Address of the token to withdraw. ## `faucet.fund` Funds an account with an initial amount of tokens on Tempo's testnet. ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { config } from './wagmi.config' const hashes = await Actions.faucet.fund(config, { account: '0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef', }) console.log('Transaction hashes:', hashes) // @log: Transaction hashes: ['0x...', '0x...'] ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Synchronous Usage Use `fundSync` to wait for the transactions to be included on a block before returning: ```ts twoslash import { Actions } from 'tempo.ts/wagmi' import { config } from './wagmi.config' const receipts = await Actions.faucet.fundSync(config, { account: '0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef', }) console.log('Receipts:', receipts) // @log: Receipts: [{ blockNumber: 123n, ... }, { blockNumber: 123n, ... }] ``` ### Return Type #### fund ```ts type ReturnType = readonly Hash[] ``` Returns an array of transaction hashes for the funding transactions. #### fundSync ```ts type ReturnType = readonly TransactionReceipt[] ``` Returns an array of transaction receipts after the transactions are confirmed. ### Parameters #### account * **Type:** `Account | Address` Account to fund with testnet tokens. #### timeout (fundSync only) * **Type:** `number` * **Default:** `10000` Timeout in milliseconds to wait for transaction confirmation. import ReadAccountParameters from '../../../../../snippets/read-account-parameters.mdx' ## `fee.getUserToken` Gets the user's default fee token preference. [Learn more about fees](/protocol/fees) ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { config } from './wagmi.config' const { address, id } = await Actions.fee.getUserToken(config, { account: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', }) console.log('Fee token address:', address) // @log: Fee token address: 0x20c0000000000000000000000000000000000000 console.log('Fee token ID:', id) // @log: Fee token ID: 0n ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Return Type ```ts type ReturnType = { /** Address of the fee token */ address: Address /** ID of the fee token */ id: bigint } | null ``` Returns `null` if the user has not set a default fee token. ### Parameters #### account * **Type:** `Address` Account address. import WriteParameters from '../../../../../snippets/write-parameters.mdx' ## `fee.setUserToken` Sets the user's default fee token preference. [Learn more about fees](/protocol/fees) ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { config } from './wagmi.config' const { receipt } = await Actions.fee.setUserTokenSync(config, { token: '0x20c0000000000000000000000000000000000001', }) console.log('Transaction hash:', receipt.transactionHash) // @log: Transaction hash: 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `fee.setUserToken` action and wait for inclusion manually: ```ts twoslash import { Actions } from 'tempo.ts/wagmi' import { Actions as viem_Actions } from 'viem/tempo' import { waitForTransactionReceipt } from 'wagmi/actions' import { config } from './wagmi.config' const hash = await Actions.fee.setUserToken(config, { token: '0x20c0000000000000000000000000000000000001', }) const receipt = await waitForTransactionReceipt(config, { hash }) const { args } = viem_Actions.fee.setUserToken.extractEvent(receipt.logs) ``` ### Return Type ```ts type ReturnType = { /** Address of the user */ user: Address /** Address of the token set */ token: Address /** Transaction receipt */ receipt: TransactionReceipt } ``` ### Parameters #### token * **Type:** `Address | bigint` Address or ID of the TIP-20 token to use for fees. ## `fee.watchSetUserToken` Watches for user token set events on the Fee Manager. ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { config } from './wagmi.config' const unwatch = Actions.fee.watchSetUserToken(config, { onUserTokenSet: (args, log) => { console.log('User:', args.user) console.log('New fee token:', args.token) }, }) // Later, stop watching unwatch() ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Return Type ```ts type ReturnType = () => void ``` Returns a function to unsubscribe from the event. ### Parameters #### onUserTokenSet * **Type:** `function` ```ts declare function onUserTokenSet(args: Args, log: Log): void type Args = { /** Address of the user */ user: Address /** Address of the new fee token */ token: Address } ``` Callback to invoke when a user token is set. #### args (optional) * **Type:** `object` ```ts type Args = { /** Address of the user to filter by */ user?: Address | Address[] | null /** Address of the token to filter by */ token?: Address | Address[] | null } ``` Optional filters for the event. #### fromBlock (optional) * **Type:** `bigint` Block to start listening from. #### onError (optional) * **Type:** `function` ```ts declare function onError(error: Error): void ``` The callback to call when an error occurred when trying to get for a new block. #### poll (optional) * **Type:** `true` Whether to use polling. #### pollingInterval (optional) * **Type:** `number` Polling frequency (in ms). Defaults to Client's pollingInterval config. ## Overview | Action | Description | | ---------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | | **AMM Actions** | | | [`amm.burn`](/sdk/typescript/wagmi/actions/amm.burn) | Burns liquidity tokens and receives the underlying token pair | | [`amm.getLiquidityBalance`](/sdk/typescript/wagmi/actions/amm.getLiquidityBalance) | Gets the liquidity balance for an address in a specific pool | | [`amm.getPool`](/sdk/typescript/wagmi/actions/amm.getPool) | Gets the reserves for a liquidity pool | | [`amm.mint`](/sdk/typescript/wagmi/actions/amm.mint) | Mints liquidity tokens by providing a token pair | | [`amm.rebalanceSwap`](/sdk/typescript/wagmi/actions/amm.rebalanceSwap) | Performs a rebalance swap between user and validator tokens | | [`amm.watchBurn`](/sdk/typescript/wagmi/actions/amm.watchBurn) | Watches for liquidity burn events | | [`amm.watchFeeSwap`](/sdk/typescript/wagmi/actions/amm.watchFeeSwap) | Watches for fee swap events | | [`amm.watchMint`](/sdk/typescript/wagmi/actions/amm.watchMint) | Watches for liquidity mint events | | [`amm.watchRebalanceSwap`](/sdk/typescript/wagmi/actions/amm.watchRebalanceSwap) | Watches for rebalance swap events | | **Faucet Actions** | | | [`faucet.fund`](/sdk/typescript/wagmi/actions/faucet.fund) | Funds an account with testnet tokens | | **Fee Actions** | | | [`fee.getUserToken`](/sdk/typescript/wagmi/actions/fee.getUserToken) | Gets the user's default fee token preference | | [`fee.setUserToken`](/sdk/typescript/wagmi/actions/fee.setUserToken) | Sets the user's default fee token preference | | [`fee.watchSetUserToken`](/sdk/typescript/wagmi/actions/fee.watchSetUserToken) | Watches for user token set events | | **Nonce Actions** | | | [`nonce.getNonce`](/sdk/typescript/wagmi/actions/nonce.getNonce) | Gets the nonce for an account and nonce key | | [`nonce.getNonceKeyCount`](/sdk/typescript/wagmi/actions/nonce.getNonceKeyCount) | Gets the number of active nonce keys for an account | | [`nonce.watchActiveKeyCountChanged`](/sdk/typescript/wagmi/actions/nonce.watchActiveKeyCountChanged) | Watches for active key count changed events | | [`nonce.watchNonceIncremented`](/sdk/typescript/wagmi/actions/nonce.watchNonceIncremented) | Watches for nonce incremented events | | **Policy Actions** | | | [`policy.create`](#TODO) | Creates a new transfer policy for token access control | | [`policy.getData`](#TODO) | Gets the data for a transfer policy, including its type and admin address | | [`policy.isAuthorized`](#TODO) | Checks if an address is authorized by a transfer policy | | [`policy.modifyBlacklist`](#TODO) | Modifies the blacklist for a blacklist-type transfer policy | | [`policy.modifyWhitelist`](#TODO) | Modifies the whitelist for a whitelist-type transfer policy | | [`policy.setAdmin`](#TODO) | Sets the admin for a transfer policy | | [`policy.watchAdminUpdated`](#TODO) | Watches for policy admin update events | | [`policy.watchBlacklistUpdated`](#TODO) | Watches for blacklist update events | | [`policy.watchCreate`](#TODO) | Watches for policy creation events | | [`policy.watchWhitelistUpdated`](#TODO) | Watches for whitelist update events | | **Reward Actions** | | | [`reward.claim`](/sdk/typescript/wagmi/actions/reward.claim) | Claims accumulated rewards for the caller | | [`reward.getTotalPerSecond`](/sdk/typescript/wagmi/actions/reward.getTotalPerSecond) | Gets the total reward per second rate for all active streams | | [`reward.getUserRewardInfo`](/sdk/typescript/wagmi/actions/reward.getUserRewardInfo) | Gets reward information for a specific account | | [`reward.setRecipient`](/sdk/typescript/wagmi/actions/reward.setRecipient) | Sets or changes the reward recipient for a token holder | | [`reward.start`](/sdk/typescript/wagmi/actions/reward.start) | Starts a new reward stream that distributes tokens to opted-in holders | | [`reward.watchRewardRecipientSet`](/sdk/typescript/wagmi/actions/reward.watchRewardRecipientSet) | Watches for reward recipient set events | | [`reward.watchRewardScheduled`](/sdk/typescript/wagmi/actions/reward.watchRewardScheduled) | Watches for reward scheduled events | | **Stablecoin DEX Actions** | | | [`dex.buy`](/sdk/typescript/wagmi/actions/dex.buy) | Buys a specific amount of tokens from the Stablecoin DEX orderbook | | [`dex.cancel`](/sdk/typescript/wagmi/actions/dex.cancel) | Cancels an order from the orderbook | | [`dex.createPair`](/sdk/typescript/wagmi/actions/dex.createPair) | Creates a new trading pair on the DEX | | [`dex.getBalance`](/sdk/typescript/wagmi/actions/dex.getBalance) | Gets a user's token balance on the Stablecoin DEX | | [`dex.getBuyQuote`](/sdk/typescript/wagmi/actions/dex.getBuyQuote) | Gets the quote for buying a specific amount of tokens | | [`dex.getOrder`](/sdk/typescript/wagmi/actions/dex.getOrder) | Gets an order's details from the orderbook | | [`dex.getTickLevel`](/sdk/typescript/wagmi/actions/dex.getTickLevel) | Gets the price level information at a specific tick | | [`dex.getSellQuote`](/sdk/typescript/wagmi/actions/dex.getSellQuote) | Gets the quote for selling a specific amount of tokens | | [`dex.place`](/sdk/typescript/wagmi/actions/dex.place) | Places a limit order on the orderbook | | [`dex.placeFlip`](/sdk/typescript/wagmi/actions/dex.placeFlip) | Places a flip order that automatically flips when filled | | [`dex.sell`](/sdk/typescript/wagmi/actions/dex.sell) | Sells a specific amount of tokens from the Stablecoin DEX orderbook | | [`dex.watchFlipOrderPlaced`](/sdk/typescript/wagmi/actions/dex.watchFlipOrderPlaced) | Watches for flip order placed events | | [`dex.watchOrderCancelled`](/sdk/typescript/wagmi/actions/dex.watchOrderCancelled) | Watches for order cancelled events | | [`dex.watchOrderFilled`](/sdk/typescript/wagmi/actions/dex.watchOrderFilled) | Watches for order filled events | | [`dex.watchOrderPlaced`](/sdk/typescript/wagmi/actions/dex.watchOrderPlaced) | Watches for order placed events | | [`dex.withdraw`](/sdk/typescript/wagmi/actions/dex.withdraw) | Withdraws tokens from the DEX to the caller's wallet | | **Token Actions** | | | [`token.approve`](/sdk/typescript/wagmi/actions/token.approve) | Approves a spender to transfer TIP-20 tokens on behalf of the caller | | [`token.burn`](/sdk/typescript/wagmi/actions/token.burn) | Burns TIP-20 tokens from the caller's balance | | [`token.burnBlocked`](/sdk/typescript/wagmi/actions/token.burnBlocked) | Burns TIP-20 tokens from a blocked address | | [`token.changeTransferPolicy`](/sdk/typescript/wagmi/actions/token.changeTransferPolicy) | Changes the transfer policy for a TIP-20 token | | [`token.create`](/sdk/typescript/wagmi/actions/token.create) | Creates a new TIP-20 token and assigns the admin role to the calling account | | [`token.getAllowance`](/sdk/typescript/wagmi/actions/token.getAllowance) | Gets the amount of tokens that a spender is approved to transfer on behalf of an owner | | [`token.getBalance`](/sdk/typescript/wagmi/actions/token.getBalance) | Gets the token balance of an address | | [`token.getMetadata`](/sdk/typescript/wagmi/actions/token.getMetadata) | Gets the metadata for a TIP-20 token, including name, symbol, decimals, currency, and total supply | | [`token.grantRoles`](/sdk/typescript/wagmi/actions/token.grantRoles) | Grants one or more roles to an address | | [`token.mint`](/sdk/typescript/wagmi/actions/token.mint) | Mints new TIP-20 tokens to a recipient | | [`token.pause`](/sdk/typescript/wagmi/actions/token.pause) | Pauses a TIP-20 token, preventing all transfers | | [`token.renounceRoles`](/sdk/typescript/wagmi/actions/token.renounceRoles) | Renounces one or more roles from the caller's address | | [`token.revokeRoles`](/sdk/typescript/wagmi/actions/token.revokeRoles) | Revokes one or more roles from an address | | [`token.setRoleAdmin`](/sdk/typescript/wagmi/actions/token.setRoleAdmin) | Sets the admin role for another role | | [`token.setSupplyCap`](/sdk/typescript/wagmi/actions/token.setSupplyCap) | Sets the supply cap for a TIP-20 token | | [`token.transfer`](/sdk/typescript/wagmi/actions/token.transfer) | Transfers TIP-20 tokens from the caller to a recipient | | [`token.unpause`](/sdk/typescript/wagmi/actions/token.unpause) | Unpauses a TIP-20 token, allowing transfers to resume | | [`token.watchAdminRole`](/sdk/typescript/wagmi/actions/token.watchAdminRole) | Watches for role admin update events | | [`token.watchApprove`](/sdk/typescript/wagmi/actions/token.watchApprove) | Watches for token approval events | | [`token.watchBurn`](/sdk/typescript/wagmi/actions/token.watchBurn) | Watches for token burn events | | [`token.watchCreate`](/sdk/typescript/wagmi/actions/token.watchCreate) | Watches for new token creation events | | [`token.watchMint`](/sdk/typescript/wagmi/actions/token.watchMint) | Watches for token mint events | | [`token.watchRole`](/sdk/typescript/wagmi/actions/token.watchRole) | Watches for role membership update events | | [`token.watchTransfer`](/sdk/typescript/wagmi/actions/token.watchTransfer) | Watches for token transfer events | import ReadAccountParameters from '../../../../../snippets/read-account-parameters.mdx' ## `nonce.getNonce` Gets the nonce for an account and nonce key. This is useful for managing multiple nonce lanes for parallel transaction submission. ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { config } from './wagmi.config' const nonce = await Actions.nonce.getNonce(config, { account: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', nonceKey: 1n, }) console.log('Nonce:', nonce) // @log: Nonce: 42n ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Return Type ```ts type ReturnType = bigint ``` The current nonce value for the given account and nonce key. ### Parameters #### account * **Type:** `Address` Account address to get the nonce for. #### nonceKey * **Type:** `bigint` Nonce key (must be > 0, key 0 is reserved for protocol nonces). import ReadAccountParameters from '../../../../../snippets/read-account-parameters.mdx' ## `nonce.getNonceKeyCount` Gets the number of active nonce keys for an account. Active nonce keys are keys that have been used at least once. ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { config } from './wagmi.config' const count = await Actions.nonce.getNonceKeyCount(config, { account: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', }) console.log('Active nonce keys:', count) // @log: Active nonce keys: 3n ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Return Type ```ts type ReturnType = bigint ``` The number of active nonce keys for the account. ### Parameters #### account * **Type:** `Address` Account address to get the active nonce key count for. ## `nonce.watchActiveKeyCountChanged` Watches for active key count changed events. This event is emitted when an account starts using a new nonce key for the first time. ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { config } from './wagmi.config' const unwatch = Actions.nonce.watchActiveKeyCountChanged(config, { onActiveKeyCountChanged: (args, log) => { console.log('Account:', args.account) console.log('New active key count:', args.newCount) }, }) // Later, stop watching unwatch() ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Return Type ```ts type ReturnType = () => void ``` Returns a function to unsubscribe from the event. ### Parameters #### onActiveKeyCountChanged * **Type:** `function` ```ts declare function onActiveKeyCountChanged(args: Args, log: Log): void type Args = { /** Address of the account */ account: Address /** New count of active nonce keys */ newCount: bigint } ``` Callback to invoke when the active key count changes. #### args (optional) * **Type:** `object` ```ts type Args = { /** Address of the account to filter by */ account?: Address | Address[] | null } ``` Optional filters for the event. #### fromBlock (optional) * **Type:** `bigint` Block to start listening from. #### onError (optional) * **Type:** `function` ```ts declare function onError(error: Error): void ``` The callback to call when an error occurred when trying to get for a new block. #### poll (optional) * **Type:** `true` Whether to use polling. #### pollingInterval (optional) * **Type:** `number` Polling frequency (in ms). Defaults to Client's pollingInterval config. ## `nonce.watchNonceIncremented` Watches for nonce incremented events. This event is emitted whenever a transaction is executed using a specific nonce key. ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { config } from './wagmi.config' const unwatch = Actions.nonce.watchNonceIncremented(config, { onNonceIncremented: (args, log) => { console.log('Account:', args.account) console.log('Nonce key:', args.nonceKey) console.log('New nonce:', args.newNonce) }, }) // Later, stop watching unwatch() ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Return Type ```ts type ReturnType = () => void ``` Returns a function to unsubscribe from the event. ### Parameters #### onNonceIncremented * **Type:** `function` ```ts declare function onNonceIncremented(args: Args, log: Log): void type Args = { /** Address of the account */ account: Address /** Nonce key that was incremented */ nonceKey: bigint /** New nonce value after increment */ newNonce: bigint } ``` Callback to invoke when a nonce is incremented. #### args (optional) * **Type:** `object` ```ts type Args = { /** Address of the account to filter by */ account?: Address | Address[] | null /** Nonce key to filter by */ nonceKey?: bigint | bigint[] | null } ``` Optional filters for the event. #### fromBlock (optional) * **Type:** `bigint` Block to start listening from. #### onError (optional) * **Type:** `function` ```ts declare function onError(error: Error): void ``` The callback to call when an error occurred when trying to get for a new block. #### poll (optional) * **Type:** `true` Whether to use polling. #### pollingInterval (optional) * **Type:** `number` Polling frequency (in ms). Defaults to Client's pollingInterval config. import WriteParameters from '../../../../../snippets/write-parameters.mdx' ## `policy.create` Creates a new transfer policy for token access control. [Learn more about transfer policies](/protocol/tip403/overview) ### Usage Use the `policy.create` action on the Wagmi `config` to create a new transfer policy. :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { config } from './wagmi.config' const { policyId, policyType, receipt } = await Actions.policy.createSync(config, { admin: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', addresses: [ '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', '0x70997970C51812dc3A010C7d01b50e0d17dc79C8', ], type: 'whitelist', }) console.log('Policy ID:', policyId) // @log: Policy ID: 1n ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `policy.create` action and wait for inclusion manually: ```ts twoslash import { Actions } from 'tempo.ts/wagmi' import { Actions as viem_Actions } from 'viem/tempo' import { waitForTransactionReceipt } from 'wagmi/actions' import { config } from './wagmi.config' const hash = await Actions.policy.create(config, { admin: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', addresses: [ '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', '0x70997970C51812dc3A010C7d01b50e0d17dc79C8', ], type: 'whitelist', }) const receipt = await waitForTransactionReceipt(config, { hash }) const { args: { policyId } } = viem_Actions.policy.create.extractEvent(receipt.logs) ``` ### Return Type ```ts type ReturnType = { /** ID of the created policy */ policyId: bigint /** Type of the policy (0 = whitelist, 1 = blacklist) */ policyType: number /** Transaction receipt */ receipt: TransactionReceipt /** Address that created the policy */ updater: Address } ``` ### Parameters #### type * **Type:** `'whitelist' | 'blacklist'` Type of policy to create. A `whitelist` policy only allows listed addresses, while a `blacklist` policy allows all except listed addresses. #### addresses (optional) * **Type:** `Address[]` Optional array of addresses to initialize the policy with. #### admin * **Type:** `Address` Address of the policy admin. import ReadParameters from '../../../../../snippets/read-parameters.mdx' ## `policy.getData` Gets the data for a transfer policy, including its type and admin address. ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { config } from './wagmi.config' const { admin, type } = await Actions.policy.getData(config, { policyId: 1n, }) console.log('Policy admin:', admin) // @log: Policy admin: 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb console.log('Policy type:', type) // @log: Policy type: whitelist ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Return Type ```ts type ReturnType = { /** Address of the policy admin */ admin: Address /** Type of policy */ type: PolicyType } ``` ### Parameters #### policyId * **Type:** `bigint` ID of the policy to query. import ReadParameters from '../../../../../snippets/read-parameters.mdx' ## `policy.isAuthorized` Checks if an address is authorized by a transfer policy. ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { config } from './wagmi.config' const isAuthorized = await Actions.policy.isAuthorized(config, { user: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', policyId: 1n, }) console.log('Is authorized:', isAuthorized) // @log: Is authorized: true ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Return Type ```ts type ReturnType = boolean ``` ### Parameters #### policyId * **Type:** `bigint` Policy ID. #### user * **Type:** `Address` User address to check. import WriteParameters from '../../../../../snippets/write-parameters.mdx' ## `policy.modifyBlacklist` Modifies the blacklist for a blacklist-type transfer policy. Requires policy admin role. ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { config } from './wagmi.config' const { receipt } = await Actions.policy.modifyBlacklistSync(config, { address: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', policyId: 1n, restricted: true, }) console.log('Transaction hash:', receipt.transactionHash) // @log: Transaction hash: 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `policy.modifyBlacklist` action and wait for inclusion manually: ```ts twoslash import { Actions } from 'tempo.ts/wagmi' import { Actions as viem_Actions } from 'viem/tempo' import { waitForTransactionReceipt } from 'wagmi/actions' import { config } from './wagmi.config' const hash = await Actions.policy.modifyBlacklist(config, { address: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', policyId: 1n, restricted: true, }) const receipt = await waitForTransactionReceipt(config, { hash }) const { args } = viem_Actions.policy.modifyBlacklist.extractEvent(receipt.logs) ``` ### Return Type ```ts type ReturnType = { /** Address that was added/removed from blacklist */ account: Address /** ID of the policy */ policyId: bigint /** Transaction receipt */ receipt: TransactionReceipt /** Whether the address is restricted */ restricted: boolean /** Address that modified the blacklist */ updater: Address } ``` ### Parameters #### address * **Type:** `Address` Target account address to add or remove from the blacklist. #### policyId * **Type:** `bigint` ID of the blacklist policy to modify. #### restricted * **Type:** `boolean` Whether the address should be restricted (`true`) or unrestricted (`false`). import WriteParameters from '../../../../../snippets/write-parameters.mdx' ## `policy.modifyWhitelist` Modifies the whitelist for a whitelist-type transfer policy. Requires policy admin role. ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { config } from './wagmi.config' const { receipt } = await Actions.policy.modifyWhitelistSync(config, { address: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', allowed: true, policyId: 1n, }) console.log('Transaction hash:', receipt.transactionHash) // @log: Transaction hash: 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `policy.modifyWhitelist` action and wait for inclusion manually: ```ts twoslash import { Actions } from 'tempo.ts/wagmi' import { Actions as viem_Actions } from 'viem/tempo' import { waitForTransactionReceipt } from 'wagmi/actions' import { config } from './wagmi.config' const hash = await Actions.policy.modifyWhitelist(config, { address: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', allowed: true, policyId: 1n, }) const receipt = await waitForTransactionReceipt(config, { hash }) const { args } = viem_Actions.policy.modifyWhitelist.extractEvent(receipt.logs) ``` ### Return Type ```ts type ReturnType = { /** Address that was added/removed from whitelist */ account: Address /** Whether the address is allowed */ allowed: boolean /** ID of the policy */ policyId: bigint /** Transaction receipt */ receipt: TransactionReceipt /** Address that modified the whitelist */ updater: Address } ``` ### Parameters #### address * **Type:** `Address` Target account address to add or remove from the whitelist. #### allowed * **Type:** `boolean` Whether the address should be allowed (`true`) or disallowed (`false`). #### policyId * **Type:** `bigint` ID of the whitelist policy to modify. import WriteParameters from '../../../../../snippets/write-parameters.mdx' ## `policy.setAdmin` Sets the admin for a transfer policy. Requires current policy admin role. ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { config } from './wagmi.config' const { receipt } = await Actions.policy.setAdminSync(config, { admin: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', policyId: 1n, }) console.log('Transaction hash:', receipt.transactionHash) // @log: Transaction hash: 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `policy.setAdmin` action and wait for inclusion manually: ```ts twoslash import { Actions } from 'tempo.ts/wagmi' import { Actions as viem_Actions } from 'viem/tempo' import { waitForTransactionReceipt } from 'wagmi/actions' import { config } from './wagmi.config' const hash = await Actions.policy.setAdmin(config, { admin: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', policyId: 1n, }) const receipt = await waitForTransactionReceipt(config, { hash }) const { args } = viem_Actions.policy.setAdmin.extractEvent(receipt.logs) ``` ### Return Type ```ts type ReturnType = { /** Address of the new admin */ admin: Address /** ID of the policy */ policyId: bigint /** Transaction receipt */ receipt: TransactionReceipt /** Address that updated the admin */ updater: Address } ``` ### Parameters #### admin * **Type:** `Address` Address to set as the new policy admin. #### policyId * **Type:** `bigint` ID of the policy to update. ## `policy.watchAdminUpdated` Watches for policy admin update events on the TIP403 Registry. ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { config } from './wagmi.config' const unwatch = Actions.policy.watchAdminUpdated(config, { onAdminUpdated: (args, log) => { console.log('Admin:', args.admin) console.log('Policy ID:', args.policyId) console.log('Updater:', args.updater) }, }) // Later, stop watching unwatch() ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Return Type ```ts type ReturnType = () => void ``` Returns a function to unsubscribe from the event. ### Parameters #### onAdminUpdated * **Type:** `function` ```ts declare function onAdminUpdated(args: Args, log: Log): void type Args = { /** ID of the policy */ policyId: bigint /** Address that updated the admin */ updater: Address /** Address of the admin */ admin: Address } ``` Callback to invoke when a policy admin is updated. #### args (optional) * **Type:** `object` ```ts type Args = { /** Filter by policy ID */ policyId?: bigint | bigint[] | null /** Filter by updater address */ updater?: Address | Address[] | null /** Filter by admin address */ admin?: Address | Address[] | null } ``` Optional filter arguments for the event. #### fromBlock (optional) * **Type:** `bigint` Block to start listening from. #### onError (optional) * **Type:** `function` ```ts declare function onError(error: Error): void ``` The callback to call when an error occurred when trying to get for a new block. #### poll (optional) * **Type:** `true` Whether to use polling. #### pollingInterval (optional) * **Type:** `number` Polling frequency (in ms). Defaults to Client's pollingInterval config. ## `policy.watchBlacklistUpdated` Watches for blacklist update events on the TIP403 Registry. ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { config } from './wagmi.config' const unwatch = Actions.policy.watchBlacklistUpdated(config, { onBlacklistUpdated: (args, log) => { console.log('Account:', args.account) console.log('Policy ID:', args.policyId) console.log('Restricted:', args.restricted) console.log('Updater:', args.updater) }, }) // Later, stop watching unwatch() ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Return Type ```ts type ReturnType = () => void ``` Returns a function to unsubscribe from the event. ### Parameters #### onBlacklistUpdated * **Type:** `function` ```ts declare function onBlacklistUpdated(args: Args, log: Log): void type Args = { /** Address of the account */ account: Address /** ID of the policy */ policyId: bigint /** Whether the account is restricted */ restricted: boolean /** Address that updated the blacklist */ updater: Address } ``` Callback to invoke when a blacklist is updated. #### args (optional) * **Type:** `object` ```ts type Args = { /** Filter by policy ID */ policyId?: bigint | bigint[] | null /** Filter by updater address */ updater?: Address | Address[] | null /** Filter by account address */ account?: Address | Address[] | null } ``` Optional filter arguments to narrow the events being watched. #### fromBlock (optional) * **Type:** `bigint` Block to start listening from. #### onError (optional) * **Type:** `function` ```ts declare function onError(error: Error): void ``` The callback to call when an error occurred when trying to get for a new block. #### poll (optional) * **Type:** `true` Whether to use polling. #### pollingInterval (optional) * **Type:** `number` Polling frequency (in ms). Defaults to Client's pollingInterval config. ## `policy.watchCreate` Watches for policy creation events on the TIP403 Registry. ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { config } from './wagmi.config' const unwatch = Actions.policy.watchCreate(config, { onPolicyCreated: (args, log) => { console.log('Policy ID:', args.policyId) console.log('Type:', args.type) console.log('Updater:', args.updater) }, }) // Later, stop watching unwatch() ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Return Type ```ts type ReturnType = () => void ``` Returns a function to unsubscribe from the event. ### Parameters #### onPolicyCreated * **Type:** `function` ```ts declare function onPolicyCreated(args: Args, log: Log): void type Args = { /** ID of the created policy */ policyId: bigint /** Type of policy */ type: PolicyType /** Address that created the policy */ updater: Address } ``` Callback to invoke when a policy is created. #### args (optional) * **Type:** `object` ```ts type Args = { /** Filter by policy ID */ policyId?: bigint | bigint[] | null /** Filter by updater address */ updater?: Address | Address[] | null } ``` Optional filter arguments to narrow which events to watch. #### fromBlock (optional) * **Type:** `bigint` Block to start listening from. #### onError (optional) * **Type:** `function` ```ts declare function onError(error: Error): void ``` The callback to call when an error occurred when trying to get for a new block. #### poll (optional) * **Type:** `true` Whether to use polling. #### pollingInterval (optional) * **Type:** `number` Polling frequency (in ms). Defaults to Client's pollingInterval config. ## `policy.watchWhitelistUpdated` Watches for whitelist update events on the TIP403 Registry. ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { config } from './wagmi.config' const unwatch = Actions.policy.watchWhitelistUpdated(config, { onWhitelistUpdated: (args, log) => { console.log('Account:', args.account) console.log('Allowed:', args.allowed) console.log('Policy ID:', args.policyId) console.log('Updater:', args.updater) }, }) // Later, stop watching unwatch() ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Return Type ```ts type ReturnType = () => void ``` Returns a function to unsubscribe from the event. ### Parameters #### onWhitelistUpdated * **Type:** `function` ```ts declare function onWhitelistUpdated(args: Args, log: Log): void type Args = { /** Address of the account */ account: Address /** Whether the account is allowed */ allowed: boolean /** ID of the policy */ policyId: bigint /** Address that updated the whitelist */ updater: Address } ``` Callback to invoke when a whitelist is updated. #### args (optional) * **Type:** `object` ```ts type Args = { /** Filter by policy ID */ policyId?: bigint | bigint[] | null /** Filter by updater address */ updater?: Address | Address[] | null /** Filter by account address */ account?: Address | Address[] | null } ``` Optional filter arguments for the watch. #### fromBlock (optional) * **Type:** `bigint` Block to start listening from. #### onError (optional) * **Type:** `function` ```ts declare function onError(error: Error): void ``` The callback to call when an error occurred when trying to get for a new block. #### poll (optional) * **Type:** `true` Whether to use polling. #### pollingInterval (optional) * **Type:** `number` Polling frequency (in ms). Defaults to Client's pollingInterval config. import WriteParameters from '../../../../../snippets/write-parameters.mdx' ## `reward.claim` Claims accumulated rewards for the caller. ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { config } from './wagmi.config' const { receipt } = await Actions.reward.claimSync(config, { token: '0x20c0000000000000000000000000000000000000', }) console.log('Transaction hash:', receipt.transactionHash) // @log: Transaction hash: 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `reward.claim` action and wait for inclusion manually: ```ts twoslash import { Actions } from 'tempo.ts/wagmi' import { waitForTransactionReceipt } from 'wagmi/actions' import { config } from './wagmi.config' const hash = await Actions.reward.claim(config, { token: '0x20c0000000000000000000000000000000000000', }) const receipt = await waitForTransactionReceipt(config, { hash }) ``` ### Return Type ```ts type ReturnType = { /** Transaction receipt */ receipt: TransactionReceipt } ``` ### Parameters #### token * **Type:** `Address` Address of the TIP-20 token. import ReadParameters from '../../../../../snippets/read-parameters.mdx' ## `reward.getTotalPerSecond` Gets the total reward per second rate for all active streams. ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { config } from './wagmi.config' const rate = await Actions.reward.getTotalPerSecond(config, { token: '0x20c0000000000000000000000000000000000000', }) console.log('Total rate per second:', rate) // @log: Total rate per second: 385802469135802469135n ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Return Type ```ts type ReturnValue = bigint ``` Returns the current aggregate per-second emission rate scaled by `ACC_PRECISION` (1e18). This value represents the sum of all active reward streams' emission rates. :::tip The rate decreases when streams end (via `finalizeStreams`) or are canceled. ::: ### Parameters #### token * **Type:** `Address` Address of the TIP-20 token. import ReadAccountParameters from '../../../../../snippets/read-account-parameters.mdx' ## `reward.getUserRewardInfo` Gets reward information for a specific account. ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { config } from './wagmi.config' const { rewardBalance, rewardPerToken, rewardRecipient } = await Actions.reward.getUserRewardInfo(config, { account: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', token: '0x20c0000000000000000000000000000000000000', }) console.log('Reward recipient:', rewardRecipient) // @log: Reward recipient: 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb console.log('Reward balance:', rewardBalance) // @log: Reward balance: 1000000000000000000n console.log('Reward per token:', rewardPerToken) // @log: Reward per token: 385802469135802469135n ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Return Type ```ts type ReturnType = { /** Accumulated reward balance claimable by the account */ rewardBalance: bigint /** Reward per token checkpoint for the account */ rewardPerToken: bigint /** Current reward recipient address (zero address if opted out) */ rewardRecipient: Address } ``` ### Parameters #### account * **Type:** `Address` Address of the account to get reward info for. #### token * **Type:** `Address` Address of the TIP-20 token. import WriteParameters from '../../../../../snippets/write-parameters.mdx' ## `reward.setRecipient` Sets or changes the reward recipient for a token holder. ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { config } from './wagmi.config' const { holder, receipt, recipient } = await Actions.reward.setRecipientSync(config, { recipient: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', token: '0x20c0000000000000000000000000000000000000', }) console.log('Holder:', holder) // @log: Holder: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 console.log('Recipient:', recipient) // @log: Recipient: 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Opt Out of Rewards Set `recipient` to the zero address to opt out from rewards distribution: ```ts twoslash import { Actions } from 'tempo.ts/wagmi' import { config } from './wagmi.config' await Actions.reward.setRecipientSync(config, { recipient: '0x0000000000000000000000000000000000000000', token: '0x20c0000000000000000000000000000000000000', }) ``` #### Delegate Rewards Set `recipient` to another address to delegate your rewards to them: ```ts twoslash import { Actions } from 'tempo.ts/wagmi' import { config } from './wagmi.config' await Actions.reward.setRecipientSync(config, { recipient: '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', token: '0x20c0000000000000000000000000000000000000', }) ``` #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `reward.setRecipient` action and wait for inclusion manually: ```ts twoslash import { Actions } from 'tempo.ts/wagmi' import { Actions as viem_Actions } from 'viem/tempo' import { waitForTransactionReceipt } from 'wagmi/actions' import { config } from './wagmi.config' const hash = await Actions.reward.setRecipient(config, { recipient: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', token: '0x20c0000000000000000000000000000000000000', }) const receipt = await waitForTransactionReceipt(config, { hash }) const { args: { holder, recipient } } = viem_Actions.reward.setRecipient.extractEvent(receipt.logs) ``` ### Return Type ```ts type ReturnType = { /** Token holder address who set their reward recipient */ holder: Address /** Transaction receipt */ receipt: TransactionReceipt /** Reward recipient address (zero address indicates opt-out) */ recipient: Address } ``` :::tip Rewards are automatically distributed to the current recipient before changing. This happens during any balance-changing operation (transfers, mints, burns). ::: ### Parameters #### recipient * **Type:** `Address` The reward recipient address. Use zero address to opt out of rewards. #### token * **Type:** `Address` The TIP20 token address. import WriteParameters from '../../../../../snippets/write-parameters.mdx' ## `reward.start` Starts a new reward stream that distributes tokens to opted-in holders. ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { parseEther } from 'viem' import { config } from './wagmi.config' const { amount, durationSeconds, funder, id, receipt } = await Actions.reward.startSync(config, { amount: parseEther('1000'), token: '0x20c0000000000000000000000000000000000000', }) console.log('Stream ID:', id) // @log: Stream ID: 1n console.log('Amount:', amount) // @log: Amount: 1000000000000000000000n console.log('Duration:', durationSeconds, 'seconds') // @log: Duration: 2592000 seconds ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `reward.start` action and wait for inclusion manually: ```ts twoslash import { Actions } from 'tempo.ts/wagmi' import { Actions as viem_Actions } from 'viem/tempo' import { parseEther } from 'viem' import { waitForTransactionReceipt } from 'wagmi/actions' import { config } from './wagmi.config' const hash = await Actions.reward.start(config, { amount: parseEther('1000'), token: '0x20c0000000000000000000000000000000000000', }) const receipt = await waitForTransactionReceipt(config, { hash }) const { args: { id, funder, amount, durationSeconds } } = viem_Actions.reward.start.extractEvent(receipt.logs) ``` ### Return Type ```ts type ReturnType = { /** Total amount allocated to the stream */ amount: bigint /** Duration of the stream in seconds (0 for immediate distributions) */ durationSeconds: number /** Address that funded the stream */ funder: Address /** Unique stream ID (0 for immediate distributions; all that is currently supported) */ id: bigint /** Transaction receipt */ receipt: TransactionReceipt } ``` ### Parameters #### amount * **Type:** `bigint` The amount of tokens to distribute. Must be greater than 0. #### token * **Type:** `Address` Address of the TIP-20 token. ## `reward.watchRewardRecipientSet` Watches for reward recipient set events when token holders change their reward recipient. ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { config } from './wagmi.config' const unwatch = Actions.reward.watchRewardRecipientSet(config, { onRewardRecipientSet: (args, log) => { console.log('Holder:', args.holder) console.log('Recipient:', args.recipient) }, token: '0x20c0000000000000000000000000000000000000', }) // Later, stop watching unwatch() ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Return Type ```ts type ReturnType = () => void ``` Returns a function to unsubscribe from the event. ### Parameters #### onRewardRecipientSet * **Type:** ```ts declare function onRewardRecipientSet(args: Args, log: Log): void type Args = { /** Token holder address who set their reward recipient */ holder: Address /** New reward recipient address (zero address indicates opt-out) */ recipient: Address } ``` Callback to invoke when a reward recipient is set. #### token * **Type:** `Address` Address of the TIP-20 token to watch. #### args (optional) * **Type:** `object` ```ts type Args = { /** Filter events by holder address */ holder?: Address /** Filter events by recipient address */ recipient?: Address } ``` Optional filters for the event. ## `reward.watchRewardScheduled` Watches for reward scheduled events when new reward streams are started. ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { config } from './wagmi.config' const unwatch = Actions.reward.watchRewardScheduled(config, { onRewardScheduled: (args, log) => { console.log('Stream ID:', args.id) console.log('Funder:', args.funder) console.log('Amount:', args.amount) console.log('Duration:', args.durationSeconds, 'seconds') }, token: '0x20c0000000000000000000000000000000000000', }) // Later, stop watching unwatch() ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Return Type ```ts type ReturnType = () => void ``` Returns a function to unsubscribe from the event. ### Parameters #### onRewardScheduled * **Type:** `function` ```ts declare function onRewardScheduled(args: Args, log: Log): void type Args = { /** Total amount allocated to the stream */ amount: bigint /** Duration of the stream in seconds (0 for immediate distributions) */ durationSeconds: number /** Address that funded the stream */ funder: Address /** Unique stream ID (0 for immediate distributions) */ id: bigint } ``` Callback to invoke when a reward stream is scheduled. #### token * **Type:** `Address` Address of the TIP-20 token to watch. #### args (optional) * **Type:** `object` ```ts type Args = { /** Filter by funder address */ funder?: Address | Address[] /** Filter by stream ID */ id?: bigint | bigint[] } ``` Optional filters to narrow down events by funder address or stream ID. import WriteParameters from '../../../../../snippets/write-parameters.mdx' ## `token.approve` Approves a spender to transfer TIP-20 tokens on behalf of the caller. ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { parseUnits } from 'viem' import { config } from './wagmi.config' const { receipt } = await Actions.token.approveSync(config, { amount: parseUnits('10.5', 6), spender: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', token: '0x20c0000000000000000000000000000000000000', }) console.log('Transaction hash:', receipt.transactionHash) // @log: Transaction hash: 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `token.approve` action and wait for inclusion manually: ```ts twoslash import { Actions } from 'tempo.ts/wagmi' import { Actions as viem_Actions } from 'viem/tempo' import { parseUnits } from 'viem' import { waitForTransactionReceipt } from 'wagmi/actions' import { config } from './wagmi.config' const hash = await Actions.token.approve(config, { amount: parseUnits('10.5', 6), spender: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', token: '0x20c0000000000000000000000000000000000000', }) const receipt = await waitForTransactionReceipt(config, { hash }) const { args } = viem_Actions.token.approve.extractEvent(receipt.logs) ``` ### Return Type ```ts type ReturnType = { /** Address of the token owner */ owner: Address /** Address of the spender */ spender: Address /** Amount of tokens approved */ amount: bigint /** Transaction receipt */ receipt: TransactionReceipt } ``` ### Parameters #### amount * **Type:** `bigint` Amount of tokens to approve. #### spender * **Type:** `Address` Address of the spender. #### token * **Type:** `Address | bigint` Address or ID of the TIP-20 token. import WriteParameters from '../../../../../snippets/write-parameters.mdx' ## `token.burn` Burns TIP-20 tokens from the caller's balance. ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { parseUnits } from 'viem' import { config } from './wagmi.config' const { receipt } = await Actions.token.burnSync(config, { amount: parseUnits('10.5', 6), token: '0x20c0000000000000000000000000000000000000', }) console.log('Transaction hash:', receipt.transactionHash) // @log: Transaction hash: 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `token.burn` action and wait for inclusion manually: ```ts twoslash import { Actions } from 'tempo.ts/wagmi' import { Actions as viem_Actions } from 'viem/tempo' import { parseUnits } from 'viem' import { waitForTransactionReceipt } from 'wagmi/actions' import { config } from './wagmi.config' const hash = await Actions.token.burn(config, { amount: parseUnits('10.5', 6), token: '0x20c0000000000000000000000000000000000000', }) const receipt = await waitForTransactionReceipt(config, { hash }) const { args } = viem_Actions.token.burn.extractEvent(receipt.logs) ``` ### Return Type ```ts type ReturnType = { /** Amount of tokens burned */ amount: bigint /** Address tokens were burned from */ from: Address /** Transaction receipt */ receipt: TransactionReceipt } ``` ### Parameters #### amount * **Type:** `bigint` Amount of tokens to burn. #### memo (optional) * **Type:** `Hex` Memo to include in the transfer. #### token * **Type:** `Address | bigint` Address or ID of the TIP-20 token. import WriteParameters from '../../../../../snippets/write-parameters.mdx' ## `token.burnBlocked` Burns TIP-20 tokens from a blocked address. Requires the `BURN_BLOCKED` role. [Learn more about token roles](/protocol/tip403/spec) ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { parseUnits } from 'viem' import { config } from './wagmi.config' const { receipt } = await Actions.token.burnBlockedSync(config, { amount: parseUnits('10.5', 6), from: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', token: '0x20c0000000000000000000000000000000000000', }) console.log('Transaction hash:', receipt.transactionHash) // @log: Transaction hash: 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `token.burnBlocked` action and wait for inclusion manually: ```ts twoslash import { Actions } from 'tempo.ts/wagmi' import { Actions as viem_Actions } from 'viem/tempo' import { parseUnits } from 'viem' import { waitForTransactionReceipt } from 'wagmi/actions' import { config } from './wagmi.config' const hash = await Actions.token.burnBlocked(config, { amount: parseUnits('10.5', 6), from: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', token: '0x20c0000000000000000000000000000000000000', }) const receipt = await waitForTransactionReceipt(config, { hash }) const { args } = viem_Actions.token.burnBlocked.extractEvent(receipt.logs) ``` ### Return Type ```ts type ReturnType = { /** Amount of tokens burned */ amount: bigint /** Address tokens were burned from */ from: Address /** Transaction receipt */ receipt: TransactionReceipt } ``` ### Parameters #### amount * **Type:** `bigint` Amount of tokens to burn. #### from * **Type:** `Address` Address to burn tokens from. #### token * **Type:** `Address | bigint` Address or ID of the TIP-20 token. import WriteParameters from '../../../../../snippets/write-parameters.mdx' ## `token.changeTransferPolicy` Changes the transfer policy for a TIP-20 token. Requires the default admin role. [Learn more about transfer policies](/protocol/tip403/spec) ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { config } from './wagmi.config' const { receipt } = await Actions.token.changeTransferPolicySync(config, { policyId: 1n, token: '0x20c0000000000000000000000000000000000000', }) console.log('Transaction hash:', receipt.transactionHash) // @log: Transaction hash: 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `token.changeTransferPolicy` action and wait for inclusion manually: ```ts twoslash import { Actions } from 'tempo.ts/wagmi' import { Actions as viem_Actions } from 'viem/tempo' import { waitForTransactionReceipt } from 'wagmi/actions' import { config } from './wagmi.config' const hash = await Actions.token.changeTransferPolicy(config, { policyId: 1n, token: '0x20c0000000000000000000000000000000000000', }) const receipt = await waitForTransactionReceipt(config, { hash }) const { args } = viem_Actions.token.changeTransferPolicy.extractEvent(receipt.logs) ``` ### Return Type ```ts type ReturnType = { /** ID of the new transfer policy */ newPolicyId: bigint /** Transaction receipt */ receipt: TransactionReceipt /** Address that updated the policy */ updater: Address } ``` ### Parameters #### policyId * **Type:** `bigint` New transfer policy ID. #### token * **Type:** `Address | bigint` Address or ID of the TIP-20 token. import WriteParameters from '../../../../../snippets/write-parameters.mdx' ## `token.create` Creates a new TIP-20 token, and assigns the admin role to the calling account. [Learn more](/protocol/tip20/overview) ### Usage Use the `token.create` action on the Wagmi `config` to create and deploy a new TIP-20 token. :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { config } from './wagmi.config' const { admin, receipt, token, tokenId } = await Actions.token.createSync(config, { admin: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', currency: 'USD', name: 'My Company USD', symbol: 'CUSD', }) console.log('Address:', token) // @log: Address: 0x20c0000000000000000000000000000000000004 console.log('Admin:', admin) // @log: Admin: 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb console.log('ID:', tokenId) // @log: ID: 4n ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `token.create` action and wait for inclusion manually: ```ts twoslash import { Actions } from 'tempo.ts/wagmi' import { Actions as viem_Actions } from 'viem/tempo' import { waitForTransactionReceipt } from 'wagmi/actions' import { config } from './wagmi.config' const hash = await Actions.token.create(config, { admin: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', currency: 'USD', name: 'My Company USD', symbol: 'CUSD', }) const receipt = await waitForTransactionReceipt(config, { hash }) const { args: { token, tokenId } } = viem_Actions.token.create.extractEvent(receipt.logs) ``` ### Return Type ```ts type ReturnType = { /** Address of the admin that was granted the admin role */ admin: Address /** Currency code of the token */ currency: string /** Name of the token */ name: string /** Address of the quote token */ quoteToken: Address /** Transaction receipt */ receipt: TransactionReceipt /** Symbol of the token */ symbol: string /** Address of the deployed TIP-20 token */ token: Address /** ID of the deployed TIP-20 token */ tokenId: bigint } ``` ### Parameters #### admin * **Type:** `Address` Admin address for the token. #### currency * **Type:** `string` Currency code for the token. #### name * **Type:** `string` Name of the token. #### quoteToken (optional) * **Type:** `Address | bigint` Quote token address or ID. #### symbol * **Type:** `string` Symbol of the token. import ReadAccountParameters from '../../../../../snippets/read-account-parameters.mdx' ## `token.getAllowance` Gets the amount of tokens that a spender is approved to transfer on behalf of an owner. ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { config } from './wagmi.config' const allowance = await Actions.token.getAllowance(config, { account: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', spender: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8', token: '0x20c0000000000000000000000000000000000000', }) console.log('Allowance:', allowance) // @log: Allowance: 10500000n ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Return Type ```ts type ReturnType = bigint // Allowance amount ``` ### Parameters #### account * **Type:** `Address` Account address. #### spender * **Type:** `Address` Address of the spender. #### token * **Type:** `Address | bigint` Address or ID of the TIP20 token. import ReadAccountParameters from '../../../../../snippets/read-account-parameters.mdx' ## `token.getBalance` Gets the token balance of an address. ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { config } from './wagmi.config' const balance = await Actions.token.getBalance(config, { account: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', token: '0x20c0000000000000000000000000000000000000', }) console.log('Balance:', balance) // @log: Balance: 10500000n ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Return Type ```ts type ReturnType = bigint // Balance amount ``` ### Parameters #### account * **Type:** `Address` Account address. #### token * **Type:** `Address | bigint` Address or ID of the TIP20 token. import ReadParameters from '../../../../../snippets/read-parameters.mdx' ## `token.getMetadata` Gets the metadata for a TIP-20 token, including name, symbol, decimals, currency, and total supply. ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { config } from './wagmi.config' const metadata = await Actions.token.getMetadata(config, { token: '0x20c0000000000000000000000000000000000000', }) console.log('Currency:', metadata.currency) // @log: Currency: USD console.log('Decimals:', metadata.decimals) // @log: Decimals: 18 console.log('Name:', metadata.name) // @log: Name: United States Dollar console.log('Symbol:', metadata.symbol) // @log: Symbol: USD console.log('Total Supply:', metadata.totalSupply) // @log: Total Supply: 1000000000000000000000n ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Return Type ```ts type ReturnType = { currency: string decimals: number name: string paused?: boolean quoteToken?: Address supplyCap?: bigint symbol: string totalSupply: bigint transferPolicyId?: bigint } ``` ### Parameters #### token * **Type:** `Address | bigint` Address or ID of the TIP-20 token. import WriteParameters from '../../../../../snippets/write-parameters.mdx' ## `token.grantRoles` Grants a role to an address. Requires the admin role for the role being granted. [Learn more about token roles](/protocol/tip403/spec) ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { config } from './wagmi.config' const { receipt, value } = await Actions.token.grantRolesSync(config, { role: 'issuer', token: '0x20c0000000000000000000000000000000000000', to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', }) console.log('Role granted:', value[0].hasRole) // @log: Role granted: true ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `token.grantRoles` action and wait for inclusion manually: ```ts twoslash import { Actions } from 'tempo.ts/wagmi' import { Actions as viem_Actions } from 'viem/tempo' import { waitForTransactionReceipt } from 'wagmi/actions' import { config } from './wagmi.config' const hash = await Actions.token.grantRoles(config, { role: 'issuer', token: '0x20c0000000000000000000000000000000000000', to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', }) const receipt = await waitForTransactionReceipt(config, { hash }) const events = viem_Actions.token.grantRoles.extractEvents(receipt.logs) ``` ### Return Type ```ts type ReturnType = { /** Transaction receipt */ receipt: TransactionReceipt /** Array of role membership update events */ value: readonly { /** Role identifier */ role: Hex /** Address that received the role */ account: Address /** Address that granted the role */ sender: Address /** Whether the role was granted (true) or revoked (false) */ hasRole: boolean }[] } ``` ### Parameters #### role * **Type:** `"defaultAdmin" | "pause" | "unpause" | "issuer" | "burnBlocked"` Role to grant. #### token * **Type:** `Address | bigint` Address or ID of the TIP20 token. #### to * **Type:** `Address` Address to grant the role to. import ReadAccountParameters from '../../../../../snippets/read-account-parameters.mdx' ## `token.hasRole` Checks if an address has a specific role for a TIP-20 token. [Learn more about token roles](/protocol/tip403/spec) ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { config } from './wagmi.config' const hasRole = await Actions.token.hasRole(config, { account: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', role: 'issuer', token: '0x20c0000000000000000000000000000000000011', }) console.log('Has issuer role:', hasRole) // @log: Has issuer role: true ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Return Type ```ts type ReturnType = boolean // Whether the account has the role ``` ### Parameters #### account * **Type:** `Address` Address to check for the role. #### role * **Type:** `"defaultAdmin" | "pause" | "unpause" | "issuer" | "burnBlocked"` Role to check. #### token * **Type:** `Address | bigint` Address or ID of the TIP-20 token. import WriteParameters from '../../../../../snippets/write-parameters.mdx' ## `token.mint` Mints new TIP-20 tokens to a recipient. Requires the `ISSUER` role. [Learn more about roles](/protocol/tip20/spec#role-based-access-control) ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { parseUnits } from 'viem' import { config } from './wagmi.config' const { receipt } = await Actions.token.mintSync(config, { amount: parseUnits('10.5', 6), to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', token: '0x20c0000000000000000000000000000000000000', }) console.log('Transaction hash:', receipt.transactionHash) // @log: Transaction hash: 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `token.mint` action and wait for inclusion manually: ```ts twoslash import { Actions } from 'tempo.ts/wagmi' import { Actions as viem_Actions } from 'viem/tempo' import { parseUnits } from 'viem' import { waitForTransactionReceipt } from 'wagmi/actions' import { config } from './wagmi.config' const hash = await Actions.token.mint(config, { amount: parseUnits('10.5', 6), to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', token: '0x20c0000000000000000000000000000000000000', }) const receipt = await waitForTransactionReceipt(config, { hash }) const { args } = viem_Actions.token.mint.extractEvent(receipt.logs) ``` ### Return Type ```ts type ReturnType = { /** Amount of tokens minted */ amount: bigint /** Transaction receipt */ receipt: TransactionReceipt /** Address tokens were minted to */ to: Address } ``` ### Parameters #### amount * **Type:** `bigint` Amount of tokens to mint. #### memo (optional) * **Type:** `Hex` Memo to include in the mint. #### to * **Type:** `Address` Address to mint tokens to. #### token * **Type:** `Address | bigint` Address or ID of the TIP-20 token. import WriteParameters from '../../../../../snippets/write-parameters.mdx' ## `token.pause` Pauses a TIP-20 token, preventing all transfers. Requires the `PAUSE` role. [Learn more about token roles](/protocol/tip403/spec) ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { config } from './wagmi.config' const { isPaused, receipt } = await Actions.token.pauseSync(config, { token: '0x20c0000000000000000000000000000000000000', }) console.log('Is paused:', isPaused) // @log: Is paused: true ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `token.pause` action and wait for inclusion manually: ```ts twoslash import { Actions } from 'tempo.ts/wagmi' import { Actions as viem_Actions } from 'viem/tempo' import { waitForTransactionReceipt } from 'wagmi/actions' import { config } from './wagmi.config' const hash = await Actions.token.pause(config, { token: '0x20c0000000000000000000000000000000000000', }) const receipt = await waitForTransactionReceipt(config, { hash }) const { args } = viem_Actions.token.pause.extractEvent(receipt.logs) ``` ### Return Type ```ts type ReturnType = { /** Whether the token is paused */ isPaused: boolean /** Transaction receipt */ receipt: TransactionReceipt /** Address that paused the token */ updater: Address } ``` ### Parameters #### token * **Type:** `Address | bigint` Address or ID of the TIP-20 token. import WriteParameters from '../../../../../snippets/write-parameters.mdx' ## `token.renounceRoles` Renounces one or more roles from the caller's address. [Learn more about token roles](/protocol/tip403/spec) ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { config } from './wagmi.config' const { receipt, value } = await Actions.token.renounceRolesSync(config, { role: 'issuer', token: '0x20c0000000000000000000000000000000000000', }) console.log('Roles renounced:', value.length) // @log: Roles renounced: 1 ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `token.renounceRoles` action and wait for inclusion manually: ```ts twoslash import { Actions } from 'tempo.ts/wagmi' import { Actions as viem_Actions } from 'viem/tempo' import { waitForTransactionReceipt } from 'wagmi/actions' import { config } from './wagmi.config' const hash = await Actions.token.renounceRoles(config, { role: 'issuer', token: '0x20c0000000000000000000000000000000000000', }) const receipt = await waitForTransactionReceipt(config, { hash }) const events = viem_Actions.token.renounceRoles.extractEvents(receipt.logs) ``` ### Return Type ```ts type ReturnType = { /** Transaction receipt */ receipt: TransactionReceipt /** Array of role membership update events */ value: readonly { /** Address that renounced the role */ account: Address /** Whether the role was granted (true) or revoked (false) */ hasRole: boolean /** Role identifier */ role: Hex /** Address that initiated the change */ sender: Address }[] } ``` ### Parameters #### role * **Type:** `"defaultAdmin" | "pause" | "unpause" | "issuer" | "burnBlocked"` Role to renounce. #### token * **Type:** `Address | bigint` Address or ID of the TIP-20 token. import WriteParameters from '../../../../../snippets/write-parameters.mdx' ## `token.revokeRoles` Revokes one or more roles from an address. Requires the admin role for each role being revoked. [Learn more about token roles](/protocol/tip403/spec) ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { config } from './wagmi.config' const { receipt, value } = await Actions.token.revokeRolesSync(config, { from: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', role: 'issuer', token: '0x20c0000000000000000000000000000000000000', }) console.log('Roles revoked:', value.length) // @log: Roles revoked: 1 ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `token.revokeRoles` action and wait for inclusion manually: ```ts twoslash import { Actions } from 'tempo.ts/wagmi' import { Actions as viem_Actions } from 'viem/tempo' import { waitForTransactionReceipt } from 'wagmi/actions' import { config } from './wagmi.config' const hash = await Actions.token.revokeRoles(config, { from: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', role: 'issuer', token: '0x20c0000000000000000000000000000000000000', }) const receipt = await waitForTransactionReceipt(config, { hash }) const events = viem_Actions.token.revokeRoles.extractEvents(receipt.logs) ``` ### Return Type ```ts type ReturnType = { /** Transaction receipt */ receipt: TransactionReceipt /** Array of role membership update events */ value: readonly { /** Role identifier */ role: Hex /** Address that had role revoked */ account: Address /** Address that revoked the role */ sender: Address /** Whether the role was granted (true) or revoked (false) */ hasRole: boolean }[] } ``` ### Parameters #### from * **Type:** `Address` Address to revoke the role from. #### role * **Type:** `"defaultAdmin" | "pause" | "unpause" | "issuer" | "burnBlocked"` Role to revoke. #### token * **Type:** `Address | bigint` Address or ID of the TIP-20 token. import WriteParameters from '../../../../../snippets/write-parameters.mdx' ## `token.setRoleAdmin` Sets the admin role for another role. Requires the current admin role for the target role. [Learn more about token roles](/protocol/tip403/spec) ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { config } from './wagmi.config' const { receipt } = await Actions.token.setRoleAdminSync(config, { adminRole: 'defaultAdmin', role: 'issuer', token: '0x20c0000000000000000000000000000000000000', }) console.log('Transaction hash:', receipt.transactionHash) // @log: Transaction hash: 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `token.setRoleAdmin` action and wait for inclusion manually: ```ts twoslash import { Actions } from 'tempo.ts/wagmi' import { Actions as viem_Actions } from 'viem/tempo' import { waitForTransactionReceipt } from 'wagmi/actions' import { config } from './wagmi.config' const hash = await Actions.token.setRoleAdmin(config, { adminRole: 'defaultAdmin', role: 'issuer', token: '0x20c0000000000000000000000000000000000000', }) const receipt = await waitForTransactionReceipt(config, { hash }) const { args } = viem_Actions.token.setRoleAdmin.extractEvent(receipt.logs) ``` ### Return Type ```ts type ReturnType = { /** New admin role identifier */ newAdminRole: Hex /** Transaction receipt */ receipt: TransactionReceipt /** Role identifier that had its admin updated */ role: Hex /** Address that updated the role admin */ sender: Address } ``` ### Parameters #### adminRole * **Type:** `"defaultAdmin" | "pause" | "unpause" | "issuer" | "burnBlocked"` New admin role. #### role * **Type:** `"defaultAdmin" | "pause" | "unpause" | "issuer" | "burnBlocked"` Role to set admin for. #### token * **Type:** `Address | bigint` Address or ID of the TIP20 token. import WriteParameters from '../../../../../snippets/write-parameters.mdx' ## `token.setSupplyCap` Sets the supply cap for a TIP-20 token. Requires the default admin role. ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { parseUnits } from 'viem' import { config } from './wagmi.config' const { newSupplyCap, receipt } = await Actions.token.setSupplyCapSync(config, { supplyCap: parseUnits('1000000', 6), token: '0x20c0000000000000000000000000000000000000', }) console.log('New supply cap:', newSupplyCap) // @log: New supply cap: 1000000000000n ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `token.setSupplyCap` action and wait for inclusion manually: ```ts twoslash import { Actions } from 'tempo.ts/wagmi' import { Actions as viem_Actions } from 'viem/tempo' import { parseUnits } from 'viem' import { waitForTransactionReceipt } from 'wagmi/actions' import { config } from './wagmi.config' const hash = await Actions.token.setSupplyCap(config, { supplyCap: parseUnits('1000000', 6), token: '0x20c0000000000000000000000000000000000000', }) const receipt = await waitForTransactionReceipt(config, { hash }) const { args } = viem_Actions.token.setSupplyCap.extractEvent(receipt.logs) ``` ### Return Type ```ts type ReturnType = { /** New supply cap value */ newSupplyCap: bigint /** Transaction receipt */ receipt: TransactionReceipt /** Address that updated the supply cap */ updater: Address } ``` ### Parameters #### supplyCap * **Type:** `bigint` Maximum total supply allowed for the token. #### token * **Type:** `Address | bigint` Address or ID of the TIP-20 token. import WriteParameters from '../../../../../snippets/write-parameters.mdx' ## `token.transfer` Transfers TIP-20 tokens from the caller to a recipient. ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { parseUnits } from 'viem' import { config } from './wagmi.config' const { receipt } = await Actions.token.transferSync(config, { amount: parseUnits('10.5', 6), to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', token: '0x20c0000000000000000000000000000000000000', }) console.log('Transaction hash:', receipt.transactionHash) // @log: Transaction hash: 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `token.transfer` action and wait for inclusion manually: ```ts twoslash import { Actions } from 'tempo.ts/wagmi' import { Actions as viem_Actions } from 'viem/tempo' import { parseUnits } from 'viem' import { waitForTransactionReceipt } from 'wagmi/actions' import { config } from './wagmi.config' const hash = await Actions.token.transfer(config, { amount: parseUnits('10.5', 6), to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', token: '0x20c0000000000000000000000000000000000000', }) const receipt = await waitForTransactionReceipt(config, { hash }) const { args } = viem_Actions.token.transfer.extractEvent(receipt.logs) ``` ### Return Type ```ts type ReturnType = { /** Amount of tokens transferred */ amount: bigint /** Address tokens were transferred from */ from: Address /** Transaction receipt */ receipt: TransactionReceipt /** Address tokens were transferred to */ to: Address } ``` ### Parameters #### amount * **Type:** `bigint` Amount of tokens to transfer. #### to * **Type:** `Address` Address to transfer tokens to. #### token * **Type:** `Address | bigint` Address or ID of the TIP-20 token. #### memo (optional) * **Type:** `Hex` Optional memo to attach to the transfer event. #### from (optional) * **Type:** `Address` Address to transfer tokens from. When specified, transfers tokens from the given address (requires prior approval). Defaults to the caller's address. import WriteParameters from '../../../../../snippets/write-parameters.mdx' ## `token.unpause` Unpauses a TIP-20 token, allowing transfers to resume. Requires the `UNPAUSE` role. [Learn more about token roles](/protocol/tip403/spec) ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { config } from './wagmi.config' const { isPaused, receipt } = await Actions.token.unpauseSync(config, { token: '0x20c0000000000000000000000000000000000000', }) console.log('Is paused:', isPaused) // @log: Is paused: false ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Asynchronous Usage The example above uses a `*Sync` variant of the action, that will wait for the transaction to be included before returning. If you are optimizing for performance, you should use the non-sync `token.unpause` action and wait for inclusion manually: ```ts twoslash import { Actions } from 'tempo.ts/wagmi' import { Actions as viem_Actions } from 'viem/tempo' import { waitForTransactionReceipt } from 'wagmi/actions' import { config } from './wagmi.config' const hash = await Actions.token.unpause(config, { token: '0x20c0000000000000000000000000000000000000', }) const receipt = await waitForTransactionReceipt(config, { hash }) const { args } = viem_Actions.token.unpause.extractEvent(receipt.logs) ``` ### Return Type ```ts type ReturnType = { /** Whether the token is paused */ isPaused: boolean /** Transaction receipt */ receipt: TransactionReceipt /** Address that unpaused the token */ updater: Address } ``` ### Parameters #### token * **Type:** `Address | bigint` Address or ID of the TIP-20 token. ## `token.watchAdminRole` Watches for role admin update events on TIP20 tokens. ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { config } from './wagmi.config' const unwatch = Actions.token.watchAdminRole(config, { token: 1n, // Token ID or address onRoleAdminUpdated: (args, log) => { console.log('Role:', args.role) console.log('New admin role:', args.newAdminRole) console.log('Sender:', args.sender) }, }) // Later, stop watching unwatch() ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Return Type ```ts type ReturnType = () => void ``` Returns a function to unsubscribe from the event. ### Parameters #### onRoleAdminUpdated * **Type:** `function` ```ts declare function onRoleAdminUpdated(args: Args, log: Log): void type Args = { /** The role whose admin role is being changed */ role: Hex /** The new admin role */ newAdminRole: Hex /** The address that initiated the change */ sender: Address } ``` Callback to invoke when a role admin is updated. #### token * **Type:** `Address | bigint` Address or ID of the TIP20 token to watch. #### args (optional) * **Type:** `object` ```ts type Args = { /** Filter by role */ role?: Hex | Hex[] | null /** Filter by new admin role */ newAdminRole?: Hex | Hex[] | null /** Filter by sender */ sender?: Address | Address[] | null } ``` Optional filter arguments to narrow down the events to watch. #### fromBlock (optional) * **Type:** `bigint` Block to start listening from. #### onError (optional) * **Type:** `(error: Error) => void` The callback to call when an error occurred when trying to get for a new block. #### poll (optional) * **Type:** `true` Whether to use polling. #### pollingInterval (optional) * **Type:** `number` Polling frequency (in ms). Defaults to Client's pollingInterval config. ## `token.watchApprove` Watches for token approval events on TIP20 tokens. ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { config } from './wagmi.config' const unwatch = Actions.token.watchApprove(config, { token: 0n, onApproval: (args, log) => { console.log('Amount:', args.amount) console.log('Owner:', args.owner) console.log('Spender:', args.spender) }, }) // Later, stop watching unwatch() ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Return Type ```ts type ReturnType = () => void ``` Returns a function to unsubscribe from the event. ### Parameters #### onApproval * **Type:** `function` ```ts declare function onApproval(args: Args, log: Log): void type Args = { /** Amount approved */ amount: bigint /** Address of the token owner */ owner: Address /** Address of the spender */ spender: Address } ``` Callback to invoke when tokens are approved. #### token * **Type:** `Address | bigint` Address or ID of the TIP20 token to watch. #### args (optional) * **Type:** `object` ```ts type Args = { /** Filter by token owner address */ owner?: Address | Address[] | null /** Filter by spender address */ spender?: Address | Address[] | null } ``` Filter events by owner and/or spender addresses. #### fromBlock (optional) * **Type:** `bigint` Block to start listening from. #### onError (optional) * **Type:** `function` ```ts declare function onError(error: Error): void ``` The callback to call when an error occurred when trying to get for a new block. #### poll (optional) * **Type:** `true` Whether to use polling. #### pollingInterval (optional) * **Type:** `number` Polling frequency (in ms). Defaults to Client's pollingInterval config. ## `token.watchBurn` Watches for token burn events on TIP20 tokens. ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { config } from './wagmi.config' const unwatch = Actions.token.watchBurn(config, { token: 1n, // or token address onBurn: (args, log) => { console.log('Amount:', args.amount) console.log('From:', args.from) }, }) // Later, stop watching unwatch() ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Return Type ```ts type ReturnType = () => void ``` Returns a function to unsubscribe from the event. ### Parameters #### onBurn * **Type:** `function` ```ts declare function onBurn(args: Args, log: Log): void type Args = { /** Address whose tokens were burned */ from: Address /** Amount burned */ amount: bigint } ``` Callback to invoke when tokens are burned. #### token * **Type:** `Address | bigint` Address or ID of the TIP20 token. #### args (optional) * **Type:** `object` ```ts type Args = { /** Filter by burner address */ from?: Address | Address[] | null } ``` Optional filter arguments. #### fromBlock (optional) * **Type:** `bigint` Block to start listening from. #### onError (optional) * **Type:** `function` ```ts declare function onError(error: Error): void ``` The callback to call when an error occurred when trying to get for a new block. #### poll (optional) * **Type:** `true` Whether to use polling. #### pollingInterval (optional) * **Type:** `number` Polling frequency (in ms). Defaults to Client's pollingInterval config. ## `token.watchCreate` Watches for new TIP20 token creation events on the TIP20 Factory. ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { config } from './wagmi.config' const unwatch = Actions.token.watchCreate(config, { onTokenCreated: (args, log) => { console.log('Token:', args.token) console.log('Token ID:', args.tokenId) console.log('Name:', args.name) console.log('Symbol:', args.symbol) console.log('Currency:', args.currency) console.log('Quote Token:', args.quoteToken) console.log('Admin:', args.admin) }, }) // Later, stop watching unwatch() ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Return Type ```ts type ReturnType = () => void ``` Returns a function to unsubscribe from the event. ### Parameters #### onTokenCreated * **Type:** `function` ```ts declare function onTokenCreated(args: Args, log: Log): void type Args = { /** Address of the created token */ token: Address /** ID of the created token */ tokenId: bigint /** Name of the token */ name: string /** Symbol of the token */ symbol: string /** Currency of the token */ currency: string /** Quote token address */ quoteToken: Address /** Admin address */ admin: Address } ``` Callback to invoke when a new TIP20 token is created. #### args (optional) * **Type:** `object` ```ts type Args = { /** Filter by token address(es) */ token?: Address | Address[] | null /** Filter by token ID(s) */ tokenId?: bigint | bigint[] | null } ``` Optional filter arguments to watch for specific tokens. #### fromBlock (optional) * **Type:** `bigint` Block to start listening from. #### onError (optional) * **Type:** `function` ```ts declare function onError(error: Error): void ``` The callback to call when an error occurred when trying to get for a new block. #### poll (optional) * **Type:** `true` Whether to use polling. #### pollingInterval (optional) * **Type:** `number` Polling frequency (in ms). Defaults to Client's pollingInterval config. ## `token.watchMint` Watches for token mint events on TIP20 tokens. ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { config } from './wagmi.config' const unwatch = Actions.token.watchMint(config, { token: '0x20c0000000000000000000000000000000000001', onMint: (args, log) => { console.log('To:', args.to) console.log('Amount:', args.amount) }, }) // Later, stop watching unwatch() ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Return Type ```ts type ReturnType = () => void ``` Returns a function to unsubscribe from the event. ### Parameters #### onMint * **Type:** `function` ```ts declare function onMint(args: Args, log: Log): void type Args = { /** Address that received the tokens */ to: Address /** Amount minted */ amount: bigint } ``` Callback to invoke when tokens are minted. #### token * **Type:** `Address | bigint` Address or ID of the TIP20 token to watch. #### args (optional) * **Type:** `object` ```ts type Args = { /** Filter by recipient address */ to?: Address | Address[] | null } ``` Optional filter arguments. #### fromBlock (optional) * **Type:** `bigint` Block to start listening from. #### onError (optional) * **Type:** `function` ```ts declare function onError(error: Error): void ``` The callback to call when an error occurred when trying to get for a new block. #### poll (optional) * **Type:** `true` Whether to use polling. #### pollingInterval (optional) * **Type:** `number` Polling frequency (in ms). Defaults to Client's pollingInterval config. ## `token.watchRole` Watches for role membership update events on TIP20 tokens. ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { config } from './wagmi.config' const unwatch = Actions.token.watchRole(config, { token: '0x...', // Address or ID of the TIP20 token onRoleUpdated: (args, log) => { console.log('Account:', args.account) console.log('Has role:', args.hasRole) console.log('Role:', args.role) console.log('Type:', args.type) // 'granted' or 'revoked' console.log('Sender:', args.sender) }, }) // Later, stop watching unwatch() ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Return Type ```ts type ReturnType = () => void ``` Returns a function to unsubscribe from the event. ### Parameters #### onRoleUpdated * **Type:** `function` ```ts declare function onRoleUpdated(args: Args, log: Log): void type Args = { /** Role being updated */ role: Hex /** Account receiving or losing the role */ account: Address /** Address that updated the role */ sender: Address /** Whether the account has the role */ hasRole: boolean /** Type of role update */ type: 'granted' | 'revoked' } ``` Callback to invoke when a role membership is updated. #### token * **Type:** `Address | bigint` Address or ID of the TIP20 token. #### args (optional) * **Type:** `object` ```ts type Args = { /** Filter by role */ role?: Hex | Hex[] | null /** Filter by account */ account?: Address | Address[] | null /** Filter by sender */ sender?: Address | Address[] | null } ``` Filter parameters for the watch subscription. #### fromBlock (optional) * **Type:** `bigint` Block to start listening from. #### onError (optional) * **Type:** `function` ```ts declare function onError(error: Error): void ``` The callback to call when an error occurred when trying to get for a new block. #### poll (optional) * **Type:** `true` Whether to use polling. #### pollingInterval (optional) * **Type:** `number` Polling frequency (in ms). Defaults to Client's pollingInterval config. ## `token.watchTransfer` Watches for token transfer events on TIP20 tokens. ### Usage :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { config } from './wagmi.config' const unwatch = Actions.token.watchTransfer(config, { token: 1n, onTransfer: (args, log) => { console.log('Amount:', args.amount) console.log('From:', args.from) console.log('To:', args.to) }, }) // Later, stop watching unwatch() ``` ```ts twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: ### Return Type ```ts type ReturnType = () => void ``` Returns a function to unsubscribe from the event. ### Parameters #### onTransfer * **Type:** `function` ```ts declare function onTransfer(args: Args, log: Log): void type Args = { /** Amount transferred */ amount: bigint /** Address sending the tokens */ from: Address /** Address receiving the tokens */ to: Address } ``` Callback to invoke when tokens are transferred. #### token * **Type:** `Address | bigint` Address or ID of the TIP20 token to watch. #### args (optional) * **Type:** `object` ```ts type Args = { /** Filter by sender address(es) */ from?: Address | Address[] | null /** Filter by recipient address(es) */ to?: Address | Address[] | null } ``` Optional filter to watch only transfers from or to specific addresses. #### fromBlock (optional) * **Type:** `bigint` Block to start listening from. #### onError (optional) * **Type:** `function` ```ts declare function onError(error: Error): void ``` Callback to invoke when an error occurs while watching for new blocks. #### poll (optional) * **Type:** `true` Whether to use polling. #### pollingInterval (optional) * **Type:** `number` Polling frequency (in ms). Defaults to Client's pollingInterval config. ## `Handler.compose` Composes multiple handlers into a single handler. This is useful when you want to run multiple services (like fee payer and key manager) from a single endpoint. ### Usage ```ts twoslash [server.ts] // @noErrors import { Handler, Kv } from 'tempo.ts/server' import { tempoTestnet } from 'viem/chains' import { http } from 'viem' import { privateKeyToAccount } from 'viem/accounts' const handler = Handler.compose([ // Create a fee payer handler Handler.feePayer({ account: privateKeyToAccount('0x...'), chain: tempoTestnet.extend({ feeToken: '0x20c0...0001' }), transport: http(), path: '/fee-payer', }) // Create a key manager handler Handler.keyManager({ kv: Kv.memory(), path: '/keys', rp: 'example.com', }) ]) ``` Then plug `handler` into your server framework of choice. For example: ```ts createServer(handler.listener) // Node.js Bun.serve(handler) // Bun Deno.serve(handler) // Deno app.all('*', c => handler.fetch(c.request)) // Elysia app.use(handler.listener) // Express app.use(c => handler.fetch(c.req.raw)) // Hono export const GET = handler.fetch // Next.js export const POST = handler.fetch // Next.js ``` #### Base Path You can configure the base path for the composed handler. ```ts const handler = Handler.compose([ Handler.feePayer({ path: '/fee-payer', ... }), Handler.keyManager({ path: '/keys', ... }), ], { path: '/api' }) ``` Requests are routed as follows: * `POST /api/fee-payer` → Fee payer handler * `GET /api/keys/challenge` → Key manager handler * `GET /api/keys/:credentialId` → Key manager handler * `POST /api/keys/:credentialId` → Key manager handler ### Parameters #### handlers * **Type:** `Handler[]` An array of handlers to compose. Handlers are tried in order, and the first one that doesn't return a 404 wins. ```ts twoslash // @noErrors import { Handler } from 'tempo.ts/server' const handler = Handler.compose([ Handler.feePayer({ /* ... */ }), Handler.keyManager({ /* ... */ }), // Add more handlers as needed ]) ``` #### options.path * **Type:** `string` * **Default:** `'/'` The base path where all composed handlers will be mounted. This path is stripped from incoming requests before forwarding to individual handlers. ```ts twoslash // @noErrors import { Handler } from 'tempo.ts/server' const handler = Handler.compose( [ Handler.feePayer({ path: '/fee-payer', /* ... */ }), Handler.keyManager({ path: '/keys', /* ... */ }), ], { path: '/api' }, // [!code focus] ) // Endpoints will be: // POST /api/fee-payer // GET /api/keys/challenge // GET /api/keys/:credentialId // POST /api/keys/:credentialId ``` ## `Handler.feePayer` Creates a server handler that acts as a fee payer for transactions. This enables you to subsidize gas costs for your users by signing transactions with a dedicated fee payer account on your backend. ### Usage :::code-group ```ts twoslash [server.ts] // @noErrors import { Handler } from 'tempo.ts/server' import { tempoTestnet } from 'viem/chains' import { http } from 'viem' import { privateKeyToAccount } from 'viem/accounts' const handler = Handler.feePayer({ account: privateKeyToAccount('0x...'), chain: tempoTestnet.extend({ feeToken: '0x20c0...0001' }), path: '/fee-payer', transport: http(), }) ``` ```ts twoslash [example.client.ts] // @noErrors import { createClient, http, walletActions } from 'viem' import { tempoTestnet } from 'viem/chains' import { tempoActions, withFeePayer } from 'viem/tempo' const client = createClient({ chain: tempoTestnet, transport: withFeePayer( http(), // Default transport http('http://localhost:3000/fee-payer'), // Fee payer transport (your server) ), }).extend(tempoActions()) // Send a fee-sponsored payment const receipt = await client.token.transferSync({ amount: parseUnits('10', 6), feePayer: true, to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', token: '0x20c0000000000000000000000000000000000001', }) ``` ::: Then plug `handler` into your server framework of choice. For example: ```ts createServer(handler.listener) // Node.js Bun.serve(handler) // Bun Deno.serve(handler) // Deno app.all('*', c => handler.fetch(c.request)) // Elysia app.use(handler.listener) // Express app.use(c => handler.fetch(c.req.raw)) // Hono export const GET = handler.fetch // Next.js export const POST = handler.fetch // Next.js ``` ### How It Works The fee payer handler intercepts RPC requests and handles the following methods: * **`eth_signTransaction`** - Signs a transaction with the fee payer account * **`eth_signRawTransaction`** - Signs a pre-serialized transaction with the fee payer account * **`eth_sendRawTransaction`** - Signs and broadcasts a transaction with the fee payer account * **`eth_sendRawTransactionSync`** - Signs, broadcasts, and waits for confirmation For each of these methods, the handler: 1. Deserializes the transaction (if necessary) 2. Signs the transaction with your fee payer account 3. Returns the signed transaction or transaction hash ### Parameters #### account * **Type:** `LocalAccount` * **Required:** Yes The account to use as the fee payer. This account will sign all transactions and pay the gas fees. #### chain * **Type:** `Chain` Chain (and fee token) to use. #### transport * **Type:** `Transport` Transport to use. #### path * **Type:** `string` * **Default:** `'/'` * **Optional** The path where the handler will listen for requests. #### onRequest * **Type:** `(request: RpcRequest) => Promise` * **Optional** A callback function that's called before processing each request. Useful for logging, rate limiting, or custom validation. ## `Handler.keyManager` Creates a server handler that manages WebAuthn credential public keys. This handler stores and retrieves the public keys associated with passkey credentials, enabling users to access their accounts from any device. ### Usage :::code-group ```ts twoslash [server.ts] // @noErrors import { Handler, Kv } from 'tempo.ts/server' const handler = Handler.keyManager({ kv: Kv.memory(), path: '/keys', rp: 'example.com', }) ``` ```ts twoslash [wagmi.config.ts] // @noErrors import { tempoTestnet } from 'viem/chains' import { KeyManager, webAuthn } from 'tempo.ts/wagmi' import { createConfig, http } from 'wagmi' export const config = createConfig({ connectors: [ webAuthn({ keyManager: KeyManager.http('http://localhost:3000/keys'), rpId: 'example.com', }), ], chains: [tempoTestnet], transports: { [tempo.id]: http(), }, }) ``` ::: Then plug `handler` into your server framework of choice. For example: ```ts createServer(handler.listener) // Node.js Bun.serve(handler) // Bun Deno.serve(handler) // Deno app.all('*', c => handler.fetch(c.request)) // Elysia app.use(handler.listener) // Express app.use(c => handler.fetch(c.req.raw)) // Hono export const GET = handler.fetch // Next.js export const POST = handler.fetch // Next.js ``` :::warning It is not recommended to use `Kv.memory()` in production. Instead, use a persistent store like Cloudflare or Vercel KV, or a Redis instance. ::: ### How It Works The key manager handler exposes three endpoints: #### `GET /{path}/challenge` Generates a random challenge for WebAuthn credential creation. The challenge is stored temporarily (with a 5-minute expiration) to be verified later during credential registration. #### `GET /{path}/:credentialId` Retrieves the public key for a given credential ID. This is used when a user authenticates with an existing passkey. #### `POST /{path}/:credentialId` Stores a public key for a new credential. This endpoint: 1. Verifies the challenge was previously issued and is still valid 2. Validates the credential's `clientDataJSON.type` is `'webauthn.create'` 3. Verifies the origin matches the configured RP ID (if set) 4. Extracts and stores the public key from the authenticator data ### Parameters #### kv * **Type:** `Kv` * **Required:** Yes The key-value store to use for persisting challenges and public keys. tempo.ts provides adapters for common KV stores: ```ts twoslash // @noErrors import { Kv } from 'tempo.ts/server' // In-memory store (for development/testing) const kv = Kv.memory() // Cloudflare KV import { env } from 'cloudflare:workers' const kv = Kv.cloudflare(env.KEY_STORE) ``` :::warning `Kv.memory()` stores data only in memory and will be lost when the server restarts. Use a persistent store like Cloudflare or Vercel KV, or a Redis instance in production. ::: #### path * **Type:** `string` The base path where the handler will listen for requests. All three endpoints (`/challenge`, `/:credentialId`) will be mounted under this path. ```ts twoslash // @noErrors import { Handler } from 'tempo.ts/server' const handler = Handler.keyManager({ // ... other options path: '/api/keys', // [!code focus] }) // Endpoints will be: // GET /api/keys/challenge // GET /api/keys/:credentialId // POST /api/keys/:credentialId ``` #### rp * **Type:** `string | { id: string, name?: string }` The Relying Party (RP) identifier and name. This is used to: * Include RP information in the challenge response * Verify the origin of credential registration requests ```ts twoslash // @noErrors import { Handler } from 'tempo.ts/server' // Simple string (uses as both id and name) const handler = Handler.keyManager({ // ... other options rp: 'example.com', // [!code focus] }) // Object with custom name const handler2 = Handler.keyManager({ // ... other options rp: { // [!code focus] id: 'example.com', // [!code focus] name: 'Example App', // [!code focus] }, // [!code focus] }) ``` :::info The RP ID must match the domain where your application is hosted. For localhost, the origin verification is automatically skipped. ::: ### Storage Schema The handler uses the following key patterns in the KV store: * **`challenge:{hex}`** - Stores issued challenges * **`credential:{credentialId}`** - Stores public keys import LucideBadgeDollarSign from '~icons/lucide/badge-dollar-sign' import LucideKeyRound from '~icons/lucide/key-round' import LucideBlocks from '~icons/lucide/blocks' import * as Card from "../../../../components/Card.tsx" ## Overview Server handlers are framework-agnostic handlers that run on your backend to manage protocol operations that require server-side logic. Handlers are compatible with any server framework that supports the: * [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API), and is exposed via the `Handler#fetch` function * [Node.js `RequestListener` API](https://nodejs.org/api/http.html#http_class_http_serverrequestlistener), and is exposed via the `Handler#listener` function ```ts import { Handler } from 'tempo.ts/server' import { account, client } from './config' const handler = Handler.feePayer({ account, client, feeToken: '0x20c0…0001' path: '/fee-payer', }) createServer(handler.listener) // Node.js Bun.serve(handler) // Bun Deno.serve(handler) // Deno app.all('*', c => handler.fetch(c.request)) // Elysia app.use(handler.listener) // Express app.use(c => handler.fetch(c.req.raw)) // Hono export const GET = handler.fetch // Next.js export const POST = handler.fetch // Next.js ``` ### Handlers ## Setup Setup infinite pooled Tempo node instances in TypeScript using [`prool`](https://github.com/wevm/prool) by following the steps below. ::::steps ### Install :::code-group ```bash [npm] npm i prool ``` ```bash [pnpm] pnpm i prool ``` ```bash [bun] bun i prool ``` ::: * [Prool](https://github.com/wevm/prool) is a library that provides programmatic HTTP testing instances for Ethereum. ### Create Instance You can programmatically start a Tempo node instance in TypeScript using `Instance.tempo`: ```ts import { Instance, Server } from 'prool' const server = Server.create({ instance: Instance.tempo(), }); // Start the node await server.start() // Instances available at: // - http://localhost:8545/1 // - http://localhost:8545/2 // - http://localhost:8545/3 // - http://localhost:8545/4 // - http://localhost:8545/n ``` :::tip You can also set up the Tempo instance using `Instance.tempo` directly if you do not need pooling. ```ts import { Instance } from 'prool'; const instance = Instance.tempo() // Start the node await instance.start() // Instance available at: http://localhost:8545 ``` ::: ### Next Steps After you have set up Tempo with Prool, you can now: * Easily use Tempo in your test suite with Vitest * Run Tempo locally alongside your Vite or Next.js development server :::: ## Rust Tempo distributes a Rust SDK in the form of an [Alloy](https://alloy.rs) crate. Alloy is a popular Rust crate for interacting with EVM-compatible blockchains. The Tempo Alloy crate can be used to perform common operations with the chain, such as: querying the chain, sending Tempo transactions, managing tokens & their AMM pools, and more. ::::steps ### Install To install the Tempo extension, you will need to install [Alloy](https://alloy.rs) and Tempo: ```bash [cargo] cargo add alloy tokio cargo add tempo-alloy --git https://github.com/tempoxyz/tempo ``` :::tip We use [`tokio`](https://tokio.rs) in this example, but you can use any async runtime. ::: ### Configure A Provider To use the Tempo extension crate, you will need to create a [`Provider`] using the [`TempoNetwork`](https://tempoxyz.github.io/tempo/tempo_alloy/struct.TempoNetwork.html). This will enable the usage of Tempo specific types on the [`Provider`] instance. For more information about network types, see the Alloy [documentation](https://alloy.rs/guides/interacting-with-multiple-networks#interacting-with-multiple-networks). ```rs [main.rs] use alloy::ProviderBuilder; // [!code focus] use tempo_alloy::TempoNetwork; // [!code focus] #[tokio::main] async fn main() -> Result<(), Box> { let provider = ProviderBuilder::new_with_network::() // [!code focus] .connect(&std::env::var("RPC_URL").expect("No RPC URL set")) // [!code focus] .await?; // [!code focus] Ok(()) } ``` ### Use Actions Now we are ready to use the provider to interact with the network. We can use the provider to send transactions, read data, and more. ```rs [main.rs] use alloy::ProviderBuilder; use tempo_alloy::TempoNetwork; #[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?; // [!code focus:2] println!("{}", provider.get_block_number().await?); // @log: 421045 Ok(()) } ``` See the Alloy [documentation](https://alloy.rs) or the [`Provider`] docs for more examples. [`Provider`]: https://docs.rs/alloy/latest/alloy/providers/trait.Provider.html :::: ## Go Tempo distributes a Go SDK for building application clients. The SDK provides packages for RPC communication, transaction signing, and key management. The Tempo Go 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 Go SDK: ```bash [go] go get github.com/tempoxyz/tempo-go@v0.1.0 ``` :::tip The SDK requires Go 1.21 or higher. ::: ### Create an RPC Client To interact with Tempo, first create an RPC client connected to a Tempo node: ```go [main.go] package main import ( "fmt" "github.com/tempoxyz/tempo-go/pkg/client" ) func main() { c, err := client.New("https://rpc.testnet.tempo.xyz") // [!code focus] if err != nil { // [!code focus] panic(err) // [!code focus] } // [!code focus] fmt.Println("Connected to Tempo") } ``` ### Create a Signer Create a signer to sign transactions. The signer manages your private key and generates signatures: ```go [main.go] package main import ( "fmt" "github.com/tempoxyz/tempo-go/pkg/client" "github.com/tempoxyz/tempo-go/pkg/signer" // [!code focus] ) func main() { c, _ := client.New("https://rpc.testnet.tempo.xyz") s, err := signer.NewSigner("0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80") // [!code focus] if err != nil { // [!code focus] panic(err) // [!code focus] } // [!code focus] fmt.Printf("Address: %s\n", s.Address().Hex()) } ``` ### Send a Transaction Now we can build and send a transaction. Tempo transactions support batching multiple calls in a single transaction: ```go [main.go] package main import ( "fmt" "math/big" "github.com/ethereum/go-ethereum/common" "github.com/tempoxyz/tempo-go/pkg/client" "github.com/tempoxyz/tempo-go/pkg/signer" "github.com/tempoxyz/tempo-go/pkg/transaction" ) func main() { c, _ := client.New("https://rpc.testnet.tempo.xyz") s, _ := signer.NewSigner("0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80") recipient := common.HexToAddress("0x70997970C51812dc3A010C7d01b50e0d17dc79C8") amount := new(big.Int).Mul(big.NewInt(10), big.NewInt(1e18)) transferData := buildERC20TransferData(recipient, amount) // [!code focus:13] tx := transaction.New() tx.ChainID = big.NewInt(42429) // Tempo testnet tx.MaxFeePerGas = big.NewInt(2000000000) tx.MaxPriorityFeePerGas = big.NewInt(1000000000) tx.Gas = 100000 tx.Calls = []transaction.Call{{ To: &transaction.AlphaUSDAddress, Value: big.NewInt(0), Data: transferData, }} transaction.SignTransaction(tx, s) hash, _ := c.SendTransaction(tx) fmt.Printf("Transaction hash: %s\n", hash.Hex()) } func buildERC20TransferData(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 } ``` :::: ### Examples #### Batch Multiple Calls Tempo transactions can batch multiple calls into a single transaction: ```go [batch.go] tx := transaction.NewDefault(42429) // Tempo testnet tx.Gas = 150000 tx.Calls = []transaction.Call{ {To: &addr1, Value: big.NewInt(0), Data: transfer1Data}, {To: &addr2, Value: big.NewInt(0), Data: transfer2Data}, {To: &addr3, Value: big.NewInt(0), Data: contractCallData}, } transaction.SignTransaction(tx, signer) client.SendTransaction(tx) ``` #### Fee Sponsorship Have another account pay for transaction fees using a fee payer: ```go [feepayer.go] tx := transaction.NewDefault(42429) transaction.SignTransaction(tx, userSigner) transaction.AddFeePayerSignature(tx, feePayerSigner) client.SendTransaction(tx) ``` #### Transaction Validity Window Set a time window during which the transaction is valid: ```go [validity.go] import "time" tx := transaction.NewDefault(42429) tx.ValidAfter = uint64(time.Now().Unix()) tx.ValidBefore = uint64(time.Now().Add(1 * time.Hour).Unix()) transaction.SignTransaction(tx, signer) client.SendTransaction(tx) ``` ### Packages | Package | Description | | ------------- | -------------------------------------------------- | | `transaction` | TempoTransaction encoding, signing, and validation | | `client` | RPC client for interacting with Tempo nodes | | `signer` | Key management and signature generation | ### Next Steps After setting up the Go 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-exchange), and [more](/). * View the [examples on GitHub](https://github.com/tempoxyz/tempo-go/tree/main/examples) ## Foundry for Tempo Tempo builds on top of [Foundry](https://github.com/foundry-rs/foundry): the leading Ethereum development toolkit, through a custom fork that adds first-class support for Tempo. This fork extends Foundry with Tempo's [protocol-level features](/protocol/), enabling developers to build, test, and deploy contracts that go [beyond the limits of standard EVM chains](/quickstart/evm-compatibility). For general information about Foundry, see the [Foundry documentation](https://getfoundry.sh/). ### 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: Install regular `foundryup`: ```bash curl -L https://foundry.paradigm.xyz | bash ``` Or if you already have `foundryup` installed: ```bash foundryup --update ``` Next, run: ```bash foundryup -n tempo ``` It will automatically install the latest `nightly` release of the precompiled binaries: [`forge`](https://getfoundry.sh/forge/overview#forge) and [`cast`](https://getfoundry.sh/cast/overview#cast). To install a specific version, replace `` with the desired release tag: ```bash foundryup -n tempo -i ``` #### Verify Installation ```bash forge -V ``` You should see version information include `-tempo`, indicating you are using the Tempo fork. ```bash # forge -tempo ( ) ``` #### Create a new Foundry project Initialize a new project with Tempo support: ```bash 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. #### Test & deploy with `forge` locally ```bash # Build your contracts forge build # Run all tests locally forge test # Run deployment scripts locally forge script script/Mail.s.sol ``` #### Test & deploy with `forge` on Tempo Testnet ```bash # Set environment variables export TEMPO_RPC_URL=https://rpc.testnet.tempo.xyz export VERIFIER_URL=https://contracts.tempo.xyz # Optional: create a new keypair and request some testnet tokens from the faucet. cast wallet new cast rpc tempo_fundAddress --rpc-url $TEMPO_RPC_URL # Run all tests on Tempo's testnet forge test # Deploy a simple contract forge create src/Mail.sol:Mail \ --rpc-url $TEMPO_RPC_URL \ --interactive \ --broadcast \ --verify \ --constructor-args 0x20c0000000000000000000000000000000000001 # Deploy a simple contract with custom fee token forge create src/Mail.sol:Mail \ --fee-token \ --rpc-url $TEMPO_RPC_URL \ --interactive \ --broadcast \ --verify \ --constructor-args 0x20c0000000000000000000000000000000000001 # Run a deployment script and verify on Tempo's explorer forge script script/Mail.s.sol \ --rpc-url $TEMPO_RPC_URL \ --interactive \ --sender \ --broadcast \ --verify # Run a deployment script with custom fee token and verify on Tempo's explorer forge script script/Mail.s.sol \ --fee-token \ --rpc-url $TEMPO_RPC_URL \ --interactive \ --sender \ --broadcast \ --verify ``` #### Interact & debug with `cast` ```bash # Check that your contract is deployed: cast code \ --rpc-url $TEMPO_RPC_URL # Interact with the contract, retrieving the token address: cast call "token()" \ --rpc-url $TEMPO_RPC_URL # Get the name of an ERC20 token: cast erc20 name \ --rpc-url $TEMPO_RPC_URL # Check the ERC20 token balance of your address: cast erc20 balance \ --rpc-url $TEMPO_RPC_URL # Transfer some of your ERC20 tokens: cast erc20 transfer \ --rpc-url $TEMPO_RPC_URL \ --interactive # Transfer some of your ERC20 tokens with custom fee token: cast erc20 transfer \ --fee-token --rpc-url $TEMPO_RPC_URL \ --interactive # Send a transaction with custom fee token: cast send \ --fee-token \ --rpc-url $TEMPO_RPC_URL \ --interactive # Replay a transaction by hash: cast run \ --rpc-url $TEMPO_RPC_URL ``` #### Limitations Ledger and Trezor wallets are not yet compatible with the `--fee-token` option. import * as Demo from '../../components/guides/Demo.tsx' import { ConnectWallet } from '../../components/ConnectWallet.tsx' ### Connect to the Network You can connect with the Tempo Testnet like you would with any other EVM chain. #### Connect using a Browser Wallet Click on your browser wallet below to automatically connect it to the Tempo Testnet. :::warning Note that on some wallets, you might see an unusually high "balance". This is because, historically, blockchain wallets have always assumed that a blockchain has a "native gas token". On Tempo, there is no native gas token, and so the value shown is a placeholder. See [EVM Differences](/quickstart/evm-compatibility#handling-eth-balance-checks) for more information on this quirk. ::: #### Connect via CLI To connect via CLI, we recommend using [`cast`](https://getfoundry.sh/cast/overview/), which is a command-line tool for interacting with Ethereum networks. To install cast, you can read more in the [Foundry SDK docs](/sdk/foundry#get-started-with-foundry). ```bash /dev/null/monitor.sh#L1-11 # Check block height (should be steadily increasing) cast block-number --rpc-url https://rpc.testnet.tempo.xyz ``` #### Direct Connection Details If you're manually connecting to Tempo Testnet, you can use the following details: | **Property** | **Value** | | ------------------ | -------------------------------------------------------- | | **Network Name** | Tempo Testnet (Andantino) | | **Currency** | `USD` | | **Chain ID** | `42429` | | **HTTP URL** | `https://rpc.testnet.tempo.xyz` | | **WebSocket URL** | `wss://rpc.testnet.tempo.xyz` | | **Block Explorer** | [`https://explore.tempo.xyz`](https://explore.tempo.xyz) | import LucideHammer from '~icons/lucide/hammer' import LucideBarChart from '~icons/lucide/bar-chart' import LucideWallet from '~icons/lucide/wallet' import LucideServer from '~icons/lucide/server' import LucideShield from '~icons/lucide/shield' import LucidePackage from '~icons/lucide/package' import LucideSearch from '~icons/lucide/search' import * as Card from "../../components/Card.tsx" ## Developer Tools 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.

Reach out to get listed on this page.

Read our guide for wallet developers to get started.

### Data & Analytics #### 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). 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/). #### 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). ### 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. #### 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. ### Embedded Wallets #### 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:42429"` 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. ::: #### thirdweb [thirdweb](https://thirdweb.com) provides a full-stack developer platform for building modern onchain applications. Developers can create wallets, deploy tokens, use blockchain-native AI tools, and integrate native internet payments—all with built-in support for Tempo. Access the Tempo Testnet via the [thirdweb dashboard](https://thirdweb.com/tempo-testnet) and explore tooling in the [Thirdweb developer docs](https://portal.thirdweb.com). Try features live in the [thirdweb Playground](https://playground.thirdweb.com) and create an account by signing up [here](https://thirdweb.com/login). #### 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` example](https://github.com/tkhq/sdk/tree/main/examples/with-tempo) in their SDK to get you started quickly. ::: #### 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. ### Smart Contract Libraries #### 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. ### Node Infrastructure #### 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**. #### 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). #### 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. ### Security & Compliance #### 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). ### Issuance #### 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/). import { Callout } from 'vocs/components' import LucideWallet from '~icons/lucide/wallet' import LucideSend from '~icons/lucide/send' import LucideCode from '~icons/lucide/code' import LucideCheckCircle from '~icons/lucide/check-circle' import LucideZap from '~icons/lucide/zap' import * as Card from "../../components/Card.tsx" ### EVM Differences Tempo is fully compatible with the Ethereum Virtual Machine (EVM), targeting the **Osaka** EVM hard fork. Developers can deploy and interact with smart contracts using the same tools, languages, and frameworks they use on Ethereum, such as Solidity, Foundry, and Hardhat. All Ethereum JSON-RPC methods work out of the box. While the execution environment mirrors Ethereum's, Tempo introduces some differences optimized for payments, described below. ### Wallet Differences By default, all existing functionality will work for EVM-compatible wallets, with only a few quirks. For developers of wallets, we strongly encourage you to implement support for Tempo Transactions over regular EVM transactions. See the [transaction differences](#transaction-differences) for more. :::tip If you are building a wallet, read our [guide for wallet developers](/quickstart/wallet-developers). ::: #### Handling ETH (native token) Balance Checks Remember that on Tempo, there is no native gas token. Many wallets and applications check a user's "native account balance" before letting them complete some action. In this scenario, you might see an error message like "Insufficient balance". This stems from the return value of the `eth_getBalance` RPC method. When a wallet calls this method, it expects a hex string representing the "native token balance", hard-coded to be represented as an 18-decimal place number. On Tempo, the `eth_getBalance` method returns a hex string representing an extremely large number. Specifically it returns: `0x9612084f0316e0ebd5182f398e5195a51b5ca47667d4c9b26c9b26c9b26c9b2` which is represented in decimals as 4.242424242424242e+75. Our recommendation to wallets and to applications using this method is to remove this balance check, and to not represent any "native balance" in your user's UI. This will allow users to complete actions without being blocked by balance checks. We endorse [this proposed ERC](https://github.com/ethereum/ERCs/pull/1220) to standardize this behavior. #### Specifying a Native Token Currency Symbol Sometimes wallets will need to specify the currency symbol for the native token. On Tempo, there is no native token, but fees are denominated in USD. So, we recommend using the currency symbol "USD". ### Transaction Differences

We strongly recommend using Tempo Transactions over legacy Ethereum transactions if you have control over the transaction submission, in order to leverage features like native gas sponsorship.

#### Dealing with the fee token selection Tempo does not have a native gas token. Instead, fees are denominated in USD and fees can be paid in an stablecoin. For Tempo Transactions, the `fee_token` field can be set to any TIP-20 token, and fees are paid in that token. If your transactions are not using Tempo Transactions, there is a cascading fee token selection algorithm that determines the default fee token based on the user's preferences and the contract being called. This preference system is specified [here](/protocol/fees/spec-fee#fee-token-preferences) in detail. ##### Consideration 1: Setting a user default fee token As specified in the preference system above, the simplest way to specify the fee token for a user is to set the user default fee token. Read about how to do that [here](/protocol/fees/spec-fee#account-level) on behalf of an account. ##### Consideration 2: Paying fees in the TIP-20 contract being interacted with If the user is calling a method on a TIP-20 token (e.g., `transfer`), the default fee token is that token itself. For example, if the user is calling the `transfer` method on a TIP-20 token with a symbol of "USDC", the default fee token would be "USDC". Importantly, note that the `amount` field in this case is sent in full. So, if the user is calling the `transfer` method on a TIP-20 token with a symbol of "USDC" with the `amount` field set to 1000, the full amount of the token will be transferred **and** the sender's balance will be reduced by the amount spent in fees. So, the recipient will receive 1000 USDC. ##### Consideration 3: The fallback in the case of a non-TIP-20 contract If the user is calling a contract that is not a TIP-20 token, the EVM transaction will default to the PathUSD token. Thus, in order to send transactions to non-TIP-20 contracts, the wallet must hold some balance of PathUSD. On the Tempo Testnet, PathUSD is available from the [faucet](/quickstart/faucet). If a wallet wants to submit a non-TIP20 transaction without having to submit the above transaction, we recommend investing in using [Tempo Transactions](/quickstart/integrate-tempo#tempo-transactions) instead. ### VM Layer Differences At the VM layer, all opcodes are supported out of the box. Due to the lack of a native token, native token balance is always returning zero balances. #### Balance Opcodes and RPC Methods | Feature | Behavior on Tempo | Alternatives | | ------------------------------- | -------------------- | ------------------------------ | | **`BALANCE` and `SELFBALANCE`** | Will always return 0 | Use TIP-20 `balanceOf` instead | | **`CALLVALUE`** | Will always return 0 | There is no alternative | :::info We are exploring transaction level introspection for Tempo Transactions, with an ability to declare things like `tx.fee_token` and `tx.fee_payer` in Solidity. ::: ### Consensus & Finality Tempo uses **Simplex BFT consensus** with a permissioned validator set at launch, providing deterministic finality, unlike Ethereum's finality gadget which takes approximately 12 minutes. Block times are targeted at \~0.5 seconds compared to Ethereum's \~12 second slots. import * as Demo from '../../components/guides/Demo.tsx' import * as Step from '../../components/guides/steps' import * as Token from '../../components/guides/tokens' import { Tabs } from '../../components/Tabs.tsx' ### Faucet Get test stablecoins on Tempo testnet.
Connect your wallet to receive test stablecoins directly.
Send test stablecoins to any address.
The faucet funds the following assets. | Asset | Address | Amount | | ---------------------------------------------------------------------------------------- | -------------------------------------------- | -----: | | [AlphaUSD](https://explore.tempo.xyz/address/0x20c0000000000000000000000000000000000001) | `0x20c0000000000000000000000000000000000001` | `1M` | | [BetaUSD](https://explore.tempo.xyz/address/0x20c0000000000000000000000000000000000002) | `0x20c0000000000000000000000000000000000002` | `1M` | | [ThetaUSD](https://explore.tempo.xyz/address/0x20c0000000000000000000000000000000000003) | `0x20c0000000000000000000000000000000000003` | `1M` | import * as Demo from '../../components/guides/Demo.tsx' import { ConnectWallet } from '../../components/ConnectWallet.tsx' import LucideZap from '~icons/lucide/zap' import LucideLandmark from '~icons/lucide/landmark' import LucidePackage from '~icons/lucide/package' import LucideRocket from '~icons/lucide/rocket' import LucideHammer from '~icons/lucide/hammer' import LucidePlug from '~icons/lucide/plug' import LucideServer from '~icons/lucide/server' import LucideBookOpen from '~icons/lucide/book-open' import LucideMail from '~icons/lucide/mail' import LucideCoins from '~icons/lucide/coins' import LucideNetwork from '~icons/lucide/network' import LucideDroplet from '~icons/lucide/droplet' import LucideWallet from '~icons/lucide/wallet' import LucideSend from '~icons/lucide/send' import LucideArrowLeftRight from '~icons/lucide/arrow-left-right' import TempoTxProperties from '../../snippets/tempo-tx-properties.mdx' import { Callout } from 'vocs/components' import * as Card from "../../components/Card.tsx" ### Integrate Tempo Testnet 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.

Click here to deposit testnet funds directly into your wallet.

#### Start Building The guides below will help you get a sense for what is possible to do with Tempo. All of the guides below are fully-featured Tempo applications themselves, built with the [Tempo SDKs](/sdk).

Check out the Tempo SDKs for language-specific clients to dive in yourself.

#### Go Deeper The Tempo source code is fully open-source and available on [GitHub](https://github.com/tempoxyz). If you're interested in learning more about how Tempo works under the hood, feel free to explore the codebase directly, run a node for yourself, or refer to the protocol specifications for a deeper understanding. We welcome contributions from the community to help improve and expand the project. ### Predeployed Contracts #### System Contracts Core protocol contracts that power Tempo's features. | Contract | Address | Description | | -------------------------------------------------------------------- | -------------------------------------------- | ----------------------------------- | | [**TIP-20 Factory**](/protocol/tip20/overview) | `0x20fc000000000000000000000000000000000000` | Create new TIP-20 tokens | | [**Fee Manager**](/protocol/fees/spec-fee-amm#2-feemanager-contract) | `0xfeec000000000000000000000000000000000000` | Handle fee payments and conversions | | [**Stablecoin DEX**](/protocol/exchange) | `0xdec0000000000000000000000000000000000000` | Enshrined DEX for stablecoin swaps | | [**TIP-403 Registry**](/protocol/tip403/spec) | `0x403c000000000000000000000000000000000000` | Transfer policy registry | | [**pathUSD**](/protocol/exchange/pathUSD) | `0x20c0000000000000000000000000000000000000` | First stablecoin deployed | #### Standard Utilities Popular Ethereum contracts deployed for convenience. | Contract | Address | Description | | ------------------------------------------------------------------------------------------ | -------------------------------------------- | --------------------------------------- | | [**Multicall3**](https://www.multicall3.com/) | `0xcA11bde05977b3631167028862bE2a173976CA11` | Batch multiple calls in one transaction | | [**CreateX**](https://github.com/pcaversaccio/createx) | `0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed` | Deterministic contract deployment | | [**Permit2**](https://docs.uniswap.org/contracts/permit2/overview) | `0x000000000022d473030f116ddee9f6b43ac78ba3` | Token approvals and transfers | | [**Arachnid Create2 Factory**](https://github.com/Arachnid/deterministic-deployment-proxy) | `0x4e59b44847b379578588920cA78FbF26c0B4956C` | CREATE2 deployment proxy | | [**Safe Deployer**](https://github.com/safe-global/safe-deployer) | `0x914d7Fec6aaC8cd542e72Bca78B30650d45643d7` | Safe deployer contract | #### Contract ABIs ABIs for these contracts are available in the SDK: ```typescript import { Abis } from 'viem/tempo' const tip20Abi = Abis.tip20 const tip20FactoryAbi = Abis.tip20Factory const stablecoinExchangeAbi = Abis.stablecoinExchange const feeManagerAbi = Abis.feeManager const feeAmmAbi = Abis.feeAmm // ... ``` import { Callout } from 'vocs/components' import LucideCircleHelp from '~icons/lucide/circle-help' import LucideRoute from '~icons/lucide/route' import LucideBanknote from '~icons/lucide/banknote' import LucideWallet from '~icons/lucide/wallet' import LucideShield from '~icons/lucide/shield' import LucideFileCheck from '~icons/lucide/file-check' import LucideSettings from '~icons/lucide/settings' import LucideGift from '~icons/lucide/gift' import LucideMessageSquare from '~icons/lucide/message-square' import * as Card from "../../components/Card.tsx" ## Use the TIP-20 Standard TIP-20 is Tempo's native token standard for stablecoins and payment tokens. TIP-20 is designed for stablecoin payments, and is the foundation for many token-related functions on Tempo including transaction fees, payment lanes, and optimized routing for DEX liquidity on Tempo.

This means ERC-20 functions works with TIP-20 out of the box.

All TIP-20 tokens are created by interacting with the [TIP-20 Factory contract](/protocol/tip20/spec#tip20factory), calling the `createToken` function. If you're issuing a stablecoin on Tempo, we **strongly recommend** using the TIP-20 standard. Learn more about the benefits, or follow the guide on issuance [here](/guide/issuance). ### Benefits & Features of TIP-20 Tokens Below are some of the key benefits and features of TIP-20 tokens: #### Pay Fees in Any Stablecoin Any USD-denominated TIP-20 token can be used to pay transaction fees on Tempo. The [Fee AMM](/protocol/fees/spec-fee-amm) automatically converts your token to the validator's preferred fee token, eliminating the need for users to hold a separate gas token. This feature works natively: no additional infrastructure or integration required. Full specification of this feature can be found in the [Payment Lanes Specification](/protocol/blockspace/payment-lane-specification). #### Get Predictable Payment Fees Tempo has dedicated payment lanes: reserved blockspace for payment TIP-20 transactions that other applications cannot consume. Even if there are extremely popular applications on the chain competing for blockspace, payroll runs or customer disbursements execute predictably. Learn more about the [payments lane](/protocol/blockspace/payment-lane-specification). #### Role-Based Access Control (RBAC) TIP-20 includes a built-in [RBAC system](/protocol/tip403/spec#tip-20-token-roles) that separates administrative responsibilities: * **ISSUER\_ROLE**: Grants permission to mint and burn tokens, enabling controlled token issuance * **PAUSE\_ROLE** / **UNPAUSE\_ROLE**: Allows pausing and unpausing token transfers for emergency controls * **BURN\_BLOCKED\_ROLE**: Permits burning tokens from blocked addresses (e.g., for compliance actions) Roles can be granted, revoked, and delegated without custom contract changes. This enables issuers to separate operational roles (e.g., who can mint) from administrative roles (e.g., who can pause). Learn more in the [TIP-20 specification](/protocol/tip20/spec#roles). #### TIP-403 Transfer Policies TIP-20 tokens integrate with the [TIP-403 Policy Registry](/protocol/tip403/overview) to enforce compliance policies. Each token can reference a policy that controls who can send and receive tokens: * **Whitelist policies**: Only addresses in the whitelist can transfer tokens * **Blacklist policies**: Addresses in the blacklist are blocked from transferring tokens Policies can be shared across multiple tokens, enabling consistent compliance enforcement across your token ecosystem. See the [TIP-403 specification](/protocol/tip403/spec) for details. #### Operational Controls TIP-20 tokens can set **supply caps**, which allow you to set a maximum token supply to control issuance. TIP-20 tokens also have **pause/unpause** commands, which provide emergency controls to halt transfers when needed. #### Transfer Memos **Transfer memos** enable you to attach 32-byte memos to transfers for payment references, invoice IDs, or transaction notes. #### Reward Distribution TIP-20 supports an opt-in [reward distribution system](/protocol/tip20-rewards/overview) that allows issuers to distribute rewards to token holders. Rewards can be claimed by holders or automatically forwarded to designated recipient addresses. #### Currency Declaration A TIP-20 token can declare a currency identifier (e.g., `"USD"`, `"EUR"`) that identifies the real-world asset backing the token. This enables proper routing and pricing in Tempo's [stablecoin exchange](/protocol/exchange). USD-denominated TIP-20 tokens can be used to pay transaction fees. import * as Card from "../../components/Card.tsx" import LucideCoins from '~icons/lucide/coins' import LucideFileText from '~icons/lucide/file-text' import LucideShieldCheck from '~icons/lucide/shield-check' import LucideWallet from '~icons/lucide/wallet' ## Wallet Integration Guide Because there is [no native token on Tempo](/quickstart/evm-compatibility#handling-eth-native-token-balance-checks) and transaction fees are paid directly in stablecoins, wallets need specific UI and logic adjustments to support the network. Follow this guide if your wallet logic and/or interfaces are dependent on the existence of a native token. As part of supporting Tempo in your wallet, you can also deliver an enhanced experience for your users by integrating [Tempo Transactions](/guide/tempo-transaction). Common use cases include enabling a gasless transactions for your users, letting your users decide what token to use for fees, and more. ### Steps ::::steps #### Handle the absence of a native token If you use `eth_getBalance` to validate a user's balance, you should instead check the user's account fee token balance on Tempo. Additionally, you should not display any "native balance" in your UI for Tempo users. :::info In testnet, `eth_getBalance` [returns a large placeholder value](/quickstart/evm-compatibility#handling-eth-native-token-balance-checks) for the native token balance to unblock existing assumptions wallets have about the native token balance. ::: :::code-group ```ts twoslash [example.ts] import { client } from './viem.config' const userFeeToken = await client.fee.getUserToken({ account: '0x...' }) const balance = await client.token.getBalance({ account: '0x...', token: userFeeToken.address }) ``` ```ts twoslash [viem.config.ts] filename="viem.config.ts" // [!include ~/snippets/viem.config.ts:setup] ``` ::: #### Configure native currency symbol If you need to display a native token symbol, such as showing how much gas a transaction requires, you can set the currency symbol to `USD` for Tempo as fees are denominated in USD. #### Use fee token preferences to quote gas prices On Tempo, users can pay fees in any supported stablecoin. You should quote gas/fee prices in your UI based on a transaction's fee token. :::info As a wallet developer, you can set the fee token for your user at the account level. If you don't, Tempo uses a cascading fee token selection algorithm to determine the fee token for a transaction – learn more about [Fee Token Preferences](/protocol/fees/spec-fee#fee-token-preferences). ::: #### Display token and network assets Tempo provides a public tokenlist service that hosts token and network assets. You can pull these assets from our public tokenlist service to display in your UI. * **GitHub**: [tempoxyz/tempo-apps/apps/tokenlist](https://github.com/tempoxyz/tempo-apps/tree/main/apps/tokenlist) * **Tokenlist JSON**: [tempoxyz.github.io/tempo-apps/42429/tokenlist.json](https://tempoxyz.github.io/tempo-apps/42429/tokenlist.json) #### Integrate Tempo Transactions We strongly recommend using [Tempo Transactions](/guide/tempo-transaction) if you control transaction submission. We have [SDKs and guides](/guide/tempo-transaction#integration-guides) available to get you integrated in less than an hour! :::tip Use Tempo Transactions to * Set the fee token used for your users' transactions ([guide](/guide/payments/pay-fees-in-any-stablecoin)) * Sponsor transaction fees for your users ([guide](/guide/payments/sponsor-user-fees)) * Send concurrent transactions with independent nonces ([guide](/guide/payments/send-parallel-transactions)) ::: :::: ### Recipes #### Get user's fee token Retrieve the user's configured fee token preference: ```ts import { getUserToken } from 'viem/tempo' const feeToken = await client.fee.getUserToken({ account: userAddress }) ``` See [`getUserToken`](https://viem.sh/tempo/actions/fee.getUserToken) for full documentation. #### Get token balance Check a user's balance for a specific token: ```ts import { getBalance } from 'viem/tempo' const balance = await client.token.getBalance({ account: userAddress, token: tokenAddress }) ``` See [`getBalance`](https://viem.sh/tempo/actions/token.getBalance) for full documentation. #### Set user fee token Set the user's default fee token preference. This will be used for all transactions unless a different fee token is specified at the transaction level. ```ts import { setUserToken } from 'viem/tempo' await client.fee.setUserTokenSync({ token: '0x20c0000000000000000000000000000000000001', }) ``` See [`setUserToken`](https://viem.sh/tempo/actions/fee.setUserToken) for full documentation. ### Checklist Before launching Tempo support, ensure your wallet: * [ ] Checks fee token balance instead of native balance * [ ] Hides or removes native balance display for Tempo * [ ] Displays `USD` as the currency symbol for gas * [ ] Quotes gas prices in the user's fee token * [ ] Pulls token/network assets from Tempo's tokenlist * [ ] (Recommended) Integrates Tempo Transactions for enhanced UX ### Learning Resources import LucideCoins from '~icons/lucide/coins' import LucideGift from '~icons/lucide/gift' import LucideShield from '~icons/lucide/shield' import LucideWallet from '~icons/lucide/wallet' import LucideLayers from '~icons/lucide/layers' import LucideLayout from '~icons/lucide/layout' import LucideArrowLeftRight from '~icons/lucide/arrow-left-right' import LucideGithub from '~icons/lucide/github' import * as Card from "../../components/Card.tsx" ### Tempo Protocol Tempo is a blockchain protocol purpose-built for global payments. Rather than being a general-purpose platform, Tempo makes deliberate technical choices optimized for payments at scale. This section provides details on the Tempo Protocol specifications, and serves as a technical reference for protocol implementers, auditors, and core contributors building on Tempo. #### Protocol Components ## Account Keychain Precompile **Address:** `0xAAAAAAAA00000000000000000000000000000000` ### Overview The Account Keychain precompile manages authorized Access Keys for accounts, enabling Root Keys (e.g., passkeys) to provision scoped "secondary" Access Keys with expiry timestamps and per-TIP20 token spending limits. ### Motivation The Tempo Transaction type unlocks a number of new signature schemes, including WebAuthn (Passkeys). However, for an Account using a Passkey as its Root Key, the sender will subsequently be prompted with passkey prompts for every signature request. This can be a poor user experience for highly interactive or multi-step flows. Additionally, users would also see "Sign In" copy in prompts for signing transactions which is confusing. This proposal introduces the concept of the Root Key being able to provision a (scoped) Access Key that can be used for subsequent transactions, without the need for repetitive end-user prompting. ### Concepts #### Access Keys Access Keys are secondary signing keys authorized by an account's Root Key. They can sign transactions on behalf of the account with the following restrictions: * **Expiry**: Unix timestamp when the key becomes invalid (0 = never expires) * **Spending Limits**: Per-TIP20 token limits that deplete as tokens are spent * Limits deplete as tokens are spent and can be updated by the Root Key via `updateSpendingLimit()` * Spending limits only apply to TIP20 token transfers, not ETH or other assets * **Privilege Restrictions**: Cannot authorize new keys or modify their own limits #### Authorization Hierarchy The protocol enforces a strict hierarchy at validation time: 1. **Root Key**: The account's main key (derived from the account address) * Can call all precompile functions * Has no spending limits 2. **Access Keys**: Secondary authorized keys * Cannot call mutable precompile functions (only view functions are allowed) * Subject to per-TIP20 token spending limits * Can have expiry timestamps ### Storage The precompile uses a `keyId` (address) to uniquely identify each access key for an account. **Storage Mappings:** * `keys[account][keyId]` → Packed `AuthorizedKey` struct (signature type, expiry, enforce\_limits, is\_revoked) * `spendingLimits[keccak256(account || keyId)][token]` → Remaining spending amount for a specific token (uint256) * `transactionKey` → Transient storage for the key ID that signed the current transaction (slot 0) **AuthorizedKey Storage Layout (packed into single slot):** * byte 0: signature\_type (u8) * bytes 1-8: expiry (u64, little-endian) * byte 9: enforce\_limits (bool) * byte 10: is\_revoked (bool) ### Interface ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; interface IAccountKeychain { /*////////////////////////////////////////////////////////////// STRUCTS //////////////////////////////////////////////////////////////*/ /// @notice Signature type enum SignatureType { Secp256k1, P256, WebAuthn, } /// @notice Token spending limit structure struct TokenLimit { address token; // TIP20 token address uint256 amount; // Spending limit amount } /// @notice Key information structure struct KeyInfo { SignatureType signatureType; // Signature type of the key address keyId; // The key identifier uint64 expiry; // Unix timestamp when key expires (0 = never) bool enforceLimits; // Whether spending limits are enforced for this key bool isRevoked; // Whether this key has been revoked } /*////////////////////////////////////////////////////////////// EVENTS //////////////////////////////////////////////////////////////*/ /// @notice Emitted when a new key is authorized event KeyAuthorized( address indexed account, address indexed publicKey, uint8 signatureType, uint64 expiry ); /// @notice Emitted when a key is revoked event KeyRevoked(address indexed account, address indexed publicKey); /// @notice Emitted when a spending limit is updated event SpendingLimitUpdated( address indexed account, address indexed publicKey, address indexed token, uint256 newLimit ); /*////////////////////////////////////////////////////////////// ERRORS //////////////////////////////////////////////////////////////*/ error KeyAlreadyExists(); error KeyNotFound(); error KeyInactive(); error KeyExpired(); error KeyAlreadyRevoked(); error SpendingLimitExceeded(); error InvalidSignatureType(); error ZeroPublicKey(); error UnauthorizedCaller(); /*////////////////////////////////////////////////////////////// MANAGEMENT FUNCTIONS //////////////////////////////////////////////////////////////*/ /** * @notice Authorize a new key for the caller's account * @dev MUST only be called in transactions signed by the Root Key * The protocol enforces this restriction by checking transactionKey[msg.sender] * @param keyId The key identifier (address) to authorize * @param signatureType Signature type of the key (0: Secp256k1, 1: P256, 2: WebAuthn) * @param expiry Unix timestamp when key expires (0 = never expires) * @param enforceLimits Whether to enforce spending limits for this key * @param limits Initial spending limits for tokens (only used if enforceLimits is true) */ function authorizeKey( address keyId, SignatureType signatureType, uint64 expiry, bool enforceLimits, TokenLimit[] calldata limits ) external; /** * @notice Revoke an authorized key * @dev MUST only be called in transactions signed by the Root Key * The protocol enforces this restriction by checking transactionKey[msg.sender] * @param keyId The key ID to revoke */ function revokeKey(address keyId) external; /** * @notice Update spending limit for a specific token on an authorized key * @dev MUST only be called in transactions signed by the Root Key * The protocol enforces this restriction by checking transactionKey[msg.sender] * @param keyId The key ID to update * @param token The token address * @param newLimit The new spending limit */ function updateSpendingLimit( address keyId, address token, uint256 newLimit ) external; /*////////////////////////////////////////////////////////////// VIEW FUNCTIONS //////////////////////////////////////////////////////////////*/ /** * @notice Get key information * @param account The account address * @param keyId The key ID * @return Key information (returns default values if key doesn't exist) */ function getKey( address account, address keyId ) external view returns (KeyInfo memory); /** * @notice Get remaining spending limit for a key-token pair * @param account The account address * @param keyId The key ID * @param token The token address * @return Remaining spending amount */ function getRemainingLimit( address account, address keyId, address token ) external view returns (uint256); /** * @notice Get the transaction key used in the current transaction * @dev Returns Address::ZERO if the Root Key is being used * @return The key ID that signed the transaction */ function getTransactionKey() external view returns (address); } ``` ### Behavior #### Key Authorization * Creates a new key entry with the specified `signatureType`, `expiry`, `enforceLimits`, and `isRevoked` set to `false` * If `enforceLimits` is `true`, initializes spending limits for each specified token * Emits `KeyAuthorized` event **Requirements:** * MUST be called by Root Key only (verified by checking `transactionKey[msg.sender] == 0`) * `keyId` MUST NOT be `address(0)` (reverts with `ZeroPublicKey`) * `keyId` MUST NOT already be authorized with `expiry > 0` (reverts with `KeyAlreadyExists`) * `keyId` MUST NOT have been previously revoked (reverts with `KeyAlreadyRevoked` - prevents replay attacks) * `signatureType` MUST be `0` (Secp256k1), `1` (P256), or `2` (WebAuthn) (reverts with `InvalidSignatureType`) * `expiry` CAN be any value (0 means never expires, stored as-is) * `enforceLimits` determines whether spending limits are enforced for this key * `limits` are only processed if `enforceLimits` is `true` #### Key Revocation * Marks the key as revoked by setting `isRevoked` to `true` and `expiry` to `0` * Once revoked, a `keyId` can NEVER be re-authorized for this account (prevents replay attacks) * Key can no longer be used for transactions * Emits `KeyRevoked` event **Requirements:** * MUST be called by Root Key only (verified by checking `transactionKey[msg.sender] == 0`) * `keyId` MUST exist (key with `expiry > 0`) (reverts with `KeyNotFound` if not found) #### Spending Limit Update * Updates the spending limit for a specific token on an authorized key * Allows Root Key to modify limits without revoking and re-authorizing the key * If the key had unlimited spending (`enforceLimits == false`), enables limits * Sets the new remaining limit to `newLimit` * Emits `SpendingLimitUpdated` event **Requirements:** * MUST be called by Root Key only (verified by checking `transactionKey[msg.sender] == 0`) * `keyId` MUST exist and not be revoked (reverts with `KeyNotFound` or `KeyAlreadyRevoked`) * `keyId` MUST not be expired (reverts with `KeyExpired`) ### Security Considerations #### Access Key Storage Access Keys should be securely stored to prevent unauthorized access: * **Device and Application Scoping**: Access Keys SHOULD be scoped to a specific client device AND application combination. Access Keys SHOULD NOT be shared between devices or applications, even if they belong to the same user. * **Non-Extractable Keys**: Access Keys SHOULD be generated and stored in a non-extractable format to prevent theft. For example, use WebCrypto API with `extractable: false` when generating Keys in web browsers. * **Secure Storage**: Private Keys MUST never be stored in plaintext. Private Keys SHOULD be encrypted and stored in a secure manner. For web applications, use browser-native secure storage mechanisms like IndexedDB with non-extractable WebCrypto keys rather than storing raw key material. #### Privilege Escalation Prevention Access Keys cannot escalate their own privileges because: 1. Management functions (`authorizeKey`, `revokeKey`, `updateSpendingLimit`) are restricted to Root Key transactions 2. The protocol sets `transactionKey[account]` during transaction validation to indicate which key signed the transaction 3. These management functions check that `transactionKey[msg.sender] == 0` (Root Key) before executing 4. Access Keys cannot bypass this check - transactions will revert with `UnauthorizedCaller` #### Spending Limit Enforcement * Spending limits are only enforced if `enforceLimits == true` for the key * Keys with `enforceLimits == false` have unlimited spending (no limits checked) * Spending limits are enforced by the protocol internally calling `verify_and_update_spending()` during execution * Limits are per-TIP20 token and deplete as TIP20 tokens are spent * Spending limits only track TIP20 token transfers (via `transfer` and `transferFrom`) and approvals * For approvals: only increases in approval amount count against the spending limit * Non-TIP20 asset movements (ETH, NFTs) are not subject to spending limits * Root keys (`keyId == address(0)`) have no spending limits - the function returns immediately * Failed limit checks revert the entire transaction with `SpendingLimitExceeded` #### Key Expiry * Keys with `expiry > 0` are checked against the current timestamp during validation * Expired keys cause transaction rejection with `KeyExpired` error (checked via `validate_keychain_authorization()`) * `expiry == 0` means the key never expires * Expiry is checked as: `current_timestamp >= expiry` (key is expired when current time reaches or exceeds expiry) ### Usage Patterns #### First-Time Access Key Authorization 1. User signs Passkey prompt → signs over `key_authorization` for a new Access Key (e.g., WebCrypto P256 key) 2. User's Access Key signs the transaction 3. Transaction includes the `key_authorization` AND the Access Key `signature` 4. Protocol validates Passkey signature on `key_authorization`, sets `transactionKey[account] = 0`, calls `AccountKeychain.authorizeKey()`, then validates Access Key signature 5. Transaction executes with Access Key's spending limits enforced via internal `verify_and_update_spending()` #### Subsequent Access Key Usage 1. User's Access Key signs the transaction (no `key_authorization` needed) 2. Protocol validates the Access Key via `validate_keychain_authorization()`, sets `transactionKey[account] = keyId` 3. Transaction executes with spending limit enforcement via internal `verify_and_update_spending()` #### Root Key Revoking an Access Key 1. User signs Passkey prompt → signs transaction calling `revokeKey(keyId)` 2. Transaction executes, marking the Access Key as inactive 3. Future transactions signed by that Access Key will be rejected import * as Card from "../../../components/Card.tsx" import LucideCircleHelp from '~icons/lucide/circle-help' import LucideClock from '~icons/lucide/clock' import LucideCoins from '~icons/lucide/coins' import LucideFileCode from '~icons/lucide/file-code' import LucideFileText from '~icons/lucide/file-text' import LucideFingerprint from '~icons/lucide/fingerprint' import LucideKey from '~icons/lucide/key' import LucideLayers from '~icons/lucide/layers' import LucideShieldCheck from '~icons/lucide/shield-check' import LucideZap from '~icons/lucide/zap' import TempoTxProperties from '../../../snippets/tempo-tx-properties.mdx' ### Tempo Transactions Tempo Transactions are a new [EIP-2718](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2718.md) transaction type, exclusively available on Tempo.

Transaction [SDKs](#integration-guides) are available for TypeScript, Rust, Go, 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). ### Integration Guides Integrating Tempo Transactions is easy and can be done quickly by a developer in multiple languages. See below for quick links to some of our guides. | Language | Source | Integration Time | | ------------------- | ------------------------------------------------------------------------------------------------------------------ | ---------------- | | **TypeScript** | [tempoxyz/tempo-ts](/sdk/typescript) | \< 1 hour | | **Rust** | [tempo-alloy](/sdk/rust) | \< 1 hour | | **Golang** | [tempo-go](https://github.com/tempoxyz/tempo-go) | \< 1 hour | | **Python** | [pytempo](https://github.com/tempoxyz/pytempo) | \< 1 hour | | **Other Languages** | Reach out to us! Specification is [here](/protocol/transactions/spec-tempo-transaction) and easy to build against. | 1-3 days | If you are an EVM smart contract developer, see the [Tempo extension for Foundry](/sdk/foundry). ### Properties ### Learn more ## Accounts \[Default Delegation (Experimental)] :::warning **Note: This feature will likely be deprecated before mainnet launch.** We recommend using the standard [Tempo Transaction](/protocol/transactions/spec-tempo-transaction) type instead if you need smart contract functionality. ::: Tempo uses a "Default Delegation" (DD) model for accounts on the Tempo blockchain. DD extends the Ethereum account model by allowing any externally owned account (EOA) to be seamlessly upgraded into a smart contract wallet, without requiring user intervention or setup. The core mechanism is that, on first use (when an EOA sends its first transaction and has never been used before), the protocol automatically delegates the account to a canonical smart contract implementation by setting its code to a special format. This enables all EOAs to immediately benefit from smart wallet features, while preserving full backward compatibility with legacy ECDSA transactions and not affecting contract accounts. Additionally, a registrar precompile is introduced to allow anyone to permissionlessly delegate an EOA to the default implementation by proving control of the address via a signature. The default implementation contract is treated as always warm for gas purposes. Tempo's DD model is fully compatible with EIP-7702. For a detailed understanding of the underlying delegation format and semantics, see the [EIP-7702 specification](https://eips.ethereum.org/EIPS/eip-7702). ### Features Default Delegation (DD) allows any EOA to be used as a smart contract wallet. It does so through two new behaviors in-protocol: 1. **Auto-delegation on first use.** When a top-level transaction originates from an address `A`, `nonce(A) == 0`, and `code(A)` is empty, the protocol **sets `code(A) = 0xEF0100 || DEFAULT_7702_IMPL`** during execution, thereby delegating `A` to the default implementation per 7702 semantics. Legacy ECDSA tx validation is unchanged. 2. **Registrar precompile.** A new precompile, **DefaultAccountRegistrar**, takes `(hash, v, r, s)`, derives an `authority` address via `ecrecover(hash, v, r, s)`, and **delegates `authority` to DEFAULT\_7702\_IMPL**. It reverts if that address is already delegated or is a contract. Additionally, **DEFAULT\_7702\_IMPL** is treated as **always warm** for gas, like a precompile. ### Motivation * Make EOAs immediately usable with a canonical smart-wallet implementation without user setup. * Preserve full backward compatibility with legacy ECDSA transactions. * Avoid changing any behavior of non-EOA addresses. * Provide a permissionless path to prove that an address is an EOA (via any prior ECDSA signature) and delegate an address to DEFAULT. ### Specification #### Notation & constants * `EMPTY` — empty byte array * `EF_PREFIX` — `0xEF0100` (EIP-7702 delegation prefix) * `DEFAULT_7702_IMPL` — **`0x7702c00000000000…`** (20-byte, fixed in genesis) * `DEFAULT_ACCOUNT_REGISTRAR` — **`0x7702ac00000000000…`** (20-byte precompile address) * “Delegated to X” ⇔ `code(account) == EF_PREFIX || X` (exact 7702 format) * “Plain EOA” ⇔ `code(account) == EMPTY` > **Out of scope:** The runtime behavior/ABI of `DEFAULT_7702_IMPL` itself (separate spec). DD only defines delegation mechanics. #### 1) Auto-delegation on first use **Trigger:** When executing a **top-level** transaction with `tx.from = A`. **Preconditions (checked at transaction start, during execution):** * `code(A) == EMPTY` * `nonce_pre(A) == 0` *(the nonce value read before nonce increment)* **State transition:** * Set `code(A) := EF_PREFIX || DEFAULT_7702_IMPL`. **Ordering / validation:** * Legacy ECDSA tx admission and verification are unchanged. * The auto-delegation check uses **`nonce_pre(A)`** (the state prior to the normal nonce increment). This makes behavior deterministic across implementations. **Scope exclusions:** * If `code(A) != EMPTY` (e.g., a contract), do **nothing**. * If `nonce_pre(A) != 0`, do **nothing**. * This rule **does not** prohibit later re-delegation or undelegation; those are governed by standard 7702 semantics (see *Redelegation & undelegation* below). **Gas:** * The gas accounting for installing `EF_PREFIX || DEFAULT_7702_IMPL` matches the cost model for 7702 delegation on Tempo (same schedule as 7702 delegation). #### 2) DefaultAccountRegistrar precompile **Address:** `DEFAULT_ACCOUNT_REGISTRAR = 0x7702ac00000000000…` **ABI (EVM call interface):** ``` delegateToDefault(bytes32 hash, uint8 v, bytes32 r, bytes32 s) ``` * **Inputs**: * `hash`: any 32-byte string, typically a hash of a message * `(v, r, s)`: ECDSA signature parameters * **Internal derivation**: * `pubkey = ecrecover(hash, v, r, s)` with standard Ethereum constraints: * Accept `v ∈ {27,28}` or `{0,1}` (treated as 27/28, respectively) * Enforce EIP-2 style “low-s” (reject high-`s`) * Reject invalid points or failed recovery * `authority = address(pubkey)` **Effects (on success):** * **Require** `code(authority) == EMPTY`. If not, **revert**. * **Require** `nonce(authority) == 0`. If not, **revert**. * **Set** `code(authority) := EF_PREFIX || DEFAULT_7702_IMPL`. **Outputs / logs:** * Returns **authority** on success. * **No event** is emitted. **Gas:** * Charge identical total gas as a 7702 delegation on Tempo (i.e., `ecrecover` cost + code-installation cost consistent with 7702). Exact numeric schedule is inherited from 7702 on Tempo. #### Always-warm rule * **`DEFAULT_7702_IMPL` is always warm** (like a precompile). For gas, treat it as if it appears in the access list at the start of every transaction. This applies chain-wide and unconditionally. #### Redelegation & undelegation * After auto-delegation or registrar delegation, an account **may**: * Re-delegate to another 7702 target, or * Delegate to **nothing** (undelegate), * …using the **exact same mechanisms and semantics as EIP-7702**. ### Rationale * **First-use default:** Ensures EOAs are smart-wallet capable immediately without wallet migration UX, while preserving legacy tx signing. * **Registrar “any message”:** The design intentionally accepts **any** valid ECDSA signature to “prove EOA control,” enabling use of existing public signatures (tweets, GitHub sigs, old onchain proofs). Replay across chains or contexts is **explicitly allowed** by design (no domain binding). * **Revert if nonce is not 0:** Allows addresses to opt out (by delegating to some other 7702 contract or to empty code) and not be forcibly redelegated. * **Revert if code is not empty:** Prevents edge cases (including repeated auto-delegation of the same account). * **Always-warm DEFAULT\_7702\_IMPL:** Smooths gas, based on the assumption that many transactions will use the default contract. ### Backwards Compatibility * **Legacy ECDSA transactions:** Unchanged validation. The only new effect is auto-delegation on the **first** tx for plain EOAs with `nonce == 0`. * **Contracts / codeful accounts:** Never auto-delegated; registrar reverts. * **7702 tooling:** Fully compatible; DD uses the **exact** 7702 delegation bytecode format and override semantics. ### Security Considerations * **Forced delegation by third parties:** Anyone can delegate an EOA via the registrar if they can produce **any** valid signature by that key (by design). This does **not** grant fund control if `DEFAULT_7702_IMPL` respects the original key, but it does change account semantics and may surprise tooling. Accepted as a trade-off. * **Signature replay & no domain binding:** Signatures from other chains or contexts can be reused. This is deliberate; downstream apps MUST NOT treat registrar delegation as consent for anything beyond delegation. * **Malleability constraints:** Enforce low-`s` and canonical `v` mapping to avoid malleability and recovery edge cases. * **CREATE/CREATE2 & contracts at EOA addresses:** DD never writes code to an account that already has code; changing a contract account’s code via registrar is disallowed (revert). Accounts created as contracts at genesis are unaffected. ### Test Cases (illustrative) 1. **First legacy tx auto-delegates** * Pre: `code(A)=EMPTY`, `nonce(A)=0` * Execute: valid legacy ECDSA tx `from=A` * Post: `code(A)=EF_PREFIX||DEFAULT_7702_IMPL`; tx body executes normally. 2. **First tx when `nonce(A)=1` (e.g., state pre-set)** * Pre: `code(A)=EMPTY`, `nonce(A)=1` * Execute: tx `from=A` * Post: No delegation performed. 3. **Address has code (contract)** * Pre: `code(A)=`, `nonce(A)=0` * Execute: tx `from=A` * Post: No delegation performed. 4. **Registrar happy path** * Input: `(hash, v, r, s)` where `ecrecover(hash, v,r,s)=A` * Pre: `code(A)=EMPTY` * Call: `DEFAULT_ACCOUNT_REGISTRAR.delegateToDefault(...)` * Post: `code(A)=EF_PREFIX||DEFAULT_7702_IMPL`; success (empty returndata). 5. **Registrar with reused public signature** * As (4), but `hash` is a keccak hash of a known public message (e.g., a tweet contents). Same success outcome. 6. **Registrar when already delegated (to default or another impl)** * Pre: `code(A)=EF_PREFIX||X` for any `X` * Call: registrar * Post: **Revert**. 7. **Registrar for contract address** * Pre: `code(A) != EMPTY` * Call: registrar with signature yielding `A` * Post: **Revert**. 8. **Invalid signature** * `ecrecover` fails or `s` is high * Post: **Revert**. 9. **Re-delegation via 7702** * Pre: `code(A)=EF_PREFIX||DEFAULT_7702_IMPL` * Action: perform a standard 7702 redelegation of `A` to `I2` (or to none) * Post: `code(A)=EF_PREFIX||I2` (or `EMPTY` if undelegated) 10. **Gas warmness** * Any tx executing a `CALL`/`DELEGATECALL` to `DEFAULT_7702_IMPL` treats it as warm without prior access-list touch. ### Reference Pseudocode (consensus-level) **Auto-delegation on tx start** ``` /// Executed at the beginning of a top-level tx, during execution: fn maybe_auto_delegate_on_first_use(sender: Address): let code = state.get_code(sender) let nonce_pre = state.get_nonce(sender) // value before nonce increment if code.length == 0 && nonce_pre == 0: state.set_code(sender, EF_PREFIX ++ DEFAULT_7702_IMPL) ``` **DefaultAccountRegistrar precompile** ``` /// At address DEFAULT_ACCOUNT_REGISTRAR /// abi: delegateToDefault(bytes hash, uint8 v, bytes32 r, bytes32 s) fn delegateToDefault(message: bytes, v: u8, r: bytes32, s: bytes32): let v_norm = if v in {27,28} then v else if v in {0,1} then (v + 27) else revert() require(is_low_s(s)) // EIP-2 style let authority = ecrecover(hash, v_norm, r, s) or revert() require(state.get_code(authority).length == 0) // must be plain EOA require(!is_7702_delegated(state.get_code(authority))) // no EF_PREFIX state.set_code(authority, EF_PREFIX ++ DEFAULT_7702_IMPL) return authority ``` **Helpers** ``` const EF_PREFIX = 0xEF0100 const DEFAULT_7702_IMPL = 0x7702c00000000000... // 20 bytes fn is_7702_delegated(code: bytes) -> bool: return code.length == 1+1+1+20 // 0xEF 0x01 0x00 || 20 bytes && code[0..3] == EF_PREFIX ``` ### Deployment / Activation * **Genesis:** Insert `DEFAULT_7702_IMPL` as an immutable, predeployed contract at `0x7702c00000000000…` with its code defined by the separate implementation spec. * **Fork rules:** DD is active from genesis on Tempo. Clients must include: * the auto-delegation state transition hook, * the DefaultAccountRegistrar precompile at `0x7702ac00000000000…`, * the always-warm treatment for `DEFAULT_7702_IMPL`. ## Tempo Transaction ### Abstract This spec introduces native protocol support for the following features, using a new Tempo transaction type: * WebAuthn/P256 signature validation - enables passkey accounts * Parallelizable nonces - allows higher tx throughput for each account * Gas sponsorship - allows apps to pay for their users' transactions * Call Batching - allows users to multicall efficiently and atomically * Scheduled Txs - allow users to specify a time window in which their tx can be executed * Access Keys - allow a sender's key to provision scoped access keys with spending limits ### Motivation Current accounts are limited to secp256k1 signatures and sequential nonces, creating UX and scalability challenges.\ Users cannot leverage modern authentication methods like passkeys, applications face throughput limitations due to sequential nonces. ### Specification #### Transaction Type A new EIP-2718 transaction type is introduced with type byte `0x76`: ```rust pub struct TempoTransaction { // Standard EIP-1559 fields chain_id: ChainId, // EIP-155 replay protection max_priority_fee_per_gas: u128, max_fee_per_gas: u128, gas_limit: u64, calls: Vec, // Batch of calls to execute atomically access_list: AccessList, // EIP-2930 access list // nonce-related fields nonce_key: U256, // 2D nonce key (0 = protocol nonce, >0 = user nonces) nonce: u64, // Current nonce value for the nonce key // Optional features fee_token: Option
, // Optional fee token preference fee_payer_signature: Option, // Sponsored transactions (secp256k1 only) valid_before: Option, // Transaction expiration timestamp valid_after: Option, // Transaction can only be included after this timestamp key_authorization: Option, // Access key authorization (optional) aa_authorization_list: Vec, // EIP-7702 style authorizations with AA signatures } // Call structure for batching pub struct Call { to: TxKind, // Can be Address or Create value: U256, input: Bytes // Calldata for the call } // Key authorization for provisioning access keys // RLP encoding: [chain_id, key_type, key_id, expiry?, limits?] pub struct KeyAuthorization { chain_id: u64, // Chain ID for replay protection (0 = valid on any chain) key_type: SignatureType, // Type of key: Secp256k1 (0), P256 (1), or WebAuthn (2) key_id: Address, // Key identifier (address derived from public key) expiry: Option, // Unix timestamp when key expires (None = never expires) limits: Option>, // TIP20 spending limits (None = unlimited spending) } // Signed key authorization (authorization + root key signature) pub struct SignedKeyAuthorization { authorization: KeyAuthorization, signature: PrimitiveSignature, // Root key's signature over keccak256(rlp(authorization)) } // TIP20 spending limits for access keys pub struct TokenLimit { token: Address, // TIP20 token address limit: U256, // Maximum spending amount for this token } ``` #### Signature Types Four signature schemes are supported. The signature type is determined by length and type identifier: ##### secp256k1 (65 bytes) ```rust pub struct Signature { r: B256, // 32 bytes s: B256, // 32 bytes v: u8 // 1 byte (recovery id) } ``` **Format**: No type identifier prefix (backward compatible). Total length: 65 bytes. **Detection**: Exactly 65 bytes with no type identifier. ##### P256 (130 bytes) ```rust pub struct P256SignatureWithPreHash { typeId: u8, // 0x01 r: B256, // 32 bytes s: B256, // 32 bytes pub_key_x: B256, // 32 bytes pub_key_y: B256, // 32 bytes pre_hash: bool // 1 byte } ``` **Format**: Type identifier `0x01` + 129 bytes of signature data. Total length: 130 bytes. The `typeId` is a wire format prefix (not a struct field) prepended during encoding. Note: Some P256 implementers (like Web Crypto) require the digests to be pre-hashed before verification. If `pre_hash` is set to `true`, then before verification: `digest = sha256(digest)`. ##### WebAuthn (Variable length, max 2KB) ```rust pub struct WebAuthnSignature { typeId: u8, // 0x02 webauthn_data: Bytes, // Variable length (authenticatorData || clientDataJSON) r: B256, // 32 bytes s: B256, // 32 bytes pub_key_x: B256, // 32 bytes pub_key_y: B256 // 32 bytes } ``` **Format**: Type identifier `0x02` + variable webauthn\_data + 128 bytes (r, s, pub\_key\_x, pub\_key\_y). Total length: variable (minimum 129 bytes, maximum 2049 bytes). The `typeId` is a wire format prefix prepended during encoding. Parse by working backwards: last 128 bytes are r, s, pub\_key\_x, pub\_key\_y. ##### Keychain (Variable length) ```rust pub struct KeychainSignature { typeId: u8, // 0x03 user_address: Address, // 20 bytes - root account address signature: PrimitiveSignature // Inner signature (Secp256k1, P256, or WebAuthn) } ``` **Format**: Type identifier `0x03` + user\_address (20 bytes) + inner signature. The `typeId` is a wire format prefix prepended during encoding. **Purpose**: Allows an access key to sign on behalf of a root account. The handler validates that `user_address` has authorized the access key in the AccountKeychain precompile. #### Address Derivation ##### secp256k1 ```solidity address(uint160(uint256(keccak256(abi.encode(x, y))))) ``` ##### P256 and WebAuthn ```solidity function deriveAddressFromP256(bytes32 pubKeyX, bytes32 pubKeyY) public pure returns (address) { // Hash bytes32 hash = keccak256(abi.encodePacked( pubKeyX, pubKeyY )); // Take last 20 bytes as address return address(uint160(uint256(hash))); } ``` #### Tempo Authorization List The `aa_authorization_list` field enables EIP-7702 style delegation with support for all three AA signature types (secp256k1, P256, and WebAuthn), not just secp256k1. ##### Structure ```rust pub struct TempoSignedAuthorization { inner: Authorization, // Standard EIP-7702 authorization signature: TempoSignature, // Can be Secp256k1, P256, or WebAuthn } ``` Each authorization in the list: * Delegates an account to a specified implementation contract * Is signed by the account's authority using any supported signature type * Follows EIP-7702 semantics for delegation and execution ##### Validation * Cannot have `Create` calls when `aa_authorization_list` is non-empty (follows EIP-7702 semantics) * Authority address is recovered from the signature and matched against the authorization #### Parallelizable Nonces * **Protocol nonce (key 0)**: Existing account nonce, incremented for regular txs, 7702 authorization, or `CREATE` * **User nonces (keys 1-N)**: Enable parallel execution with special gas schedule * **Reserved sequence keys**: Nonce sequence keys with the most significant byte `0x5b` are reserved for [sub-block transactions](/protocol/blockspace/sub-block-specification#11-sub-block-transactions). ##### Account State Changes * `nonces: mapping(uint256 => uint64)` - 2D nonce tracking **Implementation Note:** Nonces are stored in the storage of a designated precompile at address `0x4E4F4E4345000000000000000000000000000000` (ASCII hex for "NONCE"), as there is currently no clean way to extend account state in Reth. **Storage Layout at 0x4E4F4E4345:** * Storage key: `keccak256(abi.encode(account_address, nonce_key))` * Storage value: `nonce` (uint64) Note: Protocol Nonce key (0), is directly stored in the account state, just like normal transaction types. ##### Nonce Precompile The nonce precompile implements the following interface for managing 2D nonces: ```solidity /// @title INonce - Nonce Precompile Interface /// @notice Interface for managing 2D nonces as per the Tempo Transaction spec /// @dev This precompile manages user nonce keys (1-N) while protocol nonces (key 0) /// are handled directly by account state. Each account can have multiple /// independent nonce sequences identified by a nonce key. interface INonce { /// @notice Emitted when a nonce is incremented for an account and nonce key /// @param account The account whose nonce was incremented /// @param nonceKey The nonce key that was incremented /// @param newNonce The new nonce value after incrementing event NonceIncremented(address indexed account, uint256 indexed nonceKey, uint64 newNonce); /// @notice Thrown when trying to access protocol nonce (key 0) through the precompile /// @dev Protocol nonce should be accessed through account state, not this precompile error ProtocolNonceNotSupported(); /// @notice Thrown when an invalid nonce key is provided error InvalidNonceKey(); /// @notice Thrown when a nonce value would overflow error NonceOverflow(); /// @notice Get the current nonce for a specific account and nonce key /// @param account The account address /// @param nonceKey The nonce key (must be > 0, protocol nonce key 0 not supported) /// @return nonce The current nonce value function getNonce(address account, uint256 nonceKey) external view returns (uint64 nonce); } ``` ##### Precompile Implementation The precompile contract maintains a single storage mapping: ```solidity contract Nonce is INonce { /// @dev Mapping from account -> nonce key -> nonce value mapping(address => mapping(uint256 => uint64)) private nonces; } ``` ##### Gas Schedule For transactions using nonce keys: 1. **Protocol nonce (key 0)**: No additional gas cost * Uses the standard account nonce stored in account state 2. **Existing user key (nonce > 0)**: Add 5,000 gas to base cost * Rationale: Cold SLOAD (2,100) + warm SSTORE reset (2,900) 3. **New user key (nonce == 0)**: Add 22,100 gas to base cost * Rationale: Cold SLOAD (2,100) + SSTORE set for 0 → non-zero (20,000) We specify the complete gas schedule in more detail in the [gas costs section](#gas-costs) #### Transaction Validation ##### Signature Validation 1. Determine type from signature format: * 65 bytes (no type identifier) = secp256k1 * First byte `0x01` + 129 bytes = P256 (total 130 bytes) * First byte `0x02` + variable data = WebAuthn (total 129-2049 bytes) * First byte `0x03` + 20 bytes + inner signature = Keychain * Otherwise invalid 2. Apply appropriate verification: * secp256k1: Standard `ecrecover` * P256: P256 curve verification with provided public key (sha256 pre-hash if flag set) * WebAuthn: Parse clientDataJSON, verify challenge and type, then P256 verify * Keychain: Verify inner signature, then validate access key authorization via AccountKeychain precompile ##### Nonce Validation 1. Fetch sequence for given nonce key 2. Verify sequence matches transaction 3. Increment sequence ##### Fee Payer Validation (if present) 1. Verify fee payer signature (K1 only initially) 2. Recover payer address via `ecrecover` 3. Deduct fees from payer instead of sender #### Fee Payer Signature Details The Tempo Transaction Type (0x76) supports **gas sponsorship** where a third party (fee payer) can pay transaction fees on behalf of the sender. This is achieved through dual signature domains—the sender signs with transaction type byte `0x76`, while the fee payer signs with magic byte `0x78` to ensure domain separation and prevent signature reuse attacks. ##### Signing Domains ##### Sender Signature For computing the transaction hash that the sender signs: * Fields are preceded by transaction type byte `0x76` * Field 11 (`fee_token`) is encoded as empty string (`0x80`) **if and only if** `fee_payer_signature` is present. This allows the fee payer to specify the fee token. * Field 12 (`fee_payer_signature`) is encoded as: * Single byte `0x00` if fee payer signature will be present (placeholder) * Empty string `0x80` if no fee payer **Sender Signature Hash:** ```rust // When fee_payer_signature is present: sender_hash = keccak256(0x76 || rlp([ chain_id, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, calls, access_list, nonce_key, nonce, valid_before, valid_after, 0x80, // fee_token encoded as EMPTY (skipped) 0x00 // placeholder byte for fee_payer_signature ])) // When no fee_payer_signature: sender_hash = keccak256(0x76 || rlp([ chain_id, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, calls, access_list, nonce_key, nonce, valid_before, valid_after, fee_token, // fee_token is INCLUDED 0x80 // empty for no fee_payer_signature ])) ``` ##### Fee Payer Signature Only included for sponsored transactions. For computing the fee payer's signature hash: * Fields are preceded by **magic byte `0x78`** (different from transaction type `0x76`) * Field 11 (`fee_token`) is **always included** (20-byte address or `0x80` for None) * Field 12 is serialized as the **sender address** (20 bytes). This commits the fee payer to sponsoring a specific sender. **Fee Payer Signature Hash:** ```rust fee_payer_hash = keccak256(0x78 || rlp([ // Note: 0x78 magic byte chain_id, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, calls, access_list, nonce_key, nonce, valid_before, valid_after, fee_token, // fee_token ALWAYS included sender_address // 20-byte sender address key_authorization, ])) ``` ##### Key Properties 1. **Sender Flexibility**: By omitting `fee_token` from sender signature when fee payer is present, the fee payer can specify which token to use for payment without invalidating the sender's signature 2. **Fee Payer Commitment**: Fee payer's signature includes `fee_token` and `sender_address`, ensuring they agree to: * Pay for the specific sender * Use the specific fee token 3. **Domain Separation**: Different magic bytes (`0x76` vs `0x78`) prevent signature reuse attacks between sender and fee payer roles 4. **Deterministic Fee Payer**: The fee payer address is statically recoverable from the transaction via secp256k1 signature recovery ##### Validation Rules **Signature Requirements:** * Sender signature MUST be valid (secp256k1, P256, or WebAuthn depending on signature length) * If `fee_payer_signature` present: * MUST be recoverable via secp256k1 (only secp256k1 supported for fee payers) * Recovery MUST succeed, otherwise transaction is invalid * If `fee_payer_signature` absent: * Fee payer defaults to sender address (self-paid transaction) **Token Preference:** * When `fee_token` is `Some(address)`, this overrides any account/validator-level preferences * Validation ensures the token is a valid TIP-20 token with sufficient balance/liquidity * Failures reject the transaction before execution (see Token Preferences spec) **Fee Payer Resolution:** * Fee payer signature present → recovered address via `ecrecover` * Fee payer signature absent → sender address * This address is used for all fee accounting (pre-charge, refund) via TIP Fee Manager precompile ##### Transaction Flow 1. **User prepares transaction**: Sets `fee_payer_signature` to placeholder (`Some(Signature::default())`) 2. **User signs**: Computes sender hash (with fee\_token skipped) and signs 3. **Fee payer receives** user-signed transaction 4. **Fee payer verifies** user signature is valid 5. **Fee payer signs**: Computes fee payer hash (with fee\_token and sender\_address) and signs 6. **Complete transaction**: Replace placeholder with actual fee payer signature 7. **Broadcast**: Transaction is sent to network with both signatures ##### Error Cases * `fee_payer_signature` present but unrecoverable → invalid transaction * Fee payer balance insufficient for `gas_limit * max_fee_per_gas` in fee token → invalid * Any sender signature failure → invalid * Malformed RLP → invalid #### RLP Encoding The transaction is RLP encoded as follows: **Signed Transaction Envelope:** ``` 0x76 || rlp([ chain_id, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, calls, // RLP list of Call structs access_list, nonce_key, nonce, valid_before, // 0x80 if None valid_after, // 0x80 if None fee_token, // 0x80 if None fee_payer_signature, // 0x80 if None, RLP list [v, r, s] if Some aa_authorization_list, // EIP-7702 style authorization list with AA signatures key_authorization?, // Only encoded if present (backwards compatible) sender_signature // TempoSignature bytes (secp256k1, P256, WebAuthn, or Keychain) ]) ``` **Call Encoding:** ``` rlp([to, value, input]) ``` **Key Authorization Encoding:** ``` rlp([ chain_id, key_type, key_id, expiry?, // Optional trailing field (omitted or 0x80 if None) limits?, // Optional trailing field (omitted or 0x80 if None) signature // PrimitiveSignature bytes ]) ``` **Notes:** * Optional fields encode as `0x80` (EMPTY\_STRING\_CODE) when `None` * The `key_authorization` field is truly optional - when `None`, no bytes are encoded (backwards compatible) * The `calls` field is a list that must contain at least one Call (empty calls list is invalid) * The `sender_signature` field is the final field and contains the TempoSignature bytes (secp256k1, P256, WebAuthn, or Keychain) * KeyAuthorization uses RLP trailing field semantics for optional `expiry` and `limits` #### WebAuthn Signature Verification WebAuthn verification follows the [Daimo P256 verifier approach](https://github.com/daimo-eth/p256-verifier/blob/master/src/WebAuthn.sol). ##### Signature Format ``` signature = authenticatorData || clientDataJSON || r (32) || s (32) || pubKeyX (32) || pubKeyY (32) ``` Parse by working backwards: * Last 32 bytes: `pubKeyY` * Previous 32 bytes: `pubKeyX` * Previous 32 bytes: `s` * Previous 32 bytes: `r` * Remaining bytes: `authenticatorData || clientDataJSON` (requires parsing to split) ##### Authenticator Data Structure (minimum 37 bytes) ``` Bytes 0-31: rpIdHash (32 bytes) Byte 32: flags (1 byte) - Bit 0 (0x01): User Presence (UP) - must be set Bytes 33-36: signCount (4 bytes) ``` ##### Verification Steps ```python def verify_webauthn(tx_hash: bytes32, signature: bytes, require_uv: bool) -> bool: # 1. Parse signature pubKeyY = signature[-32:] pubKeyX = signature[-64:-32] s = signature[-96:-64] r = signature[-128:-96] webauthn_data = signature[:-128] # Parse authenticatorData and clientDataJSON # Minimum authenticatorData is 37 bytes # Simple approach: try to decode clientDataJSON from different split points authenticatorData, clientDataJSON = split_webauthn_data(webauthn_data) # 2. Validate authenticator data if len(authenticatorData) < 37: return False flags = authenticatorData[32] if not (flags & 0x01): # UP bit must be set return False # 3. Validate client data JSON if not contains(clientDataJSON, '"type":"webauthn.get"'): return False challenge_b64url = base64url_encode(tx_hash) challenge_property = '"challenge":"' + challenge_b64url + '"' if not contains(clientDataJSON, challenge_property): return False # 4. Compute message hash clientDataHash = sha256(clientDataJSON) messageHash = sha256(authenticatorData || clientDataHash) # 5. Verify P256 signature return p256_verify(messageHash, r, s, pubKeyX, pubKeyY) ``` ##### What We Verify * Authenticator data minimum length (37 bytes) * User Presence (UP) flag is set * `"type":"webauthn.get"` in clientDataJSON * Challenge matches tx\_hash (Base64URL encoded) * P256 signature validity ##### What We Skip * Origin verification (not applicable to blockchain) * RP ID hash validation (no central RP in decentralized context) * Signature counter (anti-cloning left to application layer) * Backup flags (account policy decision) ##### Parsing authenticatorData and clientDataJSON Since authenticatorData has variable length, finding the split point requires: 1. Check if AT flag (bit 6) is set at byte 32 2. If not set, authenticatorData is exactly 37 bytes 3. If set, need to parse CBOR credential data (complex, see implementation) 4. Everything after authenticatorData is clientDataJSON (valid UTF-8 JSON) **Simplified approach:** For TempoTransactions, wallets should send minimal authenticatorData (37 bytes, no AT/ED flags) to minimize gas costs and simplify parsing. #### Access Keys A sender can choose to authorize an Access Key to sign transactions on the sender's behalf. This is useful to enable flows where a root key (e.g. a passkey) would provision a short-lived (scoped) Access Key to be able to sign transactions on the sender's behalf without inducing another passkey prompt. More information about Access Keys can be found in the [Account Keychain Specification](./AccountKeychain). A sender can authorize a key by signing over a "key authorization" item that contains the following information: * **Chain ID** for replay protection (0 = valid on any chain) * **Key type** (Secp256k1, P256, or WebAuthn) * **Key ID** (address derived from the public key) * **Expiration** timestamp of when the key should expire (optional - None means never expires) * TIP20 token **spending limits** for the key (optional - None means unlimited spending): * Limits deplete as tokens are spent * Root key can update limits via `updateSpendingLimit()` without revoking the key * Note: Spending limits only apply to TIP20 token transfers, not ETH or other asset transfers ##### RLP Encoding **Unsigned Format:** The root key signs over the keccak256 hash of the RLP encoded `KeyAuthorization`: ``` key_authorization_digest = keccak256(rlp([chain_id, key_type, key_id, expiry?, limits?])) chain_id = u64 (0 = valid on any chain) key_type = 0 (Secp256k1) | 1 (P256) | 2 (WebAuthn) key_id = Address (derived from the public key) expiry = Option (unix timestamp, None = never expires, stored as u64::MAX in precompile) limits = Option> (None = unlimited spending) ``` **Signed Format:** The signed format (`SignedKeyAuthorization`) includes all fields with the `signature` appended: ``` signed_key_authorization = rlp([chain_id, key_type, key_id, expiry?, limits?, signature]) ``` The `signature` is a `PrimitiveSignature` (secp256k1, P256, or WebAuthn) signed by the root key. Note: `expiry` and `limits` use RLP trailing field semantics - they can be omitted entirely when None. ##### Keychain Precompile The Account Keychain precompile (deployed at address `0xAAAAAAAA00000000000000000000000000000000`) manages authorized access keys for accounts. It enables root keys to provision scoped access keys with expiry timestamps and per-TIP20 token spending limits. **See the [Account Keychain Specification](./AccountKeychain) for complete interface details, storage layout, and implementation.** ##### Protocol Behavior The protocol enforces Access Key authorization and spending limits natively. ##### Transaction Validation When a TempoTransaction is received, the protocol: 1. **Identifies the signing key** from the transaction signature * If signature is a `Keychain` variant: extracts the `keyId` (address) of the Access Key * Otherwise: treats it as the Root Key (keyId = address(0)) 2. **Validates KeyAuthorization** (if present in transaction) * The `key_authorization` field in `TempoTransaction` provisions a NEW Access Key * Root Key MUST sign: * The `key_authorization` digest: `keccak256(rlp([key_type, key_id, expiry, limits]))` * Access Key (being authorized) CAN sign the same tx which it is authorized in. * This enables "authorize and use" in a single transaction 3. **Sets transaction context** * Stores `transactionKey[account] = keyId` in protocol state * Used to enforce authorization hierarchy during execution, can also be used by DApps to see which key authorized the current tx. 4. **Validates Key Authorization** (for Access Keys) * Queries precompile: `getKey(account, keyId)` returns `KeyInfo` * Checks key is active (not revoked) * Checks expiry: `current_timestamp < expiry` (or `expiry == 0` for never expires) * Rejects transaction if validation fails ##### Authorization Hierarchy Enforcement The protocol enforces a strict two-tier hierarchy: **Root Key** (keyId = address(0)): * The account's primary key (address matches account address) * Can call ALL precompile functions * No spending limits * Can authorize, revoke, and update Access Keys **Access Keys** (keyId != address(0)): * Secondary keys authorized by Root Key * CANNOT call mutable precompile functions (`authorizeKey`, `revokeKey`, `updateSpendingLimit`) * Precompile functions check: `transactionKey[msg.sender] == 0` before allowing mutations * Subject to per-TIP20 token spending limits * Can have expiry timestamps When an Access Key attempts to call `authorizeKey()`, `revokeKey()`, or `updateSpendingLimit()`: 1. Transaction executes normally until the precompile call 2. Precompile checks `getTransactionKey()` returns non-zero (Access Key) 3. Call reverts with `UnauthorizedCaller` error 4. Entire transaction is reverted ##### Spending Limit Enforcement The protocol tracks and enforces spending limits for TIP20 token transfers: **Scope:** Only TIP20 `transfer()` and `approve()` calls are tracked * Native value transfers are NOT limited * NFT transfers are NOT limited * Other asset types are NOT limited **Tracking:** During transaction execution, when an Access Key's transaction calls TIP20 methods: 1. Protocol intercepts `transfer(to, amount)` and `approve(spender, amount)` calls 2. For `transfer`, the full `amount` is checked against the remaining limit 3. For `approve`, only **increases** in approval (new approval minus previous allowance) are checked and counted against the limit 4. Queries: `getRemainingLimit(account, keyId, token)` 5. Checks: relevant amount (transfer amount or approval increase) `<= remaining_limit` 6. If check fails: reverts with `SpendingLimitExceeded` 7. If check passes: decrements the limit by the relevant amount 8. Updates are stored in precompile state **Root Key Behavior:** Spending limit checks are skipped entirely (no limits apply) **Limit Updates:** * Limits deplete as tokens are spent * Root Key can call `updateSpendingLimit(keyId, token, newLimit)` to set new limits * Setting a new limit REPLACES the current remaining amount (does not add to it) * Limits do not reset automatically (no time-based periods) ##### Creating and Using KeyAuthorization **First-Time Authorization Flow:** 1. **Generate Access Key** ```typescript // Generate a new P256 or secp256k1 key pair const accessKey = generateKeyPair("p256"); // or "secp256k1" const keyId = deriveAddress(accessKey.publicKey); ``` 2. **Create Authorization Message** ```typescript // Define key parameters const keyAuth = { key_type: SignatureType.P256, // 1 key_id: keyId, // address derived from public key expiry: timestamp + 86400, // 24 hours from now (or 0 for never) limits: [ { token: USDC_ADDRESS, amount: 1000000000 }, // 1000 USDC (6 decimals) { token: DAI_ADDRESS, amount: 500000000000000000000 } // 500 DAI (18 decimals) ] }; // Compute digest: keccak256(rlp([key_type, key_id, expiry, limits])) const authDigest = computeAuthorizationDigest(keyAuth); ``` 3. **Root Key Signs Authorization** ```typescript // Sign with Root Key (e.g., passkey prompt) const rootSignature = await signWithRootKey(authDigest); ``` 4. **Build TempoTransaction** ```typescript const tx = { chain_id: 1, nonce: await getNonce(account), nonce_key: 0, calls: [{ to: recipient, value: 0, input: "0x" }], gas_limit: 200000, max_fee_per_gas: 1000000000, max_priority_fee_per_gas: 1000000000, key_authorization: { key_type: keyAuth.key_type, expiry: keyAuth.expiry, limits: keyAuth.limits, key_id: keyAuth.key_id, signature: rootSignature // Root Key's signature on authDigest }, // ... other fields }; ``` 5. **Access Key Signs Transaction** ```typescript // Sign transaction with the NEW Access Key being authorized const txHash = computeTxSignatureHash(tx); const accessSignature = await signWithAccessKey(txHash, accessKey); // Wrap in Keychain signature const finalSignature = { Keychain: { user_address: account, signature: { P256: accessSignature } // or Secp256k1 } }; ``` 6. **Submit Transaction** * Protocol validates Root Key signed the `key_authorization` * Protocol calls `authorizeKey()` on the precompile to store the key * Protocol validates Access Key signature on transaction * Transaction executes with spending limits enforced **Subsequent Usage (Key Already Authorized):** ```typescript // Access Key is already authorized, just sign transactions directly const tx = { chain_id: 1, nonce: await getNonce(account), calls: [{ to: recipient, value: 0, input: calldata }], key_authorization: null, // No authorization needed // ... other fields }; const txHash = computeTxSignatureHash(tx); const accessSignature = await signWithAccessKey(txHash, accessKey); const finalSignature = { Keychain: { user_address: account, signature: { P256: accessSignature } } }; // Submit - protocol validates key is authorized and not expired ``` ##### Key Management Operations **Revoking an Access Key:** ```typescript // Must be signed by Root Key const tx = { chain_id: 1, nonce: await getNonce(account), calls: [{ to: ACCOUNT_KEYCHAIN_ADDRESS, value: 0, input: encodeCall("revokeKey", [keyId]) }], // ... sign with Root Key }; ``` **Updating Spending Limits:** ```typescript // Must be signed by Root Key const tx = { chain_id: 1, nonce: await getNonce(account), calls: [{ to: ACCOUNT_KEYCHAIN_ADDRESS, value: 0, input: encodeCall("updateSpendingLimit", [ keyId, USDC_ADDRESS, 2000000000 // New limit: 2000 USDC ]) }], // ... sign with Root Key }; ``` **Note:** After updating, the remaining limit is set to the `newLimit` value, not added to the current remaining amount. ##### Querying Key State Applications can query key information and spending limits: ```typescript // Check if key is authorized and get info const keyInfo = await precompile.getKey(account, keyId); // Returns: { signatureType, keyId, expiry } // Check remaining spending limit for a token const remaining = await precompile.getRemainingLimit(account, keyId, USDC_ADDRESS); // Returns: uint256 amount remaining // Get which key signed current transaction (callable from contracts) const currentKey = await precompile.getTransactionKey(); // Returns: address (0x0 for Root Key, keyId for Access Key) ``` ### Rationale #### Signature Type Detection by Length Using signature length for type detection avoids adding explicit type fields while maintaining deterministic parsing. The chosen lengths (65, 129, variable) are naturally distinct. #### Linear Gas Scaling for Nonce Keys The progressive pricing model prevents state bloat while keeping initial keys affordable. The 20,000 gas increment approximates the long-term state cost of maintaining each additional nonce mapping. #### No Nonce Expiry Avoiding expiry simplifies the protocol and prevents edge cases where in-flight transactions become invalid. Wallets handle nonce key allocation to prevent conflicts. #### Backwards Compatibility This spec introduces a new transaction type and does not modify existing transaction processing. Legacy transactions continue to work unchanged. We special case `nonce key = 0` (also referred to as the protocol nonce key) to maintain compatibility with existing nonce behavior. ### Gas Costs #### Signature Verification Gas Schedule Different signature types incur different base transaction costs to reflect their computational complexity: | Signature Type | Base Gas Cost | Calculation | Rationale | | -------------- | --------------------------- | ------------------------------------------ | ------------------------------------------------------------------------- | | **secp256k1** | 21,000 | Standard | Includes 3,000 gas for ecrecover precompile | | **P256** | 26,000 | 21,000 + 5,000 | Base 21k + additional 5k for P256 verification | | **WebAuthn** | 26,000 + variable data cost | 26,000 + (calldata gas for clientDataJSON) | Base P256 cost plus variable cost for clientDataJSON based on size | | **Keychain** | Inner signature + 3,000 | primitive\_sig\_cost + 3,000 | Inner signature cost + key validation overhead (2,100 SLOAD + 900 buffer) | **Rationale:** * The base 21,000 gas for standard transactions already includes the cost of secp256k1 signature verification via ecrecover (3,000 gas) * [EIP 7951](https://eips.ethereum.org/EIPS/eip-7951) sets P256 verification cost at 6,900 gas. We add 1,100 gas to account for the additional 65 bytes of signature size (129 bytes total vs 64 bytes for secp256k1), giving 8,000 gas total. Since the base 21k already includes 3,000 gas for ecrecover (which P256 doesn't use), the net additional cost is 8,000 - 3,000 = **5,000 gas**. * WebAuthn signatures require additional computation to parse and validate the clientDataJSON structure. We cap the total signature size at 2kb. The signature is also charged using the same gas schedule as calldata (16 gas per non-zero byte, 4 gas per zero byte) to prevent the use of this signature space from spam. * Keychain signatures wrap a primitive signature and are used by access keys. They add 3,000 gas to cover key validation during transaction validation (cold SLOAD to verify key exists + processing overhead). * Individual per-signature-type gas costs allow us to add more advanced verification methods in the future like multisigs, which could have dynamic gas pricing. #### Nonce Key Gas Schedule Transactions using parallelizable nonces incur additional costs based on the nonce key usage pattern: ##### Case 1: Protocol Nonce (Key 0) * **Additional Cost:** 0 gas * **Total:** 21,000 gas (base transaction cost) * **Rationale:** Maintains backward compatibility with existing transaction flow ##### Case 2: Existing User Nonce Key (nonce > 0) * **Additional Cost:** 5,000 gas * **Total:** 26,000 gas * **Rationale:** Cold SLOAD (2,100) + warm SSTORE reset (2,900) for incrementing an existing nonce ##### Case 3: New User Nonce Key (nonce == 0) * **Additional Cost:** 22,100 gas * **Total:** 43,100 gas * **Rationale:** Cold SLOAD (2,100) + SSTORE set (20,000) for writing to a new storage slot **Rationale for Fixed Pricing:** 1. **Simplicity:** Fixed costs based on actual EVM storage operations are straightforward to reason about 2. **Storage Pattern Alignment:** Costs directly mirror EVM cold SSTORE costs for new vs existing slots 3. **State Growth:** Creating new nonce keys incurs the higher cost naturally through SSTORE set pricing #### Key Authorization Gas Schedule When a transaction includes a `key_authorization` field to provision a new access key, additional intrinsic gas is charged to cover signature verification and storage operations. This gas is charged **before execution** as part of the transaction's intrinsic gas cost. ##### Gas Components | Component | Gas Cost | Notes | | -------------------------- | -------------------------------------------------------------- | ------------------------------------------------------------ | | **Signature verification** | 3,000 (secp256k1) / 8,000 (P256) / 8,000 + calldata (WebAuthn) | Verifying the root key's signature on the authorization | | **Key storage** | 22,000 | Cold SSTORE to store new key (0→non-zero) | | **Overhead buffer** | 5,000 | Buffer for event emission, storage reads, and other overhead | | **Per spending limit** | 22,000 each | Cold SSTORE per token limit (0→non-zero) | **Signature verification rationale:** KeyAuthorization requires an *additional* signature verification beyond the transaction signature. Unlike the transaction signature (where ecrecover cost is included in the base 21k), KeyAuthorization must pay the full verification cost: * **secp256k1**: 3,000 gas (ecrecover precompile cost) * **P256**: 8,000 gas (6,900 from EIP-7951 + 1,100 for signature size). Note: the transaction signature schedule charges only 5,000 additional gas for P256 because it subtracts the 3,000 ecrecover "savings" already in base 21k. KeyAuthorization pays the full 8,000. * **WebAuthn**: 8,000 + calldata gas for webauthn\_data ##### Gas Formula ``` KEY_AUTH_BASE_GAS = 30,000 # For secp256k1 signature (3,000 + 22,000 + 5,000) KEY_AUTH_BASE_GAS = 35,000 # For P256 signature (5,000 + 3,000 + 22,000 + 5,000) KEY_AUTH_BASE_GAS = 35,000 + webauthn_calldata_gas # For WebAuthn signature PER_LIMIT_GAS = 22,000 # Per spending limit entry total_key_auth_gas = KEY_AUTH_BASE_GAS + (num_limits * PER_LIMIT_GAS) ``` ##### Examples | Configuration | Gas Cost | Calculation | | -------------------- | -------- | --------------------------- | | secp256k1, no limits | 30,000 | Base only | | secp256k1, 1 limit | 52,000 | 30,000 + 22,000 | | secp256k1, 3 limits | 96,000 | 30,000 + (3 × 22,000) | | P256, no limits | 35,000 | Base with P256 verification | | P256, 2 limits | 79,000 | 35,000 + (2 × 22,000) | ##### Rationale 1. **Pre-execution charging**: KeyAuthorization is validated and executed during transaction validation (before the EVM runs), so its gas must be included in intrinsic gas 2. **Storage cost alignment**: The 22,000 gas per storage slot approximates EVM cold SSTORE costs for new slots 3. **DoS prevention**: Progressive cost based on number of limits prevents abuse through excessive limit creation #### Reference Pseudocode ```python def calculate_calldata_gas(data: bytes) -> uint256: """ Calculate gas cost for calldata based on zero and non-zero bytes Args: data: bytes to calculate cost for Returns: gas_cost: uint256 """ CALLDATA_ZERO_BYTE_GAS = 4 CALLDATA_NONZERO_BYTE_GAS = 16 gas = 0 for byte in data: if byte == 0: gas += CALLDATA_ZERO_BYTE_GAS else: gas += CALLDATA_NONZERO_BYTE_GAS return gas def calculate_signature_verification_gas(signature: PrimitiveSignature) -> uint256: """ Calculate gas cost for verifying a primitive signature. Returns the ADDITIONAL gas beyond the base 21k transaction cost. - secp256k1: 0 (already included in base 21k via ecrecover) - P256: 5,000 (8,000 full cost - 3,000 ecrecover already in base 21k) - WebAuthn: 5,000 + calldata gas for webauthn_data """ # P256 full verification cost is 8,000 (6,900 from EIP-7951 + 1,100 for signature size) # But base 21k already includes 3,000 for ecrecover, so additional cost is 5,000 P256_ADDITIONAL_GAS = 5_000 if signature.type == Secp256k1: return 0 # Already included in base 21k elif signature.type == P256: return P256_ADDITIONAL_GAS elif signature.type == WebAuthn: webauthn_data_gas = calculate_calldata_gas(signature.webauthn_data) return P256_ADDITIONAL_GAS + webauthn_data_gas else: revert("Invalid signature type") def calculate_key_authorization_gas(key_auth: SignedKeyAuthorization) -> uint256: """ Calculate the intrinsic gas cost for a KeyAuthorization. This is charged BEFORE execution as part of transaction validation. Args: key_auth: SignedKeyAuthorization with fields: - signature: PrimitiveSignature (root key's signature) - limits: Optional[List[TokenLimit]] Returns: gas_cost: uint256 """ # Constants - KeyAuthorization pays FULL signature verification costs # (not the "additional" costs used for transaction signatures) ECRECOVER_GAS = 3_000 # Full ecrecover cost P256_FULL_GAS = 8_000 # Full P256 cost (6,900 + 1,100) COLD_SSTORE_SET_GAS = 22_000 # Storage cost for new slot OVERHEAD_BUFFER = 5_000 # Buffer for event emission, storage reads, etc. gas = 0 # Step 1: Signature verification cost (full cost, not additional) if key_auth.signature.type == Secp256k1: gas += ECRECOVER_GAS # 3,000 elif key_auth.signature.type == P256: gas += P256_FULL_GAS # 8,000 elif key_auth.signature.type == WebAuthn: webauthn_data_gas = calculate_calldata_gas(key_auth.signature.webauthn_data) gas += P256_FULL_GAS + webauthn_data_gas # 8,000 + calldata # Step 2: Key storage gas += COLD_SSTORE_SET_GAS # 22,000 - store new key (0 → non-zero) # Step 3: Overhead buffer gas += OVERHEAD_BUFFER # 5,000 # Step 4: Per-limit storage cost num_limits = len(key_auth.limits) if key_auth.limits else 0 gas += num_limits * COLD_SSTORE_SET_GAS # 22,000 per limit return gas def calculate_tempo_tx_base_gas(tx): """ Calculate the base gas cost for a TempoTransaction Args: tx: TempoTransaction object with fields: - signature: TempoSignature (variable length) - nonce_key: uint192 - nonce: uint64 - sender_address: address - key_authorization: Optional[SignedKeyAuthorization] Returns: total_gas: uint256 """ # Constants BASE_TX_GAS = 21_000 EXISTING_NONCE_KEY_GAS = 5_000 # Cold SLOAD (2,100) + warm SSTORE reset (2,900) NEW_NONCE_KEY_GAS = 22_100 # Cold SLOAD (2,100) + SSTORE set (20,000) KEYCHAIN_VALIDATION_GAS = 3_000 # 2,100 SLOAD + 900 processing buffer # Step 1: Determine signature verification cost # For Keychain signatures, use the inner primitive signature if tx.signature.type == Keychain: inner_sig = tx.signature.inner_signature else: inner_sig = tx.signature signature_gas = BASE_TX_GAS + calculate_signature_verification_gas(inner_sig) # Add keychain validation overhead if using access key if tx.signature.type == Keychain: signature_gas += KEYCHAIN_VALIDATION_GAS # Step 2: Calculate nonce key cost if tx.nonce_key == 0: # Protocol nonce (backward compatible) nonce_gas = 0 else: # User nonce key current_nonce = get_nonce(tx.sender_address, tx.nonce_key) if current_nonce > 0: # Existing nonce key - cold SLOAD + warm SSTORE reset nonce_gas = EXISTING_NONCE_KEY_GAS else: # New nonce key - cold SLOAD + SSTORE set nonce_gas = NEW_NONCE_KEY_GAS # Step 3: Calculate key authorization cost (if present) if tx.key_authorization is not None: key_auth_gas = calculate_key_authorization_gas(tx.key_authorization) else: key_auth_gas = 0 # Step 4: Calculate total base gas total_gas = signature_gas + nonce_gas + key_auth_gas return total_gas ``` ### Security Considerations #### Mempool DOS Protection Transaction pools perform pre-execution validation checks before accepting transactions. These checks are performed for free by the nodes, making them potential DOS vectors. The three primary validation checks are: 1. **Signature verification** - Must be valid 2. **Nonce verification** - Must match current account nonce 3. **Balance check** - Account must have sufficient balance to pay for transaction This transaction type impacts all three areas: ##### Signature Verification Impact * **P256 signatures**: Fixed computational cost similar to ecrecover. * **WebAuthn signatures**: Variable cost due to clientDataJSON parsing, but **capped at 2KB total signature size** to prevent abuse * **Mitigation**: All signature types have bounded computational costs that are in the same ballpark as standard ecrecover. ##### Nonce Verification Impact * **2D nonce lookup**: Requires additional storage read from nonce precompile * **Cost**: Equivalent to a cold SLOAD (\~2,100 gas worth of free computation) * **Mitigation**: Cost is bounded to a manageable value. ##### Fee Payer Impact * **Additional account read**: When fee payer is specified, must fetch fee payer's account to verify balance * **Cost**: Effectively doubles the free account access work for sponsored transactions * **Mitigation**: Cost is still bounded to a single additional account read. ##### Comparison to Ethereum The introduction of 7702 delegated accounts already created complex cross-transaction dependencies in the mempool, which prevents any static pool checks from being useful. Because a single transaction can invalidate multiple others by spending balances of multiple accounts **Assessment:** While this transaction type introduces additional pre-execution validation costs, all costs are bounded to reasonable limits. The mempool complexity issues around cross-transaction dependencies already exist in Ethereum due to 7702 and accounts with code, making static validation inherently difficult. So the incremental cost from this transaction type is acceptable given these existing constraints. import LucideFileText from '~icons/lucide/file-text' import LucideShield from '~icons/lucide/shield' import LucideUsers from '~icons/lucide/users' import LucideLock from '~icons/lucide/lock' import LucideSettings from '~icons/lucide/settings' import * as Card from "../../../components/Card.tsx" ## TIP-403 Policy Registry ### What is TIP-403? TIP-403 is Tempo's policy registry system that enables TIP-20 tokens to enforce access control. Instead of each token implementing its own logic, TIP-403 provides a registry where policies can be created once and shared across multiple tokens. ### Links ## Overview ### Abstract TIP-403 provides a policy registry system that allows TIP-20 tokens to inherit access control and compliance policies. The registry supports two types of policies (whitelist and blacklist) and includes special built-in policies for common use cases. Policies can be shared across multiple tokens, enabling consistent compliance enforcement. ### Motivation Token issuers often need to implement compliance policies such as KYC/AML requirements, access control, and risk management. Without a standardized system, each token would need to implement its own policy logic, making policy management more difficult and inconsistent across the ecosystem. TIP-403 addresses this by providing a centralized registry that tokens can reference for authorization decisions. This enables consistent policy enforcement across multiple tokens and reduces implementation complexity for token issuers. *** ## Specification The TIP-403 registry stores policies that TIP-20 tokens check against on any token transfer. Policies are associated with a unique `policyId`, can either be a blacklist or a whitelist policy, and contain a list of addresses. This list of addresses can be updated by the policy `admin`. The TIP403Registry is deployed at address `0x403c000000000000000000000000000000000000`. ### Built-in Policies Custom policies start with `policyId = 2`. The registry reserves the first two ids for built-in policies: * `policyId = 0` is the `always-reject` policy and rejects all token transfers * `policyId = 1` is the `always-allow` policy and allows all token transfers The `policyIdCounter` starts at `2` and increments with each new policy creation. ### Policy Types TIP-403 supports two policy types: * **Whitelist Policies:** Only addresses in the whitelist can transfer tokens. All other addresses are blocked * **Blacklist Policies:** Addresses in the blacklist are blocked from transferring tokens. All other addresses can transfer ### Storage and State The registry maintains the following state: * `policyIdCounter`: Starts at `2`, increments with each new policy creation. Returns the next policy ID that will be assigned. * `policyData`: Mapping from `policyId` to `PolicyData` struct containing policy type and admin address. * `policySet`: Internal mapping from `policyId` to address to boolean, tracking which addresses are in each policy's set. ### Interface Definition The complete TIP403Registry interface is defined below: ```solidity interface ITIP403Registry { // ========================================================================= // Types and Enums // ========================================================================= enum PolicyType { WHITELIST, BLACKLIST } struct PolicyData { PolicyType policyType; address admin; } // ========================================================================= // Policy Creation // ========================================================================= /// @notice Creates a new policy with the specified admin and type /// @param admin Address that can modify this policy /// @param policyType Type of policy (whitelist or blacklist) /// @return newPolicyId ID of the newly created policy /// @dev Anyone can create a policy. The creator specifies an admin address that can modify the policy. /// Assigns the next available policyId starting from 2, sets the policy admin, and initializes an empty policy set. /// Emits PolicyCreated and PolicyAdminUpdated events. function createPolicy( address admin, PolicyType policyType ) external returns (uint64 newPolicyId); /// @notice Creates a policy and immediately adds the provided accounts to the policy set /// @param admin Address that can modify this policy /// @param policyType Type of policy (whitelist or blacklist) /// @param accounts Initial addresses to add to the policy /// @return newPolicyId ID of the newly created policy /// @dev For whitelist policies: adds accounts as authorized. For blacklist policies: adds accounts as restricted. /// Emits PolicyCreated, PolicyAdminUpdated, and either WhitelistUpdated or BlacklistUpdated events for each account added. function createPolicyWithAccounts( address admin, PolicyType policyType, address[] calldata accounts ) external returns (uint64 newPolicyId); // ========================================================================= // Policy Administration // ========================================================================= /// @notice Transfers admin rights to another address /// @param policyId ID of the policy to update /// @param admin New admin address for the policy /// @dev Only the current policy admin can call this function. The new admin immediately gains full control over the policy. /// Emits PolicyAdminUpdated event. function setPolicyAdmin(uint64 policyId, address admin) external; /// @notice Adds or removes addresses from a whitelist policy /// @param policyId ID of the whitelist policy /// @param account Address to add or remove /// @param allowed true to allow, false to block /// @dev Only the policy admin can call this function. allowed = true adds the address to the whitelist (authorized to transfer). /// allowed = false removes the address from the whitelist (not authorized). Reverts if policy is not a whitelist. /// Emits WhitelistUpdated event. function modifyPolicyWhitelist( uint64 policyId, address account, bool allowed ) external; /// @notice Adds or removes addresses from a blacklist policy /// @param policyId ID of the blacklist policy /// @param account Address to add or remove /// @param restricted true to block, false to allow /// @dev Only the policy admin can call this function. restricted = true adds the address to the blacklist (not authorized to transfer). /// restricted = false removes the address from the blacklist (authorized). Reverts if policy is not a blacklist. /// Emits BlacklistUpdated event. function modifyPolicyBlacklist( uint64 policyId, address account, bool restricted ) external; // ========================================================================= // Policy Queries // ========================================================================= /// @notice Returns whether the provided user is allowed to transfer tokens under the provided policy ID /// @param policyId Policy ID to check against /// @param user Address to check /// @return True if authorized, false if blocked /// @dev For policyId = 0 (always-reject): Always returns false /// For policyId = 1 (always-allow): Always returns true /// For whitelist policies: Returns true if address is in the whitelist, false otherwise /// For blacklist policies: Returns true if address is NOT in the blacklist, false if it is function isAuthorized(uint64 policyId, address user) external view returns (bool); /// @notice Returns the next policy ID that will be assigned to a newly created policy /// @return The current policyIdCounter value /// @dev Starts at 2 and increments with each policy creation function policyIdCounter() external view returns (uint64); /// @notice Returns whether a policy exists (available from Allegretto hardfork) /// @param policyId ID of the policy to check /// @return True if the policy exists, false otherwise /// @dev Policy IDs 0 and 1 (built-in policies) always exist. For custom policies (ID >= 2), /// checks if the policy ID is within the range of created policies based on policyIdCounter. function policyExists(uint64 policyId) external view returns (bool); /// @notice Returns the policy type and admin address of the policy associated with the provided policy ID /// @param policyId ID of the policy to query /// @return policyType Type of the policy (whitelist or blacklist) /// @return admin Admin address of the policy function policyData(uint64 policyId) external view returns (PolicyType policyType, address admin); // ========================================================================= // Events // ========================================================================= /// @notice Emitted when a new policy is created /// @param policyId ID of the newly created policy /// @param updater Address that created the policy /// @param policyType Type of policy created event PolicyCreated( uint64 indexed policyId, address indexed updater, PolicyType policyType ); /// @notice Emitted when a policy's admin is changed /// @param policyId ID of the policy /// @param updater Address that made the change /// @param admin New admin address event PolicyAdminUpdated( uint64 indexed policyId, address indexed updater, address indexed admin ); /// @notice Emitted when an address is added to or removed from a whitelist policy /// @param policyId ID of the whitelist policy /// @param updater Address that made the change /// @param account Account that was added or removed /// @param allowed true if added, false if removed event WhitelistUpdated( uint64 indexed policyId, address indexed updater, address indexed account, bool allowed ); /// @notice Emitted when an address is added to or removed from a blacklist policy /// @param policyId ID of the blacklist policy /// @param updater Address that made the change /// @param account Account that was added or removed /// @param restricted true if blocked, false if unblocked event BlacklistUpdated( uint64 indexed policyId, address indexed updater, address indexed account, bool restricted ); // ========================================================================= // Errors // ========================================================================= /// @notice Caller is not the policy admin error Unauthorized(); /// @notice Wrong policy type for the operation error IncompatiblePolicyType(); } ``` ### Usage with TIP-20 Tokens TIP-20 tokens store the current TIP403 registry policy ID they adhere to in their storage. On any token transfer, they perform a TIP-403 policy check by calling `isAuthorized()` for both sender and recipient addresses. The policy to use for the token can only be set by the admin of the token. **Default Policy:** New tokens start with `transferPolicyId = 1` (always-allow policy). **Policy Changes:** When a token's transfer policy is changed via `changeTransferPolicyId()`, all future transfers are immediately subject to the new policy. #### Example Usage Creating and setting a policy: ```solidity address admin = address(this); // Create policy with registry uint64 policyId = tip403Registry.createPolicy(admin, PolicyType.WHITELIST); // Add authorized addresses to whitelist tip403Registry.modifyPolicyWhitelist(policyId, authorizedUser, true); // Set policy on the token token.changeTransferPolicyId(policyId); ``` ### Authorization Logic The `isAuthorized()` function implements the following logic: ```solidity if (policyId < 2) { return policyId == 1; // 0 = reject, 1 = allow } PolicyData memory data = policyData[policyId]; return data.policyType == PolicyType.WHITELIST ? policySet[policyId][user] : !policySet[policyId][user]; ``` ## Invariants * When policyId = 0, all authorization checks must return false for every address. * When policyId = 1, all authorization checks must return true for every address. * Only the policy’s current admin may update the admin address for that policy. import LucideFileText from '~icons/lucide/file-text' import LucideGift from '~icons/lucide/gift' import LucideTrendingUp from '~icons/lucide/trending-up' import * as Card from "../../../components/Card.tsx" ## TIP-20 Rewards TIP-20 Rewards is a built-in mechanism that allows for efficient distribution of rewards to opted-in token holders proportional to their holdings, while maintaining low gas costs at scale and complying with [TIP-403 transfer policies](/protocol/tip403/spec). Traditional reward mechanisms require tokens to be staked in separate contracts, which fragments user holdings and adds complexity to wallet implementations. TIP-20 Rewards solves this by: * **Built-in Distribution**: Rewards are integrated directly into the token contract, no separate staking required * **Opt-in Participation**: Users choose whether to participate by setting a reward recipient * **Proportional Distribution**: Rewards accrue based on token holdings automatically * **Instant Rewards**: Distribute rewards immediately to opted-in holders * **Efficient at Scale**: Constant-time updates regardless of the number of token holders * **Policy Compliant**: All reward transfers respect TIP-403 transfer policies Note: Time-based streaming rewards are planned for a future upgrade. Until then, attempting to create a timed distribution will revert (calling `startReward(amount, seconds_)` with `seconds_ > 0` reverts with `ScheduledRewardsDisabled()`). ### Links ## TIP-20 Rewards Distribution ### Abstract An opt-in, scalable, pro-rata reward distribution mechanism built into TIP-20 tokens. The system uses a "reward-per-token" accumulator pattern to distribute rewards proportionally to opted-in holders without requiring staking or per-holder iteration. Rewards are distributed instantly; time-based streaming distributions are planned for a future upgrade. ### Motivation Many applications require pro-rata distribution of tokens to existing holders (incentive programs, deterministic inflation, staking rewards). Building this into TIP-20 allows efficient distribution without forcing users to stake tokens elsewhere or requiring distributors to loop over all holders. ### Specification The rewards mechanism allows anyone to distribute token rewards to opted-in holders proportionally based on holdings. Users must opt in to receiving rewards and may delegate rewards to a recipient address. ### TIP-20 Rewards Functions These functions are part of the [ITIP20](/protocol/tip20/spec) interface: ```solidity /// @notice Distribute rewards to opted-in token holders /// @param amount Amount of tokens to distribute /// @param seconds_ Must be 0; passing > 0 reverts with ScheduledRewardsDisabled() /// @return Always returns 0 for an instant distribution function startReward(uint256 amount, uint32 seconds_) external returns (uint64); /// @notice Set the reward recipient for the caller (opt in/out of rewards) /// @param newRewardRecipient Recipient address (address(0) to opt out) function setRewardRecipient(address newRewardRecipient) external; /// @notice Claim all pending rewards for the caller /// @return maxAmount Amount claimed function claimRewards() external returns (uint256 maxAmount); /// @notice Get user reward info function userRewardInfo(address user) external view returns ( address rewardRecipient, uint256 rewardPerToken, uint256 rewardBalance ); // State variables function globalRewardPerToken() external view returns (uint256); function optedInSupply() external view returns (uint128); ``` ### Accrual Mechanism The system uses an accumulator pattern: * `globalRewardPerToken`: Cumulative rewards per token (scaled by 1e18) * Each user stores a `rewardPerToken` snapshot; pending rewards = `(globalRewardPerToken - snapshot) * balance` Instant distributions (`seconds_ == 0`) add directly to `globalRewardPerToken` as: `deltaRPT = amount * 1e18 / optedInSupply`. ### Opt-In Model Users must call `setRewardRecipient(recipient)` to opt in. When opted in: * User's balance contributes to `optedInSupply` * Rewards accrue to `rewardBalance` on balance-changing operations * Users can delegate rewards to another address Setting recipient to `address(0)` opts out. ### TIP-403 Integration All token movements must pass TIP-403 policy checks: * `startReward`: Validates funder authorization * `setRewardRecipient`: Validates holder and recipient * `claimRewards`: Validates msg.sender ### Invariants * `globalRewardPerToken` must monotonically increase * `optedInSupply` must equal the sum of balances for all opted-in users * All token movements must comply with TIP-403 policies import { Callout } from 'vocs/components' import LucideCircleHelp from '~icons/lucide/circle-help' import LucideRoute from '~icons/lucide/route' import LucideBanknote from '~icons/lucide/banknote' import LucideWallet from '~icons/lucide/wallet' import LucideShield from '~icons/lucide/shield' import LucideFileCheck from '~icons/lucide/file-check' import LucideSettings from '~icons/lucide/settings' import LucideGift from '~icons/lucide/gift' import LucideMessageSquare from '~icons/lucide/message-square' import LucideFileText from '~icons/lucide/file-text' import LucideSend from '~icons/lucide/send' import LucideRocket from '~icons/lucide/rocket' import LucideArrowLeftRight from '~icons/lucide/arrow-left-right' import * as Card from "../../../components/Card.tsx" ## TIP-20 Token Standard TIP-20 is Tempo's native token standard for stablecoins and payment tokens. TIP-20 is designed for stablecoin payments, and is the foundation for many token-related functions on Tempo including transaction fees, payment lanes, DEX quote tokens, and optimized routing for DEX liquidity on Tempo.

This means ERC-20 functions works with TIP-20 out of the box.

All TIP-20 tokens are created by interacting with the [TIP-20 Factory contract](/protocol/tip20/spec#tip20factory), calling the `createToken` function. If you're issuing a stablecoin on Tempo, we **strongly recommend** using the TIP-20 standard. Learn more about the benefits, or follow the guide on issuance [here](/guide/issuance). ### Benefits & Features of TIP-20 Tokens Below are some of the key benefits and features of TIP-20 tokens: #### Payments #### Exchange #### Compliance & Controls #### Pay Fees in Any Stablecoin Any USD-denominated TIP-20 token can be used to pay transaction fees on Tempo. The [Fee AMM](/protocol/fees/spec-fee-amm) automatically converts your token to the validator's preferred fee token, eliminating the need for users to hold a separate gas token. This feature works natively: no additional infrastructure or integration required. Full specification of this feature can be found in the [Payment Lanes Specification](/protocol/blockspace/payment-lane-specification). #### Get Predictable Payment Fees Tempo has dedicated payment lanes: reserved blockspace for payment TIP-20 transactions that other applications cannot consume. Even if there are extremely popular applications on the chain competing for blockspace, payroll runs or customer disbursements execute predictably. Learn more about the [payments lane](/protocol/blockspace/payment-lane-specification). #### Role-Based Access Control (RBAC) TIP-20 includes a built-in [RBAC system](/protocol/tip403/spec#tip-20-token-roles) that separates administrative responsibilities: * **ISSUER\_ROLE**: Grants permission to mint and burn tokens, enabling controlled token issuance * **PAUSE\_ROLE** / **UNPAUSE\_ROLE**: Allows pausing and unpausing token transfers for emergency controls * **BURN\_BLOCKED\_ROLE**: Permits burning tokens from blocked addresses (e.g., for compliance actions) Roles can be granted, revoked, and delegated without custom contract changes. This enables issuers to separate operational roles (e.g., who can mint) from administrative roles (e.g., who can pause). Learn more in the [TIP-20 specification](/protocol/tip20/spec#roles). #### TIP-403 Transfer Policies TIP-20 tokens integrate with the [TIP-403 Policy Registry](/protocol/tip403/overview) to enforce compliance policies. Each token can reference a policy that controls who can send and receive tokens: * **Whitelist policies**: Only addresses in the whitelist can transfer tokens * **Blacklist policies**: Addresses in the blacklist are blocked from transferring tokens Policies can be shared across multiple tokens, enabling consistent compliance enforcement across your token ecosystem. See the [TIP-403 specification](/protocol/tip403/spec) for details. #### Operational Controls TIP-20 tokens can set **supply caps**, which allow you to set a maximum token supply to control issuance. TIP-20 tokens also have **pause/unpause** commands, which provide emergency controls to halt transfers when needed. #### Transfer Memos **Transfer memos** enable you to attach 32-byte memos to transfers for payment references, invoice IDs, or transaction notes. #### Reward Distribution TIP-20 supports an opt-in [reward distribution system](/protocol/tip20-rewards/overview) that allows issuers to distribute rewards to token holders. Rewards can be claimed by holders or automatically forwarded to designated recipient addresses. #### Currency Declaration A TIP-20 token can declare a currency identifier (e.g., `"USD"`, `"EUR"`) that identifies the real-world asset backing the token. This enables proper routing and pricing in Tempo's [stablecoin exchange](/protocol/exchange). USD-denominated TIP-20 tokens can be used to pay transaction fees and serve as quote tokens in the DEX. #### DEX Quote Tokens TIP-20 tokens can serve as quote tokens in Tempo's decentralized exchange (DEX). When creating trading pairs on the [stablecoin exchange](/protocol/exchange), TIP-20 tokens function as the quote currency against which other tokens are priced and traded. This enables efficient stablecoin-to-stablecoin trading and provides optimized routing for liquidity. For example, a USDC TIP-20 token can be paired with other stablecoins, allowing traders to swap between different USD-denominated tokens with minimal slippage through concentrated liquidity pools. By using TIP-20 tokens as quote tokens, the DEX benefits from the same payment-optimized features like deterministic addresses, currency identifiers, and compliance policies, ensuring secure and efficient exchange operations. ### Additional Links ## TIP20 ### Abstract TIP20 is a suite of precompiles that provide a built-in optimized token implementation in the core protocol. It extends the ERC-20 token standard with built-in functionality like memo fields and reward distribution. ### Motivation All major stablecoins today use the ERC-20 token standard. While ERC-20 provides a solid foundation for fungible tokens, it lacks features critical for stablecoin issuers today such as memos, transfer policies, and rewards distribution. Additionally, since each ERC-20 token has its own implementation, integrators can't depend on consistent behavior across tokens. TIP-20 extends ERC-20, building these features into precompiled contracts that anyone can permissionlessly deploy on Tempo. This makes token operations much more efficient, allows issuers to quickly set up on Tempo, and simplifies integrations since it ensures standardized behavior across tokens. It also enables deeper integration with token-specific Tempo features like paying gas in stablecoins and payment lanes. ### Specification TIP-20 tokens support standard fungible token operations such as transfers, mints, and burns. They also support transfers, mints, and burns with an attached 32-byte memo; a role-based access control system for token administrative operations; and a system for opt-in [reward distribution](/protocol/tip20-rewards/spec). ### TIP20 The core TIP-20 contract exposes standard ERC-20 functions for balances, allowances, transfers, and delegated transfers, and also adds: * 32-byte memo support on transfers, mints, and burns. * A `TIP20Roles` module for permissioned actions like issuing, pausing, unpausing, and burning blocked balances. * Configuration options for currencies, quote tokens, and transfer policies. The complete TIP20 interface is defined below: ```solidity interface ITIP20 { // ========================================================================= // ERC-20 standard functions // ========================================================================= /// @notice Returns the name of the token /// @return The token name function name() external view returns (string memory); /// @notice Returns the symbol of the token /// @return The token symbol function symbol() external view returns (string memory); /// @notice Returns the number of decimals for the token /// @return Always returns 6 for TIP-20 tokens function decimals() external pure returns (uint8); /// @notice Returns the total amount of tokens in circulation /// @return The total supply of tokens function totalSupply() external view returns (uint256); /// @notice Returns the token balance of an account /// @param account The address to check the balance for /// @return The token balance of the account function balanceOf(address account) external view returns (uint256); /// @notice Transfers tokens from caller to recipient /// @param to The recipient address /// @param amount The amount of tokens to transfer /// @return True if successful function transfer(address to, uint256 amount) external returns (bool); /// @notice Returns the remaining allowance for a spender /// @param owner The token owner address /// @param spender The spender address /// @return The remaining allowance amount function allowance(address owner, address spender) external view returns (uint256); /// @notice Approves a spender to spend tokens on behalf of caller /// @param spender The address to approve /// @param amount The amount to approve /// @return True if successful function approve(address spender, uint256 amount) external returns (bool); /// @notice Transfers tokens from one address to another using allowance /// @param from The sender address /// @param to The recipient address /// @param amount The amount to transfer /// @return True if successful function transferFrom(address from, address to, uint256 amount) external returns (bool); /// @notice Mints new tokens to an address (requires ISSUER_ROLE) /// @param to The recipient address /// @param amount The amount of tokens to mint function mint(address to, uint256 amount) external; /// @notice Burns tokens from caller's balance (requires ISSUER_ROLE) /// @param amount The amount of tokens to burn function burn(uint256 amount) external; // ========================================================================= // TIP-20 extended functions // ========================================================================= /// @notice Transfers tokens from caller to recipient with a memo /// @param to The recipient address /// @param amount The amount of tokens to transfer /// @param memo A 32-byte memo attached to the transfer function transferWithMemo(address to, uint256 amount, bytes32 memo) external; /// @notice Transfers tokens from one address to another with a memo using allowance /// @param from The sender address /// @param to The recipient address /// @param amount The amount to transfer /// @param memo A 32-byte memo attached to the transfer /// @return True if successful function transferFromWithMemo(address from, address to, uint256 amount, bytes32 memo) external returns (bool); /// @notice Mints new tokens to an address with a memo (requires ISSUER_ROLE) /// @param to The recipient address /// @param amount The amount of tokens to mint /// @param memo A 32-byte memo attached to the mint function mintWithMemo(address to, uint256 amount, bytes32 memo) external; /// @notice Burns tokens from caller's balance with a memo (requires ISSUER_ROLE) /// @param amount The amount of tokens to burn /// @param memo A 32-byte memo attached to the burn function burnWithMemo(uint256 amount, bytes32 memo) external; /// @notice Burns tokens from a blocked address (requires BURN_BLOCKED_ROLE) /// @param from The address to burn tokens from (must be unauthorized by transfer policy) /// @param amount The amount of tokens to burn function burnBlocked(address from, uint256 amount) external; /// @notice Returns the quote token used for DEX pairing /// @return The quote token address function quoteToken() external view returns (ITIP20); /// @notice Returns the next quote token staged for update /// @return The next quote token address (zero if none staged) function nextQuoteToken() external view returns (ITIP20); /// @notice Returns the currency identifier for this token /// @return The currency string function currency() external view returns (string memory); /// @notice Returns whether the token is currently paused /// @return True if paused, false otherwise function paused() external view returns (bool); /// @notice Returns the maximum supply cap for the token /// @return The supply cap (checked on mint operations) function supplyCap() external view returns (uint256); /// @notice Returns the current transfer policy ID from TIP-403 registry /// @return The transfer policy ID function transferPolicyId() external view returns (uint64); // ========================================================================= // Admin Functions // ========================================================================= /// @notice Pauses the contract, blocking transfers (requires PAUSE_ROLE) function pause() external; /// @notice Unpauses the contract, allowing transfers (requires UNPAUSE_ROLE) function unpause() external; /// @notice Changes the transfer policy ID (requires DEFAULT_ADMIN_ROLE) /// @param newPolicyId The new policy ID from TIP-403 registry /// @dev From Allegretto hardfork onwards, validates that the policy exists using TIP403Registry.policyExists() /// Built-in policies (ID 0 = always-reject, ID 1 = always-allow) are always valid. /// For custom policies (ID >= 2), the policy must exist in the TIP-403 registry. /// Reverts with InvalidTransferPolicyId if the policy does not exist. function changeTransferPolicyId(uint64 newPolicyId) external; /// @notice Stages a new quote token for update (requires DEFAULT_ADMIN_ROLE) /// @param newQuoteToken The new quote token address function setNextQuoteToken(ITIP20 newQuoteToken) external; /// @notice Completes the quote token update process (requires DEFAULT_ADMIN_ROLE) function completeQuoteTokenUpdate() external; /// @notice Sets the maximum supply cap (requires DEFAULT_ADMIN_ROLE) /// @param newSupplyCap The new supply cap (cannot be less than current supply) function setSupplyCap(uint256 newSupplyCap) external; // ========================================================================= // Role Management // ========================================================================= /// @notice Returns the BURN_BLOCKED_ROLE constant /// @return keccak256("BURN_BLOCKED_ROLE") function BURN_BLOCKED_ROLE() external view returns (bytes32); /// @notice Returns the ISSUER_ROLE constant /// @return keccak256("ISSUER_ROLE") function ISSUER_ROLE() external view returns (bytes32); /// @notice Returns the PAUSE_ROLE constant /// @return keccak256("PAUSE_ROLE") function PAUSE_ROLE() external view returns (bytes32); /// @notice Returns the UNPAUSE_ROLE constant /// @return keccak256("UNPAUSE_ROLE") function UNPAUSE_ROLE() external view returns (bytes32); /// @notice Grants a role to an account (requires role admin) /// @param role The role to grant (keccak256 hash) /// @param account The account to grant the role to function grantRole(bytes32 role, address account) external; /// @notice Revokes a role from an account (requires role admin) /// @param role The role to revoke (keccak256 hash) /// @param account The account to revoke the role from function revokeRole(bytes32 role, address account) external; /// @notice Allows an account to remove a role from itself /// @param role The role to renounce (keccak256 hash) function renounceRole(bytes32 role) external; /// @notice Changes the admin role for a specific role (requires current role admin) /// @param role The role whose admin is being changed /// @param adminRole The new admin role function setRoleAdmin(bytes32 role, bytes32 adminRole) external; // ========================================================================= // System Functions // ========================================================================= /// @notice System-level transfer function (restricted to precompiles) /// @param from The sender address /// @param to The recipient address /// @param amount The amount to transfer /// @return True if successful function systemTransferFrom(address from, address to, uint256 amount) external returns (bool); /// @notice Pre-transaction fee transfer (restricted to precompiles) /// @param from The account to charge fees from /// @param amount The fee amount function transferFeePreTx(address from, uint256 amount) external; /// @notice Post-transaction fee handling (restricted to precompiles) /// @param to The account to refund /// @param refund The refund amount /// @param actualUsed The actual fee used function transferFeePostTx(address to, uint256 refund, uint256 actualUsed) external; // ========================================================================= // Events // ========================================================================= /// @notice Emitted when a new allowance is set by `owner` for `spender` /// @param owner The account granting the allowance /// @param spender The account being approved to spend tokens /// @param amount The new allowance amount event Approval(address indexed owner, address indexed spender, uint256 amount); /// @notice Emitted when tokens are burned from an address /// @param from The address whose tokens were burned /// @param amount The amount of tokens that were burned event Burn(address indexed from, uint256 amount); /// @notice Emitted when tokens are burned from a blocked address /// @param from The blocked address whose tokens were burned /// @param amount The amount of tokens that were burned event BurnBlocked(address indexed from, uint256 amount); /// @notice Emitted when new tokens are minted to an address /// @param to The address receiving the minted tokens /// @param amount The amount of tokens that were minted event Mint(address indexed to, uint256 amount); /// @notice Emitted when a new quote token is staged for this token /// @param updater The account that staged the new quote token /// @param nextQuoteToken The quote token that has been staged event NextQuoteTokenSet(address indexed updater, ITIP20 indexed nextQuoteToken); /// @notice Emitted when the pause state of the token changes /// @param updater The account that changed the pause state /// @param isPaused The new pause state; true if paused, false if unpaused event PauseStateUpdate(address indexed updater, bool isPaused); /// @notice Emitted when the quote token update process is completed /// @param updater The account that completed the quote token update /// @param newQuoteToken The new quote token that has been set event QuoteTokenUpdate(address indexed updater, ITIP20 indexed newQuoteToken); /// @notice Emitted when a holder sets or updates their reward recipient address /// @param holder The token holder configuring the recipient /// @param recipient The address that will receive claimed rewards event RewardRecipientSet(address indexed holder, address indexed recipient); /// @notice Emitted when a reward distribution is scheduled /// @param funder The account funding the reward distribution /// @param id The identifier of the reward (0 for instant distributions) /// @param amount The total amount of tokens allocated to the reward /// @param durationSeconds The duration in seconds (must be 0 in current version) event RewardScheduled( address indexed funder, uint64 indexed id, uint256 amount, uint32 durationSeconds ); /// @notice Emitted when the token's supply cap is updated /// @param updater The account that updated the supply cap /// @param newSupplyCap The new maximum total supply event SupplyCapUpdate(address indexed updater, uint256 indexed newSupplyCap); /// @notice Emitted for all token movements, including mints and burns /// @param from The address sending tokens (address(0) for mints) /// @param to The address receiving tokens (address(0) for burns) /// @param amount The amount of tokens transferred event Transfer(address indexed from, address indexed to, uint256 amount); /// @notice Emitted when the transfer policy ID is updated /// @param updater The account that updated the transfer policy /// @param newPolicyId The new transfer policy ID from the TIP-403 registry event TransferPolicyUpdate(address indexed updater, uint64 indexed newPolicyId); /// @notice Emitted when a transfer, mint, or burn is performed with an attached memo /// @param from The address sending tokens (address(0) for mints) /// @param to The address receiving tokens (address(0) for burns) /// @param amount The amount of tokens transferred /// @param memo The 32-byte memo associated with this movement event TransferWithMemo( address indexed from, address indexed to, uint256 amount, bytes32 indexed memo ); /// @notice Emitted when the membership of a role changes for an account /// @param role The role being granted or revoked /// @param account The account whose membership was changed /// @param sender The account that performed the change /// @param hasRole True if the role was granted, false if it was revoked event RoleMembershipUpdated( bytes32 indexed role, address indexed account, address indexed sender, bool hasRole ); /// @notice Emitted when the admin role for a role is updated /// @param role The role whose admin role was changed /// @param newAdminRole The new admin role for the given role /// @param sender The account that performed the update event RoleAdminUpdated( bytes32 indexed role, bytes32 indexed newAdminRole, address indexed sender ); // ========================================================================= // Errors // ========================================================================= /// @notice The token operation is blocked because the contract is currently paused error ContractPaused(); /// @notice The spender does not have enough allowance for the attempted transfer error InsufficientAllowance(); /// @notice The account does not have the required token balance for the operation /// @param currentBalance The current balance of the account /// @param expectedBalance The required balance for the operation to succeed /// @param token The address of the token contract error InsufficientBalance(uint256 currentBalance, uint256 expectedBalance, address token); /// @notice The provided amount is zero or otherwise invalid for the attempted operation error InvalidAmount(); /// @notice The provided currency identifier is invalid or unsupported error InvalidCurrency(); /// @notice The specified quote token is invalid, incompatible, or would create a circular reference error InvalidQuoteToken(); /// @notice The recipient address is not a valid destination for this operation /// (for example, another TIP-20 token contract) error InvalidRecipient(); /// @notice The specified transfer policy ID does not exist in the TIP-403 registry error InvalidTransferPolicyId(); /// @notice The new supply cap is invalid, for example lower than the current total supply error InvalidSupplyCap(); /// @notice A rewards operation was attempted when no opted-in supply exists error NoOptedInSupply(); /// @notice The configured transfer policy denies authorization for the sender or recipient error PolicyForbids(); /// @notice Attempted to start a timed reward distribution; streaming is disabled error ScheduledRewardsDisabled(); /// @notice The attempted operation would cause total supply to exceed the configured supply cap error SupplyCapExceeded(); /// @notice The caller does not have the required role or permission for this operation error Unauthorized(); } ``` :::warning When interacting with precompiles, **always use the provided ABI** rather than reading directly from storage slots. Direct storage access may lead to undefined behavior. ::: ### Memos Memo functions `transferWithMemo`, `transferFromWithMemo`, `mintWithMemo`, and `burnWithMemo` behave like their ERC-20 equivalents but additionally emit memo data in dedicated events. The memo is always a fixed 32-byte field. Callers should pack shorter strings or identifiers directly into this field, and use hashes or external references when the underlying payload exceeds 32 bytes. ### TIP-403 Transfer Policies All operations that move tokens: `transfer`, `transferFrom`, `transferWithMemo`, `transferFromWithMemo`, `mint`, `burn`, `mintWithMemo`, and `burnWithMemo` — enforce the token’s configured TIP-403 transfer policy. Internally, this is implemented via a `transferAuthorized` modifier that: * Calls `TIP403_REGISTRY.isAuthorized(transferPolicyId, from)` for the sender. * Calls `TIP403_REGISTRY.isAuthorized(transferPolicyId, to)` for the recipient. Both checks must return `true`, otherwise the call reverts with `PolicyForbids`. Reward operations (`startReward`, `setRewardRecipient`, `claimRewards`) also perform the same TIP-403 authorization checks before moving any funds. ### Invalid Recipient Protection TIP-20 tokens cannot be sent to other TIP-20 token contract addresses. The implementation uses a `validRecipient` guard that rejects recipients whose address is zero, or has the TIP-20 prefix (`0x20c000000000000000000000`). Any attempt to transfer to a TIP-20 token address must revert with `InvalidRecipient`. This prevents accidental token loss by sending funds to token contracts instead of user accounts. ### Currencies and Quote Tokens Each TIP-20 token declares a currency identifier and a corresponding `quoteToken` used for pricing and routing in the Stablecoin DEX. Tokens with `currency == "USD"` must pair with a USD-denominated TIP-20 token. Updating the quote token occurs in two phases: 1. `setNextQuoteToken` stages a new quote token. 2. `completeQuoteTokenUpdate` finalizes the change. The implementation must validate that the new quote token is a TIP-20 token, matches currency rules, and does not create circular quote-token chains. :::note While quote tokens can be changed, choose carefully as the update process requires careful coordination with the DEX. ::: ### Pause Controls Pause controls `pause` and `unpause` govern all transfer operations and reward related flows. When paused, transfers and memo transfers halt, but administrative and configuration functions remain allowed. The `paused()` getter reflects the current state and must be checked by all affected entrypoints. ### TIP-20 Roles TIP-20 uses a role-based authorization system. The main roles are: * `ISSUER_ROLE`: controls minting and burning. * `PAUSE_ROLE` / `UNPAUSE_ROLE`: controls the token’s paused state. * `BURN_BLOCKED_ROLE`: allows burning balances belonging to addresses that fail TIP-403 authorization. Roles are assigned and managed through `grantRole`, `revokeRole`, `renounceRole`, and `setRoleAdmin`, via the contract admin. ### System Functions System level functions `systemTransferFrom`, `transferFeePreTx`, and `transferFeePostTx` are only callable by other Tempo protocol precompiles. These entrypoints power transaction fee collection, refunds, and internal accounting within the Fee AMM and Stablecoin DEX. They must not be callable by general contracts or users. ### Token Rewards Distribution See [rewards distribution](/protocol/tip20-rewards/spec) for more information. ### TIP20Factory The `TIP20Factory` contract is the canonical entrypoint for creating new TIP-20 tokens on Tempo. The factory maintains an internal `tokenIdCounter` that increments with each deployment, and uses this counter to derive deterministic “vanity” deployment addresses under a fixed 12-byte TIP-20 prefix. This ensures that every TIP-20 token exists at a predictable, collision-free address, and that integrators can infer a token’s identifier directly from its address. The `TIP20Factory` precompile is deployed at 0x20Fc000000000000000000000000000000000000`. Newly created TIP-20 addresses are deployed a vanity address derived from `TIP20\_PREFIX || tokenId\`, where: * `TIP20_PREFIX` is the 12-byte prefix `0x20C0000000000000000000000000` * `tokenId` is the current monotonically increasing counter value, encoded into the least significant bytes of the address (eg. `0x20C0000000000000000000000000000000000001`) When creating a token, the factory performs several checks to guarantee consistency across the TIP-20 ecosystem: * The specified Quote token must be a currently deployed TIP20. * Tokens that specify their currency as USD must also specify a quote token that is denoted in USD. * At deployment, the factory initializes defaults on the TIP-20:\ `transferPolicyId = 1`, `supplyCap = type(uint128).max`, `paused = false`, and `totalSupply = 0`. * The provided `admin` address receives `DEFAULT_ADMIN_ROLE`, enabling it to manage roles and token configurations. The complete `TIP20Factory` interface is defined below: ```solidity /// @title TIP-20 Factory Interface /// @notice Deploys and initializes new TIP-20 tokens at deterministic vanity addresses interface ITIP20Factory { /// @notice Creates and deploys a new TIP-20 token /// @param name The token's ERC-20 name /// @param symbol The token's ERC-20 symbol /// @param currency The token's currency identifier (e.g. "USD") /// @param quoteToken The TIP-20 quote token used for exchange pricing /// @param admin The address to receive DEFAULT_ADMIN_ROLE on the new token /// /// @return token The deployed TIP-20 token address /// @dev /// - Computes the TIP-20 deployment address as TIP20_PREFIX || tokenId /// - Ensures the provided quote token is itself a valid TIP-20 /// - Enforces USD-denomination rules (USD tokens must use USD quote tokens) /// - Rejects configurations that would form circular quote-token chains /// - Initializes the token with default settings: /// transferPolicyId = 1 (always-allow) /// supplyCap = type(uint128).max /// paused = false /// totalSupply = 0 /// - Grants DEFAULT_ADMIN_ROLE on the new token to `admin` /// - Emits a {TokenCreated} event function createToken( string memory name, string memory symbol, string memory currency, ITIP20 quoteToken, address admin ) external returns (address token); // ========================================================================= // Helpers // ========================================================================= /// @notice Returns true if `token` is a valid TIP-20 address /// @param token The address to check /// @return True if the address is a well-formed TIP-20 /// @dev Checks the TIP-20 prefix and ensures its embedded ID <= tokenIdCounter function isTIP20(address token) external view returns (bool); /// @notice Returns the next token ID that will be assigned on creation /// @return The current tokenIdCounter value function tokenIdCounter() external view returns (uint256); // ========================================================================= // Events // ========================================================================= /// @notice Emitted when a new TIP-20 token is created /// @param token The newly deployed TIP-20 address /// @param id The assigned token ID used in address construction /// @param name The token name /// @param symbol The token symbol /// @param currency The token currency /// @param quoteToken The token's assigned quote token /// @param admin The address receiving DEFAULT_ADMIN_ROLE event TokenCreated( address indexed token, uint256 indexed id, string name, string symbol, string currency, ITIP20 indexed quoteToken, address admin ); // ========================================================================= // Errors // ========================================================================= /// @notice The provided quote token address is invalid or not a TIP-20 error InvalidQuoteToken(); } ``` ### Invariants * `totalSupply()` must always equal to the sum of all `balanceOf(account)` over all accounts. * `totalSupply()` must always be `<= supplyCap` * When `paused` is `true`, no functions that move tokens (`transfer`, `transferFrom`, memo variants, `systemTransferFrom`, `startReward`, `setRewardRecipient`, `claimRewards`) can succeed. * TIP20 tokens cannot be transferred to another TIP20 token contract address. * `systemTransferFrom`, `transferFeePreTx`, and `transferFeePostTx` never change `totalSupply()`. import LucideDroplet from '~icons/lucide/droplet' import LucideFileText from '~icons/lucide/file-text' import LucideWallet from '~icons/lucide/wallet' import LucideShieldCheck from '~icons/lucide/shield-check' import * as Card from "../../../components/Card.tsx" ## Transaction Fees Tempo has no native token. Instead, transaction fees—including both gas fees and priority fees—can be paid directly in stablecoins. When you send a transaction, you can choose which supported stablecoin to use for fees. For a stablecoin to be accepted, it must be USD-denominated, issued as a native TIP-20 contract, and have sufficient liquidity on the native Fee AMM. Tempo uses a fixed base fee (rather than a variable base fee as in EIP-1559), set so that a TIP-20 transfer costs less than $0.001. All fees accrue to the validator who proposes the block. ### Learn More import { Callout } from 'vocs/components' ## Fee AMM Specification ### Abstract This specification defines a system of one-way Automated Market Makers (AMMs) designed to facilitate gas fee payments from a user using one stablecoin (the `userToken`) to a validator who prefers a different stablecoin (the `validatorToken`). Each AMM handles fee swaps from a `userToken` to a `validatorToken` at one price (0.9970 `validatorToken` per `userToken`), and allows rebalancing in the other direction at another fixed price (0.9985 `validatorToken` per `userToken`). ### Motivation Current blockchain fee systems typically require users to hold native tokens for gas payments. This creates friction for users who prefer to transact in stablecoins. The Fee AMM is a dedicated AMM for trading between stablecoins, which can only be used by the protocol (and by arbitrageurs rebalancing it to keep it balanced). The protocol automatically collects fees in many different coins during the block, and then sells them all at the end of the block (paying a constant price) into the token preferred by the validator. The system is designed to minimize several forms of MEV: * **No Probabilistic MEV**: The fixed fee swap rate and batch settlement prevent profitable backrunning of fee swaps. There is no way to profitably spam the chain with transactions hoping an opportunity might arise. * **No Sandwich Attacks**: Fee swaps execute at a fixed rate and settle atomically at block end, eliminating sandwich attack vectors. * **Top-of-Block Auction**: The main MEV in the AMM (from rebalancing) occurs as a single race at the top of the next block rather than creating probabilistic spam throughout. ### Specification #### Overview The Fee AMM implements two distinct swap mechanisms: 1. **Fee Swaps**: Fixed-rate swaps at a price of `0.9970` (validator token per user token) from `userToken` to `validatorToken` 2. **Rebalancing Swaps**: Fixed-rate swaps at a price of `0.9985` (validator token per user token) from `validatorToken` to `userToken` #### Core Components ##### 1. FeeAMM Contract The primary AMM contract managing liquidity pools and swap operations. ##### Pool Structure ```solidity struct Pool { uint128 reserveUserToken; // Reserve of userToken uint128 reserveValidatorToken; // Reserve of validatorToken } ``` Each pool is directional: `userToken` → `validatorToken`. For a pair of tokens A and B, there are two separate pools: * Pool(A, B): for swapping A to B at fixed rate of 0.997 (fee swaps) and B to A at fixed rate of 0.9985 (rebalancing) * Pool(B, A): for swapping B to A at fixed rate of 0.997 (fee swaps) and A to B at fixed rate of 0.9985 (rebalancing) ##### Constants * `M = 9970` (scaled by 10000, representing 0.9970) * `N = 9985` (scaled by 10000, representing 0.9985) * `SCALE = 10000` * `MIN_LIQUIDITY = 1000` ##### Key Functions ```solidity function getPool( address userToken, address validatorToken ) external view returns (Pool memory) ``` Returns the pool structure for a given token pair. ```solidity function getPoolId( address userToken, address validatorToken ) external pure returns (bytes32) ``` Returns the pool ID for a given token pair (used internally for pool lookup). ```solidity function rebalanceSwap( address userToken, address validatorToken, uint256 amountOut, address to ) external returns (uint256 amountIn) ``` Executes rebalancing swaps from `validatorToken` to `userToken` at fixed rate of 0.9985 (validator token per user token). Can be executed by anyone. Calculates `amountIn = (amountOut * N) / SCALE + 1` (rounds up). Updates reserves immediately. Emits `RebalanceSwap` event. ```solidity function mint( address userToken, address validatorToken, uint256 amountUserToken, uint256 amountValidatorToken, address to ) external returns (uint256 liquidity) ``` Adds liquidity to a pool with both tokens. First provider sets initial reserves and must burn `MIN_LIQUIDITY` tokens. Subsequent providers must provide proportional amounts. Receives fungible LP tokens representing pro-rata share of pool reserves. ```solidity function mintWithValidatorToken( address userToken, address validatorToken, uint256 amountValidatorToken, address to ) external returns (uint256 liquidity) ``` Single-sided liquidity provision with validator token only. Treats the deposit as equivalent to performing a hypothetical `rebalanceSwap` first at rate `n = 0.9985` until the ratio of reserves match, then minting liquidity by depositing both. Formula: `liquidity = amountValidatorToken * _totalSupply / (V + n * U)`, where `n = N / SCALE`. Rounds down to avoid over-issuing LP tokens. Updates reserves by increasing only `validatorToken` by `amountValidatorToken`. Emits `Mint` event with `amountUserToken = 0`. ```solidity function burn( address userToken, address validatorToken, uint256 liquidity, address to ) external returns (uint256 amountUserToken, uint256 amountValidatorToken) ``` Burns LP tokens and receives pro-rata share of reserves. Reverts if withdrawal would prevent pending swaps at the end of the block. Emits `Burn` event. ```solidity function executePendingFeeSwaps( address userToken, address validatorToken ) internal returns (uint256 amountOut) ``` Settles all pending fee swaps by updating reserves. Calculates `amountOut = (amountIn * M) / SCALE`. Only executed by the protocol, at the end of each block. Emits `FeeSwap` event. ```solidity function reserveLiquidity( address userToken, address validatorToken, uint256 maxAmount ) internal returns (bool) ``` Reserves liquidity for a pending fee swap. Calculates `maxAmountOut = (maxAmount * M) / SCALE`. Verifies sufficient validator token reserves (accounting for pending swaps). Tracks pending swap input. ```solidity function releaseLiquidityPostTx( address userToken, address validatorToken, uint256 refundAmount ) internal ``` Releases reserved liquidity when fees are refunded. Decreases pending swap input by refund amount. ##### 2. FeeManager Contract Tempo introduces a precompiled contract, the `FeeManager`, at the address `0xfeec000000000000000000000000000000000000`. The `FeeManager` is a singleton contract that implements all the functions of the Fee AMM for every pool. It also handles the collection and refunding of fees during each transaction, stores fee token preferences for users and validators, and implements the `executeBlock()` function that is called by a system transaction at the end of each block. ##### Key Functions ```solidity function setUserToken(address token) external ``` Sets the default fee token preference for the caller (user). Requires token to be a USD TIP-20 token. Emits `UserTokenSet` event. Access: Direct calls only (not via delegatecall). ```solidity function setValidatorToken(address token) external ``` Sets the fee token preference for the caller (validator). Requires token to be a USD TIP-20 token. Cannot be called during a block built by that validator. Emits `ValidatorTokenSet` event. Access: Direct calls only (not via delegatecall). ```solidity function collectFeePreTx( address user, address userToken, uint256 maxAmount ) external ``` Called by the protocol before transaction execution. The fee token (`userToken`) is determined by the protocol before calling using logic that considers: explicit tx fee token, setUserToken calls, stored user preference, tx.to if TIP-20. Reserves AMM liquidity if user token differs from validator token. Collects maximum possible fee from user. Access: Protocol only (`msg.sender == address(0)`). ```solidity function collectFeePostTx( address user, uint256 maxAmount, uint256 actualUsed, address userToken ) external ``` Called by the protocol after transaction execution. The validator token and fee recipient are inferred from `block.coinbase`. Calculates refund amount: `refundAmount = maxAmount - actualUsed`. Refunds unused tokens to user. Releases reserved liquidity for refunded amount. Tracks collected fees for block-end settlement. Access: Protocol only (`msg.sender == address(0)`). ```solidity function executeBlock() external ``` Called once in a system transaction at the end of each block. Processes all collected fees and executes pending swaps. For each token with collected fees: if token differs from validator token, executes pending fee swaps via AMM and updates reserves and calculates output amount. Transfers all validator tokens to the validator (`block.coinbase`). Clears fee tracking arrays. Access: Protocol only (`msg.sender == address(0)`). #### Swap Mechanisms ##### Fee Swaps * **Rate**: Fixed at m=0.9970 (validator receives 0.9970 of their preferred token per 1 user token that user pays) * **Direction**: User token to validator token * **Purpose**: Convert tokens paid by users as fees to tokens preferred by validators * **Settlement**: Batched at block end via `executePendingFeeSwaps` * **Access**: Protocol only ##### Rebalancing Swaps * **Rate**: Fixed at n=0.9985 (swapper receives 1 of the user token for every 0.9985 that they put in of the validator's preferred token) * **Direction**: Validator token to user token * **Purpose**: Refill reserves of validator token in the pool * **Settlement**: Immediate * **Access**: Anyone #### Fee Collection Flow 1. **Pre-Transaction**: * Protocol determines user's fee token using logic that considers: explicit tx fee token, setUserToken calls, stored user preference, tx.to if TIP-20 * Protocol calculates maximum gas needed (`maxAmount = gasLimit * maxFeePerGas`) * `FeeManager.collectFeePreTx(user, userToken, maxAmount)` is called: * If user token differs from validator token, reserves AMM liquidity via `reserveLiquidity()` * Collects maximum fee from user using `transferFeePreTx()` * If any check fails (insufficient balance, insufficient liquidity), transaction is invalid 2. **Post-Transaction**: * Calculate actual gas used (`actualUsed = gasUsed * gasPrice`) * `FeeManager.collectFeePostTx(user, maxAmount, actualUsed, userToken)` is called: * Validator token and fee recipient are inferred from `block.coinbase` * Calculates refund: `refundAmount = maxAmount - actualUsed` * Refunds unused tokens to user via `transferFeePostTx()` * Releases reserved liquidity for refunded amount via `releaseLiquidityPostTx()` * Tracks collected fees (actual used amount) for block-end settlement 3. **Block End**: * System transaction calls `FeeManager.executeBlock()`: * For each token with collected fees: * If token differs from validator token, executes pending fee swaps via `executePendingFeeSwaps()` * Updates pool reserves (adds userToken, subtracts validatorToken) * Transfers all validator tokens to validator (`block.coinbase`) * Clears fee tracking arrays #### Events ```solidity event RebalanceSwap( address indexed userToken, address indexed validatorToken, address indexed swapper, uint256 amountIn, uint256 amountOut ) event FeeSwap( address indexed userToken, address indexed validatorToken, uint256 amountIn, uint256 amountOut ) event Mint( address indexed sender, address indexed userToken, address indexed validatorToken, uint256 amountUserToken, uint256 amountValidatorToken, uint256 liquidity ) event Burn( address indexed sender, address indexed userToken, address indexed validatorToken, uint256 amountUserToken, uint256 amountValidatorToken, uint256 liquidity, address to ) event UserTokenSet(address indexed user, address indexed token) event ValidatorTokenSet(address indexed validator, address indexed token) ``` `Transfer` events are emitted as usual for transactions, with the exception of paying gas fees via TIP20 tokens. For fee payments, a single `Transfer` event is emitted post execution to represent the actual fee amount consumed (i.e. `gasUsed * gasPrice`). #### System transactions This specification introduces **system transactions**, with the first being the `executeBlock()` call to the `FeeManager` contract at the end of each block. A system transaction is a legacy transaction with an empty signature (`r = 0`, `s = 0`, `yParity = false`) and with the sender as the 0 address (`0x0000000000000000000000000000000000000000`). System transactions are only allowed when there is a specific consensus rule allowing them. A block is invalid if any required system transaction is missing or if any extra system transaction is present. System transactions do not consume block gas, do not increment a sender nonce, do not contribute to block gas limit, and do not pay fees. They may set any gas price and gas limit (as specified by a specific rule), regardless of their execution gas or the block base fee. System transactions must not revert. ##### Execution transaction Under this specification, exactly one system transaction must appear at the end of every block. It must have the following parameters: | Field | Value / Requirement | Notes / Validation | | --------------------- | -------------------------------------------- | ------------------------------------------------- | | **Type** | Legacy transaction | | | **Position in Block** | **Last transaction** | Block is **invalid** if absent or not last. | | **From (sender)** | `0x0000000000000000000000000000000000000000` | Zero address | | **To (recipient)** | `0xfeec000000000000000000000000000000000000` | FeeManager precompile. | | **Calldata** | `0xb306cc70` | ABI-encoded `executeBlock()`, no arguments. | | **Value** | `0` | No native token transfer. | | **Nonce** | 0 | | | **Gas Limit** | 0 | Does **not** contribute to block gas accounting. | | **Gas Price** | 0 | Independent of block base fee; does not pay fees. | | **Signature** | `r = 0`, `s = 0`, `yParity = false` | Empty signature designates system transaction. | The proposer **must** construct and include this transaction when building the block. A block is invalid if the transaction is absent or not in the final position. #### Gas Fee swaps are designed to be gas-free from the user perspective. The pre-tx and post-tx steps in each transaction do not cost any gas; nor does the system transaction at the end of each block. ## Fees ### Abstract This spec lays out how fees work on Tempo, including how fees are calculated, who pays them, and how the default fee token for a transaction is determined. ### Motivation On Tempo, users can pay gas fees in any [TIP-20](/protocol/tip20/spec) token whose currency is USD, as long as that stablecoin has sufficient liquidity on the enshrined [fee AMM](/protocol/fees/spec-fee-amm.mdx) against the token that the current validator wants to receive. In determining *which* token a user pays fees in, we want to maximize customizability (so that wallets or users can implement more sophisticated UX than is possible at the protocol layer), minimize surprise (particularly surprises in which a user pays fees in a stablecoin they did not expect to), and have sane default behavior so that users can begin using basic functions like payments even using wallets that are not customized for Tempo support. ### Fee units Fees in the `max_base_fee_per_gas` and `max_fee_per_gas` fields of transactions, as well as in the block's `base_fee_per_gas` field, are specified in units of **USD per 10^18 gas**. Since TIP-20 tokens have 6 decimal places, that means the fee for a transaction can be calculated as `ceil(base_fee * gas_used / 10^12)`. This unit is chosen to provide sufficient precision for low-fee transactions. Since TIP-20 tokens have only 6 decimal places (as opposed to the 18 decimal places of ETH), expressing fees directly in tokens per gas would not provide enough precision for transactions with very low gas costs. By scaling the fee paid by 10^-12, the protocol ensures that even small fee amounts can be accurately represented and calculated. ### Fee payment Before the execution of each transaction, the protocol takes the following steps: * Determine the [`fee_payer`](#fee-payer) of the transaction. * Determine the `fee_token` of the transaction, according to the [rules for fee token preferences](#fee-token-preferences). If the fee token cannot be determined, the transaction is invalid. * Compute the `max_fee` of the transaction as `gas_limit * gas_price`. * Deduct `max_fee` from the `fee_payer`'s balance of `fee_token`. If `fee_payer` does not have sufficient balance in `fee_token`, the transaction is invalid. * Reserve `max_fee` of liquidity on the [fee AMM](/protocol/fees/spec-fee-amm) between the `fee_token` and the validator's preferred fee token. If there is insufficient liquidity, the transaction is invalid. After the execution of each transaction: * Compute the `refund_amount` as `(gas_limit - gas_used) * gas_price`. * Credit the `fee_payer`'s address with `refund_amount` of `fee_token`. * Log a `Transfer` event from the user to the [fee manager contract](/protocol/fees/spec-fee-amm) for the net amount of the fee payment. ### Fee payer Tempo supports *sponsored transactions* in which the `fee_payer` is a different address from the `tx.origin` of the transaction. This is supported by Tempo's [new transaction type](/protocol/transactions/spec-tempo-transaction.mdx), which has a `fee_payer_signature` field. If no `fee_payer_signature` is provided, then the `fee_payer` of the transaction is its sender (`tx.origin`). If the `fee_payer_signature` field is set, then it is used to derive the `fee_payer` for the transaction, as described in the [transaction spec](/protocol/transactions/spec-tempo-transaction.mdx). For purposes of [fee token preferences](#fee-token-preferences), the `fee_payer` is the account that chooses the fee token. #### Fee sponsorship flow Presence of the `fee_payer_signature` field authorizes a third party to pay the transaction's gas costs while the original sender executes the transaction logic. :::steps ##### Sender signs the transaction The sender signs the transaction with their private key, signing over a blank fee token field. This means the sender delegates the choice of which fee token to use to the fee payer. ##### Fee payer selects and signs The fee payer selects which fee token to use, then signs over the transaction. ##### Transaction submission The fee token and fee payer signature is added to the transaction using the `fee_payer_signature` field and is then submitted. ##### Network validation The network validates both signatures and executes the transaction. ::: ##### Validation When `feePayerSignature` is present: * Both sender and fee payer signatures must be valid * Fee payer must have sufficient balance in the fee token * Transaction is rejected if either signature fails or fee payer's balance is insufficient ### Fee token preferences The protocol checks for token preferences in five ways, with this order of precedence: 1. Transaction (set by the `fee_token` field of the transaction) 2. Account (set on the FeeManager contract by the `fee_payer` of the transaction) 3. TIP-20 contract (if the transaction is calling any function on a TIP-20 contract, the transaction uses that token as its fee token) 4. Stablecoin Exchange (for certain swap calls, the transaction uses the `tokenIn` argument as its fee token) 5. PathUSD (as a fallback) The protocol checks preferences at each of these levels, stopping at the first one at which a preference is specified. At that level, the protocol performs the following checks. If any of the checks fail, the transaction is invalid (without looking at any further levels): * The token must be a TIP-20 token whose currency is USD. * The user must have sufficient balance in that token to pay the `gasLimit` on the transaction at the transaction's `gasPrice`. * There must be sufficient liquidity on the [fee AMM](/protocol/fees/spec-fee-amm.mdx), as discussed in that specification. If no preference is specified at the transaction, account, or contract level, the protocol falls back to [pathUSD](#pathusd). #### Transaction level Tempo's [new transaction type](/protocol/transactions/spec-tempo-transaction.mdx), allows transactions to specify a `fee_token` on the transaction. This overrides any preferences set at the account, contract, or validator level. For [sponsored transactions](#fee-payer), the `tx.origin` address does not sign over the `fee_token` field (allowing the `fee_payer` to choose the fee token). #### Account level An account can specify a fee token preference for all transactions for which it is the `fee_payer` (including both transactions it sponsors as well as non-sponsored transactions for which it is the `tx.origin`). This overrides any preference set at the contract or validator level. To set its preference, the account can call the `setUserToken` function on the FeeManager precompile. At this step, the protocol does one more check: * If the transaction is not a [Tempo transaction](/protocol/transactions/spec-tempo-transaction.mdx) *and* the transaction is a top-level call to the `setUserToken` function on the FeeManager, then the protocol checks the `token` argument to the function: * If that token is a TIP-20 whose currency is USD, that token is used as the fee token (unless the transaction specifies a `fee_token` at the [transaction level](#transaction-level)). * If that token is not a TIP-20 or its currency is not USD, the transaction is invalid. #### TIP-20 contracts If the top-level call of a transaction is to a TIP-20 contract for which the currency is USD, or *all* of the top-level calls of a TempoTransaction are to the same TIP-20 contract for which the currency is USD, that token is used as the user's fee token for that transaction (unless there is a preference specified at the [transaction](#transaction-level) or [account](#account-level) level). #### Stablecoin Exchange contract If the top-level call of a transaction is to the [Stablecoin Exchange](/protocol/exchange/spec.mdx) contract, the function being called is either `swapExactAmountIn` or `swapExactAmountOut`, and the `tokenIn` argument to that function is the address of a TIP-20 token for which the currency is USD, then the `tokenIn` argument is used as the user's fee token for the transaction (unless there is a preference specified at the [transaction](#transaction-level) or [account](#account-level) level). For [Tempo transactions](/protocol/transactions/spec-tempo-transaction.mdx), this rule applies only if there is only one top-level call in the transaction. #### pathUSD If no fee preference is set at the transaction, account, or contract level, the protocol falls back to [pathUSD](/protocol/exchange/pathUSD.mdx) as the user's fee token preference. ### Validator preferences Validators can set a default fee token preference that determines which stablecoin they receive for transaction fees. When users pay in different tokens, the Fee AMM automatically converts to the validator's preferred token. #### Setting validator preference To set their preference, validators call the `setValidatorToken` function on the FeeManager precompile: ```solidity // Set your preferred fee token feeManager.setValidatorToken(preferredTokenAddress); ``` After setting a validator token preference, all fees collected in blocks the validator proposes will be automatically converted to the chosen token (if needed) and transferred to the validator's account. On the Andantino testnet, validators currently expect alphaUSD (one of the tokens distributed by the faucet) as their fee token. If validators have not specified a fee token preference, the protocol falls back to expecting pathUSD as their fee token. #### Removing validator preference To remove a validator token preference, set it to the zero address: ```solidity // Remove validator token preference feeManager.setValidatorToken(address(0)); ``` ### Fee lifecycle This section describes the complete flow of how fees are collected, converted, and distributed from user to validator. #### Fee flow steps When a user submits a transaction on Tempo, fees are paid in their chosen stablecoin (determined by the [fee token preferences](#fee-token-preferences) hierarchy). If the validator prefers a different stablecoin, the Fee AMM automatically converts the user's payment to the validator's preferred token. ##### 1. User submits transaction The transaction is submitted with the fee token determined by the preference hierarchy. ##### 2. Pre-transaction collection Before the transaction executes, the `FeeManager` contract collects the maximum possible fee amount from the user: * Verifies the user has sufficient balance in their chosen fee token * Checks if the Fee AMM has enough liquidity (if conversion is needed) * Collects the maximum fee amount based on the transaction's gas limit If either check fails, the transaction is rejected before execution. ##### 3. Transaction execution The transaction executes normally. The actual gas consumed may be less than the maximum that was collected. ##### 4. Post-transaction refund After execution, the `FeeManager`: * Calculates the actual fee owed based on gas used * Refunds any unused tokens to the user * Queues the actual fee amount for conversion (if needed) ##### 5. Fee swap queuing If the user's fee token differs from the validator's preferred token, the fee is added to a pending fee swap queue for that token pair. The swap doesn't execute immediately—it's batched with all other fees collected during the block. If the user's fee token matches the validator's preference, no conversion is needed and the fee goes directly to the validator. ##### 6. End-of-block settlement At the end of each block, the protocol: 1. Calls `executePendingFeeSwaps()` on the Fee AMM for each token pair 2. Executes all pending fee swaps at a fixed rate of **0.9970** (validator receives 0.9970 of their token per 1.0 user token paid) 3. Updates the AMM pool reserves 4. Transfers the converted tokens to the validator's account This batched settlement prevents MEV attacks like sandwiching or backrunning individual fee payments. #### Fee swap mechanics Fee swaps always execute at a fixed rate of **0.9970**: ``` validatorTokenOut = userTokenIn × 0.9970 ``` This means: * User pays 1.0 USDC for fees * Validator receives 0.9970 USDT (if that's their preferred token) * The 0.003 (0.3%) difference goes to liquidity providers as a fee #### Example flow Here's a complete example of the fee lifecycle: 1. **Alice** wants to send a transaction and pays fees in **USDC** (her preferred token) 2. **Validator** prefers to receive fees in **USDT** 3. Alice's transaction has a max fee of 1.0 USDC 4. The FeeManager collects 1.0 USDC from Alice before execution 5. Transaction executes and uses 0.8 USDC worth of gas 6. The FeeManager refunds 0.2 USDC to Alice 7. The remaining 0.8 USDC is queued for conversion 8. At block end, the Fee AMM swaps 0.8 USDC → 0.7976 USDT (0.8 × 0.9970) 9. Validator receives 0.7976 USDT 10. Liquidity providers earn 0.0024 USDT from the 0.3% fee #### Gas costs The fee conversion process adds minimal overhead to transactions: * **Pre-transaction**: \~5,000 gas for balance and liquidity checks * **Post-transaction**: \~3,000 gas for refund and queue operations * **Block settlement**: Amortized across all transactions in the block For complete technical specifications on the Fee AMM mechanism, see the [Fee AMM Protocol Specification](/protocol/fees/spec-fee-amm). import LucideDroplet from '~icons/lucide/droplet' import LucideFileText from '~icons/lucide/file-text' import LucideCircleDollarSign from '~icons/lucide/circle-dollar-sign' import * as Card from "../../../../components/Card.tsx" ## Fee AMM Overview The Fee AMM (Automated Market Maker) is a dedicated system for converting transaction fees between different stablecoins. It enables users to pay fees in any supported stablecoin while allowing validators to receive fees in their preferred token. ### How It Works When a user pays fees in a stablecoin that differs from the validator's preference, the Fee AMM automatically converts the payment: * **User pays**: 1.0 of their chosen stablecoin * **Validator receives**: 0.9970 of their preferred stablecoin * **Liquidity providers earn**: 0.003 (0.3%) as fees This conversion happens automatically at the end of each block through batched swaps, preventing MEV attacks like sandwiching. ### Learn More import { Callout } from 'vocs/components' ## DEX Balance The Stablecoin DEX allows you to hold token balances directly using the DEX contract. This eliminates the need for token transfers on every trade, significantly reducing gas costs for active traders and liquidity providers. ### Why DEX Balances? When you trade or provide liquidity on the DEX, constantly transferring tokens between your wallet and the DEX contract wastes gas. By maintaining a balance via the DEX contract, you can: * **Save on gas costs** - Avoid ERC-20 transfer costs for each trade * **Trade more efficiently** - Execute multiple swaps without transfers between each trade * **Receive maker proceeds automatically** - When your limit orders are filled, proceeds are credited to your DEX balance instead of requiring a transfer for each fill ### Checking Your Balance Use the DEX contract to view your balance of any token held on the DEX: ```solidity function balanceOf( address user, address token ) external view returns (uint128) ``` **Example:** ```solidity uint128 balance = exchange.balanceOf(msg.sender, USDC_ADDRESS); ``` ### Using Your DEX Balance Each transaction that you authorize will use your DEX balance before using funds you approve from your wallet. When you execute a swap or place an order, the DEX contract automatically: 1. Checks if you have sufficient balance in the DEX 2. If insufficient, transfers the needed amount from your wallet to your DEX balance 3. Uses your DEX balance for the operation ### Withdrawing from the DEX Transfer tokens from your DEX balance back to your wallet: ```solidity function withdraw( address token, uint128 amount ) external ``` **Parameters:** * `token` - The token address to withdraw * `amount` - The amount to withdraw **Example:** ```solidity // Withdraw 1000 USDC from exchange to your wallet exchange.withdraw(USDC_ADDRESS, 1000e6); ``` The withdraw function will revert if you attempt to withdraw more than your available balance on the exchange. ### How Balances Work #### When Swapping * **Before swap**: Exchange checks your balance, transfers from wallet if needed * **After swap**: Output tokens are transferred directly to your wallet (not kept on exchange) #### When Placing Orders * **On placement**: Required tokens are debited from your exchange balance (or transferred from wallet if insufficient) * **When filled**: Proceeds are credited to your exchange balance * **On cancellation**: Unfilled portion is refunded to your exchange balance import { Callout } from 'vocs/components' ## Executing Swaps ### Swap Functions The exchange provides two primary swap functions: #### Swap Exact Amount In Specify the exact amount of tokens you want to sell, and receive at least a minimum amount: ```solidity function swapExactAmountIn( address tokenIn, address tokenOut, uint128 amountIn, uint128 minAmountOut ) external returns (uint128 amountOut) ``` **Parameters:** * `tokenIn` - The token address you're selling * `tokenOut` - The token address you're buying * `amountIn` - The exact amount of `tokenIn` to sell * `minAmountOut` - Minimum amount of `tokenOut` you'll accept (slippage protection) **Returns:** * `amountOut` - The actual amount of `tokenOut` received **Example:** Swap exactly 1000 USDC for at least 998 USDT: ```solidity uint128 amountOut = exchange.swapExactAmountIn( USDC_ADDRESS, USDT_ADDRESS, 1000e6, // Sell exactly 1000 USDC 998e6 // Receive at least 998 USDT ); ``` #### Swap Exact Amount Out Specify the exact amount of tokens you want to receive, and pay at most a maximum amount: ```solidity function swapExactAmountOut( address tokenIn, address tokenOut, uint128 amountOut, uint128 maxAmountIn ) external returns (uint128 amountIn) ``` **Parameters:** * `tokenIn` - The token address you're selling * `tokenOut` - The token address you're buying * `amountOut` - The exact amount of `tokenOut` to receive * `maxAmountIn` - Maximum amount of `tokenIn` you'll pay (slippage protection) **Returns:** * `amountIn` - The actual amount of `tokenIn` spent **Example:** Receive exactly 1000 USDT by spending at most 1002 USDC: ```solidity uint128 amountIn = exchange.swapExactAmountOut( USDC_ADDRESS, USDT_ADDRESS, 1000e6, // Receive exactly 1000 USDT 1002e6 // Pay at most 1002 USDC ); ``` ### Quoting Prices Before executing a swap, you can query the expected price using view functions that simulate the swap without executing it: #### Quote Exact Amount In ```solidity function quoteSwapExactAmountIn( address tokenIn, address tokenOut, uint128 amountIn ) external view returns (uint128 amountOut) ``` Returns how much `tokenOut` you would receive for a given `amountIn`. #### Quote Exact Amount Out ```solidity function quoteSwapExactAmountOut( address tokenIn, address tokenOut, uint128 amountOut ) external view returns (uint128 amountIn) ``` Returns how much `tokenIn` you would need to spend to receive a given `amountOut`. **Example: Getting a price quote** ```solidity // Check how much USDT you'd get for 1000 USDC uint128 expectedOut = exchange.quoteSwapExactAmountIn( USDC_ADDRESS, USDT_ADDRESS, 1000e6 ); // Only execute if the price is acceptable if (expectedOut >= 998e6) { exchange.swapExactAmountIn(USDC_ADDRESS, USDT_ADDRESS, 1000e6, 998e6); } ``` ### How Swaps Execute When you call a swap function: 1. **Balance Check**: The contract first checks your balance on the DEX 2. **Transfer if Needed**: If your DEX balance is insufficient, tokens are transferred from your wallet 3. **Order Matching**: The DEX walks through orders at each price tick, from best to worst: * Orders are consumed in price-time priority order * Each filled order credits the maker's balance on the DEX * Continues until your swap is complete or limit price is reached 4. **Slippage Check**: Reverts if `minAmountOut` (or `maxAmountIn`) constraints aren't met 5. **Settlement**: Your output tokens are transferred to your wallet Swaps will revert with an `InsufficientLiquidity` error if there isn't enough liquidity in the orderbook to satisfy your slippage constraints. ### Gas Costs Swap gas costs scale with the number of orders and ticks your trade crosses: * Base swap cost (transfers and setup) * Per-order cost (for each order filled) * Per-tick cost (for each price level crossed) * Per-flip cost (if any flip orders are triggered) Larger swaps that cross more orders will cost more gas, but the cost per unit of volume decreases. ### Token Balances on the DEX The DEX allows you to track token balances directly within the DEX contract, which saves gas by avoiding ERC-20 transfers on every trade. When you execute a swap, the contract first checks your DEX balance and only transfers from your wallet if needed. For complete details on checking balances, depositing, withdrawing, and managing your DEX balance, see the [DEX Balance](/protocol/exchange/exchange-balance) page. import { Callout } from 'vocs/components' import LucideArrowLeftRight from '~icons/lucide/arrow-left-right' import LucideDroplet from '~icons/lucide/droplet' import LucideWallet from '~icons/lucide/wallet' import * as Card from "../../../components/Card.tsx" ## Exchanging Stablecoins Tempo features an enshrined decentralized exchange (DEX) designed specifically for trading between stablecoins of the same underlying asset (e.g., USDC to USDT). The exchange provides optimal pricing for cross-stablecoin payments while minimizing chain load from excessive market activity. The exchange operates as a singleton precompiled contract at address `0xdec0000000000000000000000000000000000000`. It maintains an orderbook with separate queues for each price tick, using price-time priority for order matching. Trading pairs are determined by each token's quote token. All TIP-20 tokens specify a quote token for trading on the exchange. Tokens can choose [pathUSD](/protocol/exchange/pathUSD) as their quote token. See the [Stablecoin DEX Specification](/protocol/exchange/spec) for detailed information on the exchange structure. The exchange supports three types of orders, each with different execution behavior: | Order Type | Description | | ----------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [**Limit Orders**](/protocol/exchange/providing-liquidity#limit-orders) | Place orders at specific price levels that wait in the book until matched or cancelled. New orders are queued and added to the book at the end of the block. | | [**Flip Orders**](/protocol/exchange/providing-liquidity#flip-orders) | Special orders that automatically reverse to the opposite side when completely filled, acting like a perpetual liquidity pool. Filled flip orders automatically create new orders on the opposite side during end-of-block settlement. | | [**Market Orders**](/protocol/exchange/executing-swaps#swap-functions) | Execute immediately against the best available orders in the book (via swap functions). Swaps and cancellations execute immediately within the transaction. | This execution model prevents MEV by making it impossible to backrun new orders or perform JIT liquidity attacks. For the complete execution mechanics, see the [Stablecoin DEX Specification](/protocol/exchange/spec). To get started with the exchange, explore these guides: For a more complete technical specification including design decisions and details of execution semantics, see the [Stablecoin DEX Specification](/protocol/exchange/spec). ## pathUSD ### Abstract pathUSD is a USD-denominated stablecoin that can be used as a quote token on Tempo's decentralized exchange. It is the first stablecoin deployed to the chain, and is used as a fallback gas token when the user or validator does not specify a gas token. Use of pathUSD is optional. ### Motivation Each USD TIP-20 on Tempo can choose any other USD TIP-20 as its [quote token](/protocol/exchange/spec.mdx#quote-tokens)—the token it is paired against on the [native decentralized exchange](/protocol/exchange/spec.mdx). This guarantees that there is one path between any two tokens, which reduces fragmentation of liquidity and simplifies routing. While on other chains, most liquidity accrues to a few stablecoins, or even one, Tempo offers a USD-denominated stablecoin, pathUSD, that other stablecoins can choose as their quote token. PathUSD is not meant to compete as a consumer-facing stablecoin. Use of pathUSD is optional, and tokens are able to list any other token as their quote token if they choose. PathUSD can also be accepted as a fee token by validators. ### Specification #### Contract PathUSD is a predeployed [TIP-20](/protocol/tip20/spec.mdx) at genesis. Note that since it is the first TIP-20 contract deployed, its quote token is the zero address. | Property | Value | | -------------- | -------------------------------------------- | | address | `0x20c0000000000000000000000000000000000000` | | `name()` | "pathUSD" | | `symbol()` | "pathUSD" | | `currency()` | "USD" | | `decimals()` | 6 | | `quoteToken()` | `address(0)` | ### How It Works When you create a USD stablecoin on Tempo, you can set pathUSD as its quote token: ```solidity TIP20 token = factory.createToken( "My Company USD", "MCUSD", "USD", TIP20(0x20c0000000000000000000000000000000000000), // pathUSD msg.sender ); ``` This means: * Your token trades against pathUSD on the decentralized exchange * Users can swap between your token and other USD stablecoins that also use pathUSD, or ones that are connected to it by a multi-hop path ##### Tree Structure This creates a tree structure where all USD stablecoins are connected via multi-hop paths. ``` USDX | pathUSD -- USDY -- USDZ | USDA ``` The tree structure guarantees that there is a single path between any two USD stablecoins, ensuring simple routing, concentrated liquidity, and efficient pricing, even for thinly-traded pairs. ##### Example: Cross-Stablecoin Payment 1. Market makers provide liquidity for USDX/pathUSD and USDY/pathUSD pairs 2. User wants to send USDX to a merchant who prefers USDY 3. DEX atomically routes: User's USDX → pathUSD → Merchant's USDY 4. Single action, no manual swaps This is critical for payments between parties with different stablecoin preferences. The user and merchant never touch pathUSD; it is used only as a routing mechanism. import { Callout } from 'vocs/components' ## Providing Liquidity Provide liquidity to the DEX by placing limit orders or flip orders in the onchain orderbook. When your orders are filled, you earn the spread between bid and ask prices while helping facilitate trades for other users. You can only place orders on pairs between a token and its designated quote token. All TIP-20 tokens specify a quote token for trading pairs. [PathUSD](/protocol/exchange/pathUSD) can be used as a simple choice for a quote token. ### Overview The DEX uses an onchain orderbook where you can place orders at specific price ticks. Orders are matched using price-time priority, meaning better-priced orders fill first, and within the same price, earlier orders fill first. Unlike traditional AMMs, you specify exact prices where you want to buy or sell, giving you more precise control over your liquidity provision strategy. ### Order Types #### Limit Orders Standard orders that remain in the book at a specific price until filled or cancelled. ```solidity function place( address token, uint128 amount, bool isBid, int16 tick ) external returns (uint128 orderId) ``` **Parameters:** * `token` - The token address you're trading (must trade against its quote token) * `amount` - The amount of the token denominated in `token` * `isBid` - `true` for a buy order, `false` for a sell order * `tick` - The price tick: `(price - 1) * 100_000` where price is in quote token per token **Returns:** * `orderId` - Unique identifier for this order **Example: Place a bid to buy 1000 USDC at $0.9990** ```solidity // tick = (0.9990 - 1) * 100_000 = -10 uint128 orderId = exchange.place( USDC_ADDRESS, 1000e6, // Amount: 1000 USDC true, // isBid: buying USDC -10 // tick: price = $0.9990 ); ``` **Example: Place an ask to sell 1000 USDC at $1.0010** ```solidity // tick = (1.0010 - 1) * 100_000 = 10 uint128 orderId = exchange.place( USDC_ADDRESS, 1000e6, // Amount: 1000 USDC false, // isBid: selling USDC 10 // tick: price = $1.0010 ); ``` #### Flip Orders Special orders that automatically reverse to the opposite side when completely filled, creating perpetual liquidity similar to an automated market maker pool. ```solidity function placeFlip( address token, uint128 amount, bool isBid, int16 tick, int16 flipTick ) external returns (uint128 orderId) ``` **Parameters:** * All parameters from `place()`, plus: * `flipTick` - The price where the order will flip to when filled * Must be greater than `tick` if `isBid` is true * Must be less than `tick` if `isBid` is false **Returns:** * `orderId` - Unique identifier for this flip order **Example: Place a flip order providing liquidity on both sides** ```solidity // Place a bid at $0.9990 that flips to an ask at $1.0010 uint128 orderId = exchange.placeFlip( USDC_ADDRESS, 1000e6, // Amount: 1000 USDC true, // isBid: start as a buy order -10, // tick: buy at $0.9990 10 // flipTick: sell at $1.0010 after filled ); ``` When this order is completely filled: 1. You buy 1000 USDC at $0.9990 2. A new order automatically sells 1000 USDC at $1.0010 3. When that fills, it flips back to a bid at $0.9990 4. This continues indefinitely, earning the spread each time Flip orders act like a liquidity pool position, automatically providing liquidity on both sides of the market as they're filled back and forth. ### Understanding Ticks Prices are specified using ticks with 0.1 basis point (0.001%) precision: **Tick Formula:** `tick = (price - 1) × 100_000` **Price Formula:** `price = 1 + (tick / 100_000)` Where `price` is the token price in quote token units. #### Example Tick Calculations | Price | Tick | Calculation | | ------- | ---- | ------------------------------ | | $0.9990 | -100 | (0.9990 - 1) × 100\_000 = -100 | | $0.9998 | -20 | (0.9998 - 1) × 100\_000 = -20 | | $1.0000 | 0 | (1.0000 - 1) × 100\_000 = 0 | | $1.0002 | 20 | (1.0002 - 1) × 100\_000 = 20 | | $1.0010 | 100 | (1.0010 - 1) × 100\_000 = 100 | Price ticks are limited to ±2% from peg (±2000 ticks). Orders outside this range will be rejected. ### Bid vs Ask * **Bid (isBid = true)**: An order to *buy* the token using its quote token * **Ask (isBid = false)**: An order to *sell* the token for its quote token For a USDC/USD pair where USD is the quote: * A bid buys USDC with USD at your specified price * An ask sells USDC for USD at your specified price ### Order Execution Timeline Orders follow a specific lifecycle: 1. **Placement**: When you call `place()` or `placeFlip()`: * Tokens are debited from your DEX balance (or transferred if insufficient) * Order is queued but **not yet visible** to other contracts * Returns an order ID immediately 2. **End of Block**: All queued orders are added to the book: * Processed in the order they were placed * Filled flip orders from this block are added first * Orders become visible and matchable 3. **Filling**: As market orders execute against your order: * Your order fills partially or completely * Proceeds are credited to your DEX balance * If a flip order fills completely, it creates a new order on the opposite side **MEV Protection**: The delayed execution prevents backrunning and JIT (Just-In-Time) liquidity attacks, since orders aren't visible until the block ends. ### Cancelling Orders Remove an order from the book before it's filled: ```solidity function cancel( uint128 orderId ) external ``` **Example:** ```solidity // Cancel order #12345 exchange.cancel(12345); ``` Cancellations execute immediately, and any unfilled portion of your order is refunded to your [DEX balance](/protocol/exchange/exchange-balance). You can only cancel your own orders. Attempting to cancel another user's order will revert. ## Stablecoin DEX ### Abstract This specification defines an enshrined decentralized exchange for trading between TIP-20 stablecoins. The exchange currently only supports trading between TIP-20 stablecoins with USD as their currency. By only allowing each stablecoin to be paired against its designated "quote token" the exchange enforces that there is only one route for trading between any two tokens. The exchange maintains price‑time priority at each discrete price tick, executes swaps immediately against the active book, and supports auto‑replenishing “flip orders” that recreate themselves on the opposite side after being fully filled. Users maintain internal balances per token on the exchange. Order placement escrows funds from these balances (or transfers from the user if necessary), fills credit makers internally, and withdrawals transfer tokens out. ### Motivation Tempo aims to provide high‑quality execution for cross‑stablecoin payments while avoiding unnecessary chain load and minimizing mid‑block MEV surfaces. A simple, on‑chain, price‑time‑priority orderbook tailored to stable pairs encourages passive liquidity to rest on chain and allows takers to execute deterministically at the best available ticks. Another design goal is to avoid fragmentation of liquidity across many different pairs. By enforcing that each stablecoin only trades against a single quote token, the system guarantees that there is only one path between any two tokens. ### Specification #### Contract and scope The exchange is a singleton contract deployed at `0xdec0000000000000000000000000000000000000`. It exposes functions to create trading pairs, place and cancel orders (including flip orders), execute swaps, produce quotes, manage internal balances, and process end‑of‑block placement. #### Key concepts ##### Internal balances The contract maintains per‑user, per‑token internal balances. Order placement escrows funds from these balances (or transfers any shortfall from the user). When an order fills, the maker is credited internally with the counter‑asset at the order’s tick price. Users can withdraw available balances at any time. ##### Flip orders A flip order behaves like a normal resting order until it is fully filled. When filled, the exchange places a new order for the same maker on the opposite side at a configured `flipTick` (which must be greater than `tick` for bids and less for asks). This enables passive liquidity with flexible strategies. ##### Pairs, ticks, and prices Pairs are identified deterministically from the two token addresses (the base token is any TIP‑20, and its `quoteToken()` function points to the quote token). Prices are discretized into integer ticks with a tick size of 0.1 bps: with `PRICE_SCALE = 100_000`, `price = PRICE_SCALE + tick`. Orders may only be placed at ticks divisible by `TICK_SPACING = 10` (effectively setting a 1 bp tick size). The orderbook tracks best bid (highest active bid tick) and best ask (lowest active ask tick), and uses bitmaps over tick words for efficient discovery of the next initialized tick. ##### Quote tokens Each TIP‑20 token specifies a single quote token in its metadata via `quoteToken()`. A trading pair on the stablecoin exchange exists only between a base token and its designated quote token, and prices for the pair are denominated in units of the quote token. This design reduces liquidity fragmentation by giving every token exactly one paired asset. It also simplifies routing. We require that: 1. each token picks a single other stablecoin as its quote token, and, 2. quote token relationships cannot have circular dependencies. This forces liquidity into a tree structure, which in turn implies that there is only one path between any two stablecoins USD tokens can only choose USD tokens as their quote token. Non-USD TIP-20 tokens can pick any token as their quote token, but currently there is no support for cross-currency trading, or same-currency trading of non-USD tokens, on the DEX. The platform offers a neutral USD stablecoin, [`pathUSD`](/protocol/exchange/pathUSD), as an option for quote token. PathUSD is the first stablecoin deployed on the chain, which means it has no quote token. Use of pathUSD is optional. ##### Swaps Swaps execute immediately against the active book. Selling base for quote starts at the current best bid and walks downward as ticks are exhausted; selling quote for base starts at the best ask and walks upward. Within a tick, fills are FIFO and decrement the tick’s total liquidity. When a tick empties, it is de‑initialized. Callers can swap between any two USD TIP-20 tokens. If `tokenIn` and `tokenOut` are not directly paired, the implementation finds the unique path between them through quote‑token relationships, and performs a multi‑hop swap/quote. ##### Crossed books Crossed books are permitted; the implementation does not enforce that best bid ≤ best ask. This primarily supports flip‑order scenarios. ##### Constraints * Only USD‑denominated tokens are supported, and their quotes must also be USD * Orders must specify ticks within the configured bounds (±2000) * Tick spacing is 10: `tick % 10 == 0` for orders and flip orders * Withdrawals require sufficient internal balance #### Interface Below is the complete on‑chain interface, organized by function. Behavior notes and constraints are included with each function where relevant. ##### Constants and pricing ```solidity function PRICE_SCALE() external view returns (uint32); ``` Scaling factor for tick‑based prices. One tick equals 1/PRICE\_SCALE above or below the peg. Current value: `100_000` (0.001% per tick). ```solidity function TICK_SPACING() external view returns (int16); ``` Orders must be placed on ticks divisible by `TICK_SPACING`. Current value: `10` (i.e., 1 bp grid). ```solidity function MIN_TICK() external view returns (int16); function MAX_TICK() external view returns (int16); ``` Inclusive tick bounds for order placement. Current range: ±2000 ticks (±2%). ```solidity function MIN_PRICE() external view returns (uint32); function MAX_PRICE() external view returns (uint32); ``` Price bounds implied by tick bounds and `PRICE_SCALE`. ```solidity function tickToPrice(int16 tick) external pure returns (uint32 price); function priceToTick(uint32 price) external pure returns (int16 tick); ``` Convert between discrete ticks and scaled prices. `priceToTick` reverts if `price` is out of bounds. ##### Pairing and orderbook ```solidity function pairKey(address tokenA, address tokenB) external pure returns (bytes32 key); ``` Deterministic key for a pair derived from the two token addresses (order‑independent). ```solidity function createPair(address base) external returns (bytes32 key); ``` Creates the pair between `base` and its `quoteToken()` (from TIP‑20). Both must be USD‑denominated. Reverts if the pair already exists or tokens are not USD. ```solidity function books(bytes32 pairKey) external view returns (address base, address quote, int16 bestBidTick, int16 bestAskTick); ``` Returns pair metadata and current best‑of‑side ticks. Best ticks may be sentinel values when no liquidity exists. ```solidity function getTickLevel(address base, int16 tick, bool isBid) external view returns (uint128 head, uint128 tail, uint128 totalLiquidity); ``` Returns FIFO head/tail order IDs and aggregate liquidity for a tick on a side, allowing indexers to reconstruct the active book. ##### Internal balances ```solidity function balanceOf(address user, address token) external view returns (uint128); ``` Returns a user’s internal balance for `token` held on the exchange. ```solidity function withdraw(address token, uint128 amount) external; ``` Transfers `amount` of `token` from the caller’s internal balance to the caller. Reverts if insufficient internal balance. ##### Order placement and lifecycle ```solidity function place(address token, uint128 amount, bool isBid, int16 tick) external returns (uint128 orderId); ``` Places a limit order against the pair of `token` and its quote, immediately adding it to the active book. Escrows funds: bids escrow quote at tick price; asks escrow base. Notes: * `tick` must be within `[MIN_TICK, MAX_TICK]` and divisible by `TICK_SPACING` (10). ```solidity function placeFlip(address token, uint128 amount, bool isBid, int16 tick, int16 flipTick) external returns (uint128 orderId); ``` Like `place`, but marks the order as a flip order. When fully filled, a new order for the same maker is scheduled on the opposite side at `flipTick` (which must be greater than `tick` for bids and less for asks). Notes: * Both `tick` and `flipTick` must be within `[MIN_TICK, MAX_TICK]` and divisible by `TICK_SPACING` (10). ```solidity function cancel(uint128 orderId) external; ``` Cancels an order owned by the caller. When canceled, the order is removed from the tick queue, liquidity is decremented, and remaining escrow is refunded to the order owner's exchange balance which can then be withdrawn. ```solidity function nextOrderId() external view returns (uint128); ``` Monotonic counter for next orderId. ##### Swaps and quoting ```solidity function quoteSwapExactAmountIn(address tokenIn, address tokenOut, uint128 amountIn) external view returns (uint128 amountOut); ``` Simulates an exact‑in swap walking initialized ticks and returns the expected output. Reverts if the pair path lacks sufficient liquidity. ```solidity function quoteSwapExactAmountOut(address tokenIn, address tokenOut, uint128 amountOut) external view returns (uint128 amountIn); ``` Simulates an exact‑out swap and returns the required input. Reverts if insufficient liquidity. ```solidity function swapExactAmountIn(address tokenIn, address tokenOut, uint128 amountIn, uint128 minAmountOut) external returns (uint128 amountOut); ``` Executes an exact‑in swap against the active book. Deducts `amountIn` from caller’s internal balance (transferring any shortfall) and transfers output to the caller. Reverts if resulting `amountOut` is below `minAmountOut` or liquidity is insufficient. ```solidity function swapExactAmountOut(address tokenIn, address tokenOut, uint128 amountOut, uint128 maxAmountIn) external returns (uint128 amountIn); ``` Executes an exact‑out swap. Deducts the actual input from the caller’s internal balance (transferring any shortfall from the user) and transfers `amountOut` to the caller. Reverts if required input exceeds `maxAmountIn` or liquidity is insufficient. ##### Events ```solidity event PairCreated(bytes32 indexed key, address indexed base, address indexed quote); event OrderPlaced(uint128 indexed orderId, address indexed maker, address indexed token, uint128 amount, bool isBid, int16 tick); event FlipOrderPlaced(uint128 indexed orderId, address indexed maker, address indexed token, uint128 amount, bool isBid, int16 tick, int16 flipTick); event OrderCancelled(uint128 indexed orderId); event OrderFilled(uint128 indexed orderId, address indexed maker, address indexed taker, uint128 amountFilled, bool partialFill); ``` ##### Errors ```solidity error Unauthorized(); ``` * Pair creation or usage: `PAIR_EXISTS`, `PAIR_NOT_EXISTS`, `ONLY_USD_PAIRS` * Bounds: `TICK_OUT_OF_BOUNDS`, `FLIP_TICK_OUT_OF_BOUNDS`, `FLIP_TICK_MUST_BE_GREATER_FOR_BID`, `FLIP_TICK_MUST_BE_LESS_FOR_ASK`, "Price out of bounds" * Tick spacing: `TICK_NOT_MULTIPLE_OF_SPACING`, `FLIP_TICK_NOT_MULTIPLE_OF_SPACING` * Liquidity and limits: `INSUFFICIENT_LIQUIDITY`, `MAX_IN_EXCEEDED`, `INSUFFICIENT_OUTPUT` * Authorization: `UNAUTHORIZED` (cancel not by maker) * Balance: `INSUFFICIENT_BALANCE` (withdraw) ## Blockspace Overview ### Abstract This specification defines the structure of valid blocks in the Tempo blockchain. ### Motivation Tempo blocks extend the Ethereum block format in multiple ways: there are new header fields to account for payment lanes and sub-blocks, and system transactions are added to the block body for the fee AMM and other protocol operations. This specification contains all the modifications to the block format. ### Specification #### Header fields Tempo extends an Ethereum header with three extra scalars. ```rust title="Header struct" pub struct Header { pub general_gas_limit: u64, pub shared_gas_limit: u64, pub timestamp_millis_part: u64, pub inner: Header, } ``` * `inner` is the canonical Ethereum header (parent\_hash, state\_root, gas\_limit, etc.). * `general_gas_limit` and `shared_gas_limit` carve up the canonical `gas_limit` for payment and sub-block gas (see [payment lane specification](/protocol/blockspace/payment-lane-specification) and [sub-block specification](/protocol/blockspace/sub-block-specification)). * `timestamp_millis_part` stores the sub‑second component; the full timestamp is `inner.timestamp * 1000 + timestamp_millis_part` . #### Block body The block body in Tempo retains the canonical Ethereum block body structure, with the addition of new system transactions. Transactions are ordered in the following sections: 1. Start-of-block system transaction(s) (must begin with the rewards registry call). 2. Proposer lane transactions, subject to `general_gas_limit` on non-payment transactions. 3. Sub-block transactions, grouped by proposer and prefixed with the reserved nonce key. 4. Gas incentive transactions that consume leftover shared gas. 5. End-of-block system transactions (see below). #### System transactions A valid tempo block must contain the following new system transactions * **Rewards Registry (start-of-block)** — must be the first transaction in the block body; refreshes validator rewards metadata before user transactions begin. Detailed specification [here](/protocol/tip20-rewards/spec). * **Fee Manager (end-of-block 1/3)** — settles block fee accounting. Detailed specification [here](/protocol/fees/spec-fee-amm). * **Stablecoin DEX (end-of-block 2/3)** — settles stablecoin exchange balances. Detailed specification [here](/protocol/exchange/spec). * **Subblock Metadata (end-of-block 3/3)** — contains metadata about the sub-blocks included in the block. Detailed specification [here](/protocol/blockspace/sub-block-specification). import { Callout } from 'vocs/components' ## Payment Lane Specification ### Abstract This specification introduces a second consensus gas constraint for **non-payment** transactions. Transactions are classified as either payments or non-payments based solely on their transaction data, without requiring any access to blockchain state. For a block to be valid, total `gas_used` by the block must be less than the `gas_limit`. Non-payment transactions executed in the proposer's lane (i.e. before the gas incentive section) must consume at most `general_gas_limit`, a new field added to the header. Once that budget is exhausted, any additional inclusion must come via the gas incentive lane defined in the [sub-blocks specification](/protocol/blockspace/sub-block-specification). ### Motivation Tempo ensures that payment transactions always have available blockspace, even during periods of high network congestion from DeFi activity or complex smart contracts. No action is required by the user to take advantage of this feature. This is achieved through **separate gas limits** for payment and non-payment transactions. When blocks are constructed, validators enforce two separate gas constraints: 1. **`gas_limit`** — The total gas available for all transactions (standard Ethereum behavior) 2. **`general_gas_limit`** — The maximum gas that non-payment transactions can consume Non-payment transactions in the proposer's lane can only fill up to `general_gas_limit`, payment transactions can still use the remaining capacity up. ### Terminology * **Payment transaction:** Determined by a function, `is_payment(tx) -> bool`. This function returns true if the transaction is a payment transaction, false otherwise. * **Non-payment transaction:** `!is_payment(tx)`. ### Specification #### 1. Transaction classification A transaction is classified as a **payment transaction** when: 1. the recipient address (`tx.to`) starts with the TIP-20 payment prefix `0x20c0000000000000000000000000`, or, 2. for TempoTransactions, every entry in `tx.calls` targets an address starting with the TIP-20 payment prefix `0x20c0000000000000000000000000`. This classification is performed entirely on the transaction payload, no account state is consulted. Later upgrades may enable other kinds of transactions to be classified as payment transactions as well. #### 2. Ordering of Transactions The specification does not require any specific ordering of transactions: payment and non-payment transactions can be intermixed. #### 3. Gas accounting & validity (consensus) Validity of a block requires that, ``` general_gas_limit >= Σ gas_consumed(tx[i]) for all i such that !is_payment(tx[i]) and tx[i] is in the proposer's lane ``` Where `gas_consumed` includes intrinsic gas and gas burned by reverts, as in the existing protocol. import { Callout } from 'vocs/components' ## Sub-block Specification ### Abstract This proposal allows non-proposing validators to propose a limited set of transactions in each block through signed **sub-blocks**. Sub-blocks are sent directly to the main proposer and their transactions are included in the block as described below. Consensus does not enforce inclusion. The proposer is incentivized to include sub-blocks by provisioning additional gas upon sub-block inclusion, which permits them to include additional transactions at the bottom of the block as described below. ### Motivation In traditional blockchains, only the current block proposer can include transactions. This means validators must wait for their scheduled slot to provide fast inclusion for their users. Tempo changes this by letting **all validators** contribute transactions to every block through sub-blocks. For validators, this is good as they no longer have to wait for their turn as proposer to provide low-latency inclusion for their users. They have guaranteed access to blockspace in every block, allowing them to include transactions whenever needed. Validators can also ensure a specific transaction execution order within their sub-block, giving them controlled ordering of their transactions. For users, this is good because transactions can be included faster since they can go through any validator, not just the current proposer. Access to blockspace becomes more predictable as it is smoothed across all validators rather than being concentrated with a single proposer. Time-sensitive transactions benefit from lower latency and can be included more quickly. This proposal smooths access to blockspace across validators. It enables every validator to provide low-latency inclusion for themselves, their users, or their partners, without waiting for their turn as proposer. ### Specification This specification describes the process in temporal order. #### 0. Definitions * The gas limit of the whole block is `G`. There are `n` validators: 1 proposer and `n-1` other non-proposers. * `f` fraction of the gas limit of the block, `0 < f < 1` is reserved for the main proposer. #### 1. Sub-blocks * Each validator can construct a sub-block. Sub-blocks follow this structure: ``` sub-block = rlp([version, parent_hash, fee_recipient, [transactions], signature]) ``` where: * `version = 1`, * `parent_hash` is the parent hash of the previous block. * `fee_recipient` is the EOA at which this validator wants to receive the fees included in this block. * `[transactions]` is an ordered list of transactions. Transactions in a sub-block must satisfy additional conditions described below in [Section 1.1](#11-sub-block-transactions). We explicitly allow for this list to be empty: a validator with no transactions to propose may still send a sub-block so that the proposer gets extra gas for the gas incentive region, described below. * The `signature` field is the validator signing over a hash computed as\ `keccak256(magic_byte || rlp([version, parent_hash, fee_recipient, [transactions]]))`, where `magic_byte = 0x78`, The signature ensures that this sub-block is valid only for the declared slot, and that the proposer cannot alter the order or set of transactions included in a sub-block. * The validator sends this sub-block directly to the next proposer. For each validator `i`, define `unreservedGas[i] = (1 - f) * G / n - Σ(gasLimit of transactions in sub-block[i])` ##### 1.1 Sub-block Transactions We use the two-dimensional nonce sequence to simplify transaction validity. In this construction, the nonce\_key is a `u256` value. Let `validatorPubKey` be the public key of the validator proposing a given sub-block. Let `validatorPubKey120` be the most significant 120 bits of the validator's public key. We reserve sequence keys to each validator by requiring that the first (most significant) byte of the `sequence key` is the constant byte `0x5b`, and the next 15 bytes (120 bits) encode `validatorPubKey120`. Formally, we require that: 1. The `sequence key` of any transaction in the sub-block is of the form `(0x5b << 248) + (validatorPubKey120 << 128) + x`, where `x` is a value between `0` and `2**128 - 1`. In other words, the most significant byte of the sequence key is always `0x5b`, the next 15 bytes are the most significant 120 bits of the validator's public key, and the final 32 bytes (128 bits) still allow for 2D-nonces. 2. No two validators share the same `validatorPubKey120`; each validator's reserved space is distinct. This explicit prefixing with `0x5b` ensures the reserved sequence key space is unambiguous and disjoint across validators. Sub-block proposers control all sequence keys of the form above, and can ensure that nonces are sequential within their space. **Reserved Nonce Space** To prevent transaction conflicts, each validator has a reserved nonce space. Transactions in sub-blocks use special nonce sequence keys that identify which validator proposed them. This ensures that validators can't interfere with each other's transactions. Further, we require that sub-block transactions are signed solely by the root EOA key of the address sending the transaction. #### 2. Block Construction The proposer collects sub-blocks from other validators. It now constructs a block with the following contents: ``` transactions = [list of own transactions] | [sub-block transactions] | [gas incentive transactions] ``` * `list of own transactions` are regular transactions from the proposer with `f * G` gas limit. * `sub-block transactions` are transactions from the included sub-blocks. **This includes a sub-block from the proposer itself if the proposer desires.** Nonce sequence keys with prefix `0x5b` should only appear in this section. * `gas incentive transactions` are additional transactions that the proposer can include after the sub-block transactions, with additional gas defined below. We have the following new **header field**: ``` shared_gas_limit // The total gas limit allocated for the sub-blocks and gas incentive transactions ``` ##### 2.1 System transaction The block includes a **new system transaction**, whose call data contains, for each included sub-block, the public key of the validator proposing, the `feeRecipient` for that sub-block and the signature of the sub-block. It is a no-op, and is there for execution layer blocks to be self-contained/carry all context. | Field | Value / Requirement | Notes / Validation | | --------------------- | ------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | | **Type** | Legacy transaction | | | **Position in Block** | **Last transaction** | Block is **invalid** if absent. | | **From (sender)** | `0x0000000000000000000000000000000000000000` | Zero address | | **To (recipient)** | `0x0000000000000000000000000000000000000000` | No-op | | **Calldata** | `rlp([[version, validator_pubkey, fee_recipient, signature], ...])` | Sub-block version (currently = 1), each included sub-block's validator public key, fee\_recipient, and signature. | | **Value** | `0` | No native token transfer. | | **Nonce** | 0 | | | **Gas Limit** | 0 | Does **not** contribute to block gas accounting. | | **Gas Price** | 0 | Independent of block base fee; does not pay fees. | | **Signature** | `r = 0`, `s = 0`, `yParity = false` | Empty signature designates system transaction. | #### 3. Proposer Behavior * Construct Main Block in the usual way. * Collect sub-blocks from validators, including from self. Verify signatures and gas bounds of sub-blocks. Skip (i.e., do not include) invalid or missing sub-blocks; include valid ones. Transactions from a sub-block must be contiguous in the block, but sub-blocks can be included in any order. * Compute proposer Gas Incentive section limit: ``` gasIncentiveLimit = Σ(unreservedGas[i]) for all included sub-blocks [i] ``` * Append transactions at the bottom up to this gas limit. * Construct and include the [system transaction](#21-system-transaction) at the bottom of the block. ##### 3.1 Proposer Incentives * We do not enforce censorship-resistance for the transactions at consensus layer. * Proposer is incentivized by additional gas from sub-blocks included and reciprocity. * Additional gas is unrestricted so it could include backruns etc from sub-block transactions. #### 4. Block Validity Rules: We can now define what a valid block is: 1. Gas Limits: * `[list of own transactions]` uses gas at most `f * G`. * `[sub-block transactions]`: the sum of `gas_limits` of all transactions in each sub-block is lower than the per-sub block gas limit: `Σ(gasLimit of transactions in sub-block[i]) <= (1-f) * G / n`. * `[gas incentive transactions]` use total gas `<= gasIncentiveLimit`. * General transactions gas limit from payments lane spec applies to `[list of own transactions]`. 2. Transactions with nonce sequence key prefix `0x5b` appear only in the `[sub-block transactions]`. Transactions are contiguous by validator. The `[list of own transactions]` and `[gas incentive transactions]` can use any un-reserved `sequence key`. 3. [System transaction](#21-system-transaction) is present, in the correct position, and valid (matches contents of the block). 4. Transactions in the main proposers's section and the gas incentive section are valid in the standard way (signature, nonce, can pay fees). ##### 4.1 Failures for Sub-block Transactions Even if a transaction can pay its fees when the sub-block is created (i.e., when the sub-block is sent to the proposer), it may not be able to pay its fees when the sub-block is included and the block is processed. Here is a list of possible scenarios: * **Fee Failure Scenarios**: - The Fee AMM liquidity for the user's `fee_token` is insufficient (e.g., it was used up by previous transactions in the block). - The user's balance of the `fee_token` is insufficient (e.g., the user spent that balance in previous transactions in the block). - The user or validator changed their `fee_token` preference in the block and the transaction cannot pay its fees because of the new preference. In all these scenarios, transaction is considered valid, increments the nonce, skips fee payment and execution, and results in an exceptional halt. #### 5. Transaction Fee Accounting The fee manager is updated to handle fee accounting across sub-blocks: * For the main proposer transactions, fees are paid to the main proposer's `fee_recipient` as usual. * For the sub-block transactions, fees are paid to the `fee_recipient` of the sub-block (available from the system transaction). * For the gas incentive transactions, fees are paid to the proposer's `fee_recipient`. In all cases, the fee is paid in the preferred `fee_token` of the `fee_recipient`, using liquidity from the fee AMM as necessary (i.e., `validatorTokens[fee_recipient]` from the FeeManager contract). If the `fee_recipient` has not set a preferred `fee_token`, then we use pathUSD as a fallback. import LucideDollarSign from '~icons/lucide/dollar-sign' import LucideInfo from '~icons/lucide/info' import LucideSend from '~icons/lucide/send' import LucideGlobe from '~icons/lucide/globe' import LucideWallet from '~icons/lucide/wallet' import LucideLandmark from '~icons/lucide/landmark' import LucideActivity from '~icons/lucide/activity' import LucideBot from '~icons/lucide/bot' import * as Card from "../../components/Card.tsx" ## Learn \[Notes on stablecoin use cases and Tempo's architecture] Tempo is a general-purpose blockchain optimized for payments. Tempo is designed to be a low-cost, high-throughput blockchain with user and developer features that we believe should be core to a modern payment system. Tempo was designed in close collaboration with an exceptional group of [design partners](https://tempo.xyz/ecosystem) who are helping to validate the system against real payment workloads. Here, we have written about what stablecoins are and how Tempo is designed from the ground up for the use cases they enable. #### Stablecoin use cases Stablecoins enable a wide range of payment and financial use cases across industries: import LucideExternalLink from '~icons/lucide/external-link' import LucideHammer from '~icons/lucide/hammer' import LucideMail from '~icons/lucide/mail' import * as Card from "../../components/Card.tsx" ### Partners Tempo is currently in development, with the mainnet launch coming in early 2026. Over the coming weeks, we're bringing online all the core infrastructure needed for stablecoin use cases like [remittances](/learn/use-cases/remittances), [global payouts](/learn/use-cases/global-payouts), [embedded finance](/learn/use-cases/embedded-finance), [tokenized deposits](/learn/use-cases/tokenized-deposits), [microtransactions](/learn/use-cases/microtransactions), and [agentic commerce](/learn/use-cases/agentic-commerce) so that they're available on Day 1 of the mainnet launch. The Tempo ecosystem is being built with a diverse range of best-in-class partners across stablecoin issuance, wallets & custody, compliance tooling, fraud monitoring, interoperability protocols, analytics & monitoring, orchestration and ramps, and more. We're designing the ecosystem to be global from day one, with issuers across the globe and broad local currency support. If you're interested in becoming an ecosystem partner, get in touch with us today to ensure you're fully set up for the Tempo mainnet launch coming soon. If you're looking for local partners to work with on your stablecoin deployment and want to work with one of our ecosystem partners, visit our [ecosystem page](https://tempo.xyz/ecosystem) to see the full list or [get in touch](mailto\:partners@tempo.xyz) with us to discuss how you can best integrate with Tempo. See which [developer tools](/quickstart/developer-tools) are already live on testnet. import { Callout } from 'vocs/components' import LucideFileText from '~icons/lucide/file-text' import LucideMail from '~icons/lucide/mail' import LucideRocket from '~icons/lucide/rocket' import LucideCoins from '~icons/lucide/coins' import LucideGlobe from '~icons/lucide/globe' import LucideSend from '~icons/lucide/send' import LucideBook from '~icons/lucide/book' import LucideWallet from '~icons/lucide/wallet' import LucideLandmark from '~icons/lucide/landmark' import LucideZap from '~icons/lucide/zap' import LucideActivity from '~icons/lucide/activity' import LucideBot from '~icons/lucide/bot' import LucideInfo from '~icons/lucide/info' import * as Card from "../../components/Card.tsx" import { ZoomableImage } from "../../components/ZoomableImage.tsx" ## Stablecoins \[Room-temperature superconductors for financial services.] Stablecoins offer businesses a faster, cheaper, and more programmable way to move money globally. They eliminate many of the delays, costs, and operational constraints of legacy rails, enabling use cases ranging from remittances and global payouts to automated treasury operations and agentic commerce. ### What are stablecoins? Stablecoins are digital representations of money on blockchains designed to hold a steady value. Unlike volatile cryptocurrencies such as Bitcoin or Ether, whose prices fluctuate based on market conditions, stablecoins are pegged to established currencies like the U.S. dollar or Euro. Real-world utility, increasing regulatory clarity, and growing institutional adoption are driving rapid growth in stablecoin usage. The circulating supply of stablecoins has grown more than tenfold over the past five years, reaching roughly $300 billion today. The U.S. Treasury now projects that this figure could rise to $3 trillion by 2030. ### How stablecoins work Fully-reserved fiat-backed stablecoins are issued by regulated entities ("stablecoin issuers") and are backed 1:1 with reserves such as cash and short-term government securities held at licensed financial institutions. Stablecoin issuers use a mint-and-burn process to keep the supply aligned with underlying reserves and ensure that holders can always redeem stablecoins at face value. When a user deposits fiat currency with an issuer, the issuer mints the corresponding amount of stablecoins onchain and sends them to the user. When the user later redeems stablecoins for fiat, the issuer receives the tokens, burns them, and sends the equivalent amount of fiat from its reserves to the user. With the introduction of regulatory frameworks like MiCA (Markets in Crypto-Assets) in the EU and the GENIUS Act in the U.S., stablecoins now operate under clearer rules governing reserve management, audits, and disclosures. As a result, they have become compliant, transparent, and reliable instruments for modern payments and financial operations. ### Why stablecoins matter for businesses For financial institutions, corporations, and payment providers, fully-reserved fiat-backed stablecoins enable near-instant payments that operate 24/7, work across borders, and bypass much of the friction found in legacy payment rails. They support established use cases such as remittances, global payouts, and embedded finance, while also enabling newer applications made possible by blockchain rails, including microtransactions and agentic commerce. ### Programmable money Stablecoins also open the door to programmable money: smart contracts, programs that run on the blockchain, can execute payments automatically based on predefined rules or conditions, improving the efficiency and transparency of payment processing, liquidity management, and reconciliation. For example, a global enterprise can use a smart contract to automatically top up or sweep the wallets of its subsidiaries based on balance thresholds and predefined rules. This allows routine treasury actions, such as maintaining minimum operating balances or consolidating excess funds, to run automatically onchain. In practice, the smart contract replaces functionality that would otherwise require custom integrations with multiple banking partners. In short, stablecoins provide the stability and familiarity of fiat currency with the speed and innovation of blockchains, offering institutions and enterprises of all sizes a faster, more efficient way to move value globally. ### Stablecoin use cases Stablecoins enable a wide range of payment and financial use cases across industries: Email us to learn more about payments on Tempo at [partners@tempo.xyz](mailto\:partners@tempo.xyz). import { Callout } from 'vocs/components' import LucideFileText from '~icons/lucide/file-text' import LucideMail from '~icons/lucide/mail' import LucideRocket from '~icons/lucide/rocket' import * as Card from "../../../components/Card.tsx" import { ZoomableImage } from "../../../components/ZoomableImage.tsx" ## Power AI agents with programmable money \[Enable autonomous agents to purchase goods, services, and digital resources with real-time, programmable stablecoin payments.] As autonomous agents begin taking on everything from reordering groceries to buying digital services, they need real-time, programmable money. Stablecoins provide instant global settlement and built-in programmability, making stablecoins the natural financial infrastructure for the next generation of autonomous systems. ### The rise of autonomous agents Autonomous agents are increasingly able to purchase goods and services, negotiate discounts, and manage everyday tasks without human input. An agent might reorder groceries when supplies run low, find better deals on household items, pay for digital services, manage streaming or app subscriptions, or book travel based on preferences and schedules. Further, the agentic commerce ecosystem is evolving quickly. New protocols are emerging that enable agents to interact directly with merchants: browsing offers, comparing terms, and purchasing goods or services autonomously. These protocols will allow businesses to sell directly to autonomous agents, whether they offer physical goods, digital items, compute, APIs, or data streams. ### The challenge with traditional payment rails However, for autonomous agents to function reliably, they need real-time, digitally native money that matches the speed and autonomy of their decisions. This money must be available 24/7, settle instantly, and support frequent microtransactions without friction. It also needs to work seamlessly across borders and offer in-built programmability so agents can operate without human intervention. Traditional payment rails were not designed to meet these requirements. Although card tokenization is being explored as a way for agents to pay, cards were never intended for machine-to-machine interactions. The entire card ecosystem is built around human cardholders and fraud models designed to stop bots, not support them. ### How stablecoins solve this Stablecoins fill this gap. They provide a stable, programmable representation of money that agents can use autonomously. Stablecoins combine the familiarity of fiat value with the instant, global settlement of public blockchains. Agents can hold stablecoins, pay for goods or services, and transact with other agents without relying on intermediaries. Agents also need an easy way to access payment credentials. Creating cards or card tokens requires coordination with issuers, and, in many cases, manual workflows. In contrast, onchain wallets can be provisioned instantly, at massive scale, and without relying on banking partners. Wallets give agents a ready-to-use financial instrument the moment they are created. ### Benefits beyond speed and cost For developers building agentic platforms, stablecoins and wallets are far simpler and more flexible to work with than traditional payment instruments. Wallets can be created instantly and at scale, without issuer partnerships or card-provisioning workflows. Stablecoins also make it easy to top up agent balances instantly whenever additional funds are needed. Developers can also set spending limits, enforce policy rules, and implement automatic top-ups directly in code, ensuring agents always have the funds they need without human intervention. Stablecoin balances can be monitored and rebalanced programmatically, enabling precise liquidity management and predictable spending across large fleets of autonomous agents. As agentic commerce begins to scale, the need for a more native payment rail becomes unavoidable. Stablecoins provide exactly the properties agents require: real-time global settlement, programmable behavior, and the ability to operate without intermediaries or issuer dependencies. They offer payment infrastructure that aligns with how agents think, act, and transact. ### Building with Tempo Autonomous agents require payment rails that are as programmable and tireless as they are. Tempo offers the throughput and sub-second settlement needed to support agent fleets at scale, without the friction of traditional card networks. If you are architecting payment flows for autonomous systems, we can help you understand how to implement automated wallet provisioning and policy-based spending controls directly into your agent's code. import { Callout } from 'vocs/components' import LucideFileText from '~icons/lucide/file-text' import LucideMail from '~icons/lucide/mail' import LucideRocket from '~icons/lucide/rocket' import * as Card from "../../../components/Card.tsx" import { ZoomableImage } from "../../../components/ZoomableImage.tsx" ## Bring embedded finance to life with stablecoins \[Enable platforms and marketplaces to streamline partner payouts, lower payment costs, and launch more rewarding loyalty programs.] Stablecoins bring the true promise of embedded finance to life. Their real-time, global nature lets platforms and marketplaces streamline partner and merchant payouts, lower the cost of payment acceptance and launch more rewarding loyalty programs for consumers. This creates a better experience for users and more efficient backend processes for platforms and marketplaces. ### The challenge with traditional embedded finance Embedded finance was meant to help platforms and marketplaces offer smoother, more integrated user experiences. Yet its impact remains constrained by traditional payment rails, whose delays, fees, and operational complexity undermine much of the value embedded finance aims to provide. For example, gig economy and creator work happens around the clock, while traditional payout rails do not. ACH transfers, while inexpensive, operate only during weekday banking windows and cannot support real-time payouts. Even when platforms offer instant payouts via card networks, these transfers carry higher fees, and the costs are often passed on to partners. Merchant payouts on marketplaces face similar challenges. Settlement is often delayed by banking windows, regional cutoff times, and inconsistent payment rails across countries. Cross-border merchant payments frequently move through multiple intermediaries, adding cost and introducing unpredictability in when funds will arrive. ### How stablecoins solve this By embedding blockchain wallets directly into their applications, these platforms can move onto modern, always-on payment rails. Partners, creators, and merchants can receive earnings in stablecoins far faster than through traditional methods, with settlement occurring onchain in seconds. Stablecoins also make these payouts inherently global, removing many of the barriers of regional banking systems. Once partners, creators and merchants receive their earnings in the embedded wallet, they can manage those funds directly within the same application they use for work. The wallet can hold stablecoin balances, allow payouts to linked bank accounts, or support spending for goods and services through a stablecoin-linked payment card. Stablecoins and embedded wallets can support far more than payouts. Marketplaces can also use stablecoins to collect consumer payments, deliver instant refunds, and issue loyalty rewards. Stablecoin payments do not have the settlement delays and interchange fees of card networks, making them a faster and cheaper payment option. ### Benefits beyond speed and cost By significantly reducing the cost of collecting payments from consumers and paying out partners, creators, and merchants, marketplaces can redirect more value toward customer and merchant benefits. Lower payment costs give merchants more flexibility to reward loyal customers with better offers, reimagining their loyalty programs. After all, merchants ultimately fund card rewards through interchange fees. For platforms, embedded wallets and stablecoin rails replace a patchwork of payment processors, banking partners, and regional settlement rules with a single, consistent infrastructure. Reconciliation relies on unified onchain records rather than scattered bank statements, and operational teams spend far less time managing exceptions or coordinating across time zones. Faster payouts, lower-cost payment acceptance, and more dynamic loyalty programs are only the beginning of what stablecoins bring to embedded finance. As stablecoins become the foundation for agentic commerce, platforms will gain new opportunities to create value for customers and expand what embedded finance can offer. ### Building with Tempo By providing a low-cost, high-speed settlement layer, Tempo enables platforms to offer financial products that were previously cost-prohibitive on legacy rails. Whether you are looking to embed wallets for gig workers or streamline complex marketplace settlements, our team can guide you through the wallet integration patterns that best fit your user experience. We are available to help you evaluate the technical lift and commercial impact of moving your platform's payments onchain. import { Callout } from 'vocs/components' import LucideFileText from '~icons/lucide/file-text' import LucideMail from '~icons/lucide/mail' import LucideRocket from '~icons/lucide/rocket' import * as Card from "../../../components/Card.tsx" import { ZoomableImage } from "../../../components/ZoomableImage.tsx" ## Pay your global workforce instantly \[Deliver faster, cheaper, and more predictable cross-border payouts to employees and contractors around the world with stablecoins.] Stablecoins can help payroll platforms, freelancer marketplaces, and companies with distributed teams deliver faster, cheaper, and more predictable cross-border payouts by eliminating slow banking rails and expensive FX conversions. This improves the payout experience for employees and contractors while giving companies meaningful operational efficiencies at a global scale. ### The challenge with global payroll Managing global payroll is both complex and expensive. Businesses must either rely on slow, expensive, unpredictable cross-border transfers to pay employees directly, or first move liquidity to local subsidiaries to access domestic payment rails. Each domestic rail, in turn, comes with its own rules, banking holidays, cutoff times, formats, and fees. This complexity results in a patchwork of manual workflows that is burdensome for finance teams and often frustrating for employees waiting on delayed transfers. Stablecoins offer a way to simplify this process by reducing cost, improving predictability, and removing much of the operational overhead associated with traditional cross-border payments. ### How stablecoins solve this Blockchains are global by design, allowing companies to send stablecoin payouts directly to employees' wallets anywhere in the world without relying on local banks or intermediaries. These transfers typically cost less than a cent and arrive within seconds, offering far more consistency than traditional international payment rails. Companies can also choose to distribute funds through their subsidiaries. In this model, a business can convert funds into stablecoins at the headquarters level, such as in the United States, and distribute those funds to subsidiary wallets around the world. Liquidity moves to subsidiaries in seconds rather than days, removing the need to pre-fund subsidiary accounts in advance. Subsidiaries can then handle payouts to local employees. ### Benefits beyond speed and cost Employees receiving stablecoins can choose how they want to use their earnings. They can hold stablecoins and spend them directly through stablecoin-linked payment cards, or convert them into fiat and withdraw the funds to a local bank account whenever they choose. This gives workers flexibility and control over how they manage their income. Stablecoins also reduce the foreign exchange complexity that comes with global payroll. Instead of converting funds into multiple local currencies and dealing with unpredictable spreads and fees, companies can make payouts in a single currency, such as USD stablecoins. Employees can then convert stablecoins to their desired currency when it's most convenient for them. This simplifies treasury operations and provides more options for workers. Finally, reconciliation becomes significantly simpler. Because stablecoin payouts settle onchain, every transaction has a clear record. Companies no longer need to track separate reporting formats from multiple domestic banks or reconcile transactions that settle at different speeds. Instead, they rely on a single, unified ledger that provides immediate visibility into outgoing payments, balances, and settlement status. Global payouts with stablecoins already deliver meaningful benefits for both employers and employees. As familiarity grows and merchant acceptance expands, stablecoins are likely to become a preferred payment method not only for international payouts, but for domestic payroll as well. With lower costs, real-time settlement, and programmability, stablecoins bring payroll systems closer to the always-on, digital nature of today's workforce. ### Building with Tempo Designed for high-volume, global disbursement, Tempo allows organizations to bypass the fragmentation of local banking rails and reach employees and contractors directly. We work closely with payroll providers and platforms to design stablecoin payout flows that are compliant, efficient, and user-friendly. If you are interested in seeing how a unified onchain ledger can simplify your global payroll operations and remove FX friction, let's discuss the integration requirements and coverage capabilities. import { Callout } from 'vocs/components' import LucideFileText from '~icons/lucide/file-text' import LucideMail from '~icons/lucide/mail' import LucideRocket from '~icons/lucide/rocket' import * as Card from "../../../components/Card.tsx" import { ZoomableImage } from "../../../components/ZoomableImage.tsx" ## Enable true pay-per-use pricing \[Build usage-based billing, pay-per-use APIs, and machine-to-machine services with real-time, sub-cent stablecoin payments.] Stablecoins unlock microtransaction-based pricing models that legacy payment rails simply cannot support. With real-time, sub-cent payments and 24/7 global settlement, developers can build usage-based billing, pay-per-use APIs, and machine-to-machine services that settle instantly without the fixed fees or delays of traditional payment systems. ### The challenge with traditional payment rails Digital services are increasingly shifting toward usage-based models, where users pay only for what they consume. This includes everything from API calls and AI inference requests to streaming content, and IoT services. Yet traditional payment rails were never designed for extremely small, frequent payments, making microtransactions impractical or impossible for most businesses today. Card networks apply fixed fees to every transaction, making any payment under a dollar uneconomical. Even instant rails like FedNow, while cheaper than cards, cannot support usage-based billing where each action costs only a few cents. This forces many digital products into subscriptions or prepaid balances instead of offering the more user-friendly and transparent pay-as-you-go model. ### How stablecoins solve this Stablecoins change this dynamic by enabling payments that cost a fraction of a cent and settle in seconds, making true microtransactions economically feasible at scale. They achieve this by removing the chain of intermediaries found in traditional payment rails, eliminating the delays, costs, and failure points that make small, frequent payments impractical today. Programmability further expands what microtransactions can support. Smart contracts can meter usage, enforce payment rules, and top up wallet balances automatically. Developers can charge per API request, per compute cycle, or per any digital action, without building custom billing logic or relying on intermediaries. Microtransactions also unlock machine-to-machine payments. Autonomous agents, bots, and IoT devices can pay for data, services, or compute in real time without human intervention. A device can pay another device for sensor data; an AI agent can pay for inference or model access; a drone can pay for map segments or airspace permissions. Stablecoins also enable pay-as-you-go digital commerce for consumers. Instead of monthly subscriptions, consumers can pay per article, per minute of video, per chapter of a book, or per stream of a song. Creators and platforms can monetize content more flexibly, while users pay only for what they actually consume. ### Benefits beyond speed and cost From a developer's perspective, building microtransaction-powered services is becoming increasingly straightforward. Stablecoins provide a simple, programmable payment primitive. At the same time, new protocols are emerging that standardize usage-based payments, authorization, and settlement, making it even easier to launch pay-per-use applications on top of stablecoin rails. Finally, onchain microtransactions improve reconciliation. Instead of aggregating thousands of small events into batched invoices that must be reconciled manually, stablecoins provide a unified ledger of each individual payment. This reduces operational overhead and simplifies financial reporting for businesses handling large volumes of small-value transactions. Ultimately, stablecoin-powered microtransactions are not simply about reducing payment friction. They unlock new business models built around real-time data and usage. By enabling instant, sub-cent payments, stablecoins let companies price, deliver, and monetize digital services in ways that were never feasible before, creating meaningful competitive advantages for those who adopt them early. ### Building with Tempo True microtransactions require a blockchain that eliminates the economic floor of traditional payment fees. Tempo's architecture is designed to make sub-cent payments viable, unlocking entirely new business models for digital services, APIs, and content platforms. If you are calculating the economics of usage-based billing or machine-to-machine payments, we can help you run the numbers and explore the technical feasibility of building on Tempo. import { Callout } from 'vocs/components' import LucideFileText from '~icons/lucide/file-text' import LucideMail from '~icons/lucide/mail' import LucideRocket from '~icons/lucide/rocket' import * as Card from "../../../components/Card.tsx" import { ZoomableImage } from "../../../components/ZoomableImage.tsx" ## Send money home faster and cheaper \[Deliver instant cross-border payments to your customers around the world with stablecoins on Tempo.] Stablecoins can help financial institutions, neobanks, and remittance providers deliver faster, cheaper, and more predictable cross-border payments by removing dependence on correspondent banking networks and pre-funded accounts. This improves the customer experience while creating meaningful operational efficiencies for the organizations that move money across borders. ### The challenge with traditional remittances Cross-border payments are notoriously slow, expensive, and unpredictable. A single transfer often passes through multiple intermediaries, each adding cost, introducing delays, and increasing the risk of errors. Newer "instant" cross-border services improve the user experience, but typically require remittance companies to hold liquidity on both sides of a payment corridor, which increases operational and capital costs. ### How stablecoins make remittances better Stablecoins can address these challenges by enabling cheap and near-instant cross-border payments. Beyond peer-to-peer transfers, where users send stablecoins directly to each other, three stablecoin-based models are now widely used in the remittance industry: #### Direct transfers A sender, for example, in the United States, can onramp to stablecoins within the mobile banking app and transfer them directly to a recipient abroad. The recipient can choose to hold the funds in stablecoins, spend them with a stablecoin-linked card, or off-ramp into local currency and deposit into a bank account. #### Orchestrated payments A sender initiates a cross-border payment from a mobile app, and the recipient receives funds directly into their bank account. Stablecoins are routed through a stablecoin orchestration platform that automatically converts them into local currency, preserving the speed and low cost of stablecoin transfers while delivering a familiar, traditional cross-border payment experience for both sender and recipient. #### Backend settlement Stablecoins operate entirely behind the scenes. A remittance company uses stablecoins to move liquidity between its own entities across jurisdictions, eliminating the need for correspondent banks and removing the requirement to maintain expensive, pre-funded liquidity on each side of the corridor. In each case, blockchains and stablecoins help eliminate the patchwork of correspondent banks in between the sender and the recipient, or the need to hold liquidity to emulate "instant" payments. Stablecoin transfers typically cost less than a cent and arrive within seconds, introducing noticeable cost reduction and increasing speed. ### Benefits beyond speed and cost Beyond the speed and cost improvements, stablecoin-based remittances offer a far smoother user experience for both senders and recipients. With no correspondent banks or intermediate hops, there are fewer points of failure and no unexpected holds, returns, or error paths. Transfers settle onchain in seconds, at any time of day, and the recipient sees the funds immediately. Further, users around the world increasingly value being able to hold USD-denominated stablecoins. These provide a simple and affordable way to send and receive remittances and pay for global digital services such as LLMs, software subscriptions, and online content platforms. Onchain settlement also delivers major reconciliation benefits. Each transaction is recorded in real time on a single ledger, giving remittance providers instant visibility into balances and flows. This reduces reliance on delayed bank statements, minimizes reconciliation errors from intermediary hops, and streamlines audit and compliance through a single, authoritative onchain record. ### Building with Tempo Tempo is purpose-built to handle the volume and cost requirements of modern remittance corridors, enabling providers to move beyond the limitations of correspondent banking. We understand that transitioning to onchain settlement involves navigating complex liquidity and operational questions. If you are evaluating how to restructure your remittance corridors for higher speed and lower cost, our team can help you map out the architecture for your specific regions. We invite you to explore the technical and operational improvements stablecoins can bring to your flows. import { Callout } from 'vocs/components' import LucideFileText from '~icons/lucide/file-text' import LucideMail from '~icons/lucide/mail' import LucideRocket from '~icons/lucide/rocket' import * as Card from "../../../components/Card.tsx" import { ZoomableImage } from "../../../components/ZoomableImage.tsx" ## Move treasury liquidity instantly across borders \[Enable corporate treasury teams to improve liquidity management across banks, currencies, and regions with tokenized deposits.] Tokenized deposits, much like stablecoins, can help corporate treasury teams meaningfully improve liquidity management across banks, currencies, and regions by enabling near-instant movement of funds and real-time visibility into cash positions. As corporate treasury moves onchain, tokenized deposits represent a major opportunity for financial institutions to capture new market share. ### The challenge with traditional treasury management Operating across multiple geographies requires companies to maintain relationships with multiple banks, manage balances in several currencies, and navigate an array of local settlement systems, cutoff times, and time zones. For global treasury teams, this results in a fragmented operational landscape with inconsistent processes across regions. Moving liquidity between a company's own accounts can take days and require subsidiaries to hold excess cash "just in case." Visibility into cash positions is incomplete, workflows vary by region, and reconciling activity across dozens of accounts becomes an ongoing operational burden. The result is inefficient use of capital and a patchwork of manual processes for controls and compliance. ### How tokenized deposits and stablecoins solve this Tokenized deposits and stablecoins offer a path toward simplifying this complexity. Instead of managing dozens of accounts across multiple banks, companies can operate a single wallet per subsidiary. Sitting alongside traditional bank accounts, these digital wallets become a natural extension of corporate treasury operations and provide a more efficient way to manage global liquidity. Each wallet can hold stablecoins, tokenized deposits, and even tokenized money market funds on the same unified onchain infrastructure. Tokenized deposits and stablecoins introduce a shared set of benefits that transform how corporates store, move, and deploy liquidity: * **Liquidity can be moved 24/7**, across time zones, and across borders, removing the delays of local settlement systems and cutoff times; * **Reconciliation becomes simpler** through onchain settlement records instead of disparate bank statements and siloed reporting systems; * **Idle balances can be deployed more efficiently**, improving working-capital usage and enabling immediate access to interest-bearing tokenized assets; * **Treasury workflows such as approvals, sweeps, and controls can be automated** with smart contracts, reducing manual processes and operational overhead. ### Understanding tokenized deposits vs. stablecoins As digital wallets become integrated into corporate treasury operations, it is important to understand how stablecoins and tokenized deposits differ. They are distinct instruments with different issuers, obligations, and regulatory treatment: * **Tokenized deposits** are commercial bank deposits issued on a blockchain. They remain a liability of the issuing bank and sit within the existing banking regulatory framework, behaving much like traditional deposits, but with the added benefits of programmability and real-time settlement. * **Stablecoins**, by contrast, are issued by regulated non-bank stablecoin providers and backed 1:1 with reserves such as cash and short-term government securities. They are designed to maintain a stable value relative to currencies like the U.S. dollar or Euro and can move across blockchains unaffected by cutoff times, banking hours, or regional settlement windows. ### The opportunity for financial institutions For financial institutions, tokenized deposits represent a major opportunity to strengthen existing corporate relationships and win market share as treasury infrastructure moves onchain. Large enterprises are already adopting stablecoins because they provide the speed, global reach, and programmability that traditional banking infrastructure cannot match. Tokenized deposits give financial institutions a way to offer many of these same benefits while maintaining the trust and regulatory certainty that corporates rely on. Tokenized deposits are also far more familiar to large corporations: they behave like traditional deposits, remain a liability of the issuing bank, and fit naturally into existing accounting and compliance models. The main challenge for banks today is interoperability. Most tokenized deposit pilots run inside the closed perimeter of a single institution, limiting their usefulness in multi-bank, multi-region treasury setups. Corporations will only adopt tokenized deposits at scale if they can move funds seamlessly across banks and easily convert between tokenized deposits, stablecoins, and other tokenized assets. Public blockchains provide this missing bridge. They allow tokenized deposits from different banks to coexist on shared infrastructure, enabling frictionless swaps between tokenized deposits and stablecoins, and supporting cross-bank liquidity movement with the same speed as stablecoin rails. Banks that issue tokenized deposits on public chains gain the interoperability corporates require, while strengthening their role at the center of onchain financial flows. ### Building with Tempo Tempo provides the neutral, high-performance infrastructure required for tokenized deposits and stablecoins to operate side-by-side. For treasury teams looking to modernize their liquidity stack, the challenge is often interoperability between these assets and existing banking systems. We collaborate with financial institutions and enterprises to model how these assets can coexist within your current treasury operations. Let's examine how onchain rails can fit into your broader liquidity management strategy. import LucideMail from '~icons/lucide/mail' import LucideRocket from '~icons/lucide/rocket' import * as Card from "../../../components/Card.tsx" ## Onchain FX An overwhelming majority of cross-border payments facilitated by stablecoins use what is known as the stablecoin sandwich: the onchain leg is conducted in stablecoins, typically USD-denominated, with on- and off-ramps converting from/to a local currency. ### Accessing FX Liquidity Onchain Tempo is exploring a design to enable stablecoin users to access FX liquidity directly onchain, including via a set of regulated non-USD stablecoin issuers. This removes the need for off-chain currency conversion and enables seamless multi-currency flows entirely onchain. When available, users will be able to: * Exchange between USD and non-USD stablecoins directly onchain * Access competitive FX rates through decentralized liquidity pools * Execute cross-border payments without relying on traditional FX providers * Reduce settlement times and costs associated with currency conversion ### Multi-Currency Fee Payments When available, Tempo will also allow users to pay for network fees in currencies beyond USD. This means users would be able to interact with Tempo in their preferred currency without needing to hold USD stablecoins specifically for transaction fees. ### Coming Soon Onchain FX is currently in development and will be available in a future release. This feature is being designed in close collaboration with regulated stablecoin issuers to ensure it meets compliance requirements while providing the flexibility and efficiency that global payment flows demand. If you're interested in multi-currency payment flows and want to explore how onchain FX can benefit your use case, reach out to our team. ### Help design this feature import LucideCoins from '~icons/lucide/coins' import LucideZap from '~icons/lucide/zap' import LucideShield from '~icons/lucide/shield' import LucideRepeat from '~icons/lucide/repeat' import LucideArrowLeftRight from '~icons/lucide/arrow-left-right' import LucideTrendingUp from '~icons/lucide/trending-up' import LucideEyeOff from '~icons/lucide/eye-off' import LucideMail from '~icons/lucide/mail' import LucideRocket from '~icons/lucide/rocket' import * as Card from "../../../components/Card.tsx" ## Tempo \[The payments-first blockchain] Tempo is a general-purpose blockchain optimized for payments, built to deliver instant, deterministic settlement, predictably low fees, and a stablecoin-native experience. Tempo's public testnet launched on December 9, 2025, and is now available for anyone to start building on. Tempo was designed in close collaboration with an exceptional group of [design partners](https://tempo.xyz/ecosystem) who are helping to validate the system against real payment workloads. The general-purpose programmability of the blockchain provides functionality directly in support of our global payments mission: stablecoin interoperability, onchain FX, compliant privacy, and more. Tempo will be a permissionless, decentralized chain. The Tempo client is open source under the Apache license, and anyone can run a node or sync the chain. ### Core Features Explore the key features that make Tempo purpose-built for payments: import LucideMail from '~icons/lucide/mail' import LucideRocket from '~icons/lucide/rocket' import LucideLayers from '~icons/lucide/layers' import LucideHeartHandshake from '~icons/lucide/heart-handshake' import LucideClock from '~icons/lucide/clock' import LucideFingerprint from '~icons/lucide/fingerprint' import * as Card from "../../../components/Card.tsx" ## Tempo Transactions \[Modern, optimized, and highly scalable blockchain transactions] Tempo has built-in support for gas sponsorship, batch transactions, scheduled payments, and modern authentication through passkeys. These features are often grouped together as "smart accounts" or "account abstraction." On other chains, even when available, these are generally add-on functionalities that require third-party providers to unlock. By natively enabling these features at the protocol level, developers on Tempo can deploy payment logic without managing additional middleware or custom contracts, and can build to enshrined standards. ### Batched Payments Payment processors and platforms often need to send thousands of payments at once (e.g., payroll runs, merchant settlements, customer refunds). Tempo supports batch transactions where multiple operations execute atomically in a single transaction. This unlocks high-volume use cases: orchestrators can submit thousands of payouts as a single operation, rather than submitting them one-by-one and tracking individual success or failure. If any operation in the batch fails, the entire batch reverts, ensuring atomic execution across all payments. This is critical for payment operators who need guaranteed settlement guarantees for their workflows. Learn more in the [batched calls specification](/protocol/transactions#batch-calls). ### Fee Sponsorship Applications often want to pay transaction fees on behalf of their users. For instance, to simplify onboarding, to improve the user experience, or to remove friction from their payment flows. Tempo's protocol-level fee sponsorship allows an account to sign a transaction while a separate sponsor (typically the application) pays the gas fee. This means end users can interact with your application without holding any tokens for fees, dramatically lowering the barrier to entry. Learn more by following the [guide on sponsoring user fees](/guide/payments/sponsor-user-fees). ### Scheduled Payments The Tempo transaction type includes scheduling as a protocol feature. Users can specify a time window for transaction execution, and validators will include the transaction when it becomes valid. This enables "set and forget" payment operations directly at the protocol level, enabling recurring payments like subscriptions or scheduled disbursements. No need for external automation services or off-chain infrastructure to manage recurring transactions. Learn more in the [Tempo Transactions specification](/protocol/transactions#scheduled-transactions). ### Modern Authentication Tempo supports passkey authentication through WebAuthn/P256 signature validation, built directly into the protocol. Users can authenticate with the same biometrics (fingerprint, Face ID) they already use for other apps. Their keys are stored in their device's secure enclave, and passkeys sync across devices via services they already use such as iCloud Keychain or Google Password Manager. This way, users don't need to secure a 12 or 24-word seed phrase for traditional wallets. For payment applications, this means onboarding flows can be as simple as existing consumer apps, without sacrificing security. Tempo uses an [EIP-2718](https://eips.ethereum.org/EIPS/eip-2718) transaction type with native support for these features. Read the [access keys specs](/protocol/transactions#access-keys) or learn by [following a guide](/guide/use-accounts/embed-passkeys). import LucideMail from '~icons/lucide/mail' import LucideRocket from '~icons/lucide/rocket' import LucideLayout from '~icons/lucide/layout' import LucideDollarSign from '~icons/lucide/dollar-sign' import LucideFileText from '~icons/lucide/file-text' import LucideShield from '~icons/lucide/shield' import LucideArrowLeftRight from '~icons/lucide/arrow-left-right' import * as Card from "../../../components/Card.tsx" ## TIP-20 Tokens \[Tempo's native stablecoin token standard] Tempo extends the [ERC-20 standard](https://ethereum.org/en/developers/docs/standards/tokens/erc-20/) used on EVM chains (Ethereum, Base, Arbitrum) with additional features, and enshrines them in protocol as the TIP-20 contract suite. These features are purpose-built for payment use cases at scale. ### Predictable Payment Throughput Tempo has dedicated payment lanes: reserved blockspace for payment transactions (specifically, TIP-20 token transfers) that other applications cannot consume. Payments have guaranteed blockspace reserved at the protocol level and don't compete with other traffic like NFT mints, liquidations, or high-frequency contract calls. Even if there are extremely popular applications on the chain competing for blockspace, payroll runs or customer disbursements execute predictably. Fees stay low and stable even when other network activity spikes, with a target of one-tenth of a cent per payment transaction. For payment processors, that means no "downtime" from congestion, and predictable economics for high-volume flows. There is no "noisy neighbor problem" like on traditional blockchains, where blockspace can fill up or cost orders of magnitude more during times of intense activity. Learn more in the [payment lanes specification](/protocol/blockspace/payment-lane-specification). ### Low, Predictable Fees, in Stables Transaction fees can be paid directly in USD-denominated stablecoins. This removes the need for volatile gas tokens and lets payment applications operate entirely in the same currency as their underlying flows, ensuring predictable costs and simpler accounting. For wallets and custodians, this removes the need to hold a balance of new cryptoassets just to facilitate stablecoin payments. Users can pay fees in any USD stablecoin, and validators can receive fees in any USD stablecoin, with the protocol automatically converting between them using onchain liquidity through Tempo's built-in DEX. Costs are predictable and low: TIP-20 transfers target one-tenth of a cent per transaction. This removes a major barrier for mainstream adoption. Users can interact with Tempo using only the stablecoins they're already familiar with, without needing to understand or manage volatile crypto assets. Learn more in the [fees specification](/protocol/fees). ### Native Reconciliation Tempo's TIP-20 tokens can natively attach a short memo directly to each transfer. This mirrors traditional payment systems, where every transaction carries context (e.g., an invoice number, a customer ID, a cost center, or a simple reference note) for backend systems to automatically match payments to internal records. For larger memos, Tempo supports flexible approaches where only a commitment (like a hash or locator) travels onchain while the full data (including PII) lives off-chain. This means finance teams can automatically match payments to invoices and payment processors can embed the structured data their systems need without custom solutions or integration with third-party reconciliation infrastructure. Learn more in the [TIP-20 specification](/protocol/tip20/overview). ### Built-in Compliance Stablecoin (and other regulated asset) issuers operate under regulatory requirements. They need to enforce whitelists (only approved addresses can transact) or blacklists (sanctioned addresses cannot transact). Tempo provides a shared compliance infrastructure through the TIP-403 Policy Registry. An issuer can thus create a single policy with their compliance rules, and multiple tokens they issue can adopt that policy. When the compliance manager updates the policy, all tokens using it automatically enforce the new rules, unlike traditional blockchains where an issuer must update each contract one-by-one. Learn more in the [TIP-403 policy specification](/protocol/tip403/overview). ### Built-in Stable Asset DEX Tempo includes a native decentralized exchange optimized for stablecoins and tokenized deposits. This means users can pay fees in any USD stablecoin, and validators can receive fees in any USD stablecoin, with the protocol automatically converting between them using onchain liquidity. Learn more about the [stablecoin DEX](/protocol/exchange). import LucideMail from '~icons/lucide/mail' import LucideRocket from '~icons/lucide/rocket' import LucideGithub from '~icons/lucide/github' import LucideGauge from '~icons/lucide/gauge' import LucideCheckCircle from '~icons/lucide/check-circle' import * as Card from "../../../components/Card.tsx" ## Performance \[Tempo's performant blockchain architecture] All of Tempo's payments-first features are built on a foundation of a highly performant blockchain. Tempo delivers the throughput and finality characteristics that payment applications require. ### High Throughput Tempo is built on the [Reth](https://reth.rs/) SDK, the most performant and flexible EVM (Ethereum Virtual Machine) execution client. Reth is also relied upon by several other blockchains including Ethereum, Base and Arc. Tempo houses the team that built and maintains Reth, and several other pieces of core blockchain developer software such as [Foundry](https://getfoundry.sh/). We are already benchmarking 20,000 TPS on testnet with a clear line of sight to an order of magnitude higher by mainnet. This throughput is critical for payment use cases. Whether processing payroll for thousands of employees, settling marketplace transactions, or handling microtransactions at scale, Tempo's performance ensures that your payment flows won't be bottlenecked by blockchain capacity. Tempo is open source. Read the code on [GitHub](https://github.com/tempoxyz/tempo)! ### Fast Finality Public blockchains that support financial activity need to balance two contradictory demands. On the one hand, the blockchain needs fast finality: a payment made on the blockchain should be quickly confirmed as final, with no risk of being "re-orged" out as can happen on many blockchains today. On the other hand, the blockchain also needs to degrade gracefully under suboptimal network conditions. Tempo uses Simplex Consensus (via [Commonware](https://www.commonware.xyz/)), which provides fast, sub-second finality in normal conditions while maintaining graceful degradation under adverse networks. Tempo uses Byzantine-fault tolerant consensus with four validators on testnet. Blocks are finalized every \~0.5 seconds, and once a block is marked as final, transactions in that block are guaranteed to be included in the chain. This cutting-edge consensus algorithm optimally balances speed with resilience. For payment applications, this gives payment operators the same settlement certainty they expect from existing financial systems, with speed that matches best-in-class blockchains. This is critical for use cases like point-of-sale systems, instant settlements, and cross-border transfers where users expect immediate, irreversible confirmation. Learn more about consensus in the [blockspace specification](/protocol/blockspace/overview). import LucideMail from '~icons/lucide/mail' import LucideRocket from '~icons/lucide/rocket' import * as Card from "../../../components/Card.tsx" ## Privacy Tempo is also developing opt-in privacy features designed to coexist with issuer compliance requirements. This functionality will enable private balances and transfers while maintaining the auditability and reporting capabilities that regulated issuers need. ### Private Token Standard for Stablecoin Issuers Traditional blockchains make all transaction data publicly visible, which creates challenges for businesses and consumers who need confidentiality. At the same time, regulated stablecoin issuers must maintain compliance and auditability. We're currently working on multiple designs which provide: * **Private balances**: Account balances that are hidden from public view while remaining auditable by authorized parties * **Confidential transfers**: Transaction amounts and participants that can be shielded from the public ledger * **Selective disclosure**: Issuers and regulators that can maintain the visibility they need for compliance without exposing sensitive business data publicly ### Use Cases for Privacy When available, private tokens will enable: **Sensitive business transactions**: Companies will be able to conduct payroll, vendor payments, and treasury operations without revealing confidential financial information to competitors or the public. **Confidential consumer balances**: Users will be able to hold and transact with stablecoins without publicly broadcasting their account balances or spending patterns. **Privacy-preserving payment flows**: Platforms will be able to process payments while protecting user privacy and maintaining confidentiality in sensitive transactions. All of this is achieved while preserving the compliance features that make stablecoins viable in regulated markets. Issuers will be able to maintain the ability to monitor, report, and enforce policies as required by regulation. ### Coming Soon The native private token standard is currently in development and will be available in a future release. This feature is being designed in close partnership with regulated stablecoin issuers to ensure it meets both privacy needs and regulatory requirements. If you're interested in private payment flows and want to explore how privacy features can benefit your use case, reach out to our team. ### Help design this feature import * as Card from "../../components/Card.tsx" import * as Demo from '../../components/guides/Demo.tsx' import * as Step from '../../components/guides/steps' import LucideFile from '~icons/lucide/file' import LucideSignature from '~icons/lucide/signature' ## !Replace Me! {/* Short description of the guide. What we are building, why we are building it, and what we will learn. */} Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam elementum odio ante, sit amet tincidunt leo scelerisque vitae. Donec placerat imperdiet nibh in efficitur. Nam pharetra euismod tortor ac suscipit. Quisque nec ultrices nisl. Sed in magna sapien. Duis vel risus sed leo gravida volutpat a id libero. Pellentesque ut nunc vel turpis tristique bibendum ut eu mauris. Aliquam aliquet nunc orci, eu mattis nisi hendrerit sed. Donec volutpat dolor lectus, sit amet facilisis arcu pretium nec. ### Steps {/* Steps to follow to get the demo working and fully functioning end-to-end */} :::steps #### Lorem Lorem ipsum dolor sit amet, consectetur adipiscing elit. #### Ipsum Lorem ipsum dolor sit amet, consectetur adipiscing elit. ::: ### Recipes {/* Any peripheral things you can do beyond the above steps */} #### Foo Lorem ipsum dolor sit amet, consectetur adipiscing elit. #### Bar Lorem ipsum dolor sit amet, consectetur adipiscing elit. ### Best Practices {/* Any best practices or tips to follow when using this feature */} #### Foo Lorem ipsum dolor sit amet, consectetur adipiscing elit. #### Bar Lorem ipsum dolor sit amet, consectetur adipiscing elit. ### Learning Resources {/* Outlink to any useful links to read in /documentation/* */} import * as Demo from '../../../components/guides/Demo.tsx' import * as Step from '../../../components/guides/steps' import * as Token from '../../../components/guides/tokens' ## Add Funds to Your Balance Get test tokens to start building on Tempo testnet. ### Direct Access You can access the faucet directly here in the docs. ### Testnet Faucet RPC The public testnet also offers an RPC endpoint for requesting test tokens. The faucet endpoint is only available at the official Tempo testnet RPC endpoint. Request test tokens using the `tempo_fundAddress` RPC method: ```bash cast rpc tempo_fundAddress \ --rpc-url https://rpc.testnet.tempo.xyz ``` Replace `` with your wallet address. ### What You'll Receive The faucet provides test stablecoins: * **pathUSD** - `0x20c0000000000000000000000000000000000000` * **AlphaUSD** - `0x20c0000000000000000000000000000000000001` * **BetaUSD** - `0x20c0000000000000000000000000000000000002` * **ThetaUSD** - `0x20c0000000000000000000000000000000000003` Each request drips a sufficient amount for testing and development. ### Verify Your Balance After requesting tokens, verify your balance: ```bash # Check AlphaUSD balance cast erc20 balance 0x20c0000000000000000000000000000000000001 \ \ --rpc-url https://rpc.testnet.tempo.xyz ``` import { Callout } from 'vocs/components' ## Batch Transactions One of the most powerful features of the [Tempo Transaction](/protocol/transactions/spec-tempo-transaction) type is batching multiple operations into a single transaction. This allows you to execute several actions atomically (i.e., they all succeed or all fail together). This helps reduce gas costs, prevents partial failures, and creates better user experiences by combining multiple steps into one transaction. Batch transactions are a feature of Tempo's [TempoTransaction type](/protocol/transactions/spec-tempo-transaction). See the TempoTransaction spec for complete details on batching and other features. ### Example: Sending a Batch of Transactions :::code-group ```ts [batchTransactions.ts] const { address, hash, id } = await client.sendTransaction({ // [!code focus] calls: [{ // [!code focus] to: tokenAddress, // [!code focus] data: transferCalldata // Transfer tokens // [!code focus] }, // [!code focus] { // [!code focus] to: anotherTokenAddress, // [!code focus] data: approveCalldata // Approve spending // [!code focus] }, // [!code focus] { // [!code focus] to: dexAddress, // [!code focus] data: swapCalldata // Execute swap // [!code focus] }], // [!code focus] }) // [!code focus] ``` ```ts [viem.config.ts] // [!include ~/snippets/setup.ts:setup] ``` ::: ### How It Works Unlike traditional EVM transactions which only support a single call per transaction, the [Tempo Transaction](/protocol/transactions/spec-tempo-transaction) type natively supports a `calls` vector—enabling multiple operations in a single atomic transaction. #### Native Protocol Support Batching is built directly into the transaction type at the protocol level. This means: * **Atomic execution**: All calls succeed or all calls revert together—no partial failures * **Single signature**: Sign once for the entire batch, reducing UX friction * **Lower gas costs**: Avoid the overhead of multiple transaction submissions * **No smart contract required**: Works with any EOA, no need to deploy a separate batching contract #### Transaction Structure The `TempoTransaction` includes a `calls` field that accepts a list of operations: ```rust pub struct TempoTransaction { // ... other fields calls: Vec, // Batch of calls to execute atomically // ... } pub struct Call { to: TxKind, // Target address or Create for contract deployment value: U256, // ETH value to send input: Bytes, // Calldata for the call } ``` import * as Demo from '../../../components/guides/Demo.tsx' import * as Step from '../../../components/guides/steps' import { ConnectWallet } from '../../../components/ConnectWallet.tsx' ## Connect to Wallets It is possible to use Tempo with EVM-compatible wallets that support the Tempo network, or support adding custom networks (like MetaMask). You can use these wallets when building your application on Tempo. This guide will walk you through how to set up your application to connect to wallets. ### Wagmi Setup ::::steps #### Set up Wagmi Ensure that you have set up your project with Wagmi by following the [guide](/sdk/typescript#wagmi-setup). #### Configure Wagmi Next, let's ensure Wagmi is configured correctly to connect to wallets. Ensure we have [`multiInjectedProviderDiscovery`](https://wagmi.sh/react/api/createConfig#multiinjectedproviderdiscovery) set to `true` to display injected browser wallets. We can also utilize [wallet connectors](https://wagmi.sh/react/api/connectors) from Wagmi like `metaMask` to support mobile devices. ```tsx twoslash [config.ts] // @noErrors import { createConfig, http } from 'wagmi' import { tempoTestnet } from 'viem/chains' import { metaMask } from 'wagmi/connectors' // [!code ++] export const config = createConfig({ chains: [tempoTestnet], connectors: [metaMask()], // [!code ++] multiInjectedProviderDiscovery: true, // [!code ++] transports: { [tempo.id]: http(), }, }) ``` #### Display Connect Buttons After that, we will set up "Connect" buttons so that the user can connect to their wallet. We will create a new `ConnectWallet.tsx` component to work in. :::code-group
::: :::code-group ```tsx twoslash [Connect.tsx] // @noErrors import { useConnect, useConnectors } from 'wagmi' export function Connect() { const connect = useConnect() const connectors = useConnectors() return (
{connectors.map((connector) => ( ))}
) } ``` ```tsx twoslash [config.ts] filename="config.ts" // @noErrors import { createConfig, http } from 'wagmi' import { tempoTestnet } from 'viem/chains' import { metaMask } from 'wagmi/connectors' // [!code ++] export const config = createConfig({ chains: [tempoTestnet], connectors: [metaMask()], // [!code ++] multiInjectedProviderDiscovery: true, // [!code ++] transports: { [tempo.id]: http(), }, }) ``` ::: #### Display Account & Sign Out After the user has connected to their wallet, we can display the account information and a sign out button. We will create a new `Account.tsx` component to work in. :::code-group
::: :::code-group ```tsx twoslash [Account.tsx] // @noErrors import { useConnection, useDisconnect } from 'wagmi' export function Account() { const account = useConnection() const disconnect = useDisconnect() return (
{account.address?.slice(0, 6)}...{account.address?.slice(-4)}
) } ``` ```tsx twoslash [config.ts] filename="config.ts" // @noErrors import { createConfig, http } from 'wagmi' import { tempoTestnet } from 'viem/chains' import { metaMask } from 'wagmi/connectors' // [!code ++] export const config = createConfig({ chains: [tempoTestnet], connectors: [metaMask()], // [!code ++] multiInjectedProviderDiscovery: true, // [!code ++] transports: { [tempo.id]: http(), }, }) ``` ::: #### Display "Add Tempo" Button If the wallet is not on the Tempo network, we can display a "Add Tempo" button so that the user can add the network to their wallet. :::code-group
::: :::code-group ```tsx twoslash [Account.tsx] // @noErrors import { useConnection, useDisconnect, useSwitchChain } from 'wagmi' import { tempoTestnet } from 'viem/chains' // [!code ++] export function Account() { const account = useConnection() const disconnect = useDisconnect() const switchChain = useSwitchChain() // [!code ++] return (
{account.address?.slice(0, 6)}...{account.address?.slice(-4)}
{/* // [!code ++] */}
) } ``` ```tsx twoslash [config.ts] filename="config.ts" // @noErrors import { createConfig, http } from 'wagmi' import { tempoTestnet } from 'viem/chains' import { metaMask } from 'wagmi/connectors' // [!code ++] export const config = createConfig({ chains: [tempoTestnet], connectors: [metaMask()], // [!code ++] multiInjectedProviderDiscovery: true, // [!code ++] transports: { [tempo.id]: http(), }, }) ``` ::: :::: ### Third-Party Libraries You can also use a third-party Wallet Connection library to handle the onboarding & connection of wallets. Such libraries include: [Privy](https://privy.io), [ConnectKit](https://docs.family.co/connectkit), [AppKit](https://reown.com/appkit), [Dynamic](https://dynamic.xyz), and [RainbowKit](https://rainbowkit.com). The above libraries are all built on top of Wagmi, handle all the edge cases around wallet connection. :::warning It is worth noting that some wallets that are included in the above libraries may not support Tempo yet. We are working on adding support for the majority of wallets. ::: ### Add to Wallet Manually You can add Tempo testnet to a wallet that supports custom networks (e.g. MetaMask) manually. For example, if you are using MetaMask: 1. Open MetaMask and click on the menu in the top right and select "Networks" 2. Click "Add a custom network" 3. Enter the network details: | **Name** | `Tempo Testnet (Andantino)` | | ------------------ | -------------------------------------------------------- | | **Currency** | `USD` | | **Chain ID** | `42429` | | **HTTP URL** | `https://rpc.testnet.tempo.xyz` | | **WebSocket URL** | `wss://rpc.testnet.tempo.xyz` | | **Block Explorer** | [`https://explore.tempo.xyz`](https://explore.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). :::warning Note that we recommend using the `symbol` "USD" for the currency symbol, despite there being no native gas token. Existing wallets like MetaMask don't natively support the Tempo network yet, so there are some quirks to the interface. You might also need to set `nativeCurrency.decimals` to `18` instead of `6` in some wallets. ::: import LucideSignature from '~icons/lucide/signature' import LucideFile from '~icons/lucide/file' import * as Card from "../../../components/Card.tsx" import * as Demo from '../../../components/guides/Demo.tsx' import { EmbedPasskeys, SignInButtons } from '../../../components/guides/EmbedPasskeys.tsx' ## Embed Passkey Accounts Create a domain-bound passkey account on Tempo using WebAuthn signatures for secure, passwordless authentication with [Tempo transactions](/protocol/transactions/spec-tempo-transaction). Passkeys enable users to authenticate with biometrics (fingerprint, Face ID, Touch ID) they already use for other apps. Keys are stored in the device's secure enclave and sync across devices via iCloud Keychain or Google Password Manager. :::info **What does "domain-bound" mean?** WebAuthn credentials are bound to a specific domain (the [Relying Party](https://en.wikipedia.org/wiki/Relying_party)). This means that credentials created for one domain (e.g., `example.com`) will only work on that domain (and its subdomains) and cannot be used to authenticate on other domains. This means your users won't be able to use the same passkey account on other applications. If this is not what you want, head to the [Connect to wallets](/guide/use-accounts/connect-to-wallets) guide that walks you through on how to connect your application to a universal wallet like MetaMask. ::: ### Demo By the end of this guide, you will be able to embed passkey accounts into your application. ### Steps ::::steps #### Set up Wagmi Ensure that you have set up your project with Wagmi by following the [guide](/sdk/typescript#wagmi-setup). #### Configure the WebAuthn Connector Next, we will need to configure the `webAuthn` connector in our Wagmi config. ```tsx twoslash [config.ts] // @noErrors import { createConfig, http } from 'wagmi' import { tempoTestnet } from 'viem/chains' import { KeyManager, webAuthn } from 'tempo.ts/wagmi' // [!code ++] export const config = createConfig({ chains: [tempoTestnet], connectors: [webAuthn({ // [!code ++] keyManager: KeyManager.localStorage(), // [!code ++] })], // [!code ++] multiInjectedProviderDiscovery: false, transports: { [tempo.id]: http(), }, }) ``` :::warning The `KeyManager.localStorage()` implementation is not recommended for production use as it stores public keys on the client device, meaning it cannot be re-extracted when the user's storage is cleared or if the user is on another device. For production, you should opt for a remote key manager such as [`KeyManager.http`](/sdk/typescript/wagmi/keyManagers/http). ::: :::tip This Wagmi configuration sets `multiInjectedProviderDiscovery` to `false` to prevent injected browser wallets from being detected, and to prefer the `webAuthn` connector. If you would like to allow connection to other wallets, set this property to `true`. ::: #### Display Sign In Buttons After that, we will set up "Sign in" and "Sign up" buttons so that the user can create or use a passkey with the application. We will create a new `Example.tsx` component to work in. :::code-group
::: :::code-group ```tsx twoslash [Example.tsx] // @noErrors import { useConnect, useConnectors } from 'wagmi' export function Example() { const connect = useConnect() const [connector] = useConnectors() return (
) } ``` ```tsx twoslash [config.ts] filename="config.ts" // @noErrors import { createConfig, http } from 'wagmi' import { tempoTestnet } from 'viem/chains' import { KeyManager, webAuthn } from 'tempo.ts/wagmi' // [!code ++] export const config = createConfig({ chains: [tempoTestnet], connectors: [webAuthn({ // [!code ++] keyManager: KeyManager.localStorage(), // [!code ++] })], // [!code ++] multiInjectedProviderDiscovery: false, transports: { [tempo.id]: http(), }, }) ``` ::: #### Display Account & Sign Out After the user has signed in, we can display the account information and a sign out button. :::code-group
::: :::code-group ```tsx twoslash [Example.tsx] // @noErrors import { useConnection, useConnect, useConnectors, useDisconnect } from 'wagmi' export function Example() { const account = useConnection() // [!code ++] const connect = useConnect() const [connector] = useConnectors() const disconnect = useDisconnect() // [!code ++] if (account.address) // [!code ++] return ( // [!code ++]
{/* // [!code ++] */}
{account.address.slice(0, 6)}...{account.address.slice(-4)}
{/* // [!code ++] */} {/* // [!code ++] */}
// [!code ++] ) // [!code ++] return (
) } ``` ```tsx twoslash [config.ts] filename="config.ts" // @noErrors import { createConfig, http } from 'wagmi' import { tempoTestnet } from 'viem/chains' import { KeyManager, webAuthn } from 'tempo.ts/wagmi' // [!code ++] export const config = createConfig({ chains: [tempoTestnet], connectors: [webAuthn({ // [!code ++] keyManager: KeyManager.localStorage(), // [!code ++] })], // [!code ++] multiInjectedProviderDiscovery: false, transports: { [tempo.id]: http(), }, }) ``` ::: #### Next Steps Now that you have created your first passkey account, you can now: * learn the [Best Practices](#best-practices) below * follow a guide on how to [make a payment](#TODO), [create a stablecoin](#TODO), and [more](#TODO) with a passkey account. :::: {/* TODO: note on public key retention with credentialId -> pubKey KV service. ## Notes for Production ### Public Key Retention */} ### Best Practices #### Loading State When the user is logging in or signing out, we should show loading state to indicate that the process is happening. We can use the `isPending` property from the `useConnect` hook to show pending state to the user. ```tsx twoslash [Example.tsx] // @noErrors import { useConnection, useConnect, useConnectors, useDisconnect } from 'wagmi' export function Example() { const account = useConnection() const connect = useConnect() const [connector] = useConnectors() const disconnect = useDisconnect() if (connect.isPending) // [!code ++] return
Check prompt...
// [!code ++] return (/* ... */) } ``` :::tip Wagmi exposes [React Query's](https://tanstack.com/query/latest/docs/framework/react/overview) interfaces on all Hooks to extract asynchronous states such as loading (e.g. `isPending`) and error (e.g. `isError`, `error`) states. ::: #### Error Handling If an error unexpectedly occurs, we should display an error message to the user. We can use the `error` property from the `useConnect` hook to show error state to the user. ```tsx twoslash [Example.tsx] // @noErrors import { useConnection, useConnect, useConnectors, useDisconnect } from 'wagmi' export function Example() { const account = useConnection() const connect = useConnect() const [connector] = useConnectors() const disconnect = useDisconnect() if (connect.error) // [!code ++] return
Error: {connect.error.message}
// [!code ++] return (/* ... */) } ``` ### Learning Resources import { Callout } from 'vocs/components' ## Fee Sponsorship Fee sponsorship enables gasless transactions where applications can subsidize user costs. Users can interact with the blockchain without holding fee tokens. This specification describes how one account can pay transaction fees on behalf of another account using the [Tempo Transaction](/protocol/transactions/spec-tempo-transaction) type with the `feePayerSignature` field. ### How It Works The [Tempo Transaction](/protocol/transactions/spec-tempo-transaction) type includes an optional `feePayerSignature` field. When present, this signature authorizes a third party to pay the transaction's gas costs while the original sender executes the transaction logic. :::steps #### Sender signs the transaction The sender signs the transaction with their private key, signing over a **blank fee token field**. This means the sender delegates the choice of which fee token to use to the fee payer. #### Fee payer selects and signs The fee payer selects which fee token to use, then signs over the transaction. #### Transaction submission The fee token and fee payer signature is added to the transaction using the `feePayerSignature` field and is then submitted. #### Network validation The network validates both signatures and executes the transaction. #### Transaction Fields The TempoTransaction includes: * Standard EIP-1559 fields (chainId, maxPriorityFeePerGas, maxFeePerGas, gasLimit) * TempoTransaction-specific fields: `signature`, `nonceKey`, `nonce`, `calls` (batch of calls to execute) * Optional `feeToken` field to specify which stablecoin pays fees * Optional `feePayerSignature` field when sponsored * Optional `validBefore` and `validAfter` for scheduled transactions #### Dual Signatures **Sender signs:** The transaction they want to execute using their preferred signature scheme (secp256k1, P256, or WebAuthn) **Fee payer signs:** Authorization to pay fees for that specific transaction and sender Each signature is recovered independently to determine the sender and fee payer addresses. ### Validation When `feePayerSignature` is present: 1. Both sender and fee payer signatures must be valid 2. Fee payer must have sufficient balance in the fee token 3. Transaction is rejected if either signature fails or fee payer's balance is insufficient When `feePayerSignature` is absent: * Sender pays their own fees (standard behavior) Fee payer information is determined statically from the transaction data, ensuring efficient validation without requiring transaction execution. ### Execution 1. Recover both sender and fee payer addresses from their signatures 2. Charge gas fees to the fee payer's balance 3. Execute transaction with sender as the transaction origin 4. Refund unused gas to the fee payer ### Security * Fee payer signature commits to both the transaction details and specific sender * Prevents signature reuse or unauthorized fee charging * Replay protection via chain ID and 2D nonce system (nonceKey + nonce) * Fee payers cannot be drained beyond the transaction's gas limit * Support for multiple signature schemes (secp256k1, P256, WebAuthn) with automatic detection based on signature length ::: import LucideFingerprint from '~icons/lucide/fingerprint' import LucideWallet from '~icons/lucide/wallet' import LucideCoins from '~icons/lucide/coins' import * as Card from "../../../components/Card.tsx" ## Create & Use Accounts Create and integrate Tempo accounts into your product with domain-bound passkeys or connecting your app to EVM-compatible wallets. :::tip **Should I use a Passkey account or a wallet?** * If you need a **domain-bound account** experience, you can embed a Passkey account. * If you need a **universal account** experience, you can integrate your app with wallets. You can even use both if you would like to offer both experiences. ::: import { Callout } from 'vocs/components' ## Scheduled Transactions Execute transactions only within a specific time window using `validAfter` and `validBefore` timestamps. Scheduled transactions are enabled by the [Tempo Transaction](/protocol/transactions/spec-tempo-transaction) type, which includes optional timestamp fields. These fields are validated by the protocol when the transaction is submitted. ### Setting Time Bounds You can add optional timestamp fields to your transaction to control when it can be executed. Both `validAfter` and `validBefore` accept Unix timestamps. For example, to create a transaction that can only execute between Jan 1, 2025 and Jan 2, 2025, you would set `validAfter: 1735689600` and `validBefore: 1735776000` when signing the transaction. ### Time Window Validation The protocol validates timestamps against the block timestamp: * **`validAfter`**: Block timestamp must be greater than or equal to this value * **`validBefore`**: Block timestamp must be less than or equal to this value * **Both optional**: Omit either or both to not restrict that bound You can use these fields individually or in combination: * **Only `validAfter`**: Transaction can execute any time after the specified date (e.g., after Jan 1, 2025) * **Only `validBefore`**: Transaction must execute by the specified date (e.g., before Jan 2, 2025) * **Both**: Transaction has a specific time window for execution * **Neither**: No time restrictions ### Examples #### Example: Vesting Schedule Release tokens at a specific future time by pre-signing a transaction with a future `validAfter` timestamp. You would sign a token transfer transaction that includes `validAfter: 1767225600` (Jan 1, 2026). The signed transaction can be stored off-chain or given to a third party. When Jan 1, 2026 arrives, anyone can submit the signed transaction to the network, and it will execute. If submitted before that date, validators will reject it. This enables trustless vesting schedules where the beneficiary holds a pre-signed transaction that becomes valid at a specific future date. #### Example: Time-Limited Offer You can create an offer that expires if not accepted by setting both `validAfter` and `validBefore` timestamps. For example, to create an offer valid for 24 hours, you would set `validAfter` to the current timestamp and `validBefore` to 86400 seconds (24 hours) in the future. The signed transaction can only be executed within that 24-hour window. After the expiration time passes, the transaction becomes invalid and cannot be included in a block. #### Example: Delayed Execution You can sign a transaction now that can only be executed at a future time by setting `validAfter` to a timestamp 7 days in the future. The signed transaction is then stored (in a database, onchain via a contract, or held by a party). After the 7-day delay period passes, anyone with access to the signed transaction can submit it to the network for execution. The protocol enforces that the transaction cannot be included in a block before the specified time. This is useful for governance proposals with timelock requirements or delayed contract upgrades. ### Storing Scheduled Transactions **Important**: It is your responsibility to store and manage signed scheduled transactions until their execution time. The protocol validates the time constraints but does not store transactions for you. When you create a scheduled transaction, you have several options for storage: * **Self-custody**: Store the signed transaction in your own database or secure storage until the execution time * **Third-party services**: Work with a company that specializes in holding and submitting scheduled transactions at the appropriate time * **Smart contracts**: Store the signed transaction onchain in a contract that can submit it when the time window is valid The Tempo team is actively exploring the design of a protocol-level scheduled transaction feature that would eliminate the need for off-chain storage. If this capability is valuable to your use case, please contact us to share your requirements and help inform the design. ### Mempool Behavior Validators will reject transactions that are outside their validity window: * **Too early**: Transactions with `validAfter` in the future won't be included in blocks. The transaction will be rejected if you try to submit it before the specified time. * **Too late**: Transactions with `validBefore` in the past will be rejected. Once the expiration time passes, the transaction can no longer be executed. For example, if you submit a transaction with `validAfter` set to Jan 1, 2026, validators will return an error until that timestamp is reached. ### Combining with Other Features **Fee sponsorship**: You can combine scheduled transactions with fee sponsorship, where a sponsor pays the fees for a time-locked transaction. The fee payer signs their portion, and the combined transaction becomes valid only within the specified time window. **Batch transactions**: Multiple operations can be scheduled together to execute atomically within a specific time window. All operations in the batch must succeed or fail together, and the entire batch is subject to the time constraints. The block timestamp is set by validators and may vary slightly. For critical timing, account for small time variations (±15 seconds). ### Technical Details For complete specifications on timestamp validation, see the [Tempo Transaction](/protocol/transactions/spec-tempo-transaction) type specification. import { Callout } from 'vocs/components' ## WebAuthn & P256 Signatures Tempo EOA addresses can be derived from multiple signature types. Allowing you to sign transactions with standard Ethereum wallets, hardware security keys, biometric authentication like Face ID and Touch ID, or even using [passkeys](https://passkeys.dev/). The supported signature types are: #### secp256k1 The standard Ethereum signature format. No type identifier needed, this is the default. #### P256 Raw P256 signatures that include the public key coordinates. Identified by type `0x01`. The `preHash` flag indicates whether the digest should be hashed with SHA256 before verification. Set this to `true` if using Web Crypto API or similar implementations that require pre-hashing. #### WebAuthn WebAuthn signatures include authenticator data from biometric devices. Identified by type `0x02`. The verification data contains information from the authenticator (device metadata, user presence flags, etc.). *** Additional signature types may be supported in the future. For complete technical specifications, see the [Tempo Transaction](/protocol/transactions/spec-tempo-transaction) protocol documentation. import LucideCoins from '~icons/lucide/coins' import LucideShieldCheck from '~icons/lucide/shield-check' import LucideLayers from '~icons/lucide/layers' import LucideKey from '~icons/lucide/key' import LucideZap from '~icons/lucide/zap' import LucideCircleHelp from '~icons/lucide/circle-help' import LucideClock from '~icons/lucide/clock' import * as Card from "../../../components/Card.tsx" import TempoTxProperties from '../../../snippets/tempo-tx-properties.mdx' ### Use Tempo Transactions Tempo Transactions are a new [EIP-2718](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2718.md) transaction type, exclusively available on Tempo.

Transaction [SDKs](#integration-guides) are available for TypeScript, Rust, Go, 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). ### Integration Guides Integrating Tempo Transactions is easy and can be done quickly by a developer in multiple languages. See below for quick links to some of our guides. | Language | Source | Integration Time | | ------------------- | ------------------------------------------------------------------------------------------------------------------ | ---------------- | | **TypeScript** | [tempoxyz/tempo-ts](/sdk/typescript) | \< 1 hour | | **Rust** | [tempo-alloy](/sdk/rust) | \< 1 hour | | **Golang** | [tempo-go](https://github.com/tempoxyz/tempo-go) | \< 1 hour | | **Python** | [pytempo](https://github.com/tempoxyz/pytempo) | \< 1 hour | | **Other Languages** | Reach out to us! Specification is [here](/protocol/transactions/spec-tempo-transaction) and easy to build against. | 1-3 days | If you are an EVM smart contract developer, see the [Tempo extension for Foundry](/sdk/foundry). ### Properties import * as Card from '../../../components/Card.tsx' import * as Demo from '../../../components/guides/Demo.tsx' import * as Step from '../../../components/guides/steps' import LucideArrowLeftRight from '~icons/lucide/arrow-left-right' import LucideWallet from '~icons/lucide/wallet' import LucideBookOpen from '~icons/lucide/book-open' ## Executing Swaps Execute swaps between stablecoins on the exchange. Swaps execute immediately against existing orders in the orderbook, providing instant liquidity for cross-stablecoin payments. By the end of this guide you will be able to execute swaps, get price quotes, and manage slippage protection. ### Steps ::::steps #### Set up your client Ensure that you have set up your client by following the [guide](/sdk/typescript). #### Get a price quote Before executing a swap, get a quote to see the expected price. :::code-group ```tsx twoslash [Buy.tsx] // @noErrors import { Hooks } from 'tempo.ts/wagmi' import { formatUnits, parseUnits } from 'viem' const alphaUsd = '0x20c0000000000000000000000000000000000001' const betaUsd = '0x20c0000000000000000000000000000000000002' function Buy() { const amount = parseUnits('10', 6) // How much AlphaUSD do I need to spend to receive 10 BetaUSD? // [!code hl] const { data: quote } = Hooks.dex.useBuyQuote({ // [!code hl] tokenIn: alphaUsd, // [!code hl] tokenOut: betaUsd, // [!code hl] amountOut: amount, // [!code hl] }) // [!code hl] return
Quote: {formatUnits(quote, 6)}
} ``` ```tsx twoslash [Sell.tsx] // @noErrors import { Hooks } from 'tempo.ts/wagmi' import { formatUnits, parseUnits } from 'viem' const alphaUsd = '0x20c0000000000000000000000000000000000001' const betaUsd = '0x20c0000000000000000000000000000000000002' function Sell() { const amount = parseUnits('10', 6) // How much BetaUSD will I receive for 10 AlphaUSD? // [!code hl] const { data: quote } = Hooks.dex.useSellQuote({ // [!code hl] tokenIn: alphaUsd, // [!code hl] tokenOut: betaUsd, // [!code hl] amountIn: amount, // [!code hl] }) // [!code hl] return
Quote: {formatUnits(quote, 6)}
} ``` ::: #### Calculate slippage tolerance Set appropriate slippage based on your quote to protect against unfavorable price movements. :::code-group ```tsx twoslash [Buy.tsx] // @noErrors import { Hooks } from 'tempo.ts/wagmi' import { formatUnits, parseUnits } from 'viem' const alphaUsd = '0x20c0000000000000000000000000000000000001' const betaUsd = '0x20c0000000000000000000000000000000000002' function Buy() { const amount = parseUnits('10', 6) // How much AlphaUSD do I need to spend to receive 10 BetaUSD? const { data: quote } = Hooks.dex.useBuyQuote({ tokenIn: alphaUsd, tokenOut: betaUsd, amountOut: amount, }) // [!code ++] Calculate 0.5% slippage tolerance const slippageTolerance = 0.005 // [!code ++] const maxAmountIn = quote // [!code ++] ? quote * BigInt(Math.floor((1 + slippageTolerance) * 1000)) / 1000n // [!code ++] : 0n // [!code ++] return (
Quote: {formatUnits(quote, 6)}
Max input (0.5% slippage): {formatUnits(maxAmountIn, 6)}
// [!code ++]
) } ``` ```tsx twoslash [Sell.tsx] // @noErrors import { Hooks } from 'tempo.ts/wagmi' import { formatUnits, parseUnits } from 'viem' const alphaUsd = '0x20c0000000000000000000000000000000000001' const betaUsd = '0x20c0000000000000000000000000000000000002' function Sell() { const amount = parseUnits('10', 6) // How much BetaUSD will I receive for 10 AlphaUSD? const { data: quote } = Hooks.dex.useSellQuote({ tokenIn: alphaUsd, tokenOut: betaUsd, amountIn: amount, }) // [!code ++] Calculate 0.5% slippage tolerance const slippageTolerance = 0.005 // [!code ++] const minAmountOut = quote // [!code ++] ? quote * BigInt(Math.floor((1 - slippageTolerance) * 1000)) / 1000n // [!code ++] : 0n // [!code ++] return (
Quote: {formatUnits(quote, 6)}
Min output (0.5% slippage): {formatUnits(minAmountOut, 6)}
// [!code ++]
) } ``` ::: #### Approve Spend To execute a swap, you need to approve the Stablecoin DEX contract to spend the token you're using to fund the swap. :::code-group ```tsx twoslash [Buy.tsx] // @noErrors import { Actions, Addresses } from 'viem/tempo' // [!code ++] import { Hooks } from 'tempo.ts/wagmi' import { formatUnits, parseUnits } from 'viem' import { useSendCallsSync } from 'wagmi' // [!code ++] const alphaUsd = '0x20c0000000000000000000000000000000000001' const betaUsd = '0x20c0000000000000000000000000000000000002' function Buy() { const amount = parseUnits('10', 6) // How much AlphaUSD do I need to spend to receive 10 BetaUSD? const { data: quote } = Hooks.dex.useBuyQuote({ tokenIn: alphaUsd, tokenOut: betaUsd, amountOut: amount, }) // Calculate 0.5% slippage tolerance const slippageTolerance = 0.005 const maxAmountIn = quote ? quote * BigInt(Math.floor((1 + slippageTolerance) * 1000)) / 1000n : 0n const sendCalls = useSendCallsSync() // [!code ++] return (
Quote: {formatUnits(quote, 6)}
Max input (0.5% slippage): {formatUnits(maxAmountIn, 6)}
// [!code ++]
) } ``` ```tsx twoslash [Sell.tsx] // @noErrors import { Actions, Addresses } from 'viem/tempo' // [!code ++] import { Hooks } from 'tempo.ts/wagmi' import { formatUnits, parseUnits } from 'viem' import { useSendCallsSync } from 'wagmi' // [!code ++] const alphaUsd = '0x20c0000000000000000000000000000000000001' const betaUsd = '0x20c0000000000000000000000000000000000002' function Sell() { const amount = parseUnits('10', 6) // How much BetaUSD will I receive for 10 AlphaUSD? const { data: quote } = Hooks.dex.useSellQuote({ tokenIn: alphaUsd, tokenOut: betaUsd, amountIn: amount, }) // Calculate 0.5% slippage tolerance const slippageTolerance = 0.005 const minAmountOut = quote ? quote * BigInt(Math.floor((1 - slippageTolerance) * 1000)) / 1000n : 0n const sendCalls = useSendCallsSync() // [!code ++] return (
Quote: {formatUnits(quote, 6)}
Min output (0.5% slippage): {formatUnits(minAmountOut, 6)}
// [!code ++]
) } ``` ::: #### Execute a swap Batch the token approval with the swap in a single transaction for better UX. :::code-group ```tsx twoslash [Buy.tsx] // @noErrors import { Actions, Addresses } from 'viem/tempo' import { Hooks } from 'tempo.ts/wagmi' import { formatUnits, parseUnits } from 'viem' import { useSendCallsSync } from 'wagmi' const alphaUsd = '0x20c0000000000000000000000000000000000001' const betaUsd = '0x20c0000000000000000000000000000000000002' function Buy() { const amount = parseUnits('10', 6) // How much AlphaUSD do I need to spend to receive 10 BetaUSD? const { data: quote } = Hooks.dex.useBuyQuote({ tokenIn: alphaUsd, tokenOut: betaUsd, amountOut: amount, }) // Calculate 0.5% slippage tolerance const slippageTolerance = 0.005 const maxAmountIn = quote ? quote * BigInt(Math.floor((1 + slippageTolerance) * 1000)) / 1000n : 0n const sendCalls = useSendCallsSync() return (
Quote: {formatUnits(quote, 6)}
Max input (0.5% slippage): {formatUnits(maxAmountIn, 6)}
) } ``` ```tsx twoslash [Sell.tsx] // @noErrors import { Actions, Addresses } from 'viem/tempo' import { Hooks } from 'tempo.ts/wagmi' import { formatUnits, parseUnits } from 'viem' import { useSendCallsSync } from 'wagmi' const alphaUsd = '0x20c0000000000000000000000000000000000001' const betaUsd = '0x20c0000000000000000000000000000000000002' function Sell() { const amount = parseUnits('10', 6) // How much BetaUSD will I receive for 10 AlphaUSD? const { data: quote } = Hooks.dex.useSellQuote({ tokenIn: alphaUsd, tokenOut: betaUsd, amountIn: amount, }) // Calculate 0.5% slippage tolerance const slippageTolerance = 0.005 const minAmountOut = quote ? quote * BigInt(Math.floor((1 - slippageTolerance) * 1000)) / 1000n : 0n const sendCalls = useSendCallsSync() return (
Quote: {formatUnits(quote, 6)}
Min output (0.5% slippage): {formatUnits(minAmountOut, 6)}
) } ``` ::: :::: ### Recipes #### Handling insufficient liquidity Quote requests will fail with an `InsufficientLiquidity` error if there isn't enough liquidity in the orderbook to satisfy the requested amount. Handle this error when fetching quotes: ```tsx import { Hooks } from 'tempo.ts/wagmi' import { parseUnits } from 'viem' const alphaUsd = '0x20c0000000000000000000000000000000000001' const betaUsd = '0x20c0000000000000000000000000000000000002' function Swap() { const amount = parseUnits('10', 6) const { data: quote, error } = Hooks.dex.useSellQuote({ tokenIn: alphaUsd, tokenOut: betaUsd, amountIn: amount, }) if (error) { if (error.message.includes('InsufficientLiquidity')) { return
Not enough liquidity available. Try a smaller amount.
} return
Error: {error.message}
} if (!quote) { return
Loading quote...
} return
Quote: {quote.toString()}
} ``` ### Best Practices #### Always get quotes before swapping Query the expected price before executing a swap to ensure you're getting a fair rate and to set appropriate slippage protection. #### Set appropriate slippage protection Use `minAmountOut` or `maxAmountIn` to protect against unfavorable price movements between quoting and execution. ### Learning Resources import LucideArrowLeftRight from '~icons/lucide/arrow-left-right' import LucideDroplet from '~icons/lucide/droplet' import LucideWallet from '~icons/lucide/wallet' import LucideCoins from '~icons/lucide/coins' import LucideBookOpen from '~icons/lucide/book-open' import * as Card from "../../../components/Card.tsx" ## Exchange Stablecoins Trade between stablecoins on Tempo's enshrined decentralized exchange (DEX). The DEX enables optimal pricing for cross-stablecoin payments while minimizing chain load. import * as Card from '../../../components/Card.tsx' import * as Demo from '../../../components/guides/Demo.tsx' import * as Step from '../../../components/guides/steps' import { Callout } from 'vocs/components' import LucideDroplet from '~icons/lucide/droplet' import LucideFileText from '~icons/lucide/file-text' import LucideCoins from '~icons/lucide/coins' ## Managing Fee Liquidity The Fee AMM converts transaction fees between stablecoins when users pay in a different token than the validator prefers. This guide shows you how to add and remove liquidity to enable fee conversions. ### Steps ::::steps #### Check pool reserves Before adding liquidity, check the current pool reserves to understand the pool state. :::code-group ```tsx twoslash [ManageFeeLiquidity.tsx] // @noErrors import { Hooks } from 'tempo.ts/wagmi' import { formatUnits } from 'viem' const userToken = '0x20c0000000000000000000000000000000000002' // BetaUSD const validatorToken = '0x20c0000000000000000000000000000000000001' // AlphaUSD function ManageFeeLiquidity() { const { data: pool } = Hooks.amm.usePool({ // [!code hl] userToken, // [!code hl] validatorToken, // [!code hl] }) // [!code hl] return (
User token reserves: {formatUnits(pool?.reserveUserToken ?? 0n, 6)}
Validator token reserves: {formatUnits(pool?.reserveValidatorToken ?? 0n, 6)}
) } ``` ```ts twoslash [wagmi.config.ts] // @noErrors import { tempoTestnet } from 'viem/chains' import { KeyManager, webAuthn } from 'tempo.ts/wagmi' import { createConfig, http } from 'wagmi' export const config = createConfig({ connectors: [ webAuthn({ keyManager: KeyManager.localStorage(), }), ], chains: [tempoTestnet], multiInjectedProviderDiscovery: false, transports: { [tempo.id]: http(), }, }) ``` ::: #### Add liquidity Add validator token to the pool to receive LP tokens representing your share. The first liquidity provider to a new pool must burn 1,000 units of liquidity. This costs approximately 0.002 USD and prevents attacks on pool reserves. Learn more in the [Fee AMM specification](/protocol/fees/spec-fee-amm). :::code-group ```tsx twoslash [ManageFeeLiquidity.tsx] // @noErrors import { Hooks } from 'tempo.ts/wagmi' import { formatUnits, parseUnits } from 'viem' import { useConnection } from 'wagmi' const userToken = '0x20c0000000000000000000000000000000000002' // BetaUSD const validatorToken = '0x20c0000000000000000000000000000000000001' // AlphaUSD function ManageFeeLiquidity() { const { address } = useConnection() const { data: pool } = Hooks.amm.usePool({ userToken, validatorToken, }) const mintLiquidity = Hooks.amm.useMintSync() // [!code ++] return (
User token reserves: {formatUnits(pool?.reserveUserToken ?? 0n, 6)}
Validator token reserves: {formatUnits(pool?.reserveValidatorToken ?? 0n, 6)}
{/* [!code ++] */}
) } ``` ```ts twoslash [wagmi.config.ts] // @noErrors import { tempoTestnet } from 'viem/chains' import { KeyManager, webAuthn } from 'tempo.ts/wagmi' import { createConfig, http } from 'wagmi' export const config = createConfig({ connectors: [ webAuthn({ keyManager: KeyManager.localStorage(), }), ], chains: [tempoTestnet], multiInjectedProviderDiscovery: false, transports: { [tempo.id]: http(), }, }) ``` ::: #### Check your LP balance View your LP token balance to see your share of the pool. :::code-group ```tsx twoslash [ManageFeeLiquidity.tsx] // @noErrors import { Hooks } from 'tempo.ts/wagmi' import { formatUnits, parseUnits } from 'viem' import { useConnection } from 'wagmi' const userToken = '0x20c0000000000000000000000000000000000002' // BetaUSD const validatorToken = '0x20c0000000000000000000000000000000000001' // AlphaUSD function ManageFeeLiquidity() { const { address } = useConnection() const { data: pool } = Hooks.amm.usePool({ userToken, validatorToken, }) const { data: balance } = Hooks.amm.useLiquidityBalance({ // [!code ++] address, // [!code ++] userToken, // [!code ++] validatorToken, // [!code ++] }) // [!code ++] const mintLiquidity = Hooks.amm.useMintSync() return (
LP token balance: {formatUnits(balance ?? 0n, 6)}
{/* [!code ++] */}
User token reserves: {formatUnits(pool?.reserveUserToken ?? 0n, 6)}
Validator token reserves: {formatUnits(pool?.reserveValidatorToken ?? 0n, 6)}
) } ``` ```ts twoslash [wagmi.config.ts] // @noErrors import { tempoTestnet } from 'viem/chains' import { KeyManager, webAuthn } from 'tempo.ts/wagmi' import { createConfig, http } from 'wagmi' export const config = createConfig({ connectors: [ webAuthn({ keyManager: KeyManager.localStorage(), }), ], chains: [tempoTestnet], multiInjectedProviderDiscovery: false, transports: { [tempo.id]: http(), }, }) ``` ::: #### Remove liquidity Burn LP tokens to withdraw your share of pool reserves plus accumulated fees. :::code-group ```tsx twoslash [ManageFeeLiquidity.tsx] // @noErrors import { Hooks } from 'tempo.ts/wagmi' import { formatUnits, parseUnits } from 'viem' import { useConnection } from 'wagmi' const userToken = '0x20c0000000000000000000000000000000000002' // BetaUSD const validatorToken = '0x20c0000000000000000000000000000000000001' // AlphaUSD function ManageFeeLiquidity() { const { address } = useConnection() const { data: pool } = Hooks.amm.usePool({ userToken, validatorToken, }) const { data: balance } = Hooks.amm.useLiquidityBalance({ address, userToken, validatorToken, }) const mintLiquidity = Hooks.amm.useMintSync() const burnLiquidity = Hooks.amm.useBurnSync() // [!code ++] return (
LP token balance: {formatUnits(balance ?? 0n, 6)}
User token reserves: {formatUnits(pool?.reserveUserToken ?? 0n, 6)}
Validator token reserves: {formatUnits(pool?.reserveValidatorToken ?? 0n, 6)}
{/* [!code ++] */}
) } ``` ```ts twoslash [wagmi.config.ts] // @noErrors import { tempoTestnet } from 'viem/chains' import { KeyManager, webAuthn } from 'tempo.ts/wagmi' import { createConfig, http } from 'wagmi' export const config = createConfig({ connectors: [ webAuthn({ keyManager: KeyManager.localStorage(), }), ], chains: [tempoTestnet], multiInjectedProviderDiscovery: false, transports: { [tempo.id]: http(), }, }) ``` ::: :::: ### Recipes #### Monitor pool utilization Track fee swap activity to understand pool utilization and revenue. :::code-group ```tsx twoslash [MonitorSwaps.tsx] // @noErrors import * as React from 'react' import { Hooks } from 'tempo.ts/wagmi' import { formatUnits } from 'viem' const userToken = '0x20c0000000000000000000000000000000000002' // BetaUSD const validatorToken = '0x20c0000000000000000000000000000000000001' // AlphaUSD function MonitorSwaps() { const [swaps, setSwaps] = React.useState([]) Hooks.amm.useWatchFeeSwap({ // [!code hl] userToken, // [!code hl] validatorToken, // [!code hl] onLogs(logs) { // [!code hl] for (const log of logs) { // [!code hl] setSwaps((prev) => [...prev, { // [!code hl] amountIn: formatUnits(log.args.amountIn, 6), // [!code hl] amountOut: formatUnits(log.args.amountOut, 6), // [!code hl] revenue: formatUnits(log.args.amountIn * 30n / 10000n, 6), // [!code hl] }]) // [!code hl] } // [!code hl] }, // [!code hl] }) // [!code hl] return (
{swaps.map((swap, i) => (
Swap: {swap.amountIn} → {swap.amountOut} (LP revenue: {swap.revenue})
))}
) } ``` ```ts twoslash [wagmi.config.ts] // @noErrors import { tempoTestnet } from 'viem/chains' import { KeyManager, webAuthn } from 'tempo.ts/wagmi' import { createConfig, http } from 'wagmi' export const config = createConfig({ connectors: [ webAuthn({ keyManager: KeyManager.localStorage(), }), ], chains: [tempoTestnet], multiInjectedProviderDiscovery: false, transports: { [tempo.id]: http(), }, }) ``` ::: #### Rebalance pools You can rebalance pools by swapping validator tokens for accumulated user tokens at a fixed rate. Rebalancing restores validator token reserves and enables continued fee conversions. Learn more [here](/protocol/fees/spec-fee-amm#swap-mechanisms). :::code-group ```tsx twoslash [RebalancePool.tsx] // @noErrors import { Hooks } from 'tempo.ts/wagmi' import { formatUnits, parseUnits } from 'viem' import { useConnection } from 'wagmi' const userToken = '0x20c0000000000000000000000000000000000002' // BetaUSD const validatorToken = '0x20c0000000000000000000000000000000000001' // AlphaUSD function RebalancePool() { const { address } = useConnection() const { data: pool } = Hooks.amm.usePool({ userToken, validatorToken, }) const rebalance = Hooks.amm.useRebalanceSwapSync() // [!code hl] return (
User token reserves: {formatUnits(pool?.reserveUserToken ?? 0n, 6)}
Validator token reserves: {formatUnits(pool?.reserveValidatorToken ?? 0n, 6)}
{ /* [!code hl] */ }
) } ``` ```ts twoslash [wagmi.config.ts] // @noErrors import { tempoTestnet } from 'viem/chains' import { KeyManager, webAuthn } from 'tempo.ts/wagmi' import { createConfig, http } from 'wagmi' export const config = createConfig({ connectors: [ webAuthn({ keyManager: KeyManager.localStorage(), }), ], chains: [tempoTestnet], multiInjectedProviderDiscovery: false, transports: { [tempo.id]: http(), }, }) ``` ::: ### Best Practices #### Monitor pool reserves Regularly check pool reserves to ensure sufficient liquidity for fee conversions. Low reserves can prevent transactions from being processed. Add liquidity when: * Transaction rates increase for a given `userToken` * Reserve levels drop below expected daily volume * Multiple validators begin preferring the same token #### Maintain adequate reserves As an issuer, keep sufficient validator token reserves to handle expected transaction volume. Consider your anticipated fee conversion volume when determining reserve levels. For new token pairs, provide the entire initial amount in the validator token. The pool naturally accumulates user tokens as fees are paid. #### Deploy liquidity strategically Focus liquidity on pools with: * High transaction volume and frequent fee conversions * New stablecoins that need initial bootstrapping * Validator tokens preferred by multiple validators ### Learning Resources import * as Demo from '../../../components/guides/Demo.tsx' import * as Step from '../../../components/guides/steps' import * as Card from '../../../components/Card.tsx' import LucideArrowLeftRight from '~icons/lucide/arrow-left-right' import LucideBookOpen from '~icons/lucide/book-open' import LucideWallet from '~icons/lucide/wallet' ## Providing Liquidity Add liquidity for a token pair by placing orders on the Stablecoin DEX. You can provide liquidity on the `buy` or `sell` side of the orderbook, with `limit` or `flip` orders. To learn more about order types see the [documentation on order types](/protocol/exchange/providing-liquidity#order-types). In this guide you will learn how to place buy and sell orders to provide liquidity on the Stablecoin DEX orderbook. ### Demo ### Steps ::::steps #### Set up your client Ensure that you have set up your client by following the [guide](/sdk/typescript). #### Approve spend To place an order, you need to approve the Stablecoin DEX contract to spend the order's "spend" token. :::info The code samples in this guide will place orders on the `AlphaUSD` / `pathUSD` pair. The "spend" token is the token that will be spent to place the order. * buying `AlphaUSD` spends `pathUSD` * selling `AlphaUSD` spends `AlphaUSD` ::: :::code-group ```tsx twoslash [ApproveSpend.tsx] // @noErrors import { Addresses, Hooks } from 'tempo.ts/wagmi' import { parseUnits } from 'viem' const pathUsd = '0x20c0000000000000000000000000000000000000' const alphaUsd = '0x20c0000000000000000000000000000000000001' function ApproveSpend(props: { orderType: 'buy' | 'sell' }) { const { orderType } = props // buying AlphaUSD requires we spend pathUSD // [!code hl] const spendToken = orderType === 'buy' ? pathUsd : alphaUsd // [!code hl] // [!code hl] const { mutate: approve } = Hooks.token.useApproveSync() // [!code hl] return ( ) } ``` ```ts [wagmi.config.ts] // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Place order Once the spend is approved, you can place an order by calling the `place` action on the Stablecoin DEX. :::info In the code sample below, we use the `useSendCallsSync` hook to batch the approve and place order calls in a single transaction for efficiency. ::: :::code-group ```tsx twoslash [PlaceOrder.tsx] // @noErrors import { Actions, Addresses } from 'viem/tempo' import { parseUnits } from 'viem' import { useSendCallsSync } from 'wagmi' // [!code hl] const pathUsd = '0x20c0000000000000000000000000000000000000' const alphaUsd = '0x20c0000000000000000000000000000000000001' function PlaceOrder(props: { orderType: 'buy' | 'sell' }) { const { orderType } = props // buying AlphaUSD requires we spend pathUSD const spendToken = orderType === 'buy' ? pathUsd : alphaUsd const sendCalls = useSendCallsSync() // [!code hl] return ( ) } ``` ```ts [wagmi.config.ts] // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### View order details After placing an order, you can query its details to see the current state, including the amount filled and remaining using [`Hooks.dex.useOrder`](/sdk/typescript/wagmi/hooks/dex.useOrder). :::code-group ```tsx twoslash [QueryOrder.tsx] // @noErrors import { Hooks } from 'tempo.ts/wagmi' const orderId = 123n const { data: order, refetch } = Hooks.dex.useOrder({ orderId, }) console.log('Type:', order?.isBid ? 'Buy' : 'Sell') console.log('Amount:', order?.amount.toString()) console.log('Remaining:', order?.remaining.toString()) console.log('Tick:', order?.tick) console.log('Is flip order:', order?.isFlip) ``` ```ts [wagmi.config.ts] // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: For more details on querying orders, see the [`Hooks.dex.useOrder`](/sdk/typescript/wagmi/hooks/dex.useOrder) documentation. :::: ### Recipes #### Cancel order Cancel an order using its order ID. When you cancel an order, any remaining funds are credited to your exchange balance (not directly to your wallet). To move funds back to your wallet, you can [withdraw them to your wallet](/protocol/exchange/exchange-balance#withdrawing-funds). :::code-group ```tsx twoslash [ManageOrder.tsx] // @noErrors import { Actions, Addresses } from 'viem/tempo' import { Hooks } from 'tempo.ts/wagmi' import { parseUnits } from 'viem' import { useSendCallsSync } from 'wagmi' const pathUsd = '0x20c0000000000000000000000000000000000000' const alphaUsd = '0x20c0000000000000000000000000000000000001' function ManageOrder() { const sendCalls = useSendCallsSync() const cancelOrder = Hooks.dex.useCancelSync() // [!code hl] const placeOrder = () => { const calls = [ Actions.token.approve.call({ spender: Addresses.stablecoinExchange, amount: parseUnits('100', 6), token: pathUsd, }), Actions.dex.place.call({ token: alphaUsd, amount: parseUnits('100', 6), type: 'buy', tick: 0, }), ] sendCalls.sendCallsSync({ calls }) } return ( <>
{ event.preventDefault() const formData = new FormData(event.target as HTMLFormElement) const orderId = BigInt(formData.get('orderId') as string) cancelOrder.mutate({ orderId }) // [!code hl] } }> {/* [!code hl] */}
) } ``` ```ts [wagmi.config.ts] // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Determining quote token Each token has a designated quote token that it trades against on the DEX. For most stablecoins, this will be `pathUSD`. Use the `token.useGetMetadata` hook to retrieve a token's quote token. :::code-group ```ts twoslash [example.ts] // @errors: 2322 import { config } from './wagmi.config' declare module 'wagmi' { interface Register { config: typeof config } } // ---cut--- import { Hooks } from 'tempo.ts/wagmi' const { data: metadata } = Hooks.token.useGetMetadata({ // [!code focus] token: '0x20c0000000000000000000000000000000000001', // AlphaUSD // [!code focus] }) // [!code focus] console.log('Token:', metadata?.symbol) // @log: Token: AlphaUSD console.log('Quote Token:', metadata?.quoteToken) // returns `pathUSD` address // @log: Quote Token: 0x20c0000000000000000000000000000000000000 ``` ```ts [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Flip order Flip orders automatically switch between buy and sell sides when filled, providing continuous liquidity. Use viem's [`dex.placeFlip`](https://viem.sh/tempo/actions/dex.placeFlip) to create a flip order call. :::code-group ```tsx twoslash [PlaceFlipOrder.tsx] // @noErrors import { Actions, Addresses } from 'viem/tempo' import { parseUnits } from 'viem' import { useSendCallsSync } from 'wagmi' const pathUsd = '0x20c0000000000000000000000000000000000000' const alphaUsd = '0x20c0000000000000000000000000000000000001' function PlaceFlipOrder(props: { orderType: 'buy' | 'sell' }) { const { orderType } = props // buying AlphaUSD requires we spend pathUSD const spendToken = orderType === 'buy' ? pathUsd : alphaUsd const sendCalls = useSendCallsSync() return ( ) } ``` ```ts [wagmi.config.ts] // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Place order at specific price Ticks represent prices relative to the quote token (usually pathUSD). The formula is: ``` tick = (price - 1) * 100_000 ``` For example, price $1.0000 → tick = 0, price $0.9990 → tick = -10, and price $1.0010 → tick = 10. Use the `Tick` utility to convert between prices and ticks: ```tsx import { Actions, Tick } from 'viem/tempo' // [!code hl] import { parseUnits } from 'viem' const alphaUsd = '0x20c0000000000000000000000000000000000001' // buy order at $0.9990 (tick: -10) // [!code hl] const buyCall = Actions.dex.place.call({ // [!code hl] token: alphaUsd, // [!code hl] amount: parseUnits('100', 6), // [!code hl] type: 'buy', // [!code hl] tick: Tick.fromPrice('0.9990'), // -10 // [!code hl] }) // [!code hl] // sell order at $1.0010 (tick: 10) // [!code hl] const sellCall = Actions.dex.place.call({ // [!code hl] token: alphaUsd, // [!code hl] amount: parseUnits('100', 6), // [!code hl] type: 'sell', // [!code hl] tick: Tick.fromPrice('1.0010'), // 10 // [!code hl] }) // [!code hl] ``` For more details including tick precision, limits, and calculation examples, see [Understanding Ticks](/protocol/exchange/providing-liquidity#understanding-ticks). ### Best practices #### Batch calls You can batch the calls to approve spend and place the order in a single transaction for efficiency. See the [guide on batch transactions](/guide/use-accounts/batch-transactions) for more details. ### Learning resources import { IndexSupplyQuery } from '../../../components/IndexSupplyQuery' ## View the Orderbook Query and inspect the orderbook to see available liquidity, price levels, and individual orders on Tempo's Stablecoin DEX. ### Recommended Approach We recommend using indexed data to query the orderbook for better performance and ease of use. While you can query logs and transactions directly from an RPC node, indexed data providers offer structured SQL interfaces that make complex queries simpler and more efficient. In this guide, we use [Index Supply](https://www.indexsupply.net) as our indexing provider, but you're free to choose your own indexing solution or query the chain directly based on your needs. ### Recipes #### Get the current spread Query the best bid and ask prices to calculate the current spread for a token pair. Find the highest bid prices (buyers) for AlphaUSD. This query filters out fully filled and cancelled orders, groups by price level (tick), and shows the top 5 bid prices with their total liquidity. Find the lowest ask prices (sellers) for AlphaUSD. The spread is the difference between the highest bid and lowest ask price. #### Inspect order depth View aggregated liquidity at each price level to understand the orderbook structure. This query shows all active orders from both OrderPlaced and FlipOrderPlaced events for BetaUSD. #### Inspect an individual order ##### Order details Get detailed information about a specific order including its placement details, fill history, and cancellation status. This query inspects the details of the most recent order for AlphaUSD. It shows when the order was created, at what price (tick), the order size, and who placed it. ##### Order fill status Check if an order has been partially or fully filled. This query shows up to 5 fill events for order number `2`, including the amount filled in each transaction and whether it was a partial fill. ##### Cancelled orders Check if an order has been cancelled. This query returns an order for AlphaUSD that was explicitly cancelled by the maker before being fully filled. #### Get recent trade prices View the last prices a token traded at to understand recent market activity. This query joins order fill events with their corresponding placement details to show the price tick and amount for recent trades. import * as Demo from '../../../components/guides/Demo.tsx' import * as Step from '../../../components/guides/steps' import * as Token from '../../../components/guides/tokens' ## Accept a Payment Accept stablecoin payments in your application. Learn how to receive payments, verify transactions, and reconcile payments using memos. ### Receiving Payments Payments are automatically credited to the recipient's address when a transfer is executed. You don't need to do anything special to "accept" a payment, it happens automatically onchain. In this basic receiving demo you can see the balances update after you add funds to your account, using the `getBalance` and `watchEvent` calls documented below. ### Verifying Payments Check if a payment has been received by querying the token balance or listening for transfer events: #### Check Balance :::code-group ```ts [TypeScript] import { client } from './viem.config' const balance = await client.token.getBalance({ token: '0x20c0000000000000000000000000000000000001', // AlphaUSD address: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', }) console.log('Balance:', balance) ``` ```rust [Rust] 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"), // Alpha USD // [!code focus] provider, // [!code focus] ) // [!code focus] .balanceOf(address!("0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb")) // [!code focus] .call() // [!code focus] .await?; // [!code focus] Ok(()) } ``` ::: #### 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, }) } }) }, }) ``` ```rust [Rust] use alloy::{primitives::address, providers::ProviderBuilder}; use futures_util::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(()) } ``` ::: ### 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) } }) }, }) ``` ```rust [Rust] use alloy::{primitives::address, providers::ProviderBuilder}; use futures_util::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] // Mark invoice as paid in your database. let invoice_id = transfer.memo; // [!code focus] mark_invoice_paid(invoice_id).await?; // [!code focus] } // [!code focus] Ok(()) } ``` ::: ### Smart Contract Integration If you're building a smart contract that accepts payments: ```solidity contract PaymentReceiver { ITIP20 public token; mapping(bytes32 => bool) public paidInvoices; event PaymentReceived( address indexed payer, uint256 amount, bytes32 indexed invoiceId ); function receivePayment( address payer, uint256 amount, bytes32 invoiceId ) external { require(!paidInvoices[invoiceId], "Invoice already paid"); // Transfer tokens from payer to this contract token.transferFrom(payer, address(this), amount); paidInvoices[invoiceId] = true; emit PaymentReceived(payer, amount, invoiceId); } } ``` ### Payment Verification Best Practices 1. **Verify onchain**: Always verify payments onchain before marking orders as paid 2. **Use memos**: Request memos from payers to link payments to invoices or orders 3. **Check confirmations**: Wait for transaction finality (\~1 second on Tempo) before processing 4. **Handle edge cases**: Account for partial payments, refunds, and failed transactions ### Cross-Stablecoin Payments If you need to accept payments in a specific stablecoin but receive a different one, use the exchange to swap: ```ts // User sends USDC, but you need USDT // Swap USDC to USDT using the exchange const { receipt } = await client.dex.sellSync({ tokenIn: usdcAddress, tokenOut: usdtAddress, amountIn: receivedAmount, minAmountOut: receivedAmount * 99n / 100n, // 1% slippage }) ``` ### Next Steps * **[Send a payment](/guide/payments/send-a-payment)** to learn how to send payments * Learn more about [Exchange](/guide/stablecoin-exchange) for cross-stablecoin payments import LucideSend from '~icons/lucide/send' import LucideHandshake from '~icons/lucide/handshake' import LucideCircleDollarSign from '~icons/lucide/circle-dollar-sign' import LucideHeartHandshake from '~icons/lucide/heart-handshake' import LucideGitBranch from '~icons/lucide/git-branch' import * as Card from "../../../components/Card.tsx" ## Stablecoin Payments Send and receive payments using stablecoins on Tempo. Learn how to integrate payments into your application with flexible fee options and sponsorship capabilities. import * as Demo from '../../../components/guides/Demo.tsx' import * as Step from '../../../components/guides/steps' import { betaUsd } from '../../../components/guides/tokens.ts' import LucideCoins from '~icons/lucide/coins' import * as Card from '../../../components/Card.tsx' import { Tabs } from '../../../components/Tabs.tsx' ## Pay Fees in Any Stablecoin Configure users to pay transaction fees in any supported stablecoin. Tempo's flexible fee system allows users to pay fees with the same token they're using, eliminating the need to hold a separate gas token. ### Demo By the end of this guide you will be able to pay fees in any stablecoin on Tempo. ### Quick Snippet Using a custom fee token is as simple as passing a `feeToken` attribute to mutable actions like `useTransferSync`, `useSendTransactionSync`, and more.
```tsx twoslash // @noErrors import { Hooks } from 'tempo.ts/wagmi' import { parseUnits } from 'viem' const alphaUsd = '0x20c0000000000000000000000000000000000001' const betaUsd = '0x20c0000000000000000000000000000000000002' const sendPayment = Hooks.token.useTransferSync() sendPayment.mutate({ amount: parseUnits('100', 6), feeToken: betaUsd, // [!code ++] to: '0x0000000000000000000000000000000000000000', token: alphaUsd, }) ```
```tsx twoslash // @noErrors import { parseUnits } from 'viem' import { client } from './viem.config' const alphaUsd = '0x20c0000000000000000000000000000000000001' const betaUsd = '0x20c0000000000000000000000000000000000002' const receipt = await client.token.transferSync({ amount: parseUnits('100', 6), feeToken: betaUsd, // [!code ++] to: '0x0000000000000000000000000000000000000000', token: alphaUsd, }) ``` ### Steps ::::steps #### Set up Wagmi & integrate accounts Ensure that you have set up your project with Wagmi and integrated accounts by following either of the guides: * [Embed Passkey accounts](/guide/use-accounts/embed-passkeys) * [Connect to wallets](/guide/use-accounts/connect-to-wallets) #### Add testnet funds¹ Before you can pay fees in a token of your choice, you need to fund your account. In this guide you will be sending `AlphaUSD` (`0x20c000…0001`) and paying fees in `BetaUSD` (`0x20c000…0002`). The built-in Tempo testnet faucet includes `AlphaUSD` and `BetaUSD` when funding. :::code-group ```tsx twoslash [AddFunds.ts] // @noErrors import { Hooks } from 'tempo.ts/wagmi' import { useConnection } from 'wagmi' function AddFunds() { const { address } = useConnection() const { mutate, isPending } = Hooks.faucet.useFundSync() return ( ) } ``` ```tsx twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: :::warning ¹ It is important to note that the `addFunds` Hook only works on testnets as a convenience feature to get started quickly. For production, you will need to onramp & fund your account manually. ::: #### Add pay with fee token logic Now that you have `AlphaUSD` to send and `BetaUSD` to pay fees with, you can add a form that allows users to select a fee token and send a payment. After this step, your users can send payments with a specified fee token by clicking the "Send Payment" button! :::code-group ```tsx twoslash [PayWithFeeToken.tsx] import { Hooks } from 'tempo.ts/wagmi' import { parseUnits } from 'viem' const alphaUsd = '0x20c0000000000000000000000000000000000001' const betaUsd = '0x20c0000000000000000000000000000000000002' // @noErrors function PayWithFeeToken() { const sendPayment = Hooks.token.useTransferSync() const metadata = Hooks.token.useGetMetadata({ token: alphaUsd, }) return (
{ event.preventDefault() const formData = new FormData(event.target as HTMLFormElement) const recipient = (formData.get('recipient') || '0x0000000000000000000000000000000000000000') as `0x${string}` const feeToken = (formData.get('feeToken') || alphaUsd) as `0x${string}` sendPayment.mutate({ // [!code hl] amount: parseUnits('100', metadata.data?.decimals ?? 6), // [!code hl] to: recipient, // [!code hl] token: alphaUsd, // [!code hl] feeToken: feeToken, // [!code hl] }) // [!code hl] } }>
) } ``` ```tsx twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Display a receipt Now that users can send payments with a specified fee token, you can link to the transaction receipt. :::code-group ```tsx twoslash [PayWithFeeToken.tsx] import { Hooks } from 'tempo.ts/wagmi' import { parseUnits } from 'viem' const alphaUsd = '0x20c0000000000000000000000000000000000001' const betaUsd = '0x20c0000000000000000000000000000000000002' // @noErrors function PayWithFeeToken() { const sendPayment = Hooks.token.useTransferSync() const metadata = Hooks.token.useGetMetadata({ token: alphaUsd, }) return ( <> {/* ... your payment form ... */} {sendPayment.data && ( // [!code ++] {/* // [!code ++] */} View receipt {/* // [!code ++] */} // [!code ++] )} // [!code ++] ) } ``` ```tsx twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Next steps Now that you have made a payment using a desired fee token, you can: * Follow a guide on how to [sponsor user fees](/guide/payments/sponsor-user-fees) to enable gasless transactions * Learn more about [transaction fees](/protocol/fees) ::::
::::steps #### Set up Viem Client First, we will set up a Viem client configured with Tempo. ```ts twoslash [viem.config.ts] // [!include ~/snippets/viem.config.ts:setup] ``` :::info For simplicity of the guide, this example uses a Private Key (Secp256k1) account instead of Passkeys (WebAuthn). ::: #### Add testnet funds¹ Before you can pay fees in a token of your choice, you need to fund your account. In this guide you will be sending `AlphaUSD` (`0x20c000…0001`) and paying fees in `BetaUSD` (`0x20c000…0002`). The built-in Tempo testnet faucet includes `AlphaUSD` and `BetaUSD` when funding. :::code-group ```tsx twoslash [example.ts] // @noErrors import { client } from './viem.config' await client.faucet.fundSync({ account: client.account, }) ``` ```tsx twoslash [viem.config.ts] filename="viem.config.ts" // @noErrors // [!include ~/snippets/viem.config.ts:setup] ``` ::: #### Add pay with fee token logic Now that you have `AlphaUSD` to send and `BetaUSD` to pay fees with, you can now add logic to send a payment with a specified fee token. :::code-group ```tsx twoslash [example.ts] // @noErrors import { client } from './viem.config' const receipt = await client.token.transferSync({ amount: parseUnits('100', 6), feeToken: betaUsd, // [!code hl] to: '0x0000000000000000000000000000000000000000', token: alphaUsd, }) ``` ```tsx twoslash [viem.config.ts] filename="viem.config.ts" // @noErrors // [!include ~/snippets/viem.config.ts:setup] ``` ::: ::::
## Recipes ### 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).
```tsx twoslash // @noErrors // [!include ~/snippets/unformatted/fee.setUserToken.ts:wagmi-hooks] ```
```ts twoslash // @noErrors // [!include ~/snippets/unformatted/fee.setUserToken.ts:viem] ``` ### Learning Resources import * as Demo from '../../../components/guides/Demo.tsx' import * as Step from '../../../components/guides/steps' import * as Card from '../../../components/Card.tsx' import LucideCode from '~icons/lucide/code' import LucideSend from '~icons/lucide/send' ## Send a Payment Send stablecoin payments between accounts on Tempo. Payments can include optional memos for reconciliation and tracking. ### Demo By the end of this guide you will be able to send payments on Tempo with an optional memo. ### Steps ::::steps #### Set up Wagmi & integrate accounts Ensure that you have set up your project with Wagmi and integrated accounts by following either of the guides: * [Embed Passkey accounts](/guide/use-accounts/embed-passkeys) * [Connect to wallets](/guide/use-accounts/connect-to-wallets) #### Add testnet funds¹ Before you can send a payment, you need to fund your account. In this guide you will be sending `AlphaUSD` (`0x20c000…0001`). The built-in Tempo testnet faucet funds accounts with `AlphaUSD`. :::code-group ```tsx twoslash [AddFunds.ts] // @noErrors import { Hooks } from 'tempo.ts/wagmi' import { useConnection } from 'wagmi' function AddFunds() { const { address } = useConnection() const { mutate, isPending } = Hooks.faucet.useFundSync() return ( ) } ``` ```tsx twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: :::warning ¹ It is important to note that the `addFunds` Hook only works on testnets as a convenience feature to get started quickly. For production, you will need to onramp & fund your account manually. ::: #### Add send payment logic Now that you have `AlphaUSD` you are ready to add logic to send a payment with an optional memo. :::code-group ```tsx twoslash [SendPaymentWithMemo.tsx] import { Hooks } from 'tempo.ts/wagmi' import { parseUnits, stringToHex, pad } from 'viem' // @noErrors function SendPaymentWithMemo() { const sendPayment = Hooks.token.useTransferSync() // [!code hl] return (
{ event.preventDefault() const formData = new FormData(event.target as HTMLFormElement) const recipient = (formData.get('recipient') || '0x0000000000000000000000000000000000000000') as `0x${string}` const memo = (formData.get('memo') || '') as string sendPayment.mutate({ // [!code hl] amount: parseUnits('100', 6), // [!code hl] to: recipient, // [!code hl] token: '0x20c0000000000000000000000000000000000001', // [!code hl] memo: memo ? pad(stringToHex(memo), { size: 32 }) : undefined, // [!code hl] }) // [!code hl] } }>
{/* [!code hl] */}
) } ``` ```tsx twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Display receipt Now that you can send a payment, you can display the transaction receipt on success. :::code-group ```tsx twoslash [SendPaymentWithMemo.tsx] import { Hooks } from 'tempo.ts/wagmi' import { parseUnits, stringToHex, pad } from 'viem' // @noErrors function SendPaymentWithMemo() { const sendPayment = Hooks.token.useTransferSync() return ( <> {/* ... your payment form ... */} {sendPayment.data && ( // [!code ++] {/* // [!code ++] */} View receipt {/* // [!code ++] */} // [!code ++] )} {/* // [!code ++] */} ) } ``` ```tsx twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Next steps Now that you have made a payment you can * **[Accept a payment](/guide/payments/accept-a-payment)** to receive payments in your application * Learn about [Batch Transactions](/guide/use-accounts/batch-transactions) * Send a payment [with a specific fee token](/guide/payments/pay-fees-in-any-stablecoin) :::: ### Recipes #### Basic transfer Send a payment using the standard `transfer` function: :::code-group ```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] ``` ```rs [Rust] use alloy::{ primitives::{U256, 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 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).pow(U256::from(10e6)), // 100 tokens (6 decimals) // [!code focus] ) // [!code focus] .send() // [!code focus] .await? // [!code focus] .get_receipt() // [!code focus] .await?; // [!code focus] Ok(()) } ``` ::: #### Transfer with memo Include a memo for payment reconciliation and tracking: :::code-group ```ts [example.ts] import { parseUnits } from 'viem' import { client } from './viem.config' const invoiceId = pad(stringToHex('INV-12345'), { size: 32 }) 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] ``` ```rs [Rust] use alloy::{ primitives::{B256, U256, 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 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).pow(U256::from(10e6)), // 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] Ok(()) } ``` ::: The memo is a 32-byte value that can store payment references, invoice IDs, order numbers, or any other metadata. #### Using Solidity If you're building a smart contract that sends payments: ```solidity interface ITIP20 { function transfer(address to, uint256 amount) external returns (bool); function transferWithMemo(address to, uint256 amount, bytes32 memo) external; } contract PaymentSender { ITIP20 public token; function sendPayment(address recipient, uint256 amount) external { token.transfer(recipient, amount); } function sendPaymentWithMemo( address recipient, uint256 amount, bytes32 invoiceId ) external { token.transferWithMemo(recipient, amount, invoiceId); } } ``` #### Batch payment transactions Send multiple payments in a single transaction using batch transactions: :::code-group ```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 }) ``` ```ts [viem.config.ts] filename="viem.config.ts" // [!include ~/snippets/viem.config.ts:setup] ``` ```rust [Rust] use alloy::{ primitives::{U256, address}, 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 calls = vec![ // [!code focus] Call { // [!code focus] to: address!("0x20c0000000000000000000000000000000000001").into(), // [!code focus] input: ITIP20::transferCall { // [!code focus] to: recipient1, // [!code focus] amount: U256::from(100).pow(U256::from(10e6)), // [!code focus] } // [!code focus] .abi_encode() // [!code focus] .into(), // [!code focus] value: U256::ZERO, // [!code focus] }, // [!code focus] Call { // [!code focus] to: address!("0x20c0000000000000000000000000000000000001").into(), // [!code focus] input: ITIP20::transferCall { // [!code focus] to: recipient2, // [!code focus] amount: U256::from(50).pow(U256::from(10e6)), // [!code focus] } // [!code focus] .abi_encode() // [!code focus] .into(), // [!code focus] value: U256::ZERO, // [!code focus] }, // [!code focus] ]; // [!code focus] let tx_hash = provider // [!code focus] .send_transaction(TempoTransactionRequest { // [!code focus] calls, // [!code focus] ..Default::default() // [!code focus] }) // [!code focus] .await? // [!code focus] .tx_hash(); // [!code focus] Ok(()) } ``` ::: #### Payment events When you send a payment, the token contract emits events: * **Transfer**: Standard ERC-20 transfer event * **TransferWithMemo**: Additional event with memo (if using `transferWithMemo`) You can filter these events to track payments in your off-chain systems. ### Best practices #### Loading state Users should see a loading state when the payment is being processed. You can use the `isPending` property from the `useTransferSync` hook to show pending state to the user. #### Error handling If an error unexpectedly occurs, you can display an error message to the user by using the `error` property from the `useTransferSync` hook. ```tsx import { Hooks } from 'tempo.ts/wagmi' function SendPayment() { const sendPayment = Hooks.token.useTransferSync() return ( <> {/* ... your paymentform ... */} {sendPayment.error &&
Error: {sendPayment.error.message}
} ) } ``` ### Learning resources import * as Demo from '../../../components/guides/Demo.tsx' import * as Step from '../../../components/guides/steps' import * as Token from '../../../components/guides/tokens' import * as Card from '../../../components/Card.tsx' import LucideCode from '~icons/lucide/code' import LucideSend from '~icons/lucide/send' import LucideGitBranch from '~icons/lucide/git-branch' ## Send Parallel Transactions Submit multiple transactions in parallel using Tempo's [2D nonces](/protocol/transactions/spec-tempo-transaction). The `nonceKey` property allow you to send concurrent transactions without waiting for each one to confirm sequentially. #### Understanding nonce keys Tempo uses a **2D nonce system** that enables parallel transaction execution: * **Protocol nonce (key 0)**: The default sequential nonce. Transactions must be processed in order. * **User nonces (keys 1+)**: Independent nonce sequences that allow concurrent transaction submission. When you send a transaction without specifying a `nonceKey`, it uses the protocol nonce and behaves like a standard sequential transaction. By specifying different nonce keys, you can submit multiple transactions simultaneously without waiting for confirmations. #### Key management strategies There are two ways to specify a `nonceKey` for a transaction: ##### Explicit keys Explicit keys (1n, 2n, etc.) are best when you want to reuse keys. For high-throughput applications, these keys can be used to load-balance transaction submission to the network in a gas-efficient way. You can track the most recent call on each key in your application, and, once that transaction confirms, the same key can be used for new transactions. This approach is more gas efficient, as provisioning a new `nonceKey` [costs gas](/protocol/transactions/spec-tempo-transaction#gas-schedule). ##### `'random'` keys For simple cases where you don't need to track keys. This approach is recommended when handling bursts of high activity, in which you need to submit transfers to the network and don't care about the added gas costs for provisioning multiple keys. ### Demo By the end of this guide you will understand how to send parallel payments using nonce keys. ### Steps ::::steps #### Set up Wagmi & integrate accounts Ensure that you have set up your project with Wagmi and integrated accounts by following either of the guides: * [Embed Passkey accounts](/guide/use-accounts/embed-passkeys) * [Connect to wallets](/guide/use-accounts/connect-to-wallets) #### Fetch current nonces In order to send a transfer on a custom `nonceKey`, you need to know the current nonce value for the keys you will send on. :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { parseUnits } from 'viem' import { config } from './wagmi.config' // @noErrors // Fetch nonces for each key in parallel const [nonce1, nonce2] = await Promise.all([ Actions.nonce.getNonce(config, { account, nonceKey: 1n }), // [!code hl] Actions.nonce.getNonce(config, { account, nonceKey: 2n }), // [!code hl] ]) console.log('Current nonce for nonceKey 1:', nonce1) console.log('Current nonce for nonceKey 2:', nonce2) ``` ```tsx twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: :::warning This nonce-fetching behavior is temporary: a forthcoming update will let the network automatically determine the nonce for a user `nonceKey`. ::: #### Send concurrent transactions with nonce keys To send multiple transactions in parallel, specify different `nonceKey` values. Each nonce key maintains its own independent sequence: :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { parseUnits } from 'viem' import { config } from './wagmi.config' // @noErrors const account = '0x...' // your sender account const alphaUsd = '0x20c0000000000000000000000000000000000001' const [nonce1, nonce2] = await Promise.all([ Actions.nonce.getNonce(config, { account, nonceKey: 1n }), Actions.nonce.getNonce(config, { account, nonceKey: 2n }), ]) // Send both transfers in parallel using different nonce keys const [hash1, hash2] = await Promise.all([ Actions.token.transfer(config, { amount: parseUnits('100', 6), to: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8', token: alphaUsd, nonceKey: 1n, // [!code hl] nonce: Number(nonce1), // [!code hl] }), Actions.token.transfer(config, { amount: parseUnits('50', 6), to: '0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC', token: alphaUsd, nonceKey: 2n, // [!code hl] nonce: Number(nonce2), // [!code hl] }), ]) console.log('Transaction 1:', hash1) console.log('Transaction 2:', hash2) ``` ```tsx twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: :::: ### Recipes #### Use random nonce keys For simple cases where you don't need to manage specific keys, use `'random'` to automatically generate a unique nonce key: :::code-group ```ts twoslash [example.ts] import { Actions } from 'tempo.ts/wagmi' import { parseUnits } from 'viem' import { config } from './wagmi.config' // @noErrors // Using 'random' automatically generates a unique nonce key const hash = await Actions.token.transfer(config, { amount: parseUnits('100', 6), to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', token: '0x20c0000000000000000000000000000000000001', nonceKey: 'random', // [!code hl] }) console.log('Transaction hash:', hash) ``` ```tsx twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:setup] ``` ::: #### Query active nonce keys Track how many nonce keys your account is using: :::code-group ```ts [example.ts] import { client } from './viem.config' // Get the count of active nonce keys for an account const count = await client.nonce.getNonceKeyCount({ account: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', }) console.log('Active nonce keys:', count) ``` ```ts [viem.config.ts] filename="viem.config.ts" // [!include ~/snippets/viem.config.ts:setup] ``` ::: ### Best Practices #### When to use nonce keys Use nonce keys when you need to: * Send multiple independent transactions simultaneously * Build high-throughput applications that can't wait for sequential confirmations * Process payments to multiple recipients concurrently #### When to use batch transactions instead Use [batch transactions](/guide/use-accounts/batch-transactions) instead of nonce keys when: * Operations need to be atomic * Calls have sequential dependencies * You want a single transaction fee for multiple operations Batch transactions are not as appropriate for the payments to multiple recipients use case, because if a single payment fails, all the calls in the batch transaction roll back. ### Learning Resources import * as Card from '../../../components/Card.tsx' import * as Demo from '../../../components/guides/Demo.tsx' import * as Step from '../../../components/guides/steps' import LucideBook from '~icons/lucide/book' import LucideCoins from '~icons/lucide/coins' import LucideFileCode from '~icons/lucide/file-code' import LucideLayers from '~icons/lucide/layers' import PublicTestnetSponsorTip from '../../../snippets/public-testnet-sponsor-tip.mdx' ## Sponsor User Fees Enable gasless transactions by sponsoring transaction fees for your users. Tempo's native fee sponsorship allows applications to pay fees on behalf of users, improving UX and removing friction from payment flows. ### Demo ### Steps ::::steps #### Set up the fee payer service :::tip Tempo provides a public testnet fee payer service at `https://sponsor.testnet.tempo.xyz` that you can use for development and testing. If you want to run your own, follow the instructions below. ::: You can stand up a minimal fee payer service using the `Handler.feePayer` handler provided by the Tempo TypeScript SDK ([link](/sdk/typescript/server/handler.feePayer)). To sponsor transactions, you need a funded account that will act as the fee payer. ```ts twoslash [server.ts] // @noErrors // [!include ~/snippets/unformatted/withFeePayer.ts:server] ``` #### Configure your client to use the fee payer service Use the `withFeePayer` transport provided by Viem ([link](https://viem.sh/tempo/transports/withFeePayer)). It routes transactions to the configured fee payer service for sponsorship when `feePayer: true` is requested on a transaction. ```ts twoslash [wagmi.config.ts] // @noErrors // [!include ~/snippets/wagmi.config.ts:withFeePayer] ``` #### Sponsor your user's transactions 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. ::: :::code-group ```tsx twoslash [SendSponsoredPayment.tsx] filename="SendSponsoredPayment.tsx" import { Hooks } from 'tempo.ts/wagmi' import { parseUnits } from 'viem' import { privateKeyToAccount } from 'viem/accounts' const alphaUsd = '0x20c0000000000000000000000000000000000001' // @noErrors function SendSponsoredPayment() { const sponsorAccount = privateKeyToAccount('0x...') const sendPayment = Hooks.token.useTransferSync() // [!code hl] const metadata = Hooks.token.useGetMetadata({ token: alphaUsd, }) return (
{ event.preventDefault() const formData = new FormData(event.target as HTMLFormElement) const recipient = (formData.get('recipient') || '0x0000000000000000000000000000000000000000') as `0x${string}` sendPayment.mutate({ // [!code hl] amount: parseUnits('100', metadata.data.decimals), // [!code hl] feePayer: true, // [!code focus] to: recipient, // [!code hl] token: alphaUsd, // [!code hl] }) // [!code hl] } }>
) } ``` ```tsx twoslash [wagmi.config.ts] filename="wagmi.config.ts" // @noErrors // [!include ~/snippets/wagmi.config.ts:withFeePayer] ``` ::: ### 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 * Explore [Batch Transactions](/guide/use-accounts/batch-transactions) to sponsor multiple operations at once * Learn how to [Pay Fees in Any Stablecoin](/guide/payments/pay-fees-in-any-stablecoin) :::: ### Recipes #### Local account sponsorship 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. ```ts twoslash [client.ts] // @noErrors import { createClient, http } from 'viem' import { privateKeyToAccount } from 'viem/accounts' import { tempoTestnet } from 'viem/chains' const client = createClient({ chain: tempoTestnet, transport: http(), }) const { receipt } = await client.token.transferSync({ amount: parseUnits('10.5', 6), to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', token: '0x20c0000000000000000000000000000000000000', feePayer: privateKeyToAccount('0x...'), // [!code hl] }) ``` \::: ### Best practices 1. **Set sponsorship limits**: Implement daily or per-user limits to control costs 2. **Monitor expenses**: Track sponsorship costs regularly to stay within budget 3. **Consider selective sponsorship**: Only sponsor fees for specific operations or user segments 4. **Educate users**: Clearly communicate when fees are being sponsored #### Security Considerations * **Transaction-specific**: Fee payer signatures are tied to specific transactions * **No delegation risk**: Fee payer can't execute arbitrary transactions * **Balance checks**: Network verifies fee payer has sufficient balance * **Signature validation**: Both signatures must be valid ### Learning Resources import { Callout } from 'vocs/components' import LucideServer from '~icons/lucide/server' import LucideHardDrive from '~icons/lucide/hard-drive' import LucideDownload from '~icons/lucide/download' import LucidePlay from '~icons/lucide/play' import * as Card from "../../../components/Card.tsx" ## Tempo Node Running a Tempo node allows you to interact with the network directly, providing your own RPC access or contributing to network infrastructure. Nodes provide JSON-RPC API access for applications, explorers, and wallets but do not participate in consensus. ### Getting Started Validator onboarding requires coordination with the Tempo team. Reach out to us if you are interested. ## Installation We provide three different installation paths - installing a pre-built binary, building from source or using our provided Docker image. ### Pre-built Binary ```bash /dev/null/download.sh#L1-4 curl -L https://tempo.xyz/install | bash tempo --version ``` To update Tempo in the future, simply run `tempoup`. ### Build from Source ```bash /dev/null/build.sh#L1-10 # Install Rust curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh source $HOME/.cargo/env # Build and install from source using cargo cargo install --git https://github.com/tempoxyz/tempo.git tempo --root /usr/local tempo --version ``` ### Docker You can find the latest tagged version of Tempo at [the Tempo GHCR package](https://github.com/tempoxyz/tempo/pkgs/container/tempo). ```bash /dev/null/docker.sh#L1-4 # Pull the latest Docker image docker pull ghcr.io/tempoxyz/tempo:0.7.2 # Run the Docker container docker run -d --name tempo ghcr.io/tempoxyz/tempo:0.7.2 --version docker logs tempo ``` ## System Requirements These are the minimum and recommended system requirements for running a validator/RPC node. It is likely, that the nodes will not require as much resources at the beginning of the chain, but we still highly recommend to follow the recommended specifications. This will allow for future growth and scalability. ### RPC Node | Component | Minimum | Recommended | | ----------- | ---------- | ----------- | | **CPU** | 8 cores | 16+ cores | | **RAM** | 16 GB | 32 GB | | **Storage** | 250 GB SSD | 500 GB NVMe | | **Network** | 500 Mbps | 1 Gbps | ### Validator Node | Component | Minimum | Recommended | | ----------- | ----------- | ----------- | | **CPU** | 8 cores | 16+ cores | | **RAM** | 16 GB | 32 GB | | **Storage** | 100 GB NVMe | 1 TB NVMe | | **Network** | 1 Gbps | 1 Gbps | ### Ports | Port | Protocol | Purpose | Expose | | ----- | -------- | ------------- | --------------- | | 30303 | TCP/UDP | Execution P2P | Public | | 8000 | TCP | Consensus P2P | Validators only | | 8545 | TCP | HTTP RPC | Optional | | 8546 | TCP | WebSocket RPC | Optional | | 9000 | TCP | Metrics | Internal | ## Running an RPC Node RPC nodes provide API access to the Tempo network without participating in consensus. These nodes currently observe and sync with the network's latest state. By default, RPC nodes will be in archive mode, meaning they do not prune historical state. ### Quick Start ```bash /dev/null/quickstart.sh#L1-15 # Download snapshot (this will help you sync much faster) tempo download # Run node tempo node \ --follow \ --http --http.port 8545 \ --http.api eth,net,web3,txpool,trace ``` ### Manually downloading snapshots Daily snapshots for the persistent testnet can be found at [https://snapshots.tempoxyz.dev](https://snapshots.tempoxyz.dev). You can extract them either using `tempo download --url ` or downloading them manually and extracting them using `curl | lz4 -d | tar -xzf -`. ### Example Systemd Service ```bash /dev/null/systemd.sh#L1-55 sudo tee /etc/systemd/system/tempo.service > /dev/null < \\ --follow \\ --port 30303 \\ --discovery.addr 0.0.0.0 \\ --discovery.port 30303 \\ --http \\ --http.addr 0.0.0.0 \\ --http.port 8545 \\ --http.api eth,net,web3,txpool,trace \\ --metrics 9000 \\ Restart=always RestartSec=10 StandardOutput=journal StandardError=journal SyslogIdentifier=tempo LimitNOFILE=infinity [Install] WantedBy=multi-user.target EOF # Enable and start sudo systemctl daemon-reload sudo systemctl enable tempo sudo systemctl start tempo # Check status sudo systemctl status tempo # View logs sudo journalctl -u tempo -f ``` ### Monitoring Once you've set up your node (whether it's with Systemd or Docker), you can verify that it's running correctly using these commands (`cast` requires installation of [Foundry](/sdk/foundry)): ```bash /dev/null/monitor.sh#L1-11 # Check service status sudo systemctl status tempo # Check peer connections (should be non-zero) cast rpc net_peerCount --rpc-url http://localhost:8545 # Check block height (should be steadily increasing) cast block-number --rpc-url http://localhost:8545 cast block --rpc-url http://localhost:8545 # Search logs sudo journalctl -u tempo -n 1000 | grep -i "error" ``` In a production setting, you should monitor the [Reth metrics port](https://reth.rs/run/monitoring) using a tool like Prometheus or Grafana. import LucideCoins from '~icons/lucide/coins' import * as Demo from '../../../components/guides/Demo.tsx' import * as Step from '../../../components/guides/steps' import * as Card from '../../../components/Card.tsx' ## Create a Stablecoin Create your own stablecoin on Tempo using the [TIP-20 token standard](/protocol/tip20/overview). TIP-20 tokens are designed specifically for payments with built-in compliance features, role-based permissions, and integration with Tempo's payment infrastructure. ### Demo By the end of this guide, you will be able to create a stablecoin on Tempo. ### Steps ::::steps #### Set up Wagmi & integrate accounts Ensure that you have set up your project with Wagmi and integrated accounts by following either of the guides: * [Embed Passkey accounts](/guide/use-accounts/embed-passkeys) * [Connect to wallets](/guide/use-accounts/connect-to-wallets) #### Add testnet funds¹ Before we send off a transaction to deploy our stablecoin to the Tempo testnet, we need to make sure our account is funded with a stablecoin to cover the transaction fee. As we have configured our project to use `AlphaUSD` (`0x20c000…0001`) as the [default fee token](/quickstart/integrate-tempo#default-fee-token), we will need to add some `AlphaUSD` to our account. Luckily, the built-in Tempo testnet faucet supports funding accounts with `AlphaUSD`. :::code-group ```tsx twoslash [AddFunds.ts] // @noErrors import { Hooks } from 'tempo.ts/wagmi' import { useConnection } from 'wagmi' export function AddFunds() { const { address } = useConnection() const addFunds = Hooks.faucet.useFund() return ( ) } ``` ```tsx twoslash [config.ts] filename="config.ts" // @noErrors import { createConfig, http } from 'wagmi' import { tempoTestnet } from 'viem/chains' import { KeyManager, webAuthn } from 'tempo.ts/wagmi' export const config = createConfig({ chains: [tempoTestnet], connectors: [webAuthn({ keyManager: KeyManager.localStorage(), })], transports: { [tempo.id]: http(), }, }) ``` ::: :::warning ¹ It is important to note that the `addFunds` Hook only works on testnets as a convenience feature to get started quickly. For production, you will need to onramp & fund your account manually. ::: #### Add form fields Now that we have some funds to cover the transaction fee in our account, we can create a stablecoin. Let's create a new component and add some input fields for the **name** and **symbol** of our stablecoin, as shown in the demo. :::code-group ```tsx twoslash [CreateStablecoin.tsx] // @noErrors export function CreateStablecoin() { return (
{ event.preventDefault() const formData = new FormData(event.target as HTMLFormElement) const name = formData.get('name') as string const symbol = formData.get('symbol') as string }} >
) } ``` ```tsx twoslash [config.ts] filename="config.ts" // @noErrors import { createConfig, http } from 'wagmi' import { tempoTestnet } from 'viem/chains' import { KeyManager, webAuthn } from 'tempo.ts/wagmi' export const config = createConfig({ chains: [tempoTestnet], connectors: [webAuthn({ keyManager: KeyManager.localStorage(), })], transports: { [tempo.id]: http(), }, }) ``` ::: #### Add submission logic Now that we have some input fields, we need to add some logic to handle the submission of the form to create the stablecoin. After this step, your users will be able to create a stablecoin by clicking the "Create" button! :::code-group ```tsx twoslash [CreateStablecoin.tsx] import { Hooks } from 'tempo.ts/wagmi' // [!code ++] // @noErrors export function CreateStablecoin() { const create = Hooks.token.useCreateSync() // [!code ++] return (
{ event.preventDefault() const formData = new FormData(event.target as HTMLFormElement) const name = formData.get('name') as string const symbol = formData.get('symbol') as string create.mutate({ // [!code ++] name, // [!code ++] symbol, // [!code ++] currency: 'USD', // [!code ++] }) // [!code ++] }} >
) } ``` ```tsx twoslash [config.ts] filename="config.ts" // @noErrors import { createConfig, http } from 'wagmi' import { tempoTestnet } from 'viem/chains' import { KeyManager, webAuthn } from 'tempo.ts/wagmi' export const config = createConfig({ chains: [tempoTestnet], connectors: [webAuthn({ keyManager: KeyManager.localStorage(), })], transports: { [tempo.id]: http(), }, }) ``` ::: #### Add success state Now that users can submit the form and create a stablecoin, let's add a basic success state to display the name of the stablecoin and a link to the transaction receipt. :::code-group ```tsx twoslash [CreateStablecoin.tsx] import { Hooks } from 'tempo.ts/wagmi' // @noErrors export function CreateStablecoin() { const create = Hooks.token.useCreateSync() return (
{ event.preventDefault() const formData = new FormData(event.target as HTMLFormElement) const name = formData.get('name') as string const symbol = formData.get('symbol') as string create.mutate({ name, symbol, currency: 'USD', }) }} >
{create.data && ( // [!code ++]
{/* // [!code ++] */} {create.data.name} created successfully! {/* // [!code ++] */} {/* // [!code ++] */} View receipt {/* // [!code ++] */} {/* // [!code ++] */}
/* // [!code ++] */ )} {/* // [!code ++] */}
) } ``` ```tsx twoslash [config.ts] filename="config.ts" // @noErrors import { createConfig, http } from 'wagmi' import { tempoTestnet } from 'viem/chains' import { KeyManager, webAuthn } from 'tempo.ts/wagmi' export const config = createConfig({ chains: [tempoTestnet], connectors: [webAuthn({ keyManager: KeyManager.localStorage(), })], transports: { [tempo.id]: http(), }, }) ``` ::: #### Next steps Now that you have created your first stablecoin, you can now: * learn the [Best Practices](#best-practices) below * follow a guide on how to [mint](/guide/issuance/mint-stablecoins) and [more](/guide/issuance/manage-stablecoin) with your stablecoin. :::: ### Best Practices #### Loading State When the user is creating a stablecoin, we should show loading state to indicate that the process is happening. We can use the `isPending` property from the `useCreateSync` hook to show pending state to the user on our "Create" button. ```tsx ``` #### Error Handling If an error unexpectedly occurs, we should display an error message to the user. We can use the `error` property from the `useCreateSync` hook to show error state to the user. ```tsx twoslash // @noErrors export function CreateStablecoin() { // ... if (create.error) // [!code ++] return
Error: {create.error.message}
// [!code ++] // ... } ``` ### Learning Resources import * as Demo from '../../../components/guides/Demo.tsx' import * as Step from '../../../components/guides/steps' import * as Card from '../../../components/Card.tsx' import LucideFileText from '~icons/lucide/file-text' import LucideBookOpen from '~icons/lucide/book-open' ## Distribute Rewards Distribute rewards to token holders using TIP-20's built-in reward distribution mechanism. Rewards allow parties to incentivize holders of a token by distributing tokens proportionally based on their balance. Rewards can be distributed by anyone on any TIP-20 token, and claimed by any holder who has opted in. This guide covers both the reward distributor and token holder use cases. While the demo below uses a token you create, the same principles apply to any token. ### Demo Try out the complete rewards flow: create a token, opt in to receive rewards on it, create a reward for yourself, and claim it. ### Steps ::::steps #### \[Optional] Create a Stablecoin If you would like to distribute rewards on a token you have created, follow the [Create a Stablecoin](/guide/issuance/create-a-stablecoin) guide to deploy your token. #### Tell Your Users to Opt In to Rewards Token holders must opt in to receive rewards by setting their reward recipient address. This is typically set to their own address. :::code-group ```tsx twoslash [OptInToRewards.tsx] import React from 'react' import { Hooks } from 'tempo.ts/wagmi' import { useConnection } from 'wagmi' // @noErrors export function OptInToRewards() { const { address } = useConnection() const tokenAddress = '0x...' // Your token address const setRecipient = Hooks.reward.useSetRecipientSync() const handleOptIn = () => { // [!code hl] if (!address) return // [!code hl] setRecipient.mutate({ // [!code hl] recipient: address, // [!code hl] token: tokenAddress, // [!code hl] }) // [!code hl] } // [!code hl] return ( ) } ``` ```tsx twoslash [config.ts] filename="config.ts" // @noErrors import { createConfig, http } from 'wagmi' import { tempoTestnet } from 'viem/chains' import { KeyManager, webAuthn } from 'tempo.ts/wagmi' export const config = createConfig({ chains: [tempoTestnet], connectors: [webAuthn({ keyManager: KeyManager.localStorage(), })], transports: { [tempo.id]: http(), }, }) ``` ::: #### Make a Reward Distribution Anyone can make a reward distribution that allocates tokens to all opted-in holders proportionally based on their balance. :::code-group ```tsx twoslash [StartReward.tsx] import React from 'react' import { Hooks } from 'tempo.ts/wagmi' import { parseUnits } from 'viem' // @noErrors export function StartReward() { const tokenAddress = '0x...' // Your token address const { data: metadata } = Hooks.token.useGetMetadata({ token: tokenAddress, }) const start = Hooks.reward.useStartSync() const handleStart = () => { // [!code hl] if (!metadata) return // [!code hl] start.mutate({ // [!code hl] amount: parseUnits('50', metadata.decimals), // [!code hl] token: tokenAddress, // [!code hl] }) // [!code hl] } // [!code hl] return ( ) } ``` ```tsx twoslash [config.ts] filename="config.ts" // @noErrors import { createConfig, http } from 'wagmi' import { tempoTestnet } from 'viem/chains' import { KeyManager, webAuthn } from 'tempo.ts/wagmi' export const config = createConfig({ chains: [tempoTestnet], connectors: [webAuthn({ keyManager: KeyManager.localStorage(), })], transports: { [tempo.id]: http(), }, }) ``` ::: #### Your Users Can Claim Rewards Once a reward is distributed, opted-in holders can claim their share. :::code-group ```tsx twoslash [ClaimReward.tsx] import React from 'react' import { Hooks } from 'tempo.ts/wagmi' // @noErrors export function ClaimReward() { const tokenAddress = '0x...' // Your token address const claim = Hooks.reward.useClaimSync() const handleClaim = () => { // [!code hl] claim.mutate({ // [!code hl] token: tokenAddress, // [!code hl] }) // [!code hl] } // [!code hl] return ( ) } ``` ```tsx twoslash [config.ts] filename="config.ts" // @noErrors import { createConfig, http } from 'wagmi' import { tempoTestnet } from 'viem/chains' import { KeyManager, webAuthn } from 'tempo.ts/wagmi' export const config = createConfig({ chains: [tempoTestnet], connectors: [webAuthn({ keyManager: KeyManager.localStorage(), })], transports: { [tempo.id]: http(), }, }) ``` ::: :::: ### Recipes #### Watch for new reward distributions Use `useWatchRewardScheduled` to listen for new reward distributions on a token. This is useful for updating your UI when a reward is distributed. ```tsx twoslash import { Hooks } from 'tempo.ts/wagmi' // @noErrors function WatchRewards() { const tokenAddress = '0x...' // Your token address Hooks.reward.useWatchRewardScheduled({ // [!code hl] token: tokenAddress, // [!code hl] onRewardScheduled(args) { // [!code hl] console.log('New reward scheduled:', args) // [!code hl] // Update UI, refetch balances, show notification, etc. // [!code hl] }, // [!code hl] }) // [!code hl] return
Watching for reward distributions...
} ``` #### Watch for reward opt-ins Use `useWatchRewardRecipientSet` to listen for when users opt in to rewards by setting their recipient address. This is useful for tracking opt-in activity. ```tsx twoslash import { Hooks } from 'tempo.ts/wagmi' // @noErrors function WatchOptIns() { const tokenAddress = '0x...' // Your token address Hooks.reward.useWatchRewardRecipientSet({ // [!code hl] token: tokenAddress, // [!code hl] onRewardRecipientSet(args) { // [!code hl] console.log('User opted in:', args) // [!code hl] // Update UI, track analytics, etc. // [!code hl] }, // [!code hl] }) // [!code hl] return
Watching for reward opt-ins...
} ``` ### Learning Resources import LucideCoins from '~icons/lucide/coins' import LucidePlus from '~icons/lucide/plus' import LucideCircleDollarSign from '~icons/lucide/circle-dollar-sign' import LucideSettings from '~icons/lucide/settings' import * as Card from "../../../components/Card.tsx" ## Stablecoin Issuance Create and manage your own stablecoin on Tempo. Learn how to issue tokens, manage supply, and integrate with Tempo's payment infrastructure. import * as Demo from '../../../components/guides/Demo.tsx' import * as Step from '../../../components/guides/steps' import * as Card from "../../../components/Card.tsx" import LucideShield from '~icons/lucide/shield' import LucideFileText from '~icons/lucide/file-text' ## Manage Your Stablecoin Configure your stablecoin's permissions, supply limits, and compliance policies after deployment. This guide covers granting roles to manage token operations, setting supply caps, configuring transfer policies, and controlling token transfers through pause/unpause functionality. TIP-20 tokens use a role-based access control system that allows you to delegate different administrative functions to different addresses. For detailed information about the role system, see the [TIP-20 specification](/protocol/tip20/spec#role-based-access-control). ### Steps In this guide, we'll walk through how to assign and check the **`issuer`** role, but the process is identical for other roles like `pause`, `unpause`, `burnBlocked`, and `defaultAdmin`. ::::steps #### Setup Before you can manage roles on your stablecoin, you need to create one. Follow the [Create a Stablecoin](/guide/issuance/create-a-stablecoin) guide to deploy your token. Once you've created your token, you can proceed to grant roles to specific addresses. #### Grant Roles to an Address Assign roles to specific addresses to delegate token management capabilities. :::code-group ```tsx twoslash [GrantRoles.tsx] import React from 'react' import { Hooks } from 'tempo.ts/wagmi' import { useQueryClient } from '@tanstack/react-query' // @noErrors export function GrantRoles() { const queryClient = useQueryClient() const tokenAddress = '0x...' // Your token address const treasuryAddress = '0x...' // Address to grant the issuer role const grant = Hooks.token.useGrantRolesSync({}) // [!code hl] const handleGrantIssuer = async () => { // [!code hl] await grant.mutate({ // [!code hl] token: tokenAddress, // [!code hl] roles: ['issuer'], // [!code hl] to: treasuryAddress, // [!code hl] }) // [!code hl] } // [!code hl] return ( {/* [!code hl] */} ) } ``` ```tsx twoslash [config.ts] filename="config.ts" // @noErrors import { createConfig, http } from 'wagmi' import { tempoTestnet } from 'viem/chains' import { KeyManager, webAuthn } from 'tempo.ts/wagmi' export const config = createConfig({ chains: [tempoTestnet], connectors: [webAuthn({ keyManager: KeyManager.localStorage(), })], transports: { [tempo.id]: http(), }, }) ``` ::: #### Check if an Address Has a Role Use `hasRole` to verify whether an address has been granted a specific role. :::code-group ```tsx twoslash [GrantRoles.tsx] import React from 'react' import { Hooks } from 'tempo.ts/wagmi' import { useQueryClient } from '@tanstack/react-query' // @noErrors export function GrantRoles() { const queryClient = useQueryClient() const tokenAddress = '0x...' // Your token address const treasuryAddress = '0x...' // Address to grant the issuer role // Grant the issuer role const grant = Hooks.token.useGrantRolesSync({ mutation: { // [!code ++] onSettled() { // [!code ++] queryClient.refetchQueries({ queryKey: ['hasRole'] }) // [!code ++] }, // [!code ++] }, // [!code ++] }) const handleGrantIssuer = async () => { await grant.mutate({ token: tokenAddress, roles: ['issuer'], to: treasuryAddress, }) } const { data: hasIssuerRole } = Hooks.token.useHasRole({ // [!code ++] account: treasuryAddress, // [!code ++] token: tokenAddress, // [!code ++] role: 'issuer', // [!code ++] }) // [!code ++] return (
{hasIssuerRole !== undefined && ( // [!code ++]
{/* [!code ++] */} Treasury {hasIssuerRole ? 'has' : 'does not have'} the issuer role {/* [!code ++] */}
{/* [!code ++] */} )} {/* [!code ++] */}
) } ``` ```tsx twoslash [config.ts] filename="config.ts" // @noErrors import { createConfig, http } from 'wagmi' import { tempoTestnet } from 'viem/chains' import { KeyManager, webAuthn } from 'tempo.ts/wagmi' export const config = createConfig({ chains: [tempoTestnet], connectors: [webAuthn({ keyManager: KeyManager.localStorage(), })], transports: { [tempo.id]: http(), }, }) ``` ::: #### Revoke the Issuer Role Revoke roles from addresses when you need to remove their permissions. :::code-group ```tsx twoslash [RevokeRoles.tsx] import React from 'react' import { Hooks } from 'tempo.ts/wagmi' import { useQueryClient } from '@tanstack/react-query' // @noErrors export function RevokeRoles() { const queryClient = useQueryClient() const tokenAddress = '0x...' // Your token address const treasuryAddress = '0x...' // Address to grant/revoke the issuer role // Grant the issuer role const grant = Hooks.token.useGrantRolesSync({ mutation: { onSettled() { queryClient.refetchQueries({ queryKey: ['hasRole'] }) }, }, }) const handleGrantIssuer = async () => { await grant.mutate({ token: tokenAddress, roles: ['issuer'], to: treasuryAddress, }) } // Check if the treasury has the issuer role const { data: hasIssuerRole } = Hooks.token.useHasRole({ account: treasuryAddress, token: tokenAddress, role: 'issuer', }) // Revoke the issuer role // [!code ++] const revoke = Hooks.token.useRevokeRolesSync({ // [!code ++] mutation: { // [!code ++] onSettled() { // [!code ++] queryClient.refetchQueries({ queryKey: ['hasRole'] }) // [!code ++] }, // [!code ++] }, // [!code ++] }) // [!code ++] const handleRevokeIssuer = async () => { // [!code ++] await revoke.mutate({ // [!code ++] token: tokenAddress, // [!code ++] roles: ['issuer'], // [!code ++] from: treasuryAddress, // [!code ++] }) // [!code ++] } // [!code ++] return (
{/* [!code ++] */} {hasIssuerRole !== undefined && (
Treasury {hasIssuerRole ? 'has' : 'does not have'} the issuer role
)}
) } ``` ```tsx twoslash [config.ts] filename="config.ts" // @noErrors import { createConfig, http } from 'wagmi' import { tempoTestnet } from 'viem/chains' import { KeyManager, webAuthn } from 'tempo.ts/wagmi' export const config = createConfig({ chains: [tempoTestnet], connectors: [webAuthn({ keyManager: KeyManager.localStorage(), })], transports: { [tempo.id]: http(), }, }) ``` ::: :::: ### Recipes #### Set Supply Cap Limit the maximum total supply of your token. Setting supply caps requires the **`DEFAULT_ADMIN_ROLE`**. The new cap cannot be less than the current total supply. :::code-group ```tsx twoslash [SetSupplyCap.tsx] import React from 'react' import { Hooks } from 'tempo.ts/wagmi' import { parseUnits } from 'viem' // @noErrors export function SetSupplyCap() { const tokenAddress = '0x...' // Your token address const { data: metadata, refetch: refetchMetadata } = // [!code hl] Hooks.token.useGetMetadata({ token: tokenAddress }) // [!code hl] const setSupplyCap = Hooks.token.useSetSupplyCapSync({ // [!code hl] mutation: { onSettled: () => refetchMetadata() }, // [!code hl] }) // [!code hl] const handleSetSupplyCap = () => { // [!code hl] setSupplyCap.mutate({ // [!code hl] token: tokenAddress, // [!code hl] supplyCap: parseUnits('1000', metadata?.decimals || 6), // [!code hl] }) // [!code hl] } // [!code hl] return ( {/* [!code hl] */} ) } ``` ```tsx twoslash [config.ts] filename="config.ts" // @noErrors import { createConfig, http } from 'wagmi' import { tempoTestnet } from 'viem/chains' import { KeyManager, webAuthn } from 'tempo.ts/wagmi' export const config = createConfig({ chains: [tempoTestnet], connectors: [webAuthn({ keyManager: KeyManager.localStorage(), })], transports: { [tempo.id]: http(), }, }) ``` ::: #### Configure Transfer Policies Control who can send and receive your stablecoin for compliance and regulatory requirements. Setting transfer policies requires the **`DEFAULT_ADMIN_ROLE`**. Transfer policies can be: * **Always allow**: Anyone can send/receive (default) * **Always reject**: Nobody can send/receive * **Whitelist**: Only authorized addresses can send/receive * **Blacklist**: Blocked addresses cannot send/receive Learn more about configuring transfer policies in the [TIP-403 specification](/protocol/tip403/spec). :::code-group ```tsx twoslash [CreateTokenPolicy.tsx] import React from 'react' import { Hooks } from 'tempo.ts/wagmi' // @noErrors export function CreateTokenPolicy() { const tokenAddress = '0x...' // Your token address const createPolicy = Hooks.policy.useCreateSync({ // [!code hl] mutation: { // [!code hl] onSuccess(result) { // [!code hl] // Store policyId for the next step // [!code hl] console.log('Policy ID:', result.policyId) // [!code hl] }, // [!code hl] }, // [!code hl] }) // [!code hl] const handleCreatePolicy = async () => { // [!code hl] await createPolicy.mutateAsync({ // [!code hl] addresses: [ // [!code hl] '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', // [!code hl] ], // [!code hl] type: 'blacklist', // [!code hl] }) // [!code hl] } // [!code hl] return ( // [!code hl] ) } ``` ```tsx twoslash [LinkTokenPolicy.tsx] import React from 'react' import { Hooks } from 'tempo.ts/wagmi' // @noErrors export function LinkTokenPolicy() { const tokenAddress = '0x...' // Your token address const policyId = 1n // Policy ID from previous step const linkPolicy = Hooks.token.useChangeTransferPolicySync() // [!code hl] const handleLinkPolicy = async () => { // [!code hl] await linkPolicy.mutateAsync({ // [!code hl] policyId, // [!code hl] token: tokenAddress, // [!code hl] }) // [!code hl] } // [!code hl] return ( // [!code hl] ) } ``` ```tsx twoslash [config.ts] filename="config.ts" // @noErrors import { createConfig, http } from 'wagmi' import { tempoTestnet } from 'viem/chains' import { KeyManager, webAuthn } from 'tempo.ts/wagmi' export const config = createConfig({ chains: [tempoTestnet], connectors: [webAuthn({ keyManager: KeyManager.localStorage(), })], transports: { [tempo.id]: http(), }, }) ``` ::: #### Pause and Unpause Token Transfers Temporarily halt all token transfers during emergency situations or maintenance windows. Pausing transfers requires the **`PAUSE_ROLE`**. Unpausing transfers requires the **`UNPAUSE_ROLE`**. :::code-group ```tsx twoslash [PauseUnpauseTransfers.tsx] import React from 'react' import { Hooks } from 'tempo.ts/wagmi' // @noErrors export function PauseUnpauseTransfers() { const tokenAddress = '0x...' // Your token address const { data: metadata, refetch: refetchMetadata } = // [!code hl] Hooks.token.useGetMetadata({ token: tokenAddress }) // [!code hl] const pause = Hooks.token.usePauseSync({ // [!code hl] mutation: { onSettled: () => refetchMetadata() }, // [!code hl] }) // [!code hl] const unpause = Hooks.token.useUnpauseSync({ // [!code hl] mutation: { onSettled: () => refetchMetadata() }, // [!code hl] }) // [!code hl] const paused = metadata?.paused || false // [!code hl] const handleToggle = () => { // [!code hl] if (paused) { // [!code hl] unpause.mutate({ // [!code hl] token: tokenAddress, // [!code hl] }) // [!code hl] } else { // [!code hl] pause.mutate({ // [!code hl] token: tokenAddress, // [!code hl] }) // [!code hl] } // [!code hl] } // [!code hl] const isProcessing = pause.isPending || unpause.isPending // [!code hl] return ( // [!code hl] ) } ``` ```tsx twoslash [config.ts] filename="config.ts" // @noErrors import { createConfig, http } from 'wagmi' import { tempoTestnet } from 'viem/chains' import { KeyManager, webAuthn } from 'tempo.ts/wagmi' export const config = createConfig({ chains: [tempoTestnet], connectors: [webAuthn({ keyManager: KeyManager.localStorage(), })], transports: { [tempo.id]: http(), }, }) ``` ::: #### Using the Burn Blocked Role The Burn Blocked role allows your team to burn tokens from blocked or frozen addresses. This is useful for regulatory compliance when you need to remove tokens from addresses that violate terms of service or legal requirements. :::code-group ```tsx twoslash [BurnBlocked.tsx] import React from 'react' import { Hooks } from 'tempo.ts/wagmi' import { parseUnits } from 'viem' // @noErrors export function BurnBlocked() { const tokenAddress = '0x...' // Your token address const blockedAddress = '0x...' // The blocked address to burn tokens from const { data: metadata } = Hooks.token.useGetMetadata({ // [!code hl] token: tokenAddress, // [!code hl] }) // [!code hl] const burnBlocked = Hooks.token.useBurnBlockedSync() // [!code hl] const handleBurnBlocked = async () => { // [!code hl] if (!metadata) return // [!code hl] await burnBlocked.mutateAsync({ // [!code hl] token: tokenAddress, // [!code hl] from: blockedAddress, // [!code hl] amount: parseUnits('100', metadata.decimals), // [!code hl] }) // [!code hl] } // [!code hl] return ( // [!code hl] ) } ``` ```tsx twoslash [config.ts] filename="config.ts" // @noErrors import { createConfig, http } from 'wagmi' import { tempoTestnet } from 'viem/chains' import { KeyManager, webAuthn } from 'tempo.ts/wagmi' export const config = createConfig({ chains: [tempoTestnet], connectors: [webAuthn({ keyManager: KeyManager.localStorage(), })], transports: { [tempo.id]: http(), }, }) ``` ::: ### Best Practices #### Role Separation Use different addresses for different roles to enhance security. For example, assign the issuer role to your treasury address for minting, and the pause role to your security team for emergency controls. #### Event Monitoring Monitor onchain events for role changes, mints, burns, and administrative actions to maintain visibility into token operations and detect unauthorized activities. #### Emergency Procedures Ensure pause and unpause roles are assigned to trusted addresses and that your team has documented procedures for responding to security incidents requiring token transfers to be halted. ### Learning Resources import * as Demo from '../../../components/guides/Demo.tsx' import * as Step from '../../../components/guides/steps' import * as Card from '../../../components/Card.tsx' import LucideCoins from '~icons/lucide/coins' import LucideShield from '~icons/lucide/shield' ## Mint Stablecoins Create new tokens by minting them to a specified address. Minting increases the total supply of your stablecoin. ### Steps ::::steps #### Create a Stablecoin Before you can mint tokens, you need to create a stablecoin. Follow the [Create a Stablecoin](/guide/issuance/create-a-stablecoin) guide to deploy your token. Once you've created your token, you can proceed to grant the issuer role and mint tokens. #### Grant the Issuer Role Assign the issuer role to the address that will mint tokens. Minting requires the **`ISSUER_ROLE`**. :::code-group ```tsx twoslash [GrantIssuerRole.tsx] import React from 'react' import { Hooks } from 'tempo.ts/wagmi' import { useQueryClient } from '@tanstack/react-query' // @noErrors export function GrantIssuerRole() { const queryClient = useQueryClient() const tokenAddress = '0x...' // Your token address const issuerAddress = '0x...' // Address to grant the issuer role const grant = Hooks.token.useGrantRolesSync({ // [!code hl] mutation: { // [!code hl] onSettled() { // [!code hl] queryClient.refetchQueries({ queryKey: ['hasRole'] }) // [!code hl] }, // [!code hl] }, // [!code hl] }) // [!code hl] const handleGrantIssuer = async () => { // [!code hl] await grant.mutate({ // [!code hl] token: tokenAddress, // [!code hl] roles: ['issuer'], // [!code hl] to: issuerAddress, // [!code hl] feeToken: '0x20c0000000000000000000000000000000000001', // [!code hl] }) // [!code hl] } // [!code hl] return ( // [!code hl] ) } ``` ```tsx twoslash [config.ts] filename="config.ts" // @noErrors import { createConfig, http } from 'wagmi' import { tempoTestnet } from 'viem/chains' import { KeyManager, webAuthn } from 'tempo.ts/wagmi' export const config = createConfig({ chains: [tempoTestnet], connectors: [webAuthn({ keyManager: KeyManager.localStorage(), })], transports: { [tempo.id]: http(), }, }) ``` ::: #### Mint Tokens to a Recipient Now that the issuer role is granted, you can mint tokens to any address. :::code-group ```tsx twoslash [MintToken.tsx] import React from 'react' import { Hooks } from 'tempo.ts/wagmi' import { useConnection } from 'wagmi' import { parseUnits, pad, stringToHex } from 'viem' import { useQueryClient } from '@tanstack/react-query' // @noErrors export function MintToken() { const { address } = useConnection() const queryClient = useQueryClient() const tokenAddress = '0x...' // Your token address const [recipient, setRecipient] = React.useState('') const [memo, setMemo] = React.useState('') const { data: metadata } = Hooks.token.useGetMetadata({ // [!code hl] token: tokenAddress, // [!code hl] }) // [!code hl] const mint = Hooks.token.useMintSync({ // [!code hl] mutation: { // [!code hl] onSettled() { // [!code hl] queryClient.refetchQueries({ queryKey: ['getBalance'] }) // [!code hl] }, // [!code hl] }, // [!code hl] }) // [!code hl] const handleMint = () => { // [!code hl] if (!tokenAddress || !recipient || !metadata) return // [!code hl] mint.mutate({ // [!code hl] amount: parseUnits('100', metadata.decimals), // [!code hl] to: recipient as `0x${string}`, // [!code hl] token: tokenAddress, // [!code hl] memo: memo ? pad(stringToHex(memo), { size: 32 }) : undefined, // [!code hl] feeToken: '0x20c0000000000000000000000000000000000001', // [!code hl] }) // [!code hl] } // [!code hl] return ( <>
setRecipient(e.target.value)} placeholder="0x..." />
setMemo(e.target.value)} placeholder="INV-12345" />
{/* [!code hl] */} ) } ``` ```tsx twoslash [config.ts] filename="config.ts" // @noErrors import { createConfig, http } from 'wagmi' import { tempoTestnet } from 'viem/chains' import { KeyManager, webAuthn } from 'tempo.ts/wagmi' export const config = createConfig({ chains: [tempoTestnet], connectors: [webAuthn({ keyManager: KeyManager.localStorage(), })], transports: { [tempo.id]: http(), }, }) ``` ```solidity [Solidity] TIP20 token = TIP20(0x20c0000000000000000000000000000000000004); // Mint 1,000 tokens to the treasury (USD has 6 decimals) address treasuryAddress = 0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef; token.mint(treasuryAddress, 1_000_000_000); // Mint with a memo for tracking token.mintWithMemo( treasuryAddress, 1_000_000_000, keccak256("Q1_2024_TREASURY_ALLOCATION") ); ``` ```rust [Rust] use alloy::{ primitives::{U256, address, keccak256}, 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!("0x20c0000000000000000000000000000000000004"), // [!code focus] provider, // [!code focus] ); // [!code focus] let treasury_address = address!("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"); // [!code focus] // Mint 1,000 tokens to the treasury (USD has 6 decimals) token // [!code focus] .mint(treasury_address, U256::from(1_000_000_000)) // [!code focus] .send() // [!code focus] .await?; // [!code focus] // Mint with a memo for tracking token // [!code focus] .mintWithMemo( // [!code focus] treasury_address, // [!code focus] U256::from(1_000_000_000), // [!code focus] keccak256("Q1_2024_TREASURY_ALLOCATION"), // [!code focus] ) // [!code focus] .send() // [!code focus] .await?; // [!code focus] Ok(()) } ``` ::: :::: ### Recipes #### Burning Stablecoins To decrease supply, you can burn tokens from your own balance. Burning requires the **`ISSUER_ROLE`** and sufficient balance in the caller's account. :::code-group ```tsx twoslash [BurnToken.tsx] import React from 'react' import { Hooks } from 'tempo.ts/wagmi' import { useConnection } from 'wagmi' import { parseUnits, pad, stringToHex } from 'viem' import { useQueryClient } from '@tanstack/react-query' // @noErrors export function BurnToken() { const { address } = useConnection() const queryClient = useQueryClient() const tokenAddress = '0x...' // Your token address const [memo, setMemo] = React.useState('') const { data: metadata } = Hooks.token.useGetMetadata({ // [!code hl] token: tokenAddress, // [!code hl] }) // [!code hl] const burn = Hooks.token.useBurnSync({ // [!code hl] mutation: { // [!code hl] onSettled() { // [!code hl] queryClient.refetchQueries({ queryKey: ['getBalance'] }) // [!code hl] }, // [!code hl] }, // [!code hl] }) // [!code hl] const handleBurn = () => { // [!code hl] if (!tokenAddress || !address || !metadata) return // [!code hl] burn.mutate({ // [!code hl] amount: parseUnits('100', metadata.decimals), // [!code hl] token: tokenAddress, // [!code hl] memo: memo ? pad(stringToHex(memo), { size: 32 }) : undefined, // [!code hl] feeToken: '0x20c0000000000000000000000000000000000001', // [!code hl] }) // [!code hl] } // [!code hl] return ( <>
setMemo(e.target.value)} placeholder="INV-12345" />
{/* [!code hl] */} ) } ``` ```tsx twoslash [config.ts] filename="config.ts" // @noErrors import { createConfig, http } from 'wagmi' import { tempoTestnet } from 'viem/chains' import { KeyManager, webAuthn } from 'tempo.ts/wagmi' export const config = createConfig({ chains: [tempoTestnet], connectors: [webAuthn({ keyManager: KeyManager.localStorage(), })], transports: { [tempo.id]: http(), }, }) ``` ```solidity [Solidity] TIP20 token = TIP20(0x20c0000000000000000000000000000000000004); // Burn 100 tokens from your own balance token.burn(100_000_000); // Burn with a memo for tracking token.burnWithMemo(100_000_000, keccak256("REDEMPTION_Q1_2024")); ``` ```rust [Rust] use alloy::{ primitives::{U256, address, keccak256}, 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!("0x20c0000000000000000000000000000000000004"), // [!code focus] provider, // [!code focus] ); // [!code focus] // Burn 100 tokens from your own balance token // [!code focus] .burn(treasury_address, U256::from(100_000_000)) // [!code focus] .send() // [!code focus] .await?; // [!code focus] // Burn with a memo for tracking token // [!code focus] .burnWithMemo( // [!code focus] treasury_address, // [!code focus] U256::from(100_000_000), // [!code focus] keccak256("REDEMPTION_Q1_2024"), // [!code focus] ) // [!code focus] .send() // [!code focus] .await?; // [!code focus] Ok(()) } ``` ::: ### Best Practices #### Monitor Supply Caps If your token has a supply cap set, any `mint()` or `mintWithMemo()` call that would exceed the cap will revert with `SupplyCapExceeded()`. You must either: * Burn tokens to reduce total supply below the cap * Increase the supply cap (requires `DEFAULT_ADMIN_ROLE`) * Remove the cap entirely by setting it to `type(uint256).max` Use [`getMetadata`](https://viem.sh/tempo/actions/token.getMetadata) to check your token's total supply before minting. #### Role Separation Assign the issuer role to dedicated treasury or minting addresses separate from your admin address. This enhances security by limiting the privileges of any single address. ### Learning Resources import * as Card from '../../../components/Card.tsx' import * as Demo from '../../../components/guides/Demo.tsx' import * as Step from '../../../components/guides/steps' import LucideSettings from '~icons/lucide/settings' import LucideDroplet from '~icons/lucide/droplet' import LucideFileText from '~icons/lucide/file-text' ## Use Your Stablecoin for Fees Enable users to pay transaction fees using your stablecoin. Tempo supports flexible fee payment options, allowing users to pay fees in any stablecoin they hold. ### Demo ### Steps ::::steps #### Create your stablecoin First, create and mint your stablecoin by following the [Create a Stablecoin](/guide/issuance/create-a-stablecoin) guide. #### Add fee pool liquidity Before users can pay fees with your token, ensure there is sufficient liquidity in the fee pool. On the Andantino testnet, all validators expect fees in AlphaUSD, so you need to add AlphaUSD liquidity to your token's fee pool. Add liquidity to your token's fee pool: :::code-group ```tsx twoslash [TypeScript] // @noErrors import { Hooks } from 'tempo.ts/wagmi' import { parseUnits } from 'viem' import { useConnection } from 'wagmi' const { address } = useConnection() const yourToken = '0x...' // Your issued token address const validatorToken = '0x20c0000000000000000000000000000000000001' // AlphaUSD on testnet const mintFeeLiquidity = Hooks.amm.useMintSync() // [!code hl] // Add 100 AlphaUSD of liquidity to the fee pool // [!code hl] mintFeeLiquidity.mutate({ // [!code hl] feeToken: validatorToken, to: address, userTokenAddress: yourToken, validatorTokenAddress: validatorToken, validatorTokenAmount: parseUnits('100', 6), }) // [!code hl] ``` ```solidity [Solidity] IFeeAMM feeAmm = IFeeAMM(TIP_FEE_AMM_ADDRESS); address yourToken = 0x20c0000000000000000000000000000000000004; // Your issued token address validatorToken = 0x20c0000000000000000000000000000000000001; // AlphaUSD // Add 100 AlphaUSD of liquidity to the fee pool feeAmm.mintWithValidatorToken( yourToken, validatorToken, 100_000_000, // 100 tokens (6 decimals) address(this) ); ``` ::: You can also check your token's fee pool liquidity at any time: :::code-group ```tsx twoslash [TypeScript] // @noErrors import { Hooks } from 'tempo.ts/wagmi' const { data: pool } = Hooks.amm.usePool({ userToken: yourToken, validatorToken: '0x20c0000000000000000000000000000000000001', // AlphaUSD on testnet }) const hasLiquidity = pool && pool.reserveValidatorToken > 0n ``` ```solidity [Solidity] IFeeAMM feeAmm = IFeeAMM(TIP_FEE_AMM_ADDRESS); address yourToken = 0x20c0000000000000000000000000000000000004; // Your issued token address validatorToken = 0x20c0000000000000000000000000000000000001; // AlphaUSD // Get pool reserves (uint256 reserveValidator, uint256 reserveUser) = feeAmm.getReserves(yourToken, validatorToken); // Check if there's sufficient liquidity require(reserveValidator > 0, "No liquidity available for fee conversion"); ``` ::: If the pool has no liquidity (`reserveValidatorToken == 0`), you'll need to add liquidity to the fee pool before users can pay fees with your token. See the [Create a Stablecoin](/guide/issuance/create-a-stablecoin) guide for instructions on minting fee AMM liquidity. #### Send payment with your token as fee Your users can send payments using your issued stablecoin as the fee token: :::code-group ```tsx twoslash [PayWithIssuedToken.tsx] import React from 'react' import { Hooks } from 'tempo.ts/wagmi' import { useConnection } from 'wagmi' import { parseUnits, pad, stringToHex, isAddress } from 'viem' // @noErrors export function PayWithIssuedToken() { const { address } = useConnection() const [recipient, setRecipient] = React.useState('') const [memo, setMemo] = React.useState('') const feeToken = '0x...' // Your issued token address const paymentToken = '0x20c0000000000000000000000000000000000001' // AlphaUSD const { data: paymentBalance, refetch: paymentBalanceRefetch } = // [!code hl] Hooks.token.useGetBalance({ // [!code hl] account: address, // [!code hl] token: paymentToken, // [!code hl] }) // [!code hl] const { data: feeTokenBalance, refetch: feeTokenBalanceRefetch } = // [!code hl] Hooks.token.useGetBalance({ // [!code hl] account: address, // [!code hl] token: feeToken, // [!code hl] }) // [!code hl] const sendPayment = Hooks.token.useTransferSync({ // [!code hl] mutation: { // [!code hl] onSettled() { // [!code hl] paymentBalanceRefetch() // [!code hl] feeTokenBalanceRefetch() // [!code hl] }, // [!code hl] }, // [!code hl] }) // [!code hl] const isValidRecipient = recipient && isAddress(recipient) const handleTransfer = () => { // [!code hl] if (!isValidRecipient) return // [!code hl] sendPayment.mutate({ // [!code hl] amount: parseUnits('100', 6), // [!code hl] to: recipient as `0x${string}`, // [!code hl] token: paymentToken, // [!code hl] memo: memo ? pad(stringToHex(memo), { size: 32 }) : undefined, // [!code hl] feeToken, // Pay fees with your issued token // [!code hl] }) // [!code hl] } // [!code hl] return ( <>
setRecipient(e.target.value)} placeholder="0x..." />
setMemo(e.target.value)} placeholder="INV-12345" />
{/* [!code hl] */} ) } ``` ```tsx twoslash [config.ts] filename="config.ts" // @noErrors import { createConfig, http } from 'wagmi' import { tempoTestnet } from 'viem/chains' import { KeyManager, webAuthn } from 'tempo.ts/wagmi' export const config = createConfig({ chains: [tempoTestnet], connectors: [webAuthn({ keyManager: KeyManager.localStorage(), })], transports: { [tempo.id]: http(), }, }) ``` ```solidity [Solidity] ITIP20 token = ITIP20(0x20c0000000000000000000000000000000000001); // AlphaUSD address yourToken = 0x20c0000000000000000000000000000000000004; // Your issued stablecoin address recipient = 0xbeefcafe54750903ac1c8909323af7beb21ea2cb; // Send payment using your token for fees IFeeManager feeManager = IFeeManager(TIP_FEE_MANAGER_ADDRESS); feeManager.setTransactionFeeToken(yourToken); token.transfer(recipient, 100_000_000); ``` ```rust [Rust] use alloy::{ primitives::{U256, address}, providers::ProviderBuilder, }; use tempo_alloy::{ TempoNetwork, contracts::precompiles::{ITIPFeeAMM, TIP_FEE_MANAGER_ADDRESS}, }; #[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?; // Your issued token let your_token = address!("0x20c0000000000000000000000000000000000004"); // [!code focus] // AlphaUSD let validator_token = address!("0x20c0000000000000000000000000000000000001"); // [!code focus] let fee_amm = ITIPFeeAMM::new( // [!code focus] TIP_FEE_MANAGER_ADDRESS, // [!code focus] provider, // [!code focus] ); // [!code focus] let recipient = address!("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"); // [!code focus] // Add 100 AlphaUSD of liquidity to the fee pool fee_amm // [!code focus] .mintWithValidatorToken( // [!code focus] your_token, // [!code focus] validator_token, // [!code focus] U256::from(100_000_000), // [!code focus] recipient, // [!code focus] ) // [!code focus] .send() // [!code focus] .await?; // [!code focus] Ok(()) } ``` ::: Users can set your stablecoin as their default fee token at the account level, or specify it for individual transactions. Learn more about [how users pay fees in different stablecoins](/guide/payments/pay-fees-in-any-stablecoin). :::: ### How It Works When users pay transaction fees with your stablecoin, Tempo's fee system automatically handles the conversion if validators prefer a different token. The [Fee AMM](/protocol/fees/spec-fee-amm) ensures seamless fee payments across all supported stablecoins. Users can select your stablecoin as their fee token through: * **Account-level preference**: Set as default for all transactions * **Transaction-level preference**: Specify for individual transactions * **Automatic selection**: When directly interacting with your token contract Learn more about [how users pay fees in different stablecoins](/guide/payments/pay-fees-in-any-stablecoin) and the complete [fee token preference hierarchy](/protocol/fees/spec-fee#fee-token-preferences). ### Benefits * **User convenience**: Users can pay fees with the same token they're using * **Liquidity**: Encourages users to hold your stablecoin * **Flexibility**: Works seamlessly with Tempo's fee system ### Best Practices #### Monitor pool liquidity Regularly check your token's fee pool reserves to ensure users can consistently pay fees with your stablecoin. Low liquidity can prevent transactions from being processed. #### Maintain adequate reserves Keep sufficient validator token reserves in your fee pool to handle expected transaction volume. Consider your user base size and typical transaction frequency when determining reserve levels. #### Test before launch Before promoting fee payments with your token, thoroughly test the flow on testnet: 1. Add liquidity to the fee pool 2. Verify users can set your token as their fee preference 3. Execute test transactions with various gas costs 4. Monitor that fee conversions work correctly ### Next Steps