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

Polish/dapp #14

Merged
merged 3 commits into from
Mar 13, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
added FlowingBalance in Streams
  • Loading branch information
iafhurtado committed Mar 2, 2024
commit 2546a0543b525233328e29b388b503e8e165ca26
102 changes: 102 additions & 0 deletions packages/nextjs/components/streams/FlowingBalance.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import React, { memo, useEffect, useMemo, useState } from "react";
import { formatEther } from "viem";

// Constants
export const ANIMATION_MINIMUM_STEP_TIME = 40;

// Utility functions
export const absoluteValue = (n: bigint) => {
return n >= BigInt(0) ? n : -n;
};

export function toFixedUsingString(numStr: string, decimalPlaces: number): string {
const [wholePart, decimalPart] = numStr.split(".");

if (!decimalPart || decimalPart.length <= decimalPlaces) {
return numStr.padEnd(wholePart.length + 1 + decimalPlaces, "0");
}

const decimalPartBigInt = BigInt(
`${decimalPart.slice(0, decimalPlaces)}${decimalPart[decimalPlaces] >= "5" ? "1" : "0"}`,
);

return `${wholePart}.${decimalPartBigInt.toString().padStart(decimalPlaces, "0")}`;
}

// Hooks
export const useSignificantFlowingDecimal = (flowRate: bigint, animationStepTimeInMs: number): number | undefined =>
useMemo(() => {
if (flowRate === BigInt(0)) {
return undefined;
}

const ticksPerSecond = 1000 / animationStepTimeInMs;
const flowRatePerTick = flowRate / BigInt(ticksPerSecond);

const [beforeEtherDecimal, afterEtherDecimal] = formatEther(flowRatePerTick).split(".");

const isFlowingInWholeNumbers = absoluteValue(BigInt(beforeEtherDecimal)) > BigInt(0);

if (isFlowingInWholeNumbers) {
return 0; // Flowing in whole numbers per tick.
}
const numberAfterDecimalWithoutLeadingZeroes = BigInt(afterEtherDecimal);

const lengthToFirstSignificantDecimal = afterEtherDecimal
.toString()
.replace(numberAfterDecimalWithoutLeadingZeroes.toString(), "").length; // We're basically counting the zeroes.

return Math.min(lengthToFirstSignificantDecimal + 2, 18); // Don't go over 18.
}, [flowRate, animationStepTimeInMs]);

const useFlowingBalance = (startingBalance: bigint, startingBalanceDate: Date, flowRate: bigint) => {
const [flowingBalance, setFlowingBalance] = useState(startingBalance);

const startingBalanceTime = startingBalanceDate.getTime();
useEffect(() => {
if (flowRate === BigInt(0)) return;

let lastAnimationTimestamp = 0;

const animationStep = (currentAnimationTimestamp: number) => {
const animationFrameId = window.requestAnimationFrame(animationStep);
if (currentAnimationTimestamp - lastAnimationTimestamp > ANIMATION_MINIMUM_STEP_TIME) {
const elapsedTimeInMilliseconds = BigInt(Date.now() - startingBalanceTime);
const flowingBalance_ = startingBalance + (flowRate * elapsedTimeInMilliseconds) / BigInt(1000);

setFlowingBalance(flowingBalance_);

lastAnimationTimestamp = currentAnimationTimestamp;
}

return () => window.cancelAnimationFrame(animationFrameId);
};

const animationFrameId = window.requestAnimationFrame(animationStep);

return () => window.cancelAnimationFrame(animationFrameId);
}, [startingBalance, startingBalanceTime, flowRate]);

return flowingBalance;
};

// FlowingBalance Component
const FlowingBalance: React.FC<{
startingBalance: bigint;
startingBalanceDate: Date;
flowRate: bigint;
}> = memo(function FlowingBalance({ startingBalance, startingBalanceDate, flowRate }) {
const flowingBalance = useFlowingBalance(startingBalance, startingBalanceDate, flowRate);

const decimalPlaces = useSignificantFlowingDecimal(flowRate, ANIMATION_MINIMUM_STEP_TIME);

return (
<div className="flowing-balance stat-value text-accent text-8xl align-middle">
{decimalPlaces !== undefined
? toFixedUsingString(formatEther(flowingBalance), decimalPlaces)
: formatEther(flowingBalance)}
</div>
);
});

export default FlowingBalance;
136 changes: 71 additions & 65 deletions packages/nextjs/components/streams/SuperTokens.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ const SupertokensComponent: React.FC = () => {
address: "0xcfA132E353cB4E398080B9700609bb008eceB125",
abi: CFAv1ForwarderABI,
functionName: "setFlowrate",
args: ["0x36d9a149895d905D117C38F3090f4344B76Ec9F4", receiverAddress, BigInt(100)],
args: ["0x36d9a149895d905D117C38F3090f4344B76Ec9F4", receiverAddress, BigInt(100000000000000)],
});

