UNPKG

medusa-payment-solana

Version:

Solana crypto currency payment provider for MedusaJS 2.0

145 lines 7.08 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.SolanaClient = void 0; const web3_js_1 = require("@solana/web3.js"); const ed25519_hd_key_1 = require("ed25519-hd-key"); const bip39_1 = require("bip39"); const crypto_1 = __importDefault(require("crypto")); const currency_converter_1 = require("./currency-converter"); const coingecko_converter_1 = require("./coingecko-converter"); const utils_1 = require("./utils"); const errors_1 = require("./errors"); class SolanaClient { constructor(options) { if (!(0, utils_1.isValidSolanaAddress)(options.coldStorageWallet)) { throw new errors_1.SolanaPaymentError(errors_1.SolanaPaymentError.Types.INVALID_DATA, `Invalid cold storage wallet address provided: ${options.coldStorageWallet}. Please provide a valid base58 encoded Solana address.`); } this.connection = new web3_js_1.Connection(options.rpcUrl, 'confirmed'); this.mnemonic = options.mnemonic; this.seed = (0, bip39_1.mnemonicToSeedSync)(this.mnemonic); this.coldStorageWallet = new web3_js_1.PublicKey(options.coldStorageWallet); if (options.currencyConverter.provider === 'coingecko' && options.currencyConverter.apiKey) { this.converter = new coingecko_converter_1.CoinGeckoConverter(options.currencyConverter.apiKey); } else { this.converter = new currency_converter_1.DefaultConverter(); } } getOneTimeKeypair(paymentId) { const index = this.paymentIdToBip44Index(paymentId); const derivationPath = `m/44'/501'/${index}'/0'`; const derivedKey = (0, ed25519_hd_key_1.derivePath)(derivationPath, this.seed.toString('hex')); return web3_js_1.Keypair.fromSeed(derivedKey.key.slice(0, 32)); } async transferToColdStorage(paymentId) { const oneTimeKeypair = this.getOneTimeKeypair(paymentId); // Get balance of one-time address const balance = await this.connection.getBalance(oneTimeKeypair.publicKey); const numericBalance = Number(balance); // Create dummy transaction to estimate fee const dummyTx = new web3_js_1.Transaction().add(web3_js_1.SystemProgram.transfer({ fromPubkey: oneTimeKeypair.publicKey, toPubkey: this.coldStorageWallet, lamports: numericBalance })); dummyTx.feePayer = oneTimeKeypair.publicKey; const { blockhash } = await this.connection.getLatestBlockhash('confirmed'); dummyTx.recentBlockhash = blockhash; const message = dummyTx.compileMessage(); const feeInfo = await this.connection.getFeeForMessage(message); const fee = feeInfo.value; if (typeof fee !== 'number' || isNaN(fee)) { throw new Error('Failed to estimate transaction fee: ' + JSON.stringify(feeInfo)); } const transferAmount = numericBalance - fee; if (transferAmount <= 0) { throw new Error('Insufficient balance to cover transaction fee'); } // Create and send actual transaction const tx = new web3_js_1.Transaction(); tx.recentBlockhash = blockhash; tx.add(web3_js_1.SystemProgram.transfer({ fromPubkey: oneTimeKeypair.publicKey, toPubkey: this.coldStorageWallet, lamports: transferAmount })); tx.feePayer = oneTimeKeypair.publicKey; try { const { blockhash } = await this.connection.getLatestBlockhash('confirmed'); tx.recentBlockhash = blockhash; const signature = await (0, web3_js_1.sendAndConfirmTransaction)(this.connection, tx, [oneTimeKeypair]); return signature; } catch (error) { throw new Error('Error transferring to cold storage:' + JSON.stringify({ error: error, paymentId: paymentId, transaction: { from: oneTimeKeypair.publicKey.toBase58(), to: this.coldStorageWallet.toBase58(), amount: transferAmount, fee: fee } })); } } async convertToSol(amount, currencyCode) { return this.converter.convertToSol(amount, currencyCode); } paymentIdToBip44Index(paymentId) { const hash = crypto_1.default.createHash('sha256').update(paymentId).digest(); const rawIndex = hash.readUInt32BE(0); return rawIndex % 0x80000000; } generateAddress(paymentId) { const index = this.paymentIdToBip44Index(paymentId); const derivationPath = `m/44'/501'/${index}'/0'`; const derivedKey = (0, ed25519_hd_key_1.derivePath)(derivationPath, this.seed.toString('hex')); const keypair = web3_js_1.Keypair.fromSeed(derivedKey.key.slice(0, 32)); return keypair.publicKey.toBase58(); } async checkPayment(paymentDetails) { try { const paymentAddress = new web3_js_1.PublicKey(paymentDetails.solana_one_time_address); const signatures = await this.connection.getSignaturesForAddress(paymentAddress, { limit: 50 } // Increased limit to check more transactions if needed ); let totalLamportsReceived = 0; let lastTransactionTime = null; // Sort signatures by blockTime descending to find the last transaction first const sortedSignatures = signatures.sort((a, b) => (b.blockTime || 0) - (a.blockTime || 0)); for (const sig of sortedSignatures) { if (!sig.blockTime) continue; const blockTimeDate = new Date(sig.blockTime * 1000); // Find the most recent transaction time if (!lastTransactionTime) { lastTransactionTime = blockTimeDate; } const tx = await this.connection.getParsedTransaction(sig.signature, { commitment: 'confirmed', maxSupportedTransactionVersion: 0 }); if (!tx || !tx.meta) continue; for (const ix of tx.transaction.message.instructions) { if ('program' in ix && ix.program === 'system' && ix.parsed?.type === 'transfer' && ix.parsed.info.destination === paymentAddress.toBase58()) { totalLamportsReceived += ix.parsed.info.lamports; } } } return { receivedAmount: totalLamportsReceived / web3_js_1.LAMPORTS_PER_SOL, lastTransactionTime: lastTransactionTime, }; } catch (error) { throw new Error(`Error checking payment: ${error}`); } } } exports.SolanaClient = SolanaClient; exports.default = SolanaClient; //# sourceMappingURL=solana-client.js.map