zic-evm-sdk
Version:
An sdk for interacting with zebec card evm contracts
495 lines (494 loc) • 21.4 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ZebecCardService = void 0;
const bignumber_js_1 = __importDefault(require("bignumber.js"));
const ethers_1 = require("ethers");
const artifacts_1 = require("./artifacts");
const constants_1 = require("./constants");
const utils_1 = require("./utils");
/**
* A class which holds methods and properties to interact with Zebec Instanct Card evm contracts.
* @example
*
* const signer = <ethers.Signer Instance> // most wallet provider have way to create signer
* const chainId = 11155111 // sepolia;
* const service = new ZebecCardService(signer, chainId);
*
*/
class ZebecCardService {
/**
* Create instance of ZebecCardService.
* @param signer ethers signer
* @param chainId chain ID supported by sdk
*/
constructor(signer, chainId) {
this.signer = signer;
this.chainId = chainId;
const zebecCardAddress = (0, constants_1.getZebecCardAddress)(chainId);
const usdcAddress = (0, constants_1.getUsdcAddress)(chainId);
const wethAddress = (0, constants_1.getWethAddress)(chainId);
this.zebecCard = [constants_1.ODYSSEY_MAINNET_CHAIN_ID, constants_1.ODYSSEY_TESTNET_CHAIN_ID].includes(chainId)
? artifacts_1.OdysseyZebecCard__factory.connect(zebecCardAddress, signer)
: artifacts_1.ZebecCard__factory.connect(zebecCardAddress, signer);
this.usdcToken = artifacts_1.Token__factory.connect(usdcAddress, signer);
this.weth = artifacts_1.Weth__factory.connect(wethAddress, signer);
}
/**
* Sets Native Fee in card config.
*
* Can only be invoked admin.
*
* @param params
* @returns
*/
async setNativeFee(params) {
const feeInBps = BigInt((0, utils_1.percentToBps)(params.feeInPercent));
return this.zebecCard.setNativeFee(feeInBps);
}
/**
* Sets NonNative Fee in card config
*
* Can only be invoked admin.
*
* @param params
* @returns
*/
async setNonNativeFee(params) {
const feeInBps = BigInt((0, utils_1.percentToBps)(params.feeInPercent));
return this.zebecCard.setNonNativeFee(feeInBps);
}
/**
* Sets Revenue Fee in card config
*
* Can only be invoked admin.
*
* @param params
* @returns
*/
async setRevenueFee(params) {
const feeInBps = BigInt((0, utils_1.percentToBps)(params.feeInPercent));
return this.zebecCard.setRevenueFee(feeInBps);
}
/**
* Sets Revenue vault address in card config
*
* Can only be invoked admin.
*
* @param params
* @returns
*/
async setRevenueVault(params) {
return this.zebecCard.setRevenueVault(params.vaultAddress);
}
/**
* Sets commission vault address in card config
*
* Can only be invoked admin.
*
* @param params
* @returns
*/
async setCommissionVault(params) {
return this.zebecCard.setComissionVault(params.vaultAddress);
}
/**
* Sets card vault address in card config
*
* Can only be invoked admin.
*
*/
async setCardVault(params) {
return this.zebecCard.setCardVault(params.vaultAddress);
}
/**
* Sets usdc address in card config
* Can only be invoked by admin
*/
async setUsdcAddress(params) {
return this.zebecCard.setUsdcAddress(params.tokenAddress);
}
/**
* Sets minimum card amount for purchasing card in card config
*/
async setMinCardAmount(params) {
const decimals = await this.usdcToken.decimals();
const minCardAmount = ethers_1.ethers.parseUnits(params.minCardAmount, decimals);
return this.zebecCard.setMinCardAmount(minCardAmount);
}
/**
* Sets maximum card amount for purchasing card in card config
*/
async setMaxCardAmount(params) {
const decimals = await this.usdcToken.decimals();
const maxCardAmount = ethers_1.ethers.parseUnits(params.maxCardAmount, decimals);
return this.zebecCard.setMaxCardAmount(maxCardAmount);
}
/**
* Sets daily card purchase limit in card config
*/
async setDailyCardPurchaseLimit(params) {
const decimals = await this.usdcToken.decimals();
const dailyCardPurchaseLimit = ethers_1.ethers.parseUnits(params.dailyCardPurchaseLimit, decimals);
return this.zebecCard.setDailyCardBuyLimit(dailyCardPurchaseLimit);
}
/**
* Updates fee for given min - max (range) amount and inserts if range in not found.
*/
async setFee(params) {
const decimals = await this.usdcToken.decimals();
const minAmount = ethers_1.ethers.parseUnits(params.minAmount, decimals);
const maxAmount = ethers_1.ethers.parseUnits(params.maxAmount, decimals);
const fee = (0, utils_1.percentToBps)(params.feePercent);
return this.zebecCard.setFee(minAmount, maxAmount, fee);
}
/**
* Sets fee tiers only be invoked by admin
*/
async setFeeTiers(params) {
const parsedFeeTiers = await this._parseFeeTiers(params.feeTiers);
return this.zebecCard.setFeeArray(parsedFeeTiers);
}
async _parseFeeTiers(feeTiers) {
const decimals = await this.usdcToken.decimals();
return feeTiers.map((feeTier) => {
return {
fee: (0, utils_1.percentToBps)(feeTier.feePercent),
minAmount: ethers_1.ethers.parseUnits(feeTier.minAmount, decimals),
maxAmount: ethers_1.ethers.parseUnits(feeTier.maxAmount, decimals),
};
});
}
async setCustomFee(params) {
const fee = (0, utils_1.percentToBps)(params.fee.toString());
return this.zebecCard.setCustomTokenFee(params.tokenAddress, fee);
}
async getCustomFee(params) {
const fee = await this.zebecCard.getCustomTokenFee(params.tokenAddress);
return (0, utils_1.bpsToPercent)(fee.toString());
}
/**
* Deposits usdc to user vault
* @param params
* @returns
*/
async depositUsdc(params) {
if ([constants_1.ODYSSEY_MAINNET_CHAIN_ID, constants_1.ODYSSEY_TESTNET_CHAIN_ID].includes(this.chainId)) {
throw new Error("Method not supported for this chain");
}
const decimals = await this.usdcToken.decimals();
const parsedAmount = ethers_1.ethers.parseUnits(params.amount, decimals);
return this.zebecCard.depositUsdc(parsedAmount);
}
/**
* Withdraw usdc from user vault
* @param params
* @returns
*/
async withdraw(params) {
if ([constants_1.ODYSSEY_MAINNET_CHAIN_ID, constants_1.ODYSSEY_TESTNET_CHAIN_ID].includes(this.chainId)) {
throw new Error("Method not supported for this chain");
}
const decimals = await this.usdcToken.decimals();
const parsedAmount = ethers_1.ethers.parseUnits(params.amount, decimals);
return this.zebecCard.withdraw(parsedAmount);
}
/**
* Transfer specified amount from user's vault balance to card vault with some fee amount for card purchase.
* @param params
* @returns
*/
async buyCard(params) {
if ([constants_1.ODYSSEY_MAINNET_CHAIN_ID, constants_1.ODYSSEY_TESTNET_CHAIN_ID].includes(this.chainId)) {
throw new Error("Method not supported for this chain");
}
const decimals = await this.usdcToken.decimals();
const parsedAmount = ethers_1.ethers.parseUnits(params.amount, decimals);
if (!(0, utils_1.isEmailValid)(params.buyerEmail)) {
throw new Error("Invalid email: " + params.buyerEmail);
}
const vaultBalance = await this.zebecCard.cardBalances(this.signer);
if (parsedAmount > vaultBalance) {
throw new Error("Not enough balance. Vault balance: " +
ethers_1.ethers.formatUnits(vaultBalance, decimals) +
" Requested amount: " +
params.amount);
}
const cardConfig = await this.zebecCard.cardConfig();
const minRange = cardConfig.minCardAmount;
const maxRange = cardConfig.maxCardAmount;
if (parsedAmount < minRange || parsedAmount > maxRange) {
throw new Error("Amount must be with range: " +
ethers_1.ethers.formatUnits(minRange, decimals) +
" - " +
ethers_1.ethers.formatUnits(maxRange, decimals));
}
const cardPurchaseInfo = await this.zebecCard.cardPurchases(this.signer);
const lastCardPurchaseDate = new Date(Number(cardPurchaseInfo.unixInRecord * 1000n));
const today = new Date();
let cardPurchaseOfDay = 0n;
if ((0, utils_1.areDatesOfSameDay)(today, lastCardPurchaseDate)) {
cardPurchaseOfDay = cardPurchaseInfo.totalCardBoughtPerDay + parsedAmount;
}
else {
cardPurchaseOfDay = parsedAmount;
}
if (cardPurchaseOfDay > cardConfig.dailyCardBuyLimit) {
throw new Error("Requested card purchase amount exceeds daily purchase limit. Daily limit: " +
ethers_1.ethers.formatUnits(cardConfig.dailyCardBuyLimit, decimals) +
" Today's purchase amount: " +
ethers_1.ethers.formatUnits(cardPurchaseInfo.totalCardBoughtPerDay, decimals));
}
const emailHash = await (0, utils_1.hashSHA256)(params.buyerEmail);
return this.zebecCard.buyCard(parsedAmount, params.cardTypeId, emailHash);
}
/**
* Swaps given src token to usdc and transfers to user's vault
* @param params
* @param overrides
* @returns
*/
async swapAndDeposit(params, overrides) {
if ([constants_1.ODYSSEY_MAINNET_CHAIN_ID, constants_1.ODYSSEY_TESTNET_CHAIN_ID].includes(this.chainId)) {
throw new Error("Method not supported for this chain");
}
const { swapParams, ether } = params;
const srcToken = artifacts_1.Token__factory.connect(swapParams.description.srcToken, this.signer);
const dstToken = artifacts_1.Token__factory.connect(swapParams.description.dstToken, this.signer);
const srcTokenDecimals = await srcToken.decimals();
const dstTokenDecimals = await dstToken.decimals();
const executor = swapParams.executor;
const description = {
srcToken: swapParams.description.srcToken,
dstToken: swapParams.description.dstToken,
srcReceiver: swapParams.description.srcReceiver,
dstReceiver: swapParams.description.dstReceiver,
amount: ethers_1.ethers.parseUnits(swapParams.description.srcAmount, srcTokenDecimals),
minReturnAmount: ethers_1.ethers.parseUnits(swapParams.description.minReturnAmount, dstTokenDecimals),
flags: BigInt(swapParams.description.flags),
};
const routeData = swapParams.routeData;
return this.zebecCard.swapAndDeposit(executor, description, routeData, {
value: ethers_1.ethers.parseEther(ether),
...overrides,
});
}
async buyCardDirect(params) {
const decimals = await this.usdcToken.decimals();
const parsedAmount = ethers_1.ethers.parseUnits(params.amount, decimals);
if (!(0, utils_1.isEmailValid)(params.buyerEmail)) {
throw new Error("Invalid email: " + params.buyerEmail);
}
const cardConfig = await this.zebecCard.cardConfig();
const minRange = cardConfig.minCardAmount;
const maxRange = cardConfig.maxCardAmount;
if (parsedAmount < minRange || parsedAmount > maxRange) {
throw new Error("Amount must be with range: " +
ethers_1.ethers.formatUnits(minRange, decimals) +
" - " +
ethers_1.ethers.formatUnits(maxRange, decimals));
}
const cardPurchaseInfo = await this.zebecCard.cardPurchases(this.signer);
const lastCardPurchaseDate = new Date(Number(cardPurchaseInfo.unixInRecord * 1000n));
const today = new Date();
let cardPurchaseOfDay = 0n;
if ((0, utils_1.areDatesOfSameDay)(today, lastCardPurchaseDate)) {
cardPurchaseOfDay = cardPurchaseInfo.totalCardBoughtPerDay + parsedAmount;
}
else {
cardPurchaseOfDay = parsedAmount;
}
if (cardPurchaseOfDay > cardConfig.dailyCardBuyLimit) {
throw new Error("Requested card purchase amount exceeds daily purchase limit. Daily limit: " +
ethers_1.ethers.formatUnits(cardConfig.dailyCardBuyLimit, decimals) +
" Today's purchase amount: " +
ethers_1.ethers.formatUnits(cardPurchaseInfo.totalCardBoughtPerDay, decimals));
}
const emailHash = await (0, utils_1.hashSHA256)(params.buyerEmail);
return this.zebecCard.buyCardDirect(parsedAmount, params.cardTypeId, emailHash);
}
async swapAndBuyCardDirect(params, overrides) {
if ([constants_1.ODYSSEY_MAINNET_CHAIN_ID, constants_1.ODYSSEY_TESTNET_CHAIN_ID].includes(this.chainId)) {
throw new Error("Method not supported for this chain");
}
const { buyerEmail, cardTypeId, swapParams, ether } = params;
const srcToken = artifacts_1.Token__factory.connect(swapParams.description.srcToken, this.signer);
const dstToken = artifacts_1.Token__factory.connect(swapParams.description.dstToken, this.signer);
const srcTokenDecimals = await srcToken.decimals();
const dstTokenDecimals = await dstToken.decimals();
const executor = swapParams.executor;
const amount = ethers_1.ethers.parseUnits(swapParams.description.srcAmount, srcTokenDecimals);
const minReturnAmount = ethers_1.ethers.parseUnits(swapParams.description.minReturnAmount, dstTokenDecimals);
const description = {
srcToken: swapParams.description.srcToken,
dstToken: swapParams.description.dstToken,
srcReceiver: swapParams.description.srcReceiver,
dstReceiver: swapParams.description.dstReceiver,
amount,
minReturnAmount,
flags: BigInt(swapParams.description.flags),
};
const routeData = swapParams.routeData;
const cardConfig = await this.zebecCard.cardConfig();
const minRange = cardConfig.minCardAmount;
const maxRange = cardConfig.maxCardAmount;
const fee = await this.getCustomFee({ tokenAddress: swapParams.description.srcToken });
const feeAmount = (0, bignumber_js_1.default)(amount.toString()).times((0, bignumber_js_1.default)(fee).div(100));
const amountAfterFeeDeduction = BigInt((0, bignumber_js_1.default)(amount.toString()).minus(feeAmount).toFixed(0, bignumber_js_1.default.ROUND_DOWN));
if (amountAfterFeeDeduction < minRange || amountAfterFeeDeduction > maxRange) {
throw new Error("Amount must be with range: " +
ethers_1.ethers.formatUnits(minRange, dstTokenDecimals) +
" - " +
ethers_1.ethers.formatUnits(maxRange, dstTokenDecimals));
}
const cardPurchaseInfo = await this.zebecCard.cardPurchases(this.signer);
const lastCardPurchaseDate = new Date(Number(cardPurchaseInfo.unixInRecord * 1000n));
const today = new Date();
let cardPurchaseOfDay = 0n;
if ((0, utils_1.areDatesOfSameDay)(today, lastCardPurchaseDate)) {
cardPurchaseOfDay = cardPurchaseInfo.totalCardBoughtPerDay + amountAfterFeeDeduction;
}
else {
cardPurchaseOfDay = amountAfterFeeDeduction;
}
if (cardPurchaseOfDay > cardConfig.dailyCardBuyLimit) {
throw new Error("Requested card purchase amount exceeds daily purchase limit. Daily limit: " +
ethers_1.ethers.formatUnits(cardConfig.dailyCardBuyLimit, dstTokenDecimals) +
" Today's purchase amount will be: " +
ethers_1.ethers.formatUnits(cardPurchaseOfDay, dstTokenDecimals));
}
const emailHash = await (0, utils_1.hashSHA256)(buyerEmail);
return this.zebecCard.swapAndBuy(executor, description, routeData, cardTypeId, emailHash, {
value: ethers_1.ethers.parseEther(ether),
...overrides,
});
}
/**
* Deposit usdc from user's vault to yield provider
* @param params
* @returns
*/
async generateYield(params) {
if ([constants_1.ODYSSEY_MAINNET_CHAIN_ID, constants_1.ODYSSEY_TESTNET_CHAIN_ID].includes(this.chainId)) {
throw new Error("Method not supported for this chain");
}
const decimals = await this.usdcToken.decimals();
const parsedAmount = ethers_1.ethers.parseUnits(params.amount, decimals);
return this.zebecCard.generateYield(parsedAmount);
}
/**
* Withdaw deposits and yield from yield provider
* @param params
* @returns
*/
async withdrawYield(params) {
if ([constants_1.ODYSSEY_MAINNET_CHAIN_ID, constants_1.ODYSSEY_TESTNET_CHAIN_ID].includes(this.chainId)) {
throw new Error("Method not supported for this chain");
}
const decimals = await this.usdcToken.decimals();
const parsedAmount = ethers_1.ethers.parseUnits(params.amount, decimals);
return this.zebecCard.withdrawYield(parsedAmount);
}
/**
* Gets user's vault balance
* @param params
* @returns
*/
async getUserBalance(params) {
const decimals = await this.usdcToken.decimals();
const cardBalance = await this.zebecCard.cardBalances(params.userAddress);
const formattedBalance = ethers_1.ethers.formatUnits(cardBalance, decimals);
return formattedBalance;
}
/**
* Gets user's card purchase
* @param params
* @returns
*/
async getCardPurhcaseOfDay(params) {
const decimals = await this.usdcToken.decimals();
const cardPurchase = await this.zebecCard.cardPurchases(params.userAddress);
const totalCardPurchased = ethers_1.ethers.formatUnits(cardPurchase.totalCardBoughtPerDay, decimals);
const cardPurchasedTimestamp = Number(cardPurchase.unixInRecord.toString());
return {
totalCardPurchased,
cardPurchasedTimestamp,
};
}
/**
* Gets Zebec Instant Card contract configs
* @returns
*/
async getCardConfig() {
const cardConfig = await this.zebecCard.cardConfig();
const nativeFeePercent = (0, utils_1.bpsToPercent)(cardConfig.nativeFee.toString());
const nonNativeFeePercent = (0, utils_1.bpsToPercent)(cardConfig.nonNativeFee.toString());
const revenueFeePercent = (0, utils_1.bpsToPercent)(cardConfig.revenueFee.toString());
const decimals = await this.usdcToken.decimals();
const minCardAmount = ethers_1.ethers.formatUnits(cardConfig.minCardAmount, decimals);
const maxCardAmount = ethers_1.ethers.formatUnits(cardConfig.maxCardAmount, decimals);
const dailyCardPurchaseLimit = ethers_1.ethers.formatUnits(cardConfig.dailyCardBuyLimit, decimals);
return {
cardVault: cardConfig.cardVault,
commissionVault: cardConfig.commissionVault,
revenueVault: cardConfig.revenueVault,
nativeFeePercent,
nonNativeFeePercent,
revenueFeePercent,
totalCardSold: cardConfig.counter,
usdcAddress: cardConfig.usdcAddress,
maxCardAmount,
minCardAmount,
dailyCardPurchaseLimit,
};
}
/**
* Gets fee tiers
* @returns Array of fee tier
*/
async getFeeTiers() {
const decimals = await this.usdcToken.decimals();
const feeTiers = await this.zebecCard.getFeeTiers();
return feeTiers.map((feeTier) => {
return {
feePercent: (0, utils_1.bpsToPercent)(feeTier.fee.toString()),
maxAmount: ethers_1.ethers.formatUnits(feeTier.maxAmount, decimals),
minAmount: ethers_1.ethers.formatUnits(feeTier.minAmount, decimals),
};
});
}
/**
* Gets admin adddress
* @returns
*/
async getAdmin() {
return this.zebecCard.owner();
}
/**
* add allowance of given amount of given token to given spender
* @param params
* @returns
*/
async approve(params) {
const token = artifacts_1.Token__factory.connect(params.token, this.signer);
const decimals = await token.decimals();
const parsedAmount = ethers_1.ethers.parseUnits(params.amount, decimals);
const allowance = await token.allowance(this.signer, params.spender);
if (allowance < parsedAmount) {
return await token.approve(params.spender, parsedAmount);
}
return null;
}
/**
* Wrap ETH to Wrapped ETH
* @param param
* @returns
*/
async wrapEth(param) {
const parsedAmount = ethers_1.ethers.parseEther(param.amount);
return this.weth.deposit({ value: parsedAmount });
}
}
exports.ZebecCardService = ZebecCardService;