From 36046928c37facb3c9d9357717ca41a1d0a96629 Mon Sep 17 00:00:00 2001 From: bry Date: Wed, 16 Aug 2023 14:44:25 -0500 Subject: [PATCH 1/3] untested --- package.json | 1 + src/components/MemoInput.tsx | 89 ++++++++++++++++++++++++++++ src/features/burn/BurnScreen.tsx | 42 +++++++------ src/features/payment/PaymentItem.tsx | 34 ++++++++++- src/hooks/useSubmitTxn.ts | 8 ++- src/utils/solanaUtils.ts | 38 ++++++++---- yarn.lock | 7 +++ 7 files changed, 187 insertions(+), 32 deletions(-) create mode 100644 src/components/MemoInput.tsx diff --git a/package.json b/package.json index ff0feaaec..3e5464d4d 100644 --- a/package.json +++ b/package.json @@ -83,6 +83,7 @@ "@rnmapbox/maps": "^10.0.7-rc.0", "@shopify/restyle": "1.8.0", "@solana/spl-account-compression": "0.1.4", + "@solana/spl-memo": "^0.2.3", "@solana/spl-token": "0.3.6", "@solana/spl-token-registry": "^0.2.4574", "@solana/wallet-adapter-react": "^0.15.33", diff --git a/src/components/MemoInput.tsx b/src/components/MemoInput.tsx new file mode 100644 index 000000000..0ad10a2ad --- /dev/null +++ b/src/components/MemoInput.tsx @@ -0,0 +1,89 @@ +import { BoxProps } from '@shopify/restyle' +import React, { memo, useMemo } from 'react' +import { useTranslation } from 'react-i18next' +import { Theme } from '@theme/theme' +import Box from './Box' +import Text from './Text' +import TextInput from './TextInput' + +export const DEFAULT_MEMO = 'AAAAAAAAAAA=' +export const MEMO_MAX_BYTES = 8 +export const encodeMemoString = (utf8Input: string | undefined) => { + if (!utf8Input) return undefined + const buff = Buffer.from(utf8Input, 'utf8') + return buff.toString('base64') +} + +export const decodeMemoString = (base64String: string | undefined | null) => { + if (!base64String) return '' + const buff = Buffer.from(base64String, 'base64') + return buff.toString('utf8') +} + +export const getMemoBytesLeft = (base64Memo?: string) => { + if (!base64Memo) + return { numBytes: MEMO_MAX_BYTES, valid: true, bytesUsed: 0 } + const buff = Buffer.from(base64Memo, 'base64') + const size = buff.byteLength + return { + bytesRemaining: MEMO_MAX_BYTES - size, + valid: size <= MEMO_MAX_BYTES, + bytesUsed: size, + } +} + +export const getMemoStrValid = (memoStr?: string) => { + const base64Memo = encodeMemoString(memoStr) + if (!base64Memo) { + return true + } + const buff = Buffer.from(base64Memo, 'base64') + const size = buff.byteLength + return size <= MEMO_MAX_BYTES +} + +export const useMemoValid = (txnMemo?: string) => { + return useMemo(() => { + const base64Memo = encodeMemoString(txnMemo) + return getMemoBytesLeft(base64Memo) + }, [txnMemo]) +} + +type Props = { + onChangeText?: ((text: string) => void) | undefined + value?: string | undefined +} & BoxProps +const MemoInput = ({ onChangeText, value, ...boxProps }: Props) => { + const { t } = useTranslation() + const { bytesUsed, valid } = useMemoValid(value) + + return ( + + + + + {t('payment.memoBytes', { + used: bytesUsed, + total: MEMO_MAX_BYTES, + })} + + + + ) +} +export default memo(MemoInput) diff --git a/src/features/burn/BurnScreen.tsx b/src/features/burn/BurnScreen.tsx index 543ff8fa9..b76b647e9 100644 --- a/src/features/burn/BurnScreen.tsx +++ b/src/features/burn/BurnScreen.tsx @@ -99,6 +99,7 @@ const BurnScreen = () => { (reduxState: RootState) => reduxState.solana.delegate, ) const [mint, setMint] = useState(MOBILE_MINT) + const [memo, setMemo] = useState('') const { symbol } = useMetaplexMetadata(mint) const tokenSelectorRef = useRef(null) @@ -180,6 +181,7 @@ const BurnScreen = () => { delegateAddress, amountBalance.toNumber(), mint, + memo, ) } } catch (e) { @@ -190,6 +192,7 @@ const BurnScreen = () => { delegateAddress, isDelegate, mint, + memo, submitDelegateDataCredits, setSubmitError, ]) @@ -381,23 +384,28 @@ const BurnScreen = () => { /> {isDelegate ? ( - { - setDelegateAddress(address) - handleAddressError({ - address, - }) - }} - handleAddressError={handleAddressError} - mint={DC_MINT} - address={delegateAddress} - amount={amountBalance} - hasError={hasError} - hideMemo - /> + + { + setMemo(m) + }} + onEditAddress={({ address }) => { + setDelegateAddress(address) + handleAddressError({ + address, + }) + }} + handleAddressError={handleAddressError} + mint={DC_MINT} + address={delegateAddress} + amount={amountBalance} + memo={memo} + hasError={hasError} + /> + ) : ( <> @@ -44,6 +51,7 @@ type Props = { onEditAmount: (opts: { address?: string; index: number }) => void onToggleMax?: (opts: { address?: string; index: number }) => void onEditAddress: (opts: { index: number; address: string }) => void + onEditMemo?: (opts: { address?: string; index: number; memo: string }) => void handleAddressError: (opts: { index: number address: string @@ -66,12 +74,15 @@ const PaymentItem = ({ fee, handleAddressError, hasError, + hideMemo, index, max, + memo, netType, onAddressBookSelected, onEditAddress, onEditAmount, + onEditMemo, onRemove, onToggleMax, onUpdateError, @@ -127,6 +138,15 @@ const PaymentItem = ({ [index, onEditAddress], ) + const handleEditMemo = useCallback( + (text?: string) => { + if (!onEditMemo) return + + onEditMemo({ memo: text || '', address, index }) + }, + [address, index, onEditMemo], + ) + const handleAddressBlur = useCallback( (event?: NativeSyntheticEvent) => { const text = event?.nativeEvent.text @@ -297,7 +317,17 @@ const PaymentItem = ({ )} + + {!hideMemo && ( + <> + + + + + + + )} ) } -export default memo(PaymentItem) +export default reactMemo(PaymentItem) diff --git a/src/hooks/useSubmitTxn.ts b/src/hooks/useSubmitTxn.ts index bdbeef351..a466c3bf1 100644 --- a/src/hooks/useSubmitTxn.ts +++ b/src/hooks/useSubmitTxn.ts @@ -386,7 +386,12 @@ export default () => { ) const submitDelegateDataCredits = useCallback( - async (delegateAddress: string, amount: number, mint: PublicKey) => { + async ( + delegateAddress: string, + amount: number, + mint: PublicKey, + memo: string, + ) => { if (!currentAccount || !anchorProvider || !walletSignBottomSheetRef) { throw new Error(t('errors.account')) } @@ -396,6 +401,7 @@ export default () => { delegateAddress, amount, mint, + memo, ) const serializedTx = delegateDCTxn.serialize({ diff --git a/src/utils/solanaUtils.ts b/src/utils/solanaUtils.ts index 63c78af73..33e3e442b 100644 --- a/src/utils/solanaUtils.ts +++ b/src/utils/solanaUtils.ts @@ -60,6 +60,7 @@ import { SPL_ACCOUNT_COMPRESSION_PROGRAM_ID, SPL_NOOP_PROGRAM_ID, } from '@solana/spl-account-compression' +import { createMemoInstruction } from '@solana/spl-memo' import { ASSOCIATED_TOKEN_PROGRAM_ID, AccountLayout, @@ -654,6 +655,7 @@ export const delegateDataCredits = async ( delegateAddress: string, amount: number, mint: PublicKey, + memo: string, ) => { try { const { connection } = anchorProvider @@ -662,22 +664,34 @@ export const delegateDataCredits = async ( const program = await dc.init(anchorProvider) const subDao = subDaoKey(mint)[0] - const tx = await program.methods - .delegateDataCreditsV0({ - amount: new BN(amount, 0), - routerKey: delegateAddress, - }) - .accounts({ - subDao, - }) - .transaction() + const instructions: TransactionInstruction[] = [] + + instructions.push(createMemoInstruction(memo, [payer])) + + instructions.push( + await program.methods + .delegateDataCreditsV0({ + amount: new BN(amount, 0), + routerKey: delegateAddress, + }) + .accounts({ + subDao, + }) + .instruction(), + ) const { blockhash } = await connection.getLatestBlockhash() - tx.recentBlockhash = blockhash - tx.feePayer = payer + const transaction = new Transaction() - return tx + instructions.forEach((instruction) => { + transaction.add(instruction) + }) + + transaction.recentBlockhash = blockhash + transaction.feePayer = payer + + return transaction } catch (e) { Logger.error(e) throw e as Error diff --git a/yarn.lock b/yarn.lock index 83610c9a2..3dfabc494 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3813,6 +3813,13 @@ js-sha3 "^0.8.0" typescript-collections "^1.3.3" +"@solana/spl-memo@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@solana/spl-memo/-/spl-memo-0.2.3.tgz#594a28c37b40c0e22143f38f71b4f56d1f5b24fd" + integrity sha512-CNsKSsl85ebuVoeGq1LDYi5M/PMs1Pxv2/UsyTgS6b30qrYqZOXha5ouZzgGKtJtZ3C3dxfOAEw6caJPN1N63w== + dependencies: + buffer "^6.0.3" + "@solana/spl-token-registry@^0.2.4574": version "0.2.4574" resolved "https://registry.yarnpkg.com/@solana/spl-token-registry/-/spl-token-registry-0.2.4574.tgz#13f4636b7bec90d2bb43bbbb83512cd90d2ce257" From 3ff1c41bc71d6cabaa112646f658c99563621f05 Mon Sep 17 00:00:00 2001 From: bry Date: Wed, 16 Aug 2023 15:59:12 -0500 Subject: [PATCH 2/3] all for qr scanner to set memo --- src/features/burn/BurnScreen.tsx | 2 +- src/features/payment/PaymentQrScanner.tsx | 1 + src/hooks/useSubmitTxn.ts | 2 +- src/utils/solanaUtils.ts | 6 ++++-- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/features/burn/BurnScreen.tsx b/src/features/burn/BurnScreen.tsx index b76b647e9..fb8e173cb 100644 --- a/src/features/burn/BurnScreen.tsx +++ b/src/features/burn/BurnScreen.tsx @@ -94,12 +94,12 @@ const BurnScreen = () => { const [dcAmount, setDcAmount] = useState(new BN(route.params.amount)) const [submitError, setSubmitError] = useState(undefined) const [delegateAddress, setDelegateAddress] = useState(route.params.address) + const [memo, setMemo] = useState(route.params.memo) const [hasError, setHasError] = useState(false) const delegatePayment = useSelector( (reduxState: RootState) => reduxState.solana.delegate, ) const [mint, setMint] = useState(MOBILE_MINT) - const [memo, setMemo] = useState('') const { symbol } = useMetaplexMetadata(mint) const tokenSelectorRef = useRef(null) diff --git a/src/features/payment/PaymentQrScanner.tsx b/src/features/payment/PaymentQrScanner.tsx index fdcd87171..6ebf7da61 100644 --- a/src/features/payment/PaymentQrScanner.tsx +++ b/src/features/payment/PaymentQrScanner.tsx @@ -30,6 +30,7 @@ const PaymentQrScanner = () => { isDelegate: true, address: delegate.address, amount: delegate.amount, + memo: delegate.memo, }) } else { await showOKAlert({ diff --git a/src/hooks/useSubmitTxn.ts b/src/hooks/useSubmitTxn.ts index a466c3bf1..92b9c1ef1 100644 --- a/src/hooks/useSubmitTxn.ts +++ b/src/hooks/useSubmitTxn.ts @@ -390,7 +390,7 @@ export default () => { delegateAddress: string, amount: number, mint: PublicKey, - memo: string, + memo?: string, ) => { if (!currentAccount || !anchorProvider || !walletSignBottomSheetRef) { throw new Error(t('errors.account')) diff --git a/src/utils/solanaUtils.ts b/src/utils/solanaUtils.ts index 33e3e442b..319a7b169 100644 --- a/src/utils/solanaUtils.ts +++ b/src/utils/solanaUtils.ts @@ -655,7 +655,7 @@ export const delegateDataCredits = async ( delegateAddress: string, amount: number, mint: PublicKey, - memo: string, + memo?: string, ) => { try { const { connection } = anchorProvider @@ -666,7 +666,9 @@ export const delegateDataCredits = async ( const instructions: TransactionInstruction[] = [] - instructions.push(createMemoInstruction(memo, [payer])) + if (memo) { + instructions.push(createMemoInstruction(memo, [payer])) + } instructions.push( await program.methods From 981b4a977d9261c97db9c6d55e0e645962bf9abf Mon Sep 17 00:00:00 2001 From: bry Date: Wed, 16 Aug 2023 16:26:26 -0500 Subject: [PATCH 3/3] memo on delegate --- src/features/addressBook/ContactDetails.tsx | 287 ++++++++++---------- src/features/burn/BurnScreen.tsx | 1 - 2 files changed, 148 insertions(+), 140 deletions(-) diff --git a/src/features/addressBook/ContactDetails.tsx b/src/features/addressBook/ContactDetails.tsx index 8471fe956..91edb1b69 100644 --- a/src/features/addressBook/ContactDetails.tsx +++ b/src/features/addressBook/ContactDetails.tsx @@ -144,158 +144,167 @@ const ContactDetails = ({ action, contact }: Props) => { }, [address]) return ( - - - - - {isAddingContact ? t('addNewContact.title') : t('editContact.title')} - - - - - - - {addressIsValid && ( - - )} - {!!nickname && ( - - {nickname} - - )} - - - + + - - - {t('addNewContact.address.title', { - network: 'Solana', - })} - - + + {isAddingContact + ? t('addNewContact.title') + : t('editContact.title')} + + + - - - {t('addNewContact.nickname.title')} - {!!nickname && } - - - - + + {addressIsValid && ( + + )} + {!!nickname && ( + + {nickname} + + )} + + + - + + {t('addNewContact.address.title', { + network: 'Solana', + })} + + + + + + {t('addNewContact.nickname.title')} + {!!nickname && } + + - - - - + + + + + + + + ) } diff --git a/src/features/burn/BurnScreen.tsx b/src/features/burn/BurnScreen.tsx index fb8e173cb..1300fcee5 100644 --- a/src/features/burn/BurnScreen.tsx +++ b/src/features/burn/BurnScreen.tsx @@ -355,7 +355,6 @@ const BurnScreen = () => {