Skip to content

Commit

Permalink
CD-2489 updated pricer (#6)
Browse files Browse the repository at this point in the history
  • Loading branch information
Munkhzulne authored Sep 9, 2024
1 parent 8bbdd79 commit 538f8c3
Show file tree
Hide file tree
Showing 9 changed files with 343 additions and 153 deletions.
46 changes: 30 additions & 16 deletions example/src/components/Bnpl/Options.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,45 +3,54 @@ import { IOption } from '@usecyan/sdk';
import { ethers } from 'ethers';

type IProps = {
item: { address: string; tokenId: string };
value?: { loanRate: number; duration: number };
options: IOption[];
onChange: (p: IOption) => void;
};

export const Options = ({ options, onChange }: IProps) => {
const loanRates = new Set<number>(options.map((o) => o.loanRate));
const durations = new Set<number>(options.map((o) => o.term * o.totalNumberOfPayments));
export const Options = ({ options, onChange, value, item }: IProps) => {
const loanRates = new Set<number>(options.map(o => o.loanRate));
const durations = new Set<number>(options.map(o => o.term * o.totalNumberOfPayments));

const [selected, setSelected] = useState<{ loanRate: number; duration: number }>({
loanRate: loanRates.values().next().value,
duration: durations.values().next().value,
loanRate: value?.loanRate ?? loanRates.values().next().value,
duration: value?.duration ?? durations.values().next().value,
});

const selectedOption = options.find((o) => o.loanRate === selected.loanRate && o.term * o.totalNumberOfPayments === selected.duration);
const selectedOption = options.find(
o => o.loanRate === selected.loanRate && o.term * o.totalNumberOfPayments === selected.duration
);

useEffect(() => {
if (!selectedOption) return;

onChange(selectedOption);
}, [selected]);

const onSelect = (o: {duration?: number, loanRate?: number} ) => {
setSelected((oldSelection) => ({...oldSelection, ...o}));
const onSelect = (o: { duration?: number; loanRate?: number }) => {
setSelected(oldSelection => ({ ...oldSelection, ...o }));
};

return (
<div>
<fieldset className="flex">
<fieldset className="flex" id={`${item.address}-${item.tokenId}-loanRate`}>
<legend>Select downpayment</legend>

{Array.from(loanRates).map((n) => (
{Array.from(loanRates).map(n => (
<div className="px-1" key={n}>
<input
type="radio"
id={`${n}-loanRate`}
name="loanRate"
id={`${n}-${item.address}-${item.tokenId}-loanRate`}
name={`${item.address}-${item.tokenId}-loanRate`}
value={n}
checked={selected.loanRate === n}
onChange={() => onSelect({ loanRate: n })}
disabled={
!options.some(
op => op.loanRate === n && op.term * op.totalNumberOfPayments === selected.duration
)
}
/>
<label htmlFor={`${n}-loanRate`} className="px-1">
{100 - n / 100} %
Expand All @@ -50,18 +59,23 @@ export const Options = ({ options, onChange }: IProps) => {
))}
</fieldset>

<fieldset className="flex">
<fieldset className="flex" id={`${item.address}-${item.tokenId}-duration`}>
<legend>Select duration</legend>

{Array.from(durations).map((n) => (
{Array.from(durations).map(n => (
<div className="px-1" key={n}>
<input
type="radio"
id={`${n}-duration`}
name="duration"
id={`${n}-${item.address}-${item.tokenId}-duration`}
name={`${item.address}-${item.tokenId}-duration`}
value={n}
checked={selected.duration === n}
onChange={() => onSelect({ duration: n })}
disabled={
!options.some(
op => op.loanRate === selected.loanRate && op.term * op.totalNumberOfPayments === n
)
}
/>
<label htmlFor={`${n}-duration`} className="px-2">
{n} sec.
Expand Down
96 changes: 75 additions & 21 deletions example/src/components/Bnpl/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,35 +10,44 @@ import { ItemForm } from '../ItemForm';
type IItem = {
address: string;
tokenId: string;
isAutoLiquidated: boolean;
};

type IProps = { provider: ethers.providers.Web3Provider; cyan: Cyan; chain: IChain };
export const CreateBNPL = React.memo(({ provider, cyan }: IProps) => {
const [currencyAddress, setCurrencyAddress] = useState<string>('');
const [currencyAddress, setCurrencyAddress] = useState<string>(ethers.constants.AddressZero);
const [items, setItems] = useState<IItem[]>([]);

const [selectedOption, setSelectedOption] = useState<IOption>(null);
const [itemsWithSelectedOptions, setItemsWithSelectedOptions] = useState<
Array<
ISdkPricerStep1['result']['items'][0] & {
option: IOption;
}
>
>([]);
const [pricerStep1Data, setPricerStep1Data] = useState<ISdkPricerStep1['result']>(null);
const [pricerStep2Data, setPricerStep2Data] = useState<ISdkPricerStep2['result']>(null);

const [loading, setLoading] = useState(false);

const onAddItem = (address: string, tokenId: string) => {
setItems([...items, { address, tokenId }]);
const onAddItem = (address: string, tokenId: string, isAutoLiquidated: boolean) => {
setItems([...items, { address, tokenId, isAutoLiquidated }]);
};

const pricerStep1 = async () => {
setLoading(true);
try {
const result = await cyan.priceBnplsStep1(
ethers.constants.AddressZero,
items.map(({ address, tokenId }) => ({
items.map(({ address, tokenId, isAutoLiquidated }) => ({
address,
tokenId,
isAutoLiquidated,
amount: 0,
itemType: ItemType.ERC721,
}))
);
setItemsWithSelectedOptions(result.items.map(item => ({ ...item, option: item.options?.[0] })));
setPricerStep1Data(result);
} catch (e) {
alert(e.message);
Expand All @@ -51,10 +60,12 @@ export const CreateBNPL = React.memo(({ provider, cyan }: IProps) => {
setLoading(true);
try {
const result = await cyan.priceBnplsStep2({
option: selectedOption,
items: pricerStep1Data.items,
items: itemsWithSelectedOptions.map(({ options, ...item }) => ({
...item,
option: item.option,
})),
autoRepayStatus: 0,
currencyAddress,
currencyAddress: currencyAddress,
wallet,
});
setPricerStep2Data(result);
Expand Down Expand Up @@ -84,7 +95,17 @@ export const CreateBNPL = React.memo(({ provider, cyan }: IProps) => {
}
setLoading(false);
};

const setSelectedOptionForItem = (_item: ISdkPricerStep1['result']['items'][0], option: IOption) => {
setItemsWithSelectedOptions([
...itemsWithSelectedOptions.filter(
item => item.address !== _item.address || item.tokenId !== _item.tokenId
),
{
..._item,
option,
},
]);
};
return (
<form className="block p-6 rounded-lg shadow-lg border border-2 border-black bg-white">
<h4 className="text-xl mb-3">Create BNPL</h4>
Expand All @@ -93,25 +114,59 @@ export const CreateBNPL = React.memo(({ provider, cyan }: IProps) => {
value={currencyAddress}
className="input flex-1 mx-2 mb-1"
placeholder="Currency address"
onChange={(e) => setCurrencyAddress(e.target.value)}
onChange={e => setCurrencyAddress(e.target.value)}
required
/>
{items.map((item) => {
{items.map(item => {
return (
<div key={`${item.address}:${item.tokenId}`} className="border border-black p-1 mb-1">
<div>Collection address: {item.address}</div>
<div>Token ID: {item.tokenId}</div>
<div>Auto liquidate: {item.isAutoLiquidated ? 'Yes' : 'No'}</div>
</div>
);
})}
<ItemForm onAddItem={onAddItem} />
</div>
{pricerStep1Data && <Options options={pricerStep1Data.options} onChange={setSelectedOption} />}
{pricerStep2Data && (
<div className="bg-slate-300 p-1 border border-black">
<b>Pricer Step 2 Result:</b>
{pricerStep2Data.filter(utils.isNonErrored).map((paymentPlan) => (
<div key={paymentPlan.planId}>
{pricerStep1Data &&
pricerStep1Data.items.map((item, i) => {
return (
<div key={`${item.address}:${item.tokenId}`} className="border border-black p-1 mb-1">
<div>Collection address: {item.address}</div>
<div>Token ID: {item.tokenId}</div>
{item.options?.length ? (
<Options
key={`${item.address}:${item.tokenId}`}
value={
itemsWithSelectedOptions[i]?.option
? {
loanRate: itemsWithSelectedOptions[i].option.loanRate,
duration:
itemsWithSelectedOptions[i].option.term *
itemsWithSelectedOptions[i].option.totalNumberOfPayments,
}
: undefined
}
item={{
address: item.address,
tokenId: item.tokenId,
}}
options={item.options}
onChange={(option: IOption) => setSelectedOptionForItem(item, option)}
/>
) : (
<div>
<b>No loan options available</b>
</div>
)}
</div>
);
})}{' '}
{pricerStep2Data &&
pricerStep2Data.filter(utils.isNonErrored).map(paymentPlan => (
<div className="bg-slate-300 p-1 border border-black" key={paymentPlan.planId}>
<b>Pricer Step 2 Result:</b>
<div>
<p>Plan ID: {paymentPlan.planId}</p>
<p>Interest Rate: {paymentPlan.plan.interestRate / 100}%</p>
<p>Priced Market: {paymentPlan.marketName}</p>
Expand All @@ -121,15 +176,14 @@ export const CreateBNPL = React.memo(({ provider, cyan }: IProps) => {
<p>Block number: {paymentPlan.blockNum}</p>
<p>Signature: {paymentPlan.signature}</p>
</div>
))}
</div>
)}
</div>
))}
{loading ? (
<Loading />
) : (
<>
<Button onClick={pricerStep1}>Pricer Step 1</Button>
<Button onClick={pricerStep2} disabled={!pricerStep1Data}>
<Button onClick={pricerStep2} disabled={itemsWithSelectedOptions.length === 0}>
Pricer Step 2
</Button>
<Button onClick={handleAcceptance} disabled={!pricerStep2Data}>
Expand Down
18 changes: 14 additions & 4 deletions example/src/components/ItemForm.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { useState } from 'react';

type IProps = {
onAddItem(address: string, tokenId: string): void;
onAddItem(address: string, tokenId: string, isAutoLiquidated: boolean): void;
};
export const ItemForm = ({ onAddItem }: IProps) => {
const [address, setAddress] = useState<string>('');
const [tokenId, setTokenId] = useState<string>('');
const [isAutoLiquidated, setIsAutoLiquidated] = useState<boolean>(false);

const _onAddItem = () => {
onAddItem(address, tokenId);
onAddItem(address, tokenId, isAutoLiquidated);
setAddress('');
setTokenId('');
};
Expand All @@ -19,16 +20,25 @@ export const ItemForm = ({ onAddItem }: IProps) => {
value={address}
className="input flex-1 mx-2"
placeholder="Collection address"
onChange={(e) => setAddress(e.target.value)}
onChange={e => setAddress(e.target.value)}
required
/>
<input
className="input flex-1 mx-2"
placeholder="Token ID"
value={tokenId}
onChange={(e) => setTokenId(e.target.value)}
onChange={e => setTokenId(e.target.value)}
required
/>
<div className="flex-col mx-2">
<div>Auto liquidate:</div>
<input
type="checkbox"
className="input flex-1 h-7"
checked={isAutoLiquidated}
onChange={e => setIsAutoLiquidated(e.target.checked)}
/>
</div>
<button className="button" onClick={_onAddItem}>
Add item
</button>
Expand Down
Loading

0 comments on commit 538f8c3

Please sign in to comment.