@bit-gpt/h402
Version:
BitGPT's 402 open protocol for blockchain-native payments
179 lines • 6.12 kB
JavaScript
import { decodeFunctionData, decodeEventLog, keccak256, toBytes, } from "viem";
import { evm } from "../../../shared/index.js";
import { parsePaymentRequirementsForAmount } from "../../../shared/parsePaymentRequirements.js";
/**
* Validates transfer data for both native and ERC20 token transfers
*/
async function validateTransferData(txData, paymentRequirements) {
const isNativeTransfer = paymentRequirements.tokenAddress === evm.ZERO_ADDRESS;
if (isNativeTransfer) {
return validateNativeTransfer(txData, paymentRequirements);
}
else {
return await validateERC20Transfer(txData, paymentRequirements);
}
}
/**
* Validates native token (ETH) transfers
*/
function validateNativeTransfer(txData, paymentRequirements) {
// Check transfer amount
const transferAmount = txData.value ?? 0n;
if (transferAmount < paymentRequirements.amountRequired) {
return {
isValid: false,
errorMessage: "Insufficient transfer amount",
};
}
// Check recipient address
if (txData.to?.toLowerCase() !==
paymentRequirements.payToAddress.toLowerCase()) {
return {
isValid: false,
errorMessage: "Invalid recipient address",
};
}
return {
isValid: true,
transferDetails: {
recipient: paymentRequirements.payToAddress,
amount: transferAmount,
isNativeTransfer: true,
},
};
}
/**
* Validates ERC20 token transfers
*/
async function validateERC20Transfer(txData, paymentRequirements) {
// Check if transaction is to the correct token contract
if (txData.to?.toLowerCase() !==
paymentRequirements.tokenAddress.toLowerCase()) {
return {
isValid: false,
errorMessage: "Invalid token contract address",
};
}
// Parse and validate the transfer function call
const transferData = txData.input || txData.data;
if (!transferData) {
return {
isValid: false,
errorMessage: "No transaction data found",
};
}
try {
const decodedInput = decodeFunctionData({
abi: [
{
name: "transfer",
type: "function",
inputs: [
{ name: "to", type: "address" },
{ name: "value", type: "uint256" },
],
outputs: [{ name: "success", type: "bool" }],
stateMutability: "nonpayable",
},
],
data: transferData,
});
if (decodedInput.functionName !== "transfer") {
return {
isValid: false,
errorMessage: "Not a transfer function call",
};
}
const [recipient, amount] = decodedInput.args;
// Validate recipient
if (recipient.toLowerCase() !==
paymentRequirements.payToAddress.toLowerCase()) {
return {
isValid: false,
errorMessage: "Invalid transfer recipient",
};
}
// Convert payment requirements to smallest unit if needed
const parsedRequirements = await parsePaymentRequirementsForAmount(paymentRequirements);
// Validate amount
if (amount < parsedRequirements.amountRequired) {
return {
isValid: false,
errorMessage: "Insufficient transfer amount",
};
}
return {
isValid: true,
transferDetails: {
recipient: recipient,
amount: amount,
isNativeTransfer: false,
},
};
}
catch (error) {
return {
isValid: false,
errorMessage: "Invalid transfer data: " +
(error instanceof Error ? error.message : String(error)),
};
}
}
/**
* Validates transfer event logs from transaction receipt
* Only used for ERC20 transfers in executed transactions
*/
function validateTransferEventLog(logs, paymentRequirements) {
const transferEvent = logs.find((log) => log.address.toLowerCase() ===
paymentRequirements.tokenAddress.toLowerCase() &&
log.topics[0] === keccak256(toBytes("Transfer(address,address,uint256)")));
if (!transferEvent) {
return {
isValid: false,
errorMessage: "No transfer event found",
};
}
try {
const decodedEvent = decodeEventLog({
abi: [
{
name: "Transfer",
type: "event",
inputs: [
{ name: "from", type: "address", indexed: true },
{ name: "to", type: "address", indexed: true },
{ name: "value", type: "uint256", indexed: false },
],
},
],
data: transferEvent.data,
topics: transferEvent.topics,
});
// Validate recipient and amount from event
if (decodedEvent.args.to.toLowerCase() !==
paymentRequirements.payToAddress.toLowerCase() ||
decodedEvent.args.value < paymentRequirements.amountRequired) {
return {
isValid: false,
errorMessage: "Transfer event data does not match requirements",
};
}
return {
isValid: true,
transferDetails: {
recipient: decodedEvent.args.to,
amount: decodedEvent.args.value,
isNativeTransfer: false,
},
};
}
catch (error) {
return {
isValid: false,
errorMessage: "Failed to decode transfer event: " +
(error instanceof Error ? error.message : String(error)),
};
}
}
export { validateTransferData, validateTransferEventLog, validateNativeTransfer, validateERC20Transfer, };
//# sourceMappingURL=validation.js.map