medusa-payment-solana
Version:
Solana crypto currency payment provider for MedusaJS 2.0
145 lines • 7.08 kB
JavaScript
;
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