Skip to content

Commit

Permalink
Merge pull request #12939 from brave/add-send-spl-tokens-in-ui
Browse files Browse the repository at this point in the history
feat(wallet): Add and Send SPL tokens in UI
  • Loading branch information
Douglashdaniel authored Apr 8, 2022
2 parents 12c661f + c8aa277 commit 6042ab5
Show file tree
Hide file tree
Showing 14 changed files with 193 additions and 49 deletions.
4 changes: 3 additions & 1 deletion components/brave_wallet_ui/common/actions/wallet_actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ import {
DefaultCurrencies,
GetPriceReturnInfo,
OriginInfo,
SolFeeEstimates
SolFeeEstimates,
SPLTransferFromParams
} from '../../constants/types'

export const initialize = createAction('initialize')
Expand Down Expand Up @@ -79,6 +80,7 @@ export const selectPortfolioTimeline = createAction<BraveWallet.AssetPriceTimefr
export const portfolioTimelineUpdated = createAction<BraveWallet.AssetPriceTimeframe>('portfolioTimelineUpdated')
export const sendTransaction = createAction<SendTransactionParams>('sendTransaction')
export const sendERC20Transfer = createAction<ER20TransferParams>('sendERC20Transfer')
export const sendSPLTransfer = createAction<SPLTransferFromParams>('sendSPLTransfer')
export const sendERC721TransferFrom = createAction<ERC721TransferFromParams>('sendERC721TransferFrom')
export const approveERC20Allowance = createAction<ApproveERC20Params>('approveERC20Allowance')
export const newUnapprovedTxAdded = createAction<NewUnapprovedTxAdded>('newUnapprovedTxAdded')
Expand Down
20 changes: 17 additions & 3 deletions components/brave_wallet_ui/common/async/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ import {
TransactionProviderError,
SupportedCoinTypes,
SendFilTransactionParams,
SendSolTransactionParams
SendSolTransactionParams,
SPLTransferFromParams
} from '../../constants/types'

// Utils
Expand All @@ -45,7 +46,8 @@ import {
refreshPrices,
sendEthTransaction,
sendFilTransaction,
sendSolTransaction
sendSolTransaction,
sendSPLTransaction
} from './lib'
import { Store } from './types'
import InteractionNotifier from './interactionNotifier'
Expand Down Expand Up @@ -327,6 +329,17 @@ handler.on(WalletActions.sendTransaction.getType(), async (store: Store, payload
await store.dispatch(refreshTransactionHistory(payload.from))
})

handler.on(WalletActions.sendSPLTransfer.getType(), async (store: Store, payload: SPLTransferFromParams) => {
const { solanaTxManagerProxy } = getAPIProxy()
const value = await solanaTxManagerProxy.makeTokenProgramTransferTxData(payload.splTokenMintAddress, payload.from, payload.to, BigInt(payload.value))
if (!value.txData) {
console.log('Failed making SPL transfer data, to: ', payload.to, ', value: ', payload.value)
return
}
await sendSPLTransaction(value.txData)
await store.dispatch(refreshTransactionHistory(payload.from))
})

handler.on(WalletActions.sendERC20Transfer.getType(), async (store: Store, payload: ER20TransferParams) => {
const apiProxy = getAPIProxy()
const { data, success } = await apiProxy.ethTxManagerProxy.makeERC20TransferData(payload.to, payload.value)
Expand Down Expand Up @@ -432,7 +445,8 @@ handler.on(WalletActions.refreshGasEstimates.getType(), async (store: Store, txI

if (
txInfo.txType === BraveWallet.TransactionType.SolanaSystemTransfer ||
txInfo.txType === BraveWallet.TransactionType.SolanaSPLTokenTransfer
txInfo.txType === BraveWallet.TransactionType.SolanaSPLTokenTransfer ||
txInfo.txType === BraveWallet.TransactionType.SolanaSPLTokenTransferWithAssociatedTokenAccountCreation
) {
const getSolFee = await solanaTxManagerProxy.getEstimatedTxFee(txInfo.id)
if (!getSolFee.fee) {
Expand Down
33 changes: 27 additions & 6 deletions components/brave_wallet_ui/common/async/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,21 +280,36 @@ export function refreshBalances () {
const visibleTokens = userVisibleTokensInfo.filter(asset => asset.contractAddress !== '')

const getBlockchainTokensBalanceReturnInfos = await Promise.all(accounts.map(async (account) => {
const networks = getNetworksByCoinType(networkList, account.coin)
if (account.coin === BraveWallet.CoinType.ETH) {
return Promise.all(visibleTokens.map(async (token) => {
if (token.isErc721) {
return jsonRpcService.getERC721TokenBalance(token.contractAddress, token.tokenId ?? '', account.address, token?.chainId ?? '')
if (networks.some(n => n.chainId === token.chainId)) {
if (token.isErc721) {
return jsonRpcService.getERC721TokenBalance(token.contractAddress, token.tokenId ?? '', account.address, token?.chainId ?? '')
}
return jsonRpcService.getERC20TokenBalance(token.contractAddress, account.address, token?.chainId ?? '')
}
return emptyBalance
}))
} else if (account.coin === BraveWallet.CoinType.SOL) {
return Promise.all(visibleTokens.map(async (token) => {
if (networks.some(n => n.chainId === token.chainId)) {
const getSolTokenBalance = await jsonRpcService.getSPLTokenAccountBalance(account.address, token.contractAddress, token.chainId)
return {
balance: getSolTokenBalance.amount,
error: getSolTokenBalance.error,
errorMessage: getSolTokenBalance.errorMessage
}
}
return jsonRpcService.getERC20TokenBalance(token.contractAddress, account.address, token?.chainId ?? '')
return emptyBalance
}))
} else {
// MULTICHAIN: We do not yet support getting
// token balances for SOL and FIL
// token balances for FIL
// Will be implemented here https://github.com/brave/brave-browser/issues/21695
return []
}
}))

await dispatch(WalletActions.tokenBalancesUpdated({
balances: getBlockchainTokensBalanceReturnInfos
}))
Expand Down Expand Up @@ -336,7 +351,7 @@ export function refreshPrices () {
}

// If a tokens balance is 0 we do not make an unnecessary api call for the price of that token
const price = token.balance > 0 && token.token.isErc20
const price = token.balance > 0 && !token.token.isErc721
? await assetRatioService.getPrice([getTokenParam(token.token)], [defaultFiatCurrency], selectedPortfolioTimeline)
: { values: [{ ...emptyPrice, price: '0' }], success: true }

Expand Down Expand Up @@ -655,3 +670,9 @@ export async function sendSolTransaction (payload: SendSolTransactionParams) {
// @ts-expect-error google closure is ok with undefined for other fields but mojom runtime is not
return await txService.addUnapprovedTransaction({ solanaTxData: value.txData }, payload.from)
}

export async function sendSPLTransaction (payload: BraveWallet.SolanaTxData) {
const { txService } = getAPIProxy()
// @ts-expect-error google closure is ok with undefined for other fields but mojom runtime is not
return await txService.addUnapprovedTransaction({ solanaTxData: payload }, payload.feePayer)
}
6 changes: 5 additions & 1 deletion components/brave_wallet_ui/common/hooks/explorer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,13 @@ export default function useExplorer (network: BraveWallet.NetworkInfo) {
return
}

const explorerIndex = explorerURL.lastIndexOf('?')

const url = type === 'contract'
? `${explorerURL}/${value}?a=${new Amount(id ?? '').format()}`
: `${explorerURL}/${type}/${value}`
: (network.chainId === BraveWallet.SOLANA_TESTNET || network.chainId === BraveWallet.SOLANA_DEVNET)
? `${explorerURL.substring(0, explorerIndex)}/${type}/${value}${explorerURL.substring(explorerIndex)}`
: `${explorerURL}/${type}/${value}`

if (!chrome.tabs) {
window.open(url, '_blank')
Expand Down
19 changes: 19 additions & 0 deletions components/brave_wallet_ui/common/hooks/send.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,25 @@ export default function useSend () {
coin: selectedAccount.coin
}))

if (
selectedAccount.coin === BraveWallet.CoinType.SOL &&
selectedSendAsset.contractAddress !== '' &&
!selectedSendAsset.isErc20 && !selectedSendAsset.isErc721
) {
dispatch(WalletActions.sendSPLTransfer({
from: selectedAccount.address,
to: toAddress,
value: new Amount(sendAmount)
.multiplyByDecimals(selectedSendAsset.decimals)
.toHex(),
coin: selectedAccount.coin,
splTokenMintAddress: selectedSendAsset.contractAddress
}))
setToAddressOrUrl('')
setSendAmount('')
return
}

if (selectedSendAsset.isErc721 || selectedSendAsset.isErc20) { return }

dispatch(WalletActions.sendTransaction({
Expand Down
72 changes: 66 additions & 6 deletions components/brave_wallet_ui/common/hooks/transaction-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,14 +88,17 @@ export function useTransactionFeesParser (selectedNetwork: BraveWallet.NetworkIn

return React.useCallback((transactionInfo: BraveWallet.TransactionInfo): ParsedTransactionFees => {
const { txDataUnion: { ethTxData1559: txData }, txType } = transactionInfo
const isSolTransaction = txType === BraveWallet.TransactionType.SolanaSystemTransfer
const isSolTransaction =
txType === BraveWallet.TransactionType.SolanaSystemTransfer ||
txType === BraveWallet.TransactionType.SolanaSPLTokenTransfer ||
txType === BraveWallet.TransactionType.SolanaSPLTokenTransferWithAssociatedTokenAccountCreation
const gasLimit = txData?.baseData.gasLimit || ''
const gasPrice = txData?.baseData.gasPrice || ''
const maxFeePerGas = txData?.maxFeePerGas || ''
const maxPriorityFeePerGas = txData?.maxPriorityFeePerGas || ''
const isEIP1559Transaction = maxPriorityFeePerGas !== '' && maxFeePerGas !== ''
const gasFee = isSolTransaction
? solFeeEstimates?.fee.toString() ?? ''
? new Amount(solFeeEstimates?.fee.toString() ?? '').format()
: isEIP1559Transaction
? new Amount(maxFeePerGas)
.times(gasLimit)
Expand Down Expand Up @@ -192,12 +195,23 @@ export function useTransactionParser (

return React.useCallback((transactionInfo: BraveWallet.TransactionInfo) => {
const { txArgs, txDataUnion: { ethTxData1559: txData, solanaTxData: solTxData }, fromAddress, txType } = transactionInfo
const isSolTransaction = txType === BraveWallet.TransactionType.SolanaSystemTransfer
const value = isSolTransaction ? solTxData?.lamports.toString() ?? '' : txData?.baseData.value || ''
const to = isSolTransaction ? solTxData?.toWalletAddress ?? '' : txData?.baseData.to || ''
const isSPLTransaction =
txType === BraveWallet.TransactionType.SolanaSPLTokenTransfer ||
txType === BraveWallet.TransactionType.SolanaSPLTokenTransferWithAssociatedTokenAccountCreation
const isSolTransaction =
txType === BraveWallet.TransactionType.SolanaSystemTransfer ||
isSPLTransaction
const value = isSPLTransaction
? solTxData?.amount.toString() ?? ''
: isSolTransaction
? solTxData?.lamports.toString() ?? ''
: txData?.baseData.value || ''
const to = isSolTransaction
? solTxData?.toWalletAddress ?? ''
: txData?.baseData.to || ''
const nonce = txData?.baseData.nonce || ''
const account = accounts.find((account) => account.address.toLowerCase() === fromAddress.toLowerCase())
const token = findToken(to)
const token = isSPLTransaction ? findToken(solTxData?.splTokenMintAddress ?? '') : findToken(to)
const accountNativeBalance = getBalance(account, nativeAsset)
const accountTokenBalance = getBalance(account, token)

Expand Down Expand Up @@ -333,6 +347,52 @@ export function useTransactionParser (
} as ParsedTransaction
}

case txType === BraveWallet.TransactionType.SolanaSPLTokenTransfer:
case txType === BraveWallet.TransactionType.SolanaSPLTokenTransferWithAssociatedTokenAccountCreation: {
const price = findAssetPrice(token?.symbol ?? '')
const sendAmountFiat = new Amount(value)
.divideByDecimals(token?.decimals ?? 9)
.times(price)

const feeDetails = parseTransactionFees(transactionInfo)
const { gasFeeFiat, gasFee } = feeDetails
const totalAmountFiat = new Amount(gasFeeFiat)
.plus(sendAmountFiat)

const insufficientNativeFunds = new Amount(gasFee)
.gt(accountNativeBalance)
const insufficientTokenFunds = new Amount(value)
.gt(accountTokenBalance)

return {
hash: transactionInfo.txHash,
nonce,
createdTime: transactionInfo.createdTime,
status: transactionInfo.txStatus,
sender: fromAddress,
senderLabel: getAddressLabel(fromAddress),
recipient: to,
recipientLabel: getAddressLabel(to),
fiatValue: sendAmountFiat,
fiatTotal: totalAmountFiat,
formattedNativeCurrencyTotal: sendAmountFiat
.div(networkSpotPrice)
.formatAsAsset(6, selectedNetwork.symbol),
value: new Amount(value)
.divideByDecimals(token?.decimals ?? 9)
.format(6),
valueExact: new Amount(value)
.divideByDecimals(token?.decimals ?? 9)
.format(),
symbol: token?.symbol ?? '',
decimals: token?.decimals ?? 9,
insufficientFundsError: insufficientNativeFunds || insufficientTokenFunds,
contractAddressError: checkForContractAddressError(solTxData?.toWalletAddress ?? ''),
sameAddressError: checkForSameAddressError(solTxData?.toWalletAddress ?? '', fromAddress),
...feeDetails
} as ParsedTransaction
}

// FIXME: swap needs a real parser to figure out the From and To details.
case to.toLowerCase() === SwapExchangeProxy:
case txType === BraveWallet.TransactionType.ETHSend:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,10 @@ export const usePendingTransactions = () => {
const isERC20Approve = transactionInfo?.txType === BraveWallet.TransactionType.ERC20Approve
const isERC721SafeTransferFrom = transactionInfo?.txType === BraveWallet.TransactionType.ERC721SafeTransferFrom
const isERC721TransferFrom = transactionInfo?.txType === BraveWallet.TransactionType.ERC721TransferFrom
const isSolanaSystemTransfer = transactionInfo?.txType === BraveWallet.TransactionType.SolanaSystemTransfer
const isSolanaTransaction =
transactionInfo?.txType === BraveWallet.TransactionType.SolanaSystemTransfer ||
transactionInfo?.txType === BraveWallet.TransactionType.SolanaSPLTokenTransfer ||
transactionInfo?.txType === BraveWallet.TransactionType.SolanaSPLTokenTransferWithAssociatedTokenAccountCreation

// methods
const findAccountName = (address: string) => {
Expand Down Expand Up @@ -219,7 +222,7 @@ export const usePendingTransactions = () => {
isERC20Approve,
isERC721SafeTransferFrom,
isERC721TransferFrom,
isSolanaSystemTransfer,
isSolanaTransaction,
onEditAllowanceSave,
queueNextTransaction,
rejectAllTransactions,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -489,13 +489,17 @@ const EditVisibleAssetsModal = (props: Props) => {
value={coingeckoID}
onChange={handleCoingeckoIDChanged}
/>
<InputLabel>{getLocale('braveWalletWatchListTokenId')}</InputLabel>
<Input
value={tokenID}
onChange={handleTokenIDChanged}
type='number'
disabled={Number(tokenDecimals) > 0}
/>
{customAssetsNetwork.coin !== BraveWallet.CoinType.SOL &&
<>
<InputLabel>{getLocale('braveWalletWatchListTokenId')}</InputLabel>
<Input
value={tokenID}
onChange={handleTokenIDChanged}
type='number'
disabled={Number(tokenDecimals) > 0}
/>
</>
}
{showTokenIDRequired &&
<ErrorText>{getLocale('braveWalletWatchListTokenIdError')}</ErrorText>
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,11 @@ const PortfolioTransactionItem = (props: Props) => {
}
}

const isSolanaTransaction =
transaction.txType === BraveWallet.TransactionType.SolanaSystemTransfer ||
transaction.txType === BraveWallet.TransactionType.SolanaSPLTokenTransfer ||
transaction.txType === BraveWallet.TransactionType.SolanaSPLTokenTransferWithAssociatedTokenAccountCreation

const transactionIntentLocale = React.useMemo(() => {
switch (true) {
case transaction.txType === BraveWallet.TransactionType.ERC20Approve: {
Expand Down Expand Up @@ -329,7 +334,7 @@ const PortfolioTransactionItem = (props: Props) => {
<DetailTextLight>{transactionDetails.formattedNativeCurrencyTotal}</DetailTextLight>
</BalanceColumn>
{/* Will remove this conditional for solana once https://github.com/brave/brave-browser/issues/22040 is implemented. */}
{transaction.txType !== BraveWallet.TransactionType.SolanaSystemTransfer &&
{!isSolanaTransaction &&
<TransactionFeesTooltip
text={
<>
Expand Down Expand Up @@ -381,23 +386,23 @@ const PortfolioTransactionItem = (props: Props) => {
}

{[BraveWallet.TransactionStatus.Submitted, BraveWallet.TransactionStatus.Approved].includes(transactionDetails.status) &&
transaction.txType !== BraveWallet.TransactionType.SolanaSystemTransfer &&
!isSolanaTransaction &&
<TransactionPopupItem
onClick={onClickSpeedupTransaction}
text={getLocale('braveWalletTransactionSpeedup')}
/>
}

{[BraveWallet.TransactionStatus.Submitted, BraveWallet.TransactionStatus.Approved].includes(transactionDetails.status) &&
transaction.txType !== BraveWallet.TransactionType.SolanaSystemTransfer &&
!isSolanaTransaction &&
<TransactionPopupItem
onClick={onClickCancelTransaction}
text={getLocale('braveWalletTransactionCancel')}
/>
}

{[BraveWallet.TransactionStatus.Error].includes(transactionDetails.status) &&
transaction.txType !== BraveWallet.TransactionType.SolanaSystemTransfer &&
!isSolanaTransaction &&
<TransactionPopupItem
onClick={onClickRetryTransaction}
text={getLocale('braveWalletTransactionRetry')}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,8 @@ const CryptoView = (props: Props) => {
if (id === 'add-asset') {
onShowVisibleAssetsModal(true)
} else {
const asset = id?.toLowerCase().startsWith('0x')
// If the id length is greater than 15 assumes it's a contractAddress
const asset = id?.length > 15
? userVisibleTokensInfo.find((token) => token.contractAddress === id)
: userVisibleTokensInfo.find((token) => token.symbol.toLowerCase() === id?.toLowerCase())
onSelectAsset(asset)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ function ConfirmTransactionPanel ({
isERC20Approve,
isERC721SafeTransferFrom,
isERC721TransferFrom,
isSolanaSystemTransfer,
isSolanaTransaction,
onEditAllowanceSave,
queueNextTransaction,
rejectAllTransactions,
Expand Down Expand Up @@ -277,7 +277,7 @@ function ConfirmTransactionPanel ({
onSubmit={onSelectTab('details')}
text='Details'
/>
{!isSolanaSystemTransfer &&
{!isSolanaTransaction &&
<AdvancedTransactionSettingsButton
onSubmit={onToggleAdvancedTransactionSettings}
/>
Expand All @@ -291,7 +291,7 @@ function ConfirmTransactionPanel ({
{selectedTab === 'transaction' ? (
<>
{isERC20Approve && <Erc20ApproveTransactionInfo onToggleEditGas={onToggleEditGas} />}
{!isERC20Approve && <TransactionInfo onToggleEditGas={onToggleEditGas} /> }
{!isERC20Approve && <TransactionInfo onToggleEditGas={onToggleEditGas} />}
</>
) : <TransactionDetailBox transactionInfo={transactionInfo} />}
</MessageBox>
Expand Down
Loading

0 comments on commit 6042ab5

Please sign in to comment.