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