Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
78 changes: 67 additions & 11 deletions packages/common/src/store/purchase-content/sagas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ import {
TransactionMessage
} from '@solana/web3.js'
import BN from 'bn.js'
import bs58 from 'bs58'
import { sumBy } from 'lodash'
import { takeLatest } from 'redux-saga/effects'
import nacl, { BoxKeyPair } from 'tweetnacl'
import { call, put, race, select, take } from 'typed-redux-saga'

import { userTrackMetadataFromSDK } from '~/adapters'
Expand Down Expand Up @@ -66,6 +68,7 @@ import {
CoinflowPurchaseMetadata,
coinflowOnrampModalActions
} from '~/store/ui/modals/coinflow-onramp-modal'
import { waitForValue } from '~/utils'
import { encodeHashId } from '~/utils/hashIds'
import { BN_USDC_CENT_WEI } from '~/utils/wallet'

Expand Down Expand Up @@ -111,6 +114,23 @@ type GetPurchaseConfigArgs = {
contentType: PurchaseableContentType
}

const serializeKeyPair = (value: BoxKeyPair) => {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this code is avail in mobile, put it into common util

const { publicKey, secretKey } = value
const encodedKeyPair = {
publicKey: bs58.encode(publicKey),
secretKey: bs58.encode(secretKey)
}
return JSON.stringify(encodedKeyPair)
}

const deserializeKeyPair = (value: string): BoxKeyPair => {
const { publicKey, secretKey } = JSON.parse(value)
return {
publicKey: bs58.decode(publicKey),
secretKey: bs58.decode(secretKey)
}
}

function* getContentInfo({ contentId, contentType }: GetPurchaseConfigArgs) {
const metadata =
contentType === PurchaseableContentType.ALBUM
Expand Down Expand Up @@ -911,12 +931,30 @@ function* purchaseWithAnything({
throw new Error('Failed to fetch USDC user bank token account info')
}

// Get the solana wallet provider
const provider = window.solana
if (!provider) {
throw new Error('No solana provider / wallet found')
let sourceWallet: PublicKey

const isNativeMobile = yield* getContext('isNativeMobile')
const mobileWalletActions = yield* getContext('mobileWalletActions')
if (isNativeMobile && mobileWalletActions) {
const { connect } = mobileWalletActions
const getWalletConnectPublicKey = (state: any) =>

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the first gross thing -- common needs to reach into mobile store here. ew.

state.walletConnect.publicKey
const dappKeyPair = nacl.box.keyPair()

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should create this and persist it via redux-persist, so you only have to "connect" once

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this can be a follow up

yield* put({
type: 'walletConnect/setDappKeyPair',
payload: { dappKeyPair: serializeKeyPair(dappKeyPair) }
})
yield* call(connect, dappKeyPair)
yield* call(waitForValue, getWalletConnectPublicKey)
sourceWallet = new PublicKey(yield* select(getWalletConnectPublicKey))
} else {
// Get the solana wallet provider
const provider = window.solana
if (!provider) {
throw new Error('No solana provider / wallet found')
}
sourceWallet = (yield* call(provider.connect)).publicKey
}
const sourceWallet = yield* call(provider.connect)

let transaction: VersionedTransaction
if (inputMint === TOKEN_LISTING_MAP.USDC.address) {
Expand All @@ -926,15 +964,15 @@ function* purchaseWithAnything({
sdk.services.paymentRouterClient.createTransferInstruction
],
{
sourceWallet: sourceWallet.publicKey,
sourceWallet,
total: totalAmountWithDecimals,
mint: 'USDC'
}
)
transaction = yield* call(
[sdk.services.solanaClient, sdk.services.solanaClient.buildTransaction],
{
feePayer: sourceWallet.publicKey,
feePayer: sourceWallet,
instructions: [instruction]
}
)
Expand All @@ -950,7 +988,7 @@ function* purchaseWithAnything({
)
const externalTokenAccountPublicKey = getAssociatedTokenAddressSync(
new PublicKey(inputMint),
sourceWallet.publicKey
sourceWallet
)

console.info('Calling jupiter API to get a quote')
Expand Down Expand Up @@ -984,7 +1022,7 @@ function* purchaseWithAnything({
if (inputMint === TOKEN_LISTING_MAP.SOL.address) {
const amount = yield* call(
[connection, connection.getBalance],
sourceWallet.publicKey
sourceWallet
)
hasEnoughTokens = amount >= BigInt(quote.inAmount)
}
Expand All @@ -1000,7 +1038,7 @@ function* purchaseWithAnything({
const { swapTransaction } = yield* call([jup, jup.swapPost], {
swapRequest: {
quoteResponse: quote,
userPublicKey: sourceWallet.publicKey.toString(),
userPublicKey: sourceWallet.toString(),
destinationTokenAccount: paymentRouterTokenAccount.address.toString()
}
})
Expand Down Expand Up @@ -1076,7 +1114,25 @@ function* purchaseWithAnything({
transaction.message = message.compileToV0Message(addressLookupTableAccounts)

// Execute the swap by signing and sending the transaction
return yield* call([provider, provider.signAndSendTransaction], transaction)
if (isNativeMobile && mobileWalletActions) {
const { signAndSendTransaction } = mobileWalletActions
const getWalletConnectState = (state: any) => state.walletConnect
const { dappKeyPair, sharedSecret, session } = yield* select(
getWalletConnectState
)
return yield* call(signAndSendTransaction, {
transaction,
dappKeyPair: deserializeKeyPair(dappKeyPair),
sharedSecret: new Uint8Array(bs58.decode(sharedSecret)),
session
})
} else {
const provider = window.solana
return yield* call(
[provider, provider.signAndSendTransaction],
transaction
)
}
} catch (e) {
console.error(`handlePayWithAnything | Error: ${e}`)
throw e
Expand Down
11 changes: 11 additions & 0 deletions packages/common/src/store/storeContext.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { FetchNFTClient } from '@audius/fetch-nft'
import type { AudiusSdk } from '@audius/sdk'
import { VersionedTransaction } from '@solana/web3.js'
import { Location } from 'history'
import { Dispatch } from 'redux'
import nacl from 'tweetnacl'

import {
AllTrackingEvents,
Expand Down Expand Up @@ -81,4 +83,13 @@ export type CommonStoreContext = {
}
isMobile: boolean
dispatch: Dispatch<any>
mobileWalletActions?: {
connect: (dappKeyPair: nacl.BoxKeyPair) => void
signAndSendTransaction: (params: {
transaction: VersionedTransaction
session: string
sharedSecret: Uint8Array
dappKeyPair: nacl.BoxKeyPair
}) => void
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,14 @@ const NavigationContainer = (props: NavigationContainerProps) => {

path = path.replace('#embed', '')

const connectPath = /^\/(connect)/
if (path.match(connectPath)) {
path = `${path.replace(
connectPath,
routeNameRef.current ?? '/feed'
)}&path=connect`
}

const walletConnectPath = /^\/(wallet-connect)/
if (path.match(walletConnectPath)) {
path = `${path.replace(
Expand Down
51 changes: 37 additions & 14 deletions packages/mobile/src/components/payment-method/PaymentMethod.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ import {
Text,
Flex,
IconQrCode,
IconPhantomPlain
IconPhantomPlain,
IconCaretRight
} from '@audius/harmony-native'
import { Divider, RadioButton } from 'app/components/core'
import { useNavigation } from 'app/hooks/useNavigation'
import { getPurchaseVendor } from 'app/store/purchase-vendor/selectors'
import { setPurchaseVendor } from 'app/store/purchase-vendor/slice'
import { flexRowCentered, makeStyles } from 'app/styles'
Expand All @@ -39,7 +41,7 @@ const messages = {
withCard: 'Credit/Debit Card',
withCrypto: 'USDC Transfer',
withAnything: 'Pay with Anything',
requiresPhantom: 'Phantom wallet required',
withAnyToken: 'Pay with any Solana token',
showAdvanced: 'Show advanced options',
hideAdvanced: 'Hide advanced options'
}
Expand All @@ -51,6 +53,7 @@ const useStyles = makeStyles(({ spacing }) => ({
},
rowTitle: {
...flexRowCentered(),
alignItems: 'flex-start',
gap: spacing(3)
},
rowTitleText: {
Expand Down Expand Up @@ -177,6 +180,9 @@ export const PaymentMethod = ({
icon: IconQrCode
}
]

const navigation = useNavigation()

if (
isPayWithAnythingEnabled &&
selectedPurchaseMethodMintAddress &&
Expand All @@ -186,20 +192,37 @@ export const PaymentMethod = ({
id: PurchaseMethod.WALLET,
value: PurchaseMethod.WALLET,
label: (
<Flex flex={1}>
<Flex flex={1} gap='m'>
<Flex direction='row' justifyContent='space-between'>
<Flex direction='row' gap='xs' alignItems='center'>
<Text>{messages.withAnything}</Text>
</Flex>
<TokenPicker
selectedTokenAddress={selectedPurchaseMethodMintAddress}
onChange={setSelectedPurchaseMethodMintAddress}
onOpen={handleOpenTokenPicker}
/>
<Text>{messages.withAnything}</Text>
</Flex>
<Text size='xs' color='subdued'>
{messages.requiresPhantom}
</Text>
{selectedMethod === PurchaseMethod.WALLET ? (
<TouchableOpacity
onPress={() => {
navigation.navigate('TokenPicker')
handleOpenTokenPicker()
}}
hitSlop={spacing(6)}
>
<Flex gap='s'>
<Flex
direction='row'
justifyContent='space-between'
alignItems='center'
>
<Text strength='strong'>{messages.withAnyToken}</Text>
<IconCaretRight color='subdued' size='m' />
</Flex>
<Flex direction='row' justifyContent='space-between'>
<TokenPicker
selectedTokenAddress={selectedPurchaseMethodMintAddress}
onChange={setSelectedPurchaseMethodMintAddress}
onOpen={handleOpenTokenPicker}
/>
</Flex>
</Flex>
</TouchableOpacity>
) : null}
</Flex>
),
icon: IconPhantomPlain
Expand Down
17 changes: 11 additions & 6 deletions packages/mobile/src/components/payment-method/TokenPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ export const TokenPicker = ({
onChange: (address: string) => void
onOpen: () => void
}) => {
const [selectedAddress, setSelectedAddress] = useState(selectedTokenAddress)
const [selectedAddress, setSelectedAddress] = useState<string | undefined>(
selectedTokenAddress
)

const assets = useAsync(async () => {
const res = await fetch(TOKEN_LIST_URL)
Expand All @@ -40,7 +42,9 @@ export const TokenPicker = ({
}, [])

const handleSubmit = useCallback(() => {
onChange(selectedAddress)
if (selectedAddress) {
onChange(selectedAddress)
}
}, [onChange, selectedAddress])

const navigation = useNavigation()
Expand Down Expand Up @@ -69,8 +73,8 @@ export const TokenPicker = ({
)

const selectedOption = useMemo(
() => options.find((option) => option.value === selectedTokenAddress),
[selectedTokenAddress, options]
() => options.find((option) => option.value === selectedAddress),
[selectedAddress, options]
)

if (assets.loading || assets.error) {
Expand Down Expand Up @@ -107,9 +111,10 @@ export const TokenPicker = ({
/>
<Portal hostName='TokenPickerPortal'>
<ListSelectionScreen
value={selectedAddress}
value={selectedAddress ?? ''}
data={options}
itemContentStyles={{ flexGrow: 1 }}
searchText='Search for tokens'
renderItem={({ item }) => {
const asset = optionsMap[item.value]
return (
Expand All @@ -132,7 +137,7 @@ export const TokenPicker = ({
numberOfLines={1}
ellipsizeMode='tail'
>
{asset.name}
{`(${asset.name})`}
</Text>
</Flex>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -248,11 +248,9 @@ const RenderForm = ({
const { isEnabled: isIOSUSDCPurchaseEnabled } = useFeatureFlag(
FeatureFlags.IOS_USDC_PURCHASE_ENABLED
)
// TODO Re-enable pay with anything (mobile) when fully working
// const { isEnabled: isPayWithAnythingEnabledFlag } = useFeatureFlag(
// FeatureFlags.PAY_WITH_ANYTHING_ENABLED
// )
const isPayWithAnythingEnabledFlag = false
const { isEnabled: isPayWithAnythingEnabledFlag } = useFeatureFlag(
FeatureFlags.PAY_WITH_ANYTHING_ENABLED
)
const isIOSDisabled = Platform.OS === 'ios' && !isIOSUSDCPurchaseEnabled

const { submitForm, resetForm } = useFormikContext()
Expand Down
3 changes: 3 additions & 0 deletions packages/mobile/src/screens/app-screen/AppTabsScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'
import type { NavigatorScreenParams } from '@react-navigation/native'
import { useDispatch } from 'react-redux'

import { usePhantomConnect } from '../wallet-connect/usePhantomConnect'

import { AppTabBar } from './AppTabBar'
import type { ExploreTabScreenParamList } from './ExploreTabScreen'
import { ExploreTabScreen } from './ExploreTabScreen'
Expand Down Expand Up @@ -36,6 +38,7 @@ const tabBar = (props: BottomTabBarProps) => <AppTabBar {...props} />
export const AppTabsScreen = () => {
const dispatch = useDispatch()
const appState = useAppState()
usePhantomConnect()

useEffect(() => {
if (appState === 'active') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { Button } from '@audius/harmony-native'
import { makeStyles } from 'app/styles'

import { useCanConnectNewWallet } from './useCanConnectNewWallet'
import { usePhantomConnect } from './usePhantomConnect'
import { useWalletConnect } from './useWalletConnect'

const { getConfirmingWalletStatus, getRemoveWallet } =
Expand All @@ -29,7 +28,6 @@ const useStyles = makeStyles(({ spacing }) => ({
export const ConnectNewWalletButton = () => {
const styles = useStyles()
const { connector } = useWalletConnect()
usePhantomConnect()

const newWalletStatus = useSelector(getConfirmingWalletStatus)
const { status: removeWalletStatus } = useSelector(getRemoveWallet)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ import { useAppScreenOptions } from '../app-screen/useAppScreenOptions'

import { WalletConnectScreen } from './WalletConnectScreen'
import { ConfirmRemoveWalletDrawer, WalletsDrawer } from './components'
import { usePhantomConnect } from './usePhantomConnect'

const Stack = createNativeStackNavigator()

const screenOptionOverrides = { headerRight: () => null }

export const WalletConnectModalScreen = () => {
const screenOptions = useAppScreenOptions(screenOptionOverrides)
usePhantomConnect()

return (
<ModalScreen>
Expand Down
Loading