mirror of https://github.com/certusone/oyster.git
feat: bridge
This commit is contained in:
parent
f634558f9c
commit
752f342781
|
@ -0,0 +1,117 @@
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { contexts, utils, ParsedAccount, NumericInput, TokenIcon, TokenDisplay } from '@oyster/common';
|
||||||
|
import { Card, Select } from 'antd';
|
||||||
|
import './style.less';
|
||||||
|
const { getTokenName } = utils;
|
||||||
|
const { cache } = contexts.Accounts;
|
||||||
|
const { useConnectionConfig } = contexts.Connection;
|
||||||
|
|
||||||
|
const { Option } = Select;
|
||||||
|
|
||||||
|
// User can choose a collateral they want to use, and then this will display the balance they have in Oyster's lending
|
||||||
|
// reserve for that collateral type.
|
||||||
|
export function EthereumInput(props: {
|
||||||
|
title: string;
|
||||||
|
amount?: number | null;
|
||||||
|
disabled?: boolean;
|
||||||
|
onInputChange: (value: number | null) => void;
|
||||||
|
hideBalance?: boolean;
|
||||||
|
useWalletBalance?: boolean;
|
||||||
|
useFirstReserve?: boolean;
|
||||||
|
showLeverageSelector?: boolean;
|
||||||
|
leverage?: number;
|
||||||
|
}) {
|
||||||
|
const { tokenMap } = useConnectionConfig();
|
||||||
|
const [acco, setCollateralReserve] = useState<string>();
|
||||||
|
const [balance, setBalance] = useState<number>(0);
|
||||||
|
const [lastAmount, setLastAmount] = useState<string>('');
|
||||||
|
|
||||||
|
|
||||||
|
const renderReserveAccounts = [].map((reserve: any) => {
|
||||||
|
const mint = reserve.info.liquidityMint.toBase58();
|
||||||
|
const address = reserve.pubkey.toBase58();
|
||||||
|
const name = getTokenName(tokenMap, mint);
|
||||||
|
return (
|
||||||
|
<Option key={address} value={address} name={name} title={address}>
|
||||||
|
<div key={address} style={{ display: 'flex', alignItems: 'center' }}>
|
||||||
|
<TokenIcon mintAddress={mint} />
|
||||||
|
{name}
|
||||||
|
</div>
|
||||||
|
</Option>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
className="ccy-input"
|
||||||
|
style={{ borderRadius: 20 }}
|
||||||
|
bodyStyle={{ padding: 0 }}
|
||||||
|
>
|
||||||
|
<div className="ccy-input-header">
|
||||||
|
<div className="ccy-input-header-left">{props.title}</div>
|
||||||
|
|
||||||
|
{!props.hideBalance && (
|
||||||
|
<div
|
||||||
|
className="ccy-input-header-right"
|
||||||
|
onClick={e => props.onInputChange && props.onInputChange(balance)}
|
||||||
|
>
|
||||||
|
Balance: {balance.toFixed(6)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="ccy-input-header" style={{ padding: '0px 10px 5px 7px' }}>
|
||||||
|
<NumericInput
|
||||||
|
value={
|
||||||
|
parseFloat(lastAmount || '0.00') === props.amount
|
||||||
|
? lastAmount
|
||||||
|
: props.amount?.toFixed(6)?.toString()
|
||||||
|
}
|
||||||
|
onChange={(val: string) => {
|
||||||
|
if (props.onInputChange && parseFloat(val) !== props.amount) {
|
||||||
|
if (!val || !parseFloat(val)) props.onInputChange(null);
|
||||||
|
else props.onInputChange(parseFloat(val));
|
||||||
|
}
|
||||||
|
setLastAmount(val);
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
fontSize: 20,
|
||||||
|
boxShadow: 'none',
|
||||||
|
borderColor: 'transparent',
|
||||||
|
outline: 'transparent',
|
||||||
|
}}
|
||||||
|
placeholder="0.00"
|
||||||
|
/>
|
||||||
|
<div className="ccy-input-header-right" style={{ display: 'flex' }}>
|
||||||
|
{!props.disabled ? (
|
||||||
|
<Select
|
||||||
|
size="large"
|
||||||
|
showSearch
|
||||||
|
style={{ minWidth: 150 }}
|
||||||
|
placeholder="CCY"
|
||||||
|
// value={collateralReserve}
|
||||||
|
// onChange={item => {
|
||||||
|
// if (props.onCollateralReserve) props.onCollateralReserve(item);
|
||||||
|
// setCollateralReserve(item);
|
||||||
|
// }}
|
||||||
|
filterOption={(input, option) =>
|
||||||
|
option?.name?.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{renderReserveAccounts}
|
||||||
|
</Select>
|
||||||
|
) : (
|
||||||
|
<TokenDisplay
|
||||||
|
// key={props.reserve.liquidityMint.toBase58()}
|
||||||
|
name={getTokenName(
|
||||||
|
tokenMap,
|
||||||
|
'',
|
||||||
|
)}
|
||||||
|
mintAddress={''}
|
||||||
|
showBalance={false}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * from './solana';
|
||||||
|
export * from './ethereum';
|
|
@ -0,0 +1,117 @@
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { contexts, utils, ParsedAccount, NumericInput, TokenIcon, TokenDisplay } from '@oyster/common';
|
||||||
|
import { Card, Select } from 'antd';
|
||||||
|
import './style.less';
|
||||||
|
const { getTokenName } = utils;
|
||||||
|
const { cache } = contexts.Accounts;
|
||||||
|
const { useConnectionConfig } = contexts.Connection;
|
||||||
|
|
||||||
|
const { Option } = Select;
|
||||||
|
|
||||||
|
// User can choose a collateral they want to use, and then this will display the balance they have in Oyster's lending
|
||||||
|
// reserve for that collateral type.
|
||||||
|
export function SolanaInput(props: {
|
||||||
|
title: string;
|
||||||
|
amount?: number | null;
|
||||||
|
disabled?: boolean;
|
||||||
|
onInputChange: (value: number | null) => void;
|
||||||
|
hideBalance?: boolean;
|
||||||
|
useWalletBalance?: boolean;
|
||||||
|
useFirstReserve?: boolean;
|
||||||
|
showLeverageSelector?: boolean;
|
||||||
|
leverage?: number;
|
||||||
|
}) {
|
||||||
|
const { tokenMap } = useConnectionConfig();
|
||||||
|
const [acco, setCollateralReserve] = useState<string>();
|
||||||
|
const [balance, setBalance] = useState<number>(0);
|
||||||
|
const [lastAmount, setLastAmount] = useState<string>('');
|
||||||
|
|
||||||
|
|
||||||
|
const renderReserveAccounts = [].map((reserve: any) => {
|
||||||
|
const mint = reserve.info.liquidityMint.toBase58();
|
||||||
|
const address = reserve.pubkey.toBase58();
|
||||||
|
const name = getTokenName(tokenMap, mint);
|
||||||
|
return (
|
||||||
|
<Option key={address} value={address} name={name} title={address}>
|
||||||
|
<div key={address} style={{ display: 'flex', alignItems: 'center' }}>
|
||||||
|
<TokenIcon mintAddress={mint} />
|
||||||
|
{name}
|
||||||
|
</div>
|
||||||
|
</Option>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
className="ccy-input"
|
||||||
|
style={{ borderRadius: 20 }}
|
||||||
|
bodyStyle={{ padding: 0 }}
|
||||||
|
>
|
||||||
|
<div className="ccy-input-header">
|
||||||
|
<div className="ccy-input-header-left">{props.title}</div>
|
||||||
|
|
||||||
|
{!props.hideBalance && (
|
||||||
|
<div
|
||||||
|
className="ccy-input-header-right"
|
||||||
|
onClick={e => props.onInputChange && props.onInputChange(balance)}
|
||||||
|
>
|
||||||
|
Balance: {balance.toFixed(6)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="ccy-input-header" style={{ padding: '0px 10px 5px 7px' }}>
|
||||||
|
<NumericInput
|
||||||
|
value={
|
||||||
|
parseFloat(lastAmount || '0.00') === props.amount
|
||||||
|
? lastAmount
|
||||||
|
: props.amount?.toFixed(6)?.toString()
|
||||||
|
}
|
||||||
|
onChange={(val: string) => {
|
||||||
|
if (props.onInputChange && parseFloat(val) !== props.amount) {
|
||||||
|
if (!val || !parseFloat(val)) props.onInputChange(null);
|
||||||
|
else props.onInputChange(parseFloat(val));
|
||||||
|
}
|
||||||
|
setLastAmount(val);
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
fontSize: 20,
|
||||||
|
boxShadow: 'none',
|
||||||
|
borderColor: 'transparent',
|
||||||
|
outline: 'transparent',
|
||||||
|
}}
|
||||||
|
placeholder="0.00"
|
||||||
|
/>
|
||||||
|
<div className="ccy-input-header-right" style={{ display: 'flex' }}>
|
||||||
|
{!props.disabled ? (
|
||||||
|
<Select
|
||||||
|
size="large"
|
||||||
|
showSearch
|
||||||
|
style={{ minWidth: 150 }}
|
||||||
|
placeholder="CCY"
|
||||||
|
// value={collateralReserve}
|
||||||
|
// onChange={item => {
|
||||||
|
// if (props.onCollateralReserve) props.onCollateralReserve(item);
|
||||||
|
// setCollateralReserve(item);
|
||||||
|
// }}
|
||||||
|
filterOption={(input, option) =>
|
||||||
|
option?.name?.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{renderReserveAccounts}
|
||||||
|
</Select>
|
||||||
|
) : (
|
||||||
|
<TokenDisplay
|
||||||
|
// key={props.reserve.liquidityMint.toBase58()}
|
||||||
|
name={getTokenName(
|
||||||
|
tokenMap,
|
||||||
|
'',
|
||||||
|
)}
|
||||||
|
mintAddress={''}
|
||||||
|
showBalance={false}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
.ccy-input {
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
|
||||||
|
.ant-select-selector,
|
||||||
|
.ant-select-selector:focus,
|
||||||
|
.ant-select-selector:active {
|
||||||
|
border-color: transparent !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
.ant-select-selection-item {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
.token-balance {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.token-balance {
|
||||||
|
color: grey;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ccy-input-header {
|
||||||
|
display: grid;
|
||||||
|
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
grid-column-gap: 10px;
|
||||||
|
|
||||||
|
-webkit-box-pack: justify;
|
||||||
|
justify-content: space-between;
|
||||||
|
-webkit-box-align: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: row;
|
||||||
|
padding: 10px 20px 0px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ccy-input-header-left {
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0px;
|
||||||
|
min-width: 0px;
|
||||||
|
display: flex;
|
||||||
|
padding: 0px;
|
||||||
|
-webkit-box-align: center;
|
||||||
|
align-items: center;
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ccy-input-header-right {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
-webkit-box-align: center;
|
||||||
|
align-items: center;
|
||||||
|
justify-self: flex-end;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-select-dropdown {
|
||||||
|
width: 150px !important;
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ import { LAMPORTS_PER_SOL } from '@solana/web3.js';
|
||||||
import { LABELS } from '../../constants';
|
import { LABELS } from '../../constants';
|
||||||
import { contexts, utils, ConnectButton } from '@oyster/common';
|
import { contexts, utils, ConnectButton } from '@oyster/common';
|
||||||
import { useHistory, useLocation } from "react-router-dom";
|
import { useHistory, useLocation } from "react-router-dom";
|
||||||
|
import { SolanaInput, EthereumInput } from "./../Input";
|
||||||
|
|
||||||
import './style.less';
|
import './style.less';
|
||||||
|
|
||||||
|
@ -15,46 +16,21 @@ export const Transfer = () => {
|
||||||
const connection = useConnection();
|
const connection = useConnection();
|
||||||
const { wallet } = useWallet();
|
const { wallet } = useWallet();
|
||||||
|
|
||||||
const tabStyle: React.CSSProperties = { width: 120 };
|
|
||||||
const tabList = [
|
|
||||||
{
|
|
||||||
key: "eth",
|
|
||||||
tab: <div style={tabStyle}>Transfer</div>,
|
|
||||||
render: () => {
|
|
||||||
return <div>Bring assets to Solana</div>;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "sol",
|
|
||||||
tab: <div style={tabStyle}>Wrap</div>,
|
|
||||||
render: () => {
|
|
||||||
return <div>Bring assets to Solana</div>;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const location = useLocation();
|
|
||||||
const history = useHistory();
|
|
||||||
const activeTab = location.pathname.indexOf("eth") < 0 ? "sol" : "eth";
|
|
||||||
|
|
||||||
const handleTabChange = (key: any) => {
|
|
||||||
if (activeTab !== key) {
|
|
||||||
if (key === "sol") {
|
|
||||||
history.push("/move/sol");
|
|
||||||
} else {
|
|
||||||
history.push("/move/eth");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="exchange-card">
|
<div className="exchange-card">
|
||||||
INPUT
|
<EthereumInput
|
||||||
|
title="From Ethereum"
|
||||||
|
onInputChange={() => {}}
|
||||||
|
/>
|
||||||
<Button type="primary" className="swap-button">
|
<Button type="primary" className="swap-button">
|
||||||
⇅
|
⇅
|
||||||
</Button>
|
</Button>
|
||||||
OUTPUT
|
<SolanaInput
|
||||||
|
title="To Solana"
|
||||||
|
onInputChange={() => {}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<ConnectButton type="primary">
|
<ConnectButton type="primary">
|
||||||
Transfer
|
Transfer
|
||||||
|
|
|
@ -0,0 +1,259 @@
|
||||||
|
import React, {
|
||||||
|
useCallback,
|
||||||
|
useContext,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
|
import { MintInfo } from "@solana/spl-token";
|
||||||
|
import { useHistory, useLocation } from "react-router-dom";
|
||||||
|
import bs58 from "bs58";
|
||||||
|
import { KnownToken, TokenAccount } from "@oyster/common";
|
||||||
|
import { useConnection, useConnectionConfig, useAccountByMint, useMint, getTokenName, getTokenIcon, convert } from "@oyster/common";
|
||||||
|
|
||||||
|
export interface TokenContextState {
|
||||||
|
mintAddress: string;
|
||||||
|
account?: TokenAccount;
|
||||||
|
mint?: MintInfo;
|
||||||
|
amount: string;
|
||||||
|
name: string;
|
||||||
|
icon?: string;
|
||||||
|
setAmount: (val: string) => void;
|
||||||
|
setMint: (mintAddress: string) => void;
|
||||||
|
convertAmount: () => number;
|
||||||
|
sufficientBalance: () => boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TokenPairContextState {
|
||||||
|
A: TokenContextState;
|
||||||
|
B: TokenContextState;
|
||||||
|
lastTypedAccount: string;
|
||||||
|
setLastTypedAccount: (mintAddress: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TokenPairContext = React.createContext<TokenPairContextState | null>(
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
const convertAmount = (amount: string, mint?: MintInfo) => {
|
||||||
|
return parseFloat(amount) * Math.pow(10, mint?.decimals || 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useCurrencyLeg = (defaultMint?: string) => {
|
||||||
|
const { tokenMap } = useConnectionConfig();
|
||||||
|
const [amount, setAmount] = useState("");
|
||||||
|
const [mintAddress, setMintAddress] = useState(defaultMint || "");
|
||||||
|
const account = useAccountByMint(mintAddress);
|
||||||
|
const mint = useMint(mintAddress);
|
||||||
|
|
||||||
|
return useMemo(
|
||||||
|
() => ({
|
||||||
|
mintAddress: mintAddress,
|
||||||
|
account: account,
|
||||||
|
mint: mint,
|
||||||
|
amount: amount,
|
||||||
|
name: getTokenName(tokenMap, mintAddress),
|
||||||
|
icon: getTokenIcon(tokenMap, mintAddress),
|
||||||
|
setAmount: setAmount,
|
||||||
|
setMint: setMintAddress,
|
||||||
|
convertAmount: () => convertAmount(amount, mint),
|
||||||
|
sufficientBalance: () =>
|
||||||
|
account !== undefined &&
|
||||||
|
(convert(account, mint) >= parseFloat(amount))
|
||||||
|
}),
|
||||||
|
[
|
||||||
|
mintAddress,
|
||||||
|
account,
|
||||||
|
mint,
|
||||||
|
amount,
|
||||||
|
tokenMap,
|
||||||
|
setAmount,
|
||||||
|
setMintAddress,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export function CurrencyPairProvider({ children = null as any }) {
|
||||||
|
const connection = useConnection();
|
||||||
|
const { tokens } = useConnectionConfig();
|
||||||
|
|
||||||
|
const history = useHistory();
|
||||||
|
const location = useLocation();
|
||||||
|
const [lastTypedAccount, setLastTypedAccount] = useState("");
|
||||||
|
|
||||||
|
const base = useCurrencyLeg();
|
||||||
|
const mintAddressA = base.mintAddress;
|
||||||
|
const setMintAddressA = base.setMint;
|
||||||
|
const amountA = base.amount;
|
||||||
|
const setAmountA = base.setAmount;
|
||||||
|
|
||||||
|
const quote = useCurrencyLeg();
|
||||||
|
const mintAddressB = quote.mintAddress;
|
||||||
|
const setMintAddressB = quote.setMint;
|
||||||
|
const amountB = quote.amount;
|
||||||
|
const setAmountB = quote.setAmount;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const base =
|
||||||
|
tokens.find((t) => t.mintAddress === mintAddressA)?.tokenSymbol ||
|
||||||
|
mintAddressA;
|
||||||
|
const quote =
|
||||||
|
tokens.find((t) => t.mintAddress === mintAddressB)?.tokenSymbol ||
|
||||||
|
mintAddressB;
|
||||||
|
|
||||||
|
document.title = `Swap | Serum (${base}/${quote})`;
|
||||||
|
}, [mintAddressA, mintAddressB, tokens, location]);
|
||||||
|
|
||||||
|
// updates browser history on token changes
|
||||||
|
useEffect(() => {
|
||||||
|
// set history
|
||||||
|
const base =
|
||||||
|
tokens.find((t) => t.mintAddress === mintAddressA)?.tokenSymbol ||
|
||||||
|
mintAddressA;
|
||||||
|
const quote =
|
||||||
|
tokens.find((t) => t.mintAddress === mintAddressB)?.tokenSymbol ||
|
||||||
|
mintAddressB;
|
||||||
|
|
||||||
|
if (base && quote && location.pathname.indexOf("info") < 0) {
|
||||||
|
history.push({
|
||||||
|
search: `?pair=${base}-${quote}`,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (mintAddressA && mintAddressB) {
|
||||||
|
history.push({
|
||||||
|
search: ``,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [mintAddressA, mintAddressB, tokens, history, location.pathname]);
|
||||||
|
|
||||||
|
// Updates tokens on location change
|
||||||
|
useEffect(() => {
|
||||||
|
if (!location.search && mintAddressA && mintAddressB) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { defaultBase, defaultQuote } = getDefaultTokens(
|
||||||
|
tokens,
|
||||||
|
location.search
|
||||||
|
);
|
||||||
|
if (!defaultBase || !defaultQuote) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setMintAddressA(
|
||||||
|
tokens.find((t) => t.tokenSymbol === defaultBase)?.mintAddress ||
|
||||||
|
(isValidAddress(defaultBase) ? defaultBase : "") ||
|
||||||
|
""
|
||||||
|
);
|
||||||
|
setMintAddressB(
|
||||||
|
tokens.find((t) => t.tokenSymbol === defaultQuote)?.mintAddress ||
|
||||||
|
(isValidAddress(defaultQuote) ? defaultQuote : "") ||
|
||||||
|
""
|
||||||
|
);
|
||||||
|
// mintAddressA and mintAddressB are not included here to prevent infinite loop
|
||||||
|
// eslint-disable-next-line
|
||||||
|
}, [location, location.search, setMintAddressA, setMintAddressB, tokens]);
|
||||||
|
|
||||||
|
const calculateDependent = useCallback(async () => {
|
||||||
|
if (mintAddressA && mintAddressB) {
|
||||||
|
let setDependent;
|
||||||
|
let amount;
|
||||||
|
let independent;
|
||||||
|
if (lastTypedAccount === mintAddressA) {
|
||||||
|
independent = mintAddressA;
|
||||||
|
setDependent = setAmountB;
|
||||||
|
amount = parseFloat(amountA);
|
||||||
|
} else {
|
||||||
|
independent = mintAddressB;
|
||||||
|
setDependent = setAmountA;
|
||||||
|
amount = parseFloat(amountB);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: calculate
|
||||||
|
const result: number | string = 0;
|
||||||
|
if (typeof result === "string") {
|
||||||
|
setDependent(result);
|
||||||
|
} else if (result !== undefined && Number.isFinite(result)) {
|
||||||
|
setDependent(result.toFixed(6));
|
||||||
|
} else {
|
||||||
|
setDependent("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
mintAddressA,
|
||||||
|
mintAddressB,
|
||||||
|
setAmountA,
|
||||||
|
setAmountB,
|
||||||
|
amountA,
|
||||||
|
amountB,
|
||||||
|
connection,
|
||||||
|
lastTypedAccount,
|
||||||
|
]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
calculateDependent();
|
||||||
|
}, [amountB, amountA, lastTypedAccount, calculateDependent]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TokenPairContext.Provider
|
||||||
|
value={{
|
||||||
|
A: base,
|
||||||
|
B: quote,
|
||||||
|
lastTypedAccount,
|
||||||
|
setLastTypedAccount,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</TokenPairContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useCurrencyPairState = () => {
|
||||||
|
const context = useContext(TokenPairContext);
|
||||||
|
|
||||||
|
return context as TokenPairContextState;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isValidAddress = (address: string) => {
|
||||||
|
try {
|
||||||
|
const decoded = bs58.decode(address);
|
||||||
|
return decoded.length === 32;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function getDefaultTokens(tokens: KnownToken[], search: string) {
|
||||||
|
let defaultBase = "SOL";
|
||||||
|
let defaultQuote = "USDC";
|
||||||
|
|
||||||
|
const nameToToken = tokens.reduce((map, item) => {
|
||||||
|
map.set(item.tokenSymbol, item);
|
||||||
|
return map;
|
||||||
|
}, new Map<string, any>());
|
||||||
|
|
||||||
|
if (search) {
|
||||||
|
const urlParams = new URLSearchParams(search);
|
||||||
|
const pair = urlParams.get("pair");
|
||||||
|
if (pair) {
|
||||||
|
let items = pair.split("-");
|
||||||
|
|
||||||
|
if (items.length > 1) {
|
||||||
|
if (nameToToken.has(items[0]) || isValidAddress(items[0])) {
|
||||||
|
defaultBase = items[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nameToToken.has(items[1]) || isValidAddress(items[1])) {
|
||||||
|
defaultQuote = items[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
defaultBase,
|
||||||
|
defaultQuote,
|
||||||
|
};
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ import { LAMPORTS_PER_SOL } from '@solana/web3.js';
|
||||||
import { LABELS } from '../../constants';
|
import { LABELS } from '../../constants';
|
||||||
import { contexts, utils, ConnectButton } from '@oyster/common';
|
import { contexts, utils, ConnectButton } from '@oyster/common';
|
||||||
import { useHistory, useLocation } from "react-router-dom";
|
import { useHistory, useLocation } from "react-router-dom";
|
||||||
|
import { Transfer } from '../../components/Transfer';
|
||||||
const { useConnection } = contexts.Connection;
|
const { useConnection } = contexts.Connection;
|
||||||
const { useWallet } = contexts.Wallet;
|
const { useWallet } = contexts.Wallet;
|
||||||
const { notify } = utils;
|
const { notify } = utils;
|
||||||
|
@ -18,7 +19,7 @@ export const TransferView = () => {
|
||||||
key: "eth",
|
key: "eth",
|
||||||
tab: <div style={tabStyle}>Transfer</div>,
|
tab: <div style={tabStyle}>Transfer</div>,
|
||||||
render: () => {
|
render: () => {
|
||||||
return <div>Bring assets to Solana</div>;
|
return <Transfer />;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -32,7 +33,7 @@ export const TransferView = () => {
|
||||||
|
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const activeTab = location.pathname.indexOf("eth") < 0 ? "sol" : "eth";
|
const activeTab = location.pathname.indexOf("sol") >= 0 ? "sol" : "eth";
|
||||||
|
|
||||||
const handleTabChange = (key: any) => {
|
const handleTabChange = (key: any) => {
|
||||||
if (activeTab !== key) {
|
if (activeTab !== key) {
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
"main": "dist/lib/index.js",
|
"main": "dist/lib/index.js",
|
||||||
"types": "dist/lib/index.d.ts",
|
"types": "dist/lib/index.d.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./dist/lib/index.js"
|
".": "./dist/lib/"
|
||||||
},
|
},
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
|
|
|
@ -2,3 +2,7 @@ export * as Accounts from './accounts';
|
||||||
export * as Connection from './connection';
|
export * as Connection from './connection';
|
||||||
export * as Wallet from './wallet';
|
export * as Wallet from './wallet';
|
||||||
export { ParsedAccount, ParsedAccountBase } from './accounts';
|
export { ParsedAccount, ParsedAccountBase } from './accounts';
|
||||||
|
|
||||||
|
export * from './accounts';
|
||||||
|
export * from './wallet';
|
||||||
|
export * from './connection';
|
||||||
|
|
|
@ -4,9 +4,12 @@ export * from './components'; // Allow direct exports too
|
||||||
export * as config from './config';
|
export * as config from './config';
|
||||||
export * as constants from './constants';
|
export * as constants from './constants';
|
||||||
export * as hooks from './hooks';
|
export * as hooks from './hooks';
|
||||||
|
export * from './hooks';
|
||||||
export * as contexts from './contexts';
|
export * as contexts from './contexts';
|
||||||
|
export * from './contexts';
|
||||||
export * as models from './models';
|
export * as models from './models';
|
||||||
export * as utils from './utils';
|
export * as utils from './utils';
|
||||||
|
export * from './utils';
|
||||||
export * as walletAdapters from './wallet-adapters';
|
export * as walletAdapters from './wallet-adapters';
|
||||||
|
|
||||||
export { TokenAccount } from './models';
|
export { TokenAccount } from './models';
|
||||||
|
|
Loading…
Reference in New Issue