UNPKG

chaingate

Version:

Multi-chain cryptocurrency SDK for TypeScript — unified API for Bitcoin, Ethereum, Litecoin, Dogecoin, Bitcoin Cash, Polygon, Arbitrum, and any EVM-compatible chain. Create wallets, query balances, send transactions, and manage tokens and NFTs across UTXO

151 lines (150 loc) 5.84 kB
"use strict"; /** * EIP-1559 (type-2) transaction serialization and signing. * * Produces the signed raw transaction hex ready for broadcast. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.signEip1559Transaction = signEip1559Transaction; exports.signLegacyTransaction = signLegacyTransaction; const secp256k1_js_1 = require("@noble/curves/secp256k1.js"); const sha3_js_1 = require("@noble/hashes/sha3.js"); const rlp_1 = require("./rlp"); const encoding_1 = require("./encoding"); /** Converts a bigint to minimal big-endian bytes (no leading zeros). */ function bigintToBytes(n) { if (n === 0n) return new Uint8Array(0); let hex = n.toString(16); if (hex.length % 2 !== 0) hex = '0' + hex; return (0, encoding_1.hexToBytes)(hex); } /** * Builds the RLP-encoded EIP-1559 transaction payload (unsigned) for signing. * * Format: 0x02 || RLP([chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, * gasLimit, to, value, data, accessList]) */ function encodeUnsigned(tx) { const fields = [ bigintToBytes(tx.chainId), bigintToBytes(tx.nonce), bigintToBytes(tx.maxPriorityFeePerGas), bigintToBytes(tx.maxFeePerGas), bigintToBytes(tx.gasLimit), (0, encoding_1.hexToBytes)(tx.to), // 20 bytes bigintToBytes(tx.value), tx.data === '0x' || tx.data === '' ? new Uint8Array(0) : (0, encoding_1.hexToBytes)(tx.data), [], // accessList — empty for simple transfers ]; const rlp = (0, rlp_1.rlpEncode)(fields); // Prepend the EIP-2718 type byte (0x02). const envelope = new Uint8Array(1 + rlp.length); envelope[0] = 0x02; envelope.set(rlp, 1); return envelope; } /** * Signs an EIP-1559 transaction and returns the raw signed transaction hex * ready for broadcast (with `0x` prefix). * * @param tx - Transaction parameters. * @param privateKey - 32-byte private key. * @returns Signed raw transaction as a hex string with `0x` prefix. */ function signEip1559Transaction(tx, privateKey) { const unsigned = encodeUnsigned(tx); const hash = (0, sha3_js_1.keccak_256)(unsigned); // Sign with secp256k1. Use 'recovered' format to get [recovery, r, s]. // prehash: false because we already hashed the payload ourselves. const sigBytes = secp256k1_js_1.secp256k1.sign(hash, privateKey, { prehash: false, lowS: true, format: 'recovered', }); // 'recovered' format: 1 byte recovery || 32 bytes r || 32 bytes s = 65 bytes const recovery = sigBytes[0]; // 0 or 1 const r = sigBytes.subarray(1, 33); const s = sigBytes.subarray(33, 65); // Strip leading zeros from r and s for RLP encoding. const rTrimmed = trimLeadingZeros(r); const sTrimmed = trimLeadingZeros(s); // Signed payload: 0x02 || RLP([chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, // gasLimit, to, value, data, accessList, v, r, s]) const fields = [ bigintToBytes(tx.chainId), bigintToBytes(tx.nonce), bigintToBytes(tx.maxPriorityFeePerGas), bigintToBytes(tx.maxFeePerGas), bigintToBytes(tx.gasLimit), (0, encoding_1.hexToBytes)(tx.to), bigintToBytes(tx.value), tx.data === '0x' || tx.data === '' ? new Uint8Array(0) : (0, encoding_1.hexToBytes)(tx.data), [], // accessList bigintToBytes(BigInt(recovery)), rTrimmed, sTrimmed, ]; const rlp = (0, rlp_1.rlpEncode)(fields); const signed = new Uint8Array(1 + rlp.length); signed[0] = 0x02; signed.set(rlp, 1); return '0x' + (0, encoding_1.bytesToHex)(signed); } /** * Signs a legacy (type-0) transaction and returns the raw signed transaction hex * ready for broadcast (with `0x` prefix). * * Uses EIP-155 replay protection. * * @param tx - Transaction parameters. * @param privateKey - 32-byte private key. * @returns Signed raw transaction as a hex string with `0x` prefix. */ function signLegacyTransaction(tx, privateKey) { // EIP-155 unsigned payload: RLP([nonce, gasPrice, gasLimit, to, value, data, chainId, 0, 0]) const unsignedFields = [ bigintToBytes(tx.nonce), bigintToBytes(tx.gasPrice), bigintToBytes(tx.gasLimit), (0, encoding_1.hexToBytes)(tx.to), bigintToBytes(tx.value), tx.data === '0x' || tx.data === '' ? new Uint8Array(0) : (0, encoding_1.hexToBytes)(tx.data), bigintToBytes(tx.chainId), new Uint8Array(0), // 0 for EIP-155 new Uint8Array(0), // 0 for EIP-155 ]; const rlpUnsigned = (0, rlp_1.rlpEncode)(unsignedFields); const hash = (0, sha3_js_1.keccak_256)(rlpUnsigned); const sigBytes = secp256k1_js_1.secp256k1.sign(hash, privateKey, { prehash: false, lowS: true, format: 'recovered', }); const recovery = sigBytes[0]; // 0 or 1 const r = sigBytes.subarray(1, 33); const s = sigBytes.subarray(33, 65); // EIP-155: v = chainId * 2 + 35 + recovery const v = tx.chainId * 2n + 35n + BigInt(recovery); const signedFields = [ bigintToBytes(tx.nonce), bigintToBytes(tx.gasPrice), bigintToBytes(tx.gasLimit), (0, encoding_1.hexToBytes)(tx.to), bigintToBytes(tx.value), tx.data === '0x' || tx.data === '' ? new Uint8Array(0) : (0, encoding_1.hexToBytes)(tx.data), bigintToBytes(v), trimLeadingZeros(r), trimLeadingZeros(s), ]; const rlpSigned = (0, rlp_1.rlpEncode)(signedFields); return '0x' + (0, encoding_1.bytesToHex)(rlpSigned); } /** Removes leading zero bytes from a byte array. */ function trimLeadingZeros(bytes) { let i = 0; while (i < bytes.length - 1 && bytes[i] === 0) i++; return i === 0 ? bytes : bytes.subarray(i); }