@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
JavaScript
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 };
}