Skip to content

Commit

Permalink
send function implemented
Browse files Browse the repository at this point in the history
  • Loading branch information
phipsae committed Nov 30, 2023
1 parent d24129f commit 2116f0b
Show file tree
Hide file tree
Showing 2 changed files with 161 additions and 7 deletions.
111 changes: 111 additions & 0 deletions packages/nextjs/components/scaffold-eth/Input/ERC20Input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { useMemo, useState } from "react";
import { CommonInputProps, InputBase, SIGNED_NUMBER_REGEX } from "~~/components/scaffold-eth";
import { useGlobalState } from "~~/services/store/store";

const MAX_DECIMALS_USD = 2;

function etherValueToDisplayValue(usdMode: boolean, etherValue: string, nativeCurrencyPrice: number) {
if (usdMode && nativeCurrencyPrice) {
const parsedEthValue = parseFloat(etherValue);
if (Number.isNaN(parsedEthValue)) {
return etherValue;
} else {
// We need to round the value rather than use toFixed,
// since otherwise a user would not be able to modify the decimal value
return (
Math.round(parsedEthValue * nativeCurrencyPrice * 10 ** MAX_DECIMALS_USD) /
10 ** MAX_DECIMALS_USD
).toString();
}
} else {
return etherValue;
}
}

function displayValueToEtherValue(usdMode: boolean, displayValue: string, nativeCurrencyPrice: number) {
if (usdMode && nativeCurrencyPrice) {
const parsedDisplayValue = parseFloat(displayValue);
if (Number.isNaN(parsedDisplayValue)) {
// Invalid number.
return displayValue;
} else {
// Compute the ETH value if a valid number.
return (parsedDisplayValue / nativeCurrencyPrice).toString();
}
} else {
return displayValue;
}
}

/**
* Input for ETH amount with USD conversion.
*
* onChange will always be called with the value in ETH
*/
export const ERC20Input = ({ value, name, placeholder, onChange, disabled }: CommonInputProps) => {
const [transitoryDisplayValue, setTransitoryDisplayValue] = useState<string>();
const nativeCurrencyPrice = useGlobalState(state => state.nativeCurrencyPrice);
const [usdMode] = useState(false);

// The displayValue is derived from the ether value that is controlled outside of the component
// In usdMode, it is converted to its usd value, in regular mode it is unaltered
const displayValue = useMemo(() => {
const newDisplayValue = etherValueToDisplayValue(usdMode, value, nativeCurrencyPrice);
if (transitoryDisplayValue && parseFloat(newDisplayValue) === parseFloat(transitoryDisplayValue)) {
return transitoryDisplayValue;
}
// Clear any transitory display values that might be set
setTransitoryDisplayValue(undefined);
return newDisplayValue;
}, [nativeCurrencyPrice, transitoryDisplayValue, usdMode, value]);

const handleChangeNumber = (newValue: string) => {
if (newValue && !SIGNED_NUMBER_REGEX.test(newValue)) {
return;
}

// Following condition is a fix to prevent usdMode from experiencing different display values
// than what the user entered. This can happen due to floating point rounding errors that are introduced in the back and forth conversion
// if (usdMode) {
// const decimals = newValue.split(".")[1];
// if (decimals && decimals.length > MAX_DECIMALS_USD) {
// return;
// }
// }

// Since the display value is a derived state (calculated from the ether value), usdMode would not allow introducing a decimal point.
// This condition handles a transitory state for a display value with a trailing decimal sign
if (newValue.endsWith(".") || newValue.endsWith(".0")) {
setTransitoryDisplayValue(newValue);
} else {
setTransitoryDisplayValue(undefined);
}

const newEthValue = displayValueToEtherValue(usdMode, newValue, nativeCurrencyPrice);
onChange(newEthValue);
};

// const toggleMode = () => {
// setUSDMode(!usdMode);
// };

return (
<InputBase
name={name}
value={displayValue}
placeholder={placeholder}
onChange={handleChangeNumber}
disabled={disabled}
// prefix={<span className="pl-4 -mr-2 text-accent self-center">{usdMode ? "$" : "Ξ"}</span>}
// suffix={
// <button
// className={`btn btn-primary h-[2.2rem] min-h-[2.2rem] ${nativeCurrencyPrice > 0 ? "" : "hidden"}`}
// onClick={toggleMode}
// disabled={!usdMode && !nativeCurrencyPrice}
// >
// <ArrowsRightLeftIcon className="h-3 w-3 cursor-pointer" aria-hidden="true" />
// </button>
// }
/>
);
};
57 changes: 50 additions & 7 deletions packages/nextjs/components/wallet/ERC20TokenTransaction.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { useState } from "react";
import { useEffect, useState } from "react";
import { ERC20Input } from "../scaffold-eth/Input/ERC20Input";
import { publicClientSelector } from "./publicClientSelector";
import { walletClientSelector } from "./walletClientSelector";
import { parseAbi } from "viem";
import { AddressInput, IntegerInput } from "~~/components/scaffold-eth/Input";
import { parseAbi, parseEther } from "viem";
import { EnvelopeIcon } from "@heroicons/react/24/outline";
import { Spinner } from "~~/components/assets/Spinner";
import { AddressInput } from "~~/components/scaffold-eth/Input";
import { notification } from "~~/utils/scaffold-eth";

interface ERC20TokenTransactionProps {
account: any;
Expand All @@ -12,13 +16,16 @@ interface ERC20TokenTransactionProps {

const abi = parseAbi([
// "function balanceOf(address owner) view returns (uint256)",
// "event Transfer(address indexed from, address indexed to, uint256 amount)",
"event Transfer(address indexed from, address indexed to, uint256 amount)",
"function transfer(address to, uint256 value) public returns (bool)",
]);

export const ERC20TokenTransaction = ({ account, tokenAddress, selectedChain }: ERC20TokenTransactionProps) => {
const [to, setTo] = useState("");
const [amount, setAmount] = useState("");
const [isSent, setIsSent] = useState(false);
const [isConfirmed, setIsConfirmed] = useState(false);
const [isLoading, setIsLoading] = useState(false);

const walletClient = walletClientSelector(selectedChain, account);
const publicClient = publicClientSelector(selectedChain);
Expand All @@ -30,30 +37,66 @@ export const ERC20TokenTransaction = ({ account, tokenAddress, selectedChain }:
address: tokenAddress,
abi: abi,
functionName: "transfer",
args: [to, BigInt(amount)],
args: [to, parseEther(amount)],
});

const requestAny: any = response.request;
console.log(requestAny);
await walletClient.writeContract(requestAny);
setIsSent(true);
setIsLoading(true);

const unwatch = publicClient.watchContractEvent({
address: tokenAddress,
abi: abi,
eventName: "Transfer",
args: { from: account.address },
onLogs: logs => {
console.log(logs);
setIsConfirmed(true);
setIsLoading(false);
},
});
console.log(unwatch);
}
};

useEffect(() => {
if (isSent) {
notification.success("Transaction successfully submitted");
setIsSent(false);
}
if (isConfirmed && !isSent) {
notification.success("Transaction successfully confirmed");
setIsConfirmed(false);
}
}, [isSent, isConfirmed]);

return (
<>
<span className="w-1/2 mb-5">
<AddressInput value={to ?? ""} onChange={to => setTo(to)} placeholder="Address Receiver" />
</span>
<span className="w-1/2 mb-5">
<IntegerInput value={amount} onChange={amount => setAmount(amount.toString())} placeholder="#" />
<ERC20Input value={amount} onChange={amount => setAmount(amount)} placeholder="#" />
</span>

<button
className="btn btn-primary h-[2.2rem] min-h-[2.2rem] mt-auto"
onClick={() => {
txRequest();
}}
>
Send
{!isConfirmed && isLoading ? (
<div className="flex w-[100px] justify-center">
<Spinner width="100" height="100"></Spinner>
</div>
) : (
<div className="flex flex-row w-full">
<EnvelopeIcon className="h-4 w-4" />
<span className="mx-3"> Send </span>
</div>
)}
</button>
</>
);
Expand Down

0 comments on commit 2116f0b

Please sign in to comment.