diff --git a/AGENTS.md b/.agents/skills/tempo-codegen/SKILL.md similarity index 93% rename from AGENTS.md rename to .agents/skills/tempo-codegen/SKILL.md index 5ea212063e..029e66ea9a 100644 --- a/AGENTS.md +++ b/.agents/skills/tempo-codegen/SKILL.md @@ -1,22 +1,21 @@ -# Agent Guidelines +--- +name: tempo-codegen +description: Skill for generating Tempo code. +--- -This document provides guidelines for AI code generation agents working on this codebase. - -## Code Generation - -### `viem/tempo` +# Tempo Code Generation When generating actions (in `src/tempo/actions/`), follow these guidelines. An example of a generated action set can be found in `src/tempo/actions/token.ts`. -#### Source of Truth +## Source of Truth - **All actions must be based on precompile contract specifications** in `test/tempo/docs/specs/`. - It could be likely that some interfaces may be inconsistent between the specs (`test/tempo/docs/specs`) and the precompiles (`test/tempo/crates/contracts/src/precompiles`). Always prefer the precompile interfaces over the specs. - If the specification is unclear or missing details, **prompt the developer** for guidance rather than making assumptions -#### Documentation Requirements +## Documentation Requirements All actions **must include comprehensive JSDoc** with: @@ -57,9 +56,9 @@ Example: */ ``` -#### Action Types +## Action Types -##### Read-Only Actions +### Read-Only Actions For view/pure functions that only read state: @@ -67,7 +66,7 @@ For view/pure functions that only read state: - Return type should use `ReadContractReturnType` - Parameters extend `ReadParameters` -##### Mutate-Based Actions +### Mutate-Based Actions For state-changing functions, **both variants must be implemented**: @@ -118,11 +117,11 @@ export async function myActionSync< } ``` -#### Namespace Properties +## Namespace Properties All actions **must include** the following components within their namespace: -##### 1. `Parameters` Type +### 1. `Parameters` Type ```typescript // Read actions @@ -135,7 +134,7 @@ export type Parameters< > = WriteParameters & Args ``` -##### 2. `Args` Type +### 2. `Args` Type Arguments must be documented with JSDoc. @@ -146,7 +145,7 @@ export type Args = { } ``` -##### 3. `ReturnValue` Type +### 3. `ReturnValue` Type ```typescript // Read actions @@ -156,7 +155,7 @@ export type ReturnValue = ReadContractReturnType=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + oxc-resolver@11.8.3: resolution: {integrity: sha512-wPY3eiw24QOiNqArh5FWRrKYr1Yuxya8bE8CV7yBfz7jodCrg0HqBu6cvHHSicxFug7D4TN6ox1hysA9arHTqw==} @@ -5892,8 +5900,8 @@ packages: resolution: {integrity: sha512-sF308EhTenb/pDRPakm+WgiN+VdM/T1RaHj1x+MvAuT8UiQP8JmOEbxVqtkbfR4LrvOg5n7ic01kRBDGXjYikA==} engines: {node: '>=10'} - prool@0.2.1: - resolution: {integrity: sha512-K9mVhCIf80ANLp3F4KO3n6z3zGTgUXTi4uxjGxHtW4VSRsg8cEUZYcDxcCOFhXzzR3Mv7GzlIKo9vekEL2fDxg==} + prool@0.2.2: + resolution: {integrity: sha512-vtbgLBHKvPIVuVPR5t13fEKQ/xTqPNvIqLTcyJAyEBEBDzfk0GUkqdYvf+qhHsLBvRjJHKNoF7GPf/NkJBj45A==} engines: {node: '>=22'} peerDependencies: '@pimlico/alto': '*' @@ -6848,8 +6856,8 @@ packages: vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} - viem@2.43.4: - resolution: {integrity: sha512-PSoML7uG5es/SA1v2988grfcHdMDIogqC0LftdX74q8ZUHj3E7uiAXypNQiUxRZBuyoD5LO1nGEgTU9AGRvuHA==} + viem@2.43.5: + resolution: {integrity: sha512-QuJpuEMEPM3EreN+vX4mVY68Sci0+zDxozYfbh/WfV+SSy/Gthm74PH8XmitXdty1xY54uTCJ+/Gbbd1IiMPSA==} peerDependencies: typescript: '>=5.0.4' peerDependenciesMeta: @@ -8583,7 +8591,7 @@ snapshots: pino-pretty: 10.3.1 prom-client: 14.2.0 type-fest: 4.39.0 - viem: 2.43.4(typescript@5.9.3)(zod@3.23.8) + viem: 2.43.5(typescript@5.9.3)(zod@3.23.8) yargs: 17.7.2 zod: 3.23.8 zod-validation-error: 1.5.0(zod@3.23.8) @@ -12929,7 +12937,22 @@ snapshots: transitivePeerDependencies: - zod - ox@0.11.1(typescript@5.9.3)(zod@3.25.76): + ox@0.11.3(typescript@5.6.2)(zod@3.25.76): + dependencies: + '@adraffy/ens-normalize': 1.11.0 + '@noble/ciphers': 1.3.0 + '@noble/curves': 1.9.1 + '@noble/hashes': 1.8.0 + '@scure/bip32': 1.7.0 + '@scure/bip39': 1.6.0 + abitype: 1.2.3(typescript@5.6.2)(zod@3.25.76) + eventemitter3: 5.0.1 + optionalDependencies: + typescript: 5.6.2 + transitivePeerDependencies: + - zod + + ox@0.11.3(typescript@5.9.3)(zod@3.25.76): dependencies: '@adraffy/ens-normalize': 1.11.0 '@noble/ciphers': 1.3.0 @@ -13067,9 +13090,9 @@ snapshots: pend@1.2.0: {} - permissionless@0.2.57(ox@0.11.1(typescript@5.9.3)(zod@3.25.76)): + permissionless@0.2.57(ox@0.11.3(typescript@5.9.3)(zod@3.25.76)): optionalDependencies: - ox: 0.11.1(typescript@5.9.3)(zod@3.25.76) + ox: 0.11.3(typescript@5.9.3)(zod@3.25.76) picocolors@1.1.1: {} @@ -13209,7 +13232,7 @@ snapshots: dependencies: tdigest: 0.1.2 - prool@0.2.1(@pimlico/alto@0.0.18(typescript@5.9.3))(testcontainers@11.10.0): + prool@0.2.2(@pimlico/alto@0.0.18(typescript@5.9.3))(testcontainers@11.10.0): dependencies: change-case: 5.4.4 eventemitter3: 5.0.1 @@ -14370,7 +14393,7 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.2 - viem@2.43.4(typescript@5.6.2)(zod@3.25.76): + viem@2.43.5(typescript@5.6.2)(zod@3.25.76): dependencies: '@noble/curves': 1.9.1 '@noble/hashes': 1.8.0 @@ -14387,7 +14410,7 @@ snapshots: - utf-8-validate - zod - viem@2.43.4(typescript@5.9.3)(zod@3.23.8): + viem@2.43.5(typescript@5.9.3)(zod@3.23.8): dependencies: '@noble/curves': 1.9.1 '@noble/hashes': 1.8.0 @@ -14412,7 +14435,7 @@ snapshots: '@scure/bip39': 1.6.0 abitype: 1.2.3(typescript@5.6.2)(zod@3.25.76) isows: 1.0.7(ws@8.18.3) - ox: 0.11.1(typescript@5.6.2)(zod@3.25.76) + ox: 0.11.3(typescript@5.6.2)(zod@3.25.76) ws: 8.18.3 optionalDependencies: typescript: 5.6.2 diff --git a/site/pages/tempo/accounts/index.mdx b/site/pages/tempo/accounts/index.mdx index b99409e027..59bd8508b4 100644 --- a/site/pages/tempo/accounts/index.mdx +++ b/site/pages/tempo/accounts/index.mdx @@ -9,14 +9,14 @@ These accounts are fully backwards compatible with Viem APIs, meaning you can us ```ts twoslash [example.ts] import { createWalletClient, http } from 'viem' import { Account, WebAuthnP256 } from 'viem/tempo' -import { tempoTestnet } from 'viem/chains' +import { tempoModerato } from 'viem/chains' // Create a passkey account const credential = await WebAuthnP256.createCredential({ label: 'Example' }) const account = Account.fromWebAuthnP256(credential) const client = createWalletClient({ - chain: tempoTestnet, + chain: tempoModerato, transport: http(), }) diff --git a/site/pages/tempo/actions/amm.watchFeeSwap.mdx b/site/pages/tempo/actions/amm.watchFeeSwap.mdx deleted file mode 100644 index 7227fbfd5a..0000000000 --- a/site/pages/tempo/actions/amm.watchFeeSwap.mdx +++ /dev/null @@ -1,98 +0,0 @@ -# `amm.watchFeeSwap` - -Watches for fee swap events on the Fee AMM. - -## Usage - -:::code-group - -```ts twoslash [example.ts] -import { client } from './viem.config' - -const unwatch = client.amm.watchFeeSwap({ - 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 [viem.config.ts] filename="viem.config.ts" -// [!include ~/snippets/tempo/viem.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. - - diff --git a/site/pages/tempo/actions/dex.cancelStale.mdx b/site/pages/tempo/actions/dex.cancelStale.mdx new file mode 100644 index 0000000000..fa0c2ec610 --- /dev/null +++ b/site/pages/tempo/actions/dex.cancelStale.mdx @@ -0,0 +1,68 @@ +import WriteParameters from '../../../snippets/tempo/write-parameters.mdx' + +# `dex.cancelStale` + +Cancels a stale order from the Stablecoin DEX orderbook. + +A stale order is one where the maker has been blacklisted by a TIP-403 policy. Anyone can cancel stale orders. + +## Usage + +:::code-group + +```ts twoslash [example.ts] +import { client } from './viem.config' + +const { orderId, receipt } = await client.dex.cancelStaleSync({ + orderId: 123n, +}) + +console.log('Cancelled stale order ID:', orderId) +// @log: Cancelled stale order ID: 123n +``` + +```ts twoslash [viem.config.ts] filename="viem.config.ts" +// [!include ~/snippets/tempo/viem.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.cancelStale` action and wait for inclusion manually: + +```ts twoslash +import { Actions } from 'viem/tempo' +import { client } from './viem.config' + +const hash = await client.dex.cancelStale({ + orderId: 123n, +}) +const receipt = await client.waitForTransactionReceipt({ hash }) + +const { args: { orderId } } + = Actions.dex.cancelStale.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 stale order to cancel. + + diff --git a/site/pages/tempo/actions/index.mdx b/site/pages/tempo/actions/index.mdx index c03eede770..445f88848a 100644 --- a/site/pages/tempo/actions/index.mdx +++ b/site/pages/tempo/actions/index.mdx @@ -9,7 +9,6 @@ | [`amm.mint`](/tempo/actions/amm.mint) | Mints liquidity tokens by providing a token pair | | [`amm.rebalanceSwap`](/tempo/actions/amm.rebalanceSwap) | Performs a rebalance swap between user and validator tokens | | [`amm.watchBurn`](/tempo/actions/amm.watchBurn) | Watches for liquidity burn events | -| [`amm.watchFeeSwap`](/tempo/actions/amm.watchFeeSwap) | Watches for fee swap events | | [`amm.watchMint`](/tempo/actions/amm.watchMint) | Watches for liquidity mint events | | [`amm.watchRebalanceSwap`](/tempo/actions/amm.watchRebalanceSwap) | Watches for rebalance swap events | | **Faucet Actions** | | @@ -20,8 +19,6 @@ | [`fee.watchSetUserToken`](/tempo/actions/fee.watchSetUserToken) | Watches for user token set events | | **Nonce Actions** | | | [`nonce.getNonce`](/tempo/actions/nonce.getNonce) | Gets the nonce for an account and nonce key | -| [`nonce.getNonceKeyCount`](/tempo/actions/nonce.getNonceKeyCount) | Gets the number of active nonce keys for an account | -| [`nonce.watchActiveKeyCountChanged`](/tempo/actions/nonce.watchActiveKeyCountChanged) | Watches for active key count changed events | | [`nonce.watchNonceIncremented`](/tempo/actions/nonce.watchNonceIncremented) | Watches for nonce incremented events | | **Policy Actions** | | | [`policy.create`](/tempo/actions/policy.create) | Creates a new transfer policy for token access control | @@ -36,15 +33,17 @@ | [`policy.watchWhitelistUpdated`](/tempo/actions/policy.watchWhitelistUpdated) | Watches for whitelist update events | | **Reward Actions** | | | [`reward.claim`](/tempo/actions/reward.claim) | Claims accumulated rewards for the caller | -| [`reward.getTotalPerSecond`](/tempo/actions/reward.getTotalPerSecond) | Gets the total reward per second rate for all active streams | +| [`reward.distribute`](/tempo/actions/reward.distribute) | Distributes rewards to opted-in token holders | +| [`reward.getGlobalRewardPerToken`](/tempo/actions/reward.getGlobalRewardPerToken) | Gets the global reward per token value | +| [`reward.getPendingRewards`](/tempo/actions/reward.getPendingRewards) | Calculates the pending claimable rewards for an account | | [`reward.getUserRewardInfo`](/tempo/actions/reward.getUserRewardInfo) | Gets reward information for a specific account | | [`reward.setRecipient`](/tempo/actions/reward.setRecipient) | Sets or changes the reward recipient for a token holder | -| [`reward.start`](/tempo/actions/reward.start) | Starts a new reward stream that distributes tokens to opted-in holders | +| [`reward.watchRewardDistributed`](/tempo/actions/reward.watchRewardDistributed) | Watches for reward distributed events | | [`reward.watchRewardRecipientSet`](/tempo/actions/reward.watchRewardRecipientSet) | Watches for reward recipient set events | -| [`reward.watchRewardScheduled`](/tempo/actions/reward.watchRewardScheduled) | Watches for reward scheduled events | | **Stablecoin DEX Actions** | | | [`dex.buy`](/tempo/actions/dex.buy) | Buys a specific amount of tokens from the Stablecoin DEX orderbook | | [`dex.cancel`](/tempo/actions/dex.cancel) | Cancels an order from the orderbook | +| [`dex.cancelStale`](/tempo/actions/dex.cancelStale) | Cancels a stale order from a blacklisted maker | | [`dex.createPair`](/tempo/actions/dex.createPair) | Creates a new trading pair on the DEX | | [`dex.getBalance`](/tempo/actions/dex.getBalance) | Gets a user's token balance on the Stablecoin DEX | | [`dex.getBuyQuote`](/tempo/actions/dex.getBuyQuote) | Gets the quote for buying a specific amount of tokens | diff --git a/site/pages/tempo/actions/nonce.getNonceKeyCount.mdx b/site/pages/tempo/actions/nonce.getNonceKeyCount.mdx deleted file mode 100644 index 4e54d5d495..0000000000 --- a/site/pages/tempo/actions/nonce.getNonceKeyCount.mdx +++ /dev/null @@ -1,45 +0,0 @@ -import ReadAccountParameters from '../../../snippets/tempo/read-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 { client } from './viem.config' - -const count = await client.nonce.getNonceKeyCount({ - account: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbb', -}) - -console.log('Active nonce keys:', count) -// @log: Active nonce keys: 3n -``` - -```ts twoslash [viem.config.ts] filename="viem.config.ts" -// [!include ~/snippets/tempo/viem.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. - - - diff --git a/site/pages/tempo/actions/nonce.watchActiveKeyCountChanged.mdx b/site/pages/tempo/actions/nonce.watchActiveKeyCountChanged.mdx deleted file mode 100644 index c7a636ba19..0000000000 --- a/site/pages/tempo/actions/nonce.watchActiveKeyCountChanged.mdx +++ /dev/null @@ -1,96 +0,0 @@ -# `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 { client } from './viem.config' - -const unwatch = client.nonce.watchActiveKeyCountChanged({ - onActiveKeyCountChanged: (args, log) => { - console.log('Account:', args.account) - console.log('New active key count:', args.newCount) - }, -}) - -// Later, stop watching -unwatch() -``` - -```ts twoslash [viem.config.ts] filename="viem.config.ts" -// [!include ~/snippets/tempo/viem.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. - diff --git a/site/pages/tempo/actions/reward.start.mdx b/site/pages/tempo/actions/reward.distribute.mdx similarity index 57% rename from site/pages/tempo/actions/reward.start.mdx rename to site/pages/tempo/actions/reward.distribute.mdx index 1d6d7554c5..e28014f088 100644 --- a/site/pages/tempo/actions/reward.start.mdx +++ b/site/pages/tempo/actions/reward.distribute.mdx @@ -1,12 +1,12 @@ import WriteParameters from '../../../snippets/tempo/write-parameters.mdx' -# `reward.start` +# `reward.distribute` -Starts a new reward stream that distributes tokens to opted-in holders. +Distributes rewards to opted-in token holders. ## Usage -Use the `reward.start` action on the Viem `client` to start a reward distribution stream. +Use the `reward.distribute` action on the Viem `client` to distribute rewards to token holders. :::code-group @@ -14,18 +14,16 @@ Use the `reward.start` action on the Viem `client` to start a reward distributio import { parseEther } from 'viem' import { client, token } from './viem.config' -const { amount, durationSeconds, funder, id, receipt } = - await client.reward.startSync({ +const { amount, funder, receipt } = + await client.reward.distributeSync({ amount: parseEther('1000'), token, }) -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 +console.log('Funder:', funder) +// @log: Funder: 0x... ``` ```ts twoslash [viem.config.ts] filename="viem.config.ts" @@ -38,35 +36,31 @@ console.log('Duration:', durationSeconds, 'seconds') 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: +If you are optimizing for performance, you should use the non-sync `reward.distribute` action and wait for inclusion manually: ```ts twoslash import { Actions } from 'viem/tempo' import { parseEther } from 'viem' import { client, token } from './viem.config' -const hash = await client.reward.start({ +const hash = await client.reward.distribute({ amount: parseEther('1000'), token, }) const receipt = await client.waitForTransactionReceipt({ hash }) -const { args: { id, funder, amount, durationSeconds } } - = Actions.reward.start.extractEvent(receipt.logs) +const { args: { amount, funder } } + = Actions.reward.distribute.extractEvent(receipt.logs) ``` ## Return Type ```ts type ReturnType = { - /** Total amount allocated to the stream */ + /** Amount distributed */ amount: bigint - /** Duration of the stream in seconds (0 for immediate distributions) */ - durationSeconds: number - /** Address that funded the stream */ + /** Address that funded the distribution */ funder: Address - /** Unique stream ID (0 for immediate distributions; all that is currently supported) */ - id: bigint /** Transaction receipt */ receipt: TransactionReceipt } @@ -87,4 +81,3 @@ The amount of tokens to distribute. Must be greater than 0. Address of the TIP-20 token. - diff --git a/site/pages/tempo/actions/reward.getGlobalRewardPerToken.mdx b/site/pages/tempo/actions/reward.getGlobalRewardPerToken.mdx new file mode 100644 index 0000000000..d99b93e750 --- /dev/null +++ b/site/pages/tempo/actions/reward.getGlobalRewardPerToken.mdx @@ -0,0 +1,44 @@ +import ReadParameters from '../../../snippets/tempo/read-parameters.mdx' + +# `reward.getGlobalRewardPerToken` + +Gets the global reward per token value. + +## Usage + +:::code-group + +```ts twoslash [example.ts] +import { client, token } from './viem.config' + +const rewardPerToken = await client.reward.getGlobalRewardPerToken({ + token, +}) + +console.log('Global reward per token:', rewardPerToken) +// @log: Global reward per token: 385802469135802469135n +``` + +```ts twoslash [viem.config.ts] filename="viem.config.ts" +// [!include ~/snippets/tempo/viem.config.ts:setup] +``` + +::: + +## Return Type + +```ts +type ReturnValue = bigint +``` + +Returns the current global reward per token value scaled by `ACC_PRECISION` (1e18). This value increases each time rewards are distributed. + +## Parameters + +### token + +- **Type:** `Address` + +Address of the TIP-20 token. + + diff --git a/site/pages/tempo/actions/reward.getPendingRewards.mdx b/site/pages/tempo/actions/reward.getPendingRewards.mdx new file mode 100644 index 0000000000..73a50ce982 --- /dev/null +++ b/site/pages/tempo/actions/reward.getPendingRewards.mdx @@ -0,0 +1,51 @@ +import ReadParameters from '../../../snippets/tempo/read-parameters.mdx' + +# `reward.getPendingRewards` + +Calculates the pending claimable rewards for an account without modifying state. + +## Usage + +:::code-group + +```ts twoslash [example.ts] +import { client, token } from './viem.config' + +const pending = await client.reward.getPendingRewards({ + token, + account: '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', +}) + +console.log('Pending rewards:', pending) +// @log: Pending rewards: 1000000000000000000n +``` + +```ts twoslash [viem.config.ts] filename="viem.config.ts" +// [!include ~/snippets/tempo/viem.config.ts:setup] +``` + +::: + +## Return Type + +```ts +type ReturnValue = bigint +``` + +Returns the total pending claimable reward amount, including stored balance and newly accrued rewards. + +## Parameters + +### account + +- **Type:** `Address` + +The account address to query pending rewards for. + +### token + +- **Type:** `Address` + +Address of the TIP-20 token. + + diff --git a/site/pages/tempo/actions/reward.getTotalPerSecond.mdx b/site/pages/tempo/actions/reward.getTotalPerSecond.mdx deleted file mode 100644 index f7f9459844..0000000000 --- a/site/pages/tempo/actions/reward.getTotalPerSecond.mdx +++ /dev/null @@ -1,49 +0,0 @@ -import ReadParameters from '../../../snippets/tempo/read-parameters.mdx' - -# `reward.getTotalPerSecond` - -Gets the total reward per second rate for all active streams. - -## Usage - -:::code-group - -```ts twoslash [example.ts] -import { client, token } from './viem.config' - -const rate = await client.reward.getTotalPerSecond({ - token, -}) - -console.log('Total rate per second:', rate) -// @log: Total rate per second: 385802469135802469135n -``` - -```ts twoslash [viem.config.ts] filename="viem.config.ts" -// [!include ~/snippets/tempo/viem.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. - - - diff --git a/site/pages/tempo/actions/reward.watchRewardDistributed.mdx b/site/pages/tempo/actions/reward.watchRewardDistributed.mdx new file mode 100644 index 0000000000..24fe79da34 --- /dev/null +++ b/site/pages/tempo/actions/reward.watchRewardDistributed.mdx @@ -0,0 +1,74 @@ +# `reward.watchRewardDistributed` + +Watches for reward distributed events when rewards are distributed to token holders. + +## Usage + +:::code-group + +```ts twoslash [example.ts] +import { client, token } from './viem.config' + +const unwatch = client.reward.watchRewardDistributed({ + onRewardDistributed: (args, log) => { + console.log('Funder:', args.funder) + console.log('Amount:', args.amount) + }, + token, +}) + +// Later, stop watching +unwatch() +``` + +```ts twoslash [viem.config.ts] filename="viem.config.ts" +// [!include ~/snippets/tempo/viem.config.ts:setup] +``` + +::: + +## Return Type + +```ts +type ReturnType = () => void +``` + +Returns a function to unsubscribe from the event. + +## Parameters + +### onRewardDistributed + +- **Type:** `function` + +```ts +declare function onRewardDistributed(args: Args, log: Log): void + +type Args = { + /** Amount distributed */ + amount: bigint + /** Address that funded the distribution */ + funder: Address +} +``` + +Callback to invoke when rewards are distributed. + +### 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[] +} +``` + +Optional filters to narrow down events by funder address. diff --git a/site/pages/tempo/actions/reward.watchRewardScheduled.mdx b/site/pages/tempo/actions/reward.watchRewardScheduled.mdx deleted file mode 100644 index 3e94b49251..0000000000 --- a/site/pages/tempo/actions/reward.watchRewardScheduled.mdx +++ /dev/null @@ -1,82 +0,0 @@ -# `reward.watchRewardScheduled` - -Watches for reward scheduled events when new reward streams are started. - -## Usage - -:::code-group - -```ts twoslash [example.ts] -import { client, token } from './viem.config' - -const unwatch = client.reward.watchRewardScheduled({ - 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, -}) - -// Later, stop watching -unwatch() -``` - -```ts twoslash [viem.config.ts] filename="viem.config.ts" -// [!include ~/snippets/tempo/viem.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. diff --git a/site/pages/tempo/actions/token.create.mdx b/site/pages/tempo/actions/token.create.mdx index 6a94cc7c02..2bfc7c7732 100644 --- a/site/pages/tempo/actions/token.create.mdx +++ b/site/pages/tempo/actions/token.create.mdx @@ -105,6 +105,13 @@ Name of the token. Quote token address or ID. +### salt (optional) + +- **Type:** `Hex` +- **Default:** `Hex.random(32)` + +Unique salt for deterministic token address generation. + ### symbol - **Type:** `string` diff --git a/site/pages/tempo/chains.mdx b/site/pages/tempo/chains.mdx index afd9148e23..25d519ddd3 100644 --- a/site/pages/tempo/chains.mdx +++ b/site/pages/tempo/chains.mdx @@ -6,7 +6,7 @@ The following Tempo chains are available: import { tempoDevnet, // [!code hl] tempoLocalnet, // [!code hl] - tempoTestnet, // [!code hl] + tempoModerato, // [!code hl] } from 'viem/chains' ``` @@ -17,9 +17,9 @@ It is possible to set a default fee token for a Tempo chain by adding a `feeToke Once set, all transactions will use this token as the default fee token, unless an override is provided at the transaction level. ```ts -import { tempoTestnet } from 'viem/chains' +import { tempoModerato } from 'viem/chains' -const chain = tempoTestnet.extend({ +const chain = tempoModerato.extend({ feeToken: '0x20c0000000000000000000000000000000000001', }) ``` diff --git a/site/pages/tempo/index.mdx b/site/pages/tempo/index.mdx index 24822f3fe5..22c50c4f00 100644 --- a/site/pages/tempo/index.mdx +++ b/site/pages/tempo/index.mdx @@ -43,7 +43,7 @@ It is also possible to set up a default fee token by adding a `feeToken` propert ```ts export const client = createClient({ - chain: tempoTestnet.extend({ // [!code hl] + chain: tempoModerato.extend({ // [!code hl] feeToken: '0x20c0000000000000000000000000000000000001', // [!code hl] }), // [!code hl] // ... diff --git a/site/pages/tempo/transports/withFeePayer.mdx b/site/pages/tempo/transports/withFeePayer.mdx index 6acaf4cf46..7e0e146eb3 100644 --- a/site/pages/tempo/transports/withFeePayer.mdx +++ b/site/pages/tempo/transports/withFeePayer.mdx @@ -12,12 +12,12 @@ Creates a transport that routes transactions to a fee payer service when a `feeP ```ts twoslash [example.ts] import { createWalletClient, http } from 'viem' import { privateKeyToAccount } from 'viem/accounts' -import { tempoTestnet } from 'viem/chains' +import { tempoModerato } from 'viem/chains' import { withFeePayer } from 'viem/tempo' const client = createWalletClient({ account: privateKeyToAccount('0x...'), - chain: tempoTestnet, + chain: tempoModerato, transport: withFeePayer( http(), // ← Default Transport http('https://sponsor.example.com'), // ← Fee Payer Transport // [!code hl] @@ -54,12 +54,12 @@ See `server.ts` for the server-side implementation. It uses [`Handler.feePayer` ```ts twoslash [client.ts] import { createWalletClient, http } from 'viem' import { privateKeyToAccount } from 'viem/accounts' -import { tempoTestnet } from 'viem/chains' +import { tempoModerato } from 'viem/chains' import { withFeePayer } from 'viem/tempo' const client = createWalletClient({ account: privateKeyToAccount('0x...'), - chain: tempoTestnet, + chain: tempoModerato, transport: withFeePayer( http(), http('http://localhost:3000'), @@ -77,11 +77,11 @@ const hash = await client.sendTransactionSync({ import { createServer } from 'node:http' import { createClient, http } from 'viem' import { privateKeyToAccount } from 'viem/accounts' -import { tempoTestnet } from 'viem/chains' +import { tempoModerato } from 'viem/chains' import { Handler } from 'tempo.ts/server' const client = createClient({ - chain: tempoTestnet.extend({ + chain: tempoModerato.extend({ // Note: the fee payer can specify their own fee token. feeToken: '0x20c0000000000000000000000000000000000001' }), diff --git a/site/sidebar.ts b/site/sidebar.ts index 7621f038b9..1894393ce2 100644 --- a/site/sidebar.ts +++ b/site/sidebar.ts @@ -1874,10 +1874,6 @@ export const sidebar = { text: 'watchBurn', link: '/tempo/actions/amm.watchBurn', }, - { - text: 'watchFeeSwap', - link: '/tempo/actions/amm.watchFeeSwap', - }, { text: 'watchMint', link: '/tempo/actions/amm.watchMint', @@ -1914,14 +1910,6 @@ export const sidebar = { text: 'getNonce', link: '/tempo/actions/nonce.getNonce', }, - { - text: 'getNonceKeyCount', - link: '/tempo/actions/nonce.getNonceKeyCount', - }, - { - text: 'watchActiveKeyCountChanged', - link: '/tempo/actions/nonce.watchActiveKeyCountChanged', - }, { text: 'watchNonceIncremented', link: '/tempo/actions/nonce.watchNonceIncremented', @@ -1993,8 +1981,16 @@ export const sidebar = { link: '/tempo/actions/reward.claim', }, { - text: 'getTotalPerSecond', - link: '/tempo/actions/reward.getTotalPerSecond', + text: 'distribute', + link: '/tempo/actions/reward.distribute', + }, + { + text: 'getGlobalRewardPerToken', + link: '/tempo/actions/reward.getGlobalRewardPerToken', + }, + { + text: 'getPendingRewards', + link: '/tempo/actions/reward.getPendingRewards', }, { text: 'getUserRewardInfo', @@ -2005,21 +2001,17 @@ export const sidebar = { link: '/tempo/actions/reward.setRecipient', }, { - text: 'start', - link: '/tempo/actions/reward.start', + text: 'watchRewardDistributed', + link: '/tempo/actions/reward.watchRewardDistributed', }, { text: 'watchRewardRecipientSet', link: '/tempo/actions/reward.watchRewardRecipientSet', }, - { - text: 'watchRewardScheduled', - link: '/tempo/actions/reward.watchRewardScheduled', - }, ], }, { - text: 'Stablecoin Exchange', + text: 'Stablecoin DEX', collapsed: true, items: [ { @@ -2030,6 +2022,10 @@ export const sidebar = { text: 'cancel', link: '/tempo/actions/dex.cancel', }, + { + text: 'cancelStale', + link: '/tempo/actions/dex.cancelStale', + }, { text: 'createPair', link: '/tempo/actions/dex.createPair', diff --git a/site/snippets/tempo/viem.config.ts b/site/snippets/tempo/viem.config.ts index 80713c3666..8b92be10ae 100644 --- a/site/snippets/tempo/viem.config.ts +++ b/site/snippets/tempo/viem.config.ts @@ -1,12 +1,12 @@ // [!region setup] import { createClient, http, publicActions, walletActions } from 'viem' import { privateKeyToAccount } from 'viem/accounts' -import { tempoTestnet } from 'viem/chains' +import { tempoModerato } from 'viem/chains' import { tempoActions } from 'viem/tempo' export const client = createClient({ account: privateKeyToAccount('0x...'), - chain: tempoTestnet, + chain: tempoModerato, transport: http(), }) .extend(publicActions) diff --git a/src/chains/definitions/tempoTestnet.ts b/src/chains/definitions/tempoAndantino.ts similarity index 75% rename from src/chains/definitions/tempoTestnet.ts rename to src/chains/definitions/tempoAndantino.ts index 1b0e13d218..88d8150636 100644 --- a/src/chains/definitions/tempoTestnet.ts +++ b/src/chains/definitions/tempoAndantino.ts @@ -1,16 +1,16 @@ import { chainConfig } from '../../tempo/chainConfig.js' import { defineChain } from '../../utils/chain/defineChain.js' -export const tempoTestnet = /*#__PURE__*/ defineChain({ +export const tempoAndantino = /*#__PURE__*/ defineChain({ ...chainConfig, id: 42429, blockExplorers: { default: { name: 'Tempo Explorer', - url: 'https://explore.tempo.xyz', + url: 'https://explore.testnet.tempo.xyz', }, }, - name: 'Tempo Testnet', + name: 'Tempo Testnet (Andantino)', nativeCurrency: { name: 'USD', symbol: 'USD', diff --git a/src/chains/definitions/tempoDevnet.ts b/src/chains/definitions/tempoDevnet.ts index b41f21a2aa..260a5f04f5 100644 --- a/src/chains/definitions/tempoDevnet.ts +++ b/src/chains/definitions/tempoDevnet.ts @@ -5,6 +5,12 @@ export const tempoDevnet = /*#__PURE__*/ defineChain({ ...chainConfig, id: 31318, name: 'Tempo Devnet', + blockExplorers: { + default: { + name: 'Tempo Explorer', + url: 'https://explore.devnet.tempo.xyz', + }, + }, nativeCurrency: { name: 'USD', symbol: 'USD', diff --git a/src/chains/definitions/tempoModerato.ts b/src/chains/definitions/tempoModerato.ts new file mode 100644 index 0000000000..aa8c6ad29b --- /dev/null +++ b/src/chains/definitions/tempoModerato.ts @@ -0,0 +1,25 @@ +import { chainConfig } from '../../tempo/chainConfig.js' +import { defineChain } from '../../utils/chain/defineChain.js' + +export const tempoModerato = /*#__PURE__*/ defineChain({ + ...chainConfig, + id: 42431, + blockExplorers: { + default: { + name: 'Tempo Explorer', + url: 'https://explore.moderato.tempo.xyz', + }, + }, + name: 'Tempo Testnet (Moderato)', + nativeCurrency: { + name: 'USD', + symbol: 'USD', + decimals: 6, + }, + rpcUrls: { + default: { + http: ['https://rpc.moderato.tempo.xyz'], + webSocket: ['wss://rpc.moderato.tempo.xyz'], + }, + }, +}) diff --git a/src/chains/index.ts b/src/chains/index.ts index 0a02b1331e..df470b393c 100644 --- a/src/chains/index.ts +++ b/src/chains/index.ts @@ -615,9 +615,15 @@ export { teaSepolia } from './definitions/teaSepolia.js' export { telcoinTestnet } from './definitions/telcoinTestnet.js' export { telos } from './definitions/telos.js' export { telosTestnet } from './definitions/telosTestnet.js' +export { + /** @deprecated Use `tempoModerato` instead. */ + tempoAndantino, + /** @deprecated Use `tempoModerato` instead. */ + tempoAndantino as tempoTestnet, +} from './definitions/tempoAndantino.js' export { tempoDevnet } from './definitions/tempoDevnet.js' export { tempoLocalnet } from './definitions/tempoLocalnet.js' -export { tempoTestnet } from './definitions/tempoTestnet.js' +export { tempoModerato } from './definitions/tempoModerato.js' export { tenet } from './definitions/tenet.js' export { ternoa } from './definitions/ternoa.js' export { thaiChain } from './definitions/thaiChain.js' diff --git a/src/package.json b/src/package.json index 30b9e135fe..a5c9c10528 100644 --- a/src/package.json +++ b/src/package.json @@ -218,7 +218,7 @@ "@scure/bip39": "1.6.0", "abitype": "1.2.3", "isows": "1.0.7", - "ox": "0.11.1", + "ox": "0.11.3", "ws": "8.18.3" }, "license": "MIT", diff --git a/src/tempo/Abis.ts b/src/tempo/Abis.ts index 786536a214..b113e9cf88 100644 --- a/src/tempo/Abis.ts +++ b/src/tempo/Abis.ts @@ -1,743 +1,360 @@ // Generated with `pnpm gen:abis`. Do not modify manually. -export const stablecoinExchange = [ +export const tip20 = [ { - name: 'createPair', + name: 'name', type: 'function', - stateMutability: 'nonpayable', - inputs: [{ type: 'address', name: 'base' }], - outputs: [{ type: 'bytes32', name: 'key' }], + stateMutability: 'view', + inputs: [], + outputs: [{ type: 'string' }], }, { - name: 'place', + name: 'symbol', type: 'function', - stateMutability: 'nonpayable', - inputs: [ - { type: 'address', name: 'token' }, - { type: 'uint128', name: 'amount' }, - { type: 'bool', name: 'isBid' }, - { type: 'int16', name: 'tick' }, - ], - outputs: [{ type: 'uint128', name: 'orderId' }], + stateMutability: 'view', + inputs: [], + outputs: [{ type: 'string' }], }, { - name: 'placeFlip', + name: 'decimals', type: 'function', - stateMutability: 'nonpayable', - inputs: [ - { type: 'address', name: 'token' }, - { type: 'uint128', name: 'amount' }, - { type: 'bool', name: 'isBid' }, - { type: 'int16', name: 'tick' }, - { type: 'int16', name: 'flipTick' }, - ], - outputs: [{ type: 'uint128', name: 'orderId' }], + stateMutability: 'view', + inputs: [], + outputs: [{ type: 'uint8' }], }, { - name: 'cancel', + name: 'totalSupply', type: 'function', - stateMutability: 'nonpayable', - inputs: [{ type: 'uint128', name: 'orderId' }], - outputs: [], + stateMutability: 'view', + inputs: [], + outputs: [{ type: 'uint256' }], }, { - name: 'executeBlock', + name: 'quoteToken', type: 'function', - stateMutability: 'nonpayable', + stateMutability: 'view', inputs: [], - outputs: [], + outputs: [{ type: 'address' }], }, { - name: 'swapExactAmountIn', + name: 'nextQuoteToken', type: 'function', - stateMutability: 'nonpayable', - inputs: [ - { type: 'address', name: 'tokenIn' }, - { type: 'address', name: 'tokenOut' }, - { type: 'uint128', name: 'amountIn' }, - { type: 'uint128', name: 'minAmountOut' }, - ], - outputs: [{ type: 'uint128', name: 'amountOut' }], + stateMutability: 'view', + inputs: [], + outputs: [{ type: 'address' }], }, { - name: 'swapExactAmountOut', + name: 'balanceOf', + type: 'function', + stateMutability: 'view', + inputs: [{ type: 'address', name: 'account' }], + outputs: [{ type: 'uint256' }], + }, + { + name: 'transfer', type: 'function', stateMutability: 'nonpayable', inputs: [ - { type: 'address', name: 'tokenIn' }, - { type: 'address', name: 'tokenOut' }, - { type: 'uint128', name: 'amountOut' }, - { type: 'uint128', name: 'maxAmountIn' }, + { type: 'address', name: 'to' }, + { type: 'uint256', name: 'amount' }, ], - outputs: [{ type: 'uint128', name: 'amountIn' }], + outputs: [{ type: 'bool' }], }, { - name: 'quoteSwapExactAmountIn', + name: 'approve', type: 'function', - stateMutability: 'view', + stateMutability: 'nonpayable', inputs: [ - { type: 'address', name: 'tokenIn' }, - { type: 'address', name: 'tokenOut' }, - { type: 'uint128', name: 'amountIn' }, + { type: 'address', name: 'spender' }, + { type: 'uint256', name: 'amount' }, ], - outputs: [{ type: 'uint128', name: 'amountOut' }], + outputs: [{ type: 'bool' }], }, { - name: 'quoteSwapExactAmountOut', + name: 'allowance', type: 'function', stateMutability: 'view', inputs: [ - { type: 'address', name: 'tokenIn' }, - { type: 'address', name: 'tokenOut' }, - { type: 'uint128', name: 'amountOut' }, + { type: 'address', name: 'owner' }, + { type: 'address', name: 'spender' }, ], - outputs: [{ type: 'uint128', name: 'amountIn' }], + outputs: [{ type: 'uint256' }], }, { - name: 'balanceOf', + name: 'transferFrom', type: 'function', - stateMutability: 'view', + stateMutability: 'nonpayable', inputs: [ - { type: 'address', name: 'user' }, - { type: 'address', name: 'token' }, + { type: 'address', name: 'from' }, + { type: 'address', name: 'to' }, + { type: 'uint256', name: 'amount' }, ], - outputs: [{ type: 'uint128' }], + outputs: [{ type: 'bool' }], }, { - name: 'withdraw', + name: 'mint', type: 'function', stateMutability: 'nonpayable', inputs: [ - { type: 'address', name: 'token' }, - { type: 'uint128', name: 'amount' }, + { type: 'address', name: 'to' }, + { type: 'uint256', name: 'amount' }, ], outputs: [], }, { - name: 'getOrder', + name: 'burn', type: 'function', - stateMutability: 'view', - inputs: [{ type: 'uint128', name: 'orderId' }], - outputs: [ - { - type: 'tuple', - components: [ - { type: 'uint128', name: 'orderId' }, - { type: 'address', name: 'maker' }, - { type: 'bytes32', name: 'bookKey' }, - { type: 'bool', name: 'isBid' }, - { type: 'int16', name: 'tick' }, - { type: 'uint128', name: 'amount' }, - { type: 'uint128', name: 'remaining' }, - { type: 'uint128', name: 'prev' }, - { type: 'uint128', name: 'next' }, - { type: 'bool', name: 'isFlip' }, - { type: 'int16', name: 'flipTick' }, - ], - }, - ], + stateMutability: 'nonpayable', + inputs: [{ type: 'uint256', name: 'amount' }], + outputs: [], }, { - name: 'getTickLevel', + name: 'currency', type: 'function', stateMutability: 'view', - inputs: [ - { type: 'address', name: 'base' }, - { type: 'int16', name: 'tick' }, - { type: 'bool', name: 'isBid' }, - ], - outputs: [ - { type: 'uint128', name: 'head' }, - { type: 'uint128', name: 'tail' }, - { type: 'uint128', name: 'totalLiquidity' }, - ], + inputs: [], + outputs: [{ type: 'string' }], }, { - name: 'pairKey', + name: 'supplyCap', type: 'function', - stateMutability: 'pure', - inputs: [ - { type: 'address', name: 'tokenA' }, - { type: 'address', name: 'tokenB' }, - ], - outputs: [{ type: 'bytes32' }], + stateMutability: 'view', + inputs: [], + outputs: [{ type: 'uint256' }], }, { - name: 'activeOrderId', + name: 'paused', type: 'function', stateMutability: 'view', inputs: [], - outputs: [{ type: 'uint128' }], + outputs: [{ type: 'bool' }], }, { - name: 'pendingOrderId', + name: 'transferPolicyId', type: 'function', stateMutability: 'view', inputs: [], - outputs: [{ type: 'uint128' }], + outputs: [{ type: 'uint64' }], }, { - name: 'books', + name: 'burnBlocked', type: 'function', - stateMutability: 'view', - inputs: [{ type: 'bytes32', name: 'pairKey' }], - outputs: [ - { - type: 'tuple', - components: [ - { type: 'address', name: 'base' }, - { type: 'address', name: 'quote' }, - { type: 'int16', name: 'bestBidTick' }, - { type: 'int16', name: 'bestAskTick' }, - ], - }, + stateMutability: 'nonpayable', + inputs: [ + { type: 'address', name: 'from' }, + { type: 'uint256', name: 'amount' }, ], + outputs: [], }, { - name: 'MIN_TICK', + name: 'mintWithMemo', type: 'function', - stateMutability: 'pure', - inputs: [], - outputs: [{ type: 'int16' }], + stateMutability: 'nonpayable', + inputs: [ + { type: 'address', name: 'to' }, + { type: 'uint256', name: 'amount' }, + { type: 'bytes32', name: 'memo' }, + ], + outputs: [], }, { - name: 'MAX_TICK', + name: 'burnWithMemo', type: 'function', - stateMutability: 'pure', - inputs: [], - outputs: [{ type: 'int16' }], + stateMutability: 'nonpayable', + inputs: [ + { type: 'uint256', name: 'amount' }, + { type: 'bytes32', name: 'memo' }, + ], + outputs: [], }, { - name: 'TICK_SPACING', + name: 'transferWithMemo', type: 'function', - stateMutability: 'pure', - inputs: [], - outputs: [{ type: 'int16' }], + stateMutability: 'nonpayable', + inputs: [ + { type: 'address', name: 'to' }, + { type: 'uint256', name: 'amount' }, + { type: 'bytes32', name: 'memo' }, + ], + outputs: [], }, { - name: 'PRICE_SCALE', + name: 'transferFromWithMemo', type: 'function', - stateMutability: 'pure', - inputs: [], - outputs: [{ type: 'uint32' }], + stateMutability: 'nonpayable', + inputs: [ + { type: 'address', name: 'from' }, + { type: 'address', name: 'to' }, + { type: 'uint256', name: 'amount' }, + { type: 'bytes32', name: 'memo' }, + ], + outputs: [{ type: 'bool' }], }, { - name: 'MIN_PRICE', + name: 'changeTransferPolicyId', type: 'function', - stateMutability: 'pure', - inputs: [], - outputs: [{ type: 'uint32' }], + stateMutability: 'nonpayable', + inputs: [{ type: 'uint64', name: 'newPolicyId' }], + outputs: [], }, { - name: 'MAX_PRICE', + name: 'setSupplyCap', type: 'function', - stateMutability: 'pure', - inputs: [], - outputs: [{ type: 'uint32' }], + stateMutability: 'nonpayable', + inputs: [{ type: 'uint256', name: 'newSupplyCap' }], + outputs: [], }, { - name: 'tickToPrice', - type: 'function', - stateMutability: 'pure', - inputs: [{ type: 'int16', name: 'tick' }], - outputs: [{ type: 'uint32', name: 'price' }], - }, - { - name: 'priceToTick', - type: 'function', - stateMutability: 'pure', - inputs: [{ type: 'uint32', name: 'price' }], - outputs: [{ type: 'int16', name: 'tick' }], - }, - { - name: 'PairCreated', - type: 'event', - inputs: [ - { type: 'bytes32', name: 'key', indexed: true }, - { type: 'address', name: 'base', indexed: true }, - { type: 'address', name: 'quote', indexed: true }, - ], - }, - { - name: 'OrderPlaced', - type: 'event', - inputs: [ - { type: 'uint128', name: 'orderId', indexed: true }, - { type: 'address', name: 'maker', indexed: true }, - { type: 'address', name: 'token', indexed: true }, - { type: 'uint128', name: 'amount' }, - { type: 'bool', name: 'isBid' }, - { type: 'int16', name: 'tick' }, - ], - }, - { - name: 'FlipOrderPlaced', - type: 'event', - inputs: [ - { type: 'uint128', name: 'orderId', indexed: true }, - { type: 'address', name: 'maker', indexed: true }, - { type: 'address', name: 'token', indexed: true }, - { type: 'uint128', name: 'amount' }, - { type: 'bool', name: 'isBid' }, - { type: 'int16', name: 'tick' }, - { type: 'int16', name: 'flipTick' }, - ], - }, - { - name: 'OrderFilled', - type: 'event', - inputs: [ - { type: 'uint128', name: 'orderId', indexed: true }, - { type: 'address', name: 'maker', indexed: true }, - { type: 'uint128', name: 'amountFilled' }, - { type: 'bool', name: 'partialFill' }, - ], - }, - { - name: 'OrderFilled', - type: 'event', - inputs: [ - { type: 'uint128', name: 'orderId', indexed: true }, - { type: 'address', name: 'maker', indexed: true }, - { type: 'address', name: 'taker', indexed: true }, - { type: 'uint128', name: 'amountFilled' }, - { type: 'bool', name: 'partialFill' }, - ], - }, - { - name: 'OrderCancelled', - type: 'event', - inputs: [{ type: 'uint128', name: 'orderId', indexed: true }], - }, - { name: 'Unauthorized', type: 'error', inputs: [] }, - { name: 'PairDoesNotExist', type: 'error', inputs: [] }, - { name: 'PairAlreadyExists', type: 'error', inputs: [] }, - { name: 'OrderDoesNotExist', type: 'error', inputs: [] }, - { name: 'IdenticalTokens', type: 'error', inputs: [] }, - { name: 'InvalidToken', type: 'error', inputs: [] }, - { - name: 'TickOutOfBounds', - type: 'error', - inputs: [{ type: 'int16', name: 'tick' }], - }, - { name: 'InvalidTick', type: 'error', inputs: [] }, - { name: 'InvalidFlipTick', type: 'error', inputs: [] }, - { name: 'InsufficientBalance', type: 'error', inputs: [] }, - { name: 'InsufficientLiquidity', type: 'error', inputs: [] }, - { name: 'InsufficientOutput', type: 'error', inputs: [] }, - { name: 'MaxInputExceeded', type: 'error', inputs: [] }, - { - name: 'BelowMinimumOrderSize', - type: 'error', - inputs: [{ type: 'uint128', name: 'amount' }], - }, - { name: 'InvalidBaseToken', type: 'error', inputs: [] }, -] as const - -export const tip20 = [ - { - name: 'name', + name: 'pause', type: 'function', - stateMutability: 'view', + stateMutability: 'nonpayable', inputs: [], - outputs: [{ type: 'string' }], + outputs: [], }, { - name: 'symbol', + name: 'unpause', type: 'function', - stateMutability: 'view', + stateMutability: 'nonpayable', inputs: [], - outputs: [{ type: 'string' }], + outputs: [], }, { - name: 'decimals', + name: 'setNextQuoteToken', type: 'function', - stateMutability: 'view', - inputs: [], - outputs: [{ type: 'uint8' }], + stateMutability: 'nonpayable', + inputs: [{ type: 'address', name: 'newQuoteToken' }], + outputs: [], }, { - name: 'totalSupply', + name: 'completeQuoteTokenUpdate', type: 'function', - stateMutability: 'view', + stateMutability: 'nonpayable', inputs: [], - outputs: [{ type: 'uint256' }], + outputs: [], }, { - name: 'quoteToken', + name: 'PAUSE_ROLE', type: 'function', stateMutability: 'view', inputs: [], - outputs: [{ type: 'address' }], + outputs: [{ type: 'bytes32' }], }, { - name: 'nextQuoteToken', + name: 'UNPAUSE_ROLE', type: 'function', stateMutability: 'view', inputs: [], - outputs: [{ type: 'address' }], + outputs: [{ type: 'bytes32' }], }, { - name: 'balanceOf', + name: 'ISSUER_ROLE', type: 'function', stateMutability: 'view', - inputs: [{ type: 'address', name: 'account' }], - outputs: [{ type: 'uint256' }], - }, - { - name: 'transfer', - type: 'function', - stateMutability: 'nonpayable', - inputs: [ - { type: 'address', name: 'to' }, - { type: 'uint256', name: 'amount' }, - ], - outputs: [{ type: 'bool' }], - }, - { - name: 'approve', - type: 'function', - stateMutability: 'nonpayable', - inputs: [ - { type: 'address', name: 'spender' }, - { type: 'uint256', name: 'amount' }, - ], - outputs: [{ type: 'bool' }], + inputs: [], + outputs: [{ type: 'bytes32' }], }, { - name: 'allowance', + name: 'BURN_BLOCKED_ROLE', type: 'function', stateMutability: 'view', - inputs: [ - { type: 'address', name: 'owner' }, - { type: 'address', name: 'spender' }, - ], - outputs: [{ type: 'uint256' }], + inputs: [], + outputs: [{ type: 'bytes32' }], }, { - name: 'transferFrom', + name: 'distributeReward', type: 'function', stateMutability: 'nonpayable', - inputs: [ - { type: 'address', name: 'from' }, - { type: 'address', name: 'to' }, - { type: 'uint256', name: 'amount' }, - ], - outputs: [{ type: 'bool' }], + inputs: [{ type: 'uint256', name: 'amount' }], + outputs: [], }, { - name: 'mint', + name: 'setRewardRecipient', type: 'function', stateMutability: 'nonpayable', - inputs: [ - { type: 'address', name: 'to' }, - { type: 'uint256', name: 'amount' }, - ], + inputs: [{ type: 'address', name: 'recipient' }], outputs: [], }, { - name: 'burn', + name: 'claimRewards', type: 'function', stateMutability: 'nonpayable', - inputs: [{ type: 'uint256', name: 'amount' }], - outputs: [], + inputs: [], + outputs: [{ type: 'uint256' }], }, { - name: 'currency', + name: 'optedInSupply', type: 'function', stateMutability: 'view', inputs: [], - outputs: [{ type: 'string' }], + outputs: [{ type: 'uint128' }], }, { - name: 'supplyCap', + name: 'globalRewardPerToken', type: 'function', stateMutability: 'view', inputs: [], outputs: [{ type: 'uint256' }], }, { - name: 'paused', + name: 'userRewardInfo', type: 'function', stateMutability: 'view', - inputs: [], - outputs: [{ type: 'bool' }], + inputs: [{ type: 'address', name: 'account' }], + outputs: [ + { + type: 'tuple', + components: [ + { type: 'address', name: 'rewardRecipient' }, + { type: 'uint256', name: 'rewardPerToken' }, + { type: 'uint256', name: 'rewardBalance' }, + ], + }, + ], }, { - name: 'transferPolicyId', + name: 'getPendingRewards', type: 'function', stateMutability: 'view', - inputs: [], - outputs: [{ type: 'uint64' }], + inputs: [{ type: 'address', name: 'account' }], + outputs: [{ type: 'uint128' }], }, { - name: 'burnBlocked', - type: 'function', - stateMutability: 'nonpayable', + name: 'Transfer', + type: 'event', inputs: [ - { type: 'address', name: 'from' }, + { type: 'address', name: 'from', indexed: true }, + { type: 'address', name: 'to', indexed: true }, { type: 'uint256', name: 'amount' }, ], - outputs: [], }, { - name: 'mintWithMemo', - type: 'function', - stateMutability: 'nonpayable', + name: 'Approval', + type: 'event', inputs: [ - { type: 'address', name: 'to' }, + { type: 'address', name: 'owner', indexed: true }, + { type: 'address', name: 'spender', indexed: true }, { type: 'uint256', name: 'amount' }, - { type: 'bytes32', name: 'memo' }, ], - outputs: [], }, { - name: 'burnWithMemo', - type: 'function', - stateMutability: 'nonpayable', + name: 'Mint', + type: 'event', inputs: [ + { type: 'address', name: 'to', indexed: true }, { type: 'uint256', name: 'amount' }, - { type: 'bytes32', name: 'memo' }, ], - outputs: [], }, { - name: 'transferWithMemo', - type: 'function', - stateMutability: 'nonpayable', + name: 'Burn', + type: 'event', inputs: [ - { type: 'address', name: 'to' }, + { type: 'address', name: 'from', indexed: true }, { type: 'uint256', name: 'amount' }, - { type: 'bytes32', name: 'memo' }, ], - outputs: [], }, { - name: 'transferFromWithMemo', - type: 'function', - stateMutability: 'nonpayable', - inputs: [ - { type: 'address', name: 'from' }, - { type: 'address', name: 'to' }, - { type: 'uint256', name: 'amount' }, - { type: 'bytes32', name: 'memo' }, - ], - outputs: [{ type: 'bool' }], - }, - { - name: 'feeRecipient', - type: 'function', - stateMutability: 'view', - inputs: [], - outputs: [{ type: 'address' }], - }, - { - name: 'setFeeRecipient', - type: 'function', - stateMutability: 'view', - inputs: [{ type: 'address', name: 'newRecipient' }], - outputs: [{ type: 'address' }], - }, - { - name: 'changeTransferPolicyId', - type: 'function', - stateMutability: 'nonpayable', - inputs: [{ type: 'uint64', name: 'newPolicyId' }], - outputs: [], - }, - { - name: 'setSupplyCap', - type: 'function', - stateMutability: 'nonpayable', - inputs: [{ type: 'uint256', name: 'newSupplyCap' }], - outputs: [], - }, - { - name: 'pause', - type: 'function', - stateMutability: 'nonpayable', - inputs: [], - outputs: [], - }, - { - name: 'unpause', - type: 'function', - stateMutability: 'nonpayable', - inputs: [], - outputs: [], - }, - { - name: 'setNextQuoteToken', - type: 'function', - stateMutability: 'nonpayable', - inputs: [{ type: 'address', name: 'newQuoteToken' }], - outputs: [], - }, - { - name: 'completeQuoteTokenUpdate', - type: 'function', - stateMutability: 'nonpayable', - inputs: [], - outputs: [], - }, - { - name: 'PAUSE_ROLE', - type: 'function', - stateMutability: 'view', - inputs: [], - outputs: [{ type: 'bytes32' }], - }, - { - name: 'UNPAUSE_ROLE', - type: 'function', - stateMutability: 'view', - inputs: [], - outputs: [{ type: 'bytes32' }], - }, - { - name: 'ISSUER_ROLE', - type: 'function', - stateMutability: 'view', - inputs: [], - outputs: [{ type: 'bytes32' }], - }, - { - name: 'BURN_BLOCKED_ROLE', - type: 'function', - stateMutability: 'view', - inputs: [], - outputs: [{ type: 'bytes32' }], - }, - { - name: 'startReward', - type: 'function', - stateMutability: 'nonpayable', - inputs: [ - { type: 'uint256', name: 'amount' }, - { type: 'uint32', name: 'secs' }, - ], - outputs: [{ type: 'uint64' }], - }, - { - name: 'setRewardRecipient', - type: 'function', - stateMutability: 'nonpayable', - inputs: [{ type: 'address', name: 'recipient' }], - outputs: [], - }, - { - name: 'cancelReward', - type: 'function', - stateMutability: 'nonpayable', - inputs: [{ type: 'uint64', name: 'id' }], - outputs: [{ type: 'uint256' }], - }, - { - name: 'claimRewards', - type: 'function', - stateMutability: 'nonpayable', - inputs: [], - outputs: [{ type: 'uint256' }], - }, - { - name: 'finalizeStreams', - type: 'function', - stateMutability: 'nonpayable', - inputs: [{ type: 'uint64', name: 'timestamp' }], - outputs: [], - }, - { - name: 'getStream', - type: 'function', - stateMutability: 'view', - inputs: [{ type: 'uint64', name: 'id' }], - outputs: [ - { - type: 'tuple', - components: [ - { type: 'address', name: 'funder' }, - { type: 'uint64', name: 'startTime' }, - { type: 'uint64', name: 'endTime' }, - { type: 'uint256', name: 'ratePerSecondScaled' }, - { type: 'uint256', name: 'amountTotal' }, - ], - }, - ], - }, - { - name: 'totalRewardPerSecond', - type: 'function', - stateMutability: 'view', - inputs: [], - outputs: [{ type: 'uint256' }], - }, - { - name: 'optedInSupply', - type: 'function', - stateMutability: 'view', - inputs: [], - outputs: [{ type: 'uint128' }], - }, - { - name: 'nextStreamId', - type: 'function', - stateMutability: 'view', - inputs: [], - outputs: [{ type: 'uint64' }], - }, - { - name: 'userRewardInfo', - type: 'function', - stateMutability: 'view', - inputs: [{ type: 'address', name: 'account' }], - outputs: [ - { - type: 'tuple', - components: [ - { type: 'address', name: 'rewardRecipient' }, - { type: 'uint256', name: 'rewardPerToken' }, - { type: 'uint256', name: 'rewardBalance' }, - ], - }, - ], - }, - { - name: 'Transfer', - type: 'event', - inputs: [ - { type: 'address', name: 'from', indexed: true }, - { type: 'address', name: 'to', indexed: true }, - { type: 'uint256', name: 'amount' }, - ], - }, - { - name: 'Approval', - type: 'event', - inputs: [ - { type: 'address', name: 'owner', indexed: true }, - { type: 'address', name: 'spender', indexed: true }, - { type: 'uint256', name: 'amount' }, - ], - }, - { - name: 'Mint', - type: 'event', - inputs: [ - { type: 'address', name: 'to', indexed: true }, - { type: 'uint256', name: 'amount' }, - ], - }, - { - name: 'Burn', - type: 'event', - inputs: [ - { type: 'address', name: 'from', indexed: true }, - { type: 'uint256', name: 'amount' }, - ], - }, - { - name: 'BurnBlocked', - type: 'event', + name: 'BurnBlocked', + type: 'event', inputs: [ { type: 'address', name: 'from', indexed: true }, { type: 'uint256', name: 'amount' }, @@ -794,22 +411,11 @@ export const tip20 = [ ], }, { - name: 'RewardScheduled', + name: 'RewardDistributed', type: 'event', inputs: [ { type: 'address', name: 'funder', indexed: true }, - { type: 'uint64', name: 'id', indexed: true }, { type: 'uint256', name: 'amount' }, - { type: 'uint32', name: 'durationSeconds' }, - ], - }, - { - name: 'RewardCanceled', - type: 'event', - inputs: [ - { type: 'address', name: 'funder', indexed: true }, - { type: 'uint64', name: 'id', indexed: true }, - { type: 'uint256', name: 'refund' }, ], }, { @@ -820,14 +426,6 @@ export const tip20 = [ { type: 'address', name: 'recipient', indexed: true }, ], }, - { - name: 'FeeRecipientUpdated', - type: 'event', - inputs: [ - { type: 'address', name: 'updater', indexed: true }, - { type: 'address', name: 'newRecipient', indexed: true }, - ], - }, { name: 'InsufficientBalance', type: 'error', @@ -849,14 +447,12 @@ export const tip20 = [ { name: 'InvalidQuoteToken', type: 'error', inputs: [] }, { name: 'TransfersDisabled', type: 'error', inputs: [] }, { name: 'InvalidAmount', type: 'error', inputs: [] }, - { name: 'NotStreamFunder', type: 'error', inputs: [] }, - { name: 'StreamInactive', type: 'error', inputs: [] }, { name: 'NoOptedInSupply', type: 'error', inputs: [] }, { name: 'Unauthorized', type: 'error', inputs: [] }, - { name: 'RewardsDisabled', type: 'error', inputs: [] }, - { name: 'ScheduledRewardsDisabled', type: 'error', inputs: [] }, { name: 'ProtectedAddress', type: 'error', inputs: [] }, { name: 'InvalidToken', type: 'error', inputs: [] }, + { name: 'Uninitialized', type: 'error', inputs: [] }, + { name: 'InvalidTransferPolicyId', type: 'error', inputs: [] }, { name: 'hasRole', type: 'function', @@ -895,68 +491,356 @@ export const tip20 = [ outputs: [], }, { - name: 'renounceRole', + name: 'renounceRole', + type: 'function', + stateMutability: 'nonpayable', + inputs: [{ type: 'bytes32', name: 'role' }], + outputs: [], + }, + { + name: 'setRoleAdmin', + type: 'function', + stateMutability: 'nonpayable', + inputs: [ + { type: 'bytes32', name: 'role' }, + { type: 'bytes32', name: 'adminRole' }, + ], + outputs: [], + }, + { + name: 'RoleMembershipUpdated', + type: 'event', + inputs: [ + { type: 'bytes32', name: 'role', indexed: true }, + { type: 'address', name: 'account', indexed: true }, + { type: 'address', name: 'sender', indexed: true }, + { type: 'bool', name: 'hasRole' }, + ], + }, + { + name: 'RoleAdminUpdated', + type: 'event', + inputs: [ + { type: 'bytes32', name: 'role', indexed: true }, + { type: 'bytes32', name: 'newAdminRole', indexed: true }, + { type: 'address', name: 'sender', indexed: true }, + ], + }, + { name: 'Unauthorized', type: 'error', inputs: [] }, +] as const + +export const stablecoinDex = [ + { + name: 'createPair', + type: 'function', + stateMutability: 'nonpayable', + inputs: [{ type: 'address', name: 'base' }], + outputs: [{ type: 'bytes32', name: 'key' }], + }, + { + name: 'place', + type: 'function', + stateMutability: 'nonpayable', + inputs: [ + { type: 'address', name: 'token' }, + { type: 'uint128', name: 'amount' }, + { type: 'bool', name: 'isBid' }, + { type: 'int16', name: 'tick' }, + ], + outputs: [{ type: 'uint128', name: 'orderId' }], + }, + { + name: 'placeFlip', + type: 'function', + stateMutability: 'nonpayable', + inputs: [ + { type: 'address', name: 'token' }, + { type: 'uint128', name: 'amount' }, + { type: 'bool', name: 'isBid' }, + { type: 'int16', name: 'tick' }, + { type: 'int16', name: 'flipTick' }, + ], + outputs: [{ type: 'uint128', name: 'orderId' }], + }, + { + name: 'cancel', + type: 'function', + stateMutability: 'nonpayable', + inputs: [{ type: 'uint128', name: 'orderId' }], + outputs: [], + }, + { + name: 'cancelStaleOrder', + type: 'function', + stateMutability: 'nonpayable', + inputs: [{ type: 'uint128', name: 'orderId' }], + outputs: [], + }, + { + name: 'swapExactAmountIn', + type: 'function', + stateMutability: 'nonpayable', + inputs: [ + { type: 'address', name: 'tokenIn' }, + { type: 'address', name: 'tokenOut' }, + { type: 'uint128', name: 'amountIn' }, + { type: 'uint128', name: 'minAmountOut' }, + ], + outputs: [{ type: 'uint128', name: 'amountOut' }], + }, + { + name: 'swapExactAmountOut', + type: 'function', + stateMutability: 'nonpayable', + inputs: [ + { type: 'address', name: 'tokenIn' }, + { type: 'address', name: 'tokenOut' }, + { type: 'uint128', name: 'amountOut' }, + { type: 'uint128', name: 'maxAmountIn' }, + ], + outputs: [{ type: 'uint128', name: 'amountIn' }], + }, + { + name: 'quoteSwapExactAmountIn', + type: 'function', + stateMutability: 'view', + inputs: [ + { type: 'address', name: 'tokenIn' }, + { type: 'address', name: 'tokenOut' }, + { type: 'uint128', name: 'amountIn' }, + ], + outputs: [{ type: 'uint128', name: 'amountOut' }], + }, + { + name: 'quoteSwapExactAmountOut', + type: 'function', + stateMutability: 'view', + inputs: [ + { type: 'address', name: 'tokenIn' }, + { type: 'address', name: 'tokenOut' }, + { type: 'uint128', name: 'amountOut' }, + ], + outputs: [{ type: 'uint128', name: 'amountIn' }], + }, + { + name: 'balanceOf', + type: 'function', + stateMutability: 'view', + inputs: [ + { type: 'address', name: 'user' }, + { type: 'address', name: 'token' }, + ], + outputs: [{ type: 'uint128' }], + }, + { + name: 'withdraw', + type: 'function', + stateMutability: 'nonpayable', + inputs: [ + { type: 'address', name: 'token' }, + { type: 'uint128', name: 'amount' }, + ], + outputs: [], + }, + { + name: 'getOrder', + type: 'function', + stateMutability: 'view', + inputs: [{ type: 'uint128', name: 'orderId' }], + outputs: [ + { + type: 'tuple', + components: [ + { type: 'uint128', name: 'orderId' }, + { type: 'address', name: 'maker' }, + { type: 'bytes32', name: 'bookKey' }, + { type: 'bool', name: 'isBid' }, + { type: 'int16', name: 'tick' }, + { type: 'uint128', name: 'amount' }, + { type: 'uint128', name: 'remaining' }, + { type: 'uint128', name: 'prev' }, + { type: 'uint128', name: 'next' }, + { type: 'bool', name: 'isFlip' }, + { type: 'int16', name: 'flipTick' }, + ], + }, + ], + }, + { + name: 'getTickLevel', + type: 'function', + stateMutability: 'view', + inputs: [ + { type: 'address', name: 'base' }, + { type: 'int16', name: 'tick' }, + { type: 'bool', name: 'isBid' }, + ], + outputs: [ + { type: 'uint128', name: 'head' }, + { type: 'uint128', name: 'tail' }, + { type: 'uint128', name: 'totalLiquidity' }, + ], + }, + { + name: 'pairKey', + type: 'function', + stateMutability: 'pure', + inputs: [ + { type: 'address', name: 'tokenA' }, + { type: 'address', name: 'tokenB' }, + ], + outputs: [{ type: 'bytes32' }], + }, + { + name: 'nextOrderId', + type: 'function', + stateMutability: 'view', + inputs: [], + outputs: [{ type: 'uint128' }], + }, + { + name: 'books', + type: 'function', + stateMutability: 'view', + inputs: [{ type: 'bytes32', name: 'pairKey' }], + outputs: [ + { + type: 'tuple', + components: [ + { type: 'address', name: 'base' }, + { type: 'address', name: 'quote' }, + { type: 'int16', name: 'bestBidTick' }, + { type: 'int16', name: 'bestAskTick' }, + ], + }, + ], + }, + { + name: 'MIN_TICK', + type: 'function', + stateMutability: 'pure', + inputs: [], + outputs: [{ type: 'int16' }], + }, + { + name: 'MAX_TICK', + type: 'function', + stateMutability: 'pure', + inputs: [], + outputs: [{ type: 'int16' }], + }, + { + name: 'TICK_SPACING', + type: 'function', + stateMutability: 'pure', + inputs: [], + outputs: [{ type: 'int16' }], + }, + { + name: 'PRICE_SCALE', + type: 'function', + stateMutability: 'pure', + inputs: [], + outputs: [{ type: 'uint32' }], + }, + { + name: 'MIN_ORDER_AMOUNT', + type: 'function', + stateMutability: 'pure', + inputs: [], + outputs: [{ type: 'uint128' }], + }, + { + name: 'MIN_PRICE', + type: 'function', + stateMutability: 'pure', + inputs: [], + outputs: [{ type: 'uint32' }], + }, + { + name: 'MAX_PRICE', + type: 'function', + stateMutability: 'pure', + inputs: [], + outputs: [{ type: 'uint32' }], + }, + { + name: 'tickToPrice', type: 'function', - stateMutability: 'nonpayable', - inputs: [{ type: 'bytes32', name: 'role' }], - outputs: [], + stateMutability: 'pure', + inputs: [{ type: 'int16', name: 'tick' }], + outputs: [{ type: 'uint32', name: 'price' }], }, { - name: 'setRoleAdmin', + name: 'priceToTick', type: 'function', - stateMutability: 'nonpayable', + stateMutability: 'pure', + inputs: [{ type: 'uint32', name: 'price' }], + outputs: [{ type: 'int16', name: 'tick' }], + }, + { + name: 'PairCreated', + type: 'event', inputs: [ - { type: 'bytes32', name: 'role' }, - { type: 'bytes32', name: 'adminRole' }, + { type: 'bytes32', name: 'key', indexed: true }, + { type: 'address', name: 'base', indexed: true }, + { type: 'address', name: 'quote', indexed: true }, ], - outputs: [], }, { - name: 'RoleMembershipUpdated', + name: 'OrderPlaced', type: 'event', inputs: [ - { type: 'bytes32', name: 'role', indexed: true }, - { type: 'address', name: 'account', indexed: true }, - { type: 'address', name: 'sender', indexed: true }, - { type: 'bool', name: 'hasRole' }, + { type: 'uint128', name: 'orderId', indexed: true }, + { type: 'address', name: 'maker', indexed: true }, + { type: 'address', name: 'token', indexed: true }, + { type: 'uint128', name: 'amount' }, + { type: 'bool', name: 'isBid' }, + { type: 'int16', name: 'tick' }, + { type: 'bool', name: 'isFlipOrder' }, + { type: 'int16', name: 'flipTick' }, ], }, { - name: 'RoleAdminUpdated', + name: 'OrderFilled', type: 'event', inputs: [ - { type: 'bytes32', name: 'role', indexed: true }, - { type: 'bytes32', name: 'newAdminRole', indexed: true }, - { type: 'address', name: 'sender', indexed: true }, + { type: 'uint128', name: 'orderId', indexed: true }, + { type: 'address', name: 'maker', indexed: true }, + { type: 'address', name: 'taker', indexed: true }, + { type: 'uint128', name: 'amountFilled' }, + { type: 'bool', name: 'partialFill' }, ], }, + { + name: 'OrderCancelled', + type: 'event', + inputs: [{ type: 'uint128', name: 'orderId', indexed: true }], + }, { name: 'Unauthorized', type: 'error', inputs: [] }, -] as const - -export const tipAccountRegistrar = [ + { name: 'PairDoesNotExist', type: 'error', inputs: [] }, + { name: 'PairAlreadyExists', type: 'error', inputs: [] }, + { name: 'OrderDoesNotExist', type: 'error', inputs: [] }, + { name: 'IdenticalTokens', type: 'error', inputs: [] }, + { name: 'InvalidToken', type: 'error', inputs: [] }, { - name: 'delegateToDefault', - type: 'function', - stateMutability: 'nonpayable', - inputs: [ - { type: 'bytes32', name: 'hash' }, - { type: 'bytes', name: 'signature' }, - ], - outputs: [{ type: 'address', name: 'authority' }], + name: 'TickOutOfBounds', + type: 'error', + inputs: [{ type: 'int16', name: 'tick' }], }, + { name: 'InvalidTick', type: 'error', inputs: [] }, + { name: 'InvalidFlipTick', type: 'error', inputs: [] }, + { name: 'InsufficientBalance', type: 'error', inputs: [] }, + { name: 'InsufficientLiquidity', type: 'error', inputs: [] }, + { name: 'InsufficientOutput', type: 'error', inputs: [] }, + { name: 'MaxInputExceeded', type: 'error', inputs: [] }, { - name: 'delegateToDefault', - type: 'function', - stateMutability: 'nonpayable', - inputs: [ - { type: 'bytes', name: 'message' }, - { type: 'bytes', name: 'signature' }, - ], - outputs: [{ type: 'address', name: 'authority' }], + name: 'BelowMinimumOrderSize', + type: 'error', + inputs: [{ type: 'uint128', name: 'amount' }], }, - { name: 'InvalidSignature', type: 'error', inputs: [] }, - { name: 'CodeNotEmpty', type: 'error', inputs: [] }, - { name: 'NonceNotZero', type: 'error', inputs: [] }, + { name: 'InvalidBaseToken', type: 'error', inputs: [] }, + { name: 'OrderNotStale', type: 'error', inputs: [] }, ] as const export const feeManager = [ @@ -989,21 +873,24 @@ export const feeManager = [ outputs: [], }, { - name: 'getFeeTokenBalance', + name: 'distributeFees', type: 'function', - stateMutability: 'view', + stateMutability: 'nonpayable', inputs: [ - { type: 'address', name: 'sender' }, { type: 'address', name: 'validator' }, + { type: 'address', name: 'token' }, ], - outputs: [{ type: 'address' }, { type: 'uint256' }], + outputs: [], }, { - name: 'executeBlock', + name: 'collectedFees', type: 'function', - stateMutability: 'nonpayable', - inputs: [], - outputs: [], + stateMutability: 'view', + inputs: [ + { type: 'address', name: 'validator' }, + { type: 'address', name: 'token' }, + ], + outputs: [{ type: 'uint256' }], }, { name: 'UserTokenSet', @@ -1021,6 +908,15 @@ export const feeManager = [ { type: 'address', name: 'token', indexed: true }, ], }, + { + name: 'FeesDistributed', + type: 'event', + inputs: [ + { type: 'address', name: 'validator', indexed: true }, + { type: 'address', name: 'token', indexed: true }, + { type: 'uint256', name: 'amount' }, + ], + }, { name: 'OnlyValidator', type: 'error', inputs: [] }, { name: 'OnlySystemContract', type: 'error', inputs: [] }, { name: 'InvalidToken', type: 'error', inputs: [] }, @@ -1108,19 +1004,6 @@ export const feeAmm = [ name: 'mint', type: 'function', stateMutability: 'nonpayable', - inputs: [ - { type: 'address', name: 'userToken' }, - { type: 'address', name: 'validatorToken' }, - { type: 'uint256', name: 'amountUserToken' }, - { type: 'uint256', name: 'amountValidatorToken' }, - { type: 'address', name: 'to' }, - ], - outputs: [{ type: 'uint256', name: 'liquidity' }], - }, - { - name: 'mintWithValidatorToken', - type: 'function', - stateMutability: 'nonpayable', inputs: [ { type: 'address', name: 'userToken' }, { type: 'address', name: 'validatorToken' }, @@ -1177,10 +1060,10 @@ export const feeAmm = [ name: 'Mint', type: 'event', inputs: [ - { type: 'address', name: 'sender', indexed: true }, + { type: 'address', name: 'sender' }, + { type: 'address', name: 'to', indexed: true }, { type: 'address', name: 'userToken', indexed: true }, { type: 'address', name: 'validatorToken', indexed: true }, - { type: 'uint256', name: 'amountUserToken' }, { type: 'uint256', name: 'amountValidatorToken' }, { type: 'uint256', name: 'liquidity' }, ], @@ -1209,37 +1092,13 @@ export const feeAmm = [ { type: 'uint256', name: 'amountOut' }, ], }, - { - name: 'FeeSwap', - type: 'event', - inputs: [ - { type: 'address', name: 'userToken', indexed: true }, - { type: 'address', name: 'validatorToken', indexed: true }, - { type: 'uint256', name: 'amountIn' }, - { type: 'uint256', name: 'amountOut' }, - ], - }, { name: 'IdenticalAddresses', type: 'error', inputs: [] }, - { name: 'ZeroAddress', type: 'error', inputs: [] }, - { name: 'PoolExists', type: 'error', inputs: [] }, - { name: 'PoolDoesNotExist', type: 'error', inputs: [] }, { name: 'InvalidToken', type: 'error', inputs: [] }, { name: 'InsufficientLiquidity', type: 'error', inputs: [] }, - { name: 'OnlyProtocol', type: 'error', inputs: [] }, - { name: 'InsufficientPoolBalance', type: 'error', inputs: [] }, { name: 'InsufficientReserves', type: 'error', inputs: [] }, - { name: 'InsufficientLiquidityBalance', type: 'error', inputs: [] }, - { name: 'MustDepositLowerBalanceToken', type: 'error', inputs: [] }, { name: 'InvalidAmount', type: 'error', inputs: [] }, - { name: 'InvalidRebalanceState', type: 'error', inputs: [] }, - { name: 'InvalidRebalanceDirection', type: 'error', inputs: [] }, - { name: 'InvalidNewReserves', type: 'error', inputs: [] }, - { name: 'CannotSupportPendingSwaps', type: 'error', inputs: [] }, { name: 'DivisionByZero', type: 'error', inputs: [] }, { name: 'InvalidSwapCalculation', type: 'error', inputs: [] }, - { name: 'InsufficientLiquidityForPending', type: 'error', inputs: [] }, - { name: 'TokenTransferFailed', type: 'error', inputs: [] }, - { name: 'InternalError', type: 'error', inputs: [] }, ] as const export const accountKeychain = [ @@ -1330,16 +1189,6 @@ export const accountKeychain = [ { type: 'uint64', name: 'expiry' }, ], }, - { - name: 'KeyAuthorized', - type: 'event', - inputs: [ - { type: 'address', name: 'account', indexed: true }, - { type: 'bytes32', name: 'publicKey', indexed: true }, - { type: 'uint8', name: 'signatureType' }, - { type: 'uint64', name: 'expiry' }, - ], - }, { name: 'KeyRevoked', type: 'event', @@ -1348,14 +1197,6 @@ export const accountKeychain = [ { type: 'address', name: 'publicKey', indexed: true }, ], }, - { - name: 'KeyRevoked', - type: 'event', - inputs: [ - { type: 'address', name: 'account', indexed: true }, - { type: 'bytes32', name: 'publicKey', indexed: true }, - ], - }, { name: 'SpendingLimitUpdated', type: 'event', @@ -1366,16 +1207,6 @@ export const accountKeychain = [ { type: 'uint256', name: 'newLimit' }, ], }, - { - name: 'SpendingLimitUpdated', - type: 'event', - inputs: [ - { type: 'address', name: 'account', indexed: true }, - { type: 'bytes32', name: 'publicKey', indexed: true }, - { type: 'address', name: 'token', indexed: true }, - { type: 'uint256', name: 'newLimit' }, - ], - }, { name: 'UnauthorizedCaller', type: 'error', inputs: [] }, { name: 'KeyAlreadyExists', type: 'error', inputs: [] }, { name: 'KeyNotFound', type: 'error', inputs: [] }, @@ -1398,13 +1229,6 @@ export const nonce = [ ], outputs: [{ type: 'uint64', name: 'nonce' }], }, - { - name: 'getActiveNonceKeyCount', - type: 'function', - stateMutability: 'view', - inputs: [{ type: 'address', name: 'account' }], - outputs: [{ type: 'uint256', name: 'count' }], - }, { name: 'NonceIncremented', type: 'event', @@ -1414,31 +1238,11 @@ export const nonce = [ { type: 'uint64', name: 'newNonce' }, ], }, - { - name: 'ActiveKeyCountChanged', - type: 'event', - inputs: [ - { type: 'address', name: 'account', indexed: true }, - { type: 'uint256', name: 'newCount' }, - ], - }, { name: 'ProtocolNonceNotSupported', type: 'error', inputs: [] }, { name: 'InvalidNonceKey', type: 'error', inputs: [] }, { name: 'NonceOverflow', type: 'error', inputs: [] }, ] as const -export const tip20RewardsRegistry = [ - { - name: 'finalizeStreams', - type: 'function', - stateMutability: 'nonpayable', - inputs: [], - outputs: [], - }, - { name: 'Unauthorized', type: 'error', inputs: [] }, - { name: 'StreamsAlreadyFinalized', type: 'error', inputs: [] }, -] as const - export const tip20Factory = [ { name: 'createToken', @@ -1450,36 +1254,48 @@ export const tip20Factory = [ { type: 'string', name: 'currency' }, { type: 'address', name: 'quoteToken' }, { type: 'address', name: 'admin' }, + { type: 'bytes32', name: 'salt' }, ], outputs: [{ type: 'address' }], }, { - name: 'tokenIdCounter', + name: 'isTIP20', type: 'function', stateMutability: 'view', - inputs: [], - outputs: [{ type: 'uint256' }], + inputs: [{ type: 'address', name: 'token' }], + outputs: [{ type: 'bool' }], }, { - name: 'isTIP20', + name: 'getTokenAddress', type: 'function', stateMutability: 'view', - inputs: [{ type: 'address', name: 'token' }], - outputs: [{ type: 'bool' }], + inputs: [ + { type: 'address', name: 'sender' }, + { type: 'bytes32', name: 'salt' }, + ], + outputs: [{ type: 'address' }], }, { name: 'TokenCreated', type: 'event', inputs: [ { type: 'address', name: 'token', indexed: true }, - { type: 'uint256', name: 'tokenId', indexed: true }, { type: 'string', name: 'name' }, { type: 'string', name: 'symbol' }, { type: 'string', name: 'currency' }, { type: 'address', name: 'quoteToken' }, { type: 'address', name: 'admin' }, + { type: 'bytes32', name: 'salt' }, ], }, + { name: 'AddressReserved', type: 'error', inputs: [] }, + { name: 'AddressNotReserved', type: 'error', inputs: [] }, + { name: 'InvalidQuoteToken', type: 'error', inputs: [] }, + { + name: 'TokenAlreadyExists', + type: 'error', + inputs: [{ type: 'address', name: 'token' }], + }, ] as const export const tip403Registry = [ @@ -1490,6 +1306,13 @@ export const tip403Registry = [ inputs: [], outputs: [{ type: 'uint64' }], }, + { + name: 'policyExists', + type: 'function', + stateMutability: 'view', + inputs: [{ type: 'uint64', name: 'policyId' }], + outputs: [{ type: 'bool' }], + }, { name: 'policyData', type: 'function', @@ -1603,6 +1426,7 @@ export const tip403Registry = [ }, { name: 'Unauthorized', type: 'error', inputs: [] }, { name: 'IncompatiblePolicyType', type: 'error', inputs: [] }, + { name: 'PolicyNotFound', type: 'error', inputs: [] }, ] as const export const validatorConfig = [ @@ -1675,9 +1499,57 @@ export const validatorConfig = [ inputs: [{ type: 'address', name: 'newOwner' }], outputs: [], }, + { + name: 'getNextFullDkgCeremony', + type: 'function', + stateMutability: 'view', + inputs: [], + outputs: [{ type: 'uint64' }], + }, + { + name: 'setNextFullDkgCeremony', + type: 'function', + stateMutability: 'nonpayable', + inputs: [{ type: 'uint64', name: 'epoch' }], + outputs: [], + }, + { + name: 'validatorsArray', + type: 'function', + stateMutability: 'view', + inputs: [{ type: 'uint256', name: 'index' }], + outputs: [{ type: 'address' }], + }, + { + name: 'validators', + type: 'function', + stateMutability: 'view', + inputs: [{ type: 'address', name: 'validator' }], + outputs: [ + { + type: 'tuple', + components: [ + { type: 'bytes32', name: 'publicKey' }, + { type: 'bool', name: 'active' }, + { type: 'uint64', name: 'index' }, + { type: 'address', name: 'validatorAddress' }, + { type: 'string', name: 'inboundAddress' }, + { type: 'string', name: 'outboundAddress' }, + ], + }, + ], + }, + { + name: 'validatorCount', + type: 'function', + stateMutability: 'view', + inputs: [], + outputs: [{ type: 'uint64' }], + }, { name: 'Unauthorized', type: 'error', inputs: [] }, { name: 'ValidatorAlreadyExists', type: 'error', inputs: [] }, { name: 'ValidatorNotFound', type: 'error', inputs: [] }, + { name: 'InvalidPublicKey', type: 'error', inputs: [] }, { name: 'NotHostPort', type: 'error', @@ -1697,20 +1569,3 @@ export const validatorConfig = [ ], }, ] as const - -export const pathUsd = [ - { - name: 'TRANSFER_ROLE', - type: 'function', - stateMutability: 'view', - inputs: [], - outputs: [{ type: 'bytes32' }], - }, - { - name: 'RECEIVE_WITH_MEMO_ROLE', - type: 'function', - stateMutability: 'view', - inputs: [], - outputs: [{ type: 'bytes32' }], - }, -] as const diff --git a/src/tempo/Addresses.ts b/src/tempo/Addresses.ts index 44052dcae4..e977a2d20b 100644 --- a/src/tempo/Addresses.ts +++ b/src/tempo/Addresses.ts @@ -4,6 +4,6 @@ export const accountRegistrar = '0x7702ac0000000000000000000000000000000000' export const feeManager = '0xfeec000000000000000000000000000000000000' export const nonceManager = '0x4e4F4E4345000000000000000000000000000000' export const pathUsd = '0x20c0000000000000000000000000000000000000' -export const stablecoinExchange = '0xdec0000000000000000000000000000000000000' +export const stablecoinDex = '0xdec0000000000000000000000000000000000000' export const tip20Factory = '0x20fc000000000000000000000000000000000000' export const tip403Registry = '0x403c000000000000000000000000000000000000' diff --git a/src/tempo/Decorator.ts b/src/tempo/Decorator.ts index 7d23718042..fc659029b5 100644 --- a/src/tempo/Decorator.ts +++ b/src/tempo/Decorator.ts @@ -277,31 +277,6 @@ export type Decorator< * @returns A function to unsubscribe from the event. */ watchBurn: (parameters: ammActions.watchBurn.Parameters) => () => void - /** - * Watches for fee swap events. - * - * @example - * ```ts - * import { createClient, http } from 'viem' - * import { tempo } from 'tempo.ts/chains' - * import { tempoActions } from 'tempo.ts/viem' - * - * const client = createClient({ - * chain: tempo({ feeToken: '0x20c0000000000000000000000000000000000001' }) - * transport: http(), - * }).extend(tempoActions()) - * - * const unwatch = client.amm.watchFeeSwap({ - * onFeeSwap: (args, log) => { - * console.log('Fee swap:', args) - * }, - * }) - * ``` - * - * @param parameters - Parameters. - * @returns A function to unsubscribe from the event. - */ - watchFeeSwap: (parameters: ammActions.watchFeeSwap.Parameters) => () => void /** * Watches for liquidity mint events. * @@ -470,6 +445,66 @@ export type Decorator< cancelSync: ( parameters: dexActions.cancelSync.Parameters, ) => Promise + /** + * Cancels a stale order from the orderbook. + * + * A stale order is one where the maker has been blacklisted by a TIP-403 policy. + * Anyone can cancel stale orders. + * + * @example + * ```ts + * import { createClient, http } from 'viem' + * import { privateKeyToAccount } from 'viem/accounts' + * import { tempo } from 'tempo.ts/chains' + * import { tempoActions } from 'tempo.ts/viem' + * + * const client = createClient({ + * account: privateKeyToAccount('0x...'), + * chain: tempo({ feeToken: '0x20c0000000000000000000000000000000000001' }) + * transport: http(), + * }).extend(tempoActions()) + * + * const hash = await client.dex.cancelStale({ + * orderId: 123n, + * }) + * ``` + * + * @param parameters - Parameters. + * @returns The transaction hash. + */ + cancelStale: ( + parameters: dexActions.cancelStale.Parameters, + ) => Promise + /** + * Cancels a stale order from the orderbook and waits for confirmation. + * + * A stale order is one where the maker has been blacklisted by a TIP-403 policy. + * Anyone can cancel stale orders. + * + * @example + * ```ts + * import { createClient, http } from 'viem' + * import { privateKeyToAccount } from 'viem/accounts' + * import { tempo } from 'tempo.ts/chains' + * import { tempoActions } from 'tempo.ts/viem' + * + * const client = createClient({ + * account: privateKeyToAccount('0x...'), + * chain: tempo({ feeToken: '0x20c0000000000000000000000000000000000001' }) + * transport: http(), + * }).extend(tempoActions()) + * + * const result = await client.dex.cancelStaleSync({ + * orderId: 123n, + * }) + * ``` + * + * @param parameters - Parameters. + * @returns The transaction receipt and event data. + */ + cancelStaleSync: ( + parameters: dexActions.cancelStaleSync.Parameters, + ) => Promise /** * Creates a new trading pair on the DEX. * @@ -1589,30 +1624,61 @@ export type Decorator< parameters: rewardActions.claimSync.Parameters, ) => Promise /** - * Gets the total reward per second rate for all active streams. + * Distributes rewards to opted-in token holders. + * + * @example + * ```ts + * import { createClient, http } from 'viem' + * import { privateKeyToAccount } from 'viem/accounts' + * import { tempo } from 'tempo.ts/chains' + * import { tempoActions } from 'tempo.ts/viem' + * + * const client = createClient({ + * account: privateKeyToAccount('0x...'), + * chain: tempo({ feeToken: '0x20c0000000000000000000000000000000000001' }) + * transport: http(), + * }).extend(tempoActions()) + * + * const hash = await client.reward.distribute({ + * amount: 100000000000000000000n, + * token: '0x20c0000000000000000000000000000000000001', + * }) + * ``` + * + * @param parameters - Parameters. + * @returns The transaction hash. + */ + distribute: ( + parameters: rewardActions.distribute.Parameters, + ) => Promise + /** + * Distributes rewards to opted-in token holders and waits for confirmation. * * @example * ```ts * import { createClient, http } from 'viem' + * import { privateKeyToAccount } from 'viem/accounts' * import { tempo } from 'tempo.ts/chains' * import { tempoActions } from 'tempo.ts/viem' * * const client = createClient({ + * account: privateKeyToAccount('0x...'), * chain: tempo({ feeToken: '0x20c0000000000000000000000000000000000001' }) * transport: http(), * }).extend(tempoActions()) * - * const rate = await client.reward.getTotalPerSecond({ + * const { funder, amount, receipt } = await client.reward.distributeSync({ + * amount: 100000000000000000000n, * token: '0x20c0000000000000000000000000000000000001', * }) * ``` * * @param parameters - Parameters. - * @returns The total reward per second (scaled by 1e18). + * @returns The funder, amount, and transaction receipt. */ - getTotalPerSecond: ( - parameters: rewardActions.getTotalPerSecond.Parameters, - ) => Promise + distributeSync: ( + parameters: rewardActions.distributeSync.Parameters, + ) => Promise /** * Gets the reward information for a specific account. * @@ -1696,63 +1762,33 @@ export type Decorator< parameters: rewardActions.setRecipientSync.Parameters, ) => Promise /** - * Starts a new reward stream that distributes tokens to opted-in holders. - * - * @example - * ```ts - * import { createClient, http } from 'viem' - * import { privateKeyToAccount } from 'viem/accounts' - * import { tempo } from 'tempo.ts/chains' - * import { tempoActions } from 'tempo.ts/viem' - * - * const client = createClient({ - * account: privateKeyToAccount('0x...'), - * chain: tempo({ feeToken: '0x20c0000000000000000000000000000000000001' }) - * transport: http(), - * }).extend(tempoActions()) - * - * const hash = await client.reward.start({ - * amount: 100000000000000000000n, - * seconds: 86400, - * token: '0x20c0000000000000000000000000000000000001', - * }) - * ``` - * - * @param parameters - Parameters. - * @returns The transaction hash. - */ - start: ( - parameters: rewardActions.start.Parameters, - ) => Promise - /** - * Starts a new reward stream that distributes tokens to opted-in holders and waits for confirmation. + * Watches for reward distributed events. * * @example * ```ts * import { createClient, http } from 'viem' - * import { privateKeyToAccount } from 'viem/accounts' * import { tempo } from 'tempo.ts/chains' * import { tempoActions } from 'tempo.ts/viem' * * const client = createClient({ - * account: privateKeyToAccount('0x...'), * chain: tempo({ feeToken: '0x20c0000000000000000000000000000000000001' }) * transport: http(), * }).extend(tempoActions()) * - * const result = await client.reward.startSync({ - * amount: 100000000000000000000n, - * seconds: 86400, + * const unwatch = client.reward.watchRewardDistributed({ * token: '0x20c0000000000000000000000000000000000001', + * onRewardDistributed: (args, log) => { + * console.log('Reward distributed:', args) + * }, * }) * ``` * * @param parameters - Parameters. - * @returns The transaction receipt and event data. + * @returns A function to unsubscribe from the event. */ - startSync: ( - parameters: rewardActions.startSync.Parameters, - ) => Promise + watchRewardDistributed: ( + parameters: rewardActions.watchRewardDistributed.Parameters, + ) => () => void /** * Watches for reward recipient set events. * @@ -1781,34 +1817,6 @@ export type Decorator< watchRewardRecipientSet: ( parameters: rewardActions.watchRewardRecipientSet.Parameters, ) => () => void - /** - * Watches for reward scheduled events. - * - * @example - * ```ts - * import { createClient, http } from 'viem' - * import { tempo } from 'tempo.ts/chains' - * import { tempoActions } from 'tempo.ts/viem' - * - * const client = createClient({ - * chain: tempo({ feeToken: '0x20c0000000000000000000000000000000000001' }) - * transport: http(), - * }).extend(tempoActions()) - * - * const unwatch = client.reward.watchRewardScheduled({ - * token: '0x20c0000000000000000000000000000000000001', - * onRewardScheduled: (args, log) => { - * console.log('Reward scheduled:', args) - * }, - * }) - * ``` - * - * @param parameters - Parameters. - * @returns A function to unsubscribe from the event. - */ - watchRewardScheduled: ( - parameters: rewardActions.watchRewardScheduled.Parameters, - ) => () => void } token: { /** @@ -2980,8 +2988,6 @@ export function decorator() { rebalanceSwapSync: (parameters) => ammActions.rebalanceSwapSync(client, parameters), watchBurn: (parameters) => ammActions.watchBurn(client, parameters), - watchFeeSwap: (parameters) => - ammActions.watchFeeSwap(client, parameters), watchMint: (parameters) => ammActions.watchMint(client, parameters), watchRebalanceSwap: (parameters) => ammActions.watchRebalanceSwap(client, parameters), @@ -2991,6 +2997,9 @@ export function decorator() { buySync: (parameters) => dexActions.buySync(client, parameters), cancel: (parameters) => dexActions.cancel(client, parameters), cancelSync: (parameters) => dexActions.cancelSync(client, parameters), + cancelStale: (parameters) => dexActions.cancelStale(client, parameters), + cancelStaleSync: (parameters) => + dexActions.cancelStaleSync(client, parameters), createPair: (parameters) => dexActions.createPair(client, parameters), createPairSync: (parameters) => dexActions.createPairSync(client, parameters), @@ -3065,20 +3074,20 @@ export function decorator() { reward: { claim: (parameters) => rewardActions.claim(client, parameters), claimSync: (parameters) => rewardActions.claimSync(client, parameters), - getTotalPerSecond: (parameters) => - rewardActions.getTotalPerSecond(client, parameters), + distribute: (parameters) => + rewardActions.distribute(client, parameters), + distributeSync: (parameters) => + rewardActions.distributeSync(client, parameters), getUserRewardInfo: (parameters) => rewardActions.getUserRewardInfo(client, parameters), setRecipient: (parameters) => rewardActions.setRecipient(client, parameters), setRecipientSync: (parameters) => rewardActions.setRecipientSync(client, parameters), - start: (parameters) => rewardActions.start(client, parameters), - startSync: (parameters) => rewardActions.startSync(client, parameters), + watchRewardDistributed: (parameters) => + rewardActions.watchRewardDistributed(client, parameters), watchRewardRecipientSet: (parameters) => rewardActions.watchRewardRecipientSet(client, parameters), - watchRewardScheduled: (parameters) => - rewardActions.watchRewardScheduled(client, parameters), }, token: { approve: (parameters) => tokenActions.approve(client, parameters), diff --git a/src/tempo/actions/amm.test.ts b/src/tempo/actions/amm.test.ts index 6c579f9d34..cafea507cd 100644 --- a/src/tempo/actions/amm.test.ts +++ b/src/tempo/actions/amm.test.ts @@ -92,8 +92,6 @@ describe('mint', () => { ) expect(mintReceipt).toBeDefined() - // amountUserToken should be 0 for single-sided mint - expect(mintResult.amountUserToken).toBe(0n) expect(mintResult.amountValidatorToken).toBe(parseUnits('50', 6)) expect(mintResult.liquidity).toBeGreaterThan(0n) @@ -321,11 +319,11 @@ describe('watchMint', () => { await setTimeout(1000) expect(eventArgs).toBeDefined() - expect(eventArgs.userToken.address.toLowerCase()).toBe(token.toLowerCase()) - expect(eventArgs.validatorToken.address.toLowerCase()).toBe( + expect(eventArgs.userToken.toLowerCase()).toBe(token.toLowerCase()) + expect(eventArgs.validatorToken.toLowerCase()).toBe( '0x20c0000000000000000000000000000000000001', ) - expect(eventArgs.validatorToken.amount).toBe(parseUnits('100', 6)) + expect(eventArgs.amountValidatorToken).toBe(parseUnits('100', 6)) unwatch() }) diff --git a/src/tempo/actions/amm.ts b/src/tempo/actions/amm.ts index d999410b15..f4c093a788 100644 --- a/src/tempo/actions/amm.ts +++ b/src/tempo/actions/amm.ts @@ -568,7 +568,7 @@ export namespace mint { return defineCall({ address: Addresses.feeManager, abi: Abis.feeAmm, - functionName: 'mintWithValidatorToken', + functionName: 'mint', args: [ TokenId.toAddress(userTokenAddress), TokenId.toAddress(validatorTokenAddress), @@ -958,86 +958,6 @@ export declare namespace watchRebalanceSwap { } } -/** - * Watches for fee swap events. - * - * @example - * ```ts - * import { createClient, http } from 'viem' - * import { tempo } from 'tempo.ts/chains' - * import { Actions } from 'tempo.ts/viem' - * - * const client = createClient({ - * chain: tempo({ feeToken: '0x20c0000000000000000000000000000000000001' }) - * transport: http(), - * }) - * - * const unwatch = actions.amm.watchFeeSwap(client, { - * onFeeSwap: (args, log) => { - * console.log('Fee swap:', args) - * }, - * }) - * ``` - * - * @param client - Client. - * @param parameters - Parameters. - * @returns A function to unsubscribe from the event. - */ -export function watchFeeSwap< - chain extends Chain | undefined, - account extends Account | undefined, ->( - client: Client, - parameters: watchFeeSwap.Parameters, -) { - const { onFeeSwap, userToken, validatorToken, ...rest } = parameters - return watchContractEvent(client, { - ...rest, - address: Addresses.feeManager, - abi: Abis.feeAmm, - eventName: 'FeeSwap', - args: - userToken !== undefined && validatorToken !== undefined - ? { - userToken: TokenId.toAddress(userToken), - validatorToken: TokenId.toAddress(validatorToken), - } - : undefined, - onLogs: (logs) => { - for (const log of logs) onFeeSwap(log.args, log) - }, - strict: true, - }) -} - -export declare namespace watchFeeSwap { - export type Args = GetEventArgs< - typeof Abis.feeAmm, - 'FeeSwap', - { IndexedOnly: false; Required: true } - > - - export type Log = viem_Log< - bigint, - number, - false, - ExtractAbiItem, - true - > - - export type Parameters = UnionOmit< - WatchContractEventParameters, - 'abi' | 'address' | 'batch' | 'eventName' | 'onLogs' | 'strict' - > & { - /** Callback to invoke when a fee swap occurs. */ - onFeeSwap: (args: Args, log: Log) => void - /** Address or ID of the user token to filter events. */ - userToken?: TokenId.TokenIdOrAddress | undefined - /** Address or ID of the validator token to filter events. */ - validatorToken?: TokenId.TokenIdOrAddress | undefined - } -} - /** * Watches for liquidity mint events. * @@ -1067,16 +987,14 @@ export function watchMint< chain extends Chain | undefined, account extends Account | undefined, >(client: Client, parameters: watchMint.Parameters) { - const { onMint, sender, userToken, validatorToken, ...rest } = parameters + const { onMint, to, userToken, validatorToken, ...rest } = parameters return watchContractEvent(client, { ...rest, address: Addresses.feeManager, abi: Abis.feeAmm, eventName: 'Mint', args: { - ...(sender !== undefined && { - sender: TokenId.toAddress(sender), - }), + to, ...(userToken !== undefined && { userToken: TokenId.toAddress(userToken), }), @@ -1085,40 +1003,18 @@ export function watchMint< }), }, onLogs: (logs) => { - for (const log of logs) - onMint( - { - liquidity: log.args.liquidity, - sender: log.args.sender, - userToken: { - address: log.args.userToken, - amount: log.args.amountUserToken, - }, - validatorToken: { - address: log.args.validatorToken, - amount: log.args.amountValidatorToken, - }, - }, - log, - ) + for (const log of logs) onMint(log.args, log) }, strict: true, }) } export declare namespace watchMint { - export type Args = { - liquidity: bigint - sender: Address - userToken: { - address: Address - amount: bigint - } - validatorToken: { - address: Address - amount: bigint - } - } + export type Args = GetEventArgs< + typeof Abis.feeAmm, + 'Mint', + { IndexedOnly: false; Required: true } + > export type Log = viem_Log< bigint, @@ -1134,8 +1030,10 @@ export declare namespace watchMint { > & { /** Callback to invoke when liquidity is added. */ onMint: (args: Args, log: Log) => void - /** Address or ID of the sender to filter events. */ - sender?: TokenId.TokenIdOrAddress | undefined + /** Address of the sender to filter events. */ + sender?: Address | undefined + /** Address of the recipient to filter events. */ + to?: Address | undefined /** Address or ID of the user token to filter events. */ userToken?: TokenId.TokenIdOrAddress | undefined /** Address or ID of the validator token to filter events. */ diff --git a/src/tempo/actions/dex.test.ts b/src/tempo/actions/dex.test.ts index cbc1ca69d5..df5d9c3be6 100644 --- a/src/tempo/actions/dex.test.ts +++ b/src/tempo/actions/dex.test.ts @@ -61,7 +61,7 @@ describe('buy', () => { tokenIn: quote, tokenOut: base, amountOut: parseUnits('100', 6), - maxAmountIn: parseUnits('50', 6), // Way too low for 1% premium + maxAmountIn: parseUnits('100', 6), // Way too low for 1% premium }), ).rejects.toThrow('The contract function "swapExactAmountOut" reverted') }) @@ -164,6 +164,67 @@ describe('cancel', () => { }) }) +describe('cancelStale', () => { + test('default', async () => { + const { base, quote } = await setupTokenPair(client) + + // Create a blacklist policy + const { policyId } = await Actions.policy.createSync(client, { + type: 'blacklist', + }) + + // Set the policy on the quote token (used for bid orders) + await Actions.token.changeTransferPolicySync(client, { + token: quote, + policyId, + }) + + // Place a bid order (escrows quote token) + const { orderId } = await Actions.dex.placeSync(client, { + token: base, + amount: parseUnits('100', 6), + type: 'buy', + tick: Tick.fromPrice('1.001'), + }) + + // Blacklist the maker (ourselves) + await Actions.policy.modifyBlacklistSync(client, { + policyId, + address: client.account.address, + restricted: true, + }) + + // Cancel the stale order + const { receipt, orderId: returnedOrderId } = + await Actions.dex.cancelStaleSync(client, { + orderId, + }) + + expect(receipt).toBeDefined() + expect(receipt.status).toBe('success') + expect(returnedOrderId).toBe(orderId) + }) + + test('behavior: cannot cancel non-stale order', async () => { + const { base } = await setupTokenPair(client) + + // Place a bid order + const { orderId } = await Actions.dex.placeSync(client, { + token: base, + amount: parseUnits('100', 6), + type: 'buy', + tick: Tick.fromPrice('1.001'), + }) + + // Try to cancel as stale - should fail because maker is not blacklisted + await expect( + Actions.dex.cancelStaleSync(client, { + orderId, + }), + ).rejects.toThrow('The contract function "cancelStaleOrder" reverted') + }) +}) + describe('createPair', () => { test('default', async () => { const { token: baseToken } = await Actions.token.createSync(client, { @@ -201,7 +262,7 @@ describe('getBalance', () => { // Place and cancel order to create internal balance const { orderId } = await Actions.dex.placeSync(client, { token: base, - amount: parseUnits('50', 6), + amount: parseUnits('100', 6), type: 'buy', tick: Tick.fromPrice('1.0005'), }) @@ -333,7 +394,7 @@ describe('getOrder', () => { // Place a flip order const { orderId } = await Actions.dex.placeFlipSync(client, { token: base, - amount: parseUnits('50', 6), + amount: parseUnits('100', 6), type: 'buy', tick: Tick.fromPrice('1.001'), flipTick: Tick.fromPrice('1.002'), @@ -348,7 +409,7 @@ describe('getOrder', () => { expect(order.maker).toBe(client.account.address) expect(order.isBid).toBe(true) expect(order.tick).toBe(Tick.fromPrice('1.001')) - expect(order.amount).toBe(parseUnits('50', 6)) + expect(order.amount).toBe(parseUnits('100', 6)) expect(order.isFlip).toBe(true) expect(order.flipTick).toBe(Tick.fromPrice('1.002')) }) @@ -414,7 +475,7 @@ describe('getOrder', () => { // Place second order at same tick const { orderId: orderId2 } = await Actions.dex.placeSync(client, { token: base, - amount: parseUnits('50', 6), + amount: parseUnits('100', 6), type: 'buy', tick, }) @@ -524,7 +585,7 @@ describe('getOrderbook', () => { // Place two bid orders at different ticks await Actions.dex.placeSync(client, { token: base, - amount: parseUnits('50', 6), + amount: parseUnits('100', 6), type: 'buy', tick: Tick.fromPrice('0.999'), }) @@ -652,7 +713,7 @@ describe('getTickLevel', () => { // Place second order at same tick const { orderId: orderId2 } = await Actions.dex.placeSync(client, { token: base, - amount: parseUnits('50', 6), + amount: parseUnits('100', 6), type: 'buy', tick, }) @@ -686,7 +747,7 @@ describe('getTickLevel', () => { // Place a sell order (ask) at same tick await Actions.dex.placeSync(client, { token: base, - amount: parseUnits('50', 6), + amount: parseUnits('100', 6), type: 'sell', tick, }) @@ -726,7 +787,7 @@ describe('getTickLevel', () => { await Actions.dex.placeSync(client, { token: base, - amount: parseUnits('50', 6), + amount: parseUnits('100', 6), type: 'buy', tick, }) @@ -799,7 +860,7 @@ describe('getTickLevel', () => { // Place order at min tick await Actions.dex.placeSync(client, { token: base, - amount: parseUnits('10', 6), + amount: parseUnits('100', 6), type: 'sell', tick: Tick.minTick, }) @@ -815,7 +876,7 @@ describe('getTickLevel', () => { // Place order at max tick await Actions.dex.placeSync(client, { token: base, - amount: parseUnits('10', 6), + amount: parseUnits('100', 6), type: 'buy', tick: Tick.maxTick, }) @@ -891,7 +952,9 @@ describe('place', () => { expect(result).toMatchInlineSnapshot(` { "amount": 100000000n, + "flipTick": 0, "isBid": false, + "isFlipOrder": false, "maker": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "tick": 100, } @@ -915,7 +978,9 @@ describe('place', () => { expect(result2).toMatchInlineSnapshot(` { "amount": 100000000n, + "flipTick": 0, "isBid": true, + "isFlipOrder": false, "maker": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "tick": 100, } @@ -930,7 +995,7 @@ describe('place', () => { client, { token: base, - amount: parseUnits('10', 6), + amount: parseUnits('100', 6), type: 'sell', tick: Tick.minTick, }, @@ -943,7 +1008,7 @@ describe('place', () => { client, { token: base, - amount: parseUnits('10', 6), + amount: parseUnits('100', 6), type: 'buy', tick: Tick.maxTick, }, @@ -959,7 +1024,7 @@ describe('place', () => { await expect( Actions.dex.placeSync(client, { token: base, - amount: parseUnits('10', 6), + amount: parseUnits('100', 6), type: 'buy', tick: Tick.maxTick + 1, }), @@ -969,7 +1034,7 @@ describe('place', () => { await expect( Actions.dex.placeSync(client, { token: base, - amount: parseUnits('10', 6), + amount: parseUnits('100', 6), type: 'sell', tick: Tick.minTick - 1, }), @@ -1033,7 +1098,7 @@ describe('place', () => { // Place second order at same tick const { orderId: orderId2 } = await Actions.dex.placeSync(client, { token: base, - amount: parseUnits('50', 6), + amount: parseUnits('100', 6), type: 'buy', tick, }) @@ -1067,6 +1132,7 @@ describe('placeFlip', () => { "amount": 100000000n, "flipTick": 200, "isBid": true, + "isFlipOrder": true, "maker": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "tick": 100, } @@ -1079,7 +1145,7 @@ describe('placeFlip', () => { // Valid: flipTick > tick for bid const { receipt: receipt1 } = await Actions.dex.placeFlipSync(client, { token: base, - amount: parseUnits('10', 6), + amount: parseUnits('100', 6), type: 'buy', tick: Tick.fromPrice('1.0005'), flipTick: Tick.fromPrice('1.001'), @@ -1090,7 +1156,7 @@ describe('placeFlip', () => { await expect( Actions.dex.placeFlipSync(client, { token: base, - amount: parseUnits('10', 6), + amount: parseUnits('100', 6), type: 'buy', tick: Tick.fromPrice('1.001'), flipTick: Tick.fromPrice('1.001'), // Equal @@ -1100,7 +1166,7 @@ describe('placeFlip', () => { await expect( Actions.dex.placeFlipSync(client, { token: base, - amount: parseUnits('10', 6), + amount: parseUnits('100', 6), type: 'buy', tick: Tick.fromPrice('1.001'), flipTick: Tick.fromPrice('1.0005'), // Less than @@ -1114,7 +1180,7 @@ describe('placeFlip', () => { // Valid: flipTick < tick for ask const { receipt: receipt1 } = await Actions.dex.placeFlipSync(client, { token: base, - amount: parseUnits('10', 6), + amount: parseUnits('100', 6), type: 'sell', tick: Tick.fromPrice('1.001'), flipTick: Tick.fromPrice('1.0005'), @@ -1125,7 +1191,7 @@ describe('placeFlip', () => { await expect( Actions.dex.placeFlipSync(client, { token: base, - amount: parseUnits('10', 6), + amount: parseUnits('100', 6), type: 'sell', tick: Tick.fromPrice('1.0005'), flipTick: Tick.fromPrice('1.0005'), // Equal @@ -1135,7 +1201,7 @@ describe('placeFlip', () => { await expect( Actions.dex.placeFlipSync(client, { token: base, - amount: parseUnits('10', 6), + amount: parseUnits('100', 6), type: 'sell', tick: Tick.fromPrice('1.0005'), flipTick: Tick.fromPrice('1.001'), // Greater than @@ -1149,7 +1215,7 @@ describe('placeFlip', () => { // Flip order with ticks at extreme boundaries const { receipt } = await Actions.dex.placeFlipSync(client, { token: base, - amount: parseUnits('10', 6), + amount: parseUnits('100', 6), type: 'buy', tick: Tick.minTick, flipTick: Tick.maxTick, @@ -1215,7 +1281,7 @@ describe('sell', () => { tokenIn: base, tokenOut: quote, amountIn: parseUnits('100', 6), - minAmountOut: parseUnits('50', 6), + minAmountOut: parseUnits('100', 6), }), ).rejects.toThrow('The contract function "swapExactAmountIn" reverted') }) @@ -1308,7 +1374,7 @@ describe('watchOrderCancelled', () => { const { orderId: orderId2 } = await Actions.dex.placeSync(client, { token: base, - amount: parseUnits('50', 6), + amount: parseUnits('100', 6), type: 'buy', tick: Tick.fromPrice('1.001'), }) @@ -1377,7 +1443,7 @@ describe('watchOrderPlaced', () => { // Place second order await Actions.dex.placeSync(client, { token: base, - amount: parseUnits('50', 6), + amount: parseUnits('100', 6), type: 'sell', tick: Tick.fromPrice('0.999'), }) @@ -1389,7 +1455,7 @@ describe('watchOrderPlaced', () => { expect(receivedOrders[0]?.args.isBid).toBe(true) expect(receivedOrders[0]?.args.amount).toBe(parseUnits('100', 6)) expect(receivedOrders[1]?.args.isBid).toBe(false) - expect(receivedOrders[1]?.args.amount).toBe(parseUnits('50', 6)) + expect(receivedOrders[1]?.args.amount).toBe(parseUnits('100', 6)) } finally { unwatch() } @@ -1424,7 +1490,7 @@ describe('watchOrderPlaced', () => { // Place order on base2 (should NOT be captured) await Actions.dex.placeSync(client, { token: base2, - amount: parseUnits('50', 6), + amount: parseUnits('100', 6), type: 'buy', tick: Tick.fromPrice('1.001'), }) diff --git a/src/tempo/actions/dex.ts b/src/tempo/actions/dex.ts index 12a1ea2822..3459a3ce7f 100644 --- a/src/tempo/actions/dex.ts +++ b/src/tempo/actions/dex.ts @@ -154,8 +154,8 @@ export namespace buy { export function call(args: Args) { const { tokenIn, tokenOut, amountOut, maxAmountIn } = args return defineCall({ - address: Addresses.stablecoinExchange, - abi: Abis.stablecoinExchange, + address: Addresses.stablecoinDex, + abi: Abis.stablecoinDex, functionName: 'swapExactAmountOut', args: [tokenIn, tokenOut, amountOut, maxAmountIn], }) @@ -325,8 +325,8 @@ export namespace cancel { export function call(args: Args) { const { orderId } = args return defineCall({ - address: Addresses.stablecoinExchange, - abi: Abis.stablecoinExchange, + address: Addresses.stablecoinDex, + abi: Abis.stablecoinDex, functionName: 'cancel', args: [orderId], }) @@ -340,7 +340,7 @@ export namespace cancel { */ export function extractEvent(logs: viem_Log[]) { const [log] = parseEventLogs({ - abi: Abis.stablecoinExchange, + abi: Abis.stablecoinDex, logs, eventName: 'OrderCancelled', strict: true, @@ -404,7 +404,208 @@ export namespace cancelSync { export type ReturnValue = Compute< GetEventArgs< - typeof Abis.stablecoinExchange, + typeof Abis.stablecoinDex, + 'OrderCancelled', + { IndexedOnly: false; Required: true } + > & { + /** Transaction receipt. */ + receipt: TransactionReceipt + } + > + + // TODO: exhaustive error type + export type ErrorType = BaseErrorType +} + +/** + * Cancels a stale order from the orderbook. + * + * A stale order is one where the owner's balance or allowance has dropped + * below the order amount. + * + * @example + * ```ts + * import { createClient, http } from 'viem' + * import { privateKeyToAccount } from 'viem/accounts' + * import { tempo } from 'tempo.ts/chains' + * import { Actions } from 'tempo.ts/viem' + * + * const client = createClient({ + * account: privateKeyToAccount('0x...'), + * chain: tempo({ feeToken: '0x20c0000000000000000000000000000000000001' }) + * transport: http(), + * }) + * + * const hash = await Actions.dex.cancelStale(client, { + * orderId: 123n, + * }) + * ``` + * + * @param client - Client. + * @param parameters - Parameters. + * @returns The transaction hash. + */ +export async function cancelStale< + chain extends Chain | undefined, + account extends Account | undefined, +>( + client: Client, + parameters: cancelStale.Parameters, +): Promise { + return cancelStale.inner(writeContract, client, parameters) +} + +export namespace cancelStale { + export type Parameters< + chain extends Chain | undefined = Chain | undefined, + account extends Account | undefined = Account | undefined, + > = WriteParameters & Args + + export type Args = { + /** Order ID to cancel. */ + orderId: bigint + } + + export type ReturnValue = WriteContractReturnType + + // TODO: exhaustive error type + export type ErrorType = BaseErrorType + + /** @internal */ + export async function inner< + action extends typeof writeContract | typeof writeContractSync, + chain extends Chain | undefined, + account extends Account | undefined, + >( + action: action, + client: Client, + parameters: cancelStale.Parameters, + ): Promise> { + const { orderId, ...rest } = parameters + const call = cancelStale.call({ orderId }) + return (await action(client, { + ...rest, + ...call, + } as never)) as never + } + + /** + * Defines a call to the `cancelStaleOrder` function. + * + * Can be passed as a parameter to: + * - [`estimateContractGas`](https://viem.sh/docs/contract/estimateContractGas): estimate the gas cost of the call + * - [`simulateContract`](https://viem.sh/docs/contract/simulateContract): simulate the call + * - [`sendCalls`](https://viem.sh/docs/actions/wallet/sendCalls): send multiple calls + * + * @example + * ```ts + * import { createClient, http, walletActions } from 'viem' + * import { tempo } from 'tempo.ts/chains' + * import { Actions } from 'tempo.ts/viem' + * + * const client = createClient({ + * chain: tempo({ feeToken: '0x20c0000000000000000000000000000000000001' }) + * transport: http(), + * }).extend(walletActions) + * + * const { result } = await client.sendCalls({ + * calls: [ + * Actions.dex.cancelStale.call({ + * orderId: 123n, + * }), + * ] + * }) + * ``` + * + * @param args - Arguments. + * @returns The call. + */ + export function call(args: Args) { + const { orderId } = args + return defineCall({ + address: Addresses.stablecoinDex, + abi: Abis.stablecoinDex, + functionName: 'cancelStaleOrder', + args: [orderId], + }) + } + + /** + * Extracts the `OrderCancelled` event from logs. + * + * @param logs - The logs. + * @returns The `OrderCancelled` event. + */ + export function extractEvent(logs: viem_Log[]) { + const [log] = parseEventLogs({ + abi: Abis.stablecoinDex, + logs, + eventName: 'OrderCancelled', + strict: true, + }) + if (!log) throw new Error('`OrderCancelled` event not found.') + return log + } +} + +/** + * Cancels a stale order from the orderbook and waits for confirmation. + * + * A stale order is one where the owner's balance or allowance has dropped + * below the order amount. + * + * @example + * ```ts + * import { createClient, http } from 'viem' + * import { privateKeyToAccount } from 'viem/accounts' + * import { tempo } from 'tempo.ts/chains' + * import { Actions } from 'tempo.ts/viem' + * + * const client = createClient({ + * account: privateKeyToAccount('0x...'), + * chain: tempo({ feeToken: '0x20c0000000000000000000000000000000000001' }) + * transport: http(), + * }) + * + * const result = await Actions.dex.cancelStaleSync(client, { + * orderId: 123n, + * }) + * ``` + * + * @param client - Client. + * @param parameters - Parameters. + * @returns The transaction receipt and event data. + */ +export async function cancelStaleSync< + chain extends Chain | undefined, + account extends Account | undefined, +>( + client: Client, + parameters: cancelStaleSync.Parameters, +): Promise { + const { throwOnReceiptRevert = true, ...rest } = parameters + const receipt = await cancelStale.inner(writeContractSync, client, { + ...rest, + throwOnReceiptRevert, + } as never) + const { args } = cancelStale.extractEvent(receipt.logs) + return { + ...args, + receipt, + } as never +} + +export namespace cancelStaleSync { + export type Parameters< + chain extends Chain | undefined = Chain | undefined, + account extends Account | undefined = Account | undefined, + > = cancelStale.Parameters + + export type Args = cancelStale.Args + + export type ReturnValue = Compute< + GetEventArgs< + typeof Abis.stablecoinDex, 'OrderCancelled', { IndexedOnly: false; Required: true } > & { @@ -520,8 +721,8 @@ export namespace createPair { export function call(args: Args) { const { base } = args return defineCall({ - address: Addresses.stablecoinExchange, - abi: Abis.stablecoinExchange, + address: Addresses.stablecoinDex, + abi: Abis.stablecoinDex, functionName: 'createPair', args: [base], }) @@ -535,7 +736,7 @@ export namespace createPair { */ export function extractEvent(logs: viem_Log[]) { const [log] = parseEventLogs({ - abi: Abis.stablecoinExchange, + abi: Abis.stablecoinDex, logs, eventName: 'PairCreated', strict: true, @@ -599,7 +800,7 @@ export namespace createPairSync { export type ReturnValue = Compute< GetEventArgs< - typeof Abis.stablecoinExchange, + typeof Abis.stablecoinDex, 'PairCreated', { IndexedOnly: false; Required: true } > & { @@ -665,7 +866,7 @@ export namespace getBalance { } export type ReturnValue = ReadContractReturnType< - typeof Abis.stablecoinExchange, + typeof Abis.stablecoinDex, 'balanceOf', never > @@ -679,8 +880,8 @@ export namespace getBalance { export function call(args: Args) { const { account, token } = args return defineCall({ - address: Addresses.stablecoinExchange, - abi: Abis.stablecoinExchange, + address: Addresses.stablecoinDex, + abi: Abis.stablecoinDex, args: [account, token], functionName: 'balanceOf', }) @@ -736,7 +937,7 @@ export namespace getBuyQuote { } export type ReturnValue = ReadContractReturnType< - typeof Abis.stablecoinExchange, + typeof Abis.stablecoinDex, 'quoteSwapExactAmountOut', never > @@ -750,8 +951,8 @@ export namespace getBuyQuote { export function call(args: Args) { const { tokenIn, tokenOut, amountOut } = args return defineCall({ - address: Addresses.stablecoinExchange, - abi: Abis.stablecoinExchange, + address: Addresses.stablecoinDex, + abi: Abis.stablecoinDex, args: [tokenIn, tokenOut, amountOut], functionName: 'quoteSwapExactAmountOut', }) @@ -801,7 +1002,7 @@ export namespace getOrder { } export type ReturnValue = ReadContractReturnType< - typeof Abis.stablecoinExchange, + typeof Abis.stablecoinDex, 'getOrder', never > @@ -815,8 +1016,8 @@ export namespace getOrder { export function call(args: Args) { const { orderId } = args return defineCall({ - address: Addresses.stablecoinExchange, - abi: Abis.stablecoinExchange, + address: Addresses.stablecoinDex, + abi: Abis.stablecoinDex, args: [orderId], functionName: 'getOrder', }) @@ -869,7 +1070,7 @@ export namespace getOrderbook { } export type ReturnValue = ReadContractReturnType< - typeof Abis.stablecoinExchange, + typeof Abis.stablecoinDex, 'books', never > @@ -884,8 +1085,8 @@ export namespace getOrderbook { const { base, quote } = args const pairKey = getPairKey(base, quote) return defineCall({ - address: Addresses.stablecoinExchange, - abi: Abis.stablecoinExchange, + address: Addresses.stablecoinDex, + abi: Abis.stablecoinDex, args: [pairKey], functionName: 'books', }) @@ -959,8 +1160,8 @@ export namespace getTickLevel { export function call(args: Args) { const { base, tick, isBid } = args return defineCall({ - address: Addresses.stablecoinExchange, - abi: Abis.stablecoinExchange, + address: Addresses.stablecoinDex, + abi: Abis.stablecoinDex, args: [base, tick, isBid], functionName: 'getTickLevel', }) @@ -1016,7 +1217,7 @@ export namespace getSellQuote { } export type ReturnValue = ReadContractReturnType< - typeof Abis.stablecoinExchange, + typeof Abis.stablecoinDex, 'quoteSwapExactAmountIn', never > @@ -1030,8 +1231,8 @@ export namespace getSellQuote { export function call(args: Args) { const { tokenIn, tokenOut, amountIn } = args return defineCall({ - address: Addresses.stablecoinExchange, - abi: Abis.stablecoinExchange, + address: Addresses.stablecoinDex, + abi: Abis.stablecoinDex, args: [tokenIn, tokenOut, amountIn], functionName: 'quoteSwapExactAmountIn', }) @@ -1154,8 +1355,8 @@ export namespace place { const { token, amount, type, tick } = args const isBid = type === 'buy' return defineCall({ - address: Addresses.stablecoinExchange, - abi: Abis.stablecoinExchange, + address: Addresses.stablecoinDex, + abi: Abis.stablecoinDex, functionName: 'place', args: [token, amount, isBid, tick], }) @@ -1169,7 +1370,7 @@ export namespace place { */ export function extractEvent(logs: viem_Log[]) { const [log] = parseEventLogs({ - abi: Abis.stablecoinExchange, + abi: Abis.stablecoinDex, logs, eventName: 'OrderPlaced', strict: true, @@ -1299,27 +1500,28 @@ export namespace placeFlip { const { token, amount, type, tick, flipTick } = args const isBid = type === 'buy' return defineCall({ - address: Addresses.stablecoinExchange, - abi: Abis.stablecoinExchange, + address: Addresses.stablecoinDex, + abi: Abis.stablecoinDex, functionName: 'placeFlip', args: [token, amount, isBid, tick, flipTick], }) } /** - * Extracts the `FlipOrderPlaced` event from logs. + * Extracts the `OrderPlaced` event (with `isFlipOrder: true`) from logs. * * @param logs - The logs. - * @returns The `FlipOrderPlaced` event. + * @returns The `OrderPlaced` event for a flip order. */ export function extractEvent(logs: viem_Log[]) { - const [log] = parseEventLogs({ - abi: Abis.stablecoinExchange, + const parsedLogs = parseEventLogs({ + abi: Abis.stablecoinDex, logs, - eventName: 'FlipOrderPlaced', + eventName: 'OrderPlaced', strict: true, }) - if (!log) throw new Error('`FlipOrderPlaced` event not found.') + const log = parsedLogs.find((l) => l.args.isFlipOrder) + if (!log) throw new Error('`OrderPlaced` event (flip order) not found.') return log } } @@ -1382,8 +1584,8 @@ export namespace placeFlipSync { export type ReturnValue = Compute< GetEventArgs< - typeof Abis.stablecoinExchange, - 'FlipOrderPlaced', + typeof Abis.stablecoinDex, + 'OrderPlaced', { IndexedOnly: false; Required: true } > & { /** Transaction receipt. */ @@ -1452,7 +1654,7 @@ export namespace placeSync { export type ReturnValue = Compute< GetEventArgs< - typeof Abis.stablecoinExchange, + typeof Abis.stablecoinDex, 'OrderPlaced', { IndexedOnly: false; Required: true } > & { @@ -1580,8 +1782,8 @@ export namespace sell { export function call(args: Args) { const { tokenIn, tokenOut, amountIn, minAmountOut } = args return defineCall({ - address: Addresses.stablecoinExchange, - abi: Abis.stablecoinExchange, + address: Addresses.stablecoinDex, + abi: Abis.stablecoinDex, functionName: 'swapExactAmountIn', args: [tokenIn, tokenOut, amountIn, minAmountOut], }) @@ -1683,15 +1885,17 @@ export function watchFlipOrderPlaced< const { onFlipOrderPlaced, maker, token, ...rest } = parameters return watchContractEvent(client, { ...rest, - address: Addresses.stablecoinExchange, - abi: Abis.stablecoinExchange, - eventName: 'FlipOrderPlaced', + address: Addresses.stablecoinDex, + abi: Abis.stablecoinDex, + eventName: 'OrderPlaced', args: { ...(maker !== undefined && { maker }), ...(token !== undefined && { token }), }, onLogs: (logs) => { - for (const log of logs) onFlipOrderPlaced(log.args, log) + for (const log of logs) { + if (log.args.isFlipOrder) onFlipOrderPlaced(log.args, log) + } }, strict: true, }) @@ -1699,8 +1903,8 @@ export function watchFlipOrderPlaced< export declare namespace watchFlipOrderPlaced { export type Args = GetEventArgs< - typeof Abis.stablecoinExchange, - 'FlipOrderPlaced', + typeof Abis.stablecoinDex, + 'OrderPlaced', { IndexedOnly: false; Required: true } > @@ -1708,14 +1912,14 @@ export declare namespace watchFlipOrderPlaced { bigint, number, false, - ExtractAbiItem, + ExtractAbiItem, true > export type Parameters = UnionOmit< WatchContractEventParameters< - typeof Abis.stablecoinExchange, - 'FlipOrderPlaced', + typeof Abis.stablecoinDex, + 'OrderPlaced', true >, 'abi' | 'address' | 'batch' | 'eventName' | 'onLogs' | 'strict' @@ -1764,8 +1968,8 @@ export function watchOrderCancelled< const { onOrderCancelled, orderId, ...rest } = parameters return watchContractEvent(client, { ...rest, - address: Addresses.stablecoinExchange, - abi: Abis.stablecoinExchange, + address: Addresses.stablecoinDex, + abi: Abis.stablecoinDex, eventName: 'OrderCancelled', args: orderId !== undefined ? { orderId } : undefined, onLogs: (logs) => { @@ -1777,7 +1981,7 @@ export function watchOrderCancelled< export declare namespace watchOrderCancelled { export type Args = GetEventArgs< - typeof Abis.stablecoinExchange, + typeof Abis.stablecoinDex, 'OrderCancelled', { IndexedOnly: false; Required: true } > @@ -1786,13 +1990,13 @@ export declare namespace watchOrderCancelled { bigint, number, false, - ExtractAbiItem, + ExtractAbiItem, true > export type Parameters = UnionOmit< WatchContractEventParameters< - typeof Abis.stablecoinExchange, + typeof Abis.stablecoinDex, 'OrderCancelled', true >, @@ -1840,8 +2044,8 @@ export function watchOrderFilled< const { onOrderFilled, maker, taker, orderId, ...rest } = parameters return watchContractEvent(client, { ...rest, - address: Addresses.stablecoinExchange, - abi: Abis.stablecoinExchange, + address: Addresses.stablecoinDex, + abi: Abis.stablecoinDex, eventName: 'OrderFilled', args: { ...(orderId !== undefined && { orderId }), @@ -1857,7 +2061,7 @@ export function watchOrderFilled< export declare namespace watchOrderFilled { export type Args = GetEventArgs< - typeof Abis.stablecoinExchange, + typeof Abis.stablecoinDex, 'OrderFilled', { IndexedOnly: false; Required: true } > @@ -1866,13 +2070,13 @@ export declare namespace watchOrderFilled { bigint, number, false, - ExtractAbiItem, + ExtractAbiItem, true > export type Parameters = UnionOmit< WatchContractEventParameters< - typeof Abis.stablecoinExchange, + typeof Abis.stablecoinDex, 'OrderFilled', true >, @@ -1924,8 +2128,8 @@ export function watchOrderPlaced< const { onOrderPlaced, maker, token, ...rest } = parameters return watchContractEvent(client, { ...rest, - address: Addresses.stablecoinExchange, - abi: Abis.stablecoinExchange, + address: Addresses.stablecoinDex, + abi: Abis.stablecoinDex, eventName: 'OrderPlaced', args: { ...(maker !== undefined && { maker }), @@ -1940,7 +2144,7 @@ export function watchOrderPlaced< export declare namespace watchOrderPlaced { export type Args = GetEventArgs< - typeof Abis.stablecoinExchange, + typeof Abis.stablecoinDex, 'OrderPlaced', { IndexedOnly: false; Required: true } > @@ -1949,13 +2153,13 @@ export declare namespace watchOrderPlaced { bigint, number, false, - ExtractAbiItem, + ExtractAbiItem, true > export type Parameters = UnionOmit< WatchContractEventParameters< - typeof Abis.stablecoinExchange, + typeof Abis.stablecoinDex, 'OrderPlaced', true >, @@ -2077,8 +2281,8 @@ export namespace withdraw { export function call(args: Args) { const { token, amount } = args return defineCall({ - address: Addresses.stablecoinExchange, - abi: Abis.stablecoinExchange, + address: Addresses.stablecoinDex, + abi: Abis.stablecoinDex, functionName: 'withdraw', args: [token, amount], }) @@ -2144,7 +2348,5 @@ export namespace withdrawSync { } function getPairKey(base: Address, quote: Address) { - const [tokenA, tokenB] = - Hex.toBigInt(base) < Hex.toBigInt(quote) ? [base, quote] : [quote, base] - return Hash.keccak256(Hex.concat(tokenA, tokenB)) + return Hash.keccak256(Hex.concat(base, quote)) } diff --git a/src/tempo/actions/fee.test.ts b/src/tempo/actions/fee.test.ts index 8952aba721..82b24b1a2b 100644 --- a/src/tempo/actions/fee.test.ts +++ b/src/tempo/actions/fee.test.ts @@ -4,7 +4,7 @@ import { writeContractSync } from 'viem/actions' import { Abis } from 'viem/tempo' import { afterEach, describe, expect, test } from 'vitest' import { accounts, fundAddress, getClient } from '~test/tempo/config.js' -import { rpcUrl } from '~test/tempo/prool.js' +import * as Prool from '~test/tempo/prool.js' import * as actions from './index.js' const account = accounts[0] @@ -16,7 +16,7 @@ const client = getClient({ }) afterEach(async () => { - await fetch(`${rpcUrl}/restart`) + await Prool.restart(client) }) describe('getUserToken', () => { diff --git a/src/tempo/actions/nonce.test.ts b/src/tempo/actions/nonce.test.ts index 64c19d2155..ab70a44903 100644 --- a/src/tempo/actions/nonce.test.ts +++ b/src/tempo/actions/nonce.test.ts @@ -1,7 +1,7 @@ import { setTimeout } from 'node:timers/promises' import { beforeEach, describe, expect, test } from 'vitest' import { accounts, getClient } from '~test/tempo/config.js' -import { rpcUrl } from '~test/tempo/prool.js' +import * as Prool from '~test/tempo/prool.js' import * as actions from './index.js' const account = accounts[0] @@ -12,7 +12,7 @@ const client = getClient({ }) beforeEach(async () => { - await fetch(`${rpcUrl}/restart`) + await Prool.restart(client) }) describe('getNonce', () => { diff --git a/src/tempo/actions/nonce.ts b/src/tempo/actions/nonce.ts index af67831b0b..0b70433782 100644 --- a/src/tempo/actions/nonce.ts +++ b/src/tempo/actions/nonce.ts @@ -110,81 +110,6 @@ export namespace getNonce { } } -/** - * @deprecated This function has been deprecated post-AllegroModerato. It will be removed in a future version. - */ -export async function getNonceKeyCount< - chain extends Chain | undefined, - account extends Account | undefined, ->( - client: Client, - parameters: getNonceKeyCount.Parameters, -): Promise { - const { account, ...rest } = parameters - return readContract(client, { - ...rest, - ...getNonceKeyCount.call({ account }), - }) -} - -export namespace getNonceKeyCount { - export type Parameters = ReadParameters & Args - - export type Args = { - /** Account address. */ - account: Address - } - - export type ReturnValue = ReadContractReturnType< - typeof Abis.nonce, - 'getActiveNonceKeyCount', - never - > - - /** - * Defines a call to the `getNonceKeyCount` function. - * - * Can be passed as a parameter to: - * - [`estimateContractGas`](https://viem.sh/docs/contract/estimateContractGas): estimate the gas cost of the call - * - [`simulateContract`](https://viem.sh/docs/contract/simulateContract): simulate the call - * - [`multicall`](https://viem.sh/docs/contract/multicall): execute multiple calls in parallel - * - * @example - * ```ts - * import { createClient, http } from 'viem' - * import { tempo } from 'tempo.ts/chains' - * import { Actions } from 'tempo.ts/viem' - * - * const client = createClient({ - * chain: tempo({ feeToken: '0x20c0000000000000000000000000000000000001' }), - * transport: http(), - * }) - * - * const result = await client.multicall({ - * contracts: [ - * Actions.nonce.getNonceKeyCount.call({ account: '0x...' }), - * Actions.nonce.getNonceKeyCount.call({ account: '0x...' }), - * ], - * }) - * ``` - * - * @param args - Arguments. - * @returns The call. - */ - export function call(args: Args) { - const { account } = args - return defineCall({ - address: Addresses.nonceManager, - abi: Abis.nonce, - args: [account], - functionName: 'getActiveNonceKeyCount', - }) - } -} - -/** - * @deprecated This function has been deprecated post-AllegroModerato. It will be removed in a future version. - */ export function watchNonceIncremented< chain extends Chain | undefined, account extends Account | undefined, @@ -228,76 +153,3 @@ export declare namespace watchNonceIncremented { onNonceIncremented: (args: Args, log: Log) => void } } - -/** - * Watches for active key count changed events. - * - * @example - * ```ts - * import { createClient, http } from 'viem' - * import { tempo } from 'tempo.ts/chains' - * import { Actions } from 'tempo.ts/viem' - * - * const client = createClient({ - * chain: tempo({ feeToken: '0x20c0000000000000000000000000000000000001' }), - * transport: http(), - * }) - * - * const unwatch = Actions.nonce.watchActiveKeyCountChanged(client, { - * onActiveKeyCountChanged: (args, log) => { - * console.log('Active key count changed:', args) - * }, - * }) - * ``` - * - * @param client - Client. - * @param parameters - Parameters. - * @returns A function to unsubscribe from the event. - */ -export function watchActiveKeyCountChanged< - chain extends Chain | undefined, - account extends Account | undefined, ->( - client: Client, - parameters: watchActiveKeyCountChanged.Parameters, -) { - const { onActiveKeyCountChanged, ...rest } = parameters - return watchContractEvent(client, { - ...rest, - address: Addresses.nonceManager, - abi: Abis.nonce, - eventName: 'ActiveKeyCountChanged', - onLogs: (logs) => { - for (const log of logs) onActiveKeyCountChanged(log.args, log) - }, - strict: true, - }) -} - -export declare namespace watchActiveKeyCountChanged { - export type Args = GetEventArgs< - typeof Abis.nonce, - 'ActiveKeyCountChanged', - { IndexedOnly: false; Required: true } - > - - export type Log = viem_Log< - bigint, - number, - false, - ExtractAbiItem, - true - > - - export type Parameters = UnionOmit< - WatchContractEventParameters< - typeof Abis.nonce, - 'ActiveKeyCountChanged', - true - >, - 'abi' | 'address' | 'batch' | 'eventName' | 'onLogs' | 'strict' - > & { - /** Callback to invoke when the active key count changes. */ - onActiveKeyCountChanged: (args: Args, log: Log) => void - } -} diff --git a/src/tempo/actions/policy.test.ts b/src/tempo/actions/policy.test.ts index 352ae8a15c..993ef23bb2 100644 --- a/src/tempo/actions/policy.test.ts +++ b/src/tempo/actions/policy.test.ts @@ -1,7 +1,7 @@ import { setTimeout } from 'node:timers/promises' import { beforeAll, describe, expect, test } from 'vitest' import { accounts, getClient } from '~test/tempo/config.js' -import { rpcUrl } from '~test/tempo/prool.js' +import * as Prool from '~test/tempo/prool.js' import * as actions from './index.js' const account = accounts[0] @@ -393,7 +393,7 @@ describe('watchCreate', () => { describe('watchAdminUpdated', () => { beforeAll(async () => { - await fetch(`${rpcUrl}/restart`) + await Prool.restart(client) }) test('default', async () => { diff --git a/src/tempo/actions/reward.test.ts b/src/tempo/actions/reward.test.ts index 7ef8085645..8c899b27e3 100644 --- a/src/tempo/actions/reward.test.ts +++ b/src/tempo/actions/reward.test.ts @@ -30,8 +30,8 @@ describe('claimSync', () => { token, }) - // Start immediate reward to distribute rewards - await actions.reward.startSync(client, { + // Distribute rewards + await actions.reward.distributeSync(client, { amount: rewardAmount, token, }) @@ -56,22 +56,83 @@ describe('claimSync', () => { balanceBefore + rewardAmount - parseUnits('1', 6), ) }) +}) - test('behavior: claiming from streaming reward', async () => { +describe('distributeSync', () => { + test('default', async () => { const { token } = await setupToken(client) - const balanceBefore = await actions.token.getBalance(client, { + // Opt in to rewards + await actions.reward.setRecipientSync(client, { + recipient: account.address, token, }) - // Mint tokens to have balance - const mintAmount = parseUnits('1000', 6) + const balanceBeforeReward = await actions.token.getBalance(client, { + token, + }) + + // Mint reward tokens + const rewardAmount = parseUnits('100', 6) await actions.token.mintSync(client, { - amount: mintAmount, + amount: rewardAmount, to: account.address, token, }) + // Distribute rewards + const { amount, funder, receipt } = await actions.reward.distributeSync( + client, + { + amount: rewardAmount, + token, + }, + ) + + expect(receipt).toBeDefined() + expect(funder).toBe(account.address) + expect(amount).toBe(rewardAmount) + + // Trigger reward distribution by transferring + await actions.token.transferSync(client, { + amount: 1n, + to: '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', + token, + }) + + // Claim the accumulated rewards + await actions.reward.claimSync(client, { + token, + }) + + const balanceAfter = await actions.token.getBalance(client, { + token, + }) + + // Account should have received rewards + expect(balanceAfter).toBeGreaterThanOrEqual( + balanceBeforeReward + rewardAmount - 1n, + ) + }) +}) + +describe('getGlobalRewardPerToken', () => { + test('default', async () => { + const { token } = await setupToken(client) + + const rewardPerToken = await actions.reward.getGlobalRewardPerToken( + client, + { + token, + }, + ) + + expect(rewardPerToken).toBe(0n) + }) + + test('behavior: after distribution', async () => { + const { token } = await setupToken(client) + // Opt in to rewards await actions.reward.setRecipientSync(client, { recipient: account.address, @@ -86,43 +147,64 @@ describe('claimSync', () => { token, }) - // Start a streaming reward (not immediate) - await actions.reward.startSync(client, { + // Distribute rewards + await actions.reward.distributeSync(client, { amount: rewardAmount, token, }) - // Wait a bit and trigger accrual by transferring - await new Promise((resolve) => setTimeout(resolve, 2000)) - await actions.token.transferSync(client, { - amount: 1n, - to: '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', + const rewardPerToken = await actions.reward.getGlobalRewardPerToken( + client, + { + token, + }, + ) + + expect(rewardPerToken).toBeGreaterThan(0n) + }) +}) + +describe('getPendingRewards', () => { + test('default', async () => { + const { token } = await setupToken(client) + + const pending = await actions.reward.getPendingRewards(client, { token, + account: account.address, }) - // Claim accumulated rewards from the stream - await actions.reward.claimSync(client, { + expect(pending).toBe(0n) + }) + + test('behavior: after distribution', async () => { + const { token } = await setupToken(client) + + // Opt in to rewards + await actions.reward.setRecipientSync(client, { + recipient: account.address, token, }) - const balanceAfter = await actions.token.getBalance(client, { + // Mint reward tokens + const rewardAmount = parseUnits('100', 6) + await actions.token.mintSync(client, { + amount: rewardAmount, + to: account.address, token, }) - // Should have accumulated some rewards (at least 10% of total after 2 seconds) - expect(balanceAfter).toBeGreaterThan(balanceBefore + rewardAmount / 10n) - }) -}) - -describe('getTotalPerSecond', () => { - test('default', async () => { - const { token } = await setupToken(client) + // Distribute rewards + await actions.reward.distributeSync(client, { + amount: rewardAmount, + token, + }) - const rate = await actions.reward.getTotalPerSecond(client, { + const pending = await actions.reward.getPendingRewards(client, { token, + account: account.address, }) - expect(rate).toBe(0n) + expect(pending).toBeGreaterThan(0n) }) }) @@ -181,8 +263,8 @@ describe('getUserRewardInfo', () => { token, }) - // Start immediate reward to distribute rewards - await actions.reward.startSync(client, { + // Distribute rewards + await actions.reward.distributeSync(client, { amount: rewardAmount, token, }) @@ -249,102 +331,7 @@ describe('setRecipientSync', () => { }) }) -describe('startSync', () => { - test('behavior: immediate distribution (seconds = 0)', async () => { - const { token } = await setupToken(client) - - // Opt in to rewards - await actions.reward.setRecipientSync(client, { - recipient: account.address, - token, - }) - - const balanceBeforeReward = await actions.token.getBalance(client, { - token, - }) - - // Mint reward tokens - const rewardAmount = parseUnits('100', 6) - await actions.token.mintSync(client, { - amount: rewardAmount, - to: account.address, - token, - }) - - // Start immediate reward - const { id } = await actions.reward.startSync(client, { - amount: rewardAmount, - token, - }) - - expect(id).toBe(0n) // Immediate distributions return ID 0 - - // Trigger reward distribution by transferring - await actions.token.transferSync(client, { - amount: 1n, - to: '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', - token, - }) - - // Claim the accumulated rewards - await actions.reward.claimSync(client, { - token, - }) - - const balanceAfter = await actions.token.getBalance(client, { - token, - }) - - // Account should have received rewards - expect(balanceAfter).toBeGreaterThanOrEqual( - balanceBeforeReward + rewardAmount - 1n, - ) - - // Total reward per second should be zero for immediate distributions - const totalRate = await actions.reward.getTotalPerSecond(client, { - token, - }) - expect(totalRate).toBe(0n) - }) - - test('behavior: immediate distribution with opted-in holders', async () => { - const { token } = await setupToken(client) - - // Opt in to rewards - await actions.reward.setRecipientSync(client, { - recipient: account.address, - token, - }) - - // Mint reward tokens - const rewardAmount = parseUnits('100', 6) - await actions.token.mintSync(client, { - amount: rewardAmount, - to: account.address, - token, - }) - - // Start immediate reward - const { amount, durationSeconds, funder, id } = - await actions.reward.startSync(client, { - amount: rewardAmount, - token, - }) - - expect(id).toBe(0n) // Immediate distributions return ID 0 - expect(funder).toBe(account.address) - expect(amount).toBe(rewardAmount) - expect(durationSeconds).toBe(0) - - // Total reward per second should be zero for immediate distributions - const totalRate = await actions.reward.getTotalPerSecond(client, { - token, - }) - expect(totalRate).toBe(0n) - }) -}) - -describe('watchRewardScheduled', () => { +describe('watchRewardDistributed', () => { test('default', async () => { const { token } = await setupToken(client) @@ -363,19 +350,19 @@ describe('watchRewardScheduled', () => { }) const events: Array<{ - args: actions.reward.watchRewardScheduled.Args - log: actions.reward.watchRewardScheduled.Log + args: actions.reward.watchRewardDistributed.Args + log: actions.reward.watchRewardDistributed.Log }> = [] - const unwatch = actions.reward.watchRewardScheduled(client, { + const unwatch = actions.reward.watchRewardDistributed(client, { token, - onRewardScheduled: (args, log) => { + onRewardDistributed: (args, log) => { events.push({ args, log }) }, }) try { - await actions.reward.startSync(client, { + await actions.reward.distributeSync(client, { amount: rewardAmount, token, }) @@ -385,7 +372,6 @@ describe('watchRewardScheduled', () => { expect(events.length).toBeGreaterThan(0) expect(events[0]?.args.amount).toBe(rewardAmount) expect(events[0]?.args.funder).toBe(account.address) - expect(events[0]?.args.durationSeconds).toBe(0) expect(events[0]?.log).toBeDefined() } finally { if (unwatch) unwatch() diff --git a/src/tempo/actions/reward.ts b/src/tempo/actions/reward.ts index f849195845..b3a5ddf8d8 100644 --- a/src/tempo/actions/reward.ts +++ b/src/tempo/actions/reward.ts @@ -216,43 +216,251 @@ export namespace claimSync { } /** - * Gets the total reward per second rate for all active streams. + * Distributes rewards to opted-in token holders. * - * 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. - * The rate decreases when streams end (via `finalizeStreams`) or are canceled. + * This function transfers `amount` of tokens from the caller into the token contract's + * reward pool and immediately distributes them to current opted-in holders by increasing + * `globalRewardPerToken`. + * + * Notes: + * - Reverts with `InvalidAmount` if `amount == 0`. + * - The transfer from caller to pool is subject to TIP-403 policy checks. * * @example * ```ts * import { createClient, http } from 'viem' * import { tempo } from 'tempo.ts/chains' * import { Actions } from 'tempo.ts/viem' + * import { privateKeyToAccount } from 'viem/accounts' * * const client = createClient({ + * account: privateKeyToAccount('0x...'), * chain: tempo({ feeToken: '0x20c0000000000000000000000000000000000001' }) * transport: http(), * }) * - * const rate = await Actions.rewards.getTotalPerSecond(client, { + * const hash = await Actions.reward.distribute(client, { + * amount: 100000000000000000000n, * token: '0x20c0000000000000000000000000000000000001', * }) * ``` * * @param client - Client. * @param parameters - Parameters. - * @returns The total reward per second (scaled by 1e18). + * @returns The transaction hash. */ -export async function getTotalPerSecond( +export async function distribute< + chain extends Chain | undefined, + account extends Account | undefined, +>( + client: Client, + parameters: distribute.Parameters, +): Promise { + return distribute.inner(writeContract, client, parameters) +} + +/** + * Distributes rewards to opted-in token holders and waits for confirmation. + * + * This function transfers `amount` of tokens from the caller into the token contract's + * reward pool and immediately distributes them to current opted-in holders by increasing + * `globalRewardPerToken`. + * + * Notes: + * - Reverts with `InvalidAmount` if `amount == 0`. + * - The transfer from caller to pool is subject to TIP-403 policy checks. + * + * @example + * ```ts + * import { createClient, http } from 'viem' + * import { tempo } from 'tempo.ts/chains' + * import { Actions } from 'tempo.ts/viem' + * import { privateKeyToAccount } from 'viem/accounts' + * + * const client = createClient({ + * account: privateKeyToAccount('0x...'), + * chain: tempo({ feeToken: '0x20c0000000000000000000000000000000000001' }) + * transport: http(), + * }) + * + * const { amount, funder, receipt } = await Actions.reward.distributeSync(client, { + * amount: 100000000000000000000n, + * token: '0x20c0000000000000000000000000000000000001', + * }) + * ``` + * + * @param client - Client. + * @param parameters - Parameters. + * @returns The funder, amount, and transaction receipt. + */ +export async function distributeSync< + chain extends Chain | undefined, + account extends Account | undefined, +>( + client: Client, + parameters: distributeSync.Parameters, +): Promise { + const { throwOnReceiptRevert = true, ...rest } = parameters + const receipt = await distribute.inner(writeContractSync, client, { + ...rest, + throwOnReceiptRevert, + } as never) + const { args } = distribute.extractEvent(receipt.logs) + return { + ...args, + receipt, + } as never +} + +export namespace distribute { + export type Args = { + /** The amount of tokens to distribute (must be > 0) */ + amount: bigint + /** The TIP20 token address */ + token: Address + } + + export type Parameters< + chain extends Chain | undefined = Chain | undefined, + account extends Account | undefined = Account | undefined, + > = WriteParameters & Args + + export type ReturnValue = WriteContractReturnType + + // TODO: exhaustive error type + export type ErrorType = BaseErrorType + + /** @internal */ + export async function inner< + action extends typeof writeContract | typeof writeContractSync, + chain extends Chain | undefined, + account extends Account | undefined, + >( + action: action, + client: Client, + parameters: Parameters, + ): Promise> { + const { amount, token, ...rest } = parameters + const call = distribute.call({ amount, token }) + return (await action(client, { + ...rest, + ...call, + } as never)) as never + } + + /** + * Defines a call to the `distributeReward` function. + * + * Can be passed as a parameter to: + * - [`estimateContractGas`](https://viem.sh/docs/contract/estimateContractGas): estimate the gas cost of the call + * - [`simulateContract`](https://viem.sh/docs/contract/simulateContract): simulate the call + * - [`sendCalls`](https://viem.sh/docs/actions/wallet/sendCalls): send multiple calls + * + * @example + * ```ts + * import { createClient, http, walletActions } from 'viem' + * import { tempo } from 'tempo.ts/chains' + * import { Actions } from 'tempo.ts/viem' + * + * const client = createClient({ + * chain: tempo({ feeToken: '0x20c0000000000000000000000000000000000001' }) + * transport: http(), + * }).extend(walletActions) + * + * const hash = await client.sendTransaction({ + * calls: [actions.reward.distribute.call({ + * amount: 100000000000000000000n, + * token: '0x20c0000000000000000000000000000000000001', + * })], + * }) + * ``` + * + * @param args - Arguments. + * @returns The call. + */ + export function call(args: Args) { + const { amount, token } = args + return defineCall({ + address: token, + abi: Abis.tip20, + args: [amount], + functionName: 'distributeReward', + }) + } + + /** + * Extracts the `RewardDistributed` event from logs. + * + * @param logs - The logs. + * @returns The `RewardDistributed` event. + */ + export function extractEvent(logs: Log[]) { + const [log] = parseEventLogs({ + abi: Abis.tip20, + logs, + eventName: 'RewardDistributed', + strict: true, + }) + if (!log) throw new Error('`RewardDistributed` event not found.') + return log + } +} + +export declare namespace distributeSync { + export type Parameters< + chain extends Chain | undefined = Chain | undefined, + account extends Account | undefined = Account | undefined, + > = WriteParameters & distribute.Args + + export type ReturnValue = { + /** The amount distributed */ + amount: bigint + /** The address that funded the distribution */ + funder: Address + /** The transaction receipt */ + receipt: Awaited> + } + + export type ErrorType = distribute.ErrorType +} + +/** + * Gets the global reward per token value. + * + * Returns the current global reward per token value scaled by `ACC_PRECISION` (1e18). + * This value increases each time rewards are distributed. + * + * @example + * ```ts + * import { createClient, http } from 'viem' + * import { tempo } from 'tempo.ts/chains' + * import { Actions } from 'tempo.ts/viem' + * + * const client = createClient({ + * chain: tempo({ feeToken: '0x20c0000000000000000000000000000000000001' }) + * transport: http(), + * }) + * + * const rewardPerToken = await Actions.reward.getGlobalRewardPerToken(client, { + * token: '0x20c0000000000000000000000000000000000001', + * }) + * ``` + * + * @param client - Client. + * @param parameters - Parameters. + * @returns The global reward per token (scaled by 1e18). + */ +export async function getGlobalRewardPerToken( client: Client, - parameters: getTotalPerSecond.Parameters, -): Promise { + parameters: getGlobalRewardPerToken.Parameters, +): Promise { return readContract(client, { ...parameters, - ...getTotalPerSecond.call(parameters), + ...getGlobalRewardPerToken.call(parameters), }) } -export namespace getTotalPerSecond { +export namespace getGlobalRewardPerToken { export type Parameters = ReadParameters & Args export type Args = { @@ -262,12 +470,12 @@ export namespace getTotalPerSecond { export type ReturnValue = ReadContractReturnType< typeof Abis.tip20, - 'totalRewardPerSecond', + 'globalRewardPerToken', never > /** - * Defines a call to the `totalRewardPerSecond` function. + * Defines a call to the `globalRewardPerToken` function. * * @param args - Arguments. * @returns The call. @@ -278,7 +486,76 @@ export namespace getTotalPerSecond { address: token, abi: Abis.tip20, args: [], - functionName: 'totalRewardPerSecond', + functionName: 'globalRewardPerToken', + }) + } +} + +/** + * Calculates the pending claimable rewards for an account without modifying state. + * + * Returns the total pending claimable reward amount, including stored balance and newly accrued rewards. + * + * @example + * ```ts + * import { createClient, http } from 'viem' + * import { tempo } from 'tempo.ts/chains' + * import { Actions } from 'tempo.ts/viem' + * + * const client = createClient({ + * chain: tempo({ feeToken: '0x20c0000000000000000000000000000000000001' }) + * transport: http(), + * }) + * + * const pending = await Actions.reward.getPendingRewards(client, { + * token: '0x20c0000000000000000000000000000000000001', + * account: '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', + * }) + * ``` + * + * @param client - Client. + * @param parameters - Parameters. + * @returns The total pending claimable reward amount. + */ +export async function getPendingRewards( + client: Client, + parameters: getPendingRewards.Parameters, +): Promise { + return readContract(client, { + ...parameters, + ...getPendingRewards.call(parameters), + }) +} + +export namespace getPendingRewards { + export type Parameters = ReadParameters & Args + + export type Args = { + /** The account address to query pending rewards for */ + account: Address + /** The TIP20 token address */ + token: Address + } + + export type ReturnValue = ReadContractReturnType< + typeof Abis.tip20, + 'getPendingRewards', + never + > + + /** + * Defines a call to the `getPendingRewards` function. + * + * @param args - Arguments. + * @returns The call. + */ + export function call(args: Args) { + const { account, token } = args + return defineCall({ + address: token, + abi: Abis.tip20, + args: [account], + functionName: 'getPendingRewards', }) } } @@ -381,7 +658,7 @@ export namespace getUserRewardInfo { * transport: http(), * }) * - * const hash = await Actions.rewards.setRecipient(client, { + * const hash = await Actions.reward.setRecipient(client, { * recipient: '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', * token: '0x20c0000000000000000000000000000000000001', * }) @@ -426,7 +703,7 @@ export async function setRecipient< * transport: http(), * }) * - * const { holder, recipient, receipt } = await Actions.rewards.setRecipientSync(client, { + * const { holder, recipient, receipt } = await Actions.reward.setRecipientSync(client, { * recipient: '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', * token: '0x20c0000000000000000000000000000000000001', * }) @@ -492,7 +769,7 @@ export namespace setRecipient { } /** - * Defines a call to the `setRecipient` function. + * Defines a call to the `setRewardRecipient` function. * * Can be passed as a parameter to: * - [`estimateContractGas`](https://viem.sh/docs/contract/estimateContractGas): estimate the gas cost of the call @@ -511,7 +788,7 @@ export namespace setRecipient { * }).extend(walletActions) * * const hash = await client.sendTransaction({ - * calls: [actions.rewards.setRecipient.call({ + * calls: [actions.reward.setRecipient.call({ * recipient: '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', * token: '0x20c0000000000000000000000000000000000001', * })], @@ -568,231 +845,7 @@ export declare namespace setRecipientSync { } /** - * Starts a new reward stream that distributes tokens to opted-in holders. - * - * Behavior: - * - Transfers `amount` of tokens from the caller into the token contract's reward pool. - * - If `seconds == 0`: Immediately distributes `amount` to current opted-in holders by increasing `rewardPerTokenStored`. - * Returns stream ID `0`. Distribution occurs when holders interact with the token (transfers, etc.). - * - If `seconds > 0`: Starts a linear stream that emits evenly from `block.timestamp` to `block.timestamp + seconds`. - * Returns a unique stream ID for later cancellation. - * - * Notes: - * - Reverts with `InvalidAmount` if `amount == 0`. - * - Allowed even when `optedInSupply == 0` (tokens distributed while no one is opted in are locked permanently). - * - The transfer from caller to pool is subject to TIP-403 policy checks. - * - * @example - * ```ts - * import { createClient, http } from 'viem' - * import { tempo } from 'tempo.ts/chains' - * import { Actions } from 'tempo.ts/viem' - * import { privateKeyToAccount } from 'viem/accounts' - * - * const client = createClient({ - * account: privateKeyToAccount('0x...'), - * chain: tempo({ feeToken: '0x20c0000000000000000000000000000000000001' }) - * transport: http(), - * }) - * - * const hash = await Actions.rewards.start(client, { - * amount: 100000000000000000000n, - * seconds: 86400, - * token: '0x20c0000000000000000000000000000000000001', - * }) - * ``` - * - * @param client - Client. - * @param parameters - Parameters. - * @returns The transaction hash. - */ -export async function start< - chain extends Chain | undefined, - account extends Account | undefined, ->( - client: Client, - parameters: start.Parameters, -): Promise { - return start.inner(writeContract, client, parameters) -} - -/** - * Starts a new reward stream that distributes tokens to opted-in holders and waits for confirmation. - * - * Behavior: - * - Transfers `amount` of tokens from the caller into the token contract's reward pool. - * - If `seconds == 0`: Immediately distributes `amount` to current opted-in holders by increasing `rewardPerTokenStored`. - * Returns stream ID `0`. Distribution occurs when holders interact with the token (transfers, etc.). - * - If `seconds > 0`: Starts a linear stream that emits evenly from `block.timestamp` to `block.timestamp + seconds`. - * Returns a unique stream ID for later cancellation. - * - * Notes: - * - Reverts with `InvalidAmount` if `amount == 0`. - * - Allowed even when `optedInSupply == 0` (tokens distributed while no one is opted in are locked permanently). - * - The transfer from caller to pool is subject to TIP-403 policy checks. - * - * @example - * ```ts - * import { createClient, http } from 'viem' - * import { tempo } from 'tempo.ts/chains' - * import { Actions } from 'tempo.ts/viem' - * import { privateKeyToAccount } from 'viem/accounts' - * - * const client = createClient({ - * account: privateKeyToAccount('0x...'), - * chain: tempo({ feeToken: '0x20c0000000000000000000000000000000000001' }) - * transport: http(), - * }) - * - * const { id, receipt } = await Actions.rewards.startSync(client, { - * amount: 100000000000000000000n, - * seconds: 86400, - * token: '0x20c0000000000000000000000000000000000001', - * }) - * ``` - * - * @param client - Client. - * @param parameters - Parameters. - * @returns The stream ID, funder, amount, duration, and transaction receipt. - */ -export async function startSync< - chain extends Chain | undefined, - account extends Account | undefined, ->( - client: Client, - parameters: startSync.Parameters, -): Promise { - const { throwOnReceiptRevert = true, ...rest } = parameters - const receipt = await start.inner(writeContractSync, client, { - ...rest, - throwOnReceiptRevert, - } as never) - const { args } = start.extractEvent(receipt.logs) - return { - ...args, - receipt, - } as never -} - -export namespace start { - export type Args = { - /** The amount of tokens to distribute (must be > 0) */ - amount: bigint - /** The TIP20 token address */ - token: Address - } - - export type Parameters< - chain extends Chain | undefined = Chain | undefined, - account extends Account | undefined = Account | undefined, - > = WriteParameters & Args - - export type ReturnValue = WriteContractReturnType - - // TODO: exhaustive error type - export type ErrorType = BaseErrorType - - /** @internal */ - export async function inner< - action extends typeof writeContract | typeof writeContractSync, - chain extends Chain | undefined, - account extends Account | undefined, - >( - action: action, - client: Client, - parameters: Parameters, - ): Promise> { - const { amount, token, ...rest } = parameters - const call = start.call({ amount, token }) - return (await action(client, { - ...rest, - ...call, - } as never)) as never - } - - /** - * Defines a call to the `start` function. - * - * Can be passed as a parameter to: - * - [`estimateContractGas`](https://viem.sh/docs/contract/estimateContractGas): estimate the gas cost of the call - * - [`simulateContract`](https://viem.sh/docs/contract/simulateContract): simulate the call - * - [`sendCalls`](https://viem.sh/docs/actions/wallet/sendCalls): send multiple calls - * - * @example - * ```ts - * import { createClient, http, walletActions } from 'viem' - * import { tempo } from 'tempo.ts/chains' - * import { Actions } from 'tempo.ts/viem' - * - * const client = createClient({ - * chain: tempo({ feeToken: '0x20c0000000000000000000000000000000000001' }) - * transport: http(), - * }).extend(walletActions) - * - * const hash = await client.sendTransaction({ - * calls: [actions.rewards.start.call({ - * amount: 100000000000000000000n, - * seconds: 86400, - * token: '0x20c0000000000000000000000000000000000001', - * })], - * }) - * ``` - * - * @param args - Arguments. - * @returns The call. - */ - export function call(args: Args) { - const { amount, token } = args - return defineCall({ - address: token, - abi: Abis.tip20, - args: [amount, 0], - functionName: 'startReward', - }) - } - - /** - * Extracts the `RewardScheduled` event from logs. - * - * @param logs - The logs. - * @returns The `RewardScheduled` event. - */ - export function extractEvent(logs: Log[]) { - const [log] = parseEventLogs({ - abi: Abis.tip20, - logs, - eventName: 'RewardScheduled', - strict: true, - }) - if (!log) throw new Error('`RewardScheduled` event not found.') - return log - } -} - -export declare namespace startSync { - export type Parameters< - chain extends Chain | undefined = Chain | undefined, - account extends Account | undefined = Account | undefined, - > = WriteParameters & start.Args - - export type ReturnValue = { - /** The total amount allocated to the stream */ - amount: bigint - /** The duration of the stream in seconds (0 for immediate distributions) */ - durationSeconds: number - /** The address that funded the stream */ - funder: Address - /** The unique stream ID (0 for immediate distributions, >0 for streaming distributions) */ - id: bigint - /** The transaction receipt */ - receipt: Awaited> - } - - export type ErrorType = start.ErrorType -} - -/** - * Watches for reward scheduled events. + * Watches for reward distributed events. * * @example * ```ts @@ -805,10 +858,10 @@ export declare namespace startSync { * transport: http(), * }) * - * const unwatch = Actions.reward.watchRewardScheduled(client, { + * const unwatch = Actions.reward.watchRewardDistributed(client, { * token: '0x20c0000000000000000000000000000000000001', - * onRewardScheduled: (args, log) => { - * console.log('Reward scheduled:', args) + * onRewardDistributed: (args, log) => { + * console.log('Reward distributed:', args) * }, * }) * ``` @@ -817,30 +870,30 @@ export declare namespace startSync { * @param parameters - Parameters. * @returns A function to unsubscribe from the event. */ -export function watchRewardScheduled< +export function watchRewardDistributed< chain extends Chain | undefined, account extends Account | undefined, >( client: Client, - parameters: watchRewardScheduled.Parameters, + parameters: watchRewardDistributed.Parameters, ) { - const { onRewardScheduled, token, ...rest } = parameters + const { onRewardDistributed, token, ...rest } = parameters return watchContractEvent(client, { ...rest, address: token, abi: Abis.tip20, - eventName: 'RewardScheduled', + eventName: 'RewardDistributed', onLogs: (logs) => { - for (const log of logs) onRewardScheduled(log.args, log) + for (const log of logs) onRewardDistributed(log.args, log) }, strict: true, }) } -export declare namespace watchRewardScheduled { +export declare namespace watchRewardDistributed { export type Args = GetEventArgs< typeof Abis.tip20, - 'RewardScheduled', + 'RewardDistributed', { IndexedOnly: false; Required: true } > @@ -848,16 +901,16 @@ export declare namespace watchRewardScheduled { bigint, number, false, - ExtractAbiItem, + ExtractAbiItem, true > export type Parameters = UnionOmit< - WatchContractEventParameters, + WatchContractEventParameters, 'abi' | 'address' | 'batch' | 'eventName' | 'onLogs' | 'strict' > & { - /** Callback to invoke when rewards are scheduled. */ - onRewardScheduled: (args: Args, log: Log) => void + /** Callback to invoke when rewards are distributed. */ + onRewardDistributed: (args: Args, log: Log) => void /** The TIP20 token address */ token: Address } diff --git a/src/tempo/actions/token.test.ts b/src/tempo/actions/token.test.ts index 57a85ddd68..a73de16722 100644 --- a/src/tempo/actions/token.test.ts +++ b/src/tempo/actions/token.test.ts @@ -1,12 +1,11 @@ import { setTimeout } from 'node:timers/promises' import { Hex } from 'ox' -import { TokenRole } from 'ox/tempo' +import { TokenId, TokenRole } from 'ox/tempo' import { parseUnits } from 'viem' import { getCode, writeContractSync } from 'viem/actions' import { Abis, Addresses, TokenIds } from 'viem/tempo' -import { beforeAll, describe, expect, test } from 'vitest' +import { describe, expect, test } from 'vitest' import { accounts, addresses, chain, getClient } from '~test/tempo/config.js' -import { rpcUrl } from '~test/tempo/prool.js' import * as actions from './index.js' const account = accounts[0] @@ -215,7 +214,7 @@ describe('approve', () => { describe('create', () => { test('default', async () => { - const { receipt, token, tokenId, ...result } = + const { receipt, salt, token, tokenId, ...result } = await actions.token.createSync(client, { currency: 'USD', name: 'Test USD', @@ -231,6 +230,7 @@ describe('create', () => { "symbol": "TUSD", } `) + expect(salt).toBeDefined() expect(token).toBeDefined() expect(tokenId).toBeDefined() expect(receipt).toBeDefined() @@ -1827,16 +1827,13 @@ describe('watchCreate', () => { } }) - test('behavior: filter by tokenId', async () => { - // First, create a token to know what ID we're at - const { tokenId: firstId } = await actions.token.createSync(client, { - currency: 'USD', - name: 'Setup Token', - symbol: 'SETUP', + test('behavior: filter by token', async () => { + const salt = Hex.random(32) + const tokenId = TokenId.compute({ + salt, + sender: client.account.address, }) - - // We want to watch for the token with ID = firstId + 2 - const targetTokenId = firstId + 2n + const token = TokenId.toAddress(tokenId) const receivedTokens: Array<{ args: actions.token.watchCreate.Args @@ -1846,7 +1843,7 @@ describe('watchCreate', () => { // Start watching for token creation events only for targetTokenId const unwatch = actions.token.watchCreate(client, { args: { - tokenId: targetTokenId, + token, }, onTokenCreated: (args, log) => { receivedTokens.push({ args, log }) @@ -1854,21 +1851,23 @@ describe('watchCreate', () => { }) try { - // Create first token (should NOT be captured - ID will be firstId + 1) + // Create first token (should NOT be captured) await actions.token.createSync(client, { currency: 'USD', name: 'Filtered Watch Token 1', symbol: 'FWATCH1', }) - // Create second token (should be captured - ID will be firstId + 2 = targetTokenId) - const { tokenId: id2 } = await actions.token.createSync(client, { + // Create second token (should be captured) + const result = await actions.token.createSync(client, { currency: 'USD', name: 'Filtered Watch Token 2', + salt, symbol: 'FWATCH2', }) + expect(result.token.toLowerCase()).toBe(token) - // Create third token (should NOT be captured - ID will be firstId + 3) + // Create third token (should NOT be captured) await actions.token.createSync(client, { currency: 'USD', name: 'Filtered Watch Token 3', @@ -1880,10 +1879,11 @@ describe('watchCreate', () => { // Should only receive 1 event (for targetTokenId) expect(receivedTokens).toHaveLength(1) - expect(receivedTokens.at(0)!.args.tokenId).toBe(targetTokenId) - expect(receivedTokens.at(0)!.args.tokenId).toBe(id2) - - const { token, tokenId, ...rest } = receivedTokens.at(0)!.args + const { + token: tokenAddress, + salt: tokenSalt, + ...rest + } = receivedTokens.at(0)!.args expect(rest).toMatchInlineSnapshot(` { "admin": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", @@ -1893,8 +1893,8 @@ describe('watchCreate', () => { "symbol": "FWATCH2", } `) - expect(token).toBeDefined() - expect(tokenId).toBe(targetTokenId) + expect(salt).toBe(tokenSalt) + expect(token.toLowerCase()).toBe(tokenAddress.toLowerCase()) } finally { if (unwatch) unwatch() } @@ -2457,6 +2457,8 @@ describe('watchRole', () => { symbol: 'ROLE', }) + await setTimeout(100) + const receivedRoleUpdates: Array<{ args: actions.token.watchRole.Args log: actions.token.watchRole.Log @@ -2587,10 +2589,6 @@ describe('watchRole', () => { }) describe('watchTransfer', () => { - beforeAll(async () => { - await fetch(`${rpcUrl}/restart`) - }) - test('default', async () => { // Create a new token for testing const { token: address } = await actions.token.createSync(client, { @@ -2644,21 +2642,6 @@ describe('watchTransfer', () => { await setTimeout(200) expect(receivedTransfers.length).toBeGreaterThanOrEqual(2) - - expect(receivedTransfers.at(0)!.args).toMatchInlineSnapshot(` - { - "amount": 100000000n, - "from": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "to": "0x8C8d35429F74ec245F8Ef2f4Fd1e551cFF97d650", - } - `) - expect(receivedTransfers.at(1)!.args).toMatchInlineSnapshot(` - { - "amount": 50000000n, - "from": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "to": "0x98e503f35D0a019cB0a251aD243a4cCFCF371F46", - } - `) } finally { if (unwatch) unwatch() } diff --git a/src/tempo/actions/token.ts b/src/tempo/actions/token.ts index c8f9685f95..3b209d5a51 100644 --- a/src/tempo/actions/token.ts +++ b/src/tempo/actions/token.ts @@ -913,8 +913,10 @@ export namespace create { currency: string /** Token name. */ name: string - /** quote token. */ + /** Quote token. */ quoteToken?: TokenId.TokenIdOrAddress | undefined + /** Unique salt. @default Hex.random(32) */ + salt?: Hex.Hex | undefined /** Token symbol. */ symbol: string } @@ -997,11 +999,19 @@ export namespace create { currency, quoteToken = Addresses.pathUsd, admin, + salt = Hex.random(32), } = args return defineCall({ address: Addresses.tip20Factory, abi: Abis.tip20Factory, - args: [name, symbol, currency, TokenId.toAddress(quoteToken), admin], + args: [ + name, + symbol, + currency, + TokenId.toAddress(quoteToken), + admin, + salt, + ], functionName: 'createToken', }) } @@ -1065,10 +1075,12 @@ export async function createSync< } as never) const { args } = create.extractEvent(receipt.logs) + const tokenId = TokenId.fromAddress(args.token) return { ...args, receipt, + tokenId, } as never } @@ -1086,6 +1098,8 @@ export namespace createSync { 'TokenCreated', { IndexedOnly: false; Required: true } > & { + /** Token ID. */ + tokenId: TokenId.TokenId /** Transaction receipt. */ receipt: TransactionReceipt } diff --git a/src/tempo/chainConfig.test.ts b/src/tempo/chainConfig.test.ts index f5ad3e6ac7..893905aba7 100644 --- a/src/tempo/chainConfig.test.ts +++ b/src/tempo/chainConfig.test.ts @@ -83,7 +83,7 @@ describe('prepareTransactionRequest', () => { prepareTransactionRequest(client, { feePayer: true }), // nonceKey: 2n ]) - expect(requests[0]?.nonce).toBe(0) + expect(requests[0]?.nonce).toBe(1) expect(requests[0]?.nonceKey).toBe(0n) // nonceKey >= 1n is truthy, so nonce is 0 @@ -106,7 +106,7 @@ describe('prepareTransactionRequest', () => { test('behavior: default nonceKey is 0n (falsy)', async () => { const request = await prepareTransactionRequest(client, {}) expect(request?.nonceKey).toBe(undefined) - expect(request?.nonce).toBe(0) + expect(request?.nonce).toBe(1) }) test('behavior: sendTransaction', async () => { diff --git a/src/tempo/chainConfig.ts b/src/tempo/chainConfig.ts index 63de854244..af90eb0855 100644 --- a/src/tempo/chainConfig.ts +++ b/src/tempo/chainConfig.ts @@ -32,7 +32,7 @@ export const chainConfig = { }), }, prepareTransactionRequest: [ - async (r, { phase }) => { + async (r) => { const request = r as Transaction.TransactionRequest & { account?: Account | undefined chain?: @@ -40,12 +40,6 @@ export const chainConfig = { | undefined } - if (phase === 'afterFillParameters') { - if (typeof request.nonceKey === 'bigint' && request.nonceKey > 0n) - request.gas = (request.gas ?? 0n) + 40_000n - return request as unknown as typeof r - } - request.nonceKey = (() => { if ( typeof request.nonceKey !== 'undefined' && diff --git a/src/tempo/e2e.test.ts b/src/tempo/e2e.test.ts index 78d6aa8ea8..f05c8f6ba7 100644 --- a/src/tempo/e2e.test.ts +++ b/src/tempo/e2e.test.ts @@ -20,7 +20,7 @@ import { getClient, http, } from '~test/tempo/config.js' -import { rpcUrl } from '~test/tempo/prool.js' +import * as Prool from '~test/tempo/prool.js' import { withFeePayer } from './Transport.js' const client = getClient() @@ -762,7 +762,7 @@ describe('sendTransaction', () => { "data": undefined, "feePayerSignature": undefined, "feeToken": null, - "gas": 29012n, + "gas": 26406n, "maxFeePerBlobGas": undefined, "to": null, "type": "tempo", @@ -1301,7 +1301,7 @@ describe('signTransaction', () => { ], "data": undefined, "feeToken": null, - "gas": 24002n, + "gas": 21326n, "maxFeePerBlobGas": undefined, "to": null, "type": "tempo", @@ -1323,7 +1323,7 @@ describe('relay', () => { let server: Http.Server afterEach(async () => { - await fetch(`${rpcUrl}/restart`) + await Prool.restart(client) }) beforeAll(async () => { diff --git a/test/src/tempo/config.ts b/test/src/tempo/config.ts index 8b593431fe..345c12b557 100644 --- a/test/src/tempo/config.ts +++ b/test/src/tempo/config.ts @@ -13,6 +13,7 @@ import { type Client, type ClientConfig, createClient, + defineChain, type HttpTransportConfig, type JsonRpcAccount, parseUnits, @@ -51,7 +52,10 @@ export const chain = (() => { case 'devnet': return tempoDevnet default: - return tempoLocalnet + return defineChain({ + ...tempoLocalnet, + rpcUrls: { default: { http: [rpcUrl] } }, + }) } })() @@ -221,12 +225,12 @@ export async function setupTokenPair( }), Actions.token.approve.call({ token: baseToken, - spender: Addresses.stablecoinExchange, + spender: Addresses.stablecoinDex, amount: parseUnits('10000', 6), }), Actions.token.approve.call({ token: quoteToken, - spender: Addresses.stablecoinExchange, + spender: Addresses.stablecoinDex, amount: parseUnits('10000', 6), }), ], diff --git a/test/src/tempo/prool.ts b/test/src/tempo/prool.ts index 6b102ddbd1..1191874e34 100644 --- a/test/src/tempo/prool.ts +++ b/test/src/tempo/prool.ts @@ -1,6 +1,15 @@ import { RpcTransport } from 'ox' import { type Instance, Server } from 'prool' import * as TestContainers from 'prool/testcontainers' +import { + type Chain, + type Client, + parseUnits, + type Transport, +} from '../../../src/index.js' +import { pathUsd } from '../../../src/tempo/Addresses.js' +import * as actions from '../../../src/tempo/actions/index.js' +import { accounts, nodeEnv } from './config.js' export const port = 9545 @@ -44,3 +53,26 @@ export async function createServer() { port, }) } + +export async function restart(client: Client) { + if (nodeEnv !== 'localnet') return + await fetch(`${client.chain.rpcUrls.default.http[0]}/restart`) + await setup(client) +} + +export async function setup(client: Client) { + // Mint liquidity for fee tokens. + await Promise.all( + [1n, 2n, 3n].map((id) => + actions.amm.mintSync(client, { + account: accounts[0], + feeToken: pathUsd, + nonceKey: 'random', + userTokenAddress: id, + validatorTokenAddress: pathUsd, + validatorTokenAmount: parseUnits('1000', 6), + to: accounts[0].address, + }), + ), + ) +} diff --git a/test/src/tempo/setup.global.ts b/test/src/tempo/setup.global.ts index 9ecedad1f6..367259e33f 100644 --- a/test/src/tempo/setup.global.ts +++ b/test/src/tempo/setup.global.ts @@ -1,9 +1,16 @@ import { nodeEnv } from './config.js' -import { createServer } from './prool.js' +import { createServer, port } from './prool.js' export default async function () { if (nodeEnv !== 'localnet') return const server = await createServer() - return await server.start() + const stop = await server.start() + + // Arbitrary request to start server to trigger Docker image download. + console.log('Downloading Docker image & starting Tempo server...') + await fetch(`http://localhost:${port}/1/start`) + console.log('Tempo server started.') + + return stop } diff --git a/test/src/tempo/setup.ts b/test/src/tempo/setup.ts index 661adae8a5..b3cd257c6a 100644 --- a/test/src/tempo/setup.ts +++ b/test/src/tempo/setup.ts @@ -2,12 +2,16 @@ import { setTimeout } from 'node:timers/promises' import { afterAll, beforeAll } from 'vitest' import { faucet } from '../../../src/tempo/actions/index.js' import { accounts, getClient, nodeEnv } from './config.js' -import { rpcUrl } from './prool.js' +import * as Prool from './prool.js' const client = getClient() beforeAll(async () => { - if (nodeEnv === 'localnet') return + if (nodeEnv === 'localnet') { + await Prool.setup(client) + return + } + await faucet.fundSync(client, { account: accounts[0].address, }) @@ -17,5 +21,5 @@ beforeAll(async () => { afterAll(async () => { if (nodeEnv !== 'localnet') return - await fetch(`${rpcUrl}/stop`) + await fetch(`${Prool.rpcUrl}/stop`) }) diff --git a/test/tempo b/test/tempo index f8c4c03456..b1c30ea070 160000 --- a/test/tempo +++ b/test/tempo @@ -1 +1 @@ -Subproject commit f8c4c034563b8588d094df0058e3d00341b0099b +Subproject commit b1c30ea070f9ae6c2a7e011fac4b77331eb023ee diff --git a/test/vitest.config.ts b/test/vitest.config.ts index 89b188664f..f33fa1e4c6 100644 --- a/test/vitest.config.ts +++ b/test/vitest.config.ts @@ -70,6 +70,7 @@ export default defineConfig({ setupFiles: [join(__dirname, './src/tempo/setup.ts')], globalSetup: [join(__dirname, './src/tempo/setup.global.ts')], sequence: { groupOrder: 1 }, + hookTimeout: 20_000, testTimeout: 10_000, }, },