feat: add liquidity chart

This commit is contained in:
bartosz-lipinski 2020-11-24 21:14:28 -06:00
parent 31020e7e86
commit f9adc64ca9
20 changed files with 258 additions and 112 deletions

View File

@ -20,4 +20,5 @@ Any content produced by Solana, or developer resources that Solana provides, are
- [] Add market size on front page - [] Add market size on front page
- [] Add github link - [] Add github link
- [] Repay from reserve (add selection for obligation/loan) - [] Repay from reserve (add selection for obligation/loan)
- [] - [] Add support for token names in URL in addition to reserve address

View File

@ -76,8 +76,8 @@ export const repay = async (
signers signers
); );
const loanRatio = amountLamports / wadToLamports(obligation.info.borrowAmountWad) const loanRatio =
.toNumber(); amountLamports / wadToLamports(obligation.info.borrowAmountWad).toNumber();
console.log(loanRatio); console.log(loanRatio);
// create approval for transfer transactions // create approval for transfer transactions

View File

@ -84,9 +84,7 @@ export const BorrowInput = (props: {
justifyContent: "space-around", justifyContent: "space-around",
}} }}
> >
<div className="borrow-input-title"> <div className="borrow-input-title">{LABELS.BORROW_QUESTION}</div>
{LABELS.BORROW_QUESTION}
</div>
<div className="token-input"> <div className="token-input">
<TokenIcon mintAddress={borrowReserve?.liquidityMint} /> <TokenIcon mintAddress={borrowReserve?.liquidityMint} />
<NumericInput <NumericInput

View File

@ -1,13 +1,17 @@
import { Button } from "antd" import { Button } from "antd";
import { ButtonProps } from "antd/lib/button" import { ButtonProps } from "antd/lib/button";
import React from "react" import React from "react";
import { useWallet } from "../../contexts/wallet"; import { useWallet } from "../../contexts/wallet";
import { LABELS } from './../../constants'; import { LABELS } from "./../../constants";
export const ConnectButton = (props: ButtonProps & React.RefAttributes<HTMLElement>) => { export const ConnectButton = (
props: ButtonProps & React.RefAttributes<HTMLElement>
) => {
const { wallet, connected } = useWallet(); const { wallet, connected } = useWallet();
const { onClick, children, ...rest } = props; const { onClick, children, ...rest } = props;
return <Button {...rest} onClick={connected ? onClick : wallet.connect} > return (
<Button {...rest} onClick={connected ? onClick : wallet.connect}>
{connected ? props.children : LABELS.CONNECT_LABEL} {connected ? props.children : LABELS.CONNECT_LABEL}
</Button> </Button>
} );
};

View File

