Txs
A plug and play, customizable way to manage user transaction status on your dapp
Install / Use
/learn @ConcaveFi/TxsREADME

Concave txs
A plug and play, customizable way to manage user transaction status on your dapp
- ✅ Out-of-the-box status notifications on top of @zag-js/toast (totally optional)
- 🎉 Easily customizable, or create your own with simple react hooks
- 🔥 Using
ethersandwagmi - ✨ Framework agnostic core at
@pcnv/txs-core - 📦 Tiny tiny, less than
3 kB gzipped
Install
pnpm add @pcnv/txs-react ethers wagmi @zag-js/react @zag-js/toast
# zag is not required if you want to create your own notification components
pnpm add @pcnv/txs-react ethers wagmi
Usage
Check the examples folder for a complete implementation
import {
createTransactionsStore,
ToastsViewport,
TransactionsStoreProvider,
} from '@pcnv/txs-react'
// import a builtin toast component or create your own
import { ClassicToast } from '@pcnv/txs-react/toasts/ClassicToast'
import '@pcnv/txs-react/toasts/ClassicToast/styles.css'
const transactionsStore = createTransactionsStore()
...
// Add the provider to your app
// make sure its a children of WagmiConfig
<WagmiConfig client={...}>
<TransactionsStoreProvider store={transactionsStore}>
<ToastsViewport
TransactionStatusComponent={ClassicToast}
placement="top-end"
/>
...
And in your component
import { usePrepareContractWrite, useContractWrite } from 'wagmi'
import { useAddRecentTransaction } from '@pcnv/txs-react'
...
const { config } = usePrepareContractWrite(...)
const addTransaction = useAddRecentTransaction()
const { write } = useContractWrite({
...config,
onSuccess: (tx) => {
// useContractWrite onSuccess means the transaciton was signed and sent
addTransaction({
hash: tx.hash,
meta: {
description: 'Your transaction description'
},
})
},
})
Hooks
useRecentTransactions
returns all transactions stored for the connected user in the connected chain
const recentTransactions = useRecentTransactions()
It also accepts a selector
// this component will only rerender when a new transaction is set as completed
const completedTransactions = useRecentTransactions((txs) =>
txs.filter(({ status }) => status === 'completed'),
)
useAddRecentTransaction
Adds a transaction to be tracked, to the connected user/chain
const addTransaction = useAddRecentTransaction()
...
addTransaction({
hash: '0x your transaciton hash',
meta: {
// metadata about the transaciton, description, type etc, more on the meta field below
}
})
useRemoveRecentTransaction
Removes a connected user transaction by hash
const removeTransaction = useRemoveRecentTransaction()
...
removeTransaction(hash)
useClearRecentTransactions
Clears all transactions stored on the current connected user/chain
const clearTransactions = useClearRecentTransactions()
...
clearTransactions()
useTransactionsStoreEvent
Listens for an event from the store to execute a callback
useTransactionsStoreEvent(
'added',
useCallback((tx) => {
// a new transaction was added, do something
}, []),
)
Supported events are added, updated, removed, cleared, mounted
Useful if you are building your own notification solution
Maybe you want to display a confirmation dialog on transaction confimed. that could look something like this
useTransactionsStoreEvent(
'updated',
useCallback(
(tx) => {
if (tx.status === 'confirmed') displayTxConfirmedDialog(tx)
},
[displayTxConfirmedDialog],
),
)
Built in Components
Both detect prefers-color-scheme and style light/dark accordingly, you can force by passing a colorScheme prop, default is system
import { EmojiToast } from '@pcnv/txs-react/toasts/EmojiToast'
import '@pcnv/txs-react/toasts/EmojiToast/styles.css'

import { ClassicToast } from '@pcnv/txs-react/toasts/ClassicToast'
import '@pcnv/txs-react/toasts/ClassicToast/styles.css'

Some Defaults
The following can be configured thru props on ToastsViewport
showPendingOnReopen: should pop up the pending notification when the user closes and opens the app again while still pending? defaults to truestaleTime: if the user closed the app without a status update, for how long it's still relevant to display the update on reopenstuckTime: transactions are considered stuck after 30min without a confirmation/rejection
Meta field
The meta field accepts anything serializable really,
for example, instead of a single description you may want to have custom description for pending, completed and failed
Here's an example of how that could work
import {
StoredTransaction,
ToastsViewport,
TransactionStatusToastProps,
TypedUseAddRecentTransaction,
TypedUseRecentTransactions,
useRecentTransactions as _useRecentTransactions,
useAddRecentTransaction as _useAddRecentTransaction,
} from '@pcnv/txs-react'
type TransactionMeta = {
[status in StoredTransaction['status']]: string
}
const MyCustomNotification = (props: TransactionStatusToastProps<TransactionMeta>) => {
const tx = props.transaction
return <EmojiToast {...props} description={tx.meta[tx.status]} />
}
// you can rexport the hooks passing your new type as a generic to type check on use
// just remember to import from this file, and not @pcnv/txs-react
export const useRecentTransactions: TypedUseRecentTransactions<TransactionMeta> =
_useRecentTransactions
export const useAddRecentTransaction: TypedUseAddRecentTransaction<TransactionMeta> =
_useAddRecentTransaction
...
<ToastsViewport TransactionStatusComponent={MyCustomNotification} />
...
// and in you component ts will enforce this usage
const addTransaction = useAddRecentTransaction()
...
addTransaction({
hash: tx.hash,
meta: {
pending: '...',
completed: '...',
failed: '...',
},
})
Note Beware that everything included as
metawill be saved to LocalStorage
Another example
This time only some properties are saved to localstorage based on the transaction type
type TransactionType = { type: 'approve'; amount: string; token: string } // | { ...more types }
type TransactionMetaToStatusLabel = {
[Meta in TransactionType as Meta['type']]: (
meta: Omit<Meta, 'type'>,
) => Record<StoredTransaction['status'], string>
}
const typeToStatusDescription = {
approve: ({ amount, token }) => ({
pending: `Approving ${amount} ${token}`,
confirmed: `Successfully approved ${amount} ${token}`,
failed: `Failed to approve ${amount} ${token}`,
}),
} satisfies TransactionMetaToStatusLabel
const MyCustomNotification = (props: TransactionStatusToastProps<TransactionType>) => {
const { meta, status } = props.transaction
const description = typeToStatusDescription[meta.type](meta)[status]
return <EmojiToast {...props} description={description} />
}
...
// and the hook usage
const addTransaction = useAddRecentTransaction<TransactionType>()
...
addTransaction({
hash: tx.hash,
meta: {
type: 'approve', // typescript can auto suggest all available types and their required properties
amount: 1,
token: 'CNV'
},
})
Enter & Exit Animations
Check Zagjs Docs
More
Check how's it being used it the concave repo
Related Skills
bluebubbles
348.0kUse when you need to send or manage iMessages via BlueBubbles (recommended iMessage integration). Calls go through the generic message tool with channel="bluebubbles".
node-connect
348.0kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
slack
348.0kUse when you need to control Slack from OpenClaw via the slack tool, including reacting to messages or pinning/unpinning items in Slack channels or DMs.
frontend-design
108.8kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
