@smartinvoicexyz/hooks
Version:
Unified source for React hooks used across the Smart Invoice protocol.
146 lines (145 loc) • 6.26 kB
JavaScript
import { INVOICE_VERSION, LOG_TYPE, SMART_INVOICE_FACTORY_ABI, TOASTS, } from '@smartinvoicexyz/constants';
import { waitForSubgraphSync } from '@smartinvoicexyz/graphql';
import { errorToastHandler, getInvoiceFactoryAddress, getWrappedNativeToken, parseTxLogs, uriToDocument, } from '@smartinvoicexyz/utils';
import _ from 'lodash';
import { useCallback, useMemo, useState } from 'react';
import { encodeAbiParameters, parseUnits, toHex } from 'viem';
import { useChainId, usePublicClient, useSimulateContract, useWriteContract, } from 'wagmi';
import { useDetailsPin } from './useDetailsPin';
import { useFetchTokens } from './useFetchTokens';
const INSTANT_TYPE = toHex('instant', { size: 32 });
export const useInstantCreate = ({ invoiceForm, toast, onTxSuccess, }) => {
const chainId = useChainId();
const invoiceFactory = getInvoiceFactoryAddress(chainId);
const [waitingForTx, setWaitingForTx] = useState(false);
const { getValues } = invoiceForm;
const invoiceValues = getValues();
const { client, provider, token, title, description, document, startDate, endDate, deadline, paymentDue, lateFee, lateFeeTimeInterval, } = _.pick(invoiceValues, [
'client',
'provider',
'token',
'title',
'description',
'document',
'startDate',
'endDate',
'deadline',
'paymentDue',
'lateFee',
'lateFeeTimeInterval',
]);
const detailsData = useMemo(() => {
const now = Math.floor(new Date().getTime() / 1000);
const end = endDate ? Math.floor(new Date(endDate).getTime() / 1000) : now;
const start = startDate
? Math.floor(new Date(startDate).getTime() / 1000)
: now;
return {
version: INVOICE_VERSION,
id: _.join([title, now, INVOICE_VERSION], '-'),
title,
description,
documents: document ? [uriToDocument(document)] : [],
startDate: start,
endDate: end,
createdAt: now,
};
}, [title, description, document, startDate, endDate]);
const { data: details, isLoading: detailsLoading } = useDetailsPin(detailsData);
const { data: tokens } = useFetchTokens();
const tokenMetadata = _.find(tokens, t => t.address.toLowerCase() === token?.toLowerCase() && t.chainId === chainId);
const escrowData = useMemo(() => {
const wrappedNativeToken = getWrappedNativeToken(chainId);
if (!client || !deadline || !wrappedNativeToken || !details) {
return '0x';
}
let lateFeeTimeIntervalSeconds = BigInt(0);
if (lateFeeTimeInterval) {
lateFeeTimeIntervalSeconds = BigInt(
// days to milliseconds
Number(lateFeeTimeInterval) * 24 * 60 * 60 * 1000);
}
const encodedParams = encodeAbiParameters([
{ type: 'address' }, // _client,
{ type: 'address' }, // _token,
{ type: 'uint256' }, // _deadline, // exact time when late fee kicks in
{ type: 'bytes32' }, // _details,
{ type: 'address' }, // _getWrappedNativeToken,
{ type: 'uint256' }, // _lateFee,
{ type: 'uint256' }, // _lateFeeTimeInterval
], [
client,
token, // address _token (payment token address)
BigInt(new Date(deadline).getTime() / 1000), // deadline
details, // bytes32 _details detailHash
wrappedNativeToken,
parseUnits(lateFee || '0', tokenMetadata?.decimals || 18), // late fee in payment token per interval
lateFeeTimeIntervalSeconds, // late fee time interval convert from some days duration to seconds
]);
return encodedParams;
}, [
client,
token,
tokenMetadata,
deadline,
details,
lateFee,
lateFeeTimeInterval,
]);
const paymentAmount = parseUnits(_.toString(paymentDue), tokenMetadata?.decimals ?? 18);
const { data, error: prepareError, isLoading: prepareLoading, } = useSimulateContract({
address: invoiceFactory,
chainId,
abi: SMART_INVOICE_FACTORY_ABI,
functionName: 'create',
args: [provider, [paymentAmount], escrowData, INSTANT_TYPE],
query: {
enabled: !!invoiceFactory && !!chainId && !!provider && !!escrowData,
},
});
const publicClient = usePublicClient();
const { writeContractAsync, error: writeError, isPending: isLoading, } = useWriteContract({
mutation: {
onSuccess: async (hash) => {
// wait for tx to confirm on chain
setWaitingForTx(true);
toast.loading(TOASTS.useInvoiceCreate.waitingForTx);
const txData = await publicClient?.waitForTransactionReceipt({
hash,
});
if (!txData)
return;
// wait for subgraph to index
const localInvoiceId = parseTxLogs(LOG_TYPE.Factory, txData, 'LogNewInvoice', 'invoice');
if (!localInvoiceId)
return;
toast.loading(TOASTS.useInvoiceCreate.waitingForIndex);
if (txData && publicClient) {
await waitForSubgraphSync(publicClient.chain.id, txData.blockNumber);
}
setWaitingForTx(false);
// pass back to component for further processing
onTxSuccess?.(localInvoiceId);
},
onError: (error) => errorToastHandler('useInstantCreate', error, toast),
},
});
const writeAsync = useCallback(async () => {
try {
if (!data) {
throw new Error('simulation data is not available');
}
return writeContractAsync(data.request);
}
catch (error) {
errorToastHandler('useInstantCreate', error, toast);
return undefined;
}
}, [writeContractAsync, data]);
return {
prepareError,
writeAsync,
writeError,
isLoading: isLoading || waitingForTx || prepareLoading || detailsLoading,
};
};