Skip to content
forked from ConcaveFi/txs

A plug and play, customizable way to manage user transaction status on your dapp

Notifications You must be signed in to change notification settings

greg-schrammel/txs

Β 
Β 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

66 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

aaaaaa

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 viem and wagmi
  • ✨ Framework agnostic core at @pcnv/txs-core
  • πŸ“¦ Tiny tiny, less than 3 kB gzipped

Install

pnpm add @pcnv/txs-react viem 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 viem wagmi

Usage

Check the examples folder for a complete implementation

import {
  createTransactionsStore,
  ToastsViewport,
  TransactionsStoreProvider,
  TransactionStatusToastProps,
} 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 addTransaction = useAddRecentTransaction()
  const { writeContract } = useWriteContract()

  ...

  writeContract(
    {
      address: ...,
      abi: ...,
      functionName: ...,
    },
    {
      onSuccess: (hash) => {
        addTransaction({
          hash,
          meta: {
            // more on meta below
            description: `Wrapping ${amount} ETH`,
          },
        })
      },
    },
  )

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'

EmojiToast

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

ClassicToast

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 true
  • staleTime: if the user closed the app without a status update, for how long it's still relevant to display the update on reopen
  • stuckTime: 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,
} from '@pcnv/txs-react'

// add your custom fields to the global namespace Txs.Meta
declare global {
  namespace Txs {
    export interface Meta {
      pending: string
      success: string
      reverted: string
    }
  }
}

const MyCustomNotification = (props: TransactionStatusToastProps<TransactionMeta>) => {
  const tx = props.transaction
  return <EmojiToast {...props} description={tx.meta[tx.status]} />
}

...

<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 meta will 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 }

declare global {
  namespace Txs {
    export interface Meta extends TransactionType {}
  }
}

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} />
}

...

const addTransaction = useAddRecentTransaction()
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

About

A plug and play, customizable way to manage user transaction status on your dapp

Resources

Stars

Watchers

Forks

Packages

No packages published

Languages

  • TypeScript 65.2%
  • CSS 30.1%
  • JavaScript 4.7%