UNPKG

@bit-gpt/h402

Version:

BitGPT's 402 open protocol for blockchain-native payments

218 lines 7.61 kB
import { stringToHex } from "../../../shared/encoding.js"; import { evm } from "../../../shared/index.js"; import { encodeFunctionData } from "viem"; const ERC20_ABI = [ { name: "transfer", type: "function", inputs: [ { name: "to", type: "address" }, { name: "value", type: "uint256" }, ], outputs: [{ name: "success", type: "bool" }], stateMutability: "nonpayable", }, ]; // Utility function to detect wallet type function isLocalSigner(client) { return !!(client.account && typeof client.account.signMessage === "function"); } // Unified message signing function async function signResourceMessage(client, from, resource) { const messageToSign = resource ?? `402 signature ${Date.now()}`; if (isLocalSigner(client)) { // Axios interceptor path - use local signing with hex encoding const messageHex = `0x${stringToHex(messageToSign)}`; return (await client.account?.signMessage?.({ message: { raw: messageHex }, })); } else { // MetaMask path - use client signing return await client.signMessage({ account: from, message: messageToSign, }); } } // Utility function to create transaction request async function createTransactionRequest(client, from, to, value, tokenAddress, networkId) { const chain = evm.getChain(networkId); const account = from; // Get current gas price and nonce in parallel const [gasPrice, nonce] = await Promise.all([ client.getGasPrice(), client.getTransactionCount({ address: account }), ]); let baseRequest; if (tokenAddress === evm.ZERO_ADDRESS) { // Native token transfer baseRequest = { account, to: to, value, gasPrice, nonce, chain, }; } else { // ERC20 token transfer const data = encodeFunctionData({ abi: ERC20_ABI, functionName: "transfer", args: [to, value], }); baseRequest = { account, to: tokenAddress, data, gasPrice, nonce, chain, }; } // Estimate gas for the transaction const gas = await client.estimateGas({ account: baseRequest.account, to: baseRequest.to, value: baseRequest.value, data: baseRequest.data, }); return { ...baseRequest, gas, }; } async function signAuthorization(client, { from, to, value, }, { tokenAddress, networkId, estimatedProcessingTime, }) { try { const nonce = evm.createNonce(); const validAfter = BigInt(Math.floor(Date.now() / 1000) - 5 // 1 block (2s) before to account for block timestamping ); const validBefore = BigInt(Math.floor(Date.now() / 1000 + (estimatedProcessingTime ?? 30))); const { domain } = await client.getEip712Domain({ address: tokenAddress?.toLowerCase(), }); const data = { types: { TransferWithAuthorization: [ { name: "from", type: "address" }, { name: "to", type: "address" }, { name: "value", type: "uint256" }, { name: "validAfter", type: "uint256" }, { name: "validBefore", type: "uint256" }, { name: "nonce", type: "bytes32" }, ], }, domain: { name: domain.name, version: domain.version, chainId: parseInt(networkId), verifyingContract: tokenAddress?.toLowerCase(), }, primaryType: "TransferWithAuthorization", message: { from, to, value, validAfter, validBefore, nonce, }, }; const signature = await client.signTypedData({ ...data, account: from, }); return { type: "signature", signature, nonce, version: domain.version, validAfter, validBefore, }; } catch (error) { throw new Error(`Failed to sign authorization: ${error instanceof Error ? error.message : String(error)}`); } } async function signTransaction(client, { from, to, value, }, { networkId, tokenAddress, }) { try { // Create transaction request const transactionRequest = await createTransactionRequest(client, from, to, value, tokenAddress, networkId); console.log("Attempting to sign transaction"); let signedTransaction; if (isLocalSigner(client)) { // direct local signing, for Axios interceptor signedTransaction = (await client.account?.signTransaction?.(transactionRequest)); } else { console.log("Not a local signer"); throw new Error("MetaMask doesn't support sign-only transactions"); } console.log("Transaction signed successfully"); return { signedTransaction, }; } catch (error) { throw new Error(`Failed to sign transaction: ${error instanceof Error ? error.message : String(error)}`); } } async function signAndSendTransaction(client, { from, to, value, }, { networkId, tokenAddress, }) { try { let txHash; if (isLocalSigner(client)) { // Local signer, for Axios interceptor path - sign then broadcast // Not really supposed to take that path, but kept just in case const transactionRequest = await createTransactionRequest(client, from, to, value, tokenAddress, networkId); // Sign the transaction locally const signedTx = await client.signTransaction(transactionRequest); // Broadcast the signed transaction txHash = await client.sendRawTransaction({ serializedTransaction: signedTx, }); } else { // MetaMask path - direct sendTransaction const chain = evm.getChain(networkId); if (tokenAddress === evm.ZERO_ADDRESS) { txHash = await client.sendTransaction({ account: from, to: to, value, chain, }); } else { const data = encodeFunctionData({ abi: ERC20_ABI, functionName: "transfer", args: [to, value], }); txHash = await client.sendTransaction({ account: from, to: tokenAddress, data, chain, }); } } // Wait for transaction confirmation const receipt = await client.waitForTransactionReceipt({ hash: txHash, }); if (receipt.status !== "success") { throw new Error(`Transaction failed with status: ${receipt.status}`); } return { txHash, }; } catch (error) { throw new Error(`Failed to sign and send transaction: ${error instanceof Error ? error.message : String(error)}`); } } export { signAuthorization, signAndSendTransaction, signTransaction, signResourceMessage, }; //# sourceMappingURL=sign.js.map