UNPKG

zebec-instant-card-sdk

Version:

An sdk for interacting with zebec instant card program in solana.

243 lines (242 loc) 13.7 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.TransactionPayload = void 0; const bignumber_js_1 = __importDefault(require("bignumber.js")); const anchor_1 = require("@coral-xyz/anchor"); const web3_js_1 = require("@solana/web3.js"); const constants_1 = require("./constants"); const utils_1 = require("./utils"); /** * A executable payload that holds transaction */ class TransactionPayload { /** * * @param _connection Solana web3 connnection * @param _errors Program errors map * @param instructions Transaction instructions * @param feePayer Transaction fee payer * @param signers Partial signers required to instruction * @param addressLookupTableAccounts Address lookup table accounts for transaction * @param _signTransaction function that signs and return signed transaction. Transaction build from the instructions is passed to this function */ constructor(_connection, _errors, instructions, feePayer, signers, addressLookupTableAccounts, _signTransaction) { this._connection = _connection; this._errors = _errors; this.instructions = instructions; this.feePayer = feePayer; this.signers = signers; this.addressLookupTableAccounts = addressLookupTableAccounts; this._signTransaction = _signTransaction; } simulate(options) { return __awaiter(this, void 0, void 0, function* () { if (!this._signTransaction) { throw new Error("signTransaction is required to execute transaction payload."); } const { blockhash } = yield this._connection.getLatestBlockhash(options); const message = new web3_js_1.TransactionMessage({ instructions: this.instructions, payerKey: this.feePayer, recentBlockhash: blockhash, // Note: this blockhash will be replaced at the time of sign and send }).compileToV0Message(this.addressLookupTableAccounts); const transaction = new web3_js_1.VersionedTransaction(message); let signedTransaction = transaction; if (options === null || options === void 0 ? void 0 : options.sigVerify) { // sign transaction if (this.signers && this.signers.length > 0) { transaction.sign(this.signers); } signedTransaction = yield this._signTransaction(transaction); } try { return this._connection.simulateTransaction(signedTransaction, options); } catch (err) { // console.debug("error:", err); const translatedError = (0, anchor_1.translateError)(err, this._errors); // console.debug("translated error:", translatedError); if (constants_1.isSdkEnvDev && err instanceof web3_js_1.SendTransactionError) { console.debug("Debug: program logs"); console.debug(yield err.getLogs(this._connection)); } throw translatedError; } }); } /** * Signs, send and confim transaction * * `Note`: requires signTransaction while creating instance of this object. * @param options solana confirm options * @returns */ execute(options) { return __awaiter(this, void 0, void 0, function* () { if (!this._signTransaction) { throw new Error("signTransaction is required to execute transaction payload."); } const simulationResult = yield this.simulate(options); // 300 added for Compute Budget Instructions const computeUnit = simulationResult.value.unitsConsumed ? simulationResult.value.unitsConsumed + 300 : constants_1.MAX_COMPUTE_UNIT; // console.log("compute unit:", computeUnit); // console.log("compute unit:", computeUnit); const hasComputeUnitLimitInstruction = this.instructions.some((instruction) => instruction.programId.equals(web3_js_1.ComputeBudgetProgram.programId) && web3_js_1.ComputeBudgetInstruction.decodeInstructionType(instruction) === "SetComputeUnitLimit"); const hasComputerUnitPriceInstruction = this.instructions.some((instruction) => instruction.programId.equals(web3_js_1.ComputeBudgetProgram.programId) && web3_js_1.ComputeBudgetInstruction.decodeInstructionType(instruction) === "SetComputeUnitPrice"); if (!hasComputeUnitLimitInstruction) { this.instructions.unshift(web3_js_1.ComputeBudgetProgram.setComputeUnitLimit({ units: computeUnit })); } if (!hasComputerUnitPriceInstruction) { const exactPriorityFeeSol = (options === null || options === void 0 ? void 0 : options.exactPriorityFeeSol) ? (0, bignumber_js_1.default)(options.exactPriorityFeeSol) : undefined; let priorityFeeInMicroLamports = BigInt(0); if (!exactPriorityFeeSol) { const priorityLevel = (options === null || options === void 0 ? void 0 : options.priorityLevel) ? options.priorityLevel : "medium"; const maxPriorityFeeSol = (options === null || options === void 0 ? void 0 : options.maxPriorityFeeSol) ? options === null || options === void 0 ? void 0 : options.maxPriorityFeeSol : constants_1.DEFAULT_MAX_PRIORITY_FEE; const maxPriorityFeePerCU = (0, bignumber_js_1.default)(maxPriorityFeeSol) .times(web3_js_1.LAMPORTS_PER_SOL) .minus(constants_1.BASE_FEE_LAMPORTS) .div(computeUnit) .div(constants_1.LAMPORTS_PER_MICRO_LAMPORT); // console.log("max priority fee per cu:", maxPriorityFeePerCU.toFixed()); const priorityFeePerCU = yield (0, utils_1.getRecentPriorityFee)(this._connection, this.instructions, priorityLevel, maxPriorityFeePerCU); // console.log("priority fee per cu:", priorityFeePerCU.toFixed()); priorityFeeInMicroLamports = BigInt(priorityFeePerCU.toFixed(0, bignumber_js_1.default.ROUND_DOWN)); } else { priorityFeeInMicroLamports = BigInt(exactPriorityFeeSol .times(web3_js_1.LAMPORTS_PER_SOL) .minus(constants_1.BASE_FEE_LAMPORTS) .div(computeUnit) .div(constants_1.LAMPORTS_PER_MICRO_LAMPORT) .toFixed(0, bignumber_js_1.default.ROUND_DOWN)); } this.instructions.unshift(web3_js_1.ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priorityFeeInMicroLamports })); } const { lastValidBlockHeight, blockhash } = yield this._connection.getLatestBlockhash(options); const message = new web3_js_1.TransactionMessage({ instructions: this.instructions, payerKey: this.feePayer, recentBlockhash: blockhash, // Note: this blockhash will be replaced at the time of sign and send }).compileToV0Message(this.addressLookupTableAccounts); const transaction = new web3_js_1.VersionedTransaction(message); if (this.signers && this.signers.length > 0) { transaction.sign(this.signers); } // sign transaction const signedTransaction = yield this._signTransaction(transaction); try { let signature = anchor_1.utils.bytes.bs58.encode(signedTransaction.signatures[0]); let confirmed = false; const sendTxMultiple = () => __awaiter(this, void 0, void 0, function* () { let blockheight = yield this._connection.getBlockHeight(options); let retry = 0; const sendTransactionInterval = (options === null || options === void 0 ? void 0 : options.sendTransactionInterval) ? options.sendTransactionInterval : constants_1.DEFAULT_SEND_TRANSACTION_INTERVAL; const sendTransactionRetry = (options === null || options === void 0 ? void 0 : options.maxSendTransactionRetries) ? options.maxSendTransactionRetries : Number.MAX_SAFE_INTEGER; while (!confirmed && blockheight < lastValidBlockHeight && retry < sendTransactionRetry) { try { yield this._connection.sendRawTransaction(signedTransaction.serialize(), options); console.debug("Signatue sent: %s at %d", signature, Date.now()); retry += 1; yield (0, utils_1.sleep)(sendTransactionInterval); blockheight = yield this._connection.getBlockHeight(options); } catch (err) { if (err.message) { if (err.message.includes("This transaction has already been processed")) { return; } else if (err.message.includes("Blockhash not found")) { console.debug("Expected error: ", err.message); retry += 1; yield (0, utils_1.sleep)(sendTransactionInterval); blockheight = yield this._connection.getBlockHeight(options); continue; } else { throw err; } } throw err; } } // Execution may not reach here if (!confirmed) { if (blockheight >= lastValidBlockHeight) { throw new Error("Blockheght exceeded."); } } }); let startTime = Date.now(); const confirmTransaction = () => __awaiter(this, void 0, void 0, function* () { let err = null; try { const response = yield this._connection.confirmTransaction({ signature: signature, blockhash: blockhash, lastValidBlockHeight: lastValidBlockHeight, }, options === null || options === void 0 ? void 0 : options.commitment); err = response.value.err; if (!err) { const endTime = Date.now(); console.debug("Confirmed at: %d", endTime); console.debug("Time elapsed: %d", endTime - startTime); confirmed = true; return; } if (err) { if (typeof err === "string") { console.debug("confirm transaction err: " + err); throw new Error("Failed to confirm transaction: " + err); } else { console.debug("confirm transaction err: " + JSON.stringify(err)); throw new Error("Failed to confirm transaction"); } } } catch (err) { throw err; } }); yield Promise.all([sendTxMultiple(), confirmTransaction()]); return signature; } catch (err) { console.debug("error:", err); const translatedError = (0, anchor_1.translateError)(err, this._errors); console.debug("translated error:", translatedError); if (constants_1.isSdkEnvDev && err instanceof web3_js_1.SendTransactionError) { console.debug("Debug: program logs"); console.debug(yield err.getLogs(this._connection)); } throw translatedError; } }); } } exports.TransactionPayload = TransactionPayload;