const { write: deleteFlow } = useContractWrite({
Expand All @@ -50,70 +50,76 @@ const SupertokensComponent: React.FC = () => {

return (
<>
<div>
<h1>Approve Upgrade</h1>
<input
type="text"
placeholder="Type here"
className="input input-bordered w-full max-w-xs"
value={approveAmount}
onChange={e => setApproveAmount(e.target.value)}
/>
<button className="btn btn-success" onClick={() => approveUpgrade()}>
Approve Upgrade
</button>
</div>
<div>
<h1>Upgrade</h1>
<input
type="text"
placeholder="Type here"
className="input input-bordered w-full max-w-xs"
value={upgradeAmount}
onChange={e => setUpgradeAmount(e.target.value)}
/>
<button className="btn btn-success" onClick={() => upgrade()}>
Upgrade
</button>
</div>
<div>
<h1>Downgrade</h1>
<input
type="text"
placeholder="Type here"
className="input input-bordered w-full max-w-xs"
value={downgradeAmount}
onChange={e => setDowngradeAmount(e.target.value)}
/>
<button className="btn btn-error" onClick={() => downgrade()}>
Downgrade
</button>
</div>
<div>
<h1>Set Flowrate</h1>
<input
type="text"
placeholder="Type here"
className="input input-bordered w-full max-w-xs"
value={receiverAddress}
onChange={e => setReceiverAddress(e.target.value)}
/>
<button className="btn btn-success" onClick={() => forwarder()}>
Set Flowrate
</button>
</div>
<div>
<h1>Delete Flowrate</h1>
<input
type="text"
placeholder="Type receiver address"
className="input input-bordered w-full max-w-xs"
value={deleteAddress}
onChange={e => setDeleteAddress(e.target.value)}
/>
<button className="btn btn-error" onClick={() => deleteFlow()}>
Delete Flowrate
</button>
<div className="flex items-center justify-center space-y-8">
<div className="text-center">
<h1>Approve Upgrade</h1>
<input
type="text"
placeholder="Type here"
className="input input-bordered w-full max-w-xs"
value={approveAmount}
onChange={e => setApproveAmount(e.target.value)}
/>
<button className="btn btn-success mt-2" onClick={() => approveUpgrade()}>
Approve Upgrade
</button>
</div>

<div className="text-center">
<h1>Upgrade</h1>
<input
type="text"
placeholder="Type here"
className="input input-bordered w-full max-w-xs"
value={upgradeAmount}
onChange={e => setUpgradeAmount(e.target.value)}
/>
<button className="btn btn-success mt-2" onClick={() => upgrade()}>
Upgrade
</button>
</div>

<div className="text-center">
<h1>Downgrade</h1>
<input
type="text"
placeholder="Type here"
className="input input-bordered w-full max-w-xs"
value={downgradeAmount}
onChange={e => setDowngradeAmount(e.target.value)}
/>
<button className="btn btn-error mt-2" onClick={() => downgrade()}>
Downgrade
</button>
</div>

<div className="text-center">
<h1>Set Flowrate</h1>
<input
type="text"
placeholder="Type here"
className="input input-bordered w-full max-w-xs"
value={receiverAddress}
onChange={e => setReceiverAddress(e.target.value)}
/>
<button className="btn btn-success mt-2" onClick={() => forwarder()}>
Set Flowrate
</button>
</div>

<div className="text-center">
<h1>Delete Flowrate</h1>
<input
type="text"
placeholder="Type receiver address"
className="input input-bordered w-full max-w-xs"
value={deleteAddress}
onChange={e => setDeleteAddress(e.target.value)}
/>
<button className="btn btn-error mt-2" onClick={() => deleteFlow()}>
Delete Flowrate
</button>
</div>
</div>
</>
);
Expand Down
37 changes: 36 additions & 1 deletion packages/nextjs/pages/streams.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,47 @@
import { MetaHeader } from "../components/MetaHeader";
import type { NextPage } from "next";
import { useContractRead } from "wagmi";
import { useAccount } from "wagmi";
import FlowingBalance from "~~/components/streams/FlowingBalance";
import SuperTokens from "~~/components/streams/SuperTokens";
import { CFAv1ForwarderABI } from "~~/components/streams/abis/CFAv1Forwarder";
import { SuperTokenABI } from "~~/components/streams/abis/SuperTokenABI";

const Streams: NextPage = () => {
const account = useAccount();

const { data: balanceOf } = useContractRead({
address: "0x36d9a149895d905D117C38F3090f4344B76Ec9F4",
abi: SuperTokenABI,
functionName: "balanceOf",
args: [account.address],
});

console.log(balanceOf);

const { data: getAccountFlowrate } = useContractRead({
address: "0xcfA132E353cB4E398080B9700609bb008eceB125",
abi: CFAv1ForwarderABI,
functionName: "getAccountFlowrate",
args: ["0x36d9a149895d905D117C38F3090f4344B76Ec9F4", account.address],
});

return (
<>
<MetaHeader title="XocDapp | Wagmi" description="XocDapp page" />
<SuperTokens />
<div className="flex flex-col m-10 p-20 items-center ">
<SuperTokens />
<div className="card w-3/5 bg-base-100 shadow-xl mt-16">
<div className="card-body">
<FlowingBalance
startingBalance={BigInt(balanceOf?.toString() ?? "0")}
startingBalanceDate={new Date()}
flowRate={BigInt(getAccountFlowrate?.toString() ?? "0")}
/>
<div className="stat-title">Account&apos;s Flowrate</div>
</div>
</div>
</div>
</>
);
};
Expand Down
Loading