@ -36,9 +36,7 @@ export const AppLayout = (props: any) => {
return ( return (
<div className="App"> <div className="App">
<div className="Banner"> <div className="Banner">
<div className="Banner-description"> <div className="Banner-description">{LABELS.AUDIT_WARNING}</div>
{LABELS.AUDIT_WARNING}
</div>
</div> </div>
<BasicLayout <BasicLayout
title={LABELS.APP_TITLE} title={LABELS.APP_TITLE}

View File

@ -14,7 +14,12 @@ import { useWallet } from "../../contexts/wallet";
import { repay } from "../../actions"; import { repay } from "../../actions";
import { CollateralSelector } from "./../CollateralSelector"; import { CollateralSelector } from "./../CollateralSelector";
import "./style.less"; import "./style.less";
import { wadToLamports, formatNumber, fromLamports, toLamports } from "../../utils/utils"; import {
wadToLamports,
formatNumber,
fromLamports,
toLamports,
} from "../../utils/utils";
import { LABELS } from "../../constants"; import { LABELS } from "../../constants";
export const RepayInput = (props: { export const RepayInput = (props: {
@ -50,9 +55,14 @@ export const RepayInput = (props: {
const obligationAccount = useAccountByMint(obligation?.info.tokenMint); const obligationAccount = useAccountByMint(obligation?.info.tokenMint);
const lamports = useMemo(() => toLamports(parseFloat(value), repayLiquidityMint), [value, repayLiquidityMint]); const lamports = useMemo(
() => toLamports(parseFloat(value), repayLiquidityMint),
[value, repayLiquidityMint]
);
const mark = wadToLamports(obligation?.info.borrowAmountWad).toNumber() / lamports * 100; const mark =
(wadToLamports(obligation?.info.borrowAmountWad).toNumber() / lamports) *
100;
const onRepay = useCallback(() => { const onRepay = useCallback(() => {
if ( if (
@ -103,8 +113,8 @@ export const RepayInput = (props: {
}} }}
> >
<div className="repay-input-title"> <div className="repay-input-title">
{LABELS.REPAY_QUESTION} (Currently:{" "}) {LABELS.REPAY_QUESTION} (Currently: ){formatNumber.format(balance)}{" "}
{formatNumber.format(balance)} {name}) {name})
</div> </div>
<div className="token-input"> <div className="token-input">
<TokenIcon mintAddress={repayReserve?.info.liquidityMint} /> <TokenIcon mintAddress={repayReserve?.info.liquidityMint} />
@ -124,10 +134,22 @@ export const RepayInput = (props: {
/> />
<div>{name}</div> <div>{name}</div>
</div> </div>
<Slider marks={marks} <Slider
marks={marks}
value={mark} value={mark}
onChange={(val: number) => onChange={(val: number) =>
setValue((fromLamports(wadToLamports(obligation?.info.borrowAmountWad).toNumber(), repayLiquidityMint) * val / 100).toFixed(2))} /> setValue(
(
(fromLamports(
wadToLamports(obligation?.info.borrowAmountWad).toNumber(),
repayLiquidityMint
) *
val) /
100
).toFixed(2)
)
}
/>
<div className="repay-input-title">{LABELS.SELECT_COLLATERAL}</div> <div className="repay-input-title">{LABELS.SELECT_COLLATERAL}</div>
<CollateralSelector <CollateralSelector
reserve={repayReserve.info} reserve={repayReserve.info}
@ -148,9 +170,9 @@ export const RepayInput = (props: {
}; };
const marks = { const marks = {
0: '0%', 0: "0%",
25: '25%', 25: "25%",
50: '50%', 50: "50%",
75: '75%', 75: "75%",
100: '100%' 100: "100%",
}; };

View File

@ -1,9 +1,115 @@
import React from "react"; import React, { useEffect, useMemo, useRef } from "react";
import { LendingReserve } from "../../models/lending"; import { LendingReserve } from "../../models/lending";
import { Card } from "antd"; import { Card } from "antd";
import { PublicKey } from "@solana/web3.js"; import { PublicKey } from "@solana/web3.js";
import "./style.less"; import "./style.less";
import { LABELS } from "../../constants"; import { LABELS } from "../../constants";
import echarts from "echarts";
import { formatNumber, formatUSD, fromLamports, wadToLamports } from "../../utils/utils";
import { useMint } from "../../contexts/accounts";
export const ReserveUtilizationChart = (props: {
reserve: LendingReserve;
}) => {
const chartDiv = useRef<HTMLDivElement>(null);
// dispose chart
useEffect(() => {
const div = chartDiv.current;
return () => {
let instance = div && echarts.getInstanceByDom(div);
instance && instance.dispose();
};
}, []);
const liquidityMint = useMint(props.reserve.liquidityMint);
const avilableLiquidity = fromLamports(
props.reserve.totalLiquidity.toNumber(),
liquidityMint
);
const totalBorrows = useMemo(
() =>
fromLamports(wadToLamports(props.reserve.totalBorrowsWad), liquidityMint),
[props.reserve, liquidityMint]
);
useEffect(() => {
if (!chartDiv.current) {
return;
}
let instance = echarts.getInstanceByDom(chartDiv.current);
if (!instance) {
instance = echarts.init(chartDiv.current as any);
}
const data = [
{
name: 'Available Liquidity',
value: avilableLiquidity,
tokens: avilableLiquidity,
},
{
name: 'Total Borrowed',
value: totalBorrows,
tokens: totalBorrows,
},
];
instance.setOption({
tooltip: {
trigger: "item",
formatter: function (params: any) {
var val = formatUSD.format(params.value);
var tokenAmount = formatNumber.format(params.data.tokens);
return `${params.name}: \n${val}\n(${tokenAmount})`;
},
},
series: [
{
name: "Liquidity",
type: "pie",
radius: ['50%', '70%'],
top: 0,
bottom: 0,
left: 0,
right: 0,
animation: false,
label: {
fontSize: 14,
show: true,
formatter: function (params: any) {
var val = formatUSD.format(params.value);
var tokenAmount = formatNumber.format(params.data.tokens);
return `{c|${params.name}}\n{r|${tokenAmount}}\n{r|${val}}`;
},
rich: {
c: {
color: "#999",
lineHeight: 22,
align: "center",
},
r: {
color: "#999",
align: "right",
},
},
color: "rgba(255, 255, 255, 0.5)",
},
itemStyle: {
normal: {
borderColor: "#000",
},
},
data,
},
],
});
}, [totalBorrows, avilableLiquidity]);
return <div ref={chartDiv} style={{ height: 300, width: 400 }} />
}
export const ReserveStatus = (props: { export const ReserveStatus = (props: {
className?: string; className?: string;
@ -29,7 +135,7 @@ export const ReserveStatus = (props: {
justifyContent: "space-around", justifyContent: "space-around",
}} }}
> >
TODO: Reserve Status - add chart <ReserveUtilizationChart reserve={props.reserve} />
</div> </div>
</Card> </Card>
); );

View File

@ -6,12 +6,10 @@ import {
useBorrowedAmount, useBorrowedAmount,
} from "./../../hooks"; } from "./../../hooks";
import { LendingReserve } from "../../models/lending"; import { LendingReserve } from "../../models/lending";
import { formatNumber, wadToLamports } from "../../utils/utils"; import { formatNumber } from "../../utils/utils";
import { Button, Card, Typography } from "antd"; import { Button, Card, Typography } from "antd";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { PublicKey } from "@solana/web3.js"; import { PublicKey } from "@solana/web3.js";
import { cache, ParsedAccount } from "../../contexts/accounts";
import { MintInfo } from "@solana/spl-token";
const { Text } = Typography; const { Text } = Typography;

View File

@ -76,9 +76,7 @@ export const WithdrawInput = (props: {
justifyContent: "space-around", justifyContent: "space-around",
}} }}
> >
<div className="withdraw-input-title"> <div className="withdraw-input-title">{LABELS.WITHDRAW_QUESTION}</div>
{LABELS.WITHDRAW_QUESTION}
</div>
<div className="token-input"> <div className="token-input">
<TokenIcon mintAddress={reserve?.liquidityMint} /> <TokenIcon mintAddress={reserve?.liquidityMint} />
<NumericInput <NumericInput

View File

@ -1,3 +1,3 @@
export * from './ids'; export * from "./ids";
export * from './labels'; export * from "./labels";
export * from './math'; export * from "./math";

View File

@ -1,11 +1,12 @@
export const LABELS = { export const LABELS = {
CONNECT_LABEL: "Connect Wallet", CONNECT_LABEL: "Connect Wallet",
GIVE_SOL: "Give me SOL", GIVE_SOL: "Give me SOL",
FAUCET_INFO: "This faucet will help you fund your accounts outside of Solana main network.", FAUCET_INFO:
"This faucet will help you fund your accounts outside of Solana main network.",
ACCOUNT_FUNDED: "Account funded.", ACCOUNT_FUNDED: "Account funded.",
REPAY_QUESTION: "How much would you like to repay?", REPAY_QUESTION: "How much would you like to repay?",
REPAY_ACTION: "Repay", REPAY_ACTION: "Repay",
RESERVE_STATUS_TITLE: "Reserve Status &amp; Configuration", RESERVE_STATUS_TITLE: "Reserve Status & Configuration",
AUDIT_WARNING: "Oyster Lending is unaudited software. Use at your own risk.", AUDIT_WARNING: "Oyster Lending is unaudited software. Use at your own risk.",
MENU_HOME: "Home", MENU_HOME: "Home",
MENU_DASHBOARD: "Dashboard", MENU_DASHBOARD: "Dashboard",
@ -27,4 +28,4 @@ export const LABELS = {
DASHBOARD_TITLE_DEPOSITS: "Deposts", DASHBOARD_TITLE_DEPOSITS: "Deposts",
WITHDRAW_ACTION: "Withdraw", WITHDRAW_ACTION: "Withdraw",
WITHDRAW_QUESTION: "How much would you like to withdraw?", WITHDRAW_QUESTION: "How much would you like to withdraw?",
} };

View File

@ -1,11 +1,14 @@
import { useEffect, useMemo, useState } from "react"; import { useEffect, useState } from "react";
import { useUserAccounts } from "./useUserAccounts";
import { useLendingObligations } from "./useLendingObligations";
import { TokenAccount } from "../models";
import { PublicKey } from "@solana/web3.js"; import { PublicKey } from "@solana/web3.js";
import { useUserObligationByReserve } from "./useUserObligationByReserve"; import { useUserObligationByReserve } from "./useUserObligationByReserve";
import { fromLamports, wadToLamports } from "../utils/utils"; import { fromLamports, wadToLamports } from "../utils/utils";
import { cache, getMultipleAccounts, MintParser, ParsedAccount, useMint } from "../contexts/accounts"; import {
cache,
getMultipleAccounts,
MintParser,
ParsedAccount,
useMint,
} from "../contexts/accounts";
import { useConnection } from "../contexts/connection"; import { useConnection } from "../contexts/connection";
import { MintInfo } from "@solana/spl-token"; import { MintInfo } from "@solana/spl-token";
import { useLendingReserve } from "./useLendingReserves"; import { useLendingReserve } from "./useLendingReserves";
@ -24,30 +27,40 @@ export function useBorrowedAmount(address?: string | PublicKey) {
// precache obligation mints // precache obligation mints
const { keys, array } = await getMultipleAccounts( const { keys, array } = await getMultipleAccounts(
connection, connection,
userObligationsByReserve userObligationsByReserve.map((item) =>
.map(item => item.obligation.info.tokenMint.toBase58()), item.obligation.info.tokenMint.toBase58()
"single"); ),
"single"
);
array.forEach((item, index) => { array.forEach((item, index) => {
const address = keys[index]; const address = keys[index];
cache.add(new PublicKey(address), item, MintParser); cache.add(new PublicKey(address), item, MintParser);
}); });
setBorrowedLamports(userObligationsByReserve.reduce((result, item) => { setBorrowedLamports(
userObligationsByReserve.reduce((result, item) => {
const borrowed = wadToLamports(
item.obligation.info.borrowAmountWad
).toNumber();
const borrowed = wadToLamports(item.obligation.info.borrowAmountWad).toNumber(); const owned = item.userAccounts.reduce(
(amount, acc) => (amount += acc.info.amount.toNumber()),
0
);
const obligationMint = cache.get(
item.obligation.info.tokenMint
) as ParsedAccount<MintInfo>;
const owned = item.userAccounts.reduce((amount, acc) => amount += acc.info.amount.toNumber(), 0); result += (borrowed * owned) / obligationMint?.info.supply.toNumber();
const obligationMint = cache.get(item.obligation.info.tokenMint) as ParsedAccount<MintInfo>; return result;
}, 0)
result += borrowed * owned / obligationMint?.info.supply.toNumber(); );
return result
}, 0));
})(); })();
}, [connection, userObligationsByReserve]);
return {
}, [userObligationsByReserve]); borrowed: fromLamports(borrowedLamports, liquidityMint),
borrowedLamports,
return { borrowed: fromLamports(borrowedLamports, liquidityMint), borrowedLamports }; };
} }

View File

@ -41,7 +41,7 @@ export function useLendingReserve(address?: string | PublicKey) {
>(); >();
useEffect(() => { useEffect(() => {
setReserveAccount(cache.get(id || '')); setReserveAccount(cache.get(id || ""));
const dispose = cache.emitter.onCache((args) => { const dispose = cache.emitter.onCache((args) => {
if (args.id === id) { if (args.id === id) {

View File

@ -5,14 +5,12 @@ import { PublicKey } from "@solana/web3.js";
export function useUserObligationByReserve(reserve?: string | PublicKey) { export function useUserObligationByReserve(reserve?: string | PublicKey) {
const { userObligations } = useUserObligations(); const { userObligations } = useUserObligations();
const userObligationsByReserve = useMemo( const userObligationsByReserve = useMemo(() => {
() => { const id = typeof reserve === "string" ? reserve : reserve?.toBase58();
const id = typeof reserve === 'string' ? reserve : reserve?.toBase58(); return userObligations.filter(
return userObligations.filter((item) => (item) => item.obligation.info.borrowReserve.toBase58() === id
item.obligation.info.borrowReserve.toBase58() === id
)
}, [reserve, userObligations]
); );
}, [reserve, userObligations]);
return { return {
userObligationsByReserve, userObligationsByReserve,

View File

@ -262,21 +262,26 @@ export const withdrawInstruction = (
}; };
export const calculateBorrowAPY = (reserve: LendingReserve) => { export const calculateBorrowAPY = (reserve: LendingReserve) => {
const totalBorrows = reserve.totalBorrowsWad.div(WAD).toNumber() const totalBorrows = reserve.totalBorrowsWad.div(WAD).toNumber();
const currentUtilization = totalBorrows / (reserve.totalLiquidity.toNumber() + totalBorrows) const currentUtilization =
const optimalUtilization = reserve.config.optimalUtilizationRate totalBorrows / (reserve.totalLiquidity.toNumber() + totalBorrows);
const optimalUtilization = reserve.config.optimalUtilizationRate;
let borrowAPY; let borrowAPY;
if (currentUtilization < optimalUtilization) { if (currentUtilization < optimalUtilization) {
const normalized_factor = currentUtilization / optimalUtilization; const normalized_factor = currentUtilization / optimalUtilization;
const optimalBorrowRate = reserve.config.optimalBorrowRate / 100; const optimalBorrowRate = reserve.config.optimalBorrowRate / 100;
const minBorrowRate = reserve.config.minBorrowRate / 100; const minBorrowRate = reserve.config.minBorrowRate / 100;
borrowAPY = normalized_factor * (optimalBorrowRate - minBorrowRate) + minBorrowRate; borrowAPY =
normalized_factor * (optimalBorrowRate - minBorrowRate) + minBorrowRate;
} else { } else {
const normalized_factor = (currentUtilization - optimalUtilization) / (100 - optimalUtilization); const normalized_factor =
(currentUtilization - optimalUtilization) / (100 - optimalUtilization);
const optimalBorrowRate = reserve.config.optimalBorrowRate / 100; const optimalBorrowRate = reserve.config.optimalBorrowRate / 100;
const maxBorrowRate = reserve.config.maxBorrowRate / 100; const maxBorrowRate = reserve.config.maxBorrowRate / 100;
borrowAPY = normalized_factor * (maxBorrowRate - optimalBorrowRate) + optimalBorrowRate; borrowAPY =
normalized_factor * (maxBorrowRate - optimalBorrowRate) +
optimalBorrowRate;
} }
return borrowAPY; return borrowAPY;
} };

View File

@ -2,7 +2,7 @@ import React from "react";
import { LABELS } from "../../constants"; import { LABELS } from "../../constants";
import { useUserObligations } from "./../../hooks"; import { useUserObligations } from "./../../hooks";
import { ObligationItem } from "./obligationItem"; import { ObligationItem } from "./obligationItem";
import "./style.less" import "./style.less";
export const DashboardView = () => { export const DashboardView = () => {
const { userObligations } = useUserObligations(); const { userObligations } = useUserObligations();
@ -14,12 +14,14 @@ export const DashboardView = () => {
</div> </div>
<div> <div>
<span>{LABELS.DASHBOARD_TITLE_LOANS}</span> <span>{LABELS.DASHBOARD_TITLE_LOANS}</span>
{userObligations.length > 0 && <div className="dashboard-item dashboard-header"> {userObligations.length > 0 && (
<div className="dashboard-item dashboard-header">
<div>{LABELS.TABLE_TITLE_ASSET}</div> <div>{LABELS.TABLE_TITLE_ASSET}</div>
<div>{LABELS.TABLE_TITLE_LOAN_BALANCE}</div> <div>{LABELS.TABLE_TITLE_LOAN_BALANCE}</div>
<div>{LABELS.TABLE_TITLE_APY}</div> <div>{LABELS.TABLE_TITLE_APY}</div>
<div>{LABELS.TABLE_TITLE_ACTION}</div> <div>{LABELS.TABLE_TITLE_ACTION}</div>
</div>} </div>
)}
{userObligations.map((item) => { {userObligations.map((item) => {
return <ObligationItem obligation={item.obligation} />; return <ObligationItem obligation={item.obligation} />;
})} })}

View File

@ -2,11 +2,7 @@ import React from "react";
import { useTokenName } from "../../hooks"; import { useTokenName } from "../../hooks";
import { LendingObligation, LendingReserve } from "../../models/lending"; import { LendingObligation, LendingReserve } from "../../models/lending";
import { TokenIcon } from "../../components/TokenIcon"; import { TokenIcon } from "../../components/TokenIcon";
import { import { wadToLamports, formatNumber, fromLamports } from "../../utils/utils";
wadToLamports,
formatNumber,
fromLamports,
} from "../../utils/utils";
import { Button, Card } from "antd"; import { Button, Card } from "antd";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { cache, ParsedAccount, useMint } from "../../contexts/accounts"; import { cache, ParsedAccount, useMint } from "../../contexts/accounts";
@ -30,9 +26,7 @@ export const ObligationItem = (props: {
); );
return ( return (
<Link <Link to={`/repay/loan/${obligation.pubkey.toBase58()}`}>
to={`/repay/loan/${obligation.pubkey.toBase58()}`}
>
<Card> <Card>
<div className="dashboard-item"> <div className="dashboard-item">
<span style={{ display: "flex" }}> <span style={{ display: "flex" }}>

View File

@ -1,13 +1,17 @@
import React from "react"; import React, { useMemo } from "react";
import { useTokenName } from "../../hooks"; import { useTokenName } from "../../hooks";
import { calculateBorrowAPY, LendingReserve } from "../../models/lending"; import { calculateBorrowAPY, LendingReserve } from "../../models/lending";
import { TokenIcon } from "../../components/TokenIcon"; import { TokenIcon } from "../../components/TokenIcon";
import { wadToLamports, formatNumber, fromLamports } from "../../utils/utils"; import {
wadToLamports,
formatNumber,
fromLamports,
formatPct,
} from "../../utils/utils";
import { Card } from "antd"; import { Card } from "antd";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { PublicKey } from "@solana/web3.js"; import { PublicKey } from "@solana/web3.js";
import { useMint } from "../../contexts/accounts"; import { useMint } from "../../contexts/accounts";
import { WAD } from "../../constants";
export const LendingReserveItem = (props: { export const LendingReserveItem = (props: {
reserve: LendingReserve; reserve: LendingReserve;
@ -22,10 +26,15 @@ export const LendingReserveItem = (props: {
liquidityMint liquidityMint
); );
console.log(props.reserve.totalBorrowsWad.toString()); const totalBorrows = useMemo(
const totalBorrows = fromLamports(wadToLamports(props.reserve.totalBorrowsWad), liquidityMint); () =>
fromLamports(wadToLamports(props.reserve.totalBorrowsWad), liquidityMint),
[props.reserve, liquidityMint]
);
console.log(liquidityMint); const borrowAPY = useMemo(() => calculateBorrowAPY(props.reserve), [
props.reserve,
]);
return ( return (
<Link to={`/reserve/${props.address.toBase58()}`}> <Link to={`/reserve/${props.address.toBase58()}`}>
@ -36,18 +45,15 @@ export const LendingReserveItem = (props: {
{name} {name}
</span> </span>
<div> <div>
{formatNumber.format(totalLiquidity)} {name} {formatNumber.format(totalLiquidity+totalBorrows)} {name}
</div> </div>
<div> <div>
{formatNumber.format(totalBorrows)} {name} {formatNumber.format(totalBorrows)} {name}
</div> </div>
<div>--</div> <div>--</div>
<div>{calculateBorrowAPY(props.reserve)}</div> <div>{formatPct.format(borrowAPY)}</div>
</div> </div>
</Card> </Card>
</Link> </Link>
); );
}; };

View File

@ -17,7 +17,9 @@ export const RepayReserveView = () => {
}>(); }>();
const lendingObligation = useLendingObligation(obligationId); const lendingObligation = useLendingObligation(obligationId);
const lendingReserve = useLendingReserve(obligationId ? lendingObligation?.info.borrowReserve : reserveId); const lendingReserve = useLendingReserve(
obligationId ? lendingObligation?.info.borrowReserve : reserveId
);
const reserve = lendingReserve?.info; const reserve = lendingReserve?.info;
console.log([reserveId, obligationId]); console.log([reserveId, obligationId]);