Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: trading vault deposit #1698

Merged
merged 1 commit into from
Aug 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions src/adapters/mochi-pay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,27 @@ class MochiPay extends Fetcher {
},
)
}

async depositToEarningVault({
profileId,
vaultId,
amount,
tokenId,
}: {
profileId: string
vaultId: string
amount: string
tokenId: string
}): Promise<any> {
return await this.jsonFetch(
`${MOCHI_PAY_API_BASE_URL}/profiles/${profileId}/syndicates/earning-vaults/${vaultId}/deposit`,
{
method: "POST",
headers: { Authorization: `Bearer ${MOCHI_BOT_SECRET}` },
body: { amount, token_id: tokenId, platform: "discord" },
},
)
}
}

export default new MochiPay()
301 changes: 299 additions & 2 deletions src/commands/vault/info/processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@ import {
MessageButton,
MessageSelectMenu,
MessageAttachment,
SelectMenuInteraction,
MessageComponentInteraction,
} from "discord.js"
import { InternalError, OriginalMessage } from "errors"
import { APIError } from "errors"
import { composeEmbedMessage2 } from "ui/discord/embed"
import { composeEmbedMessage, composeEmbedMessage2 } from "ui/discord/embed"
import {
EmojiKey,
emojis,
equalIgnoreCase,
getEmoji,
getEmojiToken,
getEmojiURL,
Expand All @@ -23,15 +26,27 @@ import {
TokenEmojiKey,
} from "utils/common"
import { HOMEPAGE_URL, VERTICAL_BAR } from "utils/constants"
import { formatUsdDigit } from "utils/defi"
import { formatDigit, formatUsdDigit } from "utils/defi"
import {
getDiscordRenderableByProfileId,
getProfileIdByDiscord,
} from "utils/profile"
import mochiPay from "adapters/mochi-pay"
import moment from "moment"
import { utils } from "@consolelabs/mochi-formatter"
import { utils as etherUtils } from "ethers"
import { drawLineChart } from "utils/chart"
import { getBalances, isTokenSupported } from "utils/tip-bot"
import { getSlashCommand } from "utils/commands"
import { BigNumber } from "ethers"
import { checkCommitableOperation } from "commands/withdraw/index/processor"
import { parseUnits } from "ethers/lib/utils"

type Params = {
amount: string
balance?: any
tokenSymbol?: string
}

const getPnlIcon = (n: number) => (n >= 0 ? ":green_circle:" : ":red_circle:")

Expand Down Expand Up @@ -436,6 +451,7 @@ export async function runGetVaultDetail({
sol: data.solana_wallet_address,
},
vaultId: selectedVault,
vaultType: "trading",
report,
profileId,
vaultName: data.name,
Expand All @@ -461,6 +477,12 @@ export async function runGetVaultDetail({
.setEmoji(getEmoji("CALENDAR"))
.setStyle("SECONDARY")
.setCustomId("rounds"),
new MessageButton()
.setLabel("Deposit")
.setStyle("SECONDARY")
.setCustomId("trading_vault_deposit")
.setEmoji(getEmoji("MONEY"))
.setDisabled(!report.member_equity || !!interaction.ephemeral),
),
],
},
Expand Down Expand Up @@ -837,3 +859,278 @@ export async function vaultClaim({
},
}
}

// select deposit token
export async function depositStep1(interaction: ButtonInteraction, ctx: any) {
const balances = await getBalances({ msgOrInteraction: interaction })
if (balances.length === 1) {
const balance = balances.at(0)

await isTokenSupported(balance.token.symbol)

const { msgOpts } = await depositStep2(interaction, {
balance,
amount: "%0",
})

return {
context: {
token: balance.token.symbol,
tokenObj: balance,
amount: "%0",
},
msgOpts,
}
}

if (!balances.length) {
return {
msgOpts: {
embeds: [
composeEmbedMessage(null, {
description: `${getEmoji(
"NO",
)} You have no balance. Try ${await getSlashCommand(
"deposit",
)} first`,
color: msgColors.ERROR,
}),
],
},
}
}

// TODO: remove hardcode 1
const { text } = formatView("compact", "filter-dust", balances, 0)

const embed = composeEmbedMessage(null, {
author: ["Choose your money source", getEmojiURL(emojis.NFT2)],
description: text,
}).addFields(renderPreview({}))

const isDuplicateSymbol = (s: string) =>
balances.filter((b: any) => b.token.symbol.toUpperCase() === s).length > 1

return {
context: {
...ctx,
depositAmount: "%0",
},
msgOpts: {
attachments: [],
embeds: [
embed,
// ...(!filteredBals.length && filterSymbol
// ? [
// new MessageEmbed({
// description: `${getEmoji("NO")} No token ${getEmojiToken(
// filterSymbol as TokenEmojiKey,
// )} **${filterSymbol}** found in your balance.`,
// color: msgColors.ERROR,
// }),
// ]
// : []),
],
components: [
new MessageActionRow().addComponents(
new MessageSelectMenu()
.setPlaceholder("💵 Choose money source (1/2)")
.setCustomId("select_token")
.setOptions(
balances.slice(0, 25).map((b: any) => ({
label: `${b.token.symbol}${
isDuplicateSymbol(b.token.symbol)
? ` (${b.token.chain.symbol})`
: ""
}`,
value: b.id,
emoji: getEmojiToken(b.token.symbol),
})),
),
),
],
},
}
}

function renderPreview(params: {
network?: string
token?: string
amount?: string
}) {
return {
name: "\u200b\nPreview",
value: [
params.network &&
`${getEmoji("SWAP_ROUTE")}\`Network. \`${params.network}`,
`${getEmoji("SWAP_ROUTE")}\`Source. \`Mochi wallet`,
params.token &&
`${getEmoji("ANIMATED_COIN_1", true)}\`Token. \`${params.token}`,
params.token &&
params.amount &&
`${getEmoji("NFT2")}\`Amount. \`${getEmojiToken(
params.token as TokenEmojiKey,
)} **${params.amount} ${params.token}**`,
]
.filter(Boolean)
.join("\n"),
inline: false,
}
}

// select deposit amount
export async function depositStep2(
interaction: MessageComponentInteraction,
params: Params,
) {
const balances = await getBalances({ msgOrInteraction: interaction })
const balance =
params.balance ||
balances.find((b: any) => equalIgnoreCase(b.id, params.tokenSymbol))

let error: string | null = ""

const tokenAmount = balance.amount
const tokenDecimal = balance.token.decimal ?? 0

const getPercentage = (percent: number) =>
BigNumber.from(tokenAmount).mul(percent).div(100).toString()
let amount

const isAll =
params.amount === "%100" || equalIgnoreCase(params.amount ?? "", "all")
if (params.amount?.startsWith("%") || isAll) {
const formatted = etherUtils.formatUnits(
getPercentage(
params.amount?.toLowerCase() === "all"
? 100
: Number(params.amount?.slice(1)),
),
tokenDecimal,
)
amount = formatDigit({
value: formatted,
fractionDigits: isAll ? 2 : Number(formatted) >= 1000 ? 0 : 2,
})
} else {
let valid
;({ valid, error } = checkCommitableOperation(
balance.amount,
params.amount ?? "0",
balance.token,
))

if (valid) {
amount = formatUsdDigit(params.amount ?? "0")
}
}

const { text } = formatView("compact", "filter-dust", [balance], 0)
const isNotEmpty = !!text
const emptyText = `${getEmoji(
"ANIMATED_POINTING_RIGHT",
true,
)} You have nothing yet, use ${await getSlashCommand(
"earn",
)} or ${await getSlashCommand("deposit")} `

const embed = composeEmbedMessage(null, {
author: [
`How many ${balance.token.symbol} to withdraw ? `,
getEmojiURL(emojis.NFT2),
],
description: isNotEmpty ? text : emptyText,
}).addFields(
renderPreview({
network: balance.token.chain.name,
token: balance.token.symbol,
amount: String(error ? 0 : amount),
}),
)

return {
context: {
...params,
token: balance.token.symbol,
balance,
amount,
},
msgOpts: {
embeds: [
embed,
...(error
? [
composeEmbedMessage(null, {
description: `${getEmoji("NO")} **${error}**`,
color: msgColors.ERROR,
}),
]
: []),
],
components: [
new MessageActionRow().addComponents(
...[10, 25, 50].map((p) =>
new MessageButton()
.setLabel(`${p}%`)
.setStyle("SECONDARY")
.setCustomId(`select_amount_${p}`),
),
new MessageButton()
.setLabel("All")
.setStyle("SECONDARY")
.setCustomId(`select_amount_100`),
new MessageButton()
.setLabel("Custom")
.setStyle("SECONDARY")
.setCustomId("enter_amount"),
),
new MessageActionRow().addComponents(
new MessageButton()
.setLabel("Confirm (2/2)")
.setCustomId("submit")
.setStyle("PRIMARY")
.setDisabled(!!error || Number(amount) <= 0),
),
],
},
}
}

export async function executeTradingVaultDeposit(
i: ButtonInteraction,
ctx: any,
) {
const { balance, amount, vaultId, profileId, vaultName } = ctx
const { ok, error } = await mochiPay.depositToEarningVault({
profileId,
vaultId,
tokenId: balance.token.id,
amount: parseUnits(
amount.replaceAll(",", ""),
balance.token.decimal,
).toString(),
})

if (!ok) {
throw new InternalError({
msgOrInteraction: i,
title: "Failed to deposit",
description: error,
})
}

const embed = composeEmbedMessage2(i as any, {
author: ["Deposit successfully", getEmojiURL(emojis["MONEY"])],
description: `You have deposited ${getEmojiToken(
balance.token.symbol,
)} **${amount} ${balance.token.symbol}** to vault **${vaultName}**!`,
})

return {
msgOpts: {
embeds: [embed],
components: [],
attachments: [],
},
}
}
Loading
Loading