Last active
August 13, 2024 07:12
-
-
Save drichar/57fa10b51d56c5af50ee296746d27117 to your computer and use it in GitHub Desktop.
Factory for creating custom useMutation hooks to sign transactions from the NFDomains API
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as React from 'react' | |
import { NfdRecord } from '@/api/api-client' | |
import { useNfdCacheUpdate } from '@/api/hooks/useNfd' | |
import { usePostContractLock } from '@/api/hooks/usePostContractLock' | |
interface ManageContractProps { | |
nfd: NfdRecord | |
} | |
export function ManageContract({ nfd }: ManageContractProps) { | |
const [isLocked, setIsLocked] = React.useState(true) | |
const handleError = useErrorToast() | |
const optimisticUpdate = useNfdCacheUpdate() | |
const { mutateAsync: lockContract } = usePostContractLock({ | |
toasts: { | |
success: `Contract successfully ${isLocked ? `locked` : `unlocked`}.` | |
}, | |
onSuccess(data, params) { | |
if (!params) return | |
const newNfd: NfdRecord = { | |
...nfd, | |
properties: { | |
...nfd.properties, | |
internal: { | |
...nfd.properties?.internal, | |
contractLocked: params.body.lock ? '1' : '0' | |
} | |
} | |
} | |
// Updates the query cache w/o refetching | |
optimisticUpdate(newNfd) | |
} | |
}) | |
const handleClickLockContract = async () => { | |
try { | |
if (!activeAddress) { | |
throw new Error('Wallet not connected!) | |
} | |
await lockContract({ | |
name: nfd.name, | |
body: { | |
sender: activeAddress, | |
lock: isLocked | |
} | |
}) | |
} catch (e) { | |
handleError(e) | |
} | |
} | |
// render component... | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { ContractLockRequestBody, nfdContractLock } from '@/api/api-client' | |
import { MutationOptions, usePostTransaction } from './usePostTransaction' | |
type ContractLockParams = { | |
name: string | |
body: ContractLockRequestBody | |
} | |
export function usePostContractLock(options: MutationOptions<ContractLockParams> = {}) { | |
return usePostTransaction<ContractLockParams>({ | |
mutationFn: ({ name, body }) => nfdContractLock(name, body), | |
...options | |
}) | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useMutation } from '@tanstack/react-query' | |
import { useWallet } from '@txnlab/use-wallet-react' | |
import algosdk from 'algosdk' | |
import * as React from 'react' | |
import toast from 'react-hot-toast' | |
import { encodeNFDTransactionsArray, type NFDTransactionsArray } from '@/helpers/encoding' | |
import { useExplorerStore } from '@/store/index' | |
import type { AxiosResponse } from 'axios' | |
import type { PendingTransactionResponse } from 'types/algosdk' | |
export type SendTxnsResponse = PendingTransactionResponse & { txId: string } | |
export type ToastProps<TParams = unknown, TContext = unknown> = { | |
data?: SendTxnsResponse | |
params: TParams | |
context?: TContext | undefined | |
explorerLink?: string | |
} | |
type ToastComponent<TParams, TContext> = ({ | |
data, | |
params, | |
context, | |
explorerLink | |
}: ToastProps<TParams, TContext>) => JSX.Element | |
type TxnToastContent<TParams, TContext> = string | ToastComponent<TParams, TContext> | |
export type TransactionToasts<TParams, TContext> = { | |
loading?: TxnToastContent<TParams, TContext> | |
success?: TxnToastContent<TParams, TContext> | |
} | |
export type PostTransactionOptions<TParams, TContext = unknown> = { | |
// eslint-disable-next-line @typescript-eslint/no-explicit-any | |
mutationFn: (params: TParams) => Promise<AxiosResponse<string | void, any>> | |
onMutate?: (params: TParams) => Promise<TContext | undefined> | TContext | undefined | |
onSuccess?: (data: SendTxnsResponse, params: TParams, context: TContext | undefined) => unknown | |
onError?: (error: unknown, params: TParams, context: TContext | undefined) => unknown | |
onSettled?: ( | |
data: SendTxnsResponse | undefined, | |
error: unknown, | |
params: TParams, | |
context: TContext | undefined | |
) => unknown | |
toasts?: TransactionToasts<TParams, TContext> | |
} | |
export type MutationOptions<TParams, TContext = unknown> = Partial< | |
Omit<PostTransactionOptions<TParams, TContext>, 'mutationFn'> | |
> | |
export function usePostTransaction<TParams, TContext = unknown>( | |
options: PostTransactionOptions<TParams, TContext> | |
) { | |
const { mutationFn, onMutate, onSuccess, onError, onSettled, toasts = {} } = options | |
const { loading = 'Waiting for user to sign transaction...', success = 'Success!' } = { | |
...toasts | |
} | |
const { algodClient, signTransactions } = useWallet() | |
const lookupByTxnId = useExplorerStore((state) => state.lookupByTxnId) | |
const toastIdRef = React.useRef(`toast-${Date.now()}-${Math.random()}`) | |
const TOAST_ID = toastIdRef.current | |
const signAndSendTransactions = async (params: TParams): Promise<SendTxnsResponse> => { | |
const { data } = await mutationFn(params) | |
if (typeof data !== 'string') { | |
throw new Error('Failed to fetch transactions') | |
} | |
const nfdTxnsArray = JSON.parse(data) as NFDTransactionsArray | |
const encodedTxns = encodeNFDTransactionsArray(nfdTxnsArray) | |
const signTxnsResult = await signTransactions(encodedTxns) | |
const signedTxns = nfdTxnsArray.map((nfdTxn, index) => | |
nfdTxn[0] === 's' ? encodedTxns[index] : signTxnsResult[index] | |
) as Uint8Array[] | |
toast.loading('Sending transaction...', { id: TOAST_ID }) | |
const { lastRound, firstRound } = algosdk.decodeSignedTransaction(signedTxns[0]).txn | |
const waitRounds = lastRound - firstRound | |
const { txId } = await algodClient.sendRawTransaction(signedTxns).do() | |
const confirmation = await algosdk.waitForConfirmation(algodClient, txId, waitRounds) | |
return { | |
...(confirmation as PendingTransactionResponse), | |
txId | |
} | |
} | |
return useMutation<SendTxnsResponse, unknown, TParams, TContext>( | |
(params: TParams) => { | |
return signAndSendTransactions(params) | |
}, | |
{ | |
onMutate: (params) => { | |
console.info('Sending transaction...') | |
const toastMsg = typeof loading === 'string' ? loading : loading({ params }) | |
toast.loading(toastMsg, { id: TOAST_ID }) | |
return onMutate?.(params) | |
}, | |
onSuccess: (data, params, context) => { | |
console.info(`Transaction ${data.txId} confirmed in round ${data['confirmed-round']}`) | |
const toastMsg = | |
typeof success === 'string' | |
? success | |
: success({ data, params, context, explorerLink: lookupByTxnId(data.txId) }) | |
toast.success(toastMsg, { | |
id: TOAST_ID, | |
duration: 5000 // 5 seconds | |
}) | |
onSuccess?.(data, params, context) | |
}, | |
onError: (error, params, context) => { | |
toast.dismiss(TOAST_ID) | |
onError?.(error, params, context) | |
}, | |
onSettled: (data, error, params, context) => { | |
onSettled?.(data, error, params, context) | |
} | |
} | |
) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment