zebec-instant-card-sdk
Version:
An sdk for interacting with zebec instant card program in solana.
775 lines • 72.2 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());
});
};
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