UNPKG

@coin-voyage/paykit

Version:

Seamless crypto payments. Onboard users from any chain, any coin into your app with one click.

107 lines (106 loc) 4.34 kB
import { usePrepareTransaction } from "@coin-voyage/crypto/hooks"; import { PaymentRail, StepKind, } from "@coin-voyage/shared/types"; import { assert } from "@coin-voyage/shared/utils"; import { useBackendApi } from "../components/contexts/api"; import { fetchPaymentDetails } from "../lib/api/payment-details"; import { isCryptoPaymentData, isDepositStepData } from "@coin-voyage/shared/payment"; async function executeWalletStep({ actions, paymentData, senderAddr, step, }) { if (step.kind === StepKind.KIND_TRANSACTION) { assert(isCryptoPaymentData(step.data), "Transaction step is missing executable wallet data"); return actions.execute({ from: senderAddr, paymentData: step.data, }); } assert(step.kind === StepKind.KIND_DEPOSIT, "Unsupported wallet payment step"); assert(isDepositStepData(step.data), "Deposit step is missing a deposit address"); return actions.execute({ amount: BigInt(paymentData.src.currency_amount.raw_amount), from: senderAddr, to: step.data.deposit_address, chainId: paymentData.src.chain_id, token: step.data.currency.address ? { address: step.data.currency.address, decimals: paymentData.src.decimals } : undefined, }); } async function executeWalletPayment({ actions, paymentData, senderAddr, log, }) { let lastTxHash; let sourceTxHash; for (const [index, step] of paymentData.steps.entries()) { if (step.rail !== PaymentRail.CRYPTO) { throw new Error(`Unsupported payment rail for wallet execution: ${step.rail}`); } log(`[PAY-WITH-TOKEN] Executing step ${index + 1}/${paymentData.steps.length}: ${step.kind}`); const txHash = await executeWalletStep({ actions, paymentData, senderAddr, log, step, }); if (!txHash) { return undefined; } if (!sourceTxHash && step.kind === StepKind.KIND_DEPOSIT) { sourceTxHash = txHash; } lastTxHash = txHash; log(`[PAY-WITH-TOKEN] Step ${index + 1}/${paymentData.steps.length} hash: ${txHash}`); } const txHash = sourceTxHash ?? lastTxHash; assert(Boolean(txHash), "Payment execution did not produce a transaction hash"); return txHash; } export function usePayFromWallet({ senderAddr, payOrder, setPayOrder, chainType, log }) { const actions = usePrepareTransaction(chainType); const api = useBackendApi(); const payFromWallet = async (currency) => { assert(payOrder != undefined, "PayOrder is required"); assert(senderAddr != undefined, "Sender address is required"); assert(actions != undefined, "Transaction actions must be defined"); const quoteId = currency.quote_id; const params = quoteId ? { payorder_id: payOrder.id, quote_id: quoteId } : { payorder_id: payOrder.id, source_currency: { address: currency.address, chain_id: currency.chain_id, }, refund_address: senderAddr, }; const paymentDetails = await fetchPaymentDetails(api, params, payOrder); const paymentData = paymentDetails.data; log(`[PAY-WITH-TOKEN] Final Quote for Order: ${JSON.stringify(paymentDetails)}, params: ${JSON.stringify(params)}`); try { const txHash = await executeWalletPayment({ actions, paymentData, senderAddr, log, }); if (!txHash) { return undefined; } const nextPaymentData = { ...paymentData, source_tx_hash: txHash, }; setPayOrder({ ...payOrder, payment: nextPaymentData, status: paymentDetails.status, }); log(`[PAY-WITH-TOKEN] Transaction hash: ${txHash}`); return txHash; } catch (e) { if (e.message?.includes("rejected")) return undefined; log(`[PAY-WITH-TOKEN] Error sending token: ${e}`); throw e; } }; return { payFromWallet }; }