diff --git a/src/containers/Accounts/AMM/AMMAccounts/index.tsx b/src/containers/Accounts/AMM/AMMAccounts/index.tsx index d066637d5..1c9407024 100644 --- a/src/containers/Accounts/AMM/AMMAccounts/index.tsx +++ b/src/containers/Accounts/AMM/AMMAccounts/index.tsx @@ -5,8 +5,9 @@ import { useLanguage } from '../../../shared/hooks' import '../../styles.scss' import { formatAmount } from '../../../../rippled/lib/txSummary/formatAmount' import { - getAccountTransactions, + getAccountInfo, getAMMInfo, + getLedgerEntry, } from '../../../../rippled/lib/rippled' import { Tabs } from '../../../shared/components/Tabs' import { useAnalytics } from '../../../shared/analytics' @@ -26,14 +27,6 @@ import { ACCOUNT_ROUTE } from '../../../App/routes' const getErrorMessage = (error: string) => ERROR_MESSAGES[error] || ERROR_MESSAGES.default -function findAMMCreate(txs: [any]) { - const ammCreate = txs.filter((tx) => tx.tx.TransactionType === 'AMMCreate') - - if (ammCreate.length < 1) throw new Error('Could not find AMM Create') - - return ammCreate[0].tx -} - function renderError(error: any) { const message = getErrorMessage(error.code) return ( @@ -70,50 +63,47 @@ export const AMMAccounts = () => { Get the first account transaction which in this case should be AMMCreate. From this we get the two assets in the asset pool. */ - return ( - getAccountTransactions(rippledSocket, accountId, 1, undefined, true) - .then((tData) => { - const tx = findAMMCreate(tData.transactions) - asset1 = formatAsset(tx.Amount) - asset2 = formatAsset(tx.Amount2) - - // if one of the assets is XRP, make sure it's the second one - if (asset1.currency === 'XRP') { - const temp = asset2 - asset2 = asset1 - asset1 = temp - } - - return getAMMInfo(rippledSocket, asset1, asset2) - }) - - /* - Use the assets to get the AMM Info. - */ - .then((ammDataWrapper) => { - ammData = ammDataWrapper.amm - const balance = formatAmount(ammData.amount) - const balance2 = formatAmount(ammData.amount2) - - const ammInfo: AmmDataType = { - balance, - balance2, - tradingFee: ammData.trading_fee, - lpBalance: ammData.lp_token.value, - accountId, - language, - } - - return ammInfo - }) - .catch((e) => { - trackException( - `Error setting up amm account --- ${JSON.stringify(e)}`, - ) - - throw e - }) - ) + return getAccountInfo(rippledSocket, accountId) + .then((accountInfo) => + getLedgerEntry(rippledSocket, { index: accountInfo.AMMID }) + .then((ammLedgerEntry) => { + asset1 = formatAsset(ammLedgerEntry.node.Asset) + asset2 = formatAsset(ammLedgerEntry.node.Asset2) + + // if one of the assets is XRP, make sure it's the second one + if (asset1.currency === 'XRP') { + const temp = asset2 + asset2 = asset1 + asset1 = temp + } + + return getAMMInfo(rippledSocket, asset1, asset2) + }) + /* + Use the assets to get the AMM Info. + */ + .then((ammDataWrapper) => { + ammData = ammDataWrapper.amm + const balance = formatAmount(ammData.amount) + const balance2 = formatAmount(ammData.amount2) + + const ammInfo: AmmDataType = { + balance, + balance2, + tradingFee: ammData.trading_fee, + lpBalance: ammData.lp_token.value, + accountId, + language, + } + + return ammInfo + }), + ) + .catch((e) => { + trackException(`Error setting up amm account --- ${JSON.stringify(e)}`) + + throw e + }) }) useEffect( diff --git a/src/containers/Accounts/AMM/AMMAccounts/test/AMMAccounts.test.tsx b/src/containers/Accounts/AMM/AMMAccounts/test/AMMAccounts.test.tsx index 0deefa651..8f3a4f260 100644 --- a/src/containers/Accounts/AMM/AMMAccounts/test/AMMAccounts.test.tsx +++ b/src/containers/Accounts/AMM/AMMAccounts/test/AMMAccounts.test.tsx @@ -9,12 +9,18 @@ import { AMMAccounts } from '../index' import { flushPromises, QuickHarness } from '../../../../test/utils' import { ACCOUNT_ROUTE } from '../../../../App/routes' -function setSpy(accountTransactions: any, ammInfo: any) { +function setSpy(accountInfo: any, getLedgerEntry: any, ammInfo: any) { + const spyAccountInfo = jest.spyOn(rippled, 'getAccountInfo') + const spyLedgerEntry = jest.spyOn(rippled, 'getLedgerEntry') const spyInfo = jest.spyOn(rippled, 'getAMMInfo') - const spyTransactions = jest.spyOn(rippled, 'getAccountTransactions') - spyTransactions.mockReturnValue( + spyAccountInfo.mockReturnValue( new Promise((resolve) => { - resolve(accountTransactions) + resolve(accountInfo) + }), + ) + spyLedgerEntry.mockReturnValue( + new Promise((resolve) => { + resolve(getLedgerEntry) }), ) spyInfo.mockReturnValue( @@ -26,27 +32,100 @@ function setSpy(accountTransactions: any, ammInfo: any) { describe('AMM Account Page', () => { const TEST_ACCOUNT_ID = 'rTEST_ACCOUNT' - const accountTransactions: any = { - transactions: [ - { - tx: { - Amount: '10000000000', - Amount2: { currency: 'USD', amount: '100000', issuer: 'SOLO' }, - TransactionType: 'AMMCreate', + const accountInfo: any = { + AMMID: '0017D8D412779284FDA6A63CEBEADD43BC2FEF37181C3C234ADAC9EFBB5FDB53', + Account: 'rJbt6ryq1TzikBuvVQDaxVLqL77eJeibsj', + Balance: '10000000', + Flags: 26214400, + LedgerEntryType: 'AccountRoot', + OwnerCount: 1, + PreviousTxnID: + '2A9F2B8D74CBECFF339BBD5CD9E42468984D3D8AA5D521B9610F31B014629DC2', + PreviousTxnLgrSeq: 58180, + Sequence: 58180, + index: '115CA30FD281E3265AA22F563B4ADE4BD15A6107F1E5105056F191882BE78FC4', + } + + const ledgerEntry: any = { + index: '0017D8D412779284FDA6A63CEBEADD43BC2FEF37181C3C234ADAC9EFBB5FDB53', + ledger_hash: + '6C1914FF5966D2FD060B92B07A30A303369F28132DB5E8D73BED4FFC8A372EF2', + ledger_index: 285601, + node: { + Account: 'rJbt6ryq1TzikBuvVQDaxVLqL77eJeibsj', + Asset: { + currency: 'XRP', + }, + Asset2: { + currency: 'USD', + issuer: 'rJd9Ti4TF2Mrn268LW7sSw8E16J4hYzMiD', + }, + AuctionSlot: { + Account: 'rJd9Ti4TF2Mrn268LW7sSw8E16J4hYzMiD', + Expiration: 745719332, + Price: { + currency: '03930D02208264E2E40EC1B0C09E4DB96EE197B1', + issuer: 'rJbt6ryq1TzikBuvVQDaxVLqL77eJeibsj', + value: '0', }, }, - ], + Flags: 0, + LPTokenBalance: { + currency: '03930D02208264E2E40EC1B0C09E4DB96EE197B1', + issuer: 'rJbt6ryq1TzikBuvVQDaxVLqL77eJeibsj', + value: '10000', + }, + LedgerEntryType: 'AMM', + VoteSlots: [ + { + VoteEntry: { + Account: 'rJd9Ti4TF2Mrn268LW7sSw8E16J4hYzMiD', + VoteWeight: 100000, + }, + }, + ], + index: '0017D8D412779284FDA6A63CEBEADD43BC2FEF37181C3C234ADAC9EFBB5FDB53', + }, + validated: true, } const ammInfo: any = { amm: { - amount: '10000000000', - amount2: { currency: 'USD', value: '100000' }, - trading_fee: 10, + account: 'rJbt6ryq1TzikBuvVQDaxVLqL77eJeibsj', + amount: '10000000', + amount2: { + currency: 'USD', + issuer: 'rJd9Ti4TF2Mrn268LW7sSw8E16J4hYzMiD', + value: '10', + }, + asset2_frozen: false, + auction_slot: { + account: 'rJd9Ti4TF2Mrn268LW7sSw8E16J4hYzMiD', + discounted_fee: 0, + expiration: '2023-08-19T00:15:32+0000', + price: { + currency: '03930D02208264E2E40EC1B0C09E4DB96EE197B1', + issuer: 'rJbt6ryq1TzikBuvVQDaxVLqL77eJeibsj', + value: '0', + }, + time_interval: 20, + }, lp_token: { - value: '8989', + currency: '03930D02208264E2E40EC1B0C09E4DB96EE197B1', + issuer: 'rJbt6ryq1TzikBuvVQDaxVLqL77eJeibsj', + value: '10000', }, + trading_fee: 0, + vote_slots: [ + { + account: 'rJd9Ti4TF2Mrn268LW7sSw8E16J4hYzMiD', + trading_fee: 0, + vote_weight: 100000, + }, + ], }, + ledger_current_index: 285641, + validated: false, } const createWrapper = () => @@ -59,8 +138,8 @@ describe('AMM Account Page', () => { , ) - it('renders AMM account page when TVL not present', async () => { - setSpy(accountTransactions, ammInfo) + it('renders AMM account page', async () => { + setSpy(accountInfo, ledgerEntry, ammInfo) const wrapper = createWrapper() await flushPromises() @@ -71,7 +150,7 @@ describe('AMM Account Page', () => { }) it('shows error when amm info data is formatted incorrectly', async () => { - setSpy(accountTransactions, 'ammInfo') + setSpy(accountInfo, ledgerEntry, 'ammInfo') const wrapper = await createWrapper() await flushPromises() @@ -82,12 +161,34 @@ describe('AMM Account Page', () => { wrapper.unmount() }) - it('shows error when account transactions data is formatted incorrectly', async () => { - const accTransBad: any = { - transactions: [], + it('shows error when account_info has no AMMID', async () => { + const badAccountInfo: any = { + ...accountInfo, + } + + delete badAccountInfo.AMMID + + const badLedgerEntry = { + error: 'invalidParams', + error_code: 31, + error_message: 'indexMalformed', + status: 'error', + type: 'response', + request: { + command: 'ledger_entry', + index: '', + ledger_index: 'validated', + }, + warnings: [ + { + id: 2001, + message: + "This is a clio server. clio only serves validated data. If you want to talk to rippled, include 'ledger_index':'current' in your request", + }, + ], } - setSpy(accTransBad, ammInfo) + setSpy(badAccountInfo, badLedgerEntry, ammInfo) const wrapper = createWrapper() await flushPromises() diff --git a/src/containers/Accounts/AccountsRouter.tsx b/src/containers/Accounts/AccountsRouter.tsx index 052562b5c..1141bff59 100644 --- a/src/containers/Accounts/AccountsRouter.tsx +++ b/src/containers/Accounts/AccountsRouter.tsx @@ -13,7 +13,7 @@ import NoMatch from '../NoMatch' import { Accounts } from './index' import { ERROR_MESSAGES } from './Errors' import { Loader } from '../shared/components/Loader' -import { ACCOUNT_FLAGS, Error } from '../../rippled/lib/utils' +import { Error } from '../../rippled/lib/utils' import { BAD_REQUEST } from '../shared/utils' const getErrorMessage = (error: any) => @@ -31,10 +31,6 @@ function renderError(error: any) { export const AccountsRouter = () => { const { id: accountId = '' } = useParams<{ id: string }>() const rippledSocket = useContext(SocketContext) - const flags: any = Object.entries(ACCOUNT_FLAGS).reduce( - (all, [key, value]) => ({ ...all, [value]: key }), - {}, - ) const { data: comp, error } = useQuery([accountId], () => { let classicAddress = accountId @@ -49,7 +45,7 @@ export const AccountsRouter = () => { return ( getAccountInfo(rippledSocket, classicAddress) .then((data: any) => { - if (data.Flags & flags.lsfAMM) { + if (data.AMMID) { return } return diff --git a/src/rippled/lib/rippled.js b/src/rippled/lib/rippled.js index 7420909d3..e6b5ac6b3 100644 --- a/src/rippled/lib/rippled.js +++ b/src/rippled/lib/rippled.js @@ -74,6 +74,70 @@ const getLedger = (rippledSocket, parameters) => { }) } +// get ledger_entry +const getLedgerEntry = (rippledSocket, { index }) => { + const request = { + command: 'ledger_entry', + index, + ledger_index: 'validated', + } + + return query(rippledSocket, request).then((resp) => { + if (resp.error_message === 'entryNotFound') { + throw new Error('ledger entry not found', 404) + } + + if (resp.error_message === 'invalidParams') { + throw new Error('invalidParams for ledger_entry', 404) + } + + if (resp.error_message === 'lgrNotFound') { + throw new Error('invalid ledger index/hash', 400) + } + + if (resp.error_message === 'malformedAddress') { + throw new Error( + 'The ledger_entry request improperly specified an Address field.', + 404, + ) + } + + if (resp.error_message === 'malformedCurrency') { + throw new Error( + 'The ledger_entry request improperly specified a Currency Code field.', + 404, + ) + } + + if (resp.error_message === 'malformedOwner') { + throw new Error( + 'The ledger_entry request improperly specified the escrow.owner sub-field.', + 404, + ) + } + + if (resp.error_message === 'malformedRequest') { + throw new Error( + 'The ledger_entry request provided an invalid combination of fields, or provided the wrong type for one or more fields.', + 404, + ) + } + + if (resp.error_message === 'unknownOption') { + throw new Error( + 'The fields provided in the ledger_entry request did not match any of the expected request formats.', + 404, + ) + } + + if (resp.error_message) { + throw new Error(resp.error_message, 500) + } + + return resp + }) +} + // get transaction const getTransaction = (rippledSocket, txHash) => { const params = { @@ -279,7 +343,6 @@ const getAccountTransactions = ( account, limit = 20, marker = '', - reverseOrder = false, ) => { const markerComponents = marker.split('.') const ledger = parseInt(markerComponents[0], 10) @@ -288,7 +351,6 @@ const getAccountTransactions = ( command: 'account_tx', account, limit, - forward: reverseOrder, ledger_index_max: -1, ledger_index_min: -1, marker: marker @@ -481,6 +543,7 @@ const getAMMInfo = (rippledSocket, asset, asset2) => { export { getLedger, + getLedgerEntry, getTransaction, getAccountInfo, getAccountEscrows, diff --git a/src/rippled/lib/utils.js b/src/rippled/lib/utils.js index d801452f6..59fedbee3 100644 --- a/src/rippled/lib/utils.js +++ b/src/rippled/lib/utils.js @@ -19,7 +19,6 @@ export const ACCOUNT_FLAGS = { 0x08000000: 'lsfDisallowIncomingCheck', 0x10000000: 'lsfDisallowIncomingPayChan', 0x20000000: 'lsfDisallowIncomingTrustline', - 0x40000000: 'lsfAMM', 0x80000000: 'lsfAllowTrustLineClawback', } const NFT_FLAGS = {