UNPKG

zebec-instant-card-sdk

Version:

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

386 lines (385 loc) 18.1 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.getRecentPriorityFee = exports.sleep = exports.areDateOfSameDay = exports.getBuyCardMemoFromParsedTransaction = exports.hashSHA256 = exports.parseEmailString = exports.assertEmailString = exports.parseFeeTiers = exports.parsePublicKeyString = exports.parsePercentString = exports.parseDecimalString = exports.parseBigIntString = exports.assertValidBigIntString = exports.assertValidPublicKeyString = exports.assertValidPercentString = exports.assertValidDecimalString = exports.sortFeeTierDesc = exports.getAssociatedTokenAddressSync = exports.createAssociatedTokenAccountInstruction = exports.getZebecConnectionProvider = exports.getAnchorProvider = exports.parseCardPurchaseEvent = exports.parseDepositToUserPdaEvent = exports.parseQuoteInfo = exports.bpsToPercent = exports.percentToBps = exports.getMintDecimals = void 0; const assert_1 = __importDefault(require("assert")); 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 errors_1 = require("./errors"); const provider_1 = require("./provider"); const mintToDecimalsMap = new Map(); /** * Gets decimals for given mint * @param connection * @param mint * @returns */ function getMintDecimals(connection, mint) { return __awaiter(this, void 0, void 0, function* () { if (mintToDecimalsMap.has(mint.toString())) { return mintToDecimalsMap.get(mint.toString()); } else { const info = yield connection.getTokenSupply(mint); mintToDecimalsMap.set(mint.toString(), info.value.decimals); return info.value.decimals; } }); } exports.getMintDecimals = getMintDecimals; /** * Converts percentage value to bps * @param percent * @returns */ function percentToBps(percent) { return Number((0, bignumber_js_1.default)(percent.toString()).times(100).toFixed(0, bignumber_js_1.default.ROUND_DOWN)); } exports.percentToBps = percentToBps; function bpsToPercent(bps) { return parsePercentString((0, bignumber_js_1.default)(bps.toString()).div(100).toFixed(2, bignumber_js_1.default.ROUND_DOWN)); } exports.bpsToPercent = bpsToPercent; /** * Parse jupiter quote response * @param connection * @param quoteInfo * @returns */ function parseQuoteInfo(quoteInfo) { return __awaiter(this, void 0, void 0, function* () { (0, assert_1.default)(typeof quoteInfo === "object" && quoteInfo); if ("error" in quoteInfo) { (0, assert_1.default)(typeof quoteInfo.error === "string"); return { error: quoteInfo.error, }; } (0, assert_1.default)("routePlan" in quoteInfo && Array.isArray(quoteInfo.routePlan)); (0, assert_1.default)("outputMint" in quoteInfo && typeof quoteInfo.outputMint === "string"); (0, assert_1.default)("outAmount" in quoteInfo && typeof quoteInfo.outAmount === "string"); (0, assert_1.default)("inputMint" in quoteInfo && typeof quoteInfo.inputMint === "string"); (0, assert_1.default)("inAmount" in quoteInfo && typeof quoteInfo.inAmount === "string"); (0, assert_1.default)("swapMode" in quoteInfo && (quoteInfo.swapMode === "ExactIn" || quoteInfo.swapMode === "ExactOut")); (0, assert_1.default)("otherAmountThreshold" in quoteInfo && typeof quoteInfo.otherAmountThreshold === "string"); (0, assert_1.default)("contextSlot" in quoteInfo && typeof quoteInfo.contextSlot === "number"); (0, assert_1.default)("timeTaken" in quoteInfo && typeof quoteInfo.timeTaken === "number"); const routePlan = yield Promise.all(quoteInfo.routePlan.map((r) => __awaiter(this, void 0, void 0, function* () { (0, assert_1.default)(typeof r === "object" && r); (0, assert_1.default)("percent" in r && typeof r.percent === "number"); (0, assert_1.default)("swapInfo" in r && typeof r.swapInfo === "object" && r.swapInfo); (0, assert_1.default)("inputMint" in r.swapInfo && typeof r.swapInfo.inputMint === "string"); (0, assert_1.default)("inAmount" in r.swapInfo && typeof r.swapInfo.inAmount === "string"); (0, assert_1.default)("outputMint" in r.swapInfo && typeof r.swapInfo.outputMint === "string"); (0, assert_1.default)("outAmount" in r.swapInfo && typeof r.swapInfo.outAmount === "string"); (0, assert_1.default)("ammKey" in r.swapInfo && typeof r.swapInfo.ammKey === "string"); (0, assert_1.default)("feeAmount" in r.swapInfo && typeof r.swapInfo.feeAmount === "string"); (0, assert_1.default)("feeMint" in r.swapInfo && typeof r.swapInfo.feeMint === "string"); (0, assert_1.default)("label" in r.swapInfo && typeof r.swapInfo.label === "string"); return { percent: r.percent, swapInfo: { ammKey: parsePublicKeyString(r.swapInfo.ammKey), feeAmount: parseBigIntString(r.swapInfo.feeAmount), feeMint: parsePublicKeyString(r.swapInfo.feeMint), inAmount: parseBigIntString(r.swapInfo.inAmount), inputMint: parsePublicKeyString(r.swapInfo.inputMint), label: r.swapInfo.label, outAmount: parseBigIntString(r.swapInfo.outAmount), outputMint: parsePublicKeyString(r.swapInfo.outputMint), }, }; }))); (0, assert_1.default)("slippageBps" in quoteInfo && typeof quoteInfo.slippageBps === "number"); (0, assert_1.default)("platformFee" in quoteInfo && (typeof quoteInfo.platformFee === "string" || quoteInfo.platformFee === null)); (0, assert_1.default)("priceImpactPct" in quoteInfo && typeof quoteInfo.priceImpactPct === "string"); return { inputMint: parsePublicKeyString(quoteInfo.inputMint), inAmount: parseBigIntString(quoteInfo.inAmount), outputMint: parsePublicKeyString(quoteInfo.outputMint), outAmount: parseBigIntString(quoteInfo.outAmount), otherAmountThreshold: parseBigIntString(quoteInfo.otherAmountThreshold), swapMode: quoteInfo.swapMode, slippageBps: quoteInfo.slippageBps, platformFee: quoteInfo.platformFee, priceImpactPct: parsePercentString(quoteInfo.priceImpactPct), routePlan, contextSlot: quoteInfo.contextSlot, timeTaken: quoteInfo.timeTaken, }; }); } exports.parseQuoteInfo = parseQuoteInfo; /** * Parse deposit event * @param event * @returns */ function parseDepositToUserPdaEvent(event) { const amount = parseDecimalString((0, bignumber_js_1.default)(event.amount.toString()).div(constants_1.TEN_BIGNUM.pow(constants_1.USDC_DECIMALS)).toFixed()); return { amount, from: parsePublicKeyString(event.from.toString()), to: parsePublicKeyString(event.to.toString()), }; } exports.parseDepositToUserPdaEvent = parseDepositToUserPdaEvent; function parseCardPurchaseEvent(event) { const amount = parseDecimalString((0, bignumber_js_1.default)(event.amount.toString()).div(constants_1.TEN_BIGNUM.pow(constants_1.USDC_DECIMALS)).toFixed()); return { index: BigInt(event.index.toString()), amount, from: parsePublicKeyString(event.from.toString()), to: parsePublicKeyString(event.to.toString()), }; } exports.parseCardPurchaseEvent = parseCardPurchaseEvent; /** * Create anchor provider * @param connection Solana web3 connection * @param wallet Anchor wallet * @param opts Confirm options * * @example * ``` * const connection = new Connection(process.env.RPC_URL || clusterApiUrl("mainnet-beta")) * const wallet = useWallet(); * const provider = getAnchorProvider(connection, wallet); * ``` */ function getAnchorProvider(connection, wallet, opts) { const provider = new anchor_1.AnchorProvider(connection, wallet, opts); return provider; } exports.getAnchorProvider = getAnchorProvider; /** * Create Zebec Connection Provider. Can we used for readonly operation to solana program * @param connection Solana web3 connection * @returns */ function getZebecConnectionProvider(connection) { return new provider_1.ZebecConnectionProvider(connection); } exports.getZebecConnectionProvider = getZebecConnectionProvider; /** * Construct a CreateAssociatedTokenAccount instruction * * @param payer Payer of the initialization fees * @param associatedToken New associated token account * @param owner Owner of the new account * @param mint Token mint account * @param programId SPL Token program account * @param associatedTokenProgramId SPL Associated Token program account * * @return Instruction to add to a transaction */ function createAssociatedTokenAccountInstruction(payer, associatedToken, owner, mint, programId = constants_1.TOKEN_PROGRAM_ID, associatedTokenProgramId = constants_1.ASSOCIATED_TOKEN_PROGRAM_ID) { const keys = [ { pubkey: payer, isSigner: true, isWritable: true }, { pubkey: associatedToken, isSigner: false, isWritable: true }, { pubkey: owner, isSigner: false, isWritable: false }, { pubkey: mint, isSigner: false, isWritable: false }, { pubkey: web3_js_1.SystemProgram.programId, isSigner: false, isWritable: false }, { pubkey: programId, isSigner: false, isWritable: false }, ]; return new web3_js_1.TransactionInstruction({ keys, programId: associatedTokenProgramId, data: Buffer.alloc(0), }); } exports.createAssociatedTokenAccountInstruction = createAssociatedTokenAccountInstruction; /** * Get the address of the associated token account for a given mint and owner * * @param mint Token mint account * @param owner Owner of the new account * @param allowOwnerOffCurve Allow the owner account to be a PDA (Program Derived Address) * @param programId SPL Token program account * @param associatedTokenProgramId SPL Associated Token program account * * @return Address of the associated token account */ function getAssociatedTokenAddressSync(mint, owner, allowOwnerOffCurve = false, programId = constants_1.TOKEN_PROGRAM_ID, associatedTokenProgramId = constants_1.ASSOCIATED_TOKEN_PROGRAM_ID) { if (!allowOwnerOffCurve && !web3_js_1.PublicKey.isOnCurve(owner.toBuffer())) throw new Error("Token owner is off curve."); const [address] = web3_js_1.PublicKey.findProgramAddressSync([owner.toBuffer(), programId.toBuffer(), mint.toBuffer()], associatedTokenProgramId); return address; } exports.getAssociatedTokenAddressSync = getAssociatedTokenAddressSync; function sortFeeTierDesc(feeTiers) { return feeTiers.sort((a, b) => { if (a.minAmount.eq(b.minAmount)) { return 0; } else if (a.minAmount.gt(b.minAmount)) { return -1; } else { return 1; } }); } exports.sortFeeTierDesc = sortFeeTierDesc; function assertValidDecimalString(input) { if (!/^\d+(\.\d+)?$/.test(input)) { throw new errors_1.InvalidDecimalStringError(input); } } exports.assertValidDecimalString = assertValidDecimalString; function assertValidPercentString(input) { if (!/^(100(\.0{1,2})?|(0|[1-9]\d?)(\.\d+)?)$/.test(input)) { throw new errors_1.InvalidPercentStringError(input); } } exports.assertValidPercentString = assertValidPercentString; function assertValidPublicKeyString(input) { try { new web3_js_1.PublicKey(input); } catch (_a) { throw new errors_1.InvalidPublicKeyStringError(input); } } exports.assertValidPublicKeyString = assertValidPublicKeyString; function assertValidBigIntString(input) { try { BigInt(input); } catch (_a) { throw new errors_1.InvalidBigIntStringError(input); } } exports.assertValidBigIntString = assertValidBigIntString; function parseBigIntString(input) { assertValidBigIntString(input); return input; } exports.parseBigIntString = parseBigIntString; function parseDecimalString(input) { const value = input.toString(); assertValidDecimalString(value); return value; } exports.parseDecimalString = parseDecimalString; function parsePercentString(input) { const value = input.toString(); assertValidPercentString(value); return value; } exports.parsePercentString = parsePercentString; function parsePublicKeyString(input) { const value = input.toString(); assertValidPublicKeyString(value); return value; } exports.parsePublicKeyString = parsePublicKeyString; function parseFeeTiers(input) { return input.map((ft) => { return { feePercent: parsePercentString(ft.feePercent), maxAmount: parseDecimalString(ft.maxAmount), minAmount: parseDecimalString(ft.minAmount), }; }); } exports.parseFeeTiers = parseFeeTiers; function assertEmailString(value) { if (!/^[a-zA-Z0-9._+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,10}$/.test(value)) { throw new errors_1.InvalidEmailStringError(value); } } exports.assertEmailString = assertEmailString; function parseEmailString(input) { assertEmailString(input); return input; } exports.parseEmailString = parseEmailString; function hashSHA256(input) { return __awaiter(this, void 0, void 0, function* () { const utf8 = new TextEncoder().encode(input); const hashBuffer = yield crypto.subtle.digest("SHA-256", utf8); const hashArray = Array.from(new Uint8Array(hashBuffer)); const hashHex = hashArray.map((bytes) => bytes.toString(16).padStart(2, "0")).join(""); return hashHex; }); } exports.hashSHA256 = hashSHA256; function getBuyCardMemoFromParsedTransaction(transaction) { const memoInstruction = transaction.message.instructions.find((ix) => ix.programId.equals(constants_1.MEMO_PROGRAM_ID)); (0, assert_1.default)(memoInstruction, "Transaction does not contain memo instruction"); // throw error if instruction does not have parsed property (0, assert_1.default)("parsed" in memoInstruction, "Memo instruction does not contain `parsed` property"); const memo = JSON.parse(memoInstruction.parsed); // throw error if memo does not have buyer property (0, assert_1.default)(typeof memo === "object" && memo && "buyer" in memo && typeof memo.buyer === "string", "Memo is invalid"); return memo; } exports.getBuyCardMemoFromParsedTransaction = getBuyCardMemoFromParsedTransaction; function areDateOfSameDay(date1, date2) { return (date1.getUTCDay() === date2.getUTCDay() && date1.getUTCMonth() === date2.getUTCMonth() && date1.getUTCFullYear() === date2.getUTCFullYear()); } exports.areDateOfSameDay = areDateOfSameDay; function sleep(ms) { return __awaiter(this, void 0, void 0, function* () { return yield new Promise((r) => setTimeout(r, ms)); }); } exports.sleep = sleep; function getRecentPriorityFee(connection, instructions, priorityLevel, maxFeeCap) { return __awaiter(this, void 0, void 0, function* () { const lockedWritableAccounts = [ ...new Set(instructions.flatMap((ix) => [...ix.keys.map((key) => key.pubkey), ix.programId])), ]; const recentPrioritizationFees = yield connection.getRecentPrioritizationFees({ lockedWritableAccounts, }); // console.log("recent fees", recentPrioritizationFees); const sortedNonZeroList = recentPrioritizationFees .filter((b) => b.prioritizationFee > 0) .sort((a, b) => (0, bignumber_js_1.default)(a.prioritizationFee).comparedTo(b.prioritizationFee)); let medianFee = (0, bignumber_js_1.default)(0); if (sortedNonZeroList.length > 0) { const midIndex = Math.floor(sortedNonZeroList.length / 2); medianFee = sortedNonZeroList.length % 2 !== 0 ? (0, bignumber_js_1.default)(sortedNonZeroList[midIndex].prioritizationFee).decimalPlaces(0, bignumber_js_1.default.ROUND_DOWN) : (0, bignumber_js_1.default)(sortedNonZeroList[midIndex - 1].prioritizationFee) .plus(sortedNonZeroList[midIndex].prioritizationFee) .div(2) .decimalPlaces(0, bignumber_js_1.default.ROUND_DOWN); } // console.log("median fee:", medianFee.toFixed()); // Apply multiplier based on priority level const multipliers = { low: 1, medium: 1.5, high: 2, }; // Calculate fee const calculatedFee = medianFee .times(multipliers[priorityLevel]) .decimalPlaces(0, bignumber_js_1.default.ROUND_FLOOR); // console.log("calculated fee:", calculatedFee.toFixed()); const fee = bignumber_js_1.default.min(calculatedFee, maxFeeCap); return fee; }); } exports.getRecentPriorityFee = getRecentPriorityFee;