+
+
{t('hash')}:
- {data?.raw.hash}
+ {data?.processed.hash}
- {data?.raw.tx.ctid && (
-
+ {data?.processed.tx.ctid && (
+
CTID:
- {data.raw.tx.ctid}
+ {data.processed.tx.ctid}
)}
@@ -114,7 +120,12 @@ export const Transaction = () => {
}
function renderTabs() {
- const tabs = ['simple', 'detailed', 'raw']
+ const tabs =
+ data === undefined ||
+ data.raw.tx.TransactionType === 'OfferCreate' ||
+ data.raw.tx.TransactionType === 'Payment'
+ ? ['simple', 'breakdown', 'detailed', 'raw']
+ : ['simple', 'detailed', 'raw']
const mainPath = buildPath(TRANSACTION_ROUTE, { identifier })
return
}
@@ -125,8 +136,11 @@ export const Transaction = () => {
let body
switch (tab) {
+ case 'breakdown':
+ body =
+ break
case 'detailed':
- body =
+ body =
break
case 'raw':
body =
@@ -149,7 +163,7 @@ export const Transaction = () => {
if (isError) {
const message = getErrorMessage(error)
body =
- } else if (data?.raw && data?.raw.hash) {
+ } else if (data?.processed && data?.processed.hash) {
body = renderTransaction()
} else if (!identifier) {
body = (
diff --git a/src/containers/Transactions/simpleTab.scss b/src/containers/Transactions/simpleTab.scss
index ca374a592..978ef28f3 100644
--- a/src/containers/Transactions/simpleTab.scss
+++ b/src/containers/Transactions/simpleTab.scss
@@ -1,4 +1,4 @@
-@import '../shared/css/variables';
+@use '../shared/css/variables' as *;
$subdued-color: $black-40;
diff --git a/src/containers/Transactions/test/BreakDownTab.test.tsx b/src/containers/Transactions/test/BreakDownTab.test.tsx
new file mode 100644
index 000000000..928f7ddf7
--- /dev/null
+++ b/src/containers/Transactions/test/BreakDownTab.test.tsx
@@ -0,0 +1,101 @@
+import { mount } from 'enzyme'
+import { I18nextProvider } from 'react-i18next'
+import { BrowserRouter as Router } from 'react-router-dom'
+
+import EnableAmendment from './mock_data/EnableAmendment.json'
+import Payment from './mock_data/PaymentBreakdown.json'
+import { BreakDownTab } from '../BreakDownTab'
+import i18n from '../../../i18n/testConfig'
+
+describe('BreakdownTab container', () => {
+ const createWrapper = (tx) =>
+ mount(
+
+
+
+
+ ,
+ )
+
+ it('renders EnableAmendment without crashing', () => {
+ const wrapper = createWrapper(EnableAmendment)
+ wrapper.unmount()
+ })
+
+ it('renders breakdown tab information', () => {
+ const wrapper = createWrapper(Payment)
+
+ // console.log(Payment)
+
+ expect(wrapper.find('.breakdown-body').length).toBe(1)
+ expect(wrapper.find('.detail-section').length).toBe(3)
+
+ expect(wrapper.find('.source-account').length).toBe(1)
+ expect(wrapper.find('.source-amount').length).toBe(1)
+ expect(wrapper.find('.destination-account').length).toBe(1)
+ expect(wrapper.find('.destination-amount').length).toBe(1)
+
+ expect(
+ wrapper.contains(
liquidity_source
),
+ ).toBe(true)
+
+ expect(wrapper.contains(
)).toBe(
+ true,
+ )
+ expect(
+ wrapper.contains(
),
+ ).toBe(true)
+
+ expect(wrapper.contains(
graph_dependent_currency
)).toBe(true)
+
+ expect(
+ wrapper.contains(
amm (100%)),
+ ).toBe(true)
+ expect(
+ wrapper.contains(
rippling (100%)),
+ ).toBe(true)
+ expect(
+ wrapper.contains(
dex (0%)),
+ ).toBe(true)
+ expect(
+ wrapper.contains(
direct (0%)),
+ ).toBe(true)
+
+ expect(wrapper.contains(
balance_changes
)).toBe(
+ true,
+ )
+
+ expect(wrapper.contains(
dex)).toBe(false)
+ expect(wrapper.contains(
direct)).toBe(
+ false,
+ )
+ expect(
+ wrapper.contains(
rippling),
+ ).toBe(true)
+ expect(wrapper.contains(
amm)).toBe(true)
+
+ const balanceChanges = wrapper.find('.balance-changes')
+ expect(balanceChanges.find('BalanceChange')).toHaveLength(2)
+ // console.log(wrapper.debug())
+
+ expect(
+ balanceChanges.contains(
+
26.79746261,
+ ),
+ ).toBe(true)
+ expect(
+ balanceChanges.contains(
+
-26.877855,
+ ),
+ ).toBe(true)
+ expect(
+ balanceChanges.contains(
+
-50.624692,
+ ),
+ ).toBe(true)
+
+ wrapper.unmount()
+ })
+
+ // console.log(balanceChanges.debug())
+})
diff --git a/src/containers/Transactions/test/Description.test.tsx b/src/containers/Transactions/test/Description.test.tsx
index 04ae40ccb..4c6074494 100644
--- a/src/containers/Transactions/test/Description.test.tsx
+++ b/src/containers/Transactions/test/Description.test.tsx
@@ -1,19 +1,23 @@
import { mount } from 'enzyme'
import { BrowserRouter as Router } from 'react-router-dom'
import { I18nextProvider } from 'react-i18next'
+import { QueryClientProvider } from 'react-query'
import i18n from '../../../i18n/testConfigEnglish'
import { TransactionDescription } from '../DetailTab/Description'
import Transaction from './mock_data/Transaction.json'
import OfferCreateTicket from './mock_data/OfferCreateTicket.json'
import EmittedPayment from './mock_data/EmittedPayment.json'
+import { queryClient } from '../../shared/QueryClient'
describe('Description container', () => {
const createWrapper = (data = {}) =>
mount(
-
-
-
+
+
+
+
+
,
)
diff --git a/src/containers/Transactions/test/DetailTab.test.tsx b/src/containers/Transactions/test/DetailTab.test.tsx
index 0e734c06c..46ff5488c 100644
--- a/src/containers/Transactions/test/DetailTab.test.tsx
+++ b/src/containers/Transactions/test/DetailTab.test.tsx
@@ -1,6 +1,7 @@
import { mount } from 'enzyme'
import { BrowserRouter as Router } from 'react-router-dom'
import { I18nextProvider } from 'react-i18next'
+import { QueryClientProvider } from 'react-query'
import Transaction from '../../shared/components/Transaction/EscrowCreate/test/mock_data/EscrowCreate.json'
import FailedTransaction from '../../shared/components/Transaction/SignerListSet/test/mock_data/SignerListSet.json'
import HookPayment from './mock_data/HookPayment.json'
@@ -8,14 +9,17 @@ import EmittedPayment from './mock_data/EmittedPayment.json'
import { DetailTab } from '../DetailTab'
import i18n from '../../../i18n/testConfigEnglish'
import { convertHexToString } from '../../../rippled/lib/utils'
+import { queryClient } from '../../shared/QueryClient'
describe('DetailTab container', () => {
const createWrapper = (transaction: any = Transaction) =>
mount(
-
-
-
+
+
+
+
+
,
)
diff --git a/src/containers/Transactions/test/Meta.test.tsx b/src/containers/Transactions/test/Meta.test.tsx
index e6e2fe903..ac4195799 100644
--- a/src/containers/Transactions/test/Meta.test.tsx
+++ b/src/containers/Transactions/test/Meta.test.tsx
@@ -6,6 +6,7 @@ import Transaction from './mock_data/Transaction.json'
import OfferCancel from '../../shared/components/Transaction/OfferCancel/test/mock_data/OfferCancel.json'
import OfferCreateWithMissingPreviousFields from '../../shared/components/Transaction/OfferCreate/test/mock_data/OfferCreateWithMissingPreviousFields.json'
import PaymentChannelClaim from '../../shared/components/Transaction/PaymentChannelClaim/test/mock_data/PaymentChannelClaim.json'
+import DirectMPTPayment from './mock_data/DirectMPTPayment.json'
import { TransactionMeta } from '../DetailTab/Meta'
describe('TransactionMeta container', () => {
@@ -160,4 +161,32 @@ describe('TransactionMeta container', () => {
)
expect(w.find('li').length).toBe(4)
})
+
+ it('renders MPT Payment Meta', () => {
+ const w = createWrapper(DirectMPTPayment)
+
+ expect(w.find('.title').length).toBe(1)
+ expect(w.find('.detail-subsection').length).toBe(1)
+ expect(w.contains(
number_of_affected_node
)).toBe(true)
+ expect(w.contains(
nodes_type
)).toBe(
+ true,
+ )
+ expect(w.find('li').length).toBe(6)
+
+ expect(w.find('li').at(2).html()).toBe(
+ '
It modified an MPToken node ofrnNkvddM96FE2QsaFztLAn5xicjq5d6d8d',
+ )
+
+ expect(w.find('li').at(3).html()).toBe(
+ '
Balance changed by100from0to100',
+ )
+
+ expect(w.find('li').at(4).html()).toBe(
+ '
It modified an MPTokenIssuance node ofrwREfyDU1SbcjN3xXZDbm8uNJV77T2ruKw',
+ )
+
+ expect(w.find('li').at(5).html()).toBe(
+ '
Outstanding balance changed by100from0to100',
+ )
+ })
})
diff --git a/src/containers/Transactions/test/SimpleTab.test.tsx b/src/containers/Transactions/test/SimpleTab.test.tsx
index 5b983ebd6..bce4d5502 100644
--- a/src/containers/Transactions/test/SimpleTab.test.tsx
+++ b/src/containers/Transactions/test/SimpleTab.test.tsx
@@ -2,26 +2,43 @@ import { mount } from 'enzyme'
import { I18nextProvider } from 'react-i18next'
import { BrowserRouter as Router } from 'react-router-dom'
+import { QueryClientProvider } from 'react-query'
import EnableAmendment from './mock_data/EnableAmendment.json'
import Payment from '../../shared/components/Transaction/Payment/test/mock_data/Payment.json'
import { SimpleTab } from '../SimpleTab'
import summarize from '../../../rippled/lib/txSummary'
import i18n from '../../../i18n/testConfig'
import { expectSimpleRowText } from '../../shared/components/Transaction/test'
+import SocketContext from '../../shared/SocketContext'
+import MockWsClient from '../../test/mockWsClient'
+import { queryClient } from '../../shared/QueryClient'
describe('SimpleTab container', () => {
+ let client
const createWrapper = (tx, width = 1200) =>
mount(
-
-
-
+
+
+
+
+
+
+
,
)
+ beforeEach(() => {
+ client = new MockWsClient()
+ })
+
+ afterEach(() => {
+ client.close()
+ })
+
it('renders EnableAmendment without crashing', () => {
const wrapper = createWrapper(EnableAmendment)
wrapper.unmount()
diff --git a/src/containers/Transactions/test/Transaction.test.tsx b/src/containers/Transactions/test/Transaction.test.tsx
index 774d3da56..f9573dffa 100644
--- a/src/containers/Transactions/test/Transaction.test.tsx
+++ b/src/containers/Transactions/test/Transaction.test.tsx
@@ -104,7 +104,7 @@ describe('Transaction container', () => {
beforeEach(async () => {
const transaction = {
- raw: mockTransaction,
+ processed: mockTransaction,
summary: mockTransactionSummary,
}
@@ -135,10 +135,11 @@ describe('Transaction container', () => {
)
expect(summary.contains(
)).toBe(true)
expect(wrapper.find('.tabs').length).toBe(1)
- expect(wrapper.find('a.tab').length).toBe(3)
+ expect(wrapper.find('a.tab').length).toBe(4)
expect(wrapper.find('a.tab').at(0).props().title).toBe('simple')
- expect(wrapper.find('a.tab').at(1).props().title).toBe('detailed')
- expect(wrapper.find('a.tab').at(2).props().title).toBe('raw')
+ expect(wrapper.find('a.tab').at(1).props().title).toBe('breakdown')
+ expect(wrapper.find('a.tab').at(2).props().title).toBe('detailed')
+ expect(wrapper.find('a.tab').at(3).props().title).toBe('raw')
expect(wrapper.find('a.tab.selected').text()).toEqual('simple')
wrapper.unmount()
})
diff --git a/src/containers/Transactions/test/mock_data/DirectMPTPayment.json b/src/containers/Transactions/test/mock_data/DirectMPTPayment.json
new file mode 100644
index 000000000..87d3c374b
--- /dev/null
+++ b/src/containers/Transactions/test/mock_data/DirectMPTPayment.json
@@ -0,0 +1,95 @@
+{
+ "tx": {
+ "Account": "rwREfyDU1SbcjN3xXZDbm8uNJV77T2ruKw",
+ "Amount": {
+ "mpt_issuance_id": "0000301C674EE6ECD0374A628E2C442EF8E3BBBEE8C58CF3",
+ "value": "100"
+ },
+ "DeliverMax": {
+ "mpt_issuance_id": "0000301C674EE6ECD0374A628E2C442EF8E3BBBEE8C58CF3",
+ "value": "100"
+ },
+ "Destination": "rnNkvddM96FE2QsaFztLAn5xicjq5d6d8d",
+ "Fee": "10",
+ "Flags": 2147483648,
+ "Sequence": 12317,
+ "SigningPubKey": "ED1E43F90700506F98E45CC8E77563ACB8FF0338739229AC98F0E1AEB409E786F9",
+ "TransactionType": "Payment",
+ "TxnSignature": "9949EF3E718A6776586A1DD91256C4055E761CBF1CE7A351912C08FE1BB415F6638FA096CAAE88D30B32E684448CDC52DEB022A7E3576AFA0C6E5ABB7BE2FE02",
+ "ctid": "C000302000000000",
+ "date": 1712087804000,
+ "hash": "undefined",
+ "inLedger": "undefined",
+ "ledger_index": "undefined",
+ "meta": "undefined",
+ "validated": "undefined",
+ "metaData": "undefined",
+ "status": "undefined"
+ },
+ "meta": {
+ "AffectedNodes": [
+ {
+ "ModifiedNode": {
+ "FinalFields": {
+ "Account": "rwREfyDU1SbcjN3xXZDbm8uNJV77T2ruKw",
+ "Balance": "99999980",
+ "Flags": 0,
+ "OwnerCount": 1,
+ "Sequence": 12318
+ },
+ "LedgerEntryType": "AccountRoot",
+ "LedgerIndex": "80909CC95DCD7E5E08631635C559E218D42C49721A1285192885B46E4737CF60",
+ "PreviousFields": {
+ "Balance": "99999990",
+ "Sequence": 12317
+ },
+ "PreviousTxnID": "C9D0965E7A13F54186B6501D08ED54D74AA946A856D96BF7AAE18EA5FD5E93C2",
+ "PreviousTxnLgrSeq": 12318
+ }
+ },
+ {
+ "ModifiedNode": {
+ "FinalFields": {
+ "Account": "rnNkvddM96FE2QsaFztLAn5xicjq5d6d8d",
+ "Flags": 0,
+ "MPTAmount": "100",
+ "MPTokenIssuanceID": "0000301C674EE6ECD0374A628E2C442EF8E3BBBEE8C58CF3",
+ "OwnerNode": "0"
+ },
+ "LedgerEntryType": "MPToken",
+ "LedgerIndex": "9B2E5EA9ACF16B591B941CAE5323EBED55E42495B16C92DC9FBEC0997E8E6804",
+ "PreviousFields": {},
+ "PreviousTxnID": "4C41449D0C083746CC93DDA78F00E97AB8B857188E805F39C1A250F8C9467982",
+ "PreviousTxnLgrSeq": 12319
+ }
+ },
+ {
+ "ModifiedNode": {
+ "FinalFields": {
+ "Flags": 64,
+ "Issuer": "rwREfyDU1SbcjN3xXZDbm8uNJV77T2ruKw",
+ "OutstandingAmount": "100",
+ "OwnerNode": "0",
+ "Sequence": 12316
+ },
+ "LedgerEntryType": "MPTokenIssuance",
+ "LedgerIndex": "C38E54FD2C98FE848FE31CDA95F6F990A9A8715987171DA342A3296B7A9123B6",
+ "PreviousFields": {
+ "OutstandingAmount": "0"
+ },
+ "PreviousTxnID": "C9D0965E7A13F54186B6501D08ED54D74AA946A856D96BF7AAE18EA5FD5E93C2",
+ "PreviousTxnLgrSeq": 12318
+ }
+ }
+ ],
+ "TransactionIndex": 0,
+ "TransactionResult": "tesSUCCESS",
+ "delivered_amount": {
+ "mpt_issuance_id": "0000301C674EE6ECD0374A628E2C442EF8E3BBBEE8C58CF3",
+ "value": "100"
+ }
+ },
+ "hash": "5E74603F7C2E11030E644E681508FD1F24CAEB4CC0CE1F35A6230689D9694E85",
+ "ledger_index": 12320,
+ "date": 1712087804000
+}
diff --git a/src/containers/Transactions/test/mock_data/PaymentBreakdown.json b/src/containers/Transactions/test/mock_data/PaymentBreakdown.json
new file mode 100644
index 000000000..6336382c0
--- /dev/null
+++ b/src/containers/Transactions/test/mock_data/PaymentBreakdown.json
@@ -0,0 +1,286 @@
+{
+ "tx": {
+ "Account": "r3GphWJaShhd1RNFnCaGMVyMawYcKHXCvo",
+ "Amount": {
+ "currency": "5553444300000000000000000000000000000000",
+ "issuer": "rcEGREd8NmkKRE8GE424sksyt1tJVFZwu",
+ "value": "1000000000000000e2"
+ },
+ "DeliverMax": {
+ "currency": "5553444300000000000000000000000000000000",
+ "issuer": "rcEGREd8NmkKRE8GE424sksyt1tJVFZwu",
+ "value": "1000000000000000e2"
+ },
+ "DeliverMin": {
+ "currency": "5553444300000000000000000000000000000000",
+ "issuer": "rcEGREd8NmkKRE8GE424sksyt1tJVFZwu",
+ "value": "25.639817695"
+ },
+ "Destination": "r3GphWJaShhd1RNFnCaGMVyMawYcKHXCvo",
+ "Fee": "12",
+ "Flags": 131072,
+ "LastLedgerSequence": 88214940,
+ "Memos": [
+ {
+ "Memo": {
+ "MemoData": "53574150205472616E73616374696F6E20696E69746961746564207669612058506D61726B65742E636F6D"
+ }
+ }
+ ],
+ "SendMax": "50624692",
+ "Sequence": 88214147,
+ "SigningPubKey": "02F00DADAD94C694359D9FB84163D390CDC418BDA6313A139945C09D2277B39B55",
+ "SourceTag": 20221212,
+ "TransactionType": "Payment",
+ "TxnSignature": "30450221009510B1D5C84A82D870C2827F6A82E6E8AE930778A75D2623D1E8D8280368D6D602207B74F93375275265AB59988748DD1D34F758960EE8FEE5C572A8E84A71205C0F",
+ "ctid": "C5420D9400120000",
+ "date": 1716579292000,
+ "hash": "undefined",
+ "inLedger": "undefined",
+ "ledger_index": "undefined",
+ "meta": {
+ "AffectedNodes": [
+ {
+ "ModifiedNode": {
+ "FinalFields": {
+ "Account": "r3GphWJaShhd1RNFnCaGMVyMawYcKHXCvo",
+ "Balance": "15877383",
+ "Flags": 0,
+ "OwnerCount": 1,
+ "Sequence": 88214148
+ },
+ "LedgerEntryType": "AccountRoot",
+ "LedgerIndex": "4ACE281E26640B951CB4A23768CA6C7E1B0F602053B3C86A653847FE5A5AA1ED",
+ "PreviousFields": { "Balance": "66502087", "Sequence": 88214147 },
+ "PreviousTxnID": "6947EB21D0A745A96F9B2F9B5BABFFE43CE66AF8488365DBC112BC38F8FAC0EA",
+ "PreviousTxnLgrSeq": 88214779
+ }
+ },
+ {
+ "ModifiedNode": {
+ "FinalFields": {
+ "Balance": {
+ "currency": "5553444300000000000000000000000000000000",
+ "issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji",
+ "value": "-26.79746261485543"
+ },
+ "Flags": 2228224,
+ "HighLimit": {
+ "currency": "5553444300000000000000000000000000000000",
+ "issuer": "r3GphWJaShhd1RNFnCaGMVyMawYcKHXCvo",
+ "value": "775905"
+ },
+ "HighNode": "0",
+ "LowLimit": {
+ "currency": "5553444300000000000000000000000000000000",
+ "issuer": "rcEGREd8NmkKRE8GE424sksyt1tJVFZwu",
+ "value": "0"
+ },
+ "LowNode": "14f"
+ },
+ "LedgerEntryType": "RippleState",
+ "LedgerIndex": "5F1DFBA426DB53A57ECEFE3EAEB804107DA1B899D4A90C794E577E5082D7C125",
+ "PreviousFields": {
+ "Balance": {
+ "currency": "5553444300000000000000000000000000000000",
+ "issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji",
+ "value": "0"
+ }
+ },
+ "PreviousTxnID": "6947EB21D0A745A96F9B2F9B5BABFFE43CE66AF8488365DBC112BC38F8FAC0EA",
+ "PreviousTxnLgrSeq": 88214779
+ }
+ },
+ {
+ "ModifiedNode": {
+ "FinalFields": {
+ "AMMID": "383669860F36CDD7BF543C0711DD523E35F60ACA22C8AAD8FDDBB2632C4C5821",
+ "Account": "rGHt6LT5v9DVaEAmFzj5ciuxuj41ZjLofs",
+ "Balance": "534441055888",
+ "Flags": 26214400,
+ "OwnerCount": 1,
+ "Sequence": 86795379
+ },
+ "LedgerEntryType": "AccountRoot",
+ "LedgerIndex": "AEAC617A4934A4E1029581A159AF8EB3356E4DE3C52324D9D5A73C00B8828BCF",
+ "PreviousFields": { "Balance": "534390431196" },
+ "PreviousTxnID": "1FADA966F7F1E8465E17DE2F83216663BE8360B172AC3A0D13FC81757C64AC42",
+ "PreviousTxnLgrSeq": 88214747
+ }
+ },
+ {
+ "ModifiedNode": {
+ "FinalFields": {
+ "Balance": {
+ "currency": "5553444300000000000000000000000000000000",
+ "issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji",
+ "value": "-284983.085646821"
+ },
+ "Flags": 16908288,
+ "HighLimit": {
+ "currency": "5553444300000000000000000000000000000000",
+ "issuer": "rGHt6LT5v9DVaEAmFzj5ciuxuj41ZjLofs",
+ "value": "0"
+ },
+ "HighNode": "0",
+ "LowLimit": {
+ "currency": "5553444300000000000000000000000000000000",
+ "issuer": "rcEGREd8NmkKRE8GE424sksyt1tJVFZwu",
+ "value": "0"
+ },
+ "LowNode": "10a"
+ },
+ "LedgerEntryType": "RippleState",
+ "LedgerIndex": "CDE55A8290DF221A643F51798BE6294A69384066D1ECFB7C2B9BEE2004B38D63",
+ "PreviousFields": {
+ "Balance": {
+ "currency": "5553444300000000000000000000000000000000",
+ "issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji",
+ "value": "-285009.9635018237"
+ }
+ },
+ "PreviousTxnID": "1FADA966F7F1E8465E17DE2F83216663BE8360B172AC3A0D13FC81757C64AC42",
+ "PreviousTxnLgrSeq": 88214747
+ }
+ }
+ ],
+ "DeliveredAmount": {
+ "currency": "5553444300000000000000000000000000000000",
+ "issuer": "rcEGREd8NmkKRE8GE424sksyt1tJVFZwu",
+ "value": "26.79746261485543"
+ },
+ "TransactionIndex": 18,
+ "TransactionResult": "tesSUCCESS",
+ "delivered_amount": {
+ "currency": "5553444300000000000000000000000000000000",
+ "issuer": "rcEGREd8NmkKRE8GE424sksyt1tJVFZwu",
+ "value": "26.79746261485543"
+ }
+ },
+ "validated": "undefined",
+ "metaData": "undefined",
+ "status": "undefined"
+ },
+ "meta": {
+ "AffectedNodes": [
+ {
+ "ModifiedNode": {
+ "FinalFields": {
+ "Account": "r3GphWJaShhd1RNFnCaGMVyMawYcKHXCvo",
+ "Balance": "15877383",
+ "Flags": 0,
+ "OwnerCount": 1,
+ "Sequence": 88214148
+ },
+ "LedgerEntryType": "AccountRoot",
+ "LedgerIndex": "4ACE281E26640B951CB4A23768CA6C7E1B0F602053B3C86A653847FE5A5AA1ED",
+ "PreviousFields": { "Balance": "66502087", "Sequence": 88214147 },
+ "PreviousTxnID": "6947EB21D0A745A96F9B2F9B5BABFFE43CE66AF8488365DBC112BC38F8FAC0EA",
+ "PreviousTxnLgrSeq": 88214779
+ }
+ },
+ {
+ "ModifiedNode": {
+ "FinalFields": {
+ "Balance": {
+ "currency": "5553444300000000000000000000000000000000",
+ "issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji",
+ "value": "-26.79746261485543"
+ },
+ "Flags": 2228224,
+ "HighLimit": {
+ "currency": "5553444300000000000000000000000000000000",
+ "issuer": "r3GphWJaShhd1RNFnCaGMVyMawYcKHXCvo",
+ "value": "775905"
+ },
+ "HighNode": "0",
+ "LowLimit": {
+ "currency": "5553444300000000000000000000000000000000",
+ "issuer": "rcEGREd8NmkKRE8GE424sksyt1tJVFZwu",
+ "value": "0"
+ },
+ "LowNode": "14f"
+ },
+ "LedgerEntryType": "RippleState",
+ "LedgerIndex": "5F1DFBA426DB53A57ECEFE3EAEB804107DA1B899D4A90C794E577E5082D7C125",
+ "PreviousFields": {
+ "Balance": {
+ "currency": "5553444300000000000000000000000000000000",
+ "issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji",
+ "value": "0"
+ }
+ },
+ "PreviousTxnID": "6947EB21D0A745A96F9B2F9B5BABFFE43CE66AF8488365DBC112BC38F8FAC0EA",
+ "PreviousTxnLgrSeq": 88214779
+ }
+ },
+ {
+ "ModifiedNode": {
+ "FinalFields": {
+ "AMMID": "383669860F36CDD7BF543C0711DD523E35F60ACA22C8AAD8FDDBB2632C4C5821",
+ "Account": "rGHt6LT5v9DVaEAmFzj5ciuxuj41ZjLofs",
+ "Balance": "534441055888",
+ "Flags": 26214400,
+ "OwnerCount": 1,
+ "Sequence": 86795379
+ },
+ "LedgerEntryType": "AccountRoot",
+ "LedgerIndex": "AEAC617A4934A4E1029581A159AF8EB3356E4DE3C52324D9D5A73C00B8828BCF",
+ "PreviousFields": { "Balance": "534390431196" },
+ "PreviousTxnID": "1FADA966F7F1E8465E17DE2F83216663BE8360B172AC3A0D13FC81757C64AC42",
+ "PreviousTxnLgrSeq": 88214747
+ }
+ },
+ {
+ "ModifiedNode": {
+ "FinalFields": {
+ "Balance": {
+ "currency": "5553444300000000000000000000000000000000",
+ "issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji",
+ "value": "-284983.085646821"
+ },
+ "Flags": 16908288,
+ "HighLimit": {
+ "currency": "5553444300000000000000000000000000000000",
+ "issuer": "rGHt6LT5v9DVaEAmFzj5ciuxuj41ZjLofs",
+ "value": "0"
+ },
+ "HighNode": "0",
+ "LowLimit": {
+ "currency": "5553444300000000000000000000000000000000",
+ "issuer": "rcEGREd8NmkKRE8GE424sksyt1tJVFZwu",
+ "value": "0"
+ },
+ "LowNode": "10a"
+ },
+ "LedgerEntryType": "RippleState",
+ "LedgerIndex": "CDE55A8290DF221A643F51798BE6294A69384066D1ECFB7C2B9BEE2004B38D63",
+ "PreviousFields": {
+ "Balance": {
+ "currency": "5553444300000000000000000000000000000000",
+ "issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji",
+ "value": "-285009.9635018237"
+ }
+ },
+ "PreviousTxnID": "1FADA966F7F1E8465E17DE2F83216663BE8360B172AC3A0D13FC81757C64AC42",
+ "PreviousTxnLgrSeq": 88214747
+ }
+ }
+ ],
+ "DeliveredAmount": {
+ "currency": "5553444300000000000000000000000000000000",
+ "issuer": "rcEGREd8NmkKRE8GE424sksyt1tJVFZwu",
+ "value": "26.79746261485543"
+ },
+ "TransactionIndex": 18,
+ "TransactionResult": "tesSUCCESS",
+ "delivered_amount": {
+ "currency": "5553444300000000000000000000000000000000",
+ "issuer": "rcEGREd8NmkKRE8GE424sksyt1tJVFZwu",
+ "value": "26.79746261485543"
+ }
+ },
+ "hash": "2C9C11811DFC51263267F7D5AA81C807876B905A99AE52321C582D8E186B7E12",
+ "ledger_index": 88214932,
+ "date": 1716579292000
+}
diff --git a/src/containers/Transactions/transaction.scss b/src/containers/Transactions/transaction.scss
index 7c80014fc..45997a886 100644
--- a/src/containers/Transactions/transaction.scss
+++ b/src/containers/Transactions/transaction.scss
@@ -1,4 +1,4 @@
-@import '../shared/css/variables';
+@use '../shared/css/variables' as *;
.transaction {
position: relative;
diff --git a/src/containers/Validators/historyTab.scss b/src/containers/Validators/historyTab.scss
index 4112abe75..69127e96f 100644
--- a/src/containers/Validators/historyTab.scss
+++ b/src/containers/Validators/historyTab.scss
@@ -1,4 +1,4 @@
-@import '../shared/css/variables';
+@use '../shared/css/variables' as *;
.history-table {
// Col overall styling
diff --git a/src/containers/Validators/simpleTab.scss b/src/containers/Validators/simpleTab.scss
index 224a55cc7..f95bbde77 100644
--- a/src/containers/Validators/simpleTab.scss
+++ b/src/containers/Validators/simpleTab.scss
@@ -1,4 +1,4 @@
-@import '../shared/css/variables';
+@use '../shared/css/variables' as *;
.simple-body-validator {
.row {
diff --git a/src/containers/Validators/validator.scss b/src/containers/Validators/validator.scss
index 58a5bfe75..7cda86f67 100644
--- a/src/containers/Validators/validator.scss
+++ b/src/containers/Validators/validator.scss
@@ -1,4 +1,4 @@
-@import '../shared/css/variables';
+@use '../shared/css/variables' as *;
.validator {
position: relative;
diff --git a/src/containers/Validators/votingTab.scss b/src/containers/Validators/votingTab.scss
index 71b6cd95b..eb6c7a489 100644
--- a/src/containers/Validators/votingTab.scss
+++ b/src/containers/Validators/votingTab.scss
@@ -1,4 +1,4 @@
-@import '../shared/css/variables';
+@use '../shared/css/variables' as *;
.voting {
margin-left: 24px;
diff --git a/src/containers/shared/Interfaces.tsx b/src/containers/shared/Interfaces.tsx
index 201b0daea..83c03903f 100644
--- a/src/containers/shared/Interfaces.tsx
+++ b/src/containers/shared/Interfaces.tsx
@@ -36,6 +36,20 @@ export interface NFTFormattedInfo {
warnings?: string[]
}
+/**
+ * Values returned by 'formatMPTIssuanceInfo' from /src/rippled/lib/utils.js
+ */
+export interface MPTIssuanceFormattedInfo {
+ issuer: string
+ sequence: number
+ assetScale?: number
+ maxAmt?: string
+ outstandingAmt?: string
+ flags?: string[]
+ transferFee?: number
+ metadata?: string
+}
+
export interface ErrorMessage {
title: string
hints: string[]
diff --git a/src/containers/shared/SocketContext.tsx b/src/containers/shared/SocketContext.tsx
index a8ed6f217..17ef964e4 100644
--- a/src/containers/shared/SocketContext.tsx
+++ b/src/containers/shared/SocketContext.tsx
@@ -29,11 +29,13 @@ function getSocket(rippledUrl?: string): ExplorerXrplClient {
if (host?.includes(':')) {
wsUrls.push(`${prefix}://${host}`)
+ } else if (process.env.VITE_RIPPLED_WS_PORT) {
+ wsUrls.push(`${prefix}://${host}:${process.env.VITE_RIPPLED_WS_PORT}`)
+ if (process.env.VITE_ENVIRONMENT === 'custom') {
+ wsUrls.push(`${prefix}://${host}`)
+ }
} else {
- wsUrls.push.apply(wsUrls, [
- `${prefix}://${host}:${process.env.VITE_RIPPLED_WS_PORT}`,
- `${prefix}://${host}:443`,
- ])
+ wsUrls.push(`${prefix}://${host}`)
}
})
diff --git a/src/containers/shared/amendmentUtils.ts b/src/containers/shared/amendmentUtils.ts
index d1961bd22..b9e21af55 100644
--- a/src/containers/shared/amendmentUtils.ts
+++ b/src/containers/shared/amendmentUtils.ts
@@ -1,14 +1,9 @@
-import { sha512 } from '@xrplf/isomorphic/sha512'
-import { hexToBytes, bytesToHex, stringToHex } from '@xrplf/isomorphic/utils'
import axios from 'axios'
import { localizeDate } from './utils'
-const cachedAmendmentIDs: any = {}
let cachedRippledVersions: any = {}
-const ACTIVE_AMENDMENT_REGEX = /^\s*REGISTER_F[A-Z]+\s*\((\S+),\s*.*$/
-const RETIRED_AMENDMENT_REGEX = /^ .*retireFeature\("(\S+)"\)[,;].*$/
const TIME_ZONE = 'UTC'
const DATE_OPTIONS = {
hour: 'numeric',
@@ -31,42 +26,6 @@ export function getExpectedDate(date: string, language: string) {
)
}
-async function fetchAmendmentNames() {
- const response = await axios.get(
- 'https://raw.githubusercontent.com/ripple/rippled/develop/src/ripple/protocol/impl/Feature.cpp',
- )
- const text = response.data
-
- const amendmentNames: string[] = []
- text.split('\n').forEach((line: string) => {
- const name = line.match(ACTIVE_AMENDMENT_REGEX)
- if (name) {
- amendmentNames.push(name[1])
- } else {
- const name2 = line.match(RETIRED_AMENDMENT_REGEX)
- name2 && amendmentNames.push(name2[1])
- }
- })
- return amendmentNames
-}
-
-function sha512Half(bytes: Uint8Array) {
- return bytesToHex(sha512(bytes)).slice(0, 64)
-}
-
-export async function nameOfAmendmentID(id: string) {
- if (cachedAmendmentIDs[id]) {
- return cachedAmendmentIDs[id]
- }
- // The Amendment ID is the hash of the Amendment name
- const amendmentNames = await fetchAmendmentNames()
- amendmentNames.forEach((name) => {
- cachedAmendmentIDs[sha512Half(hexToBytes(stringToHex(name)))] = name
- })
-
- return cachedAmendmentIDs[id]
-}
-
async function fetchMinRippledVersions() {
const response = await axios.get(
'https://raw.githubusercontent.com/XRPLF/xrpl-dev-portal/master/resources/known-amendments.md',
diff --git a/src/containers/shared/analytics.ts b/src/containers/shared/analytics.ts
index bff40dcfb..305c43521 100644
--- a/src/containers/shared/analytics.ts
+++ b/src/containers/shared/analytics.ts
@@ -10,6 +10,7 @@ export type AnalyticsEventNames =
| 'network_switch'
| 'load_more'
| 'not_found'
+ | 'token_search_click'
export interface AnalyticsFields {
network?: string
@@ -27,6 +28,7 @@ export interface AnalyticsFields {
search_term?: string
search_category?: string
validator?: string
+ mpt_issuance_id?: string
description?: string
page_title?: string
diff --git a/src/containers/shared/components/Amount.tsx b/src/containers/shared/components/Amount.tsx
index 164b06737..1a767a796 100644
--- a/src/containers/shared/components/Amount.tsx
+++ b/src/containers/shared/components/Amount.tsx
@@ -1,8 +1,15 @@
+import { useQuery } from 'react-query'
+import { useContext } from 'react'
import { CURRENCY_OPTIONS, XRP_BASE } from '../transactionUtils'
import { useLanguage } from '../hooks'
-import { localizeNumber } from '../utils'
+import { localizeNumber, convertScaledPrice } from '../utils'
import Currency from './Currency'
import { ExplorerAmount } from '../types'
+import { MPTIssuanceFormattedInfo } from '../Interfaces'
+import { getMPTIssuance } from '../../../rippled/lib/rippled'
+import { formatMPTIssuanceInfo } from '../../../rippled/lib/utils'
+import SocketContext from '../SocketContext'
+import { useAnalytics } from '../analytics'
export interface AmountProps {
value: ExplorerAmount | string
@@ -16,15 +23,17 @@ export const Amount = ({
value,
}: AmountProps) => {
const language = useLanguage()
+ const rippledSocket = useContext(SocketContext)
+ const { trackException } = useAnalytics()
const issuer = typeof value === 'string' ? undefined : value.issuer
const currency = typeof value === 'string' ? 'XRP' : value.currency
const amount =
typeof value === 'string' ? parseInt(value, 10) / XRP_BASE : value.amount
+ const isMPT = typeof value === 'string' ? false : value.isMPT ?? false
const options = { ...CURRENCY_OPTIONS, currency }
- const localizedAmount = localizeNumber(amount, language, options)
- return (
+ const renderAmount = (localizedAmount) => (
{modifier && {modifier}}
@@ -35,7 +44,43 @@ export const Amount = ({
currency={currency}
link
displaySymbol={false}
+ isMPT={isMPT}
/>
)
+
+ const mptID = isMPT ? (value as ExplorerAmount).currency : null
+
+ // fetch MPTIssuance only if isMPT is true
+ const { data: mptIssuanceData } =
+ useQuery(
+ ['getMPTIssuanceScale', mptID],
+ async () => {
+ const info = await getMPTIssuance(rippledSocket, mptID)
+ return formatMPTIssuanceInfo(info)
+ },
+ {
+ onError: (e: any) => {
+ trackException(`mptIssuance ${mptID} --- ${JSON.stringify(e)}`)
+ },
+ enabled: isMPT,
+ },
+ ) || {}
+
+ // if amount is MPT type, we need to fetch the scale from the MPTokenIssuance
+ // object so we can show the scaled amount
+ if (isMPT && typeof value !== 'string') {
+ if (mptIssuanceData) {
+ const scale = mptIssuanceData.assetScale ?? 0
+ const scaledAmount = convertScaledPrice(
+ parseInt(amount as string, 10).toString(16),
+ scale,
+ )
+
+ return renderAmount(localizeNumber(scaledAmount, language, {}, true))
+ }
+ return null
+ }
+
+ return renderAmount(localizeNumber(amount, language, options))
}
diff --git a/src/containers/shared/components/Currency.tsx b/src/containers/shared/components/Currency.tsx
index 64727ca0a..ac48efa7c 100644
--- a/src/containers/shared/components/Currency.tsx
+++ b/src/containers/shared/components/Currency.tsx
@@ -1,5 +1,5 @@
import { RouteLink } from '../routing'
-import { TOKEN_ROUTE } from '../../App/routes'
+import { TOKEN_ROUTE, MPT_ROUTE } from '../../App/routes'
// https://xrpl.org/currency-formats.html#nonstandard-currency-codes
const NON_STANDARD_CODE_LENGTH = 40
@@ -12,6 +12,7 @@ export interface Props {
link?: boolean
shortenIssuer?: boolean
displaySymbol?: boolean
+ isMPT?: boolean
}
/*
@@ -25,33 +26,46 @@ const Currency = (props: Props) => {
link = true,
shortenIssuer = false,
displaySymbol = true,
+ isMPT = false,
} = props
+ let content
- const currencyCode =
- currency?.length === NON_STANDARD_CODE_LENGTH &&
- currency?.substring(0, 2) !== LP_TOKEN_IDENTIFIER
- ? hexToString(currency)
- : currency
-
- let display = `${currencyCode}`
-
- if (currencyCode === XRP && displaySymbol) {
- display = `\uE900 ${display}`
- }
-
- if (issuer) {
- display += '.'
- display += shortenIssuer ? issuer.substring(0, 4) : issuer
- }
-
- const content =
- link && issuer ? (
-
+ if (isMPT) {
+ const display = `MPT (${currency})`
+ content = link ? (
+
{display}
) : (
display
)
+ } else {
+ const currencyCode =
+ currency?.length === NON_STANDARD_CODE_LENGTH &&
+ currency?.substring(0, 2) !== LP_TOKEN_IDENTIFIER
+ ? hexToString(currency)
+ : currency
+
+ let display = `${currencyCode}`
+
+ if (currencyCode === XRP && displaySymbol) {
+ display = `\uE900 ${display}`
+ }
+
+ if (issuer) {
+ display += '.'
+ display += shortenIssuer ? issuer.substring(0, 4) : issuer
+ }
+
+ content =
+ link && issuer ? (
+
+ {display}
+
+ ) : (
+ display
+ )
+ }
return {content}
}
diff --git a/src/containers/shared/components/DomainLink.tsx b/src/containers/shared/components/DomainLink.tsx
index 512482d35..4afe8e831 100644
--- a/src/containers/shared/components/DomainLink.tsx
+++ b/src/containers/shared/components/DomainLink.tsx
@@ -5,13 +5,15 @@ export interface Props {
className?: string
decode?: boolean
domain: string
+ keepProtocol?: boolean
}
// Matches a protocol (e.g. 'http://' or 'https://') at the start of a string.
const PROTOCOL_REGEX = /^([a-z][a-z0-9+\-.]*):\/\//
+const PROTOCOL_REMOVAL_REGEX = /^(https?:\/\/)?(.*?)(\/)?$/
const DomainLink = (props: Props) => {
- const { className, decode = false, domain } = props
+ const { className, decode = false, domain, keepProtocol = true } = props
// If decode is true, decode the domain
const decodedDomain = decode ? decodeHex(domain) : domain
@@ -26,14 +28,19 @@ const DomainLink = (props: Props) => {
href = href.replace('ipfs://', 'https://ipfs.io/ipfs/')
}
+ const domainText = keepProtocol
+ ? decodedDomain
+ : decodedDomain.replace(PROTOCOL_REMOVAL_REGEX, '$2')
+
return (
event.stopPropagation()}
>
- {decode ? decodeHex(domain) : domain}
+ {domainText}
)
}
diff --git a/src/containers/shared/components/Dropdown/dropdown.scss b/src/containers/shared/components/Dropdown/dropdown.scss
index 5d23922e5..b49e23cb4 100644
--- a/src/containers/shared/components/Dropdown/dropdown.scss
+++ b/src/containers/shared/components/Dropdown/dropdown.scss
@@ -1,4 +1,4 @@
-@import '../../css/variables';
+@use '../../css/variables' as *;
.dropdown {
position: relative;
diff --git a/src/containers/shared/components/JsonView/json-view.scss b/src/containers/shared/components/JsonView/json-view.scss
index ff09d9907..0ca1c7de1 100644
--- a/src/containers/shared/components/JsonView/json-view.scss
+++ b/src/containers/shared/components/JsonView/json-view.scss
@@ -1,4 +1,4 @@
-@import '../../css/variables';
+@use '../../css/variables' as *;
@import 'react18-json-view/src/style.css';
.json-view {
diff --git a/src/containers/shared/components/MPTokenLink.tsx b/src/containers/shared/components/MPTokenLink.tsx
new file mode 100644
index 000000000..f1c2198bb
--- /dev/null
+++ b/src/containers/shared/components/MPTokenLink.tsx
@@ -0,0 +1,12 @@
+import { RouteLink } from '../routing'
+import { MPT_ROUTE } from '../../App/routes'
+
+export interface MPTokenLinkProps {
+ tokenID: string
+}
+
+export const MPTokenLink = ({ tokenID }: MPTokenLinkProps) => (
+
+ {tokenID}
+
+)
diff --git a/src/containers/shared/components/Notification/styles.scss b/src/containers/shared/components/Notification/styles.scss
index 9844d2327..a2dd6dad3 100644
--- a/src/containers/shared/components/Notification/styles.scss
+++ b/src/containers/shared/components/Notification/styles.scss
@@ -1,4 +1,4 @@
-@import '../../css/variables';
+@use '../../css/variables' as *;
.notification {
border: 1px solid;
diff --git a/src/containers/shared/components/TokenSearchResults/TokenSearchResults.tsx b/src/containers/shared/components/TokenSearchResults/TokenSearchResults.tsx
new file mode 100644
index 000000000..d2418319d
--- /dev/null
+++ b/src/containers/shared/components/TokenSearchResults/TokenSearchResults.tsx
@@ -0,0 +1,95 @@
+import { useContext } from 'react'
+import './styles.scss'
+
+import { useTranslation } from 'react-i18next'
+import axios from 'axios'
+import { useQuery } from 'react-query'
+import { useAnalytics } from '../../analytics'
+import { TokenSearchRow } from './TokenSearchRow'
+import SocketContext from '../../SocketContext'
+import Log from '../../log'
+import { getAccountLines } from '../../../../rippled/lib/rippled'
+import { FETCH_INTERVAL_XRP_USD_ORACLE_MILLIS } from '../../utils'
+
+const ORACLE_ACCOUNT = 'rXUMMaPpZqPutoRszR29jtC8amWq3APkx'
+
+interface SearchResultsProps {
+ currentSearchValue: string
+ setCurrentSearchInput: (string) => void
+}
+
+const SearchResults = ({
+ currentSearchValue,
+ setCurrentSearchInput,
+}: SearchResultsProps): JSX.Element | null => {
+ const analytics = useAnalytics()
+ const { t } = useTranslation()
+ const rippledSocket = useContext(SocketContext)
+
+ const { data: XRPUSDPrice = 0.0 } = useQuery(
+ ['fetchXRPToUSDRate'],
+ () => fetchXRPToUSDRate(),
+ {
+ refetchInterval: FETCH_INTERVAL_XRP_USD_ORACLE_MILLIS,
+ onError: (error) => {
+ Log.error(error)
+ return 0.0
+ },
+ },
+ )
+
+ const { data: tokens = [] } = useQuery(
+ ['fetchTokens', currentSearchValue],
+ () => fetchTokens(),
+ {
+ enabled: !!currentSearchValue,
+ staleTime: 0,
+ keepPreviousData: false,
+ onError: (error) => Log.error(error),
+ },
+ )
+
+ const fetchXRPToUSDRate = () =>
+ getAccountLines(rippledSocket, ORACLE_ACCOUNT, 1).then(
+ (accountLines) => accountLines.lines[0]?.limit ?? 0.0,
+ )
+
+ const fetchTokens = () => {
+ if (currentSearchValue === '') {
+ return [] // Return an empty list if search is cleared
+ }
+
+ return axios
+ .get(`/api/v1/tokens/search/${currentSearchValue}`)
+ .then((response) => response.data.tokens)
+ }
+
+ const onLinkClick = () => {
+ analytics.track('token_search_click', {
+ search_category: 'token',
+ search_term: currentSearchValue,
+ })
+
+ // clear current search on navigation
+ setCurrentSearchInput('')
+ }
+
+ return tokens.length > 0 ? (
+
+
+ {t('tokens')} ({tokens.length})
+
+
+ {tokens.map((token) => (
+
+ ))}
+
+ ) : null
+}
+
+export default SearchResults
diff --git a/src/containers/shared/components/TokenSearchResults/TokenSearchRow.tsx b/src/containers/shared/components/TokenSearchResults/TokenSearchRow.tsx
new file mode 100644
index 000000000..47bdaef51
--- /dev/null
+++ b/src/containers/shared/components/TokenSearchResults/TokenSearchRow.tsx
@@ -0,0 +1,122 @@
+import { Link } from 'react-router-dom'
+import { useTranslation } from 'react-i18next'
+import { FC } from 'react'
+import { Amount } from '../Amount'
+import { localizeNumber } from '../../utils'
+import Currency from '../Currency'
+import DomainLink from '../DomainLink'
+
+const parsePrice = (dollarPrice: string, xrpPrice: number): number => {
+ const parsedDollar = Number(dollarPrice)
+ return Number((parsedDollar * xrpPrice).toFixed(6))
+}
+
+const TokenLogo: FC<{ token: any }> = ({ token }) =>
+ token && token.meta?.token.icon ? (
+
+ ) : (
+
+ )
+
+const TokenName: FC<{ token: any }> = ({ token }) =>
+ token && token.meta?.token.name ? (
+
+ (
+ {token.meta.token.name
+ .trim()
+ .toUpperCase()
+ .replace('(', '')
+ .replace(')', '')}
+ )
+
+ ) : null
+
+const IssuerAddress: FC<{ token: any; onClick: any }> = ({ token, onClick }) =>
+ token && (token.issuer || token.meta?.issuer) ? (
+
+
+ {token.meta.issuer.name && `${token.meta.issuer.name} (`}
+
+ {token.issuer}
+ {token.meta.issuer.name && )
}
+
+ ) : null
+
+interface SearchResultRowProps {
+ token: any
+ onClick: () => void
+ xrpPrice: number
+}
+
+export const TokenSearchRow = ({
+ token,
+ onClick,
+ xrpPrice,
+}: SearchResultRowProps): JSX.Element => {
+ const { t } = useTranslation()
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {t('holders', {
+ holders: localizeNumber(token.metrics.holders),
+ })}
+
+
+ {t('trustlines', {
+ trustlines: localizeNumber(token.metrics.trustlines),
+ })}
+
+
+
+
+ {token.meta.issuer.domain && (
+ <>
+
{t('website')}:
+
+
+
+ >
+ )}
+
+
+ )
+}
diff --git a/src/containers/shared/components/TokenSearchResults/styles.scss b/src/containers/shared/components/TokenSearchResults/styles.scss
new file mode 100644
index 000000000..903d9688d
--- /dev/null
+++ b/src/containers/shared/components/TokenSearchResults/styles.scss
@@ -0,0 +1,142 @@
+@use '../../css/variables' as *;
+
+.search-results-menu {
+ position: absolute;
+ z-index: 1;
+ width: 100%;
+ max-height: 400px;
+ border: 1px solid $black-80;
+ border-radius: 8px;
+ border-top: 0px;
+ margin-top: 6px;
+ background-color: $black-100;
+ box-shadow: 2px 2px 15px 0px rgb(131 131 134 / 30%);
+ overflow-wrap: anywhere;
+ overflow-y: scroll;
+ scrollbar-color: $black-50 transparent;
+ scrollbar-width: thin;
+
+ @media (width < 900px) {
+ max-width: calc(100% - 32px);
+ }
+}
+
+.search-results-header {
+ padding: 0.5rem 0rem 0.5rem 1rem;
+ background-color: $black-80;
+ font-size: 14px;
+ font-weight: 500;
+}
+
+.result-row-icon {
+ width: 1.5rem;
+ height: 1.5rem;
+ border-radius: 16px;
+}
+
+.no-logo {
+ border-radius: 50%;
+ background-color: $black-50;
+}
+
+.result-currency {
+ flex-shrink: 0;
+ padding-top: 2px;
+ padding-right: 0px;
+ padding-bottom: 2px;
+ white-space: nowrap;
+}
+
+.result-token-name {
+ padding-top: 2px;
+ padding-bottom: 2px;
+ margin-left: 3px;
+}
+
+.result-logo {
+ padding-right: 14px;
+ padding-left: 1rem;
+}
+
+.search-result-row {
+ position: relative;
+ display: flex;
+ width: 100%;
+ box-sizing: border-box;
+ padding-top: 13px;
+ padding-bottom: 13px;
+ color: $black-0;
+ font-size: 14px;
+ font-weight: 500;
+
+ &:hover {
+ background-color: $black-70;
+ color: $black-0;
+ cursor: pointer;
+ }
+}
+
+.result-name-line {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ margin-bottom: 0.5rem;
+ white-space: nowrap;
+}
+
+.result-issuer-line {
+ display: flex;
+ overflow: hidden;
+ padding: 0 1rem;
+ margin-bottom: 0.5rem;
+ color: $black-50;
+}
+
+.result-website-line {
+ display: flex;
+ flex-direction: row;
+ padding: 0 1rem;
+ color: $black-50;
+}
+
+.metric-chip {
+ padding: 2px 12px;
+ border: 1px solid $black-70;
+ border-radius: 100px;
+ margin-right: 8px;
+ margin-left: 8px;
+ background-color: $black-100;
+ font-size: 12px;
+ font-weight: 600;
+
+ &:hover {
+ background-color: $black-100;
+ cursor: pointer;
+ }
+}
+
+.issuer-link {
+ display: inline-flex !important;
+ overflow: hidden;
+ margin-left: 0.25rem;
+ color: $black-50;
+
+ &:hover {
+ background: transparent;
+ color: $blue-purple-30;
+ }
+
+}
+
+.issuer-address {
+ @extend %truncate;
+}
+
+.issuer-name,
+.issuer-title {
+ flex-shrink: 0;
+}
+
+.result-domain-link {
+ margin-left: 0.25rem;
+}
diff --git a/src/containers/shared/components/TokenSearchResults/test/TokenSearchResults.test.js b/src/containers/shared/components/TokenSearchResults/test/TokenSearchResults.test.js
new file mode 100644
index 000000000..a3392b28a
--- /dev/null
+++ b/src/containers/shared/components/TokenSearchResults/test/TokenSearchResults.test.js
@@ -0,0 +1,75 @@
+import { mount } from 'enzyme'
+import moxios from 'moxios'
+import i18n from '../../../../../i18n/testConfig'
+import testTokens from './mock_data/tokens.json'
+import SocketContext from '../../../SocketContext'
+import SearchResults from '../TokenSearchResults'
+import MockWsClient from '../../../../test/mockWsClient'
+import { QuickHarness } from '../../../../test/utils'
+
+const testQuery = 'test'
+
+describe('Testing tokens search', () => {
+ let client
+ let wrapper
+
+ const createWrapper = () => {
+ const searchURL = `/api/v1/tokens/search/${testQuery}`
+ moxios.stubRequest(searchURL, {
+ status: 200,
+ response: testTokens,
+ })
+ return mount(
+
+
+
+
+ ,
+ )
+ }
+
+ beforeEach(() => {
+ moxios.install()
+ client = new MockWsClient()
+ wrapper = createWrapper()
+ })
+
+ afterEach(() => {
+ client.close()
+ moxios.uninstall()
+ wrapper.unmount()
+ })
+
+ it('renders without crashing', () => {
+ wrapper.update()
+ const searchMenu = wrapper.find('.search-results-menu')
+ expect(searchMenu.length).toEqual(1)
+ })
+
+ it('renders all tokens ', () => {
+ wrapper.update()
+ const searchMenu = wrapper.find('.search-results-menu')
+
+ expect(searchMenu.find('.search-results-header').at(0).html()).toEqual(
+ ``,
+ )
+ expect(searchMenu.find('.currency').at(0).html()).toEqual(
+ `SOLO`,
+ )
+ expect(searchMenu.find('.issuer-name').at(0).html()).toEqual(
+ `Sologenic (
`,
+ )
+ expect(searchMenu.find('.issuer-address').at(0).html()).toEqual(
+ `rsoLo2S1kiGeCcn6hCUXVrCpGMWLrRrLZz
`,
+ )
+ expect(
+ searchMenu.find('.search-result-row').at(0).find('.metric-chip').length,
+ ).toEqual(3)
+ expect(searchMenu.find('.domain').at(0).html()).toEqual(
+ `sologenic.com`,
+ )
+ })
+})
diff --git a/src/containers/shared/components/TokenSearchResults/test/mock_data/tokens.json b/src/containers/shared/components/TokenSearchResults/test/mock_data/tokens.json
new file mode 100644
index 000000000..eae88720e
--- /dev/null
+++ b/src/containers/shared/components/TokenSearchResults/test/mock_data/tokens.json
@@ -0,0 +1,45 @@
+{
+ "tokens": [
+ {
+ "currency": "534F4C4F00000000000000000000000000000000",
+ "issuer": "rsoLo2S1kiGeCcn6hCUXVrCpGMWLrRrLZz",
+ "meta": {
+ "token": {
+ "description": "SOLO is the utility token for the Sologenic ecosystem. It can be used for covering fees when minting NFTs on their platform.",
+ "icon": "https://s1.xrplmeta.org/icon/C40439709A.png",
+ "name": "SOLO",
+ "trust_level": 3
+ },
+ "issuer": {
+ "domain": "sologenic.com",
+ "icon": "https://s1.xrplmeta.org/icon/C40439709A.png",
+ "kyc": true,
+ "name": "Sologenic",
+ "trust_level": 3,
+ "weblinks": [
+ {
+ "url": "https://sologenic.com"
+ },
+ {
+ "url": "https://twitter.com/realSologenic",
+ "type": "socialmedia"
+ }
+ ]
+ }
+ },
+ "metrics": {
+ "trustlines": 283920,
+ "holders": 227379,
+ "supply": "399057268.13655",
+ "marketcap": "73737104.7448899",
+ "price": "0.184476049183255",
+ "volume_24h": "97065.7035689995",
+ "volume_7d": "1387820.55867489",
+ "exchanges_24h": "3446",
+ "exchanges_7d": "38976",
+ "takers_24h": "125",
+ "takers_7d": "627"
+ }
+ }
+ ]
+}
diff --git a/src/containers/shared/components/Tooltip/Tooltip.tsx b/src/containers/shared/components/Tooltip/Tooltip.tsx
index 7154eb8ca..673cd72de 100644
--- a/src/containers/shared/components/Tooltip/Tooltip.tsx
+++ b/src/containers/shared/components/Tooltip/Tooltip.tsx
@@ -91,6 +91,8 @@ export const Tooltip = ({ tooltip }: { tooltip?: TooltipInstance }) => {
>
)
+ const renderMPTId = () => {data.tokenId}
+
const { x, y, mode } = tooltip
const style: CSSProperties = { top: y + PADDING_Y, left: x }
const modeMap = {
@@ -100,6 +102,7 @@ export const Tooltip = ({ tooltip }: { tooltip?: TooltipInstance }) => {
missing: renderMissingValidators,
paystring: renderPayStringToolTip,
nftId: renderNFTId,
+ mptId: renderMPTId,
}
return modeMap[mode] ? (
diff --git a/src/containers/shared/components/Transaction/Clawback/Description.tsx b/src/containers/shared/components/Transaction/Clawback/Description.tsx
index ec59c0a96..42641b95c 100644
--- a/src/containers/shared/components/Transaction/Clawback/Description.tsx
+++ b/src/containers/shared/components/Transaction/Clawback/Description.tsx
@@ -6,8 +6,8 @@ import { formatAmount } from '../../../../../rippled/lib/txSummary/formatAmount'
export const Description = ({ data }: TransactionDescriptionProps) => {
const issuer = data.tx.Account
- const holder = data.tx.Amount.issuer
const amount = formatAmount(data.tx.Amount)
+ const holder = amount.isMPT ? data.tx.Holder : data.tx.Amount.issuer
amount.issuer = issuer
return (
<>
diff --git a/src/containers/shared/components/Transaction/Clawback/parser.ts b/src/containers/shared/components/Transaction/Clawback/parser.ts
index 0da7126b7..0f96430df 100644
--- a/src/containers/shared/components/Transaction/Clawback/parser.ts
+++ b/src/containers/shared/components/Transaction/Clawback/parser.ts
@@ -1,7 +1,10 @@
import { Clawback, ClawbackInstructions } from './types'
import { TransactionParser } from '../types'
import { formatAmount } from '../../../../../rippled/lib/txSummary/formatAmount'
-import { computeBalanceChange } from '../../../utils'
+import {
+ computeRippleStateBalanceChange,
+ computeMPTokenBalanceChange,
+} from '../../../utils'
export const parser: TransactionParser = (
tx,
@@ -9,6 +12,37 @@ export const parser: TransactionParser = (
) => {
const account = tx.Account
const amount = formatAmount(tx.Amount)
+
+ if (amount.isMPT === true) {
+ const holder = tx.Holder
+
+ const filteredMptNode = meta.AffectedNodes.filter(
+ (node: any) => node.ModifiedNode?.LedgerEntryType === 'MPToken',
+ )
+
+ // If no mpt is modified, it means the tx failed.
+ // We just return the amount that was attempted to claw.
+ if (!filteredMptNode || filteredMptNode.length !== 1)
+ return {
+ amount,
+ account,
+ holder,
+ }
+
+ const mptNode = filteredMptNode[0].ModifiedNode
+ const { change } = computeMPTokenBalanceChange(mptNode)
+ amount.amount =
+ BigInt(change) < 0
+ ? BigInt(-change).toString(10)
+ : BigInt(change).toString(10)
+
+ return {
+ account,
+ amount,
+ holder,
+ }
+ }
+
const holder = amount.issuer
amount.issuer = account
@@ -30,7 +64,7 @@ export const parser: TransactionParser = (
holder,
}
- const { change } = computeBalanceChange(
+ const { change } = computeRippleStateBalanceChange(
trustlineNode[0].ModifiedNode ?? trustlineNode[0].DeletedNode,
)
diff --git a/src/containers/shared/components/Transaction/Clawback/test/ClawbackSimple.test.tsx b/src/containers/shared/components/Transaction/Clawback/test/ClawbackSimple.test.tsx
index 6110b2b41..1a7e19ee7 100644
--- a/src/containers/shared/components/Transaction/Clawback/test/ClawbackSimple.test.tsx
+++ b/src/containers/shared/components/Transaction/Clawback/test/ClawbackSimple.test.tsx
@@ -1,7 +1,15 @@
+import { useQuery } from 'react-query'
import { createSimpleWrapperFactory, expectSimpleRowText } from '../../test'
import { Simple } from '../Simple'
import transaction from './mock_data/Clawback.json'
import transactionFailure from './mock_data/Clawback_Failure.json'
+import transactionMPT from './mock_data/ClawbackMPT.json'
+import transactionMPTFailure from './mock_data/ClawbackMPT_Failure.json'
+
+jest.mock('react-query', () => ({
+ ...jest.requireActual('react-query'),
+ useQuery: jest.fn(),
+}))
const createWrapper = createSimpleWrapperFactory(Simple)
@@ -17,6 +25,26 @@ describe('Clawback', () => {
wrapper.unmount()
})
+ it('handles MPT Clawback simple view ', () => {
+ const data = {
+ assetScale: 3,
+ }
+
+ // @ts-ignore
+ useQuery.mockImplementation(() => ({
+ data,
+ }))
+ const wrapper = createWrapper(transactionMPT)
+ expectSimpleRowText(wrapper, 'holder', 'rUZTPFN7MBJkjiZ48rak6q7MbhT4ur2kAD')
+ expectSimpleRowText(
+ wrapper,
+ 'amount',
+ '0.05 MPT (00000D668E702F54A27C42EF98C13B0787D1766CC9162A47)',
+ )
+
+ wrapper.unmount()
+ })
+
it('handles failed Clawback simple view ', () => {
const wrapper = createWrapper(transactionFailure)
expectSimpleRowText(wrapper, 'holder', 'rDZ713igKfedN4hhY6SjQse4Mv3ZrBxnn9')
@@ -27,4 +55,24 @@ describe('Clawback', () => {
)
wrapper.unmount()
})
+
+ it('handles failed MPT Clawback simple view ', () => {
+ const data = {
+ assetScale: 3,
+ }
+
+ // @ts-ignore
+ useQuery.mockImplementation(() => ({
+ data,
+ }))
+ const wrapper = createWrapper(transactionMPTFailure)
+
+ expectSimpleRowText(wrapper, 'holder', 'r9rAqX8Jjo4uACsimYDVsy5thHDPivujqf')
+ expectSimpleRowText(
+ wrapper,
+ 'amount',
+ '0.05 MPT (000010952ECE2AFC727F1C67EF568F360A2D92CB7C29FF7C)',
+ )
+ wrapper.unmount()
+ })
})
diff --git a/src/containers/shared/components/Transaction/Clawback/test/mock_data/ClawbackMPT.json b/src/containers/shared/components/Transaction/Clawback/test/mock_data/ClawbackMPT.json
new file mode 100644
index 000000000..37e1d6630
--- /dev/null
+++ b/src/containers/shared/components/Transaction/Clawback/test/mock_data/ClawbackMPT.json
@@ -0,0 +1,85 @@
+{
+ "tx": {
+ "Account": "rDz9LyymZh4C1jJvFK6v6qXeeARLdYKuEW",
+ "Amount": {
+ "mpt_issuance_id": "00000D668E702F54A27C42EF98C13B0787D1766CC9162A47",
+ "value": "50"
+ },
+ "Fee": "10",
+ "Flags": 2147483648,
+ "Holder": "rUZTPFN7MBJkjiZ48rak6q7MbhT4ur2kAD",
+ "Sequence": 3432,
+ "SigningPubKey": "ED0C1DE70A8762E6C98EC78CF13D278D6950ECDFE8E87BAD3732730845E2D9AB6A",
+ "TransactionType": "Clawback",
+ "TxnSignature": "8099CED925A463A2A24F55A496D2BB40108B75840770DFBA9796FBBD40AA482126EE9DAF2512D5D2E8268BCBFC277828E66A28CF3394702611290B45FBA88109",
+ "ctid": "C0000D6D00000000",
+ "date": 1728575981000
+ },
+ "meta": {
+ "AffectedNodes": [
+ {
+ "ModifiedNode": {
+ "FinalFields": {
+ "Account": "rDz9LyymZh4C1jJvFK6v6qXeeARLdYKuEW",
+ "Balance": "99999970",
+ "Flags": 0,
+ "OwnerCount": 1,
+ "Sequence": 3433
+ },
+ "LedgerEntryType": "AccountRoot",
+ "LedgerIndex": "06A3654A4A8829FD0575ADDD068BD04F7483C407E027CB43F77C2A5CA575368B",
+ "PreviousFields": {
+ "Balance": "99999980",
+ "Sequence": 3432
+ },
+ "PreviousTxnID": "4B6D63C7AA15899EC1CB3D84C923B08A62D6643A75D28B254F7A0C082B2C0D75",
+ "PreviousTxnLgrSeq": 3436
+ }
+ },
+ {
+ "ModifiedNode": {
+ "FinalFields": {
+ "AssetScale": 3,
+ "Flags": 98,
+ "Issuer": "rDz9LyymZh4C1jJvFK6v6qXeeARLdYKuEW",
+ "MPTokenMetadata": "7B226E616D65223A2255532054726561737572792042696C6C20546F6B656E222C2273796D626F6C223A225553544254222C22646563696D616C73223A322C22746F74616C537570706C79223A313030303030302C22697373756572223A225553205472656173757279222C22697373756544617465223A22323032342D30332D3235222C226D6174757269747944617465223A22323032352D30332D3235222C226661636556616C7565223A2231303030222C22696E74657265737452617465223A22322E35222C22696E7465726573744672657175656E6379223A22517561727465726C79222C22636F6C6C61746572616C223A22555320476F7665726E6D656E74222C226A7572697364696374696F6E223A22556E6974656420537461746573222C22726567756C61746F7279436F6D706C69616E6365223A2253454320526567756C6174696F6E73222C22736563757269747954797065223A2254726561737572792042696C6C222C2265787465726E616C5F75726C223A2268747470733A2F2F6578616D706C652E636F6D2F742D62696C6C2D746F6B656E2D6D657461646174612E6A736F6E227D",
+ "MaximumAmount": "9223372036854775807",
+ "OutstandingAmount": "50",
+ "OwnerNode": "0",
+ "Sequence": 3430
+ },
+ "LedgerEntryType": "MPTokenIssuance",
+ "LedgerIndex": "0EABDC95DBBC52F8A95A5F49C38211A30B916BC329774F46CC081D502F9E1895",
+ "PreviousFields": {
+ "OutstandingAmount": "100"
+ },
+ "PreviousTxnID": "4B6D63C7AA15899EC1CB3D84C923B08A62D6643A75D28B254F7A0C082B2C0D75",
+ "PreviousTxnLgrSeq": 3436
+ }
+ },
+ {
+ "ModifiedNode": {
+ "FinalFields": {
+ "Account": "rUZTPFN7MBJkjiZ48rak6q7MbhT4ur2kAD",
+ "Flags": 0,
+ "MPTAmount": "50",
+ "MPTokenIssuanceID": "00000D668E702F54A27C42EF98C13B0787D1766CC9162A47",
+ "OwnerNode": "0"
+ },
+ "LedgerEntryType": "MPToken",
+ "LedgerIndex": "DA40BA069F110465BD90BF5732163836F011E3E761CCF7B6949FAA24D97132F6",
+ "PreviousFields": {
+ "MPTAmount": "100"
+ },
+ "PreviousTxnID": "4B6D63C7AA15899EC1CB3D84C923B08A62D6643A75D28B254F7A0C082B2C0D75",
+ "PreviousTxnLgrSeq": 3436
+ }
+ }
+ ],
+ "TransactionIndex": 0,
+ "TransactionResult": "tesSUCCESS"
+ },
+ "hash": "46B686335794B911B3B76C2F4B76AF424F9978C3E82B2F6488801C359AA71856",
+ "ledger_index": 3437,
+ "date": 1728575981000
+}
diff --git a/src/containers/shared/components/Transaction/Clawback/test/mock_data/ClawbackMPT_Failure.json b/src/containers/shared/components/Transaction/Clawback/test/mock_data/ClawbackMPT_Failure.json
new file mode 100644
index 000000000..d48f95247
--- /dev/null
+++ b/src/containers/shared/components/Transaction/Clawback/test/mock_data/ClawbackMPT_Failure.json
@@ -0,0 +1,46 @@
+{
+ "tx": {
+ "Account": "rnGVhdnWv7g3fW8UNJyFHj6eyngsMdwA8c",
+ "Amount": {
+ "mpt_issuance_id": "000010952ECE2AFC727F1C67EF568F360A2D92CB7C29FF7C",
+ "value": "50"
+ },
+ "Fee": "10",
+ "Flags": 2147483648,
+ "Holder": "r9rAqX8Jjo4uACsimYDVsy5thHDPivujqf",
+ "Sequence": 4246,
+ "SigningPubKey": "ED4F6FF2241860884D4DD6C5797BDA553155D194F07B1BFC67129F183322DA7DC3",
+ "TransactionType": "Clawback",
+ "TxnSignature": "C54F175F0AFD950507C059E0EA5E6FA0079E7CDE5DF62BB122B56DC34A351C2369E208B31F7C27B1E9D21753506E195B147500D033884C44373899C84A97680B",
+ "ctid": "C000109B00000000",
+ "date": 1728577343000
+ },
+ "meta": {
+ "AffectedNodes": [
+ {
+ "ModifiedNode": {
+ "FinalFields": {
+ "Account": "rnGVhdnWv7g3fW8UNJyFHj6eyngsMdwA8c",
+ "Balance": "99999980",
+ "Flags": 0,
+ "OwnerCount": 1,
+ "Sequence": 4247
+ },
+ "LedgerEntryType": "AccountRoot",
+ "LedgerIndex": "95A16157D164CD90D64BC94DE3EA7758AE3088391C9AC44AFCAC90C5153D83D5",
+ "PreviousFields": {
+ "Balance": "99999990",
+ "Sequence": 4246
+ },
+ "PreviousTxnID": "8ACD0682CB1EDDCF6C61F15E6B9637D2719FDA2EC32EB384A68F36F0A0297D91",
+ "PreviousTxnLgrSeq": 4248
+ }
+ }
+ ],
+ "TransactionIndex": 0,
+ "TransactionResult": "tecINSUFFICIENT_FUNDS"
+ },
+ "hash": "26E6E7AEA4F78801EB0408D802FEBA11B962BFD680501DF0D0C58F30C6EA8951",
+ "ledger_index": 4251,
+ "date": 1728577343000
+}
diff --git a/src/containers/shared/components/Transaction/Clawback/types.ts b/src/containers/shared/components/Transaction/Clawback/types.ts
index 81930d4c2..19a3c8674 100644
--- a/src/containers/shared/components/Transaction/Clawback/types.ts
+++ b/src/containers/shared/components/Transaction/Clawback/types.ts
@@ -3,6 +3,7 @@ import { Amount, ExplorerAmount } from '../../../types'
export interface Clawback extends TransactionCommonFields {
Amount: Amount
+ Holder?: string
}
export interface ClawbackInstructions {
diff --git a/src/containers/shared/components/Transaction/EnableAmendment/Simple.tsx b/src/containers/shared/components/Transaction/EnableAmendment/Simple.tsx
index 184faee53..cd53f50b8 100644
--- a/src/containers/shared/components/Transaction/EnableAmendment/Simple.tsx
+++ b/src/containers/shared/components/Transaction/EnableAmendment/Simple.tsx
@@ -1,15 +1,14 @@
-import { useEffect, useState } from 'react'
+import { useContext, useEffect, useState } from 'react'
+import { useTranslation } from 'react-i18next'
import { useLanguage } from '../../../hooks'
import { SimpleRow } from '../SimpleRow'
import { TransactionSimpleProps } from '../types'
import { EnableAmendment } from './types'
-import {
- getExpectedDate,
- getRippledVersion,
- nameOfAmendmentID,
-} from '../../../amendmentUtils'
+import { getExpectedDate, getRippledVersion } from '../../../amendmentUtils'
import { AMENDMENT_ROUTE } from '../../../../App/routes'
import { RouteLink } from '../../../routing'
+import SocketContext from '../../../SocketContext'
+import { getFeature } from '../../../../../rippled/lib/rippled'
const states = {
loading: 'Loading',
@@ -18,35 +17,26 @@ const states = {
export const Simple = ({ data }: TransactionSimpleProps) => {
const language = useLanguage()
+ const { t } = useTranslation()
const [amendmentDetails, setAmendmentDetails] = useState({
name: states.loading,
minRippledVersion: states.loading,
})
+ const rippledSocket = useContext(SocketContext)
useEffect(() => {
- nameOfAmendmentID(data.instructions.Amendment).then((name: string) => {
- if (name) {
- getRippledVersion(name).then((rippledVersion) => {
- if (rippledVersion) {
- setAmendmentDetails({
- name,
- minRippledVersion: rippledVersion,
- })
- } else {
- setAmendmentDetails({
- name,
- minRippledVersion: states.unknown,
- })
- }
- })
- } else {
+ const amendmentId = data.instructions.Amendment
+ getFeature(rippledSocket, amendmentId).then((feature) => {
+ const name =
+ feature && feature[amendmentId] ? feature[amendmentId].name : ''
+ getRippledVersion(name).then((rippledVersion) => {
setAmendmentDetails({
- name: states.unknown,
- minRippledVersion: states.unknown,
+ name: name || states.unknown,
+ minRippledVersion: rippledVersion || states.unknown,
})
- }
+ })
})
- }, [data.instructions.Amendment])
+ }, [data.instructions.Amendment, rippledSocket])
let amendmentStatus = states.unknown
let expectedDate: string | null = states.unknown
@@ -68,7 +58,7 @@ export const Simple = ({ data }: TransactionSimpleProps) => {
return (
<>
-
+
) => {
{amendmentDetails.name}
-
+
{amendmentStatus}
-
+
{amendmentDetails.minRippledVersion}
{amendmentStatus === 'Got Majority' && (
-
+
{expectedDate}
)}
diff --git a/src/containers/shared/components/Transaction/EnableAmendment/test/EnableAmendmentSimple.test.tsx b/src/containers/shared/components/Transaction/EnableAmendment/test/EnableAmendmentSimple.test.tsx
index d04002da3..476931b4d 100644
--- a/src/containers/shared/components/Transaction/EnableAmendment/test/EnableAmendmentSimple.test.tsx
+++ b/src/containers/shared/components/Transaction/EnableAmendment/test/EnableAmendmentSimple.test.tsx
@@ -6,11 +6,11 @@ import { Simple } from '../Simple'
import mockEnableAmendmentWithEnabled from './mock_data/EnableAmendmentWithEnabled.json'
import mockEnableAmendmentWithMinority from './mock_data/EnableAmendmentWithMinority.json'
import mockEnableAmendmentWithMajority from './mock_data/EnableAmendmentWithMajority.json'
-import {
- getRippledVersion,
- nameOfAmendmentID,
-} from '../../../../amendmentUtils'
+import mockFeatureExpandedSignerList from './mock_data/FeatureExpandedSignerList.json'
+import mockFeatureNegativeUNL from './mock_data/FeatureNegativeUNL.json'
+import { getRippledVersion } from '../../../../amendmentUtils'
import { flushPromises } from '../../../../../test/utils'
+import { getFeature } from '../../../../../../rippled/lib/rippled'
const createWrapper = createSimpleWrapperFactory(Simple, i18n)
@@ -21,22 +21,35 @@ jest.mock('../../../../amendmentUtils', () => {
__esModule: true,
...originalModule,
getRippledVersion: jest.fn(),
- nameOfAmendmentID: jest.fn(),
+ }
+})
+
+jest.mock('../../../../../../rippled/lib/rippled', () => {
+ const originalModule = jest.requireActual(
+ '../../../../../../rippled/lib/rippled',
+ )
+
+ return {
+ __esModule: true,
+ ...originalModule,
+ getFeature: jest.fn(),
}
})
const mockedGetRippledVersion = getRippledVersion as jest.MockedFunction<
typeof getRippledVersion
>
-const mockedNameOfAmendmentID = nameOfAmendmentID as jest.MockedFunction<
- typeof nameOfAmendmentID
->
+
+const mockedGetFeature = getFeature as jest.MockedFunction
describe('EnableAmendment: Simple', () => {
+ afterEach(() => {
+ mockedGetFeature.mockReset()
+ })
it('renders tx that causes an amendment to loose majority', async () => {
mockedGetRippledVersion.mockImplementation(() => Promise.resolve('v1.9.1'))
- mockedNameOfAmendmentID.mockImplementation(() =>
- Promise.resolve('ExpandedSignerList'),
+ mockedGetFeature.mockImplementation(() =>
+ Promise.resolve(mockFeatureExpandedSignerList),
)
const wrapper = createWrapper(mockEnableAmendmentWithMinority)
expectSimpleRowLabel(wrapper, 'name', 'Amendment Name')
@@ -58,8 +71,8 @@ describe('EnableAmendment: Simple', () => {
it('renders tx that causes an amendment to gain majority', async () => {
mockedGetRippledVersion.mockImplementation(() => Promise.resolve('v1.9.1'))
- mockedNameOfAmendmentID.mockImplementation(() =>
- Promise.resolve('ExpandedSignerList'),
+ mockedGetFeature.mockImplementation(() =>
+ Promise.resolve(mockFeatureExpandedSignerList),
)
const wrapper = createWrapper(mockEnableAmendmentWithMajority)
expectSimpleRowLabel(wrapper, 'name', 'Amendment Name')
@@ -86,8 +99,8 @@ describe('EnableAmendment: Simple', () => {
it('renders tx that enables an amendment', async () => {
mockedGetRippledVersion.mockImplementation(() => Promise.resolve('v1.7.3'))
- mockedNameOfAmendmentID.mockImplementation(() =>
- Promise.resolve('NegativeUNL'),
+ mockedGetFeature.mockImplementation(() =>
+ Promise.resolve(mockFeatureNegativeUNL),
)
const wrapper = createWrapper(mockEnableAmendmentWithEnabled)
expectSimpleRowLabel(wrapper, 'name', 'Amendment Name')
@@ -108,7 +121,7 @@ describe('EnableAmendment: Simple', () => {
it('renders tx that cannot determine version or name', async () => {
mockedGetRippledVersion.mockImplementation(() => Promise.resolve(''))
- mockedNameOfAmendmentID.mockImplementation(() => Promise.resolve(''))
+ mockedGetFeature.mockImplementation(() => Promise.resolve(null))
const wrapper = createWrapper(mockEnableAmendmentWithEnabled)
expectSimpleRowLabel(wrapper, 'name', 'Amendment Name')
expectSimpleRowText(wrapper, 'name', 'Loading')
@@ -124,8 +137,8 @@ describe('EnableAmendment: Simple', () => {
it('renders tx that cannot determine version', async () => {
mockedGetRippledVersion.mockImplementation(() => Promise.resolve(''))
- mockedNameOfAmendmentID.mockImplementation(() =>
- Promise.resolve('NegativeUNL'),
+ mockedGetFeature.mockImplementation(() =>
+ Promise.resolve(mockFeatureNegativeUNL),
)
const wrapper = createWrapper(mockEnableAmendmentWithEnabled)
expectSimpleRowLabel(wrapper, 'name', 'Amendment Name')
@@ -142,7 +155,7 @@ describe('EnableAmendment: Simple', () => {
it('renders tx that cannot determine name', async () => {
mockedGetRippledVersion.mockImplementation(() => Promise.resolve('v1.7.3'))
- mockedNameOfAmendmentID.mockImplementation(() => Promise.resolve(''))
+ mockedGetFeature.mockImplementation(() => Promise.resolve(null))
const wrapper = createWrapper(mockEnableAmendmentWithEnabled)
expectSimpleRowLabel(wrapper, 'name', 'Amendment Name')
expectSimpleRowText(wrapper, 'name', 'Loading')
@@ -153,6 +166,6 @@ describe('EnableAmendment: Simple', () => {
wrapper.update()
expectSimpleRowText(wrapper, 'name', 'Unknown')
- expectSimpleRowText(wrapper, 'version', 'Unknown')
+ expectSimpleRowText(wrapper, 'version', 'v1.7.3')
})
})
diff --git a/src/containers/shared/components/Transaction/EnableAmendment/test/mock_data/FeatureExpandedSignerList.json b/src/containers/shared/components/Transaction/EnableAmendment/test/mock_data/FeatureExpandedSignerList.json
new file mode 100644
index 000000000..09635f407
--- /dev/null
+++ b/src/containers/shared/components/Transaction/EnableAmendment/test/mock_data/FeatureExpandedSignerList.json
@@ -0,0 +1,7 @@
+{
+ "B2A4DB846F0891BF2C76AB2F2ACC8F5B4EC64437135C6E56F3F859DE5FFD5856": {
+ "enabled": false,
+ "name": "ExpandedSignerList",
+ "supported": true
+ }
+}
diff --git a/src/containers/shared/components/Transaction/EnableAmendment/test/mock_data/FeatureNegativeUNL.json b/src/containers/shared/components/Transaction/EnableAmendment/test/mock_data/FeatureNegativeUNL.json
new file mode 100644
index 000000000..56341a552
--- /dev/null
+++ b/src/containers/shared/components/Transaction/EnableAmendment/test/mock_data/FeatureNegativeUNL.json
@@ -0,0 +1,7 @@
+{
+ "B4E4F5D2D6FB84DF7399960A732309C9FD530EAE5941838160042833625A6076": {
+ "enabled": false,
+ "name": "NegativeUNL",
+ "supported": true
+ }
+}
diff --git a/src/containers/shared/components/Transaction/MPTokenAuthorize/Simple.tsx b/src/containers/shared/components/Transaction/MPTokenAuthorize/Simple.tsx
new file mode 100644
index 000000000..fda5331b6
--- /dev/null
+++ b/src/containers/shared/components/Transaction/MPTokenAuthorize/Simple.tsx
@@ -0,0 +1,26 @@
+import { useTranslation } from 'react-i18next'
+import { SimpleRow } from '../SimpleRow'
+import { TransactionSimpleComponent, TransactionSimpleProps } from '../types'
+import { MPTokenAuthorize } from './types'
+import { Account } from '../../Account'
+import { MPTokenLink } from '../../MPTokenLink'
+
+export const Simple: TransactionSimpleComponent = ({
+ data,
+}: TransactionSimpleProps) => {
+ const { MPTokenIssuanceID, Holder } = data.instructions
+ const { t } = useTranslation()
+
+ return (
+ <>
+
+
+
+ {Holder && (
+
+
+
+ )}
+ >
+ )
+}
diff --git a/src/containers/shared/components/Transaction/MPTokenAuthorize/index.ts b/src/containers/shared/components/Transaction/MPTokenAuthorize/index.ts
new file mode 100644
index 000000000..707282fdf
--- /dev/null
+++ b/src/containers/shared/components/Transaction/MPTokenAuthorize/index.ts
@@ -0,0 +1,13 @@
+import {
+ TransactionAction,
+ TransactionCategory,
+ TransactionMapping,
+} from '../types'
+
+import { Simple } from './Simple'
+
+export const MPTokenAuthorizeTransaction: TransactionMapping = {
+ Simple,
+ action: TransactionAction.MODIFY,
+ category: TransactionCategory.MPT,
+}
diff --git a/src/containers/shared/components/Transaction/MPTokenAuthorize/test/MPTokenAuthorizeSimple.test.jsx b/src/containers/shared/components/Transaction/MPTokenAuthorize/test/MPTokenAuthorizeSimple.test.jsx
new file mode 100644
index 000000000..932a18cc1
--- /dev/null
+++ b/src/containers/shared/components/Transaction/MPTokenAuthorize/test/MPTokenAuthorizeSimple.test.jsx
@@ -0,0 +1,66 @@
+import { createSimpleWrapperFactory } from '../../test/createWrapperFactory'
+import { Simple } from '../Simple'
+import { expectSimpleRowText, expectSimpleRowNotToExist } from '../../test'
+import transactionSuccess from './mock_data/MPTokenAuthorize.json'
+import transactionFail from './mock_data/MPTokenAuthorize_Fail.json'
+import transactionWithHolder from './mock_data/MPTokenAuthorize_WithHolder.json'
+import transactionWithHolderFail from './mock_data/MPTokenAuthorize_WithHolderFail.json'
+
+const createWrapper = createSimpleWrapperFactory(Simple)
+
+describe('MPTokenAuthorize', () => {
+ it('handles MPTokenAuthorize w/o holder simple view ', () => {
+ const wrapper = createWrapper(transactionSuccess)
+
+ expectSimpleRowText(
+ wrapper,
+ 'mpt-issuance-id',
+ '000005F398B624EBD06822198649C920C8B20ADB8EBE745E',
+ )
+ expectSimpleRowNotToExist(wrapper, 'mpt-holder')
+ wrapper.unmount()
+ })
+
+ it('handles MPTokenAuthorize view w/ holder simple view ', () => {
+ const wrapper = createWrapper(transactionWithHolder)
+
+ expectSimpleRowText(
+ wrapper,
+ 'mpt-issuance-id',
+ '0000130B63FC523E33FDF4D1318D8D484B0D1111098CFD0B',
+ )
+ expectSimpleRowText(
+ wrapper,
+ 'mpt-holder',
+ 'rK3bB9myvWoMaLbLnpksGx2Zz58BL225am',
+ )
+ wrapper.unmount()
+ })
+
+ it('handles failed MPTokenAuthorize view w/ holder simple view ', () => {
+ const wrapper = createWrapper(transactionWithHolderFail)
+
+ expectSimpleRowText(
+ wrapper,
+ 'mpt-issuance-id',
+ '00000F76D46440EE21F74E5B2398315BC1CFEB9A7EB48A14',
+ )
+ expectSimpleRowText(
+ wrapper,
+ 'mpt-holder',
+ 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh',
+ )
+ wrapper.unmount()
+ })
+
+ it('handles failed MPTokenAuthorize w/o holder simple view ', () => {
+ const wrapper = createWrapper(transactionFail)
+ expectSimpleRowText(
+ wrapper,
+ 'mpt-issuance-id',
+ '0000098410531B842DEECCF4ABB1268C931EB71D9F6A1B64',
+ )
+ expectSimpleRowNotToExist(wrapper, 'mpt-holder')
+ wrapper.unmount()
+ })
+})
diff --git a/src/containers/shared/components/Transaction/MPTokenAuthorize/test/mock_data/MPTokenAuthorize.json b/src/containers/shared/components/Transaction/MPTokenAuthorize/test/mock_data/MPTokenAuthorize.json
new file mode 100644
index 000000000..e41a47854
--- /dev/null
+++ b/src/containers/shared/components/Transaction/MPTokenAuthorize/test/mock_data/MPTokenAuthorize.json
@@ -0,0 +1,70 @@
+{
+ "tx": {
+ "Account": "rnLz9TWQAvaLpdyrtb1WbMgp7jZdNQ47Ny",
+ "Fee": "10",
+ "Flags": 2147483648,
+ "MPTokenIssuanceID": "000005F398B624EBD06822198649C920C8B20ADB8EBE745E",
+ "Sequence": 1524,
+ "SigningPubKey": "ED97BAFB2D380AF67DA2C1968C3A1DC38797E9BA0653CE620F6BC97FFD66925EBB",
+ "TransactionType": "MPTokenAuthorize",
+ "TxnSignature": "28879892AF0D465063993BD1DDCA147C7CA0AB9C8429DAB3A0030AA4AC57AA80F725F295622913C07E4CAFB3160DEF7E8D0209429390B0FBD78F96B28E700A07",
+ "ctid": "C00005F600000000",
+ "date": 1711397033000,
+ "hash": "undefined",
+ "inLedger": "undefined",
+ "ledger_index": "undefined",
+ "meta": "undefined",
+ "validated": "undefined",
+ "metaData": "undefined",
+ "status": "undefined"
+ },
+ "meta": {
+ "AffectedNodes": [
+ {
+ "ModifiedNode": {
+ "FinalFields": {
+ "Account": "rnLz9TWQAvaLpdyrtb1WbMgp7jZdNQ47Ny",
+ "Balance": "99999990",
+ "Flags": 0,
+ "OwnerCount": 1,
+ "Sequence": 1525
+ },
+ "LedgerEntryType": "AccountRoot",
+ "LedgerIndex": "424AAE60FE8A4B7EF77DA492F9561AAFA1D09DB56BE5804B055235BCD662C9FE",
+ "PreviousFields": {
+ "Balance": "100000000",
+ "OwnerCount": 0,
+ "Sequence": 1524
+ },
+ "PreviousTxnID": "B6301327D79A93DC211043ABF66A60DC9C70BD2962FC42E0EAD0A829680ABAE8",
+ "PreviousTxnLgrSeq": 1524
+ }
+ },
+ {
+ "CreatedNode": {
+ "LedgerEntryType": "DirectoryNode",
+ "LedgerIndex": "65BCF554A41D30521B876D012D3DC167F9E886E02D88231E9DEBD2501A4A7BB5",
+ "NewFields": {
+ "Owner": "rnLz9TWQAvaLpdyrtb1WbMgp7jZdNQ47Ny",
+ "RootIndex": "65BCF554A41D30521B876D012D3DC167F9E886E02D88231E9DEBD2501A4A7BB5"
+ }
+ }
+ },
+ {
+ "CreatedNode": {
+ "LedgerEntryType": "MPToken",
+ "LedgerIndex": "91D261494BB3D64D5D3D12BD480EB58C5E2B21F3222B12FE442BC73276C27266",
+ "NewFields": {
+ "Account": "rnLz9TWQAvaLpdyrtb1WbMgp7jZdNQ47Ny",
+ "MPTokenIssuanceID": "000005F398B624EBD06822198649C920C8B20ADB8EBE745E"
+ }
+ }
+ }
+ ],
+ "TransactionIndex": 0,
+ "TransactionResult": "tesSUCCESS"
+ },
+ "hash": "9592E76A725CF4A5A441024EE80596DFE8809D1AD1EC28A8D9DB2CEC2CB81EDC",
+ "ledger_index": 1526,
+ "date": 1711397033000
+}
diff --git a/src/containers/shared/components/Transaction/MPTokenAuthorize/test/mock_data/MPTokenAuthorize_Fail.json b/src/containers/shared/components/Transaction/MPTokenAuthorize/test/mock_data/MPTokenAuthorize_Fail.json
new file mode 100644
index 000000000..8d305ae95
--- /dev/null
+++ b/src/containers/shared/components/Transaction/MPTokenAuthorize/test/mock_data/MPTokenAuthorize_Fail.json
@@ -0,0 +1,49 @@
+{
+ "tx": {
+ "Account": "rJtok1j4okh4HkKxC3ArAvZbMD1vcDSteo",
+ "Fee": "10",
+ "Flags": 2147483648,
+ "MPTokenIssuanceID": "0000098410531B842DEECCF4ABB1268C931EB71D9F6A1B64",
+ "Sequence": 2438,
+ "SigningPubKey": "EDE493F0B7846A102A5C6EF4FDD9E95D1A84B0BEB99DED06C4436C0D61E5FA0B67",
+ "TransactionType": "MPTokenAuthorize",
+ "TxnSignature": "C903E4BA935BA27424D05CA230385FBA392CB4B48C136E37EF5DABE07814A2A8FA0AAEE34FCAB994D1AED7A663EB4CA121FA07B83E3C5A74289ABA258AA45F00",
+ "ctid": "C000098800000000",
+ "date": 1711398512000,
+ "hash": "undefined",
+ "inLedger": "undefined",
+ "ledger_index": "undefined",
+ "meta": "undefined",
+ "validated": "undefined",
+ "metaData": "undefined",
+ "status": "undefined"
+ },
+ "meta": {
+ "AffectedNodes": [
+ {
+ "ModifiedNode": {
+ "FinalFields": {
+ "Account": "rJtok1j4okh4HkKxC3ArAvZbMD1vcDSteo",
+ "Balance": "99999980",
+ "Flags": 0,
+ "OwnerCount": 1,
+ "Sequence": 2439
+ },
+ "LedgerEntryType": "AccountRoot",
+ "LedgerIndex": "FE496E6B5CBE4778460846E5B93648B41E15463E691311EA4CD7E578561CA20E",
+ "PreviousFields": {
+ "Balance": "99999990",
+ "Sequence": 2438
+ },
+ "PreviousTxnID": "26BB8D3B11AA0C967470DBA5D6B09A10608B4D1DADE0408668A45C010F4B8DDC",
+ "PreviousTxnLgrSeq": 2439
+ }
+ }
+ ],
+ "TransactionIndex": 0,
+ "TransactionResult": "tecMPTOKEN_EXISTS"
+ },
+ "hash": "7B785C2D172D8FAE35DBBA66868D147747F32B5D6AA41F62D698E872643CE2B6",
+ "ledger_index": 2440,
+ "date": 1711398512000
+}
diff --git a/src/containers/shared/components/Transaction/MPTokenAuthorize/test/mock_data/MPTokenAuthorize_WithHolder.json b/src/containers/shared/components/Transaction/MPTokenAuthorize/test/mock_data/MPTokenAuthorize_WithHolder.json
new file mode 100644
index 000000000..010e406f9
--- /dev/null
+++ b/src/containers/shared/components/Transaction/MPTokenAuthorize/test/mock_data/MPTokenAuthorize_WithHolder.json
@@ -0,0 +1,67 @@
+{
+ "tx": {
+ "Account": "rwfgw2dWqUAexB46z5QRq2dJcgTK9piw5w",
+ "Fee": "10",
+ "Flags": 2147483648,
+ "Holder": "rK3bB9myvWoMaLbLnpksGx2Zz58BL225am",
+ "MPTokenIssuanceID": "0000130B63FC523E33FDF4D1318D8D484B0D1111098CFD0B",
+ "Sequence": 4876,
+ "SigningPubKey": "ED936E848B8E37D20991C2E1C5C76ABAEC0625D693CEB85BA495B58E16712DA627",
+ "TransactionType": "MPTokenAuthorize",
+ "TxnSignature": "3F31AD3682B9261975E27895DFCB9F373C33C174A076445C33AE13A6713D7FC8C8305A4D05C4918979C9EAD0230A61CE9998B71BEE21653D6BFCCC65F599100E",
+ "ctid": "C000130F00000000",
+ "date": 1711400951000,
+ "hash": "undefined",
+ "inLedger": "undefined",
+ "ledger_index": "undefined",
+ "meta": "undefined",
+ "validated": "undefined",
+ "metaData": "undefined",
+ "status": "undefined"
+ },
+ "meta": {
+ "AffectedNodes": [
+ {
+ "ModifiedNode": {
+ "FinalFields": {
+ "Account": "rwfgw2dWqUAexB46z5QRq2dJcgTK9piw5w",
+ "Balance": "99999980",
+ "Flags": 0,
+ "OwnerCount": 1,
+ "Sequence": 4877
+ },
+ "LedgerEntryType": "AccountRoot",
+ "LedgerIndex": "C688ECD4065B909634121581E792188424F29B48C062F1D1D4FED180DEAF3A23",
+ "PreviousFields": {
+ "Balance": "99999990",
+ "Sequence": 4876
+ },
+ "PreviousTxnID": "D12E5ED52F495449A537DB9293174209CC132CDCD4EFBBACCEB7F8E8FC582BBC",
+ "PreviousTxnLgrSeq": 4877
+ }
+ },
+ {
+ "ModifiedNode": {
+ "FinalFields": {
+ "Account": "rK3bB9myvWoMaLbLnpksGx2Zz58BL225am",
+ "Flags": 2,
+ "MPTokenIssuanceID": "0000130B63FC523E33FDF4D1318D8D484B0D1111098CFD0B",
+ "OwnerNode": "0"
+ },
+ "LedgerEntryType": "MPToken",
+ "LedgerIndex": "E6BC3F027146E5A2A50C01C37E7C5320E608C9D1D5BE763F32748865DB6EF3DE",
+ "PreviousFields": {
+ "Flags": 0
+ },
+ "PreviousTxnID": "FA5F2B8CE18C33D09E0243A2D20319AB9AF9D6CF5F1C2568B0CC4764DEC31F7A",
+ "PreviousTxnLgrSeq": 4878
+ }
+ }
+ ],
+ "TransactionIndex": 0,
+ "TransactionResult": "tesSUCCESS"
+ },
+ "hash": "5F92E78273BCF8A71E129F2CC9B8B0D5611E79D4CF81B530BF7B69892A579060",
+ "ledger_index": 4879,
+ "date": 1711400951000
+}
diff --git a/src/containers/shared/components/Transaction/MPTokenAuthorize/test/mock_data/MPTokenAuthorize_WithHolderFail.json b/src/containers/shared/components/Transaction/MPTokenAuthorize/test/mock_data/MPTokenAuthorize_WithHolderFail.json
new file mode 100644
index 000000000..b2572caaf
--- /dev/null
+++ b/src/containers/shared/components/Transaction/MPTokenAuthorize/test/mock_data/MPTokenAuthorize_WithHolderFail.json
@@ -0,0 +1,50 @@
+{
+ "tx": {
+ "Account": "rL4pMQAa3V7s9QNw1wEk2znnhjbfYo4GQC",
+ "Fee": "10",
+ "Flags": 2147483648,
+ "Holder": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
+ "MPTokenIssuanceID": "00000F76D46440EE21F74E5B2398315BC1CFEB9A7EB48A14",
+ "Sequence": 3959,
+ "SigningPubKey": "EDF7A3D93CE3AA46168649283C20C2D4FC36642FDD87449F1CCF068638BF17B10E",
+ "TransactionType": "MPTokenAuthorize",
+ "TxnSignature": "2795F1DC9C54493ADE475800A67FD5B3BC7B65F4E343CEEA0950E994F0FC10D0DAED13B4B0FD345E92BFD2B4F42A09A44906D5B2CD1D8FD7A4B3D28983F51806",
+ "ctid": "C0000F7900000000",
+ "date": 1711400033000,
+ "hash": "undefined",
+ "inLedger": "undefined",
+ "ledger_index": "undefined",
+ "meta": "undefined",
+ "validated": "undefined",
+ "metaData": "undefined",
+ "status": "undefined"
+ },
+ "meta": {
+ "AffectedNodes": [
+ {
+ "ModifiedNode": {
+ "FinalFields": {
+ "Account": "rL4pMQAa3V7s9QNw1wEk2znnhjbfYo4GQC",
+ "Balance": "99999980",
+ "Flags": 0,
+ "OwnerCount": 1,
+ "Sequence": 3960
+ },
+ "LedgerEntryType": "AccountRoot",
+ "LedgerIndex": "00BFE84169F6CAA5D03348856B57D47788B6856ABA9FA6EC7A16E6DA1B99B9D7",
+ "PreviousFields": {
+ "Balance": "99999990",
+ "Sequence": 3959
+ },
+ "PreviousTxnID": "E07D68B9728EE8954C66219FF713782933612A7D5EF44B50F5485557629DFE3D",
+ "PreviousTxnLgrSeq": 3960
+ }
+ }
+ ],
+ "TransactionIndex": 0,
+ "TransactionResult": "tecNO_AUTH"
+ },
+ "hash": "95AE2E382D6CFBFCECC012DDC52458E753FB9208A5040D2F441B5DE5BEA535CF",
+ "ledger_index": 3961,
+ "date": 1711400033000
+}
diff --git a/src/containers/shared/components/Transaction/MPTokenAuthorize/types.ts b/src/containers/shared/components/Transaction/MPTokenAuthorize/types.ts
new file mode 100644
index 000000000..c501d4639
--- /dev/null
+++ b/src/containers/shared/components/Transaction/MPTokenAuthorize/types.ts
@@ -0,0 +1,6 @@
+import { TransactionCommonFields } from '../types'
+
+export interface MPTokenAuthorize extends TransactionCommonFields {
+ MPTokenIssuanceID: string
+ Holder?: string
+}
diff --git a/src/containers/shared/components/Transaction/MPTokenIssuanceCreate/Simple.tsx b/src/containers/shared/components/Transaction/MPTokenIssuanceCreate/Simple.tsx
new file mode 100644
index 000000000..218ae93d2
--- /dev/null
+++ b/src/containers/shared/components/Transaction/MPTokenIssuanceCreate/Simple.tsx
@@ -0,0 +1,69 @@
+import { useTranslation } from 'react-i18next'
+import { SimpleRow } from '../SimpleRow'
+import { TransactionSimpleComponent, TransactionSimpleProps } from '../types'
+import { MPTokenIssuanceCreateInstructions } from './types'
+import { useLanguage } from '../../../hooks'
+import { isValidJsonString, localizeNumber } from '../../../utils'
+import { MPTokenLink } from '../../MPTokenLink'
+import { JsonView } from '../../JsonView'
+import './styles.scss'
+
+export const Simple: TransactionSimpleComponent = ({
+ data,
+}: TransactionSimpleProps) => {
+ const { issuanceID, metadata, assetScale, transferFee, maxAmount } =
+ data.instructions
+ const { t } = useTranslation()
+ const language = useLanguage()
+ const formattedFee =
+ transferFee &&
+ `${localizeNumber((transferFee / 1000).toPrecision(5), language, {
+ minimumFractionDigits: 3,
+ })}%`
+
+ return (
+ <>
+ {issuanceID && (
+
+
+
+ )}
+ {assetScale && (
+
+ {assetScale}
+
+ )}
+ {transferFee && (
+
+ {formattedFee}
+
+ )}
+ {maxAmount && (
+
+ {maxAmount}
+
+ )}
+ {metadata && (
+
+ {isValidJsonString(metadata) ? (
+
+ ) : (
+ metadata
+ )}
+
+ )}
+ >
+ )
+}
diff --git a/src/containers/shared/components/Transaction/MPTokenIssuanceCreate/index.ts b/src/containers/shared/components/Transaction/MPTokenIssuanceCreate/index.ts
new file mode 100644
index 000000000..03275f8f7
--- /dev/null
+++ b/src/containers/shared/components/Transaction/MPTokenIssuanceCreate/index.ts
@@ -0,0 +1,15 @@
+import {
+ TransactionAction,
+ TransactionCategory,
+ TransactionMapping,
+} from '../types'
+
+import { Simple } from './Simple'
+import { parser } from './parser'
+
+export const MPTokenIssuanceCreateTransaction: TransactionMapping = {
+ Simple,
+ action: TransactionAction.CREATE,
+ category: TransactionCategory.MPT,
+ parser,
+}
diff --git a/src/containers/shared/components/Transaction/MPTokenIssuanceCreate/parser.ts b/src/containers/shared/components/Transaction/MPTokenIssuanceCreate/parser.ts
new file mode 100644
index 000000000..d24770bc7
--- /dev/null
+++ b/src/containers/shared/components/Transaction/MPTokenIssuanceCreate/parser.ts
@@ -0,0 +1,21 @@
+import {
+ MPTokenIssuanceCreate,
+ MPTokenIssuanceCreateInstructions,
+} from './types'
+import { TransactionParser } from '../types'
+import { convertHexToString } from '../../../../../rippled/lib/utils'
+
+export const parser: TransactionParser<
+ MPTokenIssuanceCreate,
+ MPTokenIssuanceCreateInstructions
+> = (tx, meta) => ({
+ issuanceID: meta.mpt_issuance_id,
+ metadata: tx.MPTokenMetadata
+ ? convertHexToString(tx.MPTokenMetadata)
+ : undefined,
+ transferFee: tx.TransferFee,
+ assetScale: tx.AssetScale,
+ maxAmount: tx.MaximumAmount
+ ? BigInt(tx.MaximumAmount).toString(10)
+ : undefined,
+})
diff --git a/src/containers/shared/components/Transaction/MPTokenIssuanceCreate/styles.scss b/src/containers/shared/components/Transaction/MPTokenIssuanceCreate/styles.scss
new file mode 100644
index 000000000..f7ce40841
--- /dev/null
+++ b/src/containers/shared/components/Transaction/MPTokenIssuanceCreate/styles.scss
@@ -0,0 +1,3 @@
+.jv-indent {
+ border-left: none;
+}
diff --git a/src/containers/shared/components/Transaction/MPTokenIssuanceCreate/test/MPTokenIssuanceCreateSimple.test.jsx b/src/containers/shared/components/Transaction/MPTokenIssuanceCreate/test/MPTokenIssuanceCreateSimple.test.jsx
new file mode 100644
index 000000000..0c82efab4
--- /dev/null
+++ b/src/containers/shared/components/Transaction/MPTokenIssuanceCreate/test/MPTokenIssuanceCreateSimple.test.jsx
@@ -0,0 +1,27 @@
+import { createSimpleWrapperFactory } from '../../test/createWrapperFactory'
+import { Simple } from '../Simple'
+import { expectSimpleRowText } from '../../test'
+import transactionSuccess from './mock_data/MPTokenIssuanceCreate.json'
+
+const createWrapper = createSimpleWrapperFactory(Simple)
+
+describe('MPTokenIssuanceCreate', () => {
+ it('handles MPTokenIssuanceCreate simple view ', () => {
+ const wrapper = createWrapper(transactionSuccess)
+
+ expectSimpleRowText(
+ wrapper,
+ 'mpt-issuance-id',
+ '0000157844C3F3B57A8B579FEE1033CC8E8498729D063617',
+ )
+ expectSimpleRowText(wrapper, 'mpt-asset-scale', '2')
+ expectSimpleRowText(wrapper, 'mpt-max-amount', '9223372036854775807')
+ expectSimpleRowText(
+ wrapper,
+ 'mpt-metadata',
+ 'https://ipfs.io/ipfs/QmZnjmB9Tk4xaA9E679ytrPXda3beWMLUnMB5RFj1eStLp',
+ )
+ expectSimpleRowText(wrapper, 'mpt-fee', '0.010%')
+ wrapper.unmount()
+ })
+})
diff --git a/src/containers/shared/components/Transaction/MPTokenIssuanceCreate/test/mock_data/MPTokenIssuanceCreate.json b/src/containers/shared/components/Transaction/MPTokenIssuanceCreate/test/mock_data/MPTokenIssuanceCreate.json
new file mode 100644
index 000000000..2b5b91b7d
--- /dev/null
+++ b/src/containers/shared/components/Transaction/MPTokenIssuanceCreate/test/mock_data/MPTokenIssuanceCreate.json
@@ -0,0 +1,79 @@
+{
+ "tx": {
+ "Account": "rfGb6p2kWy3zQweWnYNxSFYoHeymcx7mhg",
+ "AssetScale": 2,
+ "Fee": "10",
+ "Flags": 34,
+ "MPTokenMetadata": "68747470733A2F2F697066732E696F2F697066732F516D5A6E6A6D4239546B34786141394536373979747250586461336265574D4C556E4D423552466A316553744C70",
+ "MaximumAmount": "9223372036854775807",
+ "Sequence": 5496,
+ "SigningPubKey": "EDAD408FAEE57EB4A347E6FE395B834DD47C6531C3C37B09ACC35528161CAD4B0E",
+ "TransactionType": "MPTokenIssuanceCreate",
+ "TransferFee": 10,
+ "TxnSignature": "F7AA8083EE7D7EFD10E11FF5A12B73D2D45A80094AEED4B41FBF2A90C9E03E5E4D162E91FA1EC156BF94E770E70E7633DF09665A5C2D5408178FC376BFC9B100",
+ "ctid": "C000157A00000000",
+ "date": 1710949602000,
+ "hash": "undefined",
+ "inLedger": "undefined",
+ "ledger_index": "undefined",
+ "meta": "undefined",
+ "validated": "undefined",
+ "metaData": "undefined",
+ "status": "undefined"
+ },
+ "meta": {
+ "AffectedNodes": [
+ {
+ "ModifiedNode": {
+ "FinalFields": {
+ "Account": "rfGb6p2kWy3zQweWnYNxSFYoHeymcx7mhg",
+ "Balance": "99999990",
+ "Flags": 0,
+ "OwnerCount": 1,
+ "Sequence": 5497
+ },
+ "LedgerEntryType": "AccountRoot",
+ "LedgerIndex": "1996F74D57092C1AC261F55CB16A45A63C785993691869A431D72A5BF8AF47A0",
+ "PreviousFields": {
+ "Balance": "100000000",
+ "OwnerCount": 0,
+ "Sequence": 5496
+ },
+ "PreviousTxnID": "531254FC0F1599CCAF9ABCDBE0854B6BFBBA225ADD7CA341D2897CBDC3E78E5E",
+ "PreviousTxnLgrSeq": 5496
+ }
+ },
+ {
+ "CreatedNode": {
+ "LedgerEntryType": "MPTokenIssuance",
+ "LedgerIndex": "265CEA78D8246B8B51D5CCC20AF4DB95569DE09E53115C888B176A3D1D05048A",
+ "NewFields": {
+ "AssetScale": 2,
+ "Flags": 34,
+ "Issuer": "rfGb6p2kWy3zQweWnYNxSFYoHeymcx7mhg",
+ "MPTokenMetadata": "68747470733A2F2F697066732E696F2F697066732F516D5A6E6A6D4239546B34786141394536373979747250586461336265574D4C556E4D423552466A316553744C70",
+ "MaximumAmount": "9223372036854775807",
+ "Sequence": 5496,
+ "TransferFee": 10
+ }
+ }
+ },
+ {
+ "CreatedNode": {
+ "LedgerEntryType": "DirectoryNode",
+ "LedgerIndex": "3A6F809498F6C0E3664BD8451BBAE5F972E45AF17537354D1C28F3A00B35BDFE",
+ "NewFields": {
+ "Owner": "rfGb6p2kWy3zQweWnYNxSFYoHeymcx7mhg",
+ "RootIndex": "3A6F809498F6C0E3664BD8451BBAE5F972E45AF17537354D1C28F3A00B35BDFE"
+ }
+ }
+ }
+ ],
+ "TransactionIndex": 0,
+ "TransactionResult": "tesSUCCESS",
+ "mpt_issuance_id": "0000157844C3F3B57A8B579FEE1033CC8E8498729D063617"
+ },
+ "hash": "9686DA1322D2D8F9CD97C5848A8E3CADB9D3F73154DA59BB3A3CACC4CA43671C",
+ "ledger_index": 5498,
+ "date": 1710949602000
+}
diff --git a/src/containers/shared/components/Transaction/MPTokenIssuanceCreate/types.ts b/src/containers/shared/components/Transaction/MPTokenIssuanceCreate/types.ts
new file mode 100644
index 000000000..2cce5aa21
--- /dev/null
+++ b/src/containers/shared/components/Transaction/MPTokenIssuanceCreate/types.ts
@@ -0,0 +1,16 @@
+import { TransactionCommonFields } from '../types'
+
+export interface MPTokenIssuanceCreate extends TransactionCommonFields {
+ AssetScale?: number
+ MaximumAmount?: string
+ TransferFee?: number
+ MPTokenMetadata?: string
+}
+
+export interface MPTokenIssuanceCreateInstructions {
+ issuanceID?: string
+ metadata?: string
+ transferFee?: number
+ assetScale?: number
+ maxAmount?: string
+}
diff --git a/src/containers/shared/components/Transaction/MPTokenIssuanceDestroy/Simple.tsx b/src/containers/shared/components/Transaction/MPTokenIssuanceDestroy/Simple.tsx
new file mode 100644
index 000000000..2b9bb6676
--- /dev/null
+++ b/src/containers/shared/components/Transaction/MPTokenIssuanceDestroy/Simple.tsx
@@ -0,0 +1,18 @@
+import { useTranslation } from 'react-i18next'
+import { SimpleRow } from '../SimpleRow'
+import { TransactionSimpleComponent, TransactionSimpleProps } from '../types'
+import { MPTokenIssuanceDestroy } from './types'
+import { MPTokenLink } from '../../MPTokenLink'
+
+export const Simple: TransactionSimpleComponent = ({
+ data,
+}: TransactionSimpleProps) => {
+ const { MPTokenIssuanceID } = data.instructions
+ const { t } = useTranslation()
+
+ return (
+
+
+
+ )
+}
diff --git a/src/containers/shared/components/Transaction/MPTokenIssuanceDestroy/index.ts b/src/containers/shared/components/Transaction/MPTokenIssuanceDestroy/index.ts
new file mode 100644
index 000000000..52f730f68
--- /dev/null
+++ b/src/containers/shared/components/Transaction/MPTokenIssuanceDestroy/index.ts
@@ -0,0 +1,13 @@
+import {
+ TransactionAction,
+ TransactionCategory,
+ TransactionMapping,
+} from '../types'
+
+import { Simple } from './Simple'
+
+export const MPTokenIssuanceDestroyTransaction: TransactionMapping = {
+ Simple,
+ action: TransactionAction.CANCEL,
+ category: TransactionCategory.MPT,
+}
diff --git a/src/containers/shared/components/Transaction/MPTokenIssuanceDestroy/test/MPTokenIssuanceDestroySimple.test.jsx b/src/containers/shared/components/Transaction/MPTokenIssuanceDestroy/test/MPTokenIssuanceDestroySimple.test.jsx
new file mode 100644
index 000000000..ee9a5b7c0
--- /dev/null
+++ b/src/containers/shared/components/Transaction/MPTokenIssuanceDestroy/test/MPTokenIssuanceDestroySimple.test.jsx
@@ -0,0 +1,29 @@
+import { createSimpleWrapperFactory } from '../../test/createWrapperFactory'
+import { Simple } from '../Simple'
+import { expectSimpleRowText } from '../../test'
+import transactionSuccess from './mock_data/MPTokenIssuanceDestroy.json'
+import transactionFail from './mock_data/MPTokenIssuanceDestroy_Fail.json'
+
+const createWrapper = createSimpleWrapperFactory(Simple)
+
+describe('MPTokenIssuanceDestroy', () => {
+ it('handles MPTokenIssuanceDestroy simple view ', () => {
+ const wrapper = createWrapper(transactionSuccess)
+ expectSimpleRowText(
+ wrapper,
+ 'mpt-issuance-id',
+ '0000071E15A457415B9A921957CA1958F0E3B8A049BE8627',
+ )
+ wrapper.unmount()
+ })
+
+ it('handles failed MPTokenIssuanceDestroy simple view ', () => {
+ const wrapper = createWrapper(transactionFail)
+ expectSimpleRowText(
+ wrapper,
+ 'mpt-issuance-id',
+ '0000097E2ACB52C693EABBB156034140B2ED5E9522C4ACF4',
+ )
+ wrapper.unmount()
+ })
+})
diff --git a/src/containers/shared/components/Transaction/MPTokenIssuanceDestroy/test/mock_data/MPTokenIssuanceDestroy.json b/src/containers/shared/components/Transaction/MPTokenIssuanceDestroy/test/mock_data/MPTokenIssuanceDestroy.json
new file mode 100644
index 000000000..3ac12863b
--- /dev/null
+++ b/src/containers/shared/components/Transaction/MPTokenIssuanceDestroy/test/mock_data/MPTokenIssuanceDestroy.json
@@ -0,0 +1,76 @@
+{
+ "tx": {
+ "Account": "rpyShdZBMVC9p6tesouh97JEEWZgYGYTW1",
+ "Fee": "10",
+ "Flags": 2147483648,
+ "MPTokenIssuanceID": "0000071E15A457415B9A921957CA1958F0E3B8A049BE8627",
+ "Sequence": 1823,
+ "SigningPubKey": "ED31ED6E308C928DA72935A03526C3C5422353EB686908D3ADAD9D573921DBDFB5",
+ "TransactionType": "MPTokenIssuanceDestroy",
+ "TxnSignature": "432417F93E86710B50619E4B9EAA43A7F636546A70D4A854E619AF01108A400519A4022B7BABE4807263CDD7EB43217B9ABB8F9745B988981B1A556D11C83200",
+ "ctid": "C000072100000000",
+ "date": 1710968140000,
+ "hash": "undefined",
+ "inLedger": "undefined",
+ "ledger_index": "undefined",
+ "meta": "undefined",
+ "validated": "undefined",
+ "metaData": "undefined",
+ "status": "undefined"
+ },
+ "meta": {
+ "AffectedNodes": [
+ {
+ "ModifiedNode": {
+ "FinalFields": {
+ "Account": "rpyShdZBMVC9p6tesouh97JEEWZgYGYTW1",
+ "Balance": "99999980",
+ "Flags": 0,
+ "OwnerCount": 0,
+ "Sequence": 1824
+ },
+ "LedgerEntryType": "AccountRoot",
+ "LedgerIndex": "27F062565B9B9226F10C5AA25E1AD5C3E70A6A93FF2AB4851614A2C43D083850",
+ "PreviousFields": {
+ "Balance": "99999990",
+ "OwnerCount": 1,
+ "Sequence": 1823
+ },
+ "PreviousTxnID": "E6DFD28EDD7213F43B7D0DB1296D09460458709583BEEA29F17C2F63B4DA9FC4",
+ "PreviousTxnLgrSeq": 1824
+ }
+ },
+ {
+ "DeletedNode": {
+ "FinalFields": {
+ "Flags": 0,
+ "Issuer": "rpyShdZBMVC9p6tesouh97JEEWZgYGYTW1",
+ "OutstandingAmount": "0",
+ "OwnerNode": "0",
+ "PreviousTxnID": "E6DFD28EDD7213F43B7D0DB1296D09460458709583BEEA29F17C2F63B4DA9FC4",
+ "PreviousTxnLgrSeq": 1824,
+ "Sequence": 1822
+ },
+ "LedgerEntryType": "MPTokenIssuance",
+ "LedgerIndex": "9C882DA4DF7B92FC968A0ADCA8BAFB7842264F98A5E147348C6E6077EAB24AA8"
+ }
+ },
+ {
+ "DeletedNode": {
+ "FinalFields": {
+ "Flags": 0,
+ "Owner": "rpyShdZBMVC9p6tesouh97JEEWZgYGYTW1",
+ "RootIndex": "B8124B2AD6A85560C73E1748E2C8B0E5C0871F4439F10B042DD7E4017D864287"
+ },
+ "LedgerEntryType": "DirectoryNode",
+ "LedgerIndex": "B8124B2AD6A85560C73E1748E2C8B0E5C0871F4439F10B042DD7E4017D864287"
+ }
+ }
+ ],
+ "TransactionIndex": 0,
+ "TransactionResult": "tesSUCCESS"
+ },
+ "hash": "9EB556D18BFB67F31C8716C7F3CBBB070E1E7B120DEDDC423D25DFAD850BD93A",
+ "ledger_index": 1825,
+ "date": 1710968140000
+}
diff --git a/src/containers/shared/components/Transaction/MPTokenIssuanceDestroy/test/mock_data/MPTokenIssuanceDestroy_Fail.json b/src/containers/shared/components/Transaction/MPTokenIssuanceDestroy/test/mock_data/MPTokenIssuanceDestroy_Fail.json
new file mode 100644
index 000000000..88b5cc651
--- /dev/null
+++ b/src/containers/shared/components/Transaction/MPTokenIssuanceDestroy/test/mock_data/MPTokenIssuanceDestroy_Fail.json
@@ -0,0 +1,49 @@
+{
+ "tx": {
+ "Account": "rJbNXmT1uhmbghSQAFcgxAAN9yCRCu9y7g",
+ "Fee": "10",
+ "Flags": 2147483648,
+ "MPTokenIssuanceID": "0000097E2ACB52C693EABBB156034140B2ED5E9522C4ACF4",
+ "Sequence": 2431,
+ "SigningPubKey": "EDF5A4F08EDD12BB89658B8DE56558600342AD92D42FEDFAD682F4DAD9647EF5AA",
+ "TransactionType": "MPTokenIssuanceDestroy",
+ "TxnSignature": "C3B5F7D9A21A5EF85663058790DE7F458EA903C0010F3C2E1FEA45647052C2D85231ED4C17EB324A97AC2D4DE9A71F46D277B8A0A7AFB42D30A3EDA42E8E4106",
+ "ctid": "C000098100000000",
+ "date": 1710970247000,
+ "hash": "undefined",
+ "inLedger": "undefined",
+ "ledger_index": "undefined",
+ "meta": "undefined",
+ "validated": "undefined",
+ "metaData": "undefined",
+ "status": "undefined"
+ },
+ "meta": {
+ "AffectedNodes": [
+ {
+ "ModifiedNode": {
+ "FinalFields": {
+ "Account": "rJbNXmT1uhmbghSQAFcgxAAN9yCRCu9y7g",
+ "Balance": "99999990",
+ "Flags": 0,
+ "OwnerCount": 0,
+ "Sequence": 2432
+ },
+ "LedgerEntryType": "AccountRoot",
+ "LedgerIndex": "BA6DF5388FF6BF026D3F8C91893534890F87309A32D30749CC70BCB1C6F1BEF6",
+ "PreviousFields": {
+ "Balance": "100000000",
+ "Sequence": 2431
+ },
+ "PreviousTxnID": "07F74CD4BD3E54410E436F9895BC2EC98D35E05F7584CB95D6660AF0411E9283",
+ "PreviousTxnLgrSeq": 2431
+ }
+ }
+ ],
+ "TransactionIndex": 0,
+ "TransactionResult": "tecNO_PERMISSION"
+ },
+ "hash": "2F31C86C343B2D2DB3D8B01BDE84E5BFA0BCB86321365A16D93DF806B79B16FD",
+ "ledger_index": 2433,
+ "date": 1710970247000
+}
diff --git a/src/containers/shared/components/Transaction/MPTokenIssuanceDestroy/types.ts b/src/containers/shared/components/Transaction/MPTokenIssuanceDestroy/types.ts
new file mode 100644
index 000000000..db9b812f9
--- /dev/null
+++ b/src/containers/shared/components/Transaction/MPTokenIssuanceDestroy/types.ts
@@ -0,0 +1,5 @@
+import { TransactionCommonFields } from '../types'
+
+export interface MPTokenIssuanceDestroy extends TransactionCommonFields {
+ MPTokenIssuanceID: string
+}
diff --git a/src/containers/shared/components/Transaction/MPTokenIssuanceSet/Simple.tsx b/src/containers/shared/components/Transaction/MPTokenIssuanceSet/Simple.tsx
new file mode 100644
index 000000000..2825a3118
--- /dev/null
+++ b/src/containers/shared/components/Transaction/MPTokenIssuanceSet/Simple.tsx
@@ -0,0 +1,26 @@
+import { useTranslation } from 'react-i18next'
+import { SimpleRow } from '../SimpleRow'
+import { TransactionSimpleComponent, TransactionSimpleProps } from '../types'
+import { MPTokenIssuanceSet } from './types'
+import { Account } from '../../Account'
+import { MPTokenLink } from '../../MPTokenLink'
+
+export const Simple: TransactionSimpleComponent = ({
+ data,
+}: TransactionSimpleProps) => {
+ const { MPTokenIssuanceID, Holder } = data.instructions
+ const { t } = useTranslation()
+
+ return (
+ <>
+
+
+
+ {Holder && (
+
+
+
+ )}
+ >
+ )
+}
diff --git a/src/containers/shared/components/Transaction/MPTokenIssuanceSet/index.ts b/src/containers/shared/components/Transaction/MPTokenIssuanceSet/index.ts
new file mode 100644
index 000000000..acbe8d389
--- /dev/null
+++ b/src/containers/shared/components/Transaction/MPTokenIssuanceSet/index.ts
@@ -0,0 +1,13 @@
+import {
+ TransactionAction,
+ TransactionCategory,
+ TransactionMapping,
+} from '../types'
+
+import { Simple } from './Simple'
+
+export const MPTokenIssuanceSetTransaction: TransactionMapping = {
+ Simple,
+ action: TransactionAction.MODIFY,
+ category: TransactionCategory.MPT,
+}
diff --git a/src/containers/shared/components/Transaction/MPTokenIssuanceSet/test/MPTokenIssuanceSetSimple.test.jsx b/src/containers/shared/components/Transaction/MPTokenIssuanceSet/test/MPTokenIssuanceSetSimple.test.jsx
new file mode 100644
index 000000000..2083e158c
--- /dev/null
+++ b/src/containers/shared/components/Transaction/MPTokenIssuanceSet/test/MPTokenIssuanceSetSimple.test.jsx
@@ -0,0 +1,53 @@
+import { createSimpleWrapperFactory } from '../../test/createWrapperFactory'
+import { Simple } from '../Simple'
+import { expectSimpleRowText, expectSimpleRowNotToExist } from '../../test'
+import transactionSuccess from './mock_data/MPTokenIssuanceSet.json'
+import transactionNoHolder from './mock_data/MPTokenIssuanceSet_NoHolder.json'
+import transactionFail from './mock_data/MPTokenIssuanceSet_Fail.json'
+
+const createWrapper = createSimpleWrapperFactory(Simple)
+
+describe('MPTokenIssuanceSet', () => {
+ it('handles MPTokenIssuanceSet simple view ', () => {
+ const wrapper = createWrapper(transactionSuccess)
+
+ expectSimpleRowText(
+ wrapper,
+ 'mpt-issuance-id',
+ '00000BED9E4ADA3DCC1BE78683C4B623A74013818160590C',
+ )
+ expectSimpleRowText(
+ wrapper,
+ 'mpt-holder',
+ 'r9hF4e3e6kLuxLobPwfQa2wzXZMDvBDeUg',
+ )
+ wrapper.unmount()
+ })
+
+ it('handles MPTokenIssuanceSet simple view w/o holder ', () => {
+ const wrapper = createWrapper(transactionNoHolder)
+
+ expectSimpleRowText(
+ wrapper,
+ 'mpt-issuance-id',
+ '000002609BB39CEC721B5AB337B6BD862ACD2811CBBB5F18',
+ )
+ expectSimpleRowNotToExist(wrapper, 'mpt-holder')
+ wrapper.unmount()
+ })
+
+ it('handles failed MPTokenIssuanceSet simple view ', () => {
+ const wrapper = createWrapper(transactionFail)
+ expectSimpleRowText(
+ wrapper,
+ 'mpt-issuance-id',
+ '00000F83146C83112AED215CD345F8E7327459BFCF6B8062',
+ )
+ expectSimpleRowText(
+ wrapper,
+ 'mpt-holder',
+ 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh',
+ )
+ wrapper.unmount()
+ })
+})
diff --git a/src/containers/shared/components/Transaction/MPTokenIssuanceSet/test/mock_data/MPTokenIssuanceSet.json b/src/containers/shared/components/Transaction/MPTokenIssuanceSet/test/mock_data/MPTokenIssuanceSet.json
new file mode 100644
index 000000000..c9beb8f58
--- /dev/null
+++ b/src/containers/shared/components/Transaction/MPTokenIssuanceSet/test/mock_data/MPTokenIssuanceSet.json
@@ -0,0 +1,67 @@
+{
+ "tx": {
+ "Account": "rERyS9qtwky94UMMjjmbku3uo5aQwAoJ58",
+ "Fee": "10",
+ "Flags": 1,
+ "Holder": "r9hF4e3e6kLuxLobPwfQa2wzXZMDvBDeUg",
+ "MPTokenIssuanceID": "00000BED9E4ADA3DCC1BE78683C4B623A74013818160590C",
+ "Sequence": 3054,
+ "SigningPubKey": "EDF73A1C528F5BFBD6FF2B05D0C71760D7D2DF1DE3496935612E47BCB440F28040",
+ "TransactionType": "MPTokenIssuanceSet",
+ "TxnSignature": "5BC9ABE91A10F86440E301F17DFADD08D2E55E0699441372BE73D843B4481869BD6224ED461BC4F9894E97F1F99562D3CF2CD1A4E991BD7993DC24EDA63F5B05",
+ "ctid": "C0000BF100000000",
+ "date": 1711047580000,
+ "hash": "undefined",
+ "inLedger": "undefined",
+ "ledger_index": "undefined",
+ "meta": "undefined",
+ "validated": "undefined",
+ "metaData": "undefined",
+ "status": "undefined"
+ },
+ "meta": {
+ "AffectedNodes": [
+ {
+ "ModifiedNode": {
+ "FinalFields": {
+ "Account": "r9hF4e3e6kLuxLobPwfQa2wzXZMDvBDeUg",
+ "Flags": 1,
+ "MPTokenIssuanceID": "00000BED9E4ADA3DCC1BE78683C4B623A74013818160590C",
+ "OwnerNode": "0"
+ },
+ "LedgerEntryType": "MPToken",
+ "LedgerIndex": "DDA698915F22D7CEA45896CB70DCC0DF803E1F573B92B6F0178F1688208EED04",
+ "PreviousFields": {
+ "Flags": 0
+ },
+ "PreviousTxnID": "39709CA66D9103354D09070234A14253EC779846BB73477EEB21C5A65144C844",
+ "PreviousTxnLgrSeq": 3056
+ }
+ },
+ {
+ "ModifiedNode": {
+ "FinalFields": {
+ "Account": "rERyS9qtwky94UMMjjmbku3uo5aQwAoJ58",
+ "Balance": "99999980",
+ "Flags": 0,
+ "OwnerCount": 1,
+ "Sequence": 3055
+ },
+ "LedgerEntryType": "AccountRoot",
+ "LedgerIndex": "EDD52DA05DAB16BAF6A3B7D47CCB9FEAB7AAC2BDD9CB007F6A3B8E0DBCE50A45",
+ "PreviousFields": {
+ "Balance": "99999990",
+ "Sequence": 3054
+ },
+ "PreviousTxnID": "41A99F7E107F813C132B105AB930FD8C6960530DDFA5D98FCEF5A5600DA39D38",
+ "PreviousTxnLgrSeq": 3055
+ }
+ }
+ ],
+ "TransactionIndex": 0,
+ "TransactionResult": "tesSUCCESS"
+ },
+ "hash": "4993E5B875E0217ABC92EFC395805F1344D8A9A3D75437EEA457C05EDB3AB20B",
+ "ledger_index": 3057,
+ "date": 1711047580000
+}
diff --git a/src/containers/shared/components/Transaction/MPTokenIssuanceSet/test/mock_data/MPTokenIssuanceSet_Fail.json b/src/containers/shared/components/Transaction/MPTokenIssuanceSet/test/mock_data/MPTokenIssuanceSet_Fail.json
new file mode 100644
index 000000000..3526bc28c
--- /dev/null
+++ b/src/containers/shared/components/Transaction/MPTokenIssuanceSet/test/mock_data/MPTokenIssuanceSet_Fail.json
@@ -0,0 +1,50 @@
+{
+ "tx": {
+ "Account": "rpizWPf4g8JLWFUT7143Zn9A1n2Dy9bnji",
+ "Fee": "10",
+ "Flags": 1,
+ "Holder": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
+ "MPTokenIssuanceID": "00000F83146C83112AED215CD345F8E7327459BFCF6B8062",
+ "Sequence": 3972,
+ "SigningPubKey": "ED4EC06184C745D99AEAAA16526C900DC181C8546899F462C3D105C11A6677A65A",
+ "TransactionType": "MPTokenIssuanceSet",
+ "TxnSignature": "2A9D1795983016A162F05CEBBD35E65B955BD67AB96B8A2DB2E31027EA67DDF082B1C4D67F9219CBF9893520ACB0ACB80E2ED3CE96496AA4668BCCF4A46EAE0A",
+ "ctid": "C0000F8700000000",
+ "date": 1711048704000,
+ "hash": "undefined",
+ "inLedger": "undefined",
+ "ledger_index": "undefined",
+ "meta": "undefined",
+ "validated": "undefined",
+ "metaData": "undefined",
+ "status": "undefined"
+ },
+ "meta": {
+ "AffectedNodes": [
+ {
+ "ModifiedNode": {
+ "FinalFields": {
+ "Account": "rpizWPf4g8JLWFUT7143Zn9A1n2Dy9bnji",
+ "Balance": "99999980",
+ "Flags": 0,
+ "OwnerCount": 1,
+ "Sequence": 3973
+ },
+ "LedgerEntryType": "AccountRoot",
+ "LedgerIndex": "F937A006AB86775D475946D43BA1612F3BC24A2C144550D4EC0266C2F08303D5",
+ "PreviousFields": {
+ "Balance": "99999990",
+ "Sequence": 3972
+ },
+ "PreviousTxnID": "8D322CBF8A965E64FB903551C80A2E1DF9E2480A7AD0B1D98BE56574BC9FE215",
+ "PreviousTxnLgrSeq": 3973
+ }
+ }
+ ],
+ "TransactionIndex": 0,
+ "TransactionResult": "tecOBJECT_NOT_FOUND"
+ },
+ "hash": "E47D5242B7E210B9E1DAEFF90DC19DA9310C04CF4444C76F2C76A44533EEC48F",
+ "ledger_index": 3975,
+ "date": 1711048704000
+}
diff --git a/src/containers/shared/components/Transaction/MPTokenIssuanceSet/test/mock_data/MPTokenIssuanceSet_NoHolder.json b/src/containers/shared/components/Transaction/MPTokenIssuanceSet/test/mock_data/MPTokenIssuanceSet_NoHolder.json
new file mode 100644
index 000000000..165596d2a
--- /dev/null
+++ b/src/containers/shared/components/Transaction/MPTokenIssuanceSet/test/mock_data/MPTokenIssuanceSet_NoHolder.json
@@ -0,0 +1,67 @@
+{
+ "tx": {
+ "Account": "rEUGuTqrySk9o1rZSVx8seuvcsEZymeEYM",
+ "Fee": "10",
+ "Flags": 1,
+ "MPTokenIssuanceID": "000002609BB39CEC721B5AB337B6BD862ACD2811CBBB5F18",
+ "Sequence": 609,
+ "SigningPubKey": "ED92DDE49AA689EC63589623067968B85E4885A9874B3CAB89E07D192EBFA42FF9",
+ "TransactionType": "MPTokenIssuanceSet",
+ "TxnSignature": "6678BF017A62360DAA39156112960D934440467D2E4098958F053B6758A2D237DA82CD27FDA3B35BAB610D74669BF6C4DEB12D77984E48158B2D79C8BAEC3303",
+ "ctid": "C000026400000000",
+ "date": 1711133159000,
+ "hash": "undefined",
+ "inLedger": "undefined",
+ "ledger_index": "undefined",
+ "meta": "undefined",
+ "validated": "undefined",
+ "metaData": "undefined",
+ "status": "undefined"
+ },
+ "meta": {
+ "AffectedNodes": [
+ {
+ "ModifiedNode": {
+ "FinalFields": {
+ "Account": "rEUGuTqrySk9o1rZSVx8seuvcsEZymeEYM",
+ "Balance": "99999980",
+ "Flags": 0,
+ "OwnerCount": 1,
+ "Sequence": 610
+ },
+ "LedgerEntryType": "AccountRoot",
+ "LedgerIndex": "1D54D263727856612FC7C7A27D93532ED0C41B74FA651992C125AD19C900669D",
+ "PreviousFields": {
+ "Balance": "99999990",
+ "Sequence": 609
+ },
+ "PreviousTxnID": "CF68A8D929F089F6F53B071250935DD7C3F52F6E175D82E53118265D776D4BF7",
+ "PreviousTxnLgrSeq": 610
+ }
+ },
+ {
+ "ModifiedNode": {
+ "FinalFields": {
+ "Flags": 35,
+ "Issuer": "rEUGuTqrySk9o1rZSVx8seuvcsEZymeEYM",
+ "OutstandingAmount": "0",
+ "OwnerNode": "0",
+ "Sequence": 608
+ },
+ "LedgerEntryType": "MPTokenIssuance",
+ "LedgerIndex": "E8DF796110B1FBD9086A4637491E49843D33D897FBC32F03A33140F76378EE86",
+ "PreviousFields": {
+ "Flags": 34
+ },
+ "PreviousTxnID": "CF68A8D929F089F6F53B071250935DD7C3F52F6E175D82E53118265D776D4BF7",
+ "PreviousTxnLgrSeq": 610
+ }
+ }
+ ],
+ "TransactionIndex": 0,
+ "TransactionResult": "tesSUCCESS"
+ },
+ "hash": "73C21D8B5DFBF5DE03FCAF0D69C00E9E2918280561E9898C556A1C743A566D47",
+ "ledger_index": 612,
+ "date": 1711133159000
+}
diff --git a/src/containers/shared/components/Transaction/MPTokenIssuanceSet/types.ts b/src/containers/shared/components/Transaction/MPTokenIssuanceSet/types.ts
new file mode 100644
index 000000000..038ee2607
--- /dev/null
+++ b/src/containers/shared/components/Transaction/MPTokenIssuanceSet/types.ts
@@ -0,0 +1,6 @@
+import { TransactionCommonFields } from '../types'
+
+export interface MPTokenIssuanceSet extends TransactionCommonFields {
+ MPTokenIssuanceID: string
+ Holder?: string
+}
diff --git a/src/containers/shared/components/Transaction/NFTokenAcceptOffer/parser.ts b/src/containers/shared/components/Transaction/NFTokenAcceptOffer/parser.ts
index 85abfdaa6..5586be0c8 100644
--- a/src/containers/shared/components/Transaction/NFTokenAcceptOffer/parser.ts
+++ b/src/containers/shared/components/Transaction/NFTokenAcceptOffer/parser.ts
@@ -35,7 +35,7 @@ export const parser: TransactionParser<
return {
amount: formatAmount(buyOfferNode.Amount),
- tokenID: buyOfferNode.NFTokenID,
+ tokenID: meta.nftoken_id,
seller: sellOfferNode.Owner,
buyer: buyOfferNode.Owner,
acceptedOfferIDs,
@@ -51,7 +51,7 @@ export const parser: TransactionParser<
}
const amount = formatAmount(acceptedOfferNode.Amount)
- const tokenID = acceptedOfferNode.NFTokenID
+ const tokenID = meta.nftoken_id
const offerer = acceptedOfferNode.Owner
const accepter = tx.Account
const isSellOffer = determineIsSellOffer(acceptedOfferNode.Flags)
diff --git a/src/containers/shared/components/Transaction/NFTokenAcceptOffer/test/mock_data/NFTokenAcceptOffer_Broker.json b/src/containers/shared/components/Transaction/NFTokenAcceptOffer/test/mock_data/NFTokenAcceptOffer_Broker.json
index 4a2ab29ab..f1bc90003 100644
--- a/src/containers/shared/components/Transaction/NFTokenAcceptOffer/test/mock_data/NFTokenAcceptOffer_Broker.json
+++ b/src/containers/shared/components/Transaction/NFTokenAcceptOffer/test/mock_data/NFTokenAcceptOffer_Broker.json
@@ -255,7 +255,8 @@
}
],
"TransactionIndex": 7,
- "TransactionResult": "tesSUCCESS"
+ "TransactionResult": "tesSUCCESS",
+ "nftoken_id": "00081B581189F5687DBB7516339D6CCB5593D96622AD82DF08CFDA8600000A17"
},
"hash": "E6CE3C3C554BA01891A9D12E89062C34BBEA6282CF8AA4D9AF8BF7D0E7D26B7D",
"ledger_index": 76208251,
diff --git a/src/containers/shared/components/Transaction/NFTokenAcceptOffer/test/mock_data/NFTokenAcceptOffer_Buy.json b/src/containers/shared/components/Transaction/NFTokenAcceptOffer/test/mock_data/NFTokenAcceptOffer_Buy.json
index d984fc1f7..6bb4a8973 100644
--- a/src/containers/shared/components/Transaction/NFTokenAcceptOffer/test/mock_data/NFTokenAcceptOffer_Buy.json
+++ b/src/containers/shared/components/Transaction/NFTokenAcceptOffer/test/mock_data/NFTokenAcceptOffer_Buy.json
@@ -439,7 +439,8 @@
}
],
"TransactionIndex": 4,
- "TransactionResult": "tesSUCCESS"
+ "TransactionResult": "tesSUCCESS",
+ "nftoken_id": "000800006203F49C21D5D6E022CB16DE3538F248662FC73C29ABA6A90000000D"
},
"hash": "21C60F255B29D0034B9EBA2ED1F7523635C61DED1D6BDBCFFD67703C45F7072D",
"ledger_index": 1799495,
diff --git a/src/containers/shared/components/Transaction/NFTokenAcceptOffer/test/mock_data/NFTokenAcceptOffer_Sell.json b/src/containers/shared/components/Transaction/NFTokenAcceptOffer/test/mock_data/NFTokenAcceptOffer_Sell.json
index 3f78b0426..77fe997f5 100644
--- a/src/containers/shared/components/Transaction/NFTokenAcceptOffer/test/mock_data/NFTokenAcceptOffer_Sell.json
+++ b/src/containers/shared/components/Transaction/NFTokenAcceptOffer/test/mock_data/NFTokenAcceptOffer_Sell.json
@@ -392,7 +392,8 @@
}
],
"TransactionIndex": 0,
- "TransactionResult": "tesSUCCESS"
+ "TransactionResult": "tesSUCCESS",
+ "nftoken_id": "000800006203F49C21D5D6E022CB16DE3538F248662FC73C216B9CBF00000023"
},
"hash": "553922B202932DD500C6E0D6905BA1D8ACC0DF746519DD6AAA724AF70D23F1E6",
"ledger_index": 1889191,
diff --git a/src/containers/shared/components/Transaction/NFTokenAcceptOffer/types.ts b/src/containers/shared/components/Transaction/NFTokenAcceptOffer/types.ts
index 249a9632d..5091a827e 100644
--- a/src/containers/shared/components/Transaction/NFTokenAcceptOffer/types.ts
+++ b/src/containers/shared/components/Transaction/NFTokenAcceptOffer/types.ts
@@ -1,6 +1,8 @@
+import { ExplorerAmount } from '../../../types'
+
export interface NFTokenAcceptOfferInstructions {
acceptedOfferIDs: string[]
- amount?: { currency: string; amount: number; issuer?: string }
+ amount?: ExplorerAmount
tokenID?: string
seller?: string
buyer?: string
diff --git a/src/containers/shared/components/Transaction/NFTokenCancelOffer/parser.ts b/src/containers/shared/components/Transaction/NFTokenCancelOffer/parser.ts
index f2a8debe2..4857fb442 100644
--- a/src/containers/shared/components/Transaction/NFTokenCancelOffer/parser.ts
+++ b/src/containers/shared/components/Transaction/NFTokenCancelOffer/parser.ts
@@ -10,7 +10,7 @@ export const parser: TransactionParser<
const cancelledOffers = meta.AffectedNodes.filter(
(node: any) => node.DeletedNode?.LedgerEntryType === 'NFTokenOffer',
).map((node: any) => ({
- offerID: node.DeletedNode.LedgerIndex,
+ offerID: meta.offer_id,
amount: formatAmount(node.DeletedNode.FinalFields.Amount),
tokenID: node.DeletedNode.FinalFields.NFTokenID,
offerer: node.DeletedNode.FinalFields.Owner,
diff --git a/src/containers/shared/components/Transaction/NFTokenCancelOffer/test/NFTokenCancelOfferSimple.test.jsx b/src/containers/shared/components/Transaction/NFTokenCancelOffer/test/NFTokenCancelOfferSimple.test.jsx
index b16576254..ec58e6280 100644
--- a/src/containers/shared/components/Transaction/NFTokenCancelOffer/test/NFTokenCancelOfferSimple.test.jsx
+++ b/src/containers/shared/components/Transaction/NFTokenCancelOffer/test/NFTokenCancelOfferSimple.test.jsx
@@ -1,21 +1,25 @@
import { BrowserRouter as Router } from 'react-router-dom'
import { mount } from 'enzyme'
import { I18nextProvider } from 'react-i18next'
+import { QueryClientProvider } from 'react-query'
import { Simple as NFTokenCancelOffer } from '../Simple'
import transaction from './mock_data/NFTokenCancelOffer.json'
import summarizeTransaction from '../../../../../../rippled/lib/txSummary'
import i18n from '../../../../../../i18n/testConfig'
+import { queryClient } from '../../../../QueryClient'
describe('NFTokenCancelOffer', () => {
it.only('handles NFTokenCancelOffer simple view ', () => {
const wrapper = mount(
-
-
-
-
- ,
+
+
+
+
+
+
+ ,
)
expect(wrapper.find('[data-test="token-id"] .value')).toHaveText(
'000800006203F49C21D5D6E022CB16DE3538F248662FC73C258BA1B200000018',
diff --git a/src/containers/shared/components/Transaction/NFTokenCancelOffer/test/mock_data/NFTokenCancelOffer.json b/src/containers/shared/components/Transaction/NFTokenCancelOffer/test/mock_data/NFTokenCancelOffer.json
index ee6e5472d..d625ca873 100644
--- a/src/containers/shared/components/Transaction/NFTokenCancelOffer/test/mock_data/NFTokenCancelOffer.json
+++ b/src/containers/shared/components/Transaction/NFTokenCancelOffer/test/mock_data/NFTokenCancelOffer.json
@@ -81,7 +81,8 @@
}
],
"TransactionIndex": 2,
- "TransactionResult": "tesSUCCESS"
+ "TransactionResult": "tesSUCCESS",
+ "offer_id": "35F3D6D99548FA5F5315580FBF8BA6B15CAA2CAE93023D5CE4FDC130602BC5C3"
},
"hash": "AF12B2694896ADE0F93C8C3D09602B242F08C50854A0C600E0E7A2E18586C8C3",
"ledger_index": 1799491,
diff --git a/src/containers/shared/components/Transaction/NFTokenCreateOffer/parser.ts b/src/containers/shared/components/Transaction/NFTokenCreateOffer/parser.ts
index e818c0294..deaaacffe 100644
--- a/src/containers/shared/components/Transaction/NFTokenCreateOffer/parser.ts
+++ b/src/containers/shared/components/Transaction/NFTokenCreateOffer/parser.ts
@@ -2,7 +2,6 @@ import type { NFTokenCreateOffer } from 'xrpl'
import { NFTokenCreateOfferInstructions } from './types'
import { TransactionParser } from '../types'
import { formatAmount } from '../../../../../rippled/lib/txSummary/formatAmount'
-import { findNode } from '../../../transactionUtils'
export const parser: TransactionParser<
NFTokenCreateOffer,
@@ -13,7 +12,7 @@ export const parser: TransactionParser<
const tokenID = tx.NFTokenID
const isSellOffer = ((tx.Flags as number)! & 1) !== 0
const owner = tx.Owner
- const offerID = findNode(meta, 'CreatedNode', 'NFTokenOffer')?.LedgerIndex
+ const offerID = meta.offer_id
const destination = tx.Destination
return {
diff --git a/src/containers/shared/components/Transaction/NFTokenCreateOffer/test/mock_data/NFTokenCreateOffer_Buy.json b/src/containers/shared/components/Transaction/NFTokenCreateOffer/test/mock_data/NFTokenCreateOffer_Buy.json
index 57b78b9c9..546e25bc7 100644
--- a/src/containers/shared/components/Transaction/NFTokenCreateOffer/test/mock_data/NFTokenCreateOffer_Buy.json
+++ b/src/containers/shared/components/Transaction/NFTokenCreateOffer/test/mock_data/NFTokenCreateOffer_Buy.json
@@ -70,7 +70,8 @@
}
],
"TransactionIndex": 0,
- "TransactionResult": "tesSUCCESS"
+ "TransactionResult": "tesSUCCESS",
+ "offer_id": "3D1C297DA5B831267CCF692F8A023688D6A4BD5AFAE9A746D5C4E0B15D256B29"
},
"hash": "385DC0497AAF8061549A7DC04EB20C2B387A167792796F5806A13E0422125563",
"ledger_index": 1887982,
diff --git a/src/containers/shared/components/Transaction/NFTokenCreateOffer/test/mock_data/NFTokenCreateOffer_Sell.json b/src/containers/shared/components/Transaction/NFTokenCreateOffer/test/mock_data/NFTokenCreateOffer_Sell.json
index ca2b485e1..01a6427e6 100644
--- a/src/containers/shared/components/Transaction/NFTokenCreateOffer/test/mock_data/NFTokenCreateOffer_Sell.json
+++ b/src/containers/shared/components/Transaction/NFTokenCreateOffer/test/mock_data/NFTokenCreateOffer_Sell.json
@@ -80,7 +80,8 @@
}
],
"TransactionIndex": 1,
- "TransactionResult": "tesSUCCESS"
+ "TransactionResult": "tesSUCCESS",
+ "offer_id": "F660CA62E16B8067649052E8FCE947049FC6EF0D8B42EF7E5819997EC5AE45B6"
},
"hash": "47DCA082AE5920D672B32E63623B799899B91B77D68365711B07376E5ACFC8DB",
"ledger_index": 1799495,
diff --git a/src/containers/shared/components/Transaction/NFTokenCreateOffer/types.ts b/src/containers/shared/components/Transaction/NFTokenCreateOffer/types.ts
index 624c2dc47..e25ed0359 100644
--- a/src/containers/shared/components/Transaction/NFTokenCreateOffer/types.ts
+++ b/src/containers/shared/components/Transaction/NFTokenCreateOffer/types.ts
@@ -1,6 +1,8 @@
+import { ExplorerAmount } from '../../../types'
+
export interface NFTokenCreateOfferInstructions {
account: string
- amount: { currency: string; amount: number; issuer?: string }
+ amount: ExplorerAmount
tokenID: string
isSellOffer: boolean
owner?: string
diff --git a/src/containers/shared/components/Transaction/NFTokenMint/parser.ts b/src/containers/shared/components/Transaction/NFTokenMint/parser.ts
index 81dbc527c..acb0cd7c1 100644
--- a/src/containers/shared/components/Transaction/NFTokenMint/parser.ts
+++ b/src/containers/shared/components/Transaction/NFTokenMint/parser.ts
@@ -7,51 +7,10 @@ import { convertHexToString } from '../../../../../rippled/lib/utils'
export const parser: TransactionParser = (
tx,
meta,
-) => {
- // When a mint results in splitting an existing page,
- // it results in a created page and a modified node. Sometimes,
- // the created node needs to be linked to a third page, resulting
- // in modifying that third page's PreviousPageMin or NextPageMin
- // field changing, but no NFTs within that page changing. In this
- // case, there will be no previous NFTs and we need to skip.
- // However, there will always be NFTs listed in the final fields,
- // as rippled outputs all fields in final fields even if they were
- // not changed. Thus why we add the additional condition to check
- // if the PreviousFields contains NFTokens
- const affectedNodes = meta.AffectedNodes.filter(
- (node: any) =>
- node.CreatedNode?.LedgerEntryType === 'NFTokenPage' ||
- (node.ModifiedNode?.LedgerEntryType === 'NFTokenPage' &&
- !!node.ModifiedNode?.PreviousFields.NFTokens),
- )
-
- const previousTokenIDSet = new Set(
- affectedNodes
- .flatMap(
- (node: any) =>
- node.ModifiedNode?.PreviousFields?.NFTokens?.map(
- (token: any) => token.NFToken.NFTokenID,
- ),
- )
- .filter((id: any) => id),
- )
-
- const finalTokenIDs = affectedNodes
- .flatMap(
- (node: any) =>
- (
- node.ModifiedNode?.FinalFields ?? node.CreatedNode?.NewFields
- )?.NFTokens?.map((token: any) => token.NFToken.NFTokenID),
- )
- .filter((id: any) => id)
-
- const tokenID = finalTokenIDs.find((id: any) => !previousTokenIDSet.has(id))
-
- return {
- tokenID,
- tokenTaxon: tx.NFTokenTaxon,
- uri: convertHexToString(tx.URI),
- transferFee: tx.TransferFee,
- issuer: tx.Issuer,
- }
-}
+) => ({
+ tokenID: meta.nftoken_id,
+ tokenTaxon: tx.NFTokenTaxon,
+ uri: convertHexToString(tx.URI),
+ transferFee: tx.TransferFee,
+ issuer: tx.Issuer,
+})
diff --git a/src/containers/shared/components/Transaction/NFTokenMint/test/mock_data/NFTokenMintModified1Created1.json b/src/containers/shared/components/Transaction/NFTokenMint/test/mock_data/NFTokenMintModified1Created1.json
index d19662a65..2fb674b4e 100644
--- a/src/containers/shared/components/Transaction/NFTokenMint/test/mock_data/NFTokenMintModified1Created1.json
+++ b/src/containers/shared/components/Transaction/NFTokenMint/test/mock_data/NFTokenMintModified1Created1.json
@@ -53,7 +53,8 @@
}
],
"TransactionIndex": 1,
- "TransactionResult": "tesSUCCESS"
+ "TransactionResult": "tesSUCCESS",
+ "nftoken_id": "0008000085D33F9C5481D3515029C9904D16F0109414D3A00000099A00000000"
},
"hash": "B0AAA46053F2570200CA1E12978EFFBB124374276669CC3F68602A6788182172",
"ledger_index": 5605230,
diff --git a/src/containers/shared/components/Transaction/NFTokenMint/test/mock_data/NFTokenMintModified2.json b/src/containers/shared/components/Transaction/NFTokenMint/test/mock_data/NFTokenMintModified2.json
index 27f0ad378..4cb746d3d 100644
--- a/src/containers/shared/components/Transaction/NFTokenMint/test/mock_data/NFTokenMintModified2.json
+++ b/src/containers/shared/components/Transaction/NFTokenMint/test/mock_data/NFTokenMintModified2.json
@@ -301,7 +301,8 @@
}
],
"TransactionIndex": 0,
- "TransactionResult": "tesSUCCESS"
+ "TransactionResult": "tesSUCCESS",
+ "nftoken_id": "000800006203F49C21D5D6E022CB16DE3538F248662FC73C535743B40000001A"
},
"hash": "B9A20167DC30985ABD983912F29DE81CB4579FAF9672C9D68BEC5219C19C7E50",
"ledger_index": 1861436,
diff --git a/src/containers/shared/components/Transaction/NFTokenMint/test/mock_data/NFTokenMintModified4Created1.json b/src/containers/shared/components/Transaction/NFTokenMint/test/mock_data/NFTokenMintModified4Created1.json
index 2c9a62634..2156f52bb 100644
--- a/src/containers/shared/components/Transaction/NFTokenMint/test/mock_data/NFTokenMintModified4Created1.json
+++ b/src/containers/shared/components/Transaction/NFTokenMint/test/mock_data/NFTokenMintModified4Created1.json
@@ -690,7 +690,8 @@
}
],
"TransactionIndex": 337,
- "TransactionResult": "tesSUCCESS"
+ "TransactionResult": "tesSUCCESS",
+ "nftoken_id": "000D0000B9BD7D214128A91ECECE5FCFF9BDB0D043567C51CFBEC443000063A7"
},
"hash": "4E0EB5F23D248740CB8FC28D1003CEFE841E21811FE2EA4B195CFE1B0BC54219",
"ledger_index": 6453184,
diff --git a/src/containers/shared/components/Transaction/NFTokenMint/test/mock_data/NFTokenMintMostModified2Created1.json b/src/containers/shared/components/Transaction/NFTokenMint/test/mock_data/NFTokenMintMostModified2Created1.json
index 7278c7c13..f2ccdcc36 100644
--- a/src/containers/shared/components/Transaction/NFTokenMint/test/mock_data/NFTokenMintMostModified2Created1.json
+++ b/src/containers/shared/components/Transaction/NFTokenMint/test/mock_data/NFTokenMintMostModified2Created1.json
@@ -457,7 +457,8 @@
}
],
"TransactionIndex": 0,
- "TransactionResult": "tesSUCCESS"
+ "TransactionResult": "tesSUCCESS",
+ "nftoken_id": "0008000085D33F9C5481D3515029C9904D16F0109414D3A0DCBA29BA00000020"
},
"hash": "C4E598099A8B13C5C8D2B8C86385A37B64C2F62BFA1FB87196401BB6ACB67A69",
"ledger_index": 5605289,
diff --git a/src/containers/shared/components/Transaction/NFTokenMint/test/mock_data/NFTokenMintWithIssuer.json b/src/containers/shared/components/Transaction/NFTokenMint/test/mock_data/NFTokenMintWithIssuer.json
index db644a078..d0dad91c5 100644
--- a/src/containers/shared/components/Transaction/NFTokenMint/test/mock_data/NFTokenMintWithIssuer.json
+++ b/src/containers/shared/components/Transaction/NFTokenMint/test/mock_data/NFTokenMintWithIssuer.json
@@ -72,7 +72,8 @@
}
],
"TransactionIndex": 0,
- "TransactionResult": "tesSUCCESS"
+ "TransactionResult": "tesSUCCESS",
+ "nftoken_id": "000861A8A99B4460C2A4CCC90634FD9C7F51940AD9450BE30000099B00000000"
},
"hash": "73629D3E84AC18C06350A9A4A0B2EF15AB52E5A62AECDAB4A0579DEF342FC61F",
"ledger_index": 4997948,
diff --git a/src/containers/shared/components/Transaction/OfferCreate/parser.ts b/src/containers/shared/components/Transaction/OfferCreate/parser.ts
index e895c2b5c..238bfd6a5 100644
--- a/src/containers/shared/components/Transaction/OfferCreate/parser.ts
+++ b/src/containers/shared/components/Transaction/OfferCreate/parser.ts
@@ -6,7 +6,7 @@ export function parser(tx: any) {
const base = tx.TakerGets.currency ? tx.TakerGets : { currency: 'XRP' }
const counter = tx.TakerPays.currency ? tx.TakerPays : { currency: 'XRP' }
const pays = formatAmount(tx.TakerPays)
- const price = pays.amount / gets.amount
+ const price = Number(pays.amount) / Number(gets.amount)
const invert =
CURRENCY_ORDER.indexOf(counter.currency) >
CURRENCY_ORDER.indexOf(base.currency)
diff --git a/src/containers/shared/components/Transaction/OracleSet/parser.ts b/src/containers/shared/components/Transaction/OracleSet/parser.ts
index bca503f5f..b27a3ba04 100644
--- a/src/containers/shared/components/Transaction/OracleSet/parser.ts
+++ b/src/containers/shared/components/Transaction/OracleSet/parser.ts
@@ -1,19 +1,6 @@
import { convertHexToString } from '../../../../../rippled/lib/utils'
import { OracleSet } from './types'
-
-// Convert scaled price (assetPrice) to original price using formula:
-// originalPrice = assetPrice / 10**scale
-// More details: https://github.com/XRPLF/XRPL-Standards/tree/master/XLS-47d-PriceOracles
-export function convertScaledPrice(assetPrice: string, scale: number) {
- const scaledPriceInBigInt = BigInt(`0x${assetPrice}`)
- const divisor = BigInt(10 ** scale)
- const integerPart = scaledPriceInBigInt / divisor
- const remainder = scaledPriceInBigInt % divisor
- const fractionalPart = (remainder * BigInt(10 ** scale)) / divisor
- return fractionalPart > 0
- ? `${integerPart}.${fractionalPart.toString().padStart(scale, '0')}`
- : `${integerPart}`
-}
+import { convertScaledPrice } from '../../../utils'
export function parser(tx: OracleSet) {
const priceDataSeries = tx.PriceDataSeries.map((priceDataObj) => ({
diff --git a/src/containers/shared/components/Transaction/OracleSet/test/ConvertScalePrice.test.ts b/src/containers/shared/components/Transaction/OracleSet/test/ConvertScalePrice.test.ts
index 984c63e2b..2437a40c9 100644
--- a/src/containers/shared/components/Transaction/OracleSet/test/ConvertScalePrice.test.ts
+++ b/src/containers/shared/components/Transaction/OracleSet/test/ConvertScalePrice.test.ts
@@ -1,4 +1,4 @@
-import { convertScaledPrice } from '../parser'
+import { convertScaledPrice } from '../../../../utils'
const numberToHex = (number) => number.toString(16)
describe('convertScaledPrice', () => {
diff --git a/src/containers/shared/components/Transaction/Payment/parser.ts b/src/containers/shared/components/Transaction/Payment/parser.ts
index 743c05367..57dc16157 100644
--- a/src/containers/shared/components/Transaction/Payment/parser.ts
+++ b/src/containers/shared/components/Transaction/Payment/parser.ts
@@ -1,4 +1,4 @@
-import type { Payment } from 'xrpl'
+// import type { Payment } from 'xrpl'
import { formatAmount } from '../../../../../rippled/lib/txSummary/formatAmount'
import { PaymentInstructions } from './types'
import { Amount, ExplorerAmount } from '../../../types'
@@ -10,7 +10,8 @@ const formatFailedPartialAmount = (d: Amount): ExplorerAmount => ({
export const isPartialPayment = (flags: any) => 0x00020000 & flags
-export const parser = (tx: Payment, meta: any): PaymentInstructions => {
+// TODO: use MPTAmount type from xrpl.js
+export const parser = (tx: any, meta: any): PaymentInstructions => {
const max = tx.SendMax ? formatAmount(tx.SendMax) : undefined
const partial = !!isPartialPayment(tx.Flags)
const failedPartial = partial && meta.TransactionResult !== 'tesSUCCESS'
diff --git a/src/containers/shared/components/Transaction/Payment/test/PaymentSimple.test.tsx b/src/containers/shared/components/Transaction/Payment/test/PaymentSimple.test.tsx
index 60c9ab4b3..5166c919f 100644
--- a/src/containers/shared/components/Transaction/Payment/test/PaymentSimple.test.tsx
+++ b/src/containers/shared/components/Transaction/Payment/test/PaymentSimple.test.tsx
@@ -1,3 +1,4 @@
+import { useQuery } from 'react-query'
import {
createSimpleWrapperFactory,
expectSimpleRowLabel,
@@ -10,6 +11,12 @@ import mockPaymentDestinationTag from './mock_data/PaymentWithDestinationTag.jso
import mockPaymentPartial from './mock_data/PaymentWithPartial.json'
import mockPaymentSendMax from './mock_data/PaymentWithSendMax.json'
import mockPaymentSourceTag from './mock_data/PaymentWithSourceTag.json'
+import mockPaymentMPT from './mock_data/PaymentMPT.json'
+
+jest.mock('react-query', () => ({
+ ...jest.requireActual('react-query'),
+ useQuery: jest.fn(),
+}))
const createWrapper = createSimpleWrapperFactory(Simple)
@@ -114,4 +121,32 @@ describe('Payment: Simple', () => {
wrapper.unmount()
})
+
+ it('renders direct MPT payment', () => {
+ const data = {
+ assetScale: 3,
+ }
+
+ // @ts-ignore
+ useQuery.mockImplementation(() => ({
+ data,
+ }))
+
+ const wrapper = createWrapper(mockPaymentMPT)
+
+ expectSimpleRowText(
+ wrapper,
+ 'amount',
+ `0.1 MPT (000003C31D321B7DDA58324DC38CDF18934FAFFFCDF69D5F)`,
+ )
+ expectSimpleRowLabel(wrapper, 'amount', `send`)
+
+ expectSimpleRowText(
+ wrapper,
+ 'destination',
+ `rw6UtpfBFaGht6SiC1HpDPNw6Yt25pKvnu`,
+ )
+
+ wrapper.unmount()
+ })
})
diff --git a/src/containers/shared/components/Transaction/Payment/test/mock_data/PaymentMPT.json b/src/containers/shared/components/Transaction/Payment/test/mock_data/PaymentMPT.json
new file mode 100644
index 000000000..d35339b88
--- /dev/null
+++ b/src/containers/shared/components/Transaction/Payment/test/mock_data/PaymentMPT.json
@@ -0,0 +1,91 @@
+{
+ "tx": {
+ "Account": "rsC4dnxCb66FQT4XmCUeuQ7dYeqNio4rWg",
+ "Amount": {
+ "mpt_issuance_id": "000003C31D321B7DDA58324DC38CDF18934FAFFFCDF69D5F",
+ "value": "100"
+ },
+ "DeliverMax": {
+ "mpt_issuance_id": "000003C31D321B7DDA58324DC38CDF18934FAFFFCDF69D5F",
+ "value": "100"
+ },
+ "Destination": "rw6UtpfBFaGht6SiC1HpDPNw6Yt25pKvnu",
+ "Fee": "10",
+ "Flags": 2147483648,
+ "Sequence": 964,
+ "SigningPubKey": "ED35B07F41420220332C35B9F4D1F7AF26E67EBD5AD6C9E106D0F774DA15924169",
+ "TransactionType": "Payment",
+ "TxnSignature": "2C5CB9740457222F928667DC1196060EF7E61B4E3A8824727AE63ACCFDE35ED5CBEE69E982423592DF0464C57C2C445B4271573DE7A3346630024287844F2502",
+ "ctid": "C00003C900000000",
+ "date": 1727802036000
+ },
+ "meta": {
+ "AffectedNodes": [
+ {
+ "ModifiedNode": {
+ "FinalFields": {
+ "AssetScale": 3,
+ "Flags": 34,
+ "Issuer": "rsC4dnxCb66FQT4XmCUeuQ7dYeqNio4rWg",
+ "MPTokenMetadata": "7B226E616D65223A2255532054726561737572792042696C6C20546F6B656E222C2273796D626F6C223A225553544254222C22646563696D616C73223A322C22746F74616C537570706C79223A313030303030302C22697373756572223A225553205472656173757279222C22697373756544617465223A22323032342D30332D3235222C226D6174757269747944617465223A22323032352D30332D3235222C226661636556616C7565223A2231303030222C22696E74657265737452617465223A22322E35222C22696E7465726573744672657175656E6379223A22517561727465726C79222C22636F6C6C61746572616C223A22555320476F7665726E6D656E74222C226A7572697364696374696F6E223A22556E6974656420537461746573222C22726567756C61746F7279436F6D706C69616E6365223A2253454320526567756C6174696F6E73222C22736563757269747954797065223A2254726561737572792042696C6C222C2265787465726E616C5F75726C223A2268747470733A2F2F6578616D706C652E636F6D2F742D62696C6C2D746F6B656E2D6D657461646174612E6A736F6E227D",
+ "MaximumAmount": "9223372036854775807",
+ "OutstandingAmount": "100",
+ "OwnerNode": "0",
+ "Sequence": 963
+ },
+ "LedgerEntryType": "MPTokenIssuance",
+ "LedgerIndex": "1CFF89335B544E0D6EEC35D74C0D26FF407DC02670F1C4E35A36CC875D34B1C3",
+ "PreviousFields": {
+ "OutstandingAmount": "0"
+ },
+ "PreviousTxnID": "6329586F264E4A6E2224318DCFC9B5F28048D84060B78A92CFFE65840DF8D970",
+ "PreviousTxnLgrSeq": 966
+ }
+ },
+ {
+ "ModifiedNode": {
+ "FinalFields": {
+ "Account": "rw6UtpfBFaGht6SiC1HpDPNw6Yt25pKvnu",
+ "Flags": 0,
+ "MPTAmount": "100",
+ "MPTokenIssuanceID": "000003C31D321B7DDA58324DC38CDF18934FAFFFCDF69D5F",
+ "OwnerNode": "0"
+ },
+ "LedgerEntryType": "MPToken",
+ "LedgerIndex": "3BAA73912496683A414494218D3CCA33D02F80D588F80C1257C691448E00E486",
+ "PreviousFields": {},
+ "PreviousTxnID": "60F99C8A23C4A366D19F43EA4BD43414AD4D4B7C21D0228FB7539D1C893E4A74",
+ "PreviousTxnLgrSeq": 967
+ }
+ },
+ {
+ "ModifiedNode": {
+ "FinalFields": {
+ "Account": "rsC4dnxCb66FQT4XmCUeuQ7dYeqNio4rWg",
+ "Balance": "99999980",
+ "Flags": 0,
+ "OwnerCount": 1,
+ "Sequence": 965
+ },
+ "LedgerEntryType": "AccountRoot",
+ "LedgerIndex": "77ECC02B8A7F16EB19A7BFBE8494E959497B4EC7734088583BD4F6B8C82878A5",
+ "PreviousFields": {
+ "Balance": "99999990",
+ "Sequence": 964
+ },
+ "PreviousTxnID": "6329586F264E4A6E2224318DCFC9B5F28048D84060B78A92CFFE65840DF8D970",
+ "PreviousTxnLgrSeq": 966
+ }
+ }
+ ],
+ "TransactionIndex": 0,
+ "TransactionResult": "tesSUCCESS",
+ "delivered_amount": {
+ "mpt_issuance_id": "000003C31D321B7DDA58324DC38CDF18934FAFFFCDF69D5F",
+ "value": "100"
+ }
+ },
+ "hash": "CD9EC015E68D3027919598E0466CFEF19950D0BC688A568DF8822A8BB0AFF98F",
+ "ledger_index": 11707,
+ "date": 1712072515000
+}
diff --git a/src/containers/shared/components/Transaction/SetHook/parser.ts b/src/containers/shared/components/Transaction/SetHook/parser.ts
index 8e0e10ca0..9761f7c78 100644
--- a/src/containers/shared/components/Transaction/SetHook/parser.ts
+++ b/src/containers/shared/components/Transaction/SetHook/parser.ts
@@ -8,11 +8,10 @@ export const parser = (tx: SetHook, meta: any): SetHookInstructions => {
(node.ModifiedNode?.LedgerEntryType === 'Hook' &&
!!node.ModifiedNode?.PreviousFields?.Hooks),
)
- const hashes = affectedNodes.flatMap(
- (node: any) =>
- (
- node.ModifiedNode?.FinalFields ?? node.CreatedNode?.NewFields
- )?.Hooks?.map((hook: any) => hook.Hook.HookHash),
+ const hashes = affectedNodes.flatMap((node: any) =>
+ (node.ModifiedNode?.FinalFields ?? node.CreatedNode?.NewFields)?.Hooks?.map(
+ (hook: any) => hook.Hook.HookHash,
+ ),
)
// TODO: there may be bugs here when a `HookHash` is already specified in a hook
// It's difficult to understand what situation that would be in, so this is left here for now
diff --git a/src/containers/shared/components/Transaction/UNLModify/test/UNLModifySimple.test.tsx b/src/containers/shared/components/Transaction/UNLModify/test/UNLModifySimple.test.tsx
index f3558c8ef..89cab8f69 100644
--- a/src/containers/shared/components/Transaction/UNLModify/test/UNLModifySimple.test.tsx
+++ b/src/containers/shared/components/Transaction/UNLModify/test/UNLModifySimple.test.tsx
@@ -44,7 +44,7 @@ describe('UNLModify: Simple', () => {
- {TestComponent}
- ,
+
+
+ {TestComponent}
+
+ ,
)
}
diff --git a/src/containers/shared/components/Transaction/types.ts b/src/containers/shared/components/Transaction/types.ts
index d1154be4a..722687b69 100644
--- a/src/containers/shared/components/Transaction/types.ts
+++ b/src/containers/shared/components/Transaction/types.ts
@@ -8,6 +8,7 @@ export enum TransactionCategory {
PAYMENT = 'PAYMENT',
NFT = 'NFT',
XCHAIN = 'XCHAIN',
+ MPT = 'MPT',
PSEUDO = 'PSEUDO',
UNKNOWN = 'UNKNOWN',
}
diff --git a/src/containers/shared/components/TransactionTable/styles.scss b/src/containers/shared/components/TransactionTable/styles.scss
index ad29c981f..fa235d2d4 100644
--- a/src/containers/shared/components/TransactionTable/styles.scss
+++ b/src/containers/shared/components/TransactionTable/styles.scss
@@ -1,5 +1,5 @@
@use 'sass:color';
-@import '../../../shared/css/variables';
+@use '../../../shared/css/variables' as *;
.transaction-table {
width: 100%;
diff --git a/src/containers/shared/components/TransactionTable/test/TransactionTable.test.js b/src/containers/shared/components/TransactionTable/test/TransactionTable.test.js
index 54517c301..734e4f0d5 100644
--- a/src/containers/shared/components/TransactionTable/test/TransactionTable.test.js
+++ b/src/containers/shared/components/TransactionTable/test/TransactionTable.test.js
@@ -1,9 +1,11 @@
import { mount } from 'enzyme'
import { I18nextProvider } from 'react-i18next'
import { BrowserRouter } from 'react-router-dom'
+import { QueryClientProvider } from 'react-query'
import { TransactionTable } from '../TransactionTable'
import i18n from '../../../../../i18n/testConfig'
import mockTx from './mockTransactions.json'
+import { queryClient } from '../../../QueryClient'
const loadMore = jest.fn()
@@ -16,17 +18,19 @@ describe('Transaction Table container', () => {
hasAdditionalResults = false,
) =>
mount(
-
-
-
-
- ,
+
+
+
+
+
+
+ ,
)
it('renders without crashing', () => {
diff --git a/src/containers/shared/components/test/Amount.test.tsx b/src/containers/shared/components/test/Amount.test.tsx
index acbe14315..749f62033 100644
--- a/src/containers/shared/components/test/Amount.test.tsx
+++ b/src/containers/shared/components/test/Amount.test.tsx
@@ -1,9 +1,15 @@
import { I18nextProvider } from 'react-i18next'
import { BrowserRouter } from 'react-router-dom'
import { mount } from 'enzyme'
+import { useQuery } from 'react-query'
import { Amount } from '../Amount'
import i18n from '../../../../i18n/testConfig'
+jest.mock('react-query', () => ({
+ ...jest.requireActual('react-query'),
+ useQuery: jest.fn(),
+}))
+
describe('Amount', () => {
const createWrapper = (component: JSX.Element) =>
mount(
@@ -117,4 +123,34 @@ describe('Amount', () => {
expect(wrapper.find('.amount-localized').text()).toEqual('+\uE9000.009')
wrapper.unmount()
})
+
+ it('handles MPT amount', async () => {
+ const data = {
+ issuer: 'rL2LzUhsBJMqsaVCXVvzedPjePbjVzBCC',
+ assetScale: 3,
+ maxAmt: '100000000',
+ outstandingAmt: '1043001',
+ sequence: 2447,
+ metadata:
+ '{"name":"US Treasury Bill Token","symbol":"USTBT","decimals":2,"totalSupply":1000000,"issuer":"US Treasury","issueDate":"2024-03-25","maturityDate":"2025-03-25","faceValue":"1000","interestRate":"2.5","interestFrequency":"Quarterly","collateral":"US Government","jurisdiction":"United States","regulatoryCompliance":"SEC Regulations","securityType":"Treasury Bill","external_url":"https://example.com/t-bill-token-metadata.json"}',
+ flags: [],
+ }
+
+ // @ts-ignore
+ useQuery.mockImplementation(() => ({
+ data,
+ }))
+
+ const value = {
+ amount: '1043001',
+ currency: '0000098F03B3BCE934EE8CAA1DF25A42032388361B9E5A65',
+ isMPT: true,
+ }
+ const wrapper = createWrapper(
+ ,
+ )
+
+ expect(wrapper.find('.amount-localized').text()).toEqual('1,043.001')
+ wrapper.unmount()
+ })
})
diff --git a/src/containers/shared/components/test/Currency.test.tsx b/src/containers/shared/components/test/Currency.test.tsx
index e3d218090..8d7de259c 100644
--- a/src/containers/shared/components/test/Currency.test.tsx
+++ b/src/containers/shared/components/test/Currency.test.tsx
@@ -63,4 +63,26 @@ describe('Currency', () => {
expect(wrapper.find('.currency').text()).toEqual('\uE900 XRP')
wrapper.unmount()
})
+
+ it('handles MPT ID ', () => {
+ const wrapper = mount(
+
+
+ ,
+ )
+ const mpt = wrapper.find('.currency').at(0)
+
+ expect(mpt).toHaveText(
+ 'MPT (00000BDE5B4F868ECE457207E2C1750065987730B8839E0D)',
+ )
+ expect(mpt.find('a')).toHaveProp(
+ 'href',
+ '/mpt/00000BDE5B4F868ECE457207E2C1750065987730B8839E0D',
+ )
+ wrapper.unmount()
+ })
})
diff --git a/src/containers/shared/components/test/DomainLink.test.tsx b/src/containers/shared/components/test/DomainLink.test.tsx
index 71e589e8a..57e83ad77 100644
--- a/src/containers/shared/components/test/DomainLink.test.tsx
+++ b/src/containers/shared/components/test/DomainLink.test.tsx
@@ -61,4 +61,12 @@ describe('DomainLink', () => {
)
wrapper.unmount()
})
+
+ it('handles domain link with protocol removal', () => {
+ const url = 'https://example.com/'
+ const wrapper = mount()
+ expect(wrapper.find('a').props().className).toEqual('domain')
+ expect(wrapper.find('a').text()).toEqual('example.com')
+ expect(wrapper.find('a').props().href).toEqual('https://example.com/')
+ })
})
diff --git a/src/containers/shared/css/box.scss b/src/containers/shared/css/box.scss
index bbd7ea06b..839b8e264 100644
--- a/src/containers/shared/css/box.scss
+++ b/src/containers/shared/css/box.scss
@@ -1,4 +1,4 @@
-@import './variables';
+@use 'variables' as *;
.box {
border-radius: 4px;
diff --git a/src/containers/shared/css/form.scss b/src/containers/shared/css/form.scss
index ee462e9a2..e4fefc169 100644
--- a/src/containers/shared/css/form.scss
+++ b/src/containers/shared/css/form.scss
@@ -1,4 +1,4 @@
-@import './variables';
+@use 'variables' as *;
label {
color: $black-40;
diff --git a/src/containers/shared/css/global.scss b/src/containers/shared/css/global.scss
index 327c74065..e10fd11d1 100644
--- a/src/containers/shared/css/global.scss
+++ b/src/containers/shared/css/global.scss
@@ -1,7 +1,7 @@
// ONLY GLOBAL CSS, KEEP IT TO MINIMAL
// CSS SHOULD BE SCOPPED TO COMPONENT
-@import './variables';
-@import './form';
+@use 'variables' as *;
+@use 'form';
/**
* `current_symbols` is used for currency codes missing from other fonts, currently `u+e900` (XRP) and `u+e901` (BTC).
@@ -121,6 +121,7 @@ div.react-stockchart div {
@include transaction-category(XCHAIN, $yellow, $yellow-30, $yellow-90);
@include transaction-category(PSEUDO, $white, $white, $black-80);
@include transaction-category(UNKNOWN, $black-50, $black-30, $black-90);
+@include transaction-category(MPT, $blue, $blue-30, $blue-90);
.tx-result {
&.success {
diff --git a/src/containers/shared/css/simpleTab.scss b/src/containers/shared/css/simpleTab.scss
index 574f4adda..087d3005c 100644
--- a/src/containers/shared/css/simpleTab.scss
+++ b/src/containers/shared/css/simpleTab.scss
@@ -1,4 +1,4 @@
-@import './variables';
+@use 'variables' as *;
$index-width: 324px;
diff --git a/src/containers/shared/css/table.scss b/src/containers/shared/css/table.scss
index 53dc1e2df..4f53bac7c 100644
--- a/src/containers/shared/css/table.scss
+++ b/src/containers/shared/css/table.scss
@@ -1,4 +1,4 @@
-@import './variables';
+@use 'variables' as *;
table.basic {
overflow: hidden;
diff --git a/src/containers/shared/css/tabs.scss b/src/containers/shared/css/tabs.scss
index 379fd220c..3ffa955ec 100644
--- a/src/containers/shared/css/tabs.scss
+++ b/src/containers/shared/css/tabs.scss
@@ -1,4 +1,4 @@
-@import './variables';
+@use 'variables' as *;
.tabs {
border-top: 1px solid $black-70;
diff --git a/src/containers/shared/css/tooltip.scss b/src/containers/shared/css/tooltip.scss
index a1c6d5e4b..eaada0ce5 100644
--- a/src/containers/shared/css/tooltip.scss
+++ b/src/containers/shared/css/tooltip.scss
@@ -1,4 +1,4 @@
-@import './variables';
+@use 'variables' as *;
.tooltip {
position: absolute;
diff --git a/src/containers/shared/css/txlabel.scss b/src/containers/shared/css/txlabel.scss
index 093f8300c..dd2b520a6 100644
--- a/src/containers/shared/css/txlabel.scss
+++ b/src/containers/shared/css/txlabel.scss
@@ -1,4 +1,4 @@
-@import './variables';
+@use 'variables' as *;
.tx-label {
display: inline-flex;
diff --git a/src/containers/shared/css/txstatus.scss b/src/containers/shared/css/txstatus.scss
index 0a447f246..66933e784 100644
--- a/src/containers/shared/css/txstatus.scss
+++ b/src/containers/shared/css/txstatus.scss
@@ -1,4 +1,4 @@
-@import './variables';
+@use 'variables' as *;
.tx-status {
display: inline-flex;
diff --git a/src/containers/shared/css/variables.scss b/src/containers/shared/css/variables.scss
index a1987f372..8acd9b3a0 100644
--- a/src/containers/shared/css/variables.scss
+++ b/src/containers/shared/css/variables.scss
@@ -120,6 +120,7 @@ $custom: $yellow-50;
// Feature Sets
$amm: $blue;
$nft: $blue-purple;
+$mpt: $blue;
// Currency colors
$CURRENCY_DEFAULT: #aedbf7;
@@ -154,7 +155,7 @@ $XRP_XRP: #0a93eb;
$XRP_CAD: #ca3103;
$XRP_OTHERS: #f52a79;
-// Font wights
+// Font weights
@mixin regular {
font-weight: 400;
}
diff --git a/src/containers/shared/test/SocketContext.test.ts b/src/containers/shared/test/SocketContext.test.ts
index 2c268ed56..2a67fae26 100644
--- a/src/containers/shared/test/SocketContext.test.ts
+++ b/src/containers/shared/test/SocketContext.test.ts
@@ -28,7 +28,7 @@ describe('getSocket', () => {
const client = getSocket()
expect(XrplClient).toHaveBeenNthCalledWith(
1,
- ['wss://somewhere.com:51233', 'wss://somewhere.com:443'],
+ ['wss://somewhere.com:51233'],
{
tryAllNodes: true,
},
@@ -47,12 +47,7 @@ describe('getSocket', () => {
const client = getSocket()
expect(XrplClient).toHaveBeenNthCalledWith(
1,
- [
- 'wss://somewhere.com:51233',
- 'wss://somewhere.com:443',
- 'wss://elsewhere.com:51233',
- 'wss://elsewhere.com:443',
- ],
+ ['wss://somewhere.com:51233', 'wss://elsewhere.com:51233'],
{
tryAllNodes: true,
},
@@ -71,7 +66,7 @@ describe('getSocket', () => {
const client = getSocket()
expect(XrplClient).toHaveBeenNthCalledWith(
1,
- ['ws://somewhere.com:51233', 'ws://somewhere.com:443'],
+ ['ws://somewhere.com:51233'],
{
tryAllNodes: true,
},
@@ -120,13 +115,9 @@ describe('getSocket', () => {
it('should use ws when supplied entry is for a localhost', () => {
const client = getSocket('localhost')
- expect(XrplClient).toHaveBeenNthCalledWith(
- 1,
- ['ws://localhost:51233', 'ws://localhost:443'],
- {
- tryAllNodes: true,
- },
- )
+ expect(XrplClient).toHaveBeenNthCalledWith(1, ['ws://localhost:51233'], {
+ tryAllNodes: true,
+ })
expect((client as any).p2pSocket).not.toBeDefined()
})
diff --git a/src/containers/shared/test/amendmentUtils.test.ts b/src/containers/shared/test/amendmentUtils.test.ts
index f7f21e645..e4ef9d4e6 100644
--- a/src/containers/shared/test/amendmentUtils.test.ts
+++ b/src/containers/shared/test/amendmentUtils.test.ts
@@ -1,170 +1,4 @@
-import { getRippledVersion, nameOfAmendmentID } from '../amendmentUtils'
-
-const nameTable = [
- [
- '32A122F1352A4C7B3A6D790362CC34749C5E57FCE896377BFDC6CCD14F6CD627',
- 'NonFungibleTokensV1_1',
- ],
- [
- '4C97EBA926031A7CF7D7B36FDE3ED66DDA5421192D63DE53FFB46E43B9DC8373',
- 'MultiSign',
- ],
- [
- '6781F8368C4771B83E8B821D88F580202BCB4228075297B19E4FDC5233F1EFDC',
- 'TrustSetAuth',
- ],
- [
- '42426C4D4F1009EE67080A9B7965B44656D7714D104A72F9B4369F97ABF044EE',
- 'FeeEscalation',
- ],
- [
- '08DE7D96082187F6E6578530258C77FAABABE4C20474BDB82F04B021F1A68647',
- 'PayChan',
- ],
- ['740352F2412A9909880C23A559FCECEDA3BE2126FED62FC7660D628A06927F11', 'Flow'],
- [
- '1562511F573A19AE9BD103B5D6B9E01B3B46805AEC5D3C4805C902B514399146',
- 'CryptoConditions',
- ],
- [
- '532651B4FD58DF8922A49BA101AB3E996E5BFBF95A913B3E392504863E63B164',
- 'TickSize',
- ],
- [
- 'E2E6F2866106419B88C50045ACE96368558C345566AC8F2BDF5A5B5587F0E6FA',
- 'fix1368',
- ],
- [
- '07D43DCE529B15A10827E5E04943B496762F9A88E3268269D69C44BE49E21104',
- 'Escrow',
- ],
- [
- '86E83A7D2ECE3AD5FA87AB2195AE015C950469ABF0B72EAACED318F74886AE90',
- 'CryptoConditionsSuite',
- ],
- [
- '42EEA5E28A97824821D4EF97081FE36A54E9593C6E4F20CBAE098C69D2E072DC',
- 'fix1373',
- ],
- [
- 'DC9CA96AEA1DCF83E527D1AFC916EFAF5D27388ECA4060A88817C1238CAEE0BF',
- 'EnforceInvariants',
- ],
- [
- '3012E8230864E95A58C60FD61430D7E1B4D3353195F2981DC12B0C7C0950FFAC',
- 'FlowCross',
- ],
- [
- 'CC5ABAE4F3EC92E94A59B1908C2BE82D2228B6485C00AFF8F22DF930D89C194E',
- 'SortedDirectories',
- ],
- [
- 'B4D44CC3111ADD964E846FC57760C8B50FFCD5A82C86A72756F6B058DDDF96AD',
- 'fix1201',
- ],
- [
- '6C92211186613F9647A89DFFBAB8F94C99D4C7E956D495270789128569177DA1',
- 'fix1512',
- ],
- [
- '67A34F2CF55BFC0F93AACD5B281413176FEE195269FA6D95219A2DF738671172',
- 'fix1513',
- ],
- [
- 'B9E739B8296B4A1BB29BE990B17D66E21B62A300A909F25AC55C22D6C72E1F9D',
- 'fix1523',
- ],
- [
- '1D3463A5891F9E589C5AE839FFAC4A917CE96197098A1EF22304E1BC5B98A454',
- 'fix1528',
- ],
- [
- 'F64E1EABBE79D55B3BB82020516CEC2C582A98A6BFE20FBE9BB6A0D233418064',
- 'DepositAuth',
- ],
- [
- '157D2D480E006395B76F948E3E07A45A05FE10230D88A7993C71F97AE4B1F2D1',
- 'Checks',
- ],
- [
- '7117E2EC2DBF119CA55181D69819F1999ECEE1A0225A7FD2B9ED47940968479C',
- 'fix1571',
- ],
- [
- 'CA7C02118BA27599528543DFE77BA6838D1B0F43B447D4D7F53523CE6A0E9AC2',
- 'fix1543',
- ],
- [
- '58BE9B5968C4DA7C59BA900961828B113E5490699B21877DEF9A31E9D0FE5D5F',
- 'fix1623',
- ],
- [
- '3CBC5C4E630A1B82380295CDA84B32B49DD066602E74E39B85EF64137FA65194',
- 'DepositPreauth',
- ],
- [
- '5D08145F0A4983F23AFFFF514E83FAD355C5ABFBB6CAB76FB5BC8519FF5F33BE',
- 'fix1515',
- ],
- [
- 'FBD513F1B893AC765B78F250E6FFA6A11B573209D1842ADC787C850696741288',
- 'fix1578',
- ],
- [
- '586480873651E106F1D6339B0C4A8945BA705A777F3F4524626FF1FC07EFE41D',
- 'MultiSignReserve',
- ],
- [
- '2CD5286D8D687E98B41102BDD797198E81EA41DF7BD104E6561FEB104EFF2561',
- 'fixTakerDryOfferRemoval',
- ],
- [
- 'C4483A1896170C66C098DEA5B0E024309C60DC960DE5F01CD7AF986AA3D9AD37',
- 'fixMasterKeyAsRegularKey',
- ],
- [
- '8F81B066ED20DAECA20DF57187767685EEF3980B228E0667A650BAF24426D3B4',
- 'fixCheckThreading',
- ],
- [
- '621A0B264970359869E3C0363A899909AAB7A887C8B73519E4ECF952D33258A8',
- 'fixPayChanRecipientOwnerDir',
- ],
- [
- '30CD365592B8EE40489BA01AE2F7555CAC9C983145871DC82A42A31CF5BAE7D9',
- 'DeletableAccounts',
- ],
- [
- '89308AF3B8B10B7192C4E613E1D2E4D9BA64B2EE2D5232402AE82A6A7220D953',
- 'fixQualityUpperBound',
- ],
- [
- '00C1FC4A53E60AB02C864641002B3172F38677E29C26C5406685179B37E1EDAC',
- 'RequireFullyCanonicalSig',
- ],
- [
- '25BA44241B3BD880770BFA4DA21C7180576831855368CBEC6A3154FDE4A7676E',
- 'fix1781',
- ],
- [
- '1F4AFA8FA1BC8827AD4C0F682C03A8B671DCDF6B5C4DE36D44243A684103EF88',
- 'HardenedValidations',
- ],
- [
- '4F46DF03559967AC60F2EB272FEFE3928A7594A45FF774B87A7E540DB0F8F068',
- 'fixAmendmentMajorityCalc',
- ],
-]
-
-describe('nameOfAmendmentID: ', () => {
- it.each(nameTable)(
- `should resolve amendment id "%s" to "%s"`,
- async (id, name) => {
- const retrievedName = await nameOfAmendmentID(id)
- return expect(retrievedName).toEqual(name)
- },
- )
-})
+import { getRippledVersion } from '../amendmentUtils'
const versionTable = [
['NonFungibleTokensV1_1', 'v1.9.2'],
diff --git a/src/containers/shared/transactionUtils.ts b/src/containers/shared/transactionUtils.ts
index e39c28ef1..0ccd26bd9 100644
--- a/src/containers/shared/transactionUtils.ts
+++ b/src/containers/shared/transactionUtils.ts
@@ -41,6 +41,21 @@ export const TX_FLAGS: Record> = {
0x00200000: 'tfOneAssetLPToken',
0x00400000: 'tfLimitLPToken',
},
+ MPTokenAuthorize: {
+ 0x00000001: 'tfMPTUnauthorize',
+ },
+ MPTokenIssuanceCreate: {
+ 0x00000002: 'tfMPTCanLock',
+ 0x00000004: 'tfMPTRequireAuth',
+ 0x00000008: 'tfMPTCanEscrow',
+ 0x00000010: 'tfMPTCanTrade',
+ 0x00000020: 'tfMPTCanTransfer',
+ 0x00000040: 'tfMPTCanClawback',
+ },
+ MPTokenIssuanceSet: {
+ 0x00000001: 'tfMPTLock',
+ 0x00000002: 'tfMPTUnlock',
+ },
NFTokenMint: {
0x00000001: 'tfBurnable',
0x00000002: 'tfOnlyXRP',
diff --git a/src/containers/shared/types.ts b/src/containers/shared/types.ts
index d219bc63e..3ae210e27 100644
--- a/src/containers/shared/types.ts
+++ b/src/containers/shared/types.ts
@@ -17,12 +17,18 @@ export interface IssuedCurrencyAmount extends IssuedCurrency {
value: string
}
-export type Amount = IssuedCurrencyAmount | string
+export interface MPTAmount {
+ mpt_issuance_id: string
+ value: string
+}
+
+export type Amount = IssuedCurrencyAmount | MPTAmount | string
export type ExplorerAmount = {
issuer?: string
currency: string
- amount: number
+ amount: number | string
+ isMPT?: boolean
}
export interface Tx {
diff --git a/src/containers/shared/utils.js b/src/containers/shared/utils.js
index bdeae56e2..9580b4754 100644
--- a/src/containers/shared/utils.js
+++ b/src/containers/shared/utils.js
@@ -21,9 +21,11 @@ export const FETCH_INTERVAL_MILLIS = 5000
export const FETCH_INTERVAL_VHS_MILLIS = 60 * 1000 // 1 minute
export const FETCH_INTERVAL_NODES_MILLIS = 60000
export const FETCH_INTERVAL_ERROR_MILLIS = 300
+export const FETCH_INTERVAL_XRP_USD_ORACLE_MILLIS = 60 * 1000
export const DECIMAL_REGEX = /^\d+$/
-export const HASH_REGEX = /[0-9A-Fa-f]{64}/i
+export const HASH256_REGEX = /[0-9A-Fa-f]{64}/i
+export const HASH192_REGEX = /[0-9A-Fa-f]{48}/i
export const CURRENCY_REGEX =
/^[a-zA-Z0-9]{3,}[.:+-]r[rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz]{27,35}$/
export const FULL_CURRENCY_REGEX =
@@ -128,15 +130,29 @@ export const isEarlierVersion = (source, target) => {
return false
}
+export const isValidJsonString = (str) => {
+ try {
+ JSON.parse(str)
+ return true
+ } catch (e) {
+ return false
+ }
+}
+
// Document: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/NumberFormat
-export const localizeNumber = (num, lang = 'en-US', options = {}) => {
+export const localizeNumber = (
+ num,
+ lang = 'en-US',
+ options = {},
+ isMPT = false,
+) => {
const number = Number.parseFloat(num)
const config = { ...NUMBER_DEFAULT_OPTIONS, ...options }
if (Number.isNaN(number)) {
return null
}
- if (config.style === 'currency') {
+ if (config.style === 'currency' && !isMPT) {
try {
const neg = number < 0 ? '-' : ''
const d = new Intl.NumberFormat(lang, config).format(number)
@@ -255,6 +271,8 @@ export const formatLargeNumber = (d = 0, digits = 4) => {
}
}
+export const convertHexToBigInt = (s) => BigInt(`0x${s}`)
+
export const durationToHuman = (s, decimal = 2) => {
const d = {}
const seconds = Math.abs(s)
@@ -301,7 +319,7 @@ export const formatTradingFee = (tradingFee) =>
})
: undefined
-export const computeBalanceChange = (node) => {
+export const computeRippleStateBalanceChange = (node) => {
const fields = node.FinalFields || node.NewFields
const prev = node.PreviousFields
const { currency } = fields.Balance
@@ -333,7 +351,50 @@ export const computeBalanceChange = (node) => {
}
}
+export const computeMPTokenBalanceChange = (node) => {
+ const final = node.FinalFields || node.NewFields
+ const prev = node.PreviousFields
+ const prevAmount = prev && prev.MPTAmount ? prev.MPTAmount : '0'
+ const finalAmount = final.MPTAmount ?? '0'
+
+ return {
+ previousBalance: BigInt(prevAmount),
+ finalBalance: BigInt(finalAmount),
+ account: final.Account,
+ change: BigInt(finalAmount) - BigInt(prevAmount),
+ }
+}
+
+export const computeMPTIssuanceBalanceChange = (node) => {
+ const final = node.FinalFields || node.NewFields
+ const prev = node.PreviousFields
+ const prevAmount =
+ prev && prev.OutstandingAmount ? prev.OutstandingAmount : '0'
+ const finalAmount = final.OutstandingAmount ?? '0'
+
+ return {
+ previousBalance: BigInt(prevAmount),
+ finalBalance: BigInt(finalAmount),
+ account: final.Issuer,
+ change: BigInt(finalAmount) - BigInt(prevAmount),
+ }
+}
+
export const renderXRP = (d, language) => {
const options = { ...CURRENCY_OPTIONS, currency: 'XRP' }
return localizeNumber(d, language, options)
}
+
+// Convert scaled price (assetPrice) in hex string to original price using formula:
+// originalPrice = assetPrice / 10**scale
+// More details: https://github.com/XRPLF/XRPL-Standards/tree/master/XLS-47d-PriceOracles
+export function convertScaledPrice(assetPrice, scale) {
+ const scaledPriceInBigInt = BigInt(`0x${assetPrice}`)
+ const divisor = BigInt(10 ** scale)
+ const integerPart = scaledPriceInBigInt / divisor
+ const remainder = scaledPriceInBigInt % divisor
+ const fractionalPart = (remainder * BigInt(10 ** scale)) / divisor
+ return fractionalPart > 0
+ ? `${integerPart}.${fractionalPart.toString().padStart(scale, '0')}`
+ : `${integerPart}`
+}
diff --git a/src/index.html b/src/index.html
index dbf1e83b5..8109d44f4 100644
--- a/src/index.html
+++ b/src/index.html
@@ -8,14 +8,16 @@
+
-
-
+
+
+
diff --git a/src/rippled/lib/rippled.js b/src/rippled/lib/rippled.js
index 598a57e63..89f47ad6e 100644
--- a/src/rippled/lib/rippled.js
+++ b/src/rippled/lib/rippled.js
@@ -1,4 +1,4 @@
-import { CTID_REGEX, HASH_REGEX } from '../../containers/shared/utils'
+import { CTID_REGEX, HASH256_REGEX } from '../../containers/shared/utils'
import { formatAmount } from './txSummary/formatAmount'
import { Error, XRP_BASE, convertRippleDate } from './utils'
@@ -144,7 +144,7 @@ const getTransaction = (rippledSocket, txId) => {
const params = {
command: 'tx',
}
- if (HASH_REGEX.test(txId)) {
+ if (HASH256_REGEX.test(txId)) {
params.transaction = txId
} else if (CTID_REGEX.test(txId)) {
params.ctid = txId
@@ -557,6 +557,90 @@ const getAMMInfo = (rippledSocket, asset, asset2) => {
})
}
+// get feature
+const getFeature = (rippledSocket, amendmentId) => {
+ const request = {
+ command: 'feature',
+ feature: amendmentId,
+ }
+ return query(rippledSocket, request).then((resp) => {
+ if (resp == null || resp.error_message) {
+ return null
+ }
+
+ return resp
+ })
+}
+
+const getMPTIssuance = (rippledSocket, tokenId) =>
+ queryP2P(rippledSocket, {
+ command: 'ledger_entry',
+ mpt_issuance: tokenId,
+ ledger_index: 'validated',
+ include_deleted: true,
+ }).then((resp) => {
+ if (
+ resp.error === 'entryNotFound' ||
+ resp.error === 'lgrNotFound' ||
+ resp.error === 'objectNotFound'
+ ) {
+ throw new Error('MPT not found', 404)
+ }
+
+ if (resp.error_message) {
+ throw new Error(resp.error_message, 500)
+ }
+ return resp
+ })
+
+const getAccountMPTs = (
+ rippledSocket,
+ account,
+ marker = '',
+ ledgerIndex = 'validated',
+) =>
+ query(rippledSocket, {
+ command: 'account_objects',
+ account,
+ ledger_index: ledgerIndex,
+ type: 'mptoken',
+ marker: marker || undefined,
+ limit: 400,
+ }).then((resp) => {
+ if (resp.error === 'actNotFound') {
+ throw new Error('account not found', 404)
+ }
+ if (resp.error === 'invalidParams') {
+ return undefined
+ }
+
+ if (resp.error_message) {
+ throw new Error(resp.error_message, 500)
+ }
+
+ return resp
+ })
+
+const getAccountLines = (rippledSocket, account, limit) =>
+ query(rippledSocket, {
+ command: 'account_lines',
+ account,
+ limit,
+ }).then((resp) => {
+ if (resp.error === 'actNotFound') {
+ throw new Error('account not found', 404)
+ }
+ if (resp.error === 'invalidParams') {
+ return undefined
+ }
+
+ if (resp.error_message) {
+ throw new Error(resp.error_message, 500)
+ }
+
+ return resp
+ })
+
export {
getLedger,
getLedgerEntry,
@@ -576,4 +660,8 @@ export {
getSellNFToffers,
getNFTTransactions,
getAMMInfo,
+ getFeature,
+ getMPTIssuance,
+ getAccountMPTs,
+ getAccountLines,
}
diff --git a/src/rippled/lib/txSummary/formatAmount.ts b/src/rippled/lib/txSummary/formatAmount.ts
index 7eaf91239..c8cccd4d5 100644
--- a/src/rippled/lib/txSummary/formatAmount.ts
+++ b/src/rippled/lib/txSummary/formatAmount.ts
@@ -1,18 +1,34 @@
-import { Amount, ExplorerAmount } from '../../../containers/shared/types'
+import {
+ Amount,
+ ExplorerAmount,
+ MPTAmount,
+} from '../../../containers/shared/types'
import { XRP_BASE } from '../utils'
+export const isMPTAmount = (amount: Amount): amount is MPTAmount =>
+ (amount as MPTAmount).mpt_issuance_id !== undefined &&
+ (amount as MPTAmount).value !== undefined
+
export const formatAmount = (d: Amount | number): ExplorerAmount => {
if (d == null) {
return d
}
- return typeof d !== 'string' && typeof d !== 'number'
+
+ if (typeof d === 'string' || typeof d === 'number')
+ return {
+ currency: 'XRP',
+ amount: Number(d) / XRP_BASE,
+ }
+
+ return isMPTAmount(d)
? {
+ currency: d.mpt_issuance_id,
+ amount: d.value,
+ isMPT: true,
+ }
+ : {
currency: d.currency,
issuer: d.issuer,
amount: Number(d.value),
}
- : {
- currency: 'XRP',
- amount: Number(d) / XRP_BASE,
- }
}
diff --git a/src/rippled/lib/utils.js b/src/rippled/lib/utils.js
index 2c95c4db4..a1b19680e 100644
--- a/src/rippled/lib/utils.js
+++ b/src/rippled/lib/utils.js
@@ -1,4 +1,5 @@
-import { hexToString } from '@xrplf/isomorphic/utils'
+import { hexToString, hexToBytes } from '@xrplf/isomorphic/utils'
+import { encodeAccountID } from 'ripple-address-codec'
import { convertRippleDate } from './convertRippleDate'
import { formatSignerList } from './formatSignerList'
import { decodeHex } from '../../containers/shared/transactionUtils'
@@ -27,6 +28,19 @@ const NFT_FLAGS = {
0x00000002: 'lsfOnlyXRP',
0x00000008: 'lsfTransferable',
}
+const MPT_ISSUANCE_FLAGS = {
+ 0x00000001: 'lsfMPTLocked',
+ 0x00000002: 'lsfMPTCanLock',
+ 0x00000004: 'lsfMPTRequireAuth',
+ 0x00000008: 'lsfMPTCanEscrow',
+ 0x00000010: 'lsfMPTCanTrade',
+ 0x00000020: 'lsfMPTCanTransfer',
+ 0x00000040: 'lsfMPTCanClawback',
+}
+const MPTOKEN_FLAGS = {
+ 0x00000001: 'lsfMPTLocked',
+ 0x00000002: 'lsfMPTAuthorized',
+}
const hex32 = (d) => {
const int = d & 0xffffffff
const hex = int.toString(16).toUpperCase()
@@ -128,6 +142,33 @@ const formatNFTInfo = (info) => ({
warnings: info.warnings,
})
+const formatMPTIssuanceInfo = (info) => ({
+ issuer: info.node.Issuer,
+ assetScale: info.node.AssetScale,
+ maxAmt: info.node.MaximumAmount
+ ? BigInt(info.node.MaximumAmount).toString(10)
+ : undefined, // default is undefined because the default maxAmt is the largest 63-bit int
+ outstandingAmt: info.node.OutstandingAmount
+ ? BigInt(info.node.OutstandingAmount).toString(10)
+ : '0',
+ transferFee: info.node.TransferFee,
+ sequence: info.node.Sequence,
+ metadata: info.node.MPTokenMetadata
+ ? decodeHex(info.node.MPTokenMetadata)
+ : info.node.MPTokenMetadata,
+ flags: buildFlags(info.node.Flags, MPT_ISSUANCE_FLAGS),
+})
+
+const formatMPTokenInfo = (info) => ({
+ account: info.Account,
+ flags: buildFlags(info.Flags, MPTOKEN_FLAGS),
+ mptIssuanceID: info.MPTokenIssuanceID,
+ mptIssuer: encodeAccountID(
+ hexToBytes(info.MPTokenIssuanceID.substring(8, 48)),
+ ),
+ mptAmount: info.MPTAmount ? info.MPTAmount.toString(10) : '0',
+})
+
export {
XRP_BASE,
RippledError as Error,
@@ -137,4 +178,6 @@ export {
formatAccountInfo,
convertHexToString,
formatNFTInfo,
+ formatMPTIssuanceInfo,
+ formatMPTokenInfo,
}
diff --git a/src/rippled/token.js b/src/rippled/token.ts
similarity index 74%
rename from src/rippled/token.js
rename to src/rippled/token.ts
index aedc188c2..9f6d8736d 100644
--- a/src/rippled/token.js
+++ b/src/rippled/token.ts
@@ -4,7 +4,26 @@ import { getBalances, getAccountInfo, getServerInfo } from './lib/rippled'
const log = logger({ name: 'iou' })
-const getToken = async (currencyCode, issuer, rippledSocket) => {
+export interface TokenData {
+ name: string
+ balance: string
+ reserve: number
+ sequence: number
+ gravatar: string
+ rate?: number
+ obligations?: string
+ domain?: string
+ emailHash?: string
+ previousLedger: number
+ previousTxn: string
+ flags: string[]
+}
+
+const getToken = async (
+ currencyCode,
+ issuer,
+ rippledSocket,
+): Promise => {
try {
log.info('fetching account info from rippled')
const accountInfo = await getAccountInfo(rippledSocket, issuer)
@@ -47,7 +66,9 @@ const getToken = async (currencyCode, issuer, rippledSocket) => {
previousLedger,
}
} catch (error) {
- log.error(error.toString())
+ if (error) {
+ log.error(error.toString())
+ }
throw error
}
}
diff --git a/src/rippled/transactions.js b/src/rippled/transactions.js
index ed2e6835b..0dc60f1a6 100644
--- a/src/rippled/transactions.js
+++ b/src/rippled/transactions.js
@@ -8,11 +8,14 @@ const log = logger({ name: 'transactions' })
const getTransaction = (transactionId, rippledSocket) => {
log.info(`get tx: ${transactionId}`)
return getRippledTransaction(rippledSocket, transactionId)
- .then((response) => formatTransaction(response))
- .then((data) => ({
- summary: summarizeTransaction(data, true).details,
- raw: data,
- }))
+ .then((data) => {
+ const formattedTransaction = formatTransaction(data)
+ return {
+ summary: summarizeTransaction(formattedTransaction, true).details,
+ processed: formattedTransaction,
+ raw: data,
+ }
+ })
.catch((error) => {
log.error(error.toString())
throw error
diff --git a/src/rootReducer.js b/src/rootReducer.js
index 69ab09b9a..0ad9a2f70 100644
--- a/src/rootReducer.js
+++ b/src/rootReducer.js
@@ -2,18 +2,13 @@ import { combineReducers } from 'redux'
import accountHeaderReducer, {
initialState as accountHeaderState,
} from './containers/Accounts/AccountHeader/reducer'
-import tokenHeaderReducer, {
- initialState as tokenHeaderState,
-} from './containers/Token/TokenHeader/reducer'
export const initialState = {
accountHeader: accountHeaderState,
- tokenHeader: tokenHeaderState,
}
const rootReducer = combineReducers({
accountHeader: accountHeaderReducer,
- tokenHeader: tokenHeaderReducer,
})
export default rootReducer
diff --git a/vite.config.js b/vite.config.js
index 2e64dc529..fd3b7cf11 100644
--- a/vite.config.js
+++ b/vite.config.js
@@ -87,5 +87,11 @@ export default defineConfig({
autoprefixer({}), // add options if needed
],
},
+ preprocessorOptions: {
+ scss: {
+ api: 'modern-compiler',
+ silenceDeprecations: ['mixed-decls'],
+ },
+ },
},
})