diff --git a/CHANGELOG.md b/CHANGELOG.md index f0f4bd672..697f90c8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,38 @@ +# [6.52.0-rc.5](https://github.com/consolelabs/mochi-discord/compare/v6.52.0-rc.4...v6.52.0-rc.5) (2024-06-03) + + +### Features + +* trading vault config view ([#1683](https://github.com/consolelabs/mochi-discord/issues/1683)) ([99d3c64](https://github.com/consolelabs/mochi-discord/commit/99d3c640cd0e6d39a310e7263eb4e3034429af6d)) + +# [6.52.0-rc.4](https://github.com/consolelabs/mochi-discord/compare/v6.52.0-rc.3...v6.52.0-rc.4) (2024-06-03) + + +### Bug Fixes + +* update select vault for profile ([#1682](https://github.com/consolelabs/mochi-discord/issues/1682)) ([5458938](https://github.com/consolelabs/mochi-discord/commit/5458938bcf258af750e296c6afbae27390ce9fba)) + +# [6.52.0-rc.3](https://github.com/consolelabs/mochi-discord/compare/v6.52.0-rc.2...v6.52.0-rc.3) (2024-06-03) + + +### Bug Fixes + +* vault report - back btn interaction ([#1681](https://github.com/consolelabs/mochi-discord/issues/1681)) ([a9a80a0](https://github.com/consolelabs/mochi-discord/commit/a9a80a0cb20031df8373084182a92f2892d9c39c)) + +# [6.52.0-rc.2](https://github.com/consolelabs/mochi-discord/compare/v6.52.0-rc.1...v6.52.0-rc.2) (2024-06-03) + + +### Bug Fixes + +* /bal ([e4541bb](https://github.com/consolelabs/mochi-discord/commit/e4541bbfd1a41b5eacc83ce21f08b24e73e16e89)) + +# [6.52.0-rc.1](https://github.com/consolelabs/mochi-discord/compare/v6.51.0...v6.52.0-rc.1) (2024-06-03) + + +### Features + +* vault commands flow ([#1679](https://github.com/consolelabs/mochi-discord/issues/1679)) ([5e1e371](https://github.com/consolelabs/mochi-discord/commit/5e1e3712c0ad59f83185e630605d3bcf94afab69)) + # [6.51.0](https://github.com/consolelabs/mochi-discord/compare/v6.50.2...v6.51.0) (2024-05-31) diff --git a/package.json b/package.json index 4de541a7c..7af8be428 100644 --- a/package.json +++ b/package.json @@ -121,5 +121,5 @@ "node": "18.x" }, "name": "mochi-discord", - "version": "6.51.0" + "version": "6.52.0-rc.5" } diff --git a/src/adapters/mochi-pay.ts b/src/adapters/mochi-pay.ts index 343812673..7db179650 100644 --- a/src/adapters/mochi-pay.ts +++ b/src/adapters/mochi-pay.ts @@ -341,9 +341,13 @@ class MochiPay extends Fetcher { return data } - async listEarningVaults(profileId: string): Promise { + async listEarningVaults( + profileId: string, + fetchTradeDetails = false, + ): Promise { const { data: res, ok } = await this.jsonFetch( `${MOCHI_PAY_API_BASE_URL}/profiles/${profileId}/syndicates/earning-vaults`, + { query: { fetchTradeDetails } }, ) let data = [] if (ok) { @@ -352,9 +356,14 @@ class MochiPay extends Fetcher { return data } - async getEarningVault(profileId: string, vaultId: string): Promise { + async getEarningVault( + profileId: string, + vaultId: string, + query?: { roundId?: string }, + ): Promise { return await this.jsonFetch( `${MOCHI_PAY_API_BASE_URL}/profiles/${profileId}/syndicates/earning-vaults/${vaultId}`, + { query }, ) } @@ -363,6 +372,20 @@ class MochiPay extends Fetcher { `${MOCHI_PAY_API_BASE_URL}/profiles/${profileId}/syndicates/earning-vaults/${vaultId}/trade-rounds`, ) } + + async getEarningVaultConfigs( + profileId: string, + vaultId: string, + ): Promise { + const { ok, data: res } = await this.jsonFetch( + `${MOCHI_PAY_API_BASE_URL}/profiles/${profileId}/syndicates/earning-vaults/${vaultId}/configs`, + ) + let data = null + if (ok) { + data = res as any + } + return data + } } export default new MochiPay() diff --git a/src/commands/balances/index/processor.ts b/src/commands/balances/index/processor.ts index 800a3a843..9313a2fe4 100644 --- a/src/commands/balances/index/processor.ts +++ b/src/commands/balances/index/processor.ts @@ -528,8 +528,15 @@ export function formatView( } }) .sort((a, b) => { - return b.usdVal - a.usdVal + if (b.avgCost && a.avgCost && b.avgCost.startsWith("+")) return 1 + if (b.avgCost && a.avgCost && a.avgCost.startsWith("+")) return -1 + if (!b.avgCost && a.avgCost) return 1 + if (b.avgCost && !a.avgCost) return -1 + return -1 }) + // .sort((a, b) => { + // return b.usdVal - a.usdVal + // }) .filter((b) => b.text) const paginated = chunk(formattedBal, PAGE_SIZE) const { joined: text } = formatDataTable( @@ -540,10 +547,10 @@ export function formatView( price: `$${b.price}`, })), { - cols: ["balance", "usd", "price", "avgCost"], + cols: ["balance", "usd", "avgCost"], rowAfterFormatter: (formatted, i) => `${paginated[page][i].emoji}${formatted}${paginated[page][i].tailEmoji}`, - separator: [` ${APPROX} `, VERTICAL_BAR, VERTICAL_BAR], + separator: [VERTICAL_BAR, VERTICAL_BAR], alignment: ["left", "left", "left"], }, ) diff --git a/src/commands/profile/index/slash.ts b/src/commands/profile/index/slash.ts index 0de803580..f697ffe6b 100644 --- a/src/commands/profile/index/slash.ts +++ b/src/commands/profile/index/slash.ts @@ -39,7 +39,11 @@ const machineConfig: (target: Target) => MachineConfig = (target) => ({ addWallet: (i) => handleWalletAddition(i), }, select: { - vault: async (i) => await runGetVaultDetail(i.values[0].split("_")[1], i), + vault: async (i) => + await runGetVaultDetail({ + interaction: i, + selectedVault: i.values[0].split("_")[1], + }), }, // indicates this action to result in ephemeral response ephemeral: { diff --git a/src/commands/vault/info/processor.ts b/src/commands/vault/info/processor.ts index 37eb5ae56..f0f30620c 100644 --- a/src/commands/vault/info/processor.ts +++ b/src/commands/vault/info/processor.ts @@ -27,14 +27,14 @@ import { getDiscordRenderableByProfileId, getProfileIdByDiscord, } from "utils/profile" -import { faker } from "@faker-js/faker" import mochiPay from "adapters/mochi-pay" import moment from "moment" import { utils } from "@consolelabs/mochi-formatter" -function formatDate(d: Date) { - return `${d.getUTCDate()}.${d.getUTCMonth() + 1}.${d.getUTCFullYear()}` -} +const getPnlIcon = (n: number) => (n >= 0 ? ":green_circle:" : ":red_circle:") + +const formatDate = (d: Date) => + `${d.getUTCDate()}.${d.getUTCMonth() + 1}.${d.getUTCFullYear()}` export async function handleVaultRounds( vaultId: string, @@ -84,6 +84,10 @@ export async function handleVaultRounds( }) return { + context: { + vaultId, + vaultType: "trading", + }, msgOpts: { embeds: [embed], components: [ @@ -114,39 +118,70 @@ export async function handleVaultRounds( } } -export async function vaultReport(interaction: ButtonInteraction) { +export async function vaultReport( + interaction: ButtonInteraction, + report: any, + vaultId: string, +) { const basicInfo = [ - `<:Look:1150701811536248865> \`Positions. \` Open 1 / Close 9`, - `${getEmoji("CASH")} \`Init. \` $11,023.61`, - `:dart: \`PnL. \` -$586.16 (:red_circle: -5.32%)`, + `<:Look:1150701811536248865> \`Positions. \` Open ${report.total_open_trade} / Close ${report.total_closed_trade}`, + `${getEmoji("CASH")} \`Init. \` ${utils.formatUsdPriceDigit({ + value: report.first_initial_balance, + shorten: false, + })}`, + `:dart: \`PnL. \` ${utils.formatUsdPriceDigit({ + value: report.total_pnl, + shorten: false, + })} (${getPnlIcon(report.total_pnl)} ${utils.formatPercentDigit( + report.total_realized_pl * 100, + )})`, ].join("\n") - const openTrades = [ - "**Open trades**", - `\`24.05.08\` ${getEmoji( - "ANIMATED_COIN_1", - )} Init: $10,410 💰 Current: $22 **(:green_circle: 0.22%)**`, - ].join("\n") - - const closedTrades = [ - "**Closed trades**", - `\`24.05.07\` ${getEmoji( - "WAVING_HAND", - )} Init: $10,653 💰 PnL: -$247 (:red_circle: -2.32%)`, - `\`24.05.07\` ${getEmoji( - "WAVING_HAND", - )} Init: $10,785 💰 PnL: -$131 (:red_circle: -1.22%)`, - `\`24.05.07\` ${getEmoji( - "WAVING_HAND", - )} Init: $10,802 💰 PnL: -$17 (:red_circle: -0.16%)`, - ].join("\n") + const { open_trades, close_trades } = report + const openTrades = open_trades + ? [ + `**Open trades (${report.total_open_trade})**`, + `\`${formatDate(new Date(open_trades.opened_time))}\` ${getEmoji( + "ANIMATED_COIN_1", + )} Init: ${utils.formatUsdPriceDigit({ + value: open_trades.initial_balance, + shorten: false, + })} 💰 Current: ${utils.formatUsdPriceDigit({ + value: open_trades.unrealized_pnl, + shorten: false, + })} (${getPnlIcon( + open_trades.unrealized_pl, + )} ${utils.formatPercentDigit(open_trades.unrealized_pl * 100)})`, + ].join("\n") + "\n\n" + : "" + + const closeTrades = close_trades?.length + ? [ + `**Closed trades (${report.total_closed_trade})**`, + ...close_trades.slice(0, 5).map( + (t: any) => + `\`${formatDate(new Date(t.closed_time))}\` ${getEmoji( + "WAVING_HAND", + )} Init: ${utils.formatUsdPriceDigit({ + value: t.initial_balance, + shorten: false, + })} 💰 PnL: ${utils.formatUsdPriceDigit({ + value: t.realized_pnl ?? 0, + shorten: false, + })} (${getPnlIcon(t.realized_pl)} ${utils.formatPercentDigit( + t.realized_pl * 100, + )})`, + ), + ].join("\n") + : "" const embed = composeEmbedMessage2(interaction as any, { color: msgColors.BLUE, author: ["Trading vault report", getEmojiURL(emojis.ANIMATED_DIAMOND)], - description: `${basicInfo}\n\n${openTrades}\n\n${closedTrades}`, + description: `${basicInfo}\n\n${openTrades}${closeTrades}`, }) return { + context: { vaultId, vaultType: "trading" }, msgOpts: { embeds: [embed], components: [], @@ -164,11 +199,17 @@ function getVaultEquityEmoji(percent: string | number = 0) { return "🐳" } -export async function runGetVaultDetail( - selectedVault: string, - interaction: OriginalMessage, +export async function runGetVaultDetail({ + interaction, + selectedVault, vaultType = "spot", -) { + roundId, +}: { + interaction: OriginalMessage + selectedVault: string + vaultType?: string + roundId?: string +}) { // trading vault if (vaultType === "trading" && "user" in interaction) { const profileId = await getProfileIdByDiscord(interaction.user.id) @@ -180,7 +221,7 @@ export async function runGetVaultDetail( originalError, log, status = 500, - } = await mochiPay.getEarningVault(profileId, selectedVault) + } = await mochiPay.getEarningVault(profileId, selectedVault, { roundId }) if (!ok) { if (status === 400 && originalError) { throw new InternalError({ @@ -193,7 +234,7 @@ export async function runGetVaultDetail( } const creator = await getDiscordRenderableByProfileId(profileId) - const { account, investor_report: report } = data + const { investor_report: report } = data const { open_trades, close_trades } = report const basicInfo = [ `${getEmoji("ANIMATED_VAULT", true)}\`Name. \` ${data.name}`, @@ -213,9 +254,6 @@ export async function runGetVaultDetail( )}`, ].join("\n") - const getPnlIcon = (n: number) => - n >= 0 ? ":green_circle:" : ":red_circle:" - const startRound = moment(new Date(report.opened_trade_round_time)) const roundFields = [ `**Round info**`, @@ -268,7 +306,7 @@ export async function runGetVaultDetail( })} (${getPnlIcon( open_trades.unrealized_pl, )} ${utils.formatPercentDigit(open_trades.unrealized_pl * 100)})`, - ].join("\n") + ].join("\n") + "\n\n" : "" const closeTrades = close_trades?.length @@ -301,7 +339,7 @@ export async function runGetVaultDetail( )}\``, ].join("\n") - const description = `${basicInfo}\n\n${vaultEquity}\n\n${address}\n\n${roundFields}\n\n${openTrades}\n\n${closeTrades}` + const description = `${basicInfo}\n\n${vaultEquity}\n\n${address}\n\n${roundFields}\n\n${openTrades}${closeTrades}` const embed = composeEmbedMessage2(interaction as any, { color: msgColors.BLUE, author: ["Trading vault info", getEmojiURL(emojis.ANIMATED_DIAMOND)], @@ -314,6 +352,8 @@ export async function runGetVaultDetail( evm: data.evm_wallet_address, sol: data.solana_wallet_address, }, + vaultId: selectedVault, + report, }, msgOpts: { embeds: [embed], diff --git a/src/commands/vault/info/slash.ts b/src/commands/vault/info/slash.ts index 556e9814f..b1f9040fb 100644 --- a/src/commands/vault/info/slash.ts +++ b/src/commands/vault/info/slash.ts @@ -14,24 +14,67 @@ import { MachineConfig, route, RouterSpecialAction } from "utils/router" import { SlashCommandSubcommandBuilder } from "@discordjs/builders" import { handleVaultRounds, runGetVaultDetail, vaultReport } from "./processor" +import profile from "adapters/profile" +import mochiPay from "adapters/mochi-pay" +import { runVaultList } from "commands/vault/list/processor" -const machineConfig: (n: string) => MachineConfig = (vaultName) => ({ +export const machineConfig: ( + id: string, + name: string, + context?: any, +) => MachineConfig = (id, vaultName, context) => ({ id: "vault-info", - initial: "vaultInfo", + initial: id, context: { button: { - vaultReport, - vaultInfo: (i) => runGetVaultDetail(vaultName, i), - vaultRounds: async (i) => await handleVaultRounds(vaultName, i), + vaultReport: (i, _ev, ctx) => { + const { report, vaultId } = ctx + return vaultReport(i, report, vaultId) + }, + vaultInfo: (i, _ev, ctx) => { + const { vaultId, vaultType } = ctx + return runGetVaultDetail({ + selectedVault: vaultName || vaultId, + interaction: i, + vaultType, + }) + }, + vaultRounds: (i, _ev, ctx) => { + const vaultId = vaultName || ctx.vaultId + return handleVaultRounds(vaultId, i) + }, + vaultList: (i) => runVaultList(i), }, select: { - vaultInfo: (i) => runGetVaultDetail(vaultName, i), + vaultInfo: async (i, _ev, ctx) => { + if (i.customId === "select_round") { + const roundId = i.values[0] + const { vaultId, vaultType } = ctx + return runGetVaultDetail({ + selectedVault: vaultId, + interaction: i, + vaultType, + roundId, + }) + } + const [vaultType, selectedVault] = i.values[0].startsWith("trading_") + ? i.values[0].split("_", 2) + : ["spot", i.values[0].split(" - ")[0]] + return runGetVaultDetail({ selectedVault, interaction: i, vaultType }) + }, }, ephemeral: { DEPOSIT: true, }, + ...context, }, states: { + vaultList: { + id: "vaultList", + on: { + VIEW_VAULT: "vaultInfo", + }, + }, vaultRounds: { on: { [RouterSpecialAction.BACK]: "vaultInfo", @@ -45,6 +88,9 @@ const machineConfig: (n: string) => MachineConfig = (vaultName) => ({ }, vaultInfo: { on: { + BACK: [ + { target: "vaultList", cond: (context) => !!context.fromVaultList }, + ], ROUNDS: "vaultRounds", REPORT: "vaultReport", DEPOSIT: { @@ -79,96 +125,119 @@ const command: SlashCommand = { return } const focusedValue = i.options.getFocused() - const data = await config.vaultList(i.guildId, true) + // const data = await config.vaultList(i.guildId, true) + const userProfile = await profile.getByDiscord(i.user.id) + const [spotVaults, tradingVaults] = await Promise.all([ + config.vaultList(i.guildId, true), + mochiPay.listEarningVaults(userProfile.id), + ]) - const options = data - .filter((d: any) => - d.name.toLowerCase().includes(focusedValue.toLowerCase()), - ) - .map((d: any) => ({ name: d.name, value: d.name })) + const options = [ + ...spotVaults + .filter((d: any) => + d.name.toLowerCase().includes(focusedValue.toLowerCase()), + ) + .map((d: any) => ({ name: d.name, value: `spot_${d.name}` })), + ...tradingVaults + .filter((v: any) => + v.name.toLowerCase().includes(focusedValue.toLowerCase()), + ) + .map((v: any) => ({ name: v.name, value: `trading_${v.id}` })), + ] await i.respond(options) }, run: async function (interaction: CommandInteraction) { - const vaultName = interaction.options.getString("name", true) - const { context, msgOpts } = await runGetVaultDetail(vaultName, interaction) + const param = interaction.options.getString("name", true) + const [vaultType, selectedVault] = param.split("_") + const { context, msgOpts } = await runGetVaultDetail({ + selectedVault, + interaction, + vaultType, + }) const reply = (await interaction.editReply(msgOpts)) as Message - route(reply, interaction, machineConfig(vaultName), { - actions: { - showVaultDeposit: async (_, event) => { - if ( - !event.interaction || - !event.interaction.isButton() || - event.interaction.customId !== "deposit" - ) - return + route( + reply, + interaction, + machineConfig("vaultInfo", selectedVault, context), + { + actions: { + showVaultDeposit: async (_, event) => { + if ( + !event.interaction || + !event.interaction.isButton() || + event.interaction.customId !== "deposit" + ) + return - const addresses = [ - { - symbol: "ETH", - address: context.deposit.evm, - decimal: 18, - chainId: 1, - chainType: chainTypes.EVM, - isNative: true, - }, - { - symbol: "SOL", - address: context.deposit.sol, - decimal: 18, - chainId: 1, - chainType: chainTypes.SOL, - isNative: true, - explorer: "https://solscan.io", - }, - ] - const { context: ctx, msgOpts } = renderListDepositAddress({ - addresses, - }) + const addresses = [ + { + symbol: "ETH", + address: context.deposit.evm, + decimal: 18, + chainId: 1, + chainType: chainTypes.EVM, + isNative: true, + }, + { + symbol: "SOL", + address: context.deposit.sol, + decimal: 18, + chainId: 1, + chainType: chainTypes.SOL, + isNative: true, + }, + ] + const { context: ctx, msgOpts } = renderListDepositAddress({ + addresses, + }) - const reply = (await event.interaction.editReply(msgOpts)) as Message + const reply = (await event.interaction.editReply( + msgOpts, + )) as Message - route(reply, event.interaction, { - id: "vault-deposit", - initial: "depositList", - context: { - button: { - depositList: () => - Promise.resolve(renderListDepositAddress({ addresses })), - }, - select: { - depositDetail: async (i, _ev, ctx) => { - return { - msgOpts: await depositDetail( - i, - 1, - ctx.addresses.find((a: any) => - equalIgnoreCase(a.address, i.values.at(0)), + route(reply, event.interaction, { + id: "vault-deposit", + initial: "depositList", + context: { + button: { + depositList: () => + Promise.resolve(renderListDepositAddress({ addresses })), + }, + select: { + depositDetail: async (i, _ev, ctx) => { + return { + msgOpts: await depositDetail( + i, + 1, + ctx.addresses.find((a: any) => + equalIgnoreCase(a.address, i.values.at(0)), + ), ), - ), - } + } + }, }, + ...ctx, }, - ...ctx, - }, - states: { - depositList: { - on: { - VIEW_DEPOSIT_ADDRESS: "depositDetail", + states: { + depositList: { + on: { + VIEW_DEPOSIT_ADDRESS: "depositDetail", + }, }, - }, - depositDetail: { - on: { - BACK: "depositList", + depositDetail: { + on: { + BACK: "depositList", + }, }, }, - }, - }) + }) + }, }, }, - }) + ) }, help: () => Promise.resolve({ diff --git a/src/commands/vault/list/processor.ts b/src/commands/vault/list/processor.ts index 279e4e04f..40ab6230f 100644 --- a/src/commands/vault/list/processor.ts +++ b/src/commands/vault/list/processor.ts @@ -1,23 +1,19 @@ import config from "adapters/config" import { + ButtonInteraction, CommandInteraction, - Message, MessageActionRow, - MessageButton, MessageSelectMenu, User, } from "discord.js" import { InternalError } from "errors" import { - authorFilter, EmojiKey, getEmoji, msgColors, shortenHashOrAddress, } from "utils/common" import { getSlashCommand } from "utils/commands" -import { wrapError } from "utils/wrap-error" -import { handleVaultRounds, runGetVaultDetail } from "../info/processor" import { composeEmbedMessage, formatDataTable } from "ui/discord/embed" import profile from "adapters/profile" import { ModelVault } from "types/api" @@ -64,14 +60,16 @@ function mockData(data: Array) { }) } -export async function runVaultList(interaction: CommandInteraction) { +export async function runVaultList( + interaction: CommandInteraction | ButtonInteraction, +) { const userProfile = await profile.getByDiscord(interaction.user.id) const spotVaults = interaction.guildId ? await config.vaultList(interaction.guildId) : await config.vaultList("", false, userProfile.id) const tradingVaults = ( - (await mochiPay.listEarningVaults(userProfile.id)) || [] + await mochiPay.listEarningVaults(userProfile.id, true) ).map((v: any) => ({ id: v.id, name: v.name, @@ -160,77 +158,8 @@ export async function runVaultList(interaction: CommandInteraction) { components, } - const reply = (await interaction.followUp({ - ephemeral: true, - fetchReply: true, - ...msgOpts, - })) as Message - - collectSelection(reply, interaction.user, components, userProfile.id) -} - -function collectSelection( - reply: Message, - author: User, - components: any, - profileId: string, -) { - reply - .createMessageComponentCollector({ - componentType: "SELECT_MENU", - filter: authorFilter(author.id), - time: 300000, - }) - .on("collect", (i) => { - wrapError(reply, async () => { - if (!i.deferred) { - await i.deferUpdate().catch(() => null) - } - - const [vaultType, selectedVault] = i.values[0].startsWith("trading_") - ? i.values[0].split("_") - : ["spot", i.values[0].split(" - ")[0]] - i.guildId = i.guildId || i.values[0].split(" - ")[1] - const { msgOpts } = await runGetVaultDetail(selectedVault, i, vaultType) - - msgOpts.components = [ - ...msgOpts.components, - new MessageActionRow().addComponents( - new MessageButton() - .setLabel("Back") - .setStyle("SECONDARY") - .setCustomId("back"), - ), - ] as any - const edited = (await i.editReply(msgOpts)) as Message - - edited - .createMessageComponentCollector({ - filter: authorFilter(author.id), - componentType: "BUTTON", - time: 300000, - }) - .on("collect", (i) => { - wrapError(edited, async () => { - if (!i.deferred) { - await i.deferUpdate().catch(() => null) - } - if (i.customId === "rounds") { - const { msgOpts } = await handleVaultRounds(selectedVault, i) - i.editReply({ - embeds: msgOpts.embeds, - components: msgOpts.components, - }) - } else { - i.editReply({ embeds: reply.embeds, components }) - } - }) - }) - }) - }) - .on("end", () => { - wrapError(reply, async () => { - await reply.edit({ components: [] }).catch(() => null) - }) - }) + return { + msgOpts, + initial: "vaultList", + } } diff --git a/src/commands/vault/list/slash.ts b/src/commands/vault/list/slash.ts index f42e3bfd5..7985cef22 100644 --- a/src/commands/vault/list/slash.ts +++ b/src/commands/vault/list/slash.ts @@ -1,9 +1,11 @@ import { SlashCommandSubcommandBuilder } from "@discordjs/builders" -import { CommandInteraction } from "discord.js" +import { CommandInteraction, Message } from "discord.js" import { SlashCommand } from "types/common" import { GM_GITBOOK, SLASH_PREFIX } from "utils/constants" import { composeEmbedMessage2 } from "ui/discord/embed" import { runVaultList } from "./processor" +import { MachineConfig, route } from "utils/router" +import { machineConfig } from "commands/vault/info/slash" const command: SlashCommand = { name: "list", @@ -14,7 +16,14 @@ const command: SlashCommand = { .setDescription("Show current vault") }, run: async function (interaction: CommandInteraction) { - await runVaultList(interaction) + const { initial, msgOpts } = await runVaultList(interaction) + const reply = (await interaction.editReply(msgOpts)) as Message + + route( + reply, + interaction, + machineConfig("vaultList", "", { fromVaultList: true }), + ) }, help: async (interaction: CommandInteraction) => ({ embeds: [ diff --git a/src/commands/vault/view/processor.ts b/src/commands/vault/view/processor.ts index be1c7e409..a44dd48f9 100644 --- a/src/commands/vault/view/processor.ts +++ b/src/commands/vault/view/processor.ts @@ -4,14 +4,48 @@ import { GuildIdNotFoundError } from "errors" import { composeEmbedMessage } from "ui/discord/embed" import { getEmoji, msgColors } from "utils/common" import { buildTreasurerFields } from "../info/processor" +import mochiPay from "adapters/mochi-pay" +import { getDiscordIdByProfileId, getProfileIdByDiscord } from "utils/profile" export async function run({ i, guildId, + vaultId, + vaultType, }: { i: CommandInteraction guildId?: string | null + vaultId: string + vaultType: string }) { + if (vaultType === "trading") { + const profileId = await getProfileIdByDiscord(i.user.id) + const data = await mochiPay.getEarningVaultConfigs(profileId, vaultId) + const { name, api_key, secret_key, thresh_hold } = data + const basicInfo = [ + `${getEmoji("ANIMATED_VAULT", true)}\`Name. ${name}\``, + `${getEmoji("ANIMATED_VAULT_KEY")}\`API Key. \` ${api_key}`, + `${getEmoji("ANIMATED_VAULT_KEY")}\`Secret Key. \` ${secret_key}`, + `${getEmoji("APPROVE")}\`Threshold. \` ${thresh_hold}%`, + // `${getEmoji("NEWS")}\`Channel. \` <#1019524376527372288>`, + ].join("\n") + + const treasurers = await Promise.all( + data.treasurers.map(async (t: any) => { + const discordId = await getDiscordIdByProfileId(t.profile_id) + return { user_discord_id: discordId, role: t.role } + }), + ) + + const treasurersFields = buildTreasurerFields({ treasurer: treasurers }) + const embed = composeEmbedMessage(null, { + title: `${getEmoji("ANIMATED_DIAMOND")} Vault config`, + description: basicInfo, + color: msgColors.BLUE, + }).addFields(treasurersFields) + + return { messageOptions: { embeds: [embed] } } + } if (!guildId) { throw new GuildIdNotFoundError({ message: i }) } diff --git a/src/commands/vault/view/slash.ts b/src/commands/vault/view/slash.ts index 72f45d772..3a1d95301 100644 --- a/src/commands/vault/view/slash.ts +++ b/src/commands/vault/view/slash.ts @@ -5,11 +5,13 @@ import { SLASH_PREFIX } from "utils/constants" import { SlashCommand } from "types/common" import { run } from "./processor" import config from "adapters/config" +import profile from "adapters/profile" +import mochiPay from "adapters/mochi-pay" const command: SlashCommand = { name: "view", category: "Config", - onlyAdministrator: true, + // onlyAdministrator: true, prepare: () => { return new SlashCommandSubcommandBuilder() .setName("view") @@ -28,20 +30,37 @@ const command: SlashCommand = { return } const focusedValue = i.options.getFocused() - const data = await config.vaultList(i.guildId, true) + // const data = await config.vaultList(i.guildId, true) - await i.respond( - data + const userProfile = await profile.getByDiscord(i.user.id) + const [spotVaults, tradingVaults] = await Promise.all([ + config.vaultList(i.guildId, true), + mochiPay.listEarningVaults(userProfile.id), + ]) + + const options = [ + ...spotVaults .filter((d: any) => d.name.toLowerCase().includes(focusedValue.toLowerCase()), ) - .map((d: any) => ({ name: d.name, value: d.name })), - ) + .map((d: any) => ({ name: d.name, value: `spot_${d.id}` })), + ...tradingVaults + .filter((v: any) => + v.name.toLowerCase().includes(focusedValue.toLowerCase()), + ) + .map((v: any) => ({ name: v.name, value: `trading_${v.id}` })), + ] + + await i.respond(options) }, run: async function (interaction: CommandInteraction) { + const arg = interaction.options.getString("name", true) + const [vaultType, vaultId] = arg.split("_", 2) return run({ i: interaction, guildId: interaction.guildId ?? undefined, + vaultId, + vaultType, }) }, help: async (interaction: CommandInteraction) => ({ diff --git a/src/utils/profile.ts b/src/utils/profile.ts index 3146c9210..f05e0b836 100644 --- a/src/utils/profile.ts +++ b/src/utils/profile.ts @@ -39,3 +39,23 @@ export async function getDiscordRenderableByProfileId(profileId: string) { return `<@${discord.platform_identifier}>` } + +export async function getDiscordIdByProfileId(profileId: string) { + const pf = await profile.getById(profileId) + if (pf.err) { + throw new APIError({ + description: `[getDiscordIdByProfileId] API error with status ${pf.status_code}`, + curl: "", + status: pf.status ?? 500, + error: pf.error, + }) + } + + const discord = pf.associated_accounts.find((aa: any) => + equalIgnoreCase(aa.platform, "discord"), + ) + + if (!discord) return pf.profile_name + + return discord.platform_identifier +}