UNPKG

@pushchain/core

Version:
242 lines 11.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SvmClient = void 0; const tslib_1 = require("tslib"); const web3_js_1 = require("@solana/web3.js"); const anchor_1 = require("@coral-xyz/anchor"); const anchor_2 = require("@coral-xyz/anchor"); /** * Solana-compatible VM client for reading and writing SVM-based chains. */ class SvmClient { constructor({ rpcUrls }) { this.currentConnectionIndex = 0; if (!rpcUrls || rpcUrls.length === 0) { throw new Error('At least one RPC URL must be provided'); } this.connections = rpcUrls.map((url) => new web3_js_1.Connection(url, 'confirmed')); } /** * Executes a function with automatic fallback to next RPC endpoint on failure */ executeWithFallback(operation_1) { return tslib_1.__awaiter(this, arguments, void 0, function* (operation, operationName = 'operation') { let lastError = null; // Try each connection starting from current index for (let attempt = 0; attempt < this.connections.length; attempt++) { const connectionIndex = (this.currentConnectionIndex + attempt) % this.connections.length; const connection = this.connections[connectionIndex]; try { const result = yield operation(connection); // Success - update current connection index if we switched if (connectionIndex !== this.currentConnectionIndex) { //console.log(`Switched to RPC endpoint ${connectionIndex + 1}: ${this.rpcUrls[connectionIndex]}`); this.currentConnectionIndex = connectionIndex; } return result; } catch (error) { lastError = error; //console.warn(`RPC endpoint ${connectionIndex + 1} failed for ${operationName}:`, error); // If this was our last attempt, throw the error if (attempt === this.connections.length - 1) { break; } // Wait a bit before trying next endpoint yield new Promise((resolve) => setTimeout(resolve, 100)); } } throw new Error(`All RPC endpoints failed for ${operationName}. Last error: ${lastError === null || lastError === void 0 ? void 0 : lastError.message}`); }); } /** Build an AnchorProvider; if a signer is passed we wrap it, otherwise we give a no-op wallet. */ createProvider(connection, signer) { let wallet; if (signer) { const feePayerPk = new web3_js_1.PublicKey(signer.account.address); wallet = { publicKey: feePayerPk, payer: signer.account, signTransaction: (tx) => tslib_1.__awaiter(this, void 0, void 0, function* () { return tx; }), signAllTransactions: (txs) => tslib_1.__awaiter(this, void 0, void 0, function* () { return txs; }), }; } else { // dummy keypair + no-op sign const kp = web3_js_1.Keypair.generate(); wallet = { publicKey: kp.publicKey, payer: kp, signTransaction: (tx) => tslib_1.__awaiter(this, void 0, void 0, function* () { return tx; }), signAllTransactions: (txs) => tslib_1.__awaiter(this, void 0, void 0, function* () { return txs; }), }; } return new anchor_1.AnchorProvider(connection, wallet, { preflightCommitment: 'confirmed', }); } /** * Returns the balance (in lamports) of a Solana address. */ getBalance(address) { return tslib_1.__awaiter(this, void 0, void 0, function* () { const pubkey = new web3_js_1.PublicKey(address); const lamports = yield this.executeWithFallback((connection) => connection.getBalance(pubkey), 'getBalance'); return BigInt(lamports); }); } /** * Reads a full program account using Anchor IDL. * `functionName` must match the account layout name in the IDL. */ readContract(_a) { return tslib_1.__awaiter(this, arguments, void 0, function* ({ abi, functionName, args = [], }) { return this.executeWithFallback((connection) => tslib_1.__awaiter(this, void 0, void 0, function* () { const provider = this.createProvider(connection); // Anchor v0.31 constructor no longer takes programId // Use the IDL's embedded metadata.address instead const program = new anchor_1.Program(abi, provider); const pubkey = new web3_js_1.PublicKey(args[0]); // Cast account namespace to any to allow dynamic string const accountNamespace = program.account; const account = yield accountNamespace[functionName].fetch(pubkey); return account; }), 'readContract'); }); } /** * Sends a Solana transaction using a smart contract instruction. */ writeContract(_a) { return tslib_1.__awaiter(this, arguments, void 0, function* ({ abi, signer, functionName, args = [], accounts = {}, extraSigners = [], }) { // 1. Grab or build your RPC connection however your class manages it const connection = this.connections[this.currentConnectionIndex]; // 2. Create an AnchorProvider const provider = this.createProvider(connection); // 3. Instantiate the program (Anchor v0.31 will infer programId from IDL.metadata.address) const program = new anchor_1.Program(abi, provider); // 4. Convert any BigInt args into BN for Anchor const convertedArgs = args.map((arg) => typeof arg === 'bigint' ? new anchor_1.BN(arg.toString()) : arg); // 5. Build the method call let builder = convertedArgs.length > 0 ? program.methods[functionName](...convertedArgs) : program.methods[functionName](); if (Object.keys(accounts).length > 0) { builder = builder.accounts(accounts); } // 6. Get the actual instruction const instruction = yield builder.instruction(); // 7. Send it and return the tx signature return this.sendTransaction({ instruction, signer, extraSigners, }); }); } /** * Sends a set of instructions as a manually-signed Solana transaction. */ sendTransaction(_a) { return tslib_1.__awaiter(this, arguments, void 0, function* ({ instruction, signer, extraSigners = [], }) { const connection = this.connections[this.currentConnectionIndex]; const feePayer = new web3_js_1.PublicKey(signer.account.address); const { blockhash, lastValidBlockHeight } = yield connection.getLatestBlockhash('finalized'); const tx = new web3_js_1.Transaction({ feePayer, blockhash, lastValidBlockHeight, }).add(instruction); if (extraSigners.length > 0) { tx.partialSign(...extraSigners); } const txBytes = tx.serialize({ requireAllSignatures: false, verifySignatures: false, }); if (!signer.signAndSendTransaction) { throw new Error('signer.signTransaction is undefined'); } const txHashBytes = yield signer.signAndSendTransaction(txBytes); return anchor_2.utils.bytes.bs58.encode(txHashBytes); // Clean, readable tx hash }); } /** * Waits for a transaction to be confirmed on the blockchain. */ confirmTransaction(signature_1) { return tslib_1.__awaiter(this, arguments, void 0, function* (signature, timeout = 30000) { const startTime = Date.now(); return this.executeWithFallback((connection) => tslib_1.__awaiter(this, void 0, void 0, function* () { while (Date.now() - startTime < timeout) { const status = yield connection.getSignatureStatus(signature); if (status === null || status === void 0 ? void 0 : status.value) { if (status.value.err) { throw new Error(`Transaction failed: ${JSON.stringify(status.value.err)}`); } if (status.value.confirmationStatus === 'confirmed' || status.value.confirmationStatus === 'finalized') { return; } } yield new Promise((r) => setTimeout(r, 500)); } throw new Error(`Transaction confirmation timeout after ${timeout}ms`); }), 'confirmTransaction'); }); } /** * Estimates the fee (in lamports) to send a transaction with the given instructions. */ estimateGas(_a) { return tslib_1.__awaiter(this, arguments, void 0, function* ({ instructions, signer, }) { return this.executeWithFallback((connection) => tslib_1.__awaiter(this, void 0, void 0, function* () { const feePayer = new web3_js_1.PublicKey(signer.account.address); const { blockhash, lastValidBlockHeight } = yield connection.getLatestBlockhash(); const tx = new web3_js_1.Transaction({ blockhash, lastValidBlockHeight, feePayer }); if (instructions.length) tx.add(...instructions); const message = tx.compileMessage(); const feeResp = yield connection.getFeeForMessage(message); if (!(feeResp === null || feeResp === void 0 ? void 0 : feeResp.value)) throw new Error('Failed to estimate fee'); return BigInt(feeResp.value); }), 'estimateGas'); }); } /** * Sleeps for the given number of milliseconds. */ sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } /** * Waits for a Solana transaction to reach the desired confirmations. * * @param txSignature - The transaction signature to monitor * @param confirmations - Desired confirmation count (default: 6) * @param timeoutMs - Max wait time in milliseconds (default: 90_000) * @param pollIntervalMs- How often to poll in milliseconds (default: 500) */ waitForConfirmations(_a) { return tslib_1.__awaiter(this, arguments, void 0, function* ({ txSignature, confirmations = 3, timeoutMs = 30000, pollIntervalMs = 500, }) { const connection = this.connections[this.currentConnectionIndex]; const deadline = Date.now() + timeoutMs; while (Date.now() < deadline) { // fetch status const { value } = yield connection.getSignatureStatuses([txSignature]); const status = value[0]; if ((status === null || status === void 0 ? void 0 : status.confirmations) != null && status.confirmations >= confirmations) { return; } // wait before retrying yield this.sleep(pollIntervalMs); } throw new Error(`Timeout: transaction ${txSignature} not confirmed with ` + `${confirmations} confirmations within ${timeoutMs} ms`); }); } } exports.SvmClient = SvmClient; //# sourceMappingURL=svm-client.js.map