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

SOV-4463: BOB Gateway example integration #1022

Open
wants to merge 11 commits into
base: develop
Choose a base branch
from
Open
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
3 changes: 3 additions & 0 deletions apps/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
"dependencies": {
"@apollo/client": "3.7.1",
"@apollo/react-hooks": "4.0.0",
"@gobob/bob-sdk": "^3.1.0",
"@gobob/sats-wagmi": "^0.3.17",
"@loadable/component": "5.15.2",
"@sovryn-zero/lib-base": "0.2.1",
"@sovryn-zero/lib-ethers": "0.2.5",
Expand All @@ -26,6 +28,7 @@
"@sovryn/tailwindcss-config": "*",
"@sovryn/ui": "*",
"@sovryn/utils": "0.0.2",
"@tanstack/react-query": "^5.59.0",
"@uniswap/permit2-sdk": "1.2.0",
"bitcoin-address-validation": "2.2.1",
"chart.js": "4.1.1",
Expand Down
50 changes: 50 additions & 0 deletions apps/frontend/src/app/5_pages/BobGateway/BobGateway.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { Network, SatsWagmiConfig } from '@gobob/sats-wagmi';
import { QueryClientProvider } from '@tanstack/react-query';

import React, { FC } from 'react';

import { t } from 'i18next';
import { Helmet } from 'react-helmet-async';

import { Heading, Paragraph, ParagraphSize } from '@sovryn/ui';

import { translations } from '../../../locales/i18n';
import { Promotions } from '../MarketMakingPage/components/Promotions/Promotions';
import { queryClient } from './BobGateway.utils';
import { BitcoinWallet } from './components/BitcoinWallet/BitcoinWallet';
import { BobGatewayForm } from './components/BobGatewayForm/BobGatewayForm';

const BobGateway: FC = () => {
return (
<>
<Helmet>
<title>{t(translations.bobGatewayPage.meta.title)}</title>
</Helmet>
<div className="px-0 container md:mx-9 mx-0 md:mb-2 mb-7 mt-8">
<Heading className="text-center mb-3 lg:text-2xl">
{t(translations.bobGatewayPage.meta.title)}
</Heading>

<Paragraph
className="text-center mb-6 lg:mb-10"
size={ParagraphSize.base}
>
{t(translations.bobGatewayPage.description)}
</Paragraph>

<QueryClientProvider client={queryClient}>
<SatsWagmiConfig network={Network.mainnet} queryClient={queryClient}>
<div className="px-0 md:mx-9 mx-0 md:mb-2 mb-7">
<div className="flex justify-end m-4">
<BitcoinWallet />
</div>
<Promotions setActivePool={() => {}} onClick={() => {}} />
<BobGatewayForm />
</div>
</SatsWagmiConfig>
</QueryClientProvider>
</div>
</>
);
};
export default BobGateway;
19 changes: 19 additions & 0 deletions apps/frontend/src/app/5_pages/BobGateway/BobGateway.utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { GatewayQuoteParams, GatewaySDK } from '@gobob/bob-sdk';
import { QueryClient } from '@tanstack/react-query';

export const bobGateway = new GatewaySDK('bob');

export const strategies: GatewayQuoteParams[] = [
{
fromToken: 'BTC',
fromChain: 'Bitcoin',
fromUserAddress: 'bc1qafk4yhqvj4wep57m62dgrmutldusqde8adh20d',
toChain: 'BOB',
toUserAddress: '0x2D2E86236a5bC1c8a5e5499C517E17Fb88Dbc18c',
toToken: 'tBTC', // or e.g. "SolvBTC"
amount: 10000000, // 0.1 BTC
gasRefill: 10000, // 0.0001 BTC. The amount of BTC to swap for ETH for tx fees.
},
];

export const queryClient = new QueryClient();
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { useAccount, useConnect, useDisconnect } from '@gobob/sats-wagmi';

import React, { FC, useCallback, useEffect, useState } from 'react';

import { t } from 'i18next';
import { nanoid } from 'nanoid';

import {
Button,
Dialog,
DialogBody,
DialogHeader,
NotificationType,
WalletIdentity,
} from '@sovryn/ui';

import { useNotificationContext } from '../../../../../contexts/NotificationContext';
import { translations } from '../../../../../locales/i18n';

export const BitcoinWallet: FC = () => {
const { addNotification } = useNotificationContext();
const { connectors, connect } = useConnect();
const { address: btcAddress } = useAccount();
const { disconnect } = useDisconnect();
const [isOpen, setIsOpen] = useState(false);

const onCopyAddress = useCallback(() => {
addNotification({
type: NotificationType.success,
title: t(translations.copyAddress),
content: '',
dismissible: true,
id: nanoid(),
});
}, [addNotification]);

useEffect(() => {
if (btcAddress && isOpen) {
setIsOpen(false);
}
}, [btcAddress, isOpen]);

if (!btcAddress) {
return (
<>
<Button
text={t(translations.connectWalletButton.connectBTC)}
onClick={() => setIsOpen(true)}
/>
<Dialog isOpen={isOpen} onClose={() => setIsOpen(false)}>
<DialogHeader
onClose={() => setIsOpen(false)}
title={t(translations.connectWalletButton.connectBTC)}
/>
<DialogBody className="p-6">
<div className="flex gap-2 my-4 flex-wrap">
{connectors.map(connector => (
<Button
key={connector.name}
text={connector.name}
onClick={() => connect({ connector })}
/>
))}
</div>
</DialogBody>
</Dialog>
</>
);
}
return (
<WalletIdentity
onDisconnect={disconnect}
onCopyAddress={onCopyAddress}
address={btcAddress}
dropdownClassName="z-[10000000]"
submenuLabels={{
copyAddress: t(translations.connectWalletButton.copyAddress),
disconnect: t(translations.connectWalletButton.disconnect),
}}
/>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { Token } from '@gobob/bob-sdk/dist/gateway/types';
import { useAccount, useBalance } from '@gobob/sats-wagmi';

import React, { FC, useEffect, useState } from 'react';

import { formatUnits, parseUnits } from 'ethers/lib/utils';

import { Button, Input, Paragraph, Select } from '@sovryn/ui';

import { BOB_CHAIN_ID } from '../../../../../config/chains';

import { useCacheCall } from '../../../../../hooks';
import { useAccount as useEvmAccount } from '../../../../../hooks/useAccount';
import { bobGateway } from '../../BobGateway.utils';
import { useSendGatewayTransaction } from '../../hooks/useSendGatewayTransaction';

export const BobGatewayDeposit: FC = () => {
const [amount, setAmount] = useState('');
const [token, setToken] = useState<string>('');
const [tokens, setTokens] = useState<Token[]>([]);
const { address: btcAddress } = useAccount();
const { account } = useEvmAccount();
const { data } = useBalance();
const {
data: hash,
error,
isPending,
sendGatewayTransaction,
} = useSendGatewayTransaction({
toChain: 'bob',
});

useEffect(() => {
if (!tokens.length) {
bobGateway.getTokens().then(list => {
console.log(list);
setTokens(list);
});
}
}, [tokens.length]);

const onSbumit = async () => {
const toToken = tokens.find(t => t.address === token);

if (!account || !amount || !toToken) {
return;
}

const params = {
toToken: 'wBTC',
evmAddress: account,
value: BigInt(parseUnits(amount, 8).toString()),
strategyAddress: '0xBA67A0a0C2dd790182D1954B4C9788f9Ae43e604',
};

console.log(params);

sendGatewayTransaction(params, {
onError: error => console.log({ error }),
});
};

const { value: orders } = useCacheCall(
`bob-orders/${account}`,
BOB_CHAIN_ID,
async () => {
const result = await bobGateway.getOrders(account);
return result;
},
[account],
[],
);

useEffect(() => {
console.log({
error,
hash,
isPending,
});
}, [error, hash, isPending]);

useEffect(() => {
if (orders.length) {
console.log({
orders,
});
}
}, [orders]);

return (
<div>
<Paragraph>BTC Wallet: {btcAddress}</Paragraph>
Amount: (Balance: {formatUnits(data?.total.toString() || '0', 8)} BTC)
<Input
placeholder="Amount (BTC)"
step="0.00000001"
value={amount}
onChangeText={setAmount}
className="mb-4"
type="number"
/>
<br />
<Select
value={token}
onChange={setToken}
options={tokens.map(token => ({
value: token.address,
label: (
<div className="flex items-center gap-2">
<img
className="w-8 h-8 rounded-full"
src={token.logoURI}
alt={token.symbol}
/>{' '}
{token.name} ({token.symbol})
</div>
),
}))}
className="min-w-36 w-full lg:w-auto"
/>
<Button loading={isPending} text="submit" onClick={onSbumit} />
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React, { FC, useMemo, useState } from 'react';

import { t } from 'i18next';

import { Tabs } from '@sovryn/ui';

import { translations } from '../../../../../locales/i18n';
import { BobGatewayDeposit } from '../BobGatewayDeposit/BobGatewayDeposit';

export const BobGatewayForm: FC = () => {
const [index, setIndex] = useState(0);

const items = useMemo(
() => [
{
label: t(translations.bobGatewayPage.deposit),
content: <BobGatewayDeposit />,
dataAttribute: 'bob-gateway-deposit',
},
{
label: t(translations.bobGatewayPage.deposit),
content: <BobGatewayDeposit />,
dataAttribute: 'bob-gateway-deposit',
},
],
[],
);

return (
<div className="flex py-8 justify-center gap-6">
<div className="p-0 sm:border sm:border-gray-50 sm:rounded lg:min-w-[28rem] sm:p-6 sm:bg-gray-90">
<Tabs
items={items}
onChange={setIndex}
index={index}
className="w-full border-none"
contentClassName="border-none"
/>
</div>
</div>
);
};
Loading
Loading