UNPKG

@pushchain/core

Version:

Push Chain is a true universal L1 that is 100% EVM compatible. It allows developers to deploy once and make their apps instantly compatible with users from all other L1s (Ethereum, Solana, etc) with zero on-chain code change.

411 lines 18.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createUniversalSigner = createUniversalSigner; exports.toUniversalFromKeypair = toUniversalFromKeypair; exports.construct = construct; exports.toUniversal = toUniversal; const tslib_1 = require("tslib"); const viem_1 = require("viem"); const nacl = tslib_1.__importStar(require("tweetnacl")); const web3_js_1 = require("@solana/web3.js"); const enums_1 = require("../../constants/enums"); const ethers_1 = require("ethers"); const chain_1 = require("../../constants/chain"); const anchor_1 = require("@coral-xyz/anchor"); /** * Creates a `UniversalSigner` object for signing messages and transactions * on any supported chain. * * @param {Object} params - The signer configuration object. * @param {string} params.address - The signer's address. * @param {(data: Uint8Array) => Promise<Uint8Array>} params.signMessage - Required function to sign messages. * @param {(data: Uint8Array) => Promise<Uint8Array>} [params.signAndSendTransaction] - Required function to sign and send transactions. * @param {CHAIN} params.chain - The chain the signer will operate on. * @returns {UniversalSigner} A signer object with chain metadata. * * @example * const signer = createUniversalSigner({ * chain: CHAIN.ETHEREUM_SEPOLIA * address: "0xabc...", * signMessage: async (data) => sign(data), * signAndSendTransaction: async (data) => signRawTx(data), * }); */ function createUniversalSigner({ account, signMessage, signAndSendTransaction, signTypedData, }) { return { account, signMessage, signAndSendTransaction, signTypedData, }; } /** * Creates a UniversalSigner from either a viem, ethers, solana WalletClient or Account instance. * * @param {WalletClient | Account | Keypair | ethers.HDNodeWallet} clientOrAccount - The viem WalletClient or Account instance * @param {CHAIN} chain - The chain the signer will operate on * @returns {Promise<UniversalSigner>} A signer object configured for the specified chain */ function toUniversalFromKeypair(clientOrAccount_1, _a) { return tslib_1.__awaiter(this, arguments, void 0, function* (clientOrAccount, { chain, library }) { let address; let signMessage; let signAndSendTransaction; let signTypedData; // Check if signer has UID='custom', then we take signMessage, signAndSendTransaction, signTypedData, chain and address from the CustomUniversalSigner. // If ViemSigner, convert ViemSigner to UniversalSigner. switch (library) { case enums_1.LIBRARY.ETHEREUM_ETHERSV6: { if (typeof clientOrAccount.signMessage !== 'function' || typeof clientOrAccount.sendTransaction !== 'function' || typeof clientOrAccount.getAddress !== 'function') { throw new Error('Expected an object with signMessage, sendTransaction, getAddress methods for ETHEREUM_ETHERSV6 library'); } const wallet = clientOrAccount; if (!wallet.provider) { throw new Error('ethers.Wallet must have a provider attached'); } // fetch on-chain chainId const { chainId } = yield wallet.provider.getNetwork(); if (chainId.toString() !== chain.split(':')[1]) { throw new Error(`Chain mismatch: wallet is on ${chainId}, expected ${chain}`); } address = yield wallet.getAddress(); // raw bytes → ethers.signMessage → hex → back to bytes signMessage = (data) => tslib_1.__awaiter(this, void 0, void 0, function* () { const sigHex = yield wallet.signMessage(data); return (0, ethers_1.getBytes)(sigHex); }); // raw unsigned tx bytes → hex → parse → signAndSendTransaction → bytes signAndSendTransaction = (raw) => tslib_1.__awaiter(this, void 0, void 0, function* () { const unsignedHex = (0, ethers_1.hexlify)(raw); const tx = ethers_1.ethers.Transaction.from(unsignedHex); const txResponse = yield wallet.sendTransaction(tx); return (0, viem_1.hexToBytes)(txResponse.hash); }); // EIP-712 typed data → _signTypedData → hex → bytes signTypedData = (_a) => tslib_1.__awaiter(this, [_a], void 0, function* ({ domain, types, primaryType, message }) { const sigHex = yield wallet.signTypedData(domain, types, message); return (0, ethers_1.getBytes)(sigHex); }); break; } case enums_1.LIBRARY.ETHEREUM_VIEM: { const wallet = clientOrAccount; address = (yield wallet.getAddresses())[0]; signMessage = (data) => tslib_1.__awaiter(this, void 0, void 0, function* () { const hexSig = yield clientOrAccount.signMessage({ account: wallet.account || address, message: { raw: data }, }); return (0, viem_1.hexToBytes)(hexSig); }); signAndSendTransaction = (unsignedTx) => tslib_1.__awaiter(this, void 0, void 0, function* () { const tx = (0, viem_1.parseTransaction)((0, viem_1.bytesToHex)(unsignedTx)); const txHash = yield wallet.sendTransaction(tx); return (0, viem_1.hexToBytes)(txHash); }); signTypedData = (_a) => tslib_1.__awaiter(this, [_a], void 0, function* ({ domain, types, primaryType, message }) { const hexSig = yield wallet.signTypedData({ domain, types, primaryType, message, account: clientOrAccount.account || address, }); return (0, viem_1.hexToBytes)(hexSig); }); break; } case enums_1.LIBRARY.SOLANA_WEB3JS: { const keypair = clientOrAccount; if (chain !== enums_1.CHAIN.SOLANA_MAINNET && chain !== enums_1.CHAIN.SOLANA_TESTNET && chain !== enums_1.CHAIN.SOLANA_DEVNET) { throw new Error('Invalid chain for Solana Keypair'); } address = keypair.publicKey.toBase58(); signMessage = (data) => tslib_1.__awaiter(this, void 0, void 0, function* () { return nacl.sign.detached(data, keypair.secretKey); }); // ✅ Sign and send the transaction to Solana network signAndSendTransaction = (unsignedTx) => tslib_1.__awaiter(this, void 0, void 0, function* () { const endpoint = chain_1.CHAIN_INFO[chain].defaultRPC[0]; const connection = new web3_js_1.Connection(endpoint, 'confirmed'); let txHash; // Prefer handling as a versioned transaction; fall back to legacy if that fails. try { const vtx = web3_js_1.VersionedTransaction.deserialize(unsignedTx); vtx.sign([keypair]); const rawVtx = vtx.serialize(); txHash = yield connection.sendRawTransaction(rawVtx); } catch (_a) { const tx = web3_js_1.Transaction.from(unsignedTx); const messageBytes = tx.serializeMessage(); const signature = nacl.sign.detached(messageBytes, keypair.secretKey); tx.addSignature(new web3_js_1.PublicKey(keypair.publicKey.toBase58()), Buffer.from(signature)); const rawTx = tx.serialize(); txHash = yield connection.sendRawTransaction(rawTx); } return anchor_1.utils.bytes.bs58.decode(txHash); }); signTypedData = () => tslib_1.__awaiter(this, void 0, void 0, function* () { throw new Error('Typed data signing is not supported for Solana'); }); break; } default: { throw new Error(`Unsupported library: ${library}`); } } const universalSigner = { account: { address, chain, }, signMessage, signAndSendTransaction, signTypedData, }; return createUniversalSigner(universalSigner); }); } // `signTypedData` is only mandatory for EVM Signers. For Solana, this is not necessary. function construct(account, options) { const { signMessage, signAndSendTransaction, signTypedData } = options; if (signTypedData && (account.chain === enums_1.CHAIN.SOLANA_MAINNET || account.chain === enums_1.CHAIN.SOLANA_TESTNET || account.chain === enums_1.CHAIN.SOLANA_DEVNET)) { throw new Error('Typed data signing is not supported for Solana'); } return { signerId: 'CustomGeneratedSigner', account, signMessage, signAndSendTransaction, signTypedData, }; } function toUniversal(signer) { return tslib_1.__awaiter(this, void 0, void 0, function* () { if ('signerId' in signer) { return createUniversalSigner(signer); } let skeleton; if (!isViemSigner(signer)) { const wallet = signer; if (!wallet.provider) { throw new Error('ethers.Wallet must have a provider attached to determine chain'); } // Check if _signTypedData property is present to determine if it's EthersV5 or EthersV6 if ('_signTypedData' in wallet) { skeleton = yield generateSkeletonFromEthersV5(wallet); } else { skeleton = yield generateSkeletonFromEthersV6(wallet); } } else { skeleton = yield generateSkeletonFromViem(signer); } return createUniversalSigner(skeleton); }); } function generateSkeletonFromEthersV5(signer) { return tslib_1.__awaiter(this, void 0, void 0, function* () { const address = yield signer.getAddress(); const { chainId } = yield signer.provider.getNetwork(); // Map chainId to CHAIN enum - this is a simplified mapping let chain; switch (chainId.toString()) { case '11155111': chain = enums_1.CHAIN.ETHEREUM_SEPOLIA; break; case '421614': chain = enums_1.CHAIN.ARBITRUM_SEPOLIA; break; case '84532': chain = enums_1.CHAIN.BASE_SEPOLIA; break; case '97': chain = enums_1.CHAIN.BNB_TESTNET; break; case '1': chain = enums_1.CHAIN.ETHEREUM_MAINNET; break; case '9': chain = enums_1.CHAIN.PUSH_MAINNET; break; case '42101': chain = enums_1.CHAIN.PUSH_TESTNET; break; case '9000': chain = enums_1.CHAIN.PUSH_LOCALNET; break; default: throw new Error(`Unsupported chainId: ${chainId}`); } if (!Object.values(enums_1.CHAIN).includes(chain)) { throw new Error(`Unsupported chainId: ${chainId}`); } return { signerId: `EthersSignerV5-${address}`, account: { address, chain }, signMessage: (data) => tslib_1.__awaiter(this, void 0, void 0, function* () { const sigHex = yield signer.signMessage(data); return (0, ethers_1.getBytes)(sigHex); }), // raw unsigned tx bytes → hex → parse → signTransaction → bytes signAndSendTransaction: (raw) => tslib_1.__awaiter(this, void 0, void 0, function* () { const unsignedHex = (0, ethers_1.hexlify)(raw); const tx = ethers_1.ethers.Transaction.from(unsignedHex); const txResponse = yield signer.sendTransaction(tx); return (0, viem_1.hexToBytes)(txResponse.hash); }), signTypedData: (_a) => tslib_1.__awaiter(this, [_a], void 0, function* ({ domain, types, primaryType, message }) { const sigHex = yield signer._signTypedData(domain, types, message); return (0, ethers_1.getBytes)(sigHex); }), }; }); } function generateSkeletonFromEthersV6(signer) { return tslib_1.__awaiter(this, void 0, void 0, function* () { const address = yield signer.getAddress(); const { chainId } = yield signer.provider.getNetwork(); // Map chainId to CHAIN enum - this is a simplified mapping let chain; switch (chainId.toString()) { case '11155111': chain = enums_1.CHAIN.ETHEREUM_SEPOLIA; break; case '421614': chain = enums_1.CHAIN.ARBITRUM_SEPOLIA; break; case '84532': chain = enums_1.CHAIN.BASE_SEPOLIA; break; case '97': chain = enums_1.CHAIN.BNB_TESTNET; break; case '1': chain = enums_1.CHAIN.ETHEREUM_MAINNET; break; case '9': chain = enums_1.CHAIN.PUSH_MAINNET; break; case '42101': chain = enums_1.CHAIN.PUSH_TESTNET; break; case '9000': chain = enums_1.CHAIN.PUSH_LOCALNET; break; default: throw new Error(`Unsupported chainId: ${chainId}`); } if (!Object.values(enums_1.CHAIN).includes(chain)) { throw new Error(`Unsupported chainId: ${chainId}`); } return { signerId: `EthersSignerV6-${address}`, account: { address, chain }, signMessage: (data) => tslib_1.__awaiter(this, void 0, void 0, function* () { const sigHex = yield signer.signMessage(data); return (0, ethers_1.getBytes)(sigHex); }), // raw unsigned tx bytes → hex → parse → signTransaction → bytes signAndSendTransaction: (raw) => tslib_1.__awaiter(this, void 0, void 0, function* () { const unsignedHex = (0, ethers_1.hexlify)(raw); const tx = ethers_1.ethers.Transaction.from(unsignedHex); const txResponse = yield signer.sendTransaction(tx); return (0, viem_1.hexToBytes)(txResponse.hash); }), signTypedData: (_a) => tslib_1.__awaiter(this, [_a], void 0, function* ({ domain, types, primaryType, message }) { const sigHex = yield signer.signTypedData(domain, types, message); return (0, ethers_1.getBytes)(sigHex); }), }; }); } function isViemSigner(signer) { return (typeof signer.signTypedData === 'function' && typeof signer.getChainId === 'function'); } function generateSkeletonFromViem(signer) { return tslib_1.__awaiter(this, void 0, void 0, function* () { if (!signer.account) { throw new Error('Signer account is not set'); } const address = signer.account['address']; const chainId = yield signer.getChainId(); // Map chainId to CHAIN enum let chain; switch (chainId.toString()) { case '11155111': chain = enums_1.CHAIN.ETHEREUM_SEPOLIA; break; case '421614': chain = enums_1.CHAIN.ARBITRUM_SEPOLIA; break; case '84532': chain = enums_1.CHAIN.BASE_SEPOLIA; break; case '97': chain = enums_1.CHAIN.BNB_TESTNET; break; case '1': chain = enums_1.CHAIN.ETHEREUM_MAINNET; break; case '9': chain = enums_1.CHAIN.PUSH_MAINNET; break; case '42101': chain = enums_1.CHAIN.PUSH_TESTNET; break; case '9000': chain = enums_1.CHAIN.PUSH_LOCALNET; break; default: throw new Error(`Unsupported chainId: ${chainId}`); } return { signerId: `ViemSigner-${address}`, account: { address, chain, }, signMessage: (data) => tslib_1.__awaiter(this, void 0, void 0, function* () { const hexSig = yield signer.signMessage({ account: address, message: { raw: data }, }); return (0, viem_1.hexToBytes)(hexSig); }), signAndSendTransaction: (unsignedTx) => tslib_1.__awaiter(this, void 0, void 0, function* () { // For viem signers, we need to handle transaction signing differently // Since the ViemSignerType doesn't have signTransaction, we'll need to // use the account's signTransaction method if available if (signer.account['signTransaction']) { const tx = (0, viem_1.parseTransaction)((0, viem_1.bytesToHex)(unsignedTx)); const txHash = yield signer.sendTransaction(tx); return (0, viem_1.hexToBytes)(txHash); } throw new Error('Transaction signing not supported for this viem signer type'); }), signTypedData: (_a) => tslib_1.__awaiter(this, [_a], void 0, function* ({ domain, types, primaryType, message }) { const hexSig = yield signer.signTypedData({ domain, types, primaryType, message, account: signer.account || address, }); return (0, viem_1.hexToBytes)(hexSig); }), }; }); } //# sourceMappingURL=signer.js.map