Skip to content

Commit

Permalink
support /account/<muxed address>
Browse files Browse the repository at this point in the history
use stellar-sdk StrKey.isValidMed25519PublicKey for proper detection of muxed address
refactor account - handle 3 types of addresses cleanly
  • Loading branch information
Chris Hatch committed Jul 15, 2021
1 parent 86702ed commit fe3494f
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 55 deletions.
99 changes: 53 additions & 46 deletions src/components/Account.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ import Tabs from 'react-bootstrap/lib/Tabs'
import {injectIntl, FormattedMessage} from 'react-intl'
import {FederationServer, MuxedAccount, StrKey} from 'stellar-sdk'
import has from 'lodash/has'

import knownAccounts from '../data/known_accounts'
import {isPublicKey, isFederatedAddress} from '../lib/stellar/utils'
import {isFederatedAddress, isMuxedAddress, isPublicKey} from '../lib/stellar/utils'
import {base64Decode, handleFetchDataFailure, setTitle} from '../lib/utils'
import {withServer} from './shared/HOCs'
import {withSpinner} from './shared/Spinner'
Expand All @@ -28,28 +29,6 @@ import PaymentTable from './PaymentTable'
import TradeTable from './TradeTable'
import TransactionTable from './TransactionTableContainer'

const stellarAddressFromURI = () => {
if (!window || !window.location || !window.location.pathname) return
const path = window.location.pathname
const lastPath = path.substring(path.lastIndexOf('/') + 1)
return isFederatedAddress(lastPath) ? lastPath : undefined
}

const getMuxedAddressFromRequest = () => {
if (window && window.location && window.location.search) {
const params = new URLSearchParams(window.location.search)
const muxedParam = params.get('muxed')
if (muxedParam) {
try {
const muxedAccount = MuxedAccount.fromAddress(muxedParam, '1')
return muxedAccount.accountId()
} catch(err) {
console.warn('invalid muxed address in request. skipping ...')
}
}
}
}

const NameValueTable = ({data, decodeValue = false}) => {
if (!data || Object.keys(data).length === 0)
return <div style={{marginTop: 20, marginBottom: 20}}>No Data</div>
Expand Down Expand Up @@ -199,6 +178,7 @@ const Data = ({data}) => <NameValueTable data={data} decodeValue />
const AccountSummaryPanel = ({
account: a,
accountUrl,
federatedAddress,
formatMessageFn,
knownAccounts,
}) => {
Expand All @@ -208,7 +188,6 @@ const AccountSummaryPanel = ({
formatMessageFn({id: 'account'}),
accountUrl
)
const stellarAddr = stellarAddressFromURI()

return (
<Panel header={header}>
Expand All @@ -224,12 +203,12 @@ const AccountSummaryPanel = ({
<ClipboardCopy text={a.id} />
</Col>
</Row>
{stellarAddr && (
{federatedAddress && (
<Row>
<Col md={3}>
<FormattedMessage id="stellar.address" />:
</Col>
<Col md={9}>{stellarAddr}</Col>
<Col md={9}>{federatedAddress}</Col>
</Row>
)}
<Row>
Expand Down Expand Up @@ -274,6 +253,10 @@ class Account extends React.Component {

constructor(props, context) {
super(props, context)
console.log('here')
console.log(this.props)


this.handleURIHash = this.handleURIHash.bind(this)
this.handleSelect = this.handleSelect.bind(this)
this.setNewState = this.setNewState.bind(this)
Expand Down Expand Up @@ -308,18 +291,18 @@ class Account extends React.Component {
render() {
const {formatMessage} = this.props.intl
const a = this.props.account
const muxedAddress = getMuxedAddressFromRequest()
return (
<Grid>
{muxedAddress &&
{this.props.muxedAddress &&
<Row>
<MuxedAccountInfoPanel address={muxedAddress}/>
<MuxedAccountInfoPanel address={this.props.muxedAddress}/>
</Row>
}
<Row>
<AccountSummaryPanel
account={a}
accountUrl={this.props.urlFn(a.id)}
federatedAddress={this.props.federatedAddress}
formatMessageFn={formatMessage}
knownAccounts={knownAccounts}
/>
Expand Down Expand Up @@ -426,8 +409,10 @@ const AccountWithSpinner = withSpinner()(Account)

class AccountContainer extends React.Component {
state = {
account: null,
isLoading: true,
account: null,
federatedAddress: null,
muxedAddress: null,
}

componentDidMount() {
Expand All @@ -439,40 +424,62 @@ class AccountContainer extends React.Component {
}

loadAccount(accountId) {
if (isPublicKey(accountId)) this.loadAccountByKey(accountId)
else if (isFederatedAddress(accountId))
this.loadAccountByStellarAddress(accountId)
else
if (isPublicKey(accountId)) {
this.loadAccountByKey(accountId)
} else if (isFederatedAddress(accountId)) {
this.loadAccountByFederatedAddress(accountId)
} else if (isMuxedAddress(accountId)) {
this.loadAccountByMuxedAddress(accountId)
} else {
handleFetchDataFailure(accountId)(
new Error(`Unrecognized account: ${accountId}`)
)
}
}

loadAccountByStellarAddress(stellarAddr) {
const [name, domain] = stellarAddr.split('*')
loadAccountByKey(accountId) {
this.loadAccountFromServer(accountId).then(res => {
this.setState({account: res, isLoading: false})
return null
})
.catch(handleFetchDataFailure(accountId))
}

loadAccountByFederatedAddress(address) {
const [name, domain] = address.split('*')
FederationServer.createForDomain(domain)
.then(fed => fed.resolveAddress(name))
.then(acc => this.loadAccount(acc.account_id))
.catch(handleFetchDataFailure(stellarAddr))
.then(acc => this.loadAccountFromServer(acc.account_id))
.then(account => {
this.setState({account, federatedAddress: address, isLoading: false})
return null
})
.catch(handleFetchDataFailure(address))
}

loadAccountByKey(accountId) {
this.props.server
loadAccountByMuxedAddress(address) {
const muxedAccount = MuxedAccount.fromAddress(address, '1')
const publicAddress = muxedAccount.account.accountId()
this.loadAccountFromServer(publicAddress).then(account => {
this.setState({account, muxedAddress: address, isLoading: false})
return null
})
}

loadAccountFromServer(accountId) {
return this.props.server
.accounts()
.accountId(accountId)
.call()
.then(res => {
this.setState({account: res, isLoading: false})
return null
})
.catch(handleFetchDataFailure(accountId))
}

render() {
return (
<AccountWithSpinner
account={this.state.account}
isLoading={this.state.isLoading}
account={this.state.account}
federatedAddress={this.state.federatedAddress}
muxedAddress={this.state.muxedAddress}
urlFn={this.props.server.accountURL}
{...this.props}
/>
Expand Down
10 changes: 5 additions & 5 deletions src/components/shared/AccountLink.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import {Link} from 'react-router-dom'
import has from 'lodash/has'
import {MuxedAccount} from 'stellar-sdk'

import {shortAddress} from '../../lib/utils'
import knownAccounts from '../../data/known_accounts'
import {isMuxedAddress} from '../../lib/stellar/utils'
import {shortAddress} from '../../lib/utils'

const AccountLinkSimple = ({title, subPath, label}) => (
<span title={title}>
Expand Down Expand Up @@ -37,9 +38,9 @@ const MuxedAccountLink = ({address, label, hideKnown}) => {
const muxedAddress = muxedAccount.accountId()
return (
<span>
<AccountLinkSimple
<AccountLinkSimple
title={`Muxed Address: ${muxedAddress}`}
subPath={`${publicAddress}?muxed=${muxedAddress}`}
subPath={muxedAddress}
label={shortAddress(muxedAddress)}
/>-
<BaseAccountLink
Expand All @@ -52,8 +53,7 @@ const MuxedAccountLink = ({address, label, hideKnown}) => {
}

const AccountLink = ({account, label, hideKnown = false}) => {
const isMuxedAccount = account.startsWith('M')
if (isMuxedAccount) {
if (isMuxedAddress(account)) {
return (
<MuxedAccountLink
address={account}
Expand Down
25 changes: 24 additions & 1 deletion src/lib/__tests__/stellar/utils.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
isPublicKey,
isFederatedAddress,
isMuxedAddress,
isPublicKey,
isTxHash,
stroopsToLumens,
} from '../../stellar/utils'
Expand Down Expand Up @@ -54,3 +55,25 @@ it('isTxHash identifies a valid transaction hash', () => {
isTxHash('ddeff3d3b8455f8173ef4d63e6650625734207fd351d2b9eeeaf0e38ffe1064b')
).toBe(true)
})

it('isMuxedAddress identifies a valid key', () => {
expect(isMuxedAddress()).toBe(false)
expect(isMuxedAddress('')).toBe(false)
expect(isMuxedAddress(null)).toBe(false)

// same length as valid key and looks valid but is not
expect(
isMuxedAddress('MDZ464OWNGEL4X2DE6JPLEARO2WJ4AGCBN3XM7E4ZSLPHRBV6AZB6AAAAAAAAAAAAGW4MX')
).toBe(false)

// valid public key is not a valid mutex address
expect(
isMuxedAddress('GBQHXMAVPD3AKY5PWFCBVT3NFIXGE345FVZLL4JXKTVSFT5FKMEV5QIX')
).toBe(false)

// valid
expect(
isMuxedAddress('MDZ464OWNGEL4X2DE6JPLEARO2WJ4AGCBN3XM7E4ZSLPHRBV6AZB6AAAAAAAAAAAAGW4M')
).toBe(true)
})

5 changes: 3 additions & 2 deletions src/lib/search.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import toNumber from 'lodash/toNumber'
import {sdk} from './stellar'

import {
isFederatedAddress,
isMuxedAddress,
isPublicKey,
isSecretKey,
isFederatedAddress,
isTxHash,
} from './stellar/utils'
import directory from '../data/directory'
Expand Down Expand Up @@ -39,7 +40,7 @@ const searchStrToPath = searchStr => {

const str = searchStr.trim()

if (isPublicKey(str) || isFederatedAddress(str)) {
if (isPublicKey(str) || isFederatedAddress(str) || isMuxedAddress(str)) {
return `/account/${str}`
} else if (isTxHash(str)) {
return `/tx/${str}`
Expand Down
10 changes: 9 additions & 1 deletion src/lib/stellar/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,16 @@ const stroopsToLumens = stroops => stroops / STROOPS_PER_LUMEN

// stellar federated address (eg. "stellar*fed.network")
const isFederatedAddress = addr => /^[^*,]*\*[a-z0-9-.]*$/i.test(addr)
const isMuxedAddress = addr => StrKey.isValidMed25519PublicKey(addr)
const isPublicKey = keyStr => StrKey.isValidEd25519PublicKey(keyStr)
const isSecretKey = keyStr => StrKey.isValidEd25519SecretSeed(keyStr)
const isTxHash = hashStr => /^[0-9a-f]{64}$/i.test(hashStr)

export {isPublicKey, isSecretKey, isFederatedAddress, isTxHash, stroopsToLumens}
export {
isFederatedAddress,
isMuxedAddress,
isPublicKey,
isSecretKey,
isTxHash,
stroopsToLumens,
}

0 comments on commit fe3494f

Please sign in to comment.