Skip to content
This repository has been archived by the owner on Oct 4, 2023. It is now read-only.

Commit

Permalink
[PAY-598] Prepopulate TransactionDetails after purchasing $AUDIO (#1832)
Browse files Browse the repository at this point in the history
* Stricter type enforcement

* Wire up modal opening on success page

* handle new stricter types

* Wire up TransactionDetails in saga ...

- Wire up transaction details at the end of the purchase saga
- Update fee calculation to use fancy helper method on Transaction
- Add default fee if fee calculation fails of 5000 Lamports
- Replace u64 with BN

* this one needs to be a u64 sadface

* Use actual AUDIO amount on success page

* Use actual AUDIO balance on success page

* Close on X clicked

* Dismiss BuyAudio modal on review transaction clicked

* refactor swap to use an async method and try/finally correctly, and save setup/cleanup transactions as well

* Save the transaction metadata to identity

* comment out libs stuff for now (needs change)

* Update links to explorer links

* lint fix
  • Loading branch information
rickyrombo committed Sep 1, 2022
1 parent b11a686 commit 3c90d3f
Show file tree
Hide file tree
Showing 8 changed files with 327 additions and 178 deletions.
48 changes: 37 additions & 11 deletions packages/common/src/store/ui/transaction-details/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@ export enum TransactionType {
}

export enum TransactionMethod {
// Transfer methods
SEND = 'SENT',
RECEIVE = 'RECEIVED',

// Purchase Methods
COINBASE = 'COINBASE',
STRIPE = 'STRIPE'
}
Expand All @@ -24,19 +27,42 @@ export type InAppAudioPurchaseMetadata = {
usd: string
sol: string
audio: StringAudio
swapTransaction: string
buyTransaction: string
purchaseTransactionId: string
setupTransactionId?: string
swapTransactionId: string
cleanupTransactionId?: string
}

export type TransactionDetails = {
signature: string
transactionType: TransactionType
method: TransactionMethod
date: string
change: StringAudio
balance: StringAudio
metadata: InAppAudioPurchaseMetadata | undefined
}
export type TransactionDetails =
| {
signature: string
transactionType: TransactionType.PURCHASE
method: TransactionMethod.COINBASE | TransactionMethod.STRIPE
date: string
change: StringAudio
balance: StringAudio
metadata: InAppAudioPurchaseMetadata
}
| {
signature: string
transactionType: TransactionType.TIP | TransactionType.TRANSFER
method: TransactionMethod.SEND | TransactionMethod.RECEIVE
date: string
change: StringAudio
balance: StringAudio
metadata?: {}
}
| {
signature: string
transactionType:
| TransactionType.CHALLENGE_REWARD
| TransactionType.TRENDING_REWARD
method: TransactionMethod.RECEIVE
date: string
change: StringAudio
balance: StringAudio
metadata: InAppAudioPurchaseMetadata | undefined
}

export type TransactionDetailsState =
| { status: Status.IDLE }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import { useCallback } from 'react'

import {
BNAudio,
BNWei,
buyAudioSelectors,
formatWei,
walletSelectors
Status,
transactionDetailsSelectors,
formatNumberString
} from '@audius/common'
import { Button, ButtonSize, ButtonType, IconInfo } from '@audius/stems'
import BN from 'bn.js'

import { useModalState } from 'common/hooks/useModalState'
import { useSelector } from 'common/hooks/useSelector'
Expand All @@ -23,19 +20,23 @@ const messages = {
done: 'Done'
}

const { getAccountTotalBalance } = walletSelectors
const { getAudioPurchaseInfo } = buyAudioSelectors
const { getTransactionDetails } = transactionDetailsSelectors

export const SuccessPage = () => {
const totalBalance = useSelector<BNAudio>(getAccountTotalBalance)
const uiBalance = formatWei(totalBalance || (new BN(0) as BNWei), true, 0)
const purchaseInfo = useSelector(getAudioPurchaseInfo)
const transactionDetails = useSelector(getTransactionDetails)
const [, setModalVisibility] = useModalState('BuyAudio')
const [, setTransactionDetailsModalVisibility] =
useModalState('TransactionDetails')

const handleDoneClicked = useCallback(() => {
setModalVisibility(false)
}, [setModalVisibility])

const handleReviewTransactionClicked = useCallback(() => {
setTransactionDetailsModalVisibility(true)
setModalVisibility(false)
}, [setModalVisibility, setTransactionDetailsModalVisibility])

return (
<div className={styles.successPage}>
<div className={styles.message}>{messages.successMessage}</div>
Expand All @@ -45,13 +46,21 @@ export const SuccessPage = () => {
<span className={styles.label}>{messages.audio}</span>
<span>
+
{purchaseInfo?.isError === false
? purchaseInfo.desiredAudioAmount.uiAmountString
{transactionDetails.status === Status.SUCCESS
? formatNumberString(
transactionDetails.transactionDetails.change,
{ maxDecimals: 0 }
)
: '0'}
</span>
</div>
<div className={styles.newBalance}>
{uiBalance}
{transactionDetails.status === Status.SUCCESS
? formatNumberString(
transactionDetails.transactionDetails.balance,
{ maxDecimals: 0 }
)
: '0'}
<span className={styles.label}>{messages.audio}</span>
</div>
</div>
Expand All @@ -67,6 +76,7 @@ export const SuccessPage = () => {
size={ButtonSize.SMALL}
text={messages.review}
leftIcon={<IconInfo />}
onClick={handleReviewTransactionClicked}
/>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export const TransactionDetailsModal = () => {

return (
<Modal isOpen={isOpen} onClose={handleClose} bodyClassName={styles.root}>
<ModalHeader>
<ModalHeader onClose={handleClose}>
<ModalTitle
title={messages.transactionDetails}
icon={<IconTransaction />}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ const messages = {
date: 'Date',
change: 'Change ($AUDIO)',
balance: 'Balance ($AUDIO)',
purchaseDescription: 'Purchased $AUDIO'
purchaseDescription: 'Purchased $AUDIO',
unknown: 'Unknown'
}

const AppLogo = () => (
Expand Down Expand Up @@ -62,18 +63,19 @@ export const TransactionDetailsContent = ({
{typeIconMap[transactionDetails.transactionType]}
</div>
</div>
{transactionDetails.metadata ? (
{transactionDetails.transactionType === TransactionType.PURCHASE ? (
<TransactionPurchaseMetadata metadata={transactionDetails.metadata} />
) : null}

{transactionDetails.method === TransactionMethod.COINBASE ? (
{transactionDetails.transactionType === TransactionType.PURCHASE ? (
<Block className={styles.method} header={messages.method}>
<LogoCoinbase />
</Block>
) : null}
{transactionDetails.method === TransactionMethod.STRIPE ? (
<Block className={styles.method} header={messages.method}>
<LogoStripe />
{transactionDetails.method === TransactionMethod.COINBASE ? (
<LogoCoinbase />
) : transactionDetails.method === TransactionMethod.STRIPE ? (
<LogoStripe />
) : (
messages.unknown
)}
</Block>
) : null}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const messages = {
cost: 'Cost',
purchased: 'Purchased',
convertedTo: 'Converted To',
viewOnSolScan: 'View on Solscan'
viewOnExplorer: 'View on Solana Explorer'
}

export const TransactionPurchaseMetadata = ({
Expand All @@ -36,9 +36,9 @@ export const TransactionPurchaseMetadata = ({
<IconButton
className={styles.iconButton}
icon={<IconExternalLink />}
title={messages.viewOnSolScan}
aria-label={messages.viewOnSolScan}
href={`https://solscan.io/tx/${metadata.buyTransaction}`}
title={messages.viewOnExplorer}
aria-label={messages.viewOnExplorer}
href={`https://explorer.solana.com/tx/${metadata.purchaseTransactionId}`}
target='_blank'
/>
</>
Expand All @@ -54,9 +54,9 @@ export const TransactionPurchaseMetadata = ({
<IconButton
className={styles.iconButton}
icon={<IconExternalLink />}
title={messages.viewOnSolScan}
aria-label={messages.viewOnSolScan}
href={`https://solscan.io/tx/${metadata.swapTransaction}`}
title={messages.viewOnExplorer}
aria-label={messages.viewOnExplorer}
href={`https://explorer.solana.com/tx/${metadata.swapTransactionId}`}
target='_blank'
/>
</>
Expand Down
31 changes: 19 additions & 12 deletions packages/web/src/services/audius-backend/BuyAudio.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { InAppAudioPurchaseMetadata } from '@audius/common'
import { AudiusLibs } from '@audius/sdk'
import {
AccountInfo,
ASSOCIATED_TOKEN_PROGRAM_ID,
Token,
TOKEN_PROGRAM_ID,
u64
} from '@solana/spl-token'
import {
Connection,
Keypair,
LAMPORTS_PER_SOL,
PublicKey,
Expand All @@ -16,7 +16,7 @@ import {

import { waitForLibsInit } from 'services/audius-backend/eagerLoadUtils'
// @ts-ignore
const libs = () => window.audiusLibs
const libs = (): AudiusLibs => window.audiusLibs

const TOKEN_ACCOUNT_POLL_MS = 5000
const MAX_TOKEN_ACCOUNT_POLL_COUNT = 20
Expand All @@ -38,14 +38,14 @@ const delay = (ms: number) =>

export const getRootSolanaAccount = async () => {
await waitForLibsInit()
return libs().solanaWeb3Manager.solanaWeb3.Keypair.fromSeed(
libs().Account.hedgehog.wallet.getPrivateKey()
) as Keypair
return libs().solanaWeb3Manager!.solanaWeb3.Keypair.fromSeed(
libs().Account!.hedgehog.wallet!.getPrivateKey()
)
}

export const getSolanaConnection = async () => {
await waitForLibsInit()
return libs().solanaWeb3Manager.connection as Connection
return libs().solanaWeb3Manager!.connection
}

export const getRootAccountRentExemptionMinimum = async () => {
Expand Down Expand Up @@ -103,9 +103,9 @@ export const getAudioAccount = async ({
rootAccount: PublicKey
}) => {
await waitForLibsInit()
return (await libs().solanaWeb3Manager.findAssociatedTokenAddress(
return await libs().solanaWeb3Manager!.findAssociatedTokenAddress(
rootAccount.toString()
)) as PublicKey
)
}

export const getAudioAccountInfo = async ({
Expand All @@ -114,9 +114,9 @@ export const getAudioAccountInfo = async ({
tokenAccount: PublicKey
}) => {
await waitForLibsInit()
const tokenAccountInfo: AccountInfo | null =
await libs().solanaWeb3Manager.getTokenAccountInfo(tokenAccount.toString())
return tokenAccountInfo
return await libs().solanaWeb3Manager!.getTokenAccountInfo(
tokenAccount.toString()
)
}

export const pollForAudioBalanceChange = async ({
Expand Down Expand Up @@ -242,3 +242,10 @@ export const createTransferToUserBankTransaction = async ({
tx.add(transferInstruction)
return tx
}

export const saveUserBankTransactionMetadata = async (
data: InAppAudioPurchaseMetadata
) => {
await waitForLibsInit()
// return await libs().identityService!.saveUserBankTransactionMetadata(data)
}

0 comments on commit 3c90d3f

Please sign in to comment.