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: add temporary pending nomic deposits #3873

Draft
wants to merge 2 commits into
base: jose/fe-1119-add-nomic-bridge-provider
Choose a base branch
from
Draft
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
42 changes: 33 additions & 9 deletions packages/bridge/src/nomic/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Dec, RatePretty } from "@keplr-wallet/unit";
import { IbcTransferMethod } from "@osmosis-labs/types";
import { isCosmosAddressValid } from "@osmosis-labs/utils";
import { generateDepositAddressIbc } from "nomic-bitcoin";
import { isCosmosAddressValid, timeout } from "@osmosis-labs/utils";
import { generateDepositAddressIbc, getPendingDeposits } from "nomic-bitcoin";

import {
BridgeAsset,
Expand All @@ -21,7 +21,15 @@ export class NomicBridgeProvider implements BridgeProvider {
static readonly ID = "Nomic";
readonly providerName = NomicBridgeProvider.ID;

constructor(protected readonly ctx: BridgeProviderContext) {}
relayers: string[];

constructor(protected readonly ctx: BridgeProviderContext) {
this.relayers =
["https://testnet-relayer.nomic.io:8443"] ??
(this.ctx.env === "testnet"
? ["https://testnet-relayer.nomic.io:8443"]
: ["https://relayer.nomic.mappum.io:8443"]);
}

async getDepositAddress({
fromChain,
Expand Down Expand Up @@ -56,12 +64,10 @@ export class NomicBridgeProvider implements BridgeProvider {
}

const depositInfo = await generateDepositAddressIbc({
relayers:
this.ctx.env === "testnet"
? ["https://testnet-relayer.nomic.io:8443"]
: ["https://relayer.nomic.mappum.io:8443"],
channel: transferMethod.counterparty.channelId, // IBC channel ID on Nomic
bitcoinNetwork: this.ctx.env === "testnet" ? "testnet" : "bitcoin",
relayers: this.relayers,
channel: "channel-2" ?? transferMethod.counterparty.channelId, // IBC channel ID on Nomic
bitcoinNetwork:
"testnet" ?? (this.ctx.env === "testnet" ? "testnet" : "bitcoin"),
receiver: toAddress,
});

Expand Down Expand Up @@ -158,4 +164,22 @@ export class NomicBridgeProvider implements BridgeProvider {
url,
};
}

async getPendingDeposits({ address }: { address: string }) {
try {
const pendingDeposits = await timeout(
() => getPendingDeposits(this.relayers, address),
10000
)();

return pendingDeposits.map((deposit) => ({
transactionId: deposit.txid,
amount: deposit.amount,
confirmations: deposit.confirmations,
}));
} catch (error) {
console.error("Error getting pending Nomic deposits:", error);
return [];
}
}
}
193 changes: 121 additions & 72 deletions packages/web/components/bridge/deposit-address-screen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { FunctionComponent, ReactNode, useState } from "react";
import { useMeasure } from "react-use";

import { Icon } from "~/components/assets";
import { BridgeQuoteRemainingTime } from "~/components/bridge/bridge-quote-remaining-time";
import { QuoteDetailRow } from "~/components/bridge/quote-detail";
import { DepositAddressBridge } from "~/components/bridge/use-bridge-quotes";
import { SkeletonLoader, Spinner } from "~/components/loaders";
Expand Down Expand Up @@ -87,6 +88,25 @@ export const DepositAddressScreen = observer(
}
);

const {
data: pendingDepositsData,
isLoading: isPendingDepositsLoading,
dataUpdatedAt: nomicDepositsDataUpdatedAt,
} = api.bridgeTransfer.getNomicPendingDeposits.useQuery(
{
userOsmoAddress: osmosisAddress!,
},
{
enabled: !!osmosisAddress && bridge === "Nomic",
refetchInterval: 30000,
trpc: {
context: {
skipBatch: true,
},
},
}
);

const { hasCopied, onCopy } = useClipboard(
data?.depositData?.depositAddress ?? "",
3000
Expand Down Expand Up @@ -181,28 +201,13 @@ export const DepositAddressScreen = observer(
</div>
) : (
<div className="z-20 flex w-full items-center justify-between rounded-2xl bg-osmoverse-850 p-4">
<div className="flex items-center gap-2">
<Tooltip content={t("transfer.showQrCode")}>
<IconButton
icon={
<Icon
id="qr"
className="transition-color text-osmoverse-400 duration-200 group-hover:text-white-full"
/>
}
className="group flex h-12 w-12 items-center justify-center rounded-full bg-osmoverse-800 hover:!bg-osmoverse-700 active:!bg-osmoverse-800"
aria-label={t("transfer.showQrCode")}
onClick={() => setShowQrCode(true)}
disabled={isLoading || isExpired}
/>
</Tooltip>

<div className="flex flex-col">
<p className="subtitle1 text-white-full">
{t("transfer.yourDepositAddress", {
denom: canonicalAsset.coinDenom,
})}
</p>
<div className="flex flex-col">
<p className="subtitle1 text-white-full">
{t("transfer.yourDepositAddress", {
denom: canonicalAsset.coinDenom,
})}
</p>
{!isExpired && (
<SkeletonLoader
isLoaded={!!data?.depositData?.depositAddress}
className={
Expand All @@ -218,32 +223,49 @@ export const DepositAddressScreen = observer(
})}
</p>
</SkeletonLoader>
</div>
)}
</div>
<Tooltip
content={
hasCopied
? t("transfer.copied")
: t("transfer.copyAddressToClipboard")
}
>
<IconButton
icon={
<Icon
id={hasCopied ? "check-mark-slim" : "copy"}
className={
hasCopied
? "text-white-full"
: "transition-color text-osmoverse-400 duration-200 group-hover:text-white-full"
}
/>

<div className="flex items-center gap-2">
<Tooltip content={t("transfer.showQrCode")}>
<IconButton
icon={
<Icon
id="qr"
className="transition-color text-osmoverse-400 duration-200 group-hover:text-white-full"
/>
}
className="group flex h-12 w-12 items-center justify-center rounded-full bg-osmoverse-800 hover:!bg-osmoverse-700 active:!bg-osmoverse-800"
aria-label={t("transfer.showQrCode")}
onClick={() => setShowQrCode(true)}
disabled={isLoading || isExpired}
/>
</Tooltip>
<Tooltip
content={
hasCopied
? t("transfer.copied")
: t("transfer.copyAddressToClipboard")
}
className="group flex h-12 w-12 items-center justify-center rounded-full bg-osmoverse-800 hover:!bg-osmoverse-700 active:!bg-osmoverse-800"
aria-label={t("transfer.copyAddress")}
onClick={onCopy}
disabled={isLoading || isExpired}
/>
</Tooltip>
>
<IconButton
icon={
<Icon
id={hasCopied ? "check-mark-slim" : "copy"}
className={
hasCopied
? "text-white-full"
: "transition-color text-osmoverse-400 duration-200 group-hover:text-white-full"
}
/>
}
className="group flex h-12 w-12 items-center justify-center rounded-full bg-osmoverse-800 hover:!bg-osmoverse-700 active:!bg-osmoverse-800"
aria-label={t("transfer.copyAddress")}
onClick={onCopy}
disabled={isLoading || isExpired}
/>
</Tooltip>
</div>
</div>
)}
</div>
Expand All @@ -255,18 +277,33 @@ export const DepositAddressScreen = observer(
height={24}
className="flex-shrink-0 self-start text-rust-400"
/>
{isExpired ? (
<p className="body2 text-osmoverse-300">
{t("transfer.addressExpired")}
</p>
) : (
<p className="body2 text-osmoverse-200">
{t("transfer.receiveOnlyAsset", {
denom: canonicalAsset.coinDenom,
network: fromChain.prettyName,
})}
<div className="flex flex-col gap-6">
<p className="body2 text-wosmongton-300">
This address expires in{" "}
<SkeletonLoader
isLoaded={!isLoading}
className={isLoading ? "inline h-[20px] w-[50.7px]" : "inline"}
>
{!!expirationTimeDayjs ? (
<RemainingTime unix={expirationTimeDayjs.unix()} />
) : (
""
)}
</SkeletonLoader>
</p>
)}
{isExpired ? (
<p className="body2 text-osmoverse-300">
{t("transfer.addressExpired")}
</p>
) : (
<p className="body2 text-osmoverse-200">
{t("transfer.receiveOnlyAsset", {
denom: canonicalAsset.coinDenom,
network: fromChain.prettyName,
})}
</p>
)}
</div>
</div>

{isExpired ? (
Expand Down Expand Up @@ -309,25 +346,35 @@ export const DepositAddressScreen = observer(
</p>
</SkeletonLoader>
</DepositInfoRow>
<DepositInfoRow label={<span>{t("transfer.expiresIn")}</span>}>
<SkeletonLoader
isLoaded={!isLoading}
className={isLoading ? "h-5 w-24" : undefined}
>
{!!expirationTimeDayjs ? (
<RemainingTime unix={expirationTimeDayjs.unix()} />
) : (
""
)}
</SkeletonLoader>
</DepositInfoRow>
<TransferDetails
isLoading={isLoading}
depositData={data?.depositData}
fromChain={fromChain}
/>
</>
)}

<div className="mb-3 mt-6 h-[1px] w-full border border-osmoverse-800" />

<DepositInfoRow label={<span>Deposit status</span>}>
{isPendingDepositsLoading ? (
<Spinner />
) : (
<div className="flex items-center gap-2">
<BridgeQuoteRemainingTime
refetchInterval={30000}
dataUpdatedAt={nomicDepositsDataUpdatedAt}
/>
<p>Awaiting BTC</p>
{pendingDepositsData?.pendingDeposits?.map((deposit) => (
<div key={deposit.transactionId}>
<p>{deposit.amount}</p>
<p>{deposit.confirmations}</p>
</div>
))}
</div>
)}
</DepositInfoRow>
</div>
);
}
Expand Down Expand Up @@ -410,7 +457,9 @@ const TransferDetails: FunctionComponent<{
{!showTotalFeeIneqSymbol && "~"}{" "}
{totalFees
.inequalitySymbol(showTotalFeeIneqSymbol)
.toString()}{" "}
.toString()}
{" + "}
{depositData.providerFee.toString()}{" "}
{t("transfer.fees")}
</span>
)}
Expand Down Expand Up @@ -559,9 +608,9 @@ const RemainingTime = ({ unix }: { unix: number }) => {
if (!humanizedRemainingTime) return null;

return (
<p className="text-osmoverse-100">
<span className="text-inherit">
{humanizedRemainingTime?.value}{" "}
{t(humanizedRemainingTime?.unitTranslationKey)}
</p>
</span>
);
};
30 changes: 29 additions & 1 deletion packages/web/server/api/routers/bridge-transfer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ import {
getChain,
getTimeoutHeight,
} from "@osmosis-labs/server";
import { createTRPCRouter, publicProcedure } from "@osmosis-labs/trpc";
import {
createTRPCRouter,
publicProcedure,
UserOsmoAddressSchema,
} from "@osmosis-labs/trpc";
import { ExternalInterfaceBridgeTransferMethod } from "@osmosis-labs/types";
import {
BitcoinChainInfo,
Expand Down Expand Up @@ -705,4 +709,28 @@ export const bridgeTransferRouter = createTRPCRouter({
},
};
}),

getNomicPendingDeposits: publicProcedure
.input(UserOsmoAddressSchema.required())
.query(async ({ input, ctx }) => {
const bridgeProviders = new BridgeProviders(
process.env.NEXT_PUBLIC_SQUID_INTEGRATOR_ID!,
{
...ctx,
env: IS_TESTNET ? "testnet" : "mainnet",
cache: lruCache,
getTimeoutHeight: (params) => getTimeoutHeight({ ...ctx, ...params }),
}
);

const nomicBridgeProvider = bridgeProviders.bridges.Nomic;

const pendingDeposits = await nomicBridgeProvider.getPendingDeposits({
address: input.userOsmoAddress,
});

return {
pendingDeposits,
};
}),
});
Loading
Loading