From e965f8003c1e64a73aa6c461aacfbe97ff46d8bc Mon Sep 17 00:00:00 2001 From: siegfried Date: Sat, 2 Sep 2023 02:57:12 +0800 Subject: [PATCH] Support Donation in New Transaction (#139) * Add donationAddress() * Extract RecipientValueInput out * Upgrade caniuse-lite * Add Donation to NewTransaction * Add Donation to README --- README.md | 6 ++ src/cardano/config.ts | 11 ++- src/components/transaction.tsx | 161 +++++++++++++++++++++------------ yarn.lock | 13 +-- 4 files changed, 124 insertions(+), 67 deletions(-) diff --git a/README.md b/README.md index d9fba26f..5b2c59f9 100644 --- a/README.md +++ b/README.md @@ -66,3 +66,9 @@ Then visit http://localhost:3000/ The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. + +## Donation + +We kindly suggest considering tipping the developer as it would greatly contribute to the development and quality of the project. Your support is highly appreciated. Thank you for your consideration. + +addr1qy8yxxrle7hq62zgpazaj7kj36nphqyyxey62wm694dgfds5kkvr22hlffqdj63vk8nf8rje5np37v4fwlpvj4c4qryqtcla0w diff --git a/src/cardano/config.ts b/src/cardano/config.ts index 7caa989b..e727e2f7 100644 --- a/src/cardano/config.ts +++ b/src/cardano/config.ts @@ -66,5 +66,14 @@ const ConfigContext = createContext<[Config, (x: Config) => void]>([defaultConfi const defaultGraphQLURI = process.env.NEXT_PUBLIC_GRAPHQL ?? (parseNetwork(process.env.NEXT_PUBLIC_NETWORK ?? 'mainnet') === 'mainnet' ? defaultGraphQLMainnet : defaultGraphQLTestnet) +const donationAddress = (network: Network): string => { + switch(network) { + case 'mainnet': + return 'addr1qy8yxxrle7hq62zgpazaj7kj36nphqyyxey62wm694dgfds5kkvr22hlffqdj63vk8nf8rje5np37v4fwlpvj4c4qryqtcla0w'; + default: + return 'addr_test1qpe7qk82nqyd77tdqmn6q7y5ll4kwwxdajgwf3llcu4e44nmcxl09wnytjsykngrga52kqhevzv2dn67rt0876qmwn3sf7qxv3'; + } +} + export type { Config, Network } -export { ConfigContext, config, defaultGraphQLURI, isMainnet } +export { ConfigContext, config, defaultGraphQLURI, donationAddress, isMainnet } diff --git a/src/components/transaction.tsx b/src/components/transaction.tsx index b501a083..c1edd7af 100644 --- a/src/components/transaction.tsx +++ b/src/components/transaction.tsx @@ -6,9 +6,9 @@ import type { Value, RecipientRegistry } from '../cardano/query-api' import { getResult, isAddressNetworkCorrect, newRecipient, toAddressString, toHex, toIter, useCardanoMultiplatformLib, verifySignature } from '../cardano/multiplatform-lib' import type { Cardano, Recipient } from '../cardano/multiplatform-lib' import type { Certificate, Transaction, TransactionHash, TransactionInput, Vkeywitness, SingleInputBuilder, InputBuilderResult, SingleCertificateBuilder, CertificateBuilderResult, TransactionWitnessSet, TransactionOutputs, SingleWithdrawalBuilder, WithdrawalBuilderResult } from '@dcspark/cardano-multiplatform-lib-browser' -import { ShareIcon, ArrowUpTrayIcon, PlusIcon, XMarkIcon, XCircleIcon, MagnifyingGlassIcon, ChevronLeftIcon, ChevronRightIcon, PencilIcon, WalletIcon } from '@heroicons/react/24/solid' +import { ShareIcon, ArrowUpTrayIcon, PlusIcon, XMarkIcon, XCircleIcon, MagnifyingGlassIcon, ChevronLeftIcon, ChevronRightIcon, PencilIcon, WalletIcon, HeartIcon } from '@heroicons/react/24/solid' import Link from 'next/link' -import { ConfigContext, isMainnet } from '../cardano/config' +import { ConfigContext, donationAddress, isMainnet } from '../cardano/config' import { CopyButton, Hero, Panel, ShareCurrentURLButton, Modal, TextareaModalBox } from './layout' import { PasswordBox } from './password' import { NotificationContext } from './notification' @@ -898,58 +898,36 @@ const RecipientAddressInput: FC<{ ) } -const TransactionRecipient: FC<{ - cardano: Cardano - recipient: Recipient +const RecipientValueInput: FC<{ + className?: string + value: Value + setValue: (value: Value) => void + minLovelace?: bigint budget: Value - getMinLovelace: (recipient: Recipient) => bigint - onChange: (recipient: Recipient) => void -}> = ({ cardano, recipient, budget, getMinLovelace, onChange }) => { - +}> = ({ className, value, setValue, minLovelace, budget }) => { const [config, _] = useContext(ConfigContext) - const setRecipient = useCallback((recipient: Recipient) => { - onChange(recipient) - }, [onChange]) - const setAddress = useCallback((address: string) => { - setRecipient({ ...recipient, address }) - }, [setRecipient, recipient]) const setLovelace = useCallback((lovelace: bigint) => { - const { value } = recipient - setRecipient({ ...recipient, value: { ...value, lovelace } }) - }, [setRecipient, recipient]) + setValue({ ...value, lovelace }) + }, [value, setValue]) const setAsset = useCallback((id: string, quantity: bigint) => { - const { value } = recipient - setRecipient({ - ...recipient, - value: { - ...value, - assets: new Map(value.assets).set(id, quantity) - } - }) - }, [setRecipient, recipient]) + setValue({ ...value, assets: new Map(value.assets).set(id, quantity) }) + }, [value, setValue]) const deleteAsset = useCallback((id: string) => { - const { value } = recipient const newAssets = new Map(value.assets) newAssets.delete(id) - setRecipient({ - ...recipient, - value: { ...value, assets: newAssets } - }) - }, [setRecipient, recipient]) - - const minLovelace = useMemo(() => cardano.isValidAddress(recipient.address) ? getMinLovelace(recipient) : undefined, [recipient, cardano, getMinLovelace]) + setValue({ ...value, assets: newAssets }) + }, [value, setValue]) const selectAsset = useCallback((id: string) => setAsset(id, BigInt(0)), [setAsset]) return ( -
- +
{minLovelace ?

@@ -963,7 +941,7 @@ const TransactionRecipient: FC<{

: null}
    - {Array.from(recipient.value.assets).map(([id, quantity]) => { + {Array.from(value.assets).map(([id, quantity]) => { const symbol = decodeASCII(getAssetName(id)) const assetBudget = (budget.assets.get(id) || BigInt(0)) const onChange = (value: bigint) => setAsset(id, value) @@ -983,7 +961,30 @@ const TransactionRecipient: FC<{ ) })}
- + +
+ ) +} + +const TransactionRecipient: FC<{ + cardano: Cardano + recipient: Recipient + budget: Value + getMinLovelace: (recipient: Recipient) => bigint + setRecipient: (recipient: Recipient) => void +}> = ({ cardano, recipient, budget, getMinLovelace, setRecipient }) => { + const minLovelace = useMemo(() => cardano.isValidAddress(recipient.address) ? getMinLovelace(recipient) : undefined, [recipient, cardano, getMinLovelace]) + const setAddress = useCallback((address: string) => { + setRecipient({ ...recipient, address }) + }, [setRecipient, recipient]) + const setValue = useCallback((value: Value) => { + setRecipient({ ...recipient, value }) + }, [setRecipient, recipient]) + + return ( +
+ +
) } @@ -1039,7 +1040,15 @@ const NewTransaction: FC<{ if (!isRegistered && delegation) return BigInt(protocolParameters.keyDeposit) return BigInt(0) }, [isRegistered, delegation, protocolParameters]) - const budget: Value = useMemo(() => recipients + const [config, _] = useContext(ConfigContext) + const donatingAddress = useMemo(() => donationAddress(config.network), [config.network]) + const [donatingValue, setDonatingValue] = useState() + const donatingRecipient: Recipient | undefined = useMemo(() => donatingValue && { + address: donatingAddress, + value: donatingValue + }, [donatingValue, donatingAddress]) + const allRecipients = useMemo(() => recipients.concat(donatingRecipient ?? []), [donatingRecipient, recipients]) + const budget: Value = useMemo(() => allRecipients .map(({ value }) => value) .concat({ lovelace: deposit, assets: new Map() }) .reduce((result, value) => { @@ -1050,7 +1059,13 @@ const NewTransaction: FC<{ _quantity && assets.set(id, _quantity - quantity) }) return { lovelace, assets } - }, getBalanceByUTxOs(utxos)), [deposit, recipients, utxos]) + }, getBalanceByUTxOs(utxos)), [deposit, allRecipients, utxos]) + const donate = useCallback(() => { + setDonatingValue({ + lovelace: BigInt(0), + assets: new Map() + }) + }, []) const stakeRegistration = useMemo(() => { if (!isRegistered && delegation) return cardano.createRegistrationCertificate(rewardAddress) }, [cardano, delegation, rewardAddress, isRegistered]) @@ -1100,7 +1115,7 @@ const NewTransaction: FC<{ }, [defaultChangeAddress, isChangeSettingDisabled]) useEffect(() => { - if (willSpendAll || recipients.length === 0) { + if (willSpendAll || allRecipients.length === 0) { setInputs(utxos) return } @@ -1122,7 +1137,7 @@ const NewTransaction: FC<{ }) } }) - const outputs: Output[] = recipients.map((recipient) => { + const outputs: Output[] = allRecipients.map((recipient) => { return { lovelace: recipient.value.lovelace, assets: Array.from(recipient.value.assets).map(([id, quantity]) => { @@ -1138,7 +1153,7 @@ const NewTransaction: FC<{ const txOutputs: TransactionOutput[] | undefined = result?.selected.map((output) => output.data) txOutputs && setInputs(txOutputs) }) - }, [utxos, recipients, willSpendAll, minLovelaceForChange]) + }, [utxos, allRecipients, willSpendAll, minLovelaceForChange]) const getMinLovelace = useCallback((recipient: Recipient): bigint => cardano.getMinLovelace(recipient, protocolParameters), [cardano, protocolParameters]) @@ -1153,9 +1168,9 @@ const NewTransaction: FC<{ txBuilder.add_input(buildInputResult(builder)) }) - recipients.forEach((recipient) => { - const result = cardano.buildTxOutput(recipient, protocolParameters) - txBuilder.add_output(result) + allRecipients.forEach((recipient) => { + const txOutput = cardano.buildTxOutput(recipient, protocolParameters) + txBuilder.add_output(txOutput) }) if (stakeRegistration) txBuilder.add_cert(buildCertResult(SingleCertificateBuilder.new(stakeRegistration))) @@ -1174,7 +1189,7 @@ const NewTransaction: FC<{ if (expirySlot) txBuilder.set_ttl(BigNum.from_str(expirySlot.toString())) return txBuilder.build(ChangeSelectionAlgo.Default, cardano.parseAddress(changeAddress)).build_unchecked() - }), [recipients, cardano, changeAddress, auxiliaryData, protocolParameters, inputs, stakeRegistration, stakeDelegation, buildInputResult, buildCertResult, buildWithdrawalResult, startSlot, expirySlot, availableReward, rewardAddress, withdrawAll, stakeDeregistration]) + }), [allRecipients, cardano, changeAddress, auxiliaryData, protocolParameters, inputs, stakeRegistration, stakeDelegation, buildInputResult, buildCertResult, buildWithdrawalResult, startSlot, expirySlot, availableReward, rewardAddress, withdrawAll, stakeDeregistration]) const changeRecipient = useCallback((index: number, recipient: Recipient) => { setRecipients(recipients.map((_recipient, _index) => index === _index ? recipient : _recipient)) @@ -1202,10 +1217,35 @@ const NewTransaction: FC<{ recipient={recipient} budget={budget} getMinLovelace={getMinLovelace} - onChange={(rec) => changeRecipient(index, rec)} /> + setRecipient={(rec) => changeRecipient(index, rec)} /> )} + {donatingValue &&
+
+

+ Donation + +

+ +
+
+
+
We kindly suggest considering tipping the developer as it would greatly contribute to the development and quality of the project. Your support is highly appreciated. Thank you for your consideration.
+
+
{donatingAddress}
+ +
+
} {withdrawAll &&

Withdraw Reward

@@ -1293,9 +1333,9 @@ const NewTransaction: FC<{
-

{recipients.length > 0 ? 'Change' : 'Send All'}

-

{recipients.length > 0 ? 'The change caused by this transaction or all remaining assets in the treasury will be sent to this address (default to the treasury address). DO NOT MODIFY IT UNLESS YOU KNOW WHAT YOU ARE DOING!' : 'All assets in this treasury will be sent to this address.'}

- {recipients.length > 0 &&

+

{allRecipients.length > 0 ? 'Change' : 'Send All'}

+

{allRecipients.length > 0 ? 'The change caused by this transaction or all remaining assets in the treasury will be sent to this address (default to the treasury address). DO NOT MODIFY IT UNLESS YOU KNOW WHAT YOU ARE DOING!' : 'All assets in this treasury will be sent to this address.'}

+ {allRecipients.length > 0 &&

+ {!willSpendAll && allRecipients.length > 0 &&
} - {!isChangeSettingDisabled && recipients.length > 0 &&
+ {!isChangeSettingDisabled && allRecipients.length > 0 &&