UNPKG

zebec-instant-card-sdk

Version:

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

775 lines 72.2 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()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ZebecCardService = void 0; const bignumber_js_1 = require("bignumber.js"); const buffer_1 = require("buffer"); 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 payload_1 = require("./payload"); const utils_1 = require("./utils"); let userVaultExistCache = new Set(); /** * Zebec card service for interacting with zebec instant card program * * @example * * ``` * const connection = new Connection(process.env.RPC_URL || clusterApiUrl("mainnet-beta")); * const wallet = useWallet(); * const provider = getAnchorProvider(connection, wallet); * const program = ZebecCardProgramFactory.getProgram(provider); * const instructions = new ZebecCardInstructions(program); * const service = new ZebecCardService(instructions, connection, wallet.signTranaction); * * // or * const provider = getZebecConnectionProvider(connection); * const program = ZebecCardProgramFactory.getProgram(provider); * const instructions = new ZebecCardInstructions(program); * const service = new ZebecCardService(instructions, connection); * ``` */ class ZebecCardService { static deriveCardConfigPda(cardProgramId) { const programId = (0, anchor_1.translateAddress)(cardProgramId); const [cardConfig] = web3_js_1.PublicKey.findProgramAddressSync([buffer_1.Buffer.from(anchor_1.utils.bytes.utf8.encode(constants_1.CARD_PROGRAM_SEEDS.cardPdaSeed))], programId); return cardConfig; } static deriveTokenFeeMapPda(cardProgramId) { const programId = (0, anchor_1.translateAddress)(cardProgramId); const [feeMapPda] = web3_js_1.PublicKey.findProgramAddressSync([buffer_1.Buffer.from(anchor_1.utils.bytes.utf8.encode(constants_1.CARD_PROGRAM_SEEDS.feeMapPdaSeed))], programId); return feeMapPda; } static deriveCardPurchaseInfoPda(buyerAddress, cardProgramId, buyerCounter) { const buyer = (0, anchor_1.translateAddress)(buyerAddress); const programId = (0, anchor_1.translateAddress)(cardProgramId); const [buyerPda] = web3_js_1.PublicKey.findProgramAddressSync([ buffer_1.Buffer.from(anchor_1.utils.bytes.utf8.encode(constants_1.CARD_PROGRAM_SEEDS.buyerPdaSeed)), buyer.toBuffer(), new anchor_1.BN(buyerCounter.toString()).toArrayLike(buffer_1.Buffer, "le", 8), ], programId); return buyerPda; } static deriveUserVaultPda(userAddress, cardProgramId) { const user = (0, anchor_1.translateAddress)(userAddress); const programId = (0, anchor_1.translateAddress)(cardProgramId); const [userVault] = web3_js_1.PublicKey.findProgramAddressSync([buffer_1.Buffer.from(anchor_1.utils.bytes.utf8.encode(constants_1.CARD_PROGRAM_SEEDS.userPdaSeed)), user.toBuffer()], programId); return userVault; } static deriveUserPurchaseRecordPda(userAddress, cardProgramId) { const user = (0, anchor_1.translateAddress)(userAddress); const programId = (0, anchor_1.translateAddress)(cardProgramId); const [purchaseRecordPda] = web3_js_1.PublicKey.findProgramAddressSync([buffer_1.Buffer.from(anchor_1.utils.bytes.utf8.encode(constants_1.CARD_PROGRAM_SEEDS.vaultInfoSeed)), user.toBuffer()], programId); return purchaseRecordPda; } static deriveFlexlendUserPda(userAddress, flexlendProgramId) { const user = (0, anchor_1.translateAddress)(userAddress); const programId = (0, anchor_1.translateAddress)(flexlendProgramId); const [flexlendAccount] = web3_js_1.PublicKey.findProgramAddressSync([buffer_1.Buffer.from(anchor_1.utils.bytes.utf8.encode(constants_1.FLEXLEND_PROGRAM_SEEDS.flexlend)), user.toBuffer()], programId); return flexlendAccount; } static deriveFlexlendPromotionReservePda(reserveAdminAddress, flexlendProgramId) { const reserveAdmin = (0, anchor_1.translateAddress)(reserveAdminAddress); const programId = (0, anchor_1.translateAddress)(flexlendProgramId); const [promotionReservePda] = web3_js_1.PublicKey.findProgramAddressSync([ buffer_1.Buffer.from(anchor_1.utils.bytes.utf8.encode(constants_1.FLEXLEND_PROGRAM_SEEDS.promotionReserve)), reserveAdmin.toBuffer(), ], programId); return promotionReservePda; } static deriveCardBotConfigPda(cardProgramId) { const programId = (0, anchor_1.translateAddress)(cardProgramId); const [cardBotConfigPda] = web3_js_1.PublicKey.findProgramAddressSync([buffer_1.Buffer.from(anchor_1.utils.bytes.utf8.encode(constants_1.CARD_PROGRAM_SEEDS.cardBotPdaSeed))], programId); return cardBotConfigPda; } static deriveBotUserCustodyPda(userId, cardProgramId) { const programId = (0, anchor_1.translateAddress)(cardProgramId); const [userCustodyPda] = web3_js_1.PublicKey.findProgramAddressSync([buffer_1.Buffer.from(anchor_1.utils.bytes.utf8.encode(userId))], programId); return userCustodyPda; } static deriveOnRampConfigPda(cardProgramId) { const programId = (0, anchor_1.translateAddress)(cardProgramId); const [onRampConfigPda] = web3_js_1.PublicKey.findProgramAddressSync([buffer_1.Buffer.from(anchor_1.utils.bytes.utf8.encode(constants_1.CARD_PROGRAM_SEEDS.onRampConfigSeed))], programId); return onRampConfigPda; } static deriveOnRampUserCustodyPda(userId, cardProgramId) { const programId = (0, anchor_1.translateAddress)(cardProgramId); const [onRampUserCustody] = web3_js_1.PublicKey.findProgramAddressSync([ buffer_1.Buffer.from(anchor_1.utils.bytes.utf8.encode(constants_1.CARD_PROGRAM_SEEDS.onRampUserCustodySeed)), buffer_1.Buffer.from(anchor_1.utils.bytes.utf8.encode(userId)), ], programId); return onRampUserCustody; } static deriveReloadableCardPda(cardProgramId) { const programId = (0, anchor_1.translateAddress)(cardProgramId); const [reloadableCardPda] = web3_js_1.PublicKey.findProgramAddressSync([buffer_1.Buffer.from(anchor_1.utils.bytes.utf8.encode(constants_1.CARD_PROGRAM_SEEDS.reloadAbleSeed))], programId); return reloadableCardPda; } constructor(instructions, _connection, _signTransaction) { this.instructions = instructions; this._connection = _connection; this._signTransaction = _signTransaction; this._programId = this.instructions.cardProgram.programId; } _createPayload(payerKey, instructions, signers, addressLookupTableAccounts) { return __awaiter(this, void 0, void 0, function* () { const errorMap = new Map(); this.instructions.cardProgram.idl.errors.forEach((error) => errorMap.set(error.code, error.msg)); return new payload_1.TransactionPayload(this._connection, errorMap, instructions, payerKey, signers, addressLookupTableAccounts, this._signTransaction); }); } initCardConfig(params) { return __awaiter(this, void 0, void 0, function* () { const { zicOwnerAddress, cardVaultAddress, revenueVaultAddress, commissionVaultAddress, usdcAddress, } = params; const zicOwner = (0, anchor_1.translateAddress)(zicOwnerAddress); const usdcToken = (0, anchor_1.translateAddress)(usdcAddress); const cardVault = (0, anchor_1.translateAddress)(cardVaultAddress); const revenueVault = (0, anchor_1.translateAddress)(revenueVaultAddress); const commissionVault = (0, anchor_1.translateAddress)(commissionVaultAddress); const cardConfig = ZebecCardService.deriveCardConfigPda(this._programId); const nativeFee = new anchor_1.BN((0, utils_1.percentToBps)(params.nativeFeePercent)); const nonNativeFee = new anchor_1.BN((0, utils_1.percentToBps)(params.nonNativeFeePercent)); const revenueFee = new anchor_1.BN((0, utils_1.percentToBps)(params.revenueFeePercent)); const maxCardAmount = new anchor_1.BN((0, bignumber_js_1.BigNumber)(params.maxCardAmount).times(constants_1.UNITS_PER_USDC).toFixed(0)); const minCardAmount = new anchor_1.BN((0, bignumber_js_1.BigNumber)(params.minCardAmount).times(constants_1.UNITS_PER_USDC).toFixed(0)); const dailyCardPurchaseLimit = new anchor_1.BN((0, bignumber_js_1.BigNumber)(params.dailyCardPurchaseLimit).times(constants_1.UNITS_PER_USDC).toFixed(0)); const feeTiers = params.feeTiers.map((feeTier) => { const minAmount = new anchor_1.BN((0, bignumber_js_1.BigNumber)(feeTier.minAmount).times(constants_1.UNITS_PER_USDC).toFixed(0)); const maxAmount = new anchor_1.BN((0, bignumber_js_1.BigNumber)(feeTier.maxAmount).times(constants_1.UNITS_PER_USDC).toFixed(0)); const fee = new anchor_1.BN((0, utils_1.percentToBps)(feeTier.feePercent)); return { minAmount, maxAmount, fee, }; }); (0, utils_1.sortFeeTierDesc)(feeTiers); const ixs = []; const ix = yield this.instructions.getInitCardConfigsInstruction(cardConfig, usdcToken, zicOwner, { revenueVault, cardVault, commissionVault, nativeFee, nonNativeFee, revenueFee, feeTiers, maxCardAmount, minCardAmount, dailyCardPurchaseLimit, }); ixs.push(ix); const cardVaultUsdcAccount = (0, utils_1.getAssociatedTokenAddressSync)(usdcToken, cardVault, true); const cardVaultUsdcAccountInfo = yield this._connection.getAccountInfo(cardVaultUsdcAccount, "confirmed"); if (!cardVaultUsdcAccountInfo) { ixs.push((0, utils_1.createAssociatedTokenAccountInstruction)(zicOwner, cardVaultUsdcAccount, cardVault, usdcToken)); } if (!revenueVault.equals(cardVault)) { const revenueVaultUsdcAccount = (0, utils_1.getAssociatedTokenAddressSync)(usdcToken, revenueVault, true); const revenueVaultUsdcAccountInfo = yield this._connection.getAccountInfo(revenueVaultUsdcAccount, "confirmed"); if (!revenueVaultUsdcAccountInfo) { ixs.push((0, utils_1.createAssociatedTokenAccountInstruction)(zicOwner, revenueVaultUsdcAccount, revenueVault, usdcToken)); } } if (!commissionVault.equals(revenueVault) && !commissionVault.equals(cardVault)) { const commissionVaultUsdcAccount = (0, utils_1.getAssociatedTokenAddressSync)(usdcToken, commissionVault, true); const commissionVaultUsdcAccountInfo = yield this._connection.getAccountInfo(commissionVaultUsdcAccount, "confirmed"); if (!commissionVaultUsdcAccountInfo) { ixs.push((0, utils_1.createAssociatedTokenAccountInstruction)(zicOwner, commissionVaultUsdcAccount, commissionVault, usdcToken)); } } const payload = yield this._createPayload(zicOwner, ixs, []); return payload; }); } setCardConfig(params) { return __awaiter(this, void 0, void 0, function* () { const { zicOwnerAddress, newZicOwnerAddress, cardVaultAddress, revenueVaultAddress, commissionVaultAddress, } = params; const zicOwner = (0, anchor_1.translateAddress)(zicOwnerAddress); const newZicOwner = (0, anchor_1.translateAddress)(newZicOwnerAddress); const cardVault = (0, anchor_1.translateAddress)(cardVaultAddress); const revenueVault = (0, anchor_1.translateAddress)(revenueVaultAddress); const commissionVault = (0, anchor_1.translateAddress)(commissionVaultAddress); const cardConfig = ZebecCardService.deriveCardConfigPda(this._programId); const cardConfigInfo = yield this.instructions.cardProgram.account.card.fetch(cardConfig, "confirmed"); if (!zicOwner.equals(cardConfigInfo.zicOwner)) { throw new Error("Invalid owner of the config."); } const usdcToken = cardConfigInfo.usdcMint; const nativeFee = new anchor_1.BN((0, utils_1.percentToBps)(params.nativeFeePercent)); const nonNativeFee = new anchor_1.BN((0, utils_1.percentToBps)(params.nonNativeFeePercent)); const revenueFee = new anchor_1.BN((0, utils_1.percentToBps)(params.revenueFeePercent)); const maxCardAmount = new anchor_1.BN((0, bignumber_js_1.BigNumber)(params.maxCardAmount).times(constants_1.UNITS_PER_USDC).toFixed(0)); const minCardAmount = new anchor_1.BN((0, bignumber_js_1.BigNumber)(params.minCardAmount).times(constants_1.UNITS_PER_USDC).toFixed(0)); const dailyCardPurchaseLimit = new anchor_1.BN((0, bignumber_js_1.BigNumber)(params.dailyCardPurchaseLimit).times(constants_1.UNITS_PER_USDC).toFixed(0)); const feeTiers = params.feeTiers.map((feeTier) => { const minAmount = new anchor_1.BN((0, bignumber_js_1.BigNumber)(feeTier.minAmount).times(constants_1.UNITS_PER_USDC).toFixed(0)); const maxAmount = new anchor_1.BN((0, bignumber_js_1.BigNumber)(feeTier.maxAmount).times(constants_1.UNITS_PER_USDC).toFixed(0)); const fee = new anchor_1.BN((0, utils_1.percentToBps)(feeTier.feePercent)); return { minAmount, maxAmount, fee, }; }); (0, utils_1.sortFeeTierDesc)(feeTiers); const ixs = []; const ix = yield this.instructions.getSetCardConfigsInstruction(cardConfig, zicOwner, { newZicOwner, revenueFee, nativeFee, nonNativeFee, cardVault, revenueVault, commissionVault, feeTiers, maxCardAmount, minCardAmount, dailyCardPurchaseLimit, }); ixs.push(ix); const cardVaultUsdcAccount = (0, utils_1.getAssociatedTokenAddressSync)(usdcToken, cardVault, true); const cardVaultUsdcAccountInfo = yield this._connection.getAccountInfo(cardVaultUsdcAccount, "confirmed"); if (!cardVaultUsdcAccountInfo) { ixs.push((0, utils_1.createAssociatedTokenAccountInstruction)(zicOwner, cardVaultUsdcAccount, cardVault, usdcToken)); } if (!revenueVault.equals(cardVault)) { const revenueVaultUsdcAccount = (0, utils_1.getAssociatedTokenAddressSync)(usdcToken, revenueVault, true); const revenueVaultUsdcAccountInfo = yield this._connection.getAccountInfo(revenueVaultUsdcAccount, "confirmed"); if (!revenueVaultUsdcAccountInfo) { ixs.push((0, utils_1.createAssociatedTokenAccountInstruction)(zicOwner, revenueVaultUsdcAccount, revenueVault, usdcToken)); } } if (!commissionVault.equals(revenueVault) && !commissionVault.equals(cardVault)) { const commissionVaultUsdcAccount = (0, utils_1.getAssociatedTokenAddressSync)(usdcToken, commissionVault, true); const commissionVaultUsdcAccountInfo = yield this._connection.getAccountInfo(commissionVaultUsdcAccount, "confirmed"); if (!commissionVaultUsdcAccountInfo) { ixs.push((0, utils_1.createAssociatedTokenAccountInstruction)(zicOwner, commissionVaultUsdcAccount, commissionVault, usdcToken)); } } const payload = yield this._createPayload(zicOwner, ixs, []); return payload; }); } setCustomFees(params) { return __awaiter(this, void 0, void 0, function* () { const zicOwner = (0, anchor_1.translateAddress)(params.zicOwnerAddress); const cardConfigPda = ZebecCardService.deriveCardConfigPda(this._programId); const tokenFeeMapPda = ZebecCardService.deriveTokenFeeMapPda(this._programId); const parsedTokenFeeList = params.tokenFeeList.map((tokenFee) => ({ tokenAddress: (0, anchor_1.translateAddress)(tokenFee.tokenAddress), fee: new anchor_1.BN((0, utils_1.percentToBps)(tokenFee.fee)), })); const ix = yield this.instructions.getSetCustomFeesInstruction(cardConfigPda, tokenFeeMapPda, zicOwner, parsedTokenFeeList); return this._createPayload(zicOwner, [ix], []); }); } deleteCustomFees(params) { return __awaiter(this, void 0, void 0, function* () { const zicOwner = (0, anchor_1.translateAddress)(params.zicOwnerAddress); const cardConfigPda = ZebecCardService.deriveCardConfigPda(this._programId); const tokenFeeMapPda = ZebecCardService.deriveTokenFeeMapPda(this._programId); const tokenList = params.tokenAddressList.map((tokenAddress) => (0, anchor_1.translateAddress)(tokenAddress)); const ix = yield this.instructions.getDeleteCustomFeesInstruction(cardConfigPda, tokenFeeMapPda, zicOwner, tokenList); return this._createPayload(zicOwner, [ix], []); }); } swapAndDeposit(params) { return __awaiter(this, void 0, void 0, function* () { const { userAddress, quoteInfo } = params; if ("error" in quoteInfo) { throw new errors_1.QuoteResponseError(quoteInfo.error); } const isSwapModeExactIn = quoteInfo.swapMode === "ExactIn"; const cardConfig = ZebecCardService.deriveCardConfigPda(this._programId); const cardPdaDecoded = yield this.instructions.cardProgram.account.card.fetch(cardConfig, "confirmed"); const user = (0, anchor_1.translateAddress)(userAddress); const inputMint = (0, anchor_1.translateAddress)(quoteInfo.inputMint); const usdc = (0, anchor_1.translateAddress)(quoteInfo.outputMint); if (!usdc.equals(cardPdaDecoded.usdcMint)) { throw new errors_1.InvalidUsdcAddressError(usdc.toString()); } const buyerInputMintAta = (0, utils_1.getAssociatedTokenAddressSync)(inputMint, user); if (!inputMint.equals(constants_1.WSOL)) { const buyerInputMintAtaInfo = yield this._connection.getAccountInfo(buyerInputMintAta, "confirmed"); if (!buyerInputMintAtaInfo) { throw new errors_1.AssociatedTokenAccountDoesNotExistsError("User doesn't have associated token account of input mint: " + inputMint.toString()); } } const revenueVault = cardPdaDecoded.revenueVault; const userPurchaseRecord = ZebecCardService.deriveUserPurchaseRecordPda(user, this._programId); const userVault = ZebecCardService.deriveUserVaultPda(user, this._programId); const tokenFeeMapPda = ZebecCardService.deriveTokenFeeMapPda(this._programId); const userVaultUsdcAta = (0, utils_1.getAssociatedTokenAddressSync)(usdc, userVault, true); const userUsdcAta = (0, utils_1.getAssociatedTokenAddressSync)(usdc, user); const revenueVaultUsdcAta = (0, utils_1.getAssociatedTokenAddressSync)(usdc, revenueVault, true); // check if user has enough balance if (!inputMint.equals(constants_1.WSOL)) { const resAndCtx = yield this._connection.getTokenAccountBalance(buyerInputMintAta, "confirmed"); const balance = resAndCtx.value.amount; if (balance === "" || (0, bignumber_js_1.BigNumber)(balance).lt(quoteInfo.inAmount)) { throw new errors_1.NotEnoughBalanceError("User doesn't have enough input mint balance"); } } else { const balance = yield this._connection.getBalance(user, "confirmed"); if ((0, bignumber_js_1.BigNumber)(balance).lt(quoteInfo.inAmount)) { throw new errors_1.NotEnoughBalanceError("User doesn't have enough SOL balance"); } } // get serialized transactions for the swap const { swapTransaction } = yield (yield fetch(constants_1.JUP_SWAP_API, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ // quoteResponse from /quote api quoteResponse: quoteInfo, // user public key to be used for the swap userPublicKey: user.toString(), // auto wrap and unwrap SOL. default is true wrapAndUnwrapSol: true, // feeAccount is optional. Use if you want to charge a fee. feeBps must have been passed in /quote API. // feeAccount: "fee_account_public_key" }), })).json(); // deserialize the transaction const swapTransactionBuf = buffer_1.Buffer.from(swapTransaction, "base64"); const transaction = web3_js_1.VersionedTransaction.deserialize(swapTransactionBuf); // get address lookup table accounts const addressLookupTableAccounts = yield Promise.all(transaction.message.addressTableLookups.map((lookup) => __awaiter(this, void 0, void 0, function* () { const data = yield this._connection .getAccountInfo(lookup.accountKey) .then((res) => res.data); return new web3_js_1.AddressLookupTableAccount({ key: lookup.accountKey, state: web3_js_1.AddressLookupTableAccount.deserialize(data), }); }))); const lookupTableData = yield this._connection .getAccountInfo(constants_1.CARD_LOOKUP_TABLE_ADDRESS) .then((res) => res.data); addressLookupTableAccounts.push(new web3_js_1.AddressLookupTableAccount({ key: constants_1.CARD_LOOKUP_TABLE_ADDRESS, state: web3_js_1.AddressLookupTableAccount.deserialize(lookupTableData), })); // console.log("address lookup table:\n", addressLookupTableAccounts); // decompile transaction message and add transfer instruction let message = web3_js_1.TransactionMessage.decompile(transaction.message, { addressLookupTableAccounts: addressLookupTableAccounts, }); const instructions = message.instructions; const amount = isSwapModeExactIn ? new anchor_1.BN(quoteInfo.otherAmountThreshold) : new anchor_1.BN(quoteInfo.outAmount); const depositInstruction = yield this.instructions.getDepositInstruction(cardConfig, tokenFeeMapPda, usdc, user, userUsdcAta, userVault, userVaultUsdcAta, userPurchaseRecord, revenueVault, revenueVaultUsdcAta, { amount, sourceTokenAddress: inputMint, }); instructions.push(depositInstruction); // const errorMap: Map<number, string> = new Map(); // this.instructions.cardProgram.idl.errors.forEach((error) => // errorMap.set(error.code, error.msg), // ); return this._createPayload(user, instructions, [], addressLookupTableAccounts); // return new TransactionPayload( // this._connection, // errorMap, // instructions, // user, // [], // addressLookupTableAccounts, // this._signTransaction, // ); }); } deposit(params) { return __awaiter(this, void 0, void 0, function* () { const { userAddress, amount, mintAddress } = params; const user = new web3_js_1.PublicKey(userAddress); const usdc = new web3_js_1.PublicKey(mintAddress); const cardConfig = ZebecCardService.deriveCardConfigPda(this._programId); const cardPdaDecoded = yield this.instructions.cardProgram.account.card.fetch(cardConfig, "confirmed"); const revenueVault = cardPdaDecoded.revenueVault; if (!usdc.equals(cardPdaDecoded.usdcMint)) { throw new errors_1.InvalidUsdcAddressError(usdc.toString()); } const userVault = ZebecCardService.deriveUserVaultPda(user, this._programId); const tokenFeeMapPda = ZebecCardService.deriveTokenFeeMapPda(this._programId); const userPurchaseRecord = ZebecCardService.deriveUserPurchaseRecordPda(user, this._programId); const userUsdcAta = (0, utils_1.getAssociatedTokenAddressSync)(usdc, user); const userVaultUsdcAta = (0, utils_1.getAssociatedTokenAddressSync)(usdc, userVault, true); const revenueVaultUsdcAta = (0, utils_1.getAssociatedTokenAddressSync)(usdc, revenueVault, true); const parsedAmount = (0, bignumber_js_1.BigNumber)(amount).times(constants_1.UNITS_PER_USDC).toFixed(); const userUsdcAtaInfo = yield this._connection.getAccountInfo(userUsdcAta, "confirmed"); if (!userUsdcAtaInfo) { throw new errors_1.AssociatedTokenAccountDoesNotExistsError("User doesn't have associated token account of token mint: " + usdc.toString()); } const resAndCtx = yield this._connection.getTokenAccountBalance(userUsdcAta, "confirmed"); const balance = resAndCtx.value.amount; if (balance === "" || (0, bignumber_js_1.BigNumber)(balance).lt(parsedAmount)) { throw new errors_1.NotEnoughBalanceError("User doesn't have enough balance"); } const ix = yield this.instructions.getDepositInstruction(cardConfig, tokenFeeMapPda, usdc, user, userUsdcAta, userVault, userVaultUsdcAta, userPurchaseRecord, revenueVault, revenueVaultUsdcAta, { amount: new anchor_1.BN(parsedAmount), }); let addressLookupTableAccounts; if (!constants_1.isSdkEnvDev) { const lookupTableData = yield this._connection .getAccountInfo(constants_1.CARD_LOOKUP_TABLE_ADDRESS) .then((res) => res.data); addressLookupTableAccounts = [ new web3_js_1.AddressLookupTableAccount({ key: constants_1.CARD_LOOKUP_TABLE_ADDRESS, state: web3_js_1.AddressLookupTableAccount.deserialize(lookupTableData), }), ]; } const payload = yield this._createPayload(user, [ix], [], addressLookupTableAccounts); return payload; }); } withdraw(params) { return __awaiter(this, void 0, void 0, function* () { const user = (0, anchor_1.translateAddress)(params.userAddress); const usdc = (0, anchor_1.translateAddress)(params.mintAddress); const userPurchaseRecord = ZebecCardService.deriveUserPurchaseRecordPda(user, this._programId); const userVault = ZebecCardService.deriveUserVaultPda(user, this._programId); const userUsdcAta = (0, utils_1.getAssociatedTokenAddressSync)(usdc, user); const userVaultUsdcAta = (0, utils_1.getAssociatedTokenAddressSync)(usdc, userVault, true); const amount = new anchor_1.BN((0, bignumber_js_1.BigNumber)(params.amount).times(constants_1.UNITS_PER_USDC).toFixed(0)); const ix = yield this.instructions.getWithdrawInstruction(user, usdc, userUsdcAta, userVault, userVaultUsdcAta, userPurchaseRecord, amount); const payload = yield this._createPayload(user, [ix], []); return payload; }); } buyCard(params) { return __awaiter(this, void 0, void 0, function* () { const user = (0, anchor_1.translateAddress)(params.buyerAddress); const usdcToken = (0, anchor_1.translateAddress)(params.mintAddress); const amount = (0, bignumber_js_1.BigNumber)(params.amount); // check if user's vault balance is enought for card purchase const userVaultBalance = yield this.getUserVaultBalance(user, usdcToken); if ((0, bignumber_js_1.BigNumber)(userVaultBalance).isLessThan(params.amount)) { throw new errors_1.NotEnoughBalanceError("Not have enough balance in user's vault for card purchase"); } const cardConfig = ZebecCardService.deriveCardConfigPda(this._programId); const cardPurchaseInfoPda = ZebecCardService.deriveCardPurchaseInfoPda(user, this._programId, params.nextBuyerCounter); const userVault = ZebecCardService.deriveUserVaultPda(user, this._programId); const userPurchaseRecord = ZebecCardService.deriveUserPurchaseRecordPda(user, this._programId); const cardConfigInfo = yield this.instructions.cardProgram.account.card.fetch(cardConfig, "confirmed"); const cardVault = cardConfigInfo.cardVault; const commissionVault = cardConfigInfo.commissionVault; const revenueVault = cardConfigInfo.revenueVault; const cardVaultAta = (0, utils_1.getAssociatedTokenAddressSync)(usdcToken, cardVault, true); const commissionVaultAta = (0, utils_1.getAssociatedTokenAddressSync)(usdcToken, commissionVault, true); const revenueVaultAta = (0, utils_1.getAssociatedTokenAddressSync)(usdcToken, revenueVault, true); const userVaultAta = (0, utils_1.getAssociatedTokenAddressSync)(usdcToken, userVault, true); this._checkAmountIsWithinProviderRange(cardConfigInfo, amount, false); yield this._checkAmountIsWithinDailyCardLimit(cardConfigInfo, userPurchaseRecord, amount, false); const parsedAmount = new anchor_1.BN(amount.times(constants_1.UNITS_PER_USDC).toFixed(0)); const index = new anchor_1.BN(params.nextBuyerCounter.toString()); const ix = yield this.instructions.getBuyPrepaidDigitalCardInstruction(cardPurchaseInfoPda, cardConfig, cardVault, cardVaultAta, commissionVault, commissionVaultAta, revenueVault, revenueVaultAta, usdcToken, user, userVault, userVaultAta, userPurchaseRecord, { amount: parsedAmount, cardType: params.cardType === "carbon" ? "reloadable" : "non_reloadable", index, }); const emailHash = yield (0, utils_1.hashSHA256)(params.buyerEmail.toString()); const memoIx = new web3_js_1.TransactionInstruction({ keys: [ { pubkey: user, isSigner: true, isWritable: true, }, ], programId: constants_1.MEMO_PROGRAM_ID, data: buffer_1.Buffer.from(JSON.stringify({ buyer: emailHash }), "utf-8"), }); const payload = yield this._createPayload(user, [ix, memoIx], []); return payload; }); } swapAndBuyCardDirect(params) { return __awaiter(this, void 0, void 0, function* () { const { buyerAddress, quoteInfo } = params; if ("error" in quoteInfo) { throw new errors_1.QuoteResponseError(quoteInfo.error); } const cardConfig = ZebecCardService.deriveCardConfigPda(this._programId); const cardConfigInfo = yield this.instructions.cardProgram.account.card.fetch(cardConfig, "confirmed"); const buyer = (0, anchor_1.translateAddress)(buyerAddress); const inputMint = (0, anchor_1.translateAddress)(quoteInfo.inputMint); const usdc = (0, anchor_1.translateAddress)(quoteInfo.outputMint); const revenueVault = cardConfigInfo.revenueVault; const userPurchaseRecord = ZebecCardService.deriveUserPurchaseRecordPda(buyer, this._programId); const cardPurchaseInfoPda = ZebecCardService.deriveCardPurchaseInfoPda(buyer, this._programId, params.nextBuyerCounter); const tokenFeeMapPda = ZebecCardService.deriveTokenFeeMapPda(this._programId); const cardVault = cardConfigInfo.cardVault; const cardVaultAta = (0, utils_1.getAssociatedTokenAddressSync)(usdc, cardVault, true); const revenueVaultAta = (0, utils_1.getAssociatedTokenAddressSync)(usdc, revenueVault, true); const buyerAta = (0, utils_1.getAssociatedTokenAddressSync)(usdc, buyer); const amount = quoteInfo.swapMode === "ExactIn" ? new bignumber_js_1.BigNumber(quoteInfo.otherAmountThreshold) : new bignumber_js_1.BigNumber(quoteInfo.outAmount); if (!usdc.equals(cardConfigInfo.usdcMint)) { throw new errors_1.InvalidUsdcAddressError(usdc.toString()); } const buyerInputMintAta = (0, utils_1.getAssociatedTokenAddressSync)(inputMint, buyer); if (!inputMint.equals(constants_1.WSOL)) { const buyerInputMintAtaInfo = yield this._connection.getAccountInfo(buyerInputMintAta, "confirmed"); if (!buyerInputMintAtaInfo) { throw new errors_1.AssociatedTokenAccountDoesNotExistsError("User doesn't have associated token account of input mint: " + inputMint.toString()); } } // check if user has enough balance if (!inputMint.equals(constants_1.WSOL)) { const resAndCtx = yield this._connection.getTokenAccountBalance(buyerInputMintAta, "confirmed"); const balance = resAndCtx.value.amount; if (balance === "" || (0, bignumber_js_1.BigNumber)(balance).lt(quoteInfo.inAmount)) { throw new errors_1.NotEnoughBalanceError("User doesn't have enough input mint balance"); } } else { const balance = yield this._connection.getBalance(buyer, "confirmed"); if ((0, bignumber_js_1.BigNumber)(balance).lt(quoteInfo.inAmount)) { throw new errors_1.NotEnoughBalanceError("User doesn't have enough SOL balance"); } } const customTokenFees = yield this.getCustomTokenFees(); const customTokenFee = customTokenFees.find((tf) => tf.tokenAddress === quoteInfo.inputMint); const swapFee = customTokenFee ? (0, bignumber_js_1.BigNumber)(customTokenFee.fee) : (0, bignumber_js_1.BigNumber)(constants_1.DEFAULT_SWAP_FEE); const amountAfterFeeDeduction = amount.minus(amount.times(swapFee.div(100))); this._checkAmountIsWithinProviderRange(cardConfigInfo, amountAfterFeeDeduction); yield this._checkAmountIsWithinDailyCardLimit(cardConfigInfo, userPurchaseRecord, amountAfterFeeDeduction); // get serialized transactions for the swap const { swapTransaction } = yield (yield fetch(constants_1.JUP_SWAP_API, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ // quoteResponse from /quote api quoteResponse: quoteInfo, // user public key to be used for the swap userPublicKey: buyer.toString(), // auto wrap and unwrap SOL. default is true wrapAndUnwrapSol: true, // feeAccount is optional. Use if you want to charge a fee. feeBps must have been passed in /quote API. // feeAccount: "fee_account_public_key" }), })).json(); // deserialize the transaction const swapTransactionBuf = buffer_1.Buffer.from(swapTransaction, "base64"); const transaction = web3_js_1.VersionedTransaction.deserialize(swapTransactionBuf); // get address lookup table accounts const addressLookupTableAccounts = yield Promise.all(transaction.message.addressTableLookups.map((lookup) => __awaiter(this, void 0, void 0, function* () { const data = yield this._connection .getAccountInfo(lookup.accountKey) .then((res) => res.data); return new web3_js_1.AddressLookupTableAccount({ key: lookup.accountKey, state: web3_js_1.AddressLookupTableAccount.deserialize(data), }); }))); const lookupTableData = yield this._connection .getAccountInfo(constants_1.CARD_LOOKUP_TABLE_ADDRESS) .then((res) => res.data); addressLookupTableAccounts.push(new web3_js_1.AddressLookupTableAccount({ key: constants_1.CARD_LOOKUP_TABLE_ADDRESS, state: web3_js_1.AddressLookupTableAccount.deserialize(lookupTableData), })); // console.log("address lookup table:\n", addressLookupTableAccounts); // decompile transaction message and add transfer instruction const message = web3_js_1.TransactionMessage.decompile(transaction.message, { addressLookupTableAccounts: addressLookupTableAccounts, }); const instructions = message.instructions; const parsedAmount = new anchor_1.BN(amount.toFixed(0, bignumber_js_1.BigNumber.ROUND_DOWN)); const index = new anchor_1.BN(params.nextBuyerCounter.toString()); const buyCardDirectIx = yield this.instructions.getBuyCardDirectInstruction(cardPurchaseInfoPda, cardConfig, cardVault, cardVaultAta, tokenFeeMapPda, revenueVault, revenueVaultAta, usdc, buyer, buyerAta, userPurchaseRecord, { amount: parsedAmount, cardType: params.cardType === "carbon" ? "reloadable" : "non_reloadable", index, sourceTokenAddress: inputMint, }); const emailHash = yield (0, utils_1.hashSHA256)(params.buyerEmail.toString()); const memoIx = new web3_js_1.TransactionInstruction({ keys: [ { pubkey: buyer, isSigner: true, isWritable: true, }, ], programId: constants_1.MEMO_PROGRAM_ID, data: buffer_1.Buffer.from(JSON.stringify({ buyer: emailHash }), "utf-8"), }); instructions.push(buyCardDirectIx, memoIx); return this._createPayload(buyer, instructions, [], addressLookupTableAccounts); }); } buyCardDirect(params) { return __awaiter(this, void 0, void 0, function* () { const user = (0, anchor_1.translateAddress)(params.buyerAddress); const usdcToken = (0, anchor_1.translateAddress)(params.mintAddress); const amount = (0, bignumber_js_1.BigNumber)(params.amount); const cardConfig = ZebecCardService.deriveCardConfigPda(this._programId); const cardPurchaseInfoPda = ZebecCardService.deriveCardPurchaseInfoPda(user, this._programId, params.nextBuyerCounter); const tokenFeeMapPda = ZebecCardService.deriveTokenFeeMapPda(this._programId); const userPurchaseRecord = ZebecCardService.deriveUserPurchaseRecordPda(user, this._programId); const cardConfigInfo = yield this.instructions.cardProgram.account.card.fetch(cardConfig, "confirmed"); const cardVault = cardConfigInfo.cardVault; const revenueVault = cardConfigInfo.revenueVault; const cardVaultAta = (0, utils_1.getAssociatedTokenAddressSync)(usdcToken, cardVault, true); const revenueVaultAta = (0, utils_1.getAssociatedTokenAddressSync)(usdcToken, revenueVault, true); const userAta = (0, utils_1.getAssociatedTokenAddressSync)(usdcToken, user); this._checkAmountIsWithinProviderRange(cardConfigInfo, amount, false); yield this._checkAmountIsWithinDailyCardLimit(cardConfigInfo, userPurchaseRecord, amount, false); const parsedAmount = new anchor_1.BN(amount.times(constants_1.UNITS_PER_USDC).toFixed(0)); const index = new anchor_1.BN(params.nextBuyerCounter.toString()); const ix = yield this.instructions.getBuyCardDirectInstruction(cardPurchaseInfoPda, cardConfig, cardVault, cardVaultAta, tokenFeeMapPda, revenueVault, revenueVaultAta, usdcToken, user, userAta, userPurchaseRecord, { amount: parsedAmount, cardType: params.cardType === "carbon" ? "reloadable" : "non_reloadable", index, }); const emailHash = yield (0, utils_1.hashSHA256)(params.buyerEmail.toString()); const memoIx = new web3_js_1.TransactionInstruction({ keys: [ { pubkey: user, isSigner: true, isWritable: true, }, ], programId: constants_1.MEMO_PROGRAM_ID, data: buffer_1.Buffer.from(JSON.stringify({ buyer: emailHash }), "utf-8"), }); const payload = yield this._createPayload(user, [ix, memoIx]); return payload; }); } _checkAmountIsWithinDailyCardLimit(cardConfigInfo_1, userPurchaseRecord_1, amount_1) { return __awaiter(this, arguments, void 0, function* (cardConfigInfo, userPurchaseRecord, amount, amountParsed = true) { const dailyCardBuyLimit = (0, bignumber_js_1.BigNumber)(cardConfigInfo.dailyCardBuyLimit.toString()); amount = amountParsed ? amount : amount.times(constants_1.UNITS_PER_USDC).decimalPlaces(0, bignumber_js_1.BigNumber.ROUND_DOWN); const today = new Date(); const userPurchaseRecordInfo = yield this.instructions.cardProgram.account.vault.fetchNullable(userPurchaseRecord, "confirmed"); if (!userPurchaseRecordInfo) { console.debug("No user purchase record exists."); return; } const lastCardBoughtDate = new Date(userPurchaseRecordInfo.dateTimeInUnix.toNumber() * 1000); let cardBoughtInADay = (0, bignumber_js_1.BigNumber)(0); if ((0, utils_1.areDateOfSameDay)(today, lastCardBoughtDate)) { cardBoughtInADay = (0, bignumber_js_1.BigNumber)(userPurchaseRecordInfo.totalBoughtPerDay.toString()).plus(amount); } else { cardBoughtInADay = amount; } if (cardBoughtInADay.isGreaterThan(dailyCardBuyLimit)) { throw new errors_1.DailyCardLimitReachedError(dailyCardBuyLimit.div(constants_1.UNITS_PER_USDC).toFixed(), cardBoughtInADay.div(constants_1.UNITS_PER_USDC).toFixed()); } }); } _checkAmountIsWithinProviderRange(cardConfigInfo, amount, amountParsed = true) { const minRange = (0, bignumber_js_1.BigNumber)(cardConfigInfo.providerConfig.minCardAmount.toString()); const maxRange = (0, bignumber_js_1.BigNumber)(cardConfigInfo.providerConfig.maxCardAmount.toString()); amount = amountParsed ? amount : amount.times(constants_1.UNITS_PER_USDC).decimalPlaces(0, bignumber_js_1.BigNumber.ROUND_DOWN); if (amount.isLessThan(minRange) || amount.isGreaterThan(maxRange)) { throw new errors_1.AmountOutOfRangeError(minRange.div(constants_1.UNITS_PER_USDC).toFixed(), maxRange.div(constants_1.UNITS_PER_USDC).toFixed(), amount.div(constants_1.UNITS_PER_USDC).toFixed()); } } initBotConfig(params) { return __awaiter(this, void 0, void 0, function* () { const zicOwner = (0, anchor_1.translateAddress)(params.zicOwnerAddress); const botAdmin = (0, anchor_1.translateAddress)(params.botAdminAddress); const cardBotConfig = ZebecCardService.deriveCardBotConfigPda(this._programId); const cardConfig = ZebecCardService.deriveCardConfigPda(this._programId); const ix = yield this.instructions.getInitBotConfigInstruction(cardBotConfig, cardConfig, zicOwner, { botAdmin, }); return this._createPayload(zicOwner, [ix], []); }); } initBotUserCustody(params) { return __awaiter(this, void 0, void 0, function* () { const { userId } = params; const botAdmin = (0, anchor_1.translateAddress)(params.botAdminAddress); const usdcToken = (0, anchor_1.translateAddress)(params.usdcMintAddress); const cardBotConfig = ZebecCardService.deriveCardBotConfigPda(this._programId); const userCustody = ZebecCardService.deriveBotUserCustodyPda(userId, this._programId); const userCustodyAta = (0, utils_1.getAssociatedTokenAddressSync)(usdcToken, userCustody, true); const ix = yield this.instructions.getInitBotUserCustodyInstruction(botAdmin, cardBotConfig, usdcToken, userCustody, userCustodyAta, { userId, }); return this._createPayload(botAdmin, [ix], []); }); } setBotAdmin(params) { return __awaiter(this, void 0, void 0, function* () { const newBotAdmin = (0, anchor_1.translateAddress)(params.newBotAdminAddress); const zicOwner = (0, anchor_1.translateAddress)(params.zicOwnerAddress); const cardBotConfig = ZebecCardService.deriveCardBotConfigPda(this._programId); const cardConfig = ZebecCardService.deriveCardConfigPda(this._programId); const ix = yield this.instructions.getSetBotAdminInstruction(cardBotConfig, cardConfig, zicOwner, { newAdmin: newBotAdmin }); return this._createPayload(zicOwner, [ix], []); }); } buyCardThroughBot(params) { return __awaiter(this, void 0, void 0, function* () { const { userId, cardType } = params; const botAdmin = (0, anchor_1.translateAddress)(params.botAdminAddress); const usdcToken = (0, anchor_1.translateAddress)(params.usdcMintAddress); const cardBotConfig = ZebecCardService.deriveCardBotConfigPda(this._programId); const cardConfig = ZebecCardService.deriveCardConfigPda(this._programId); const feeMapPda = ZebecCardService.deriveTokenFeeMapPda(this._programId); const userCustody = ZebecCardService.deriveBotUserCustodyPda(userId, this._programId); const userCustodyAta = (0, utils_1.getAssociatedTokenAddressSync)(usdcToken, userCustody, true); const cardConfigInfo = yield this.instructions.cardProgram.account.card.fetch(cardConfig); const cardVault = cardConfigInfo.cardVault; const revenueVault = cardConfigInfo.revenueVault; const cardVaultAta = (0, utils_1.getAssociatedTokenAddressSync)(usdcToken, cardVault, true); const revenueVaultAta = (0, utils_1.getAssociatedTokenAddressSync)(usdcToken, revenueVault, true); const amount = (0, bignumber_js_1.BigNumber)(params.amount) .times(constants_1.UNITS_PER_USDC) .decimalPlaces(0, bignumber_js_1.BigNumber.ROUND_DOWN); this._checkAmountIsWithinProviderRange(cardConfigInfo, amount, true); yield this._checkAmountIsWithinDailyCardLimitForBot(cardConfigInfo, userCustody, amount, true); const ix = yield this.instructions.getBuyCardBotInstruction(botAdmin, cardBotConfig, cardConfig, cardVault, cardVaultAta, feeMapPda, revenueVault, revenueVaultAta, usdcToken, userCustody, userCustodyAta, { amount: new anchor_1.BN(amount.toFixed()), sourceTokenAddress: usdcToken, userId, cardType: cardType === "carbon" ? "reloadable" : "non_reloadable", }); const memoIx = new web3_js_1.TransactionInstruction({ keys: [ { pubkey: botAdmin, isSigner: true, isWritable: true, }, ], programId: constants_1.MEMO_PROGRAM_ID, data: buffer_1.Buffer.from(JSON.stringify({ userId }), "utf-8"), }); return this._createPayload(botAdmin, [ix, memoIx], []); }); } _checkAmountIsWithinDailyCardLimitForBot(cardConfigInfo_1, botUserCustody_1, amount_1) { return __awaiter(this, arguments, void 0, function* (cardConfigInfo, botUserCustody, amount, amountParsed = true) { const dailyCardBuyLimit = (0, bignumber_js_1.BigNumber)(cardConfigInfo.dailyCardBuyLimit