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