zebec-instant-card-sdk
Version:
An sdk for interacting with zebec instant card program in solana.
243 lines (242 loc) • 13.7 kB
JavaScript
"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;