diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml new file mode 100644 index 000000000..be6e38ee4 --- /dev/null +++ b/.github/workflows/copilot-setup-steps.yml @@ -0,0 +1,46 @@ +name: Copilot Setup Steps + +env: + NODE_VERSION: 22.x + VITE_VALIDATOR: vl.ripple.com + VITE_RIPPLED_WS_PORT: 51233 + VITE_RIPPLED_HOST: fake.rippled.example.com + VITE_MAINNET_LINK: mainnet.example.com + VITE_TESTNET_LINK: testnet.example.com + VITE_CUSTOMNETWORK_LINK: custom.example.com + VITE_DATA_URL: https://data.xrpl.org/v1/network + +on: + workflow_dispatch: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + copilot-setup: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Use Node.js ${{ env.NODE_VERSION }} + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + + - name: Cache node modules + id: cache-nodemodules + uses: actions/cache@v4 + env: + cache-name: cache-node-modules + with: + # caching node_modules + path: node_modules + key: ${{ env.NODE_VERSION }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ env.NODE_VERSION }}-build-${{ env.cache-name }}- + ${{ env.NODE_VERSION }}-build- + + - name: Install Dependencies + if: steps.cache-nodemodules.outputs.cache-hit != 'true' + run: npm ci diff --git a/public/locales/en-US/translations.json b/public/locales/en-US/translations.json index 5d26eb94f..1b1acce98 100644 --- a/public/locales/en-US/translations.json +++ b/public/locales/en-US/translations.json @@ -332,6 +332,8 @@ "set_nftoken_minter_description": "It sets <0>{{account}} as the authorized minter for this account", "deposit_auth": "It authorizes <1><0>{{account}} to send payments to this account", "deposit_unauth": "It removes the authorization for <1><0>{{account}} to send payments to this account", + "deposit_auth_credentials": "It authorizes the following credentials:", + "deposit_unauth_credentials": "It removes the authorization for the following credentials:", "invalid_xrpl_address": "Invalid XRPL address", "loading": "Loading", "get_ledger_failed": "Unable to load ledger", @@ -584,6 +586,7 @@ "expiration": "Expiration", "domain_id": "Domain ID", "accepted_credentials": "Accepted Credentials", + "credential_ids": "Credential IDs", "data": "Data", "finish_function": "Finish Function", "quorum_description": "Min. number of trusted validations needed to confirm a ledger", diff --git a/public/locales/es-ES/translations.json b/public/locales/es-ES/translations.json index 55f227edb..0a1feadee 100644 --- a/public/locales/es-ES/translations.json +++ b/public/locales/es-ES/translations.json @@ -318,6 +318,8 @@ "set_nftoken_minter_description": "Se establece a <0>{{account}} como el acuñador autorizado para esta cuenta", "deposit_auth": "Se autoriza a <1><0>{{account}} a enviar pagos a esta cuenta", "deposit_unauth": "Se elimina la autorización a <1><0>{{account}} para enviar pagos a esta cuenta", + "deposit_auth_credentials": null, + "deposit_unauth_credentials": null, "invalid_xrpl_address": "Dirección XRPL inválida", "loading": "Cargando", "get_ledger_failed": "No ha sido posible cargar el libro contable", @@ -577,6 +579,7 @@ "expiration": null, "domain_id": null, "accepted_credentials": null, + "credential_ids": null, "action_from_and": null, "data": null, "finish_function": null, diff --git a/public/locales/fr-FR/translations.json b/public/locales/fr-FR/translations.json index c6551aeb9..281f370bf 100644 --- a/public/locales/fr-FR/translations.json +++ b/public/locales/fr-FR/translations.json @@ -320,6 +320,8 @@ "set_nftoken_minter_description": "Définit <0>{{account}} comme le forgeur autorisé pour ce compte.", "deposit_auth": "Autorise <1><0>{{account}} à envoyer des paiements vers ce compte.", "deposit_unauth": "Supprime l'autorisation pour <1><0>{{account}} d'envoyer des paiements à ce compte.", + "deposit_auth_credentials": null, + "deposit_unauth_credentials": null, "invalid_xrpl_address": "Adresse XRPL invalide", "loading": "Chargement en cours", "get_ledger_failed": "Impossible de charger le registre", @@ -578,6 +580,7 @@ "expiration": null, "domain_id": null, "accepted_credentials": null, + "credential_ids": null, "action_from_and": null, "data": null, "finish_function": null, diff --git a/public/locales/ja-JP/translations.json b/public/locales/ja-JP/translations.json index da41cf11f..6476699ae 100644 --- a/public/locales/ja-JP/translations.json +++ b/public/locales/ja-JP/translations.json @@ -320,6 +320,8 @@ "set_nftoken_minter_description": "<0>{{account}} をNFTの認可発行者として設定しました。", "deposit_auth": "<1><0>{{account}} からの送金を許可しました", "deposit_unauth": "<1><0>{{account}} からの送金の許可を削除しました", + "deposit_auth_credentials": null, + "deposit_unauth_credentials": null, "invalid_xrpl_address": "不正なXRPLアドレスです", "loading": "読み込み中", "get_ledger_failed": "レジャ情報ーを取得することができません", @@ -577,6 +579,7 @@ "expiration": null, "domain_id": null, "accepted_credentials": null, + "credential_ids": null, "action_from_and": null, "data": null, "finish_function": null, diff --git a/public/locales/ko-KR/translations.json b/public/locales/ko-KR/translations.json index a0f9f2749..584e8c01f 100644 --- a/public/locales/ko-KR/translations.json +++ b/public/locales/ko-KR/translations.json @@ -319,6 +319,8 @@ "set_nftoken_minter_description": "<0>{{account}}을 이 계정의 공인 발행인으로 설정합니다", "deposit_auth": "<1><0>{{account}}을 이 계정으로 결제를 보낼 수 있도록 승인합니다", "deposit_unauth": "<1><0>{{account}}의 이 계정으로의 결제 권한을 삭제합니다", + "deposit_auth_credentials": null, + "deposit_unauth_credentials": null, "invalid_xrpl_address": "유효하지 않은 XRPL 주소", "loading": "불러오는 중", "get_ledger_failed": "원장을 불러올 수 없습니다", @@ -575,6 +577,7 @@ "expiration": null, "domain_id": null, "accepted_credentials": null, + "credential_ids": null, "action_from_and": null, "data": null, "finish_function": null, diff --git a/public/locales/my-MM/translations.json b/public/locales/my-MM/translations.json index 878d5f9c5..0a2cc50e4 100644 --- a/public/locales/my-MM/translations.json +++ b/public/locales/my-MM/translations.json @@ -324,6 +324,8 @@ "set_nftoken_minter_description": "၎င်းသည် <0>{{account}} ကို ဤအကောင့်အတွက် ခွင့်ပြုချက်ရ NFT ထုတ်လုပ်သူအဖြစ် သတ်မှတ်သည်", "deposit_auth": "၎င်းသည် <1><0>{{account}} အား ဤအကောင့်သို့ ငွေပေးချေရန် ခွင့်ပြုသည်", "deposit_unauth": "၎င်းသည် <1><0>{{account}} ၏ ဤအကောင့်သို့ ငွေပေးချေခွင့်ကို ပယ်ဖျက်သည်", + "deposit_auth_credentials": null, + "deposit_unauth_credentials": null, "invalid_xrpl_address": "XRPL လိပ်စာ မှားယွင်းနေပါသည်", "loading": "ဖွင့်နေသည်", "get_ledger_failed": "Ledger ကို ဖွင့်၍မရပါ", @@ -734,6 +736,7 @@ "expiration": null, "domain_id": null, "accepted_credentials": null, + "credential_ids": null, "data": null, "tx_hash": null, "timestamp": null, diff --git a/src/containers/Transactions/simpleTab.scss b/src/containers/Transactions/simpleTab.scss index 33ca57a64..9f2681031 100644 --- a/src/containers/Transactions/simpleTab.scss +++ b/src/containers/Transactions/simpleTab.scss @@ -6,6 +6,15 @@ $subdued-color: $black-40; .rows { padding-top: 0px; + .groups-title { + display: block; + flex-direction: row; + color: $black-40; + font-size: 14px; + font-weight: 600; + text-transform: uppercase; + } + .partial-row { display: flex; overflow: hidden; diff --git a/src/containers/shared/components/Transaction/AccountDelete/Simple.tsx b/src/containers/shared/components/Transaction/AccountDelete/Simple.tsx index 321e77c4e..6865d3d50 100644 --- a/src/containers/shared/components/Transaction/AccountDelete/Simple.tsx +++ b/src/containers/shared/components/Transaction/AccountDelete/Simple.tsx @@ -4,15 +4,21 @@ import type { AccountDelete } from 'xrpl' import { SimpleRow } from '../SimpleRow' import { TransactionSimpleProps } from '../types' import { Account } from '../../Account' +import { CredentialIDs } from '../CredentialIDs' export const Simple = ({ data }: TransactionSimpleProps) => { const { t } = useTranslation() const tx = data.instructions return ( - - - {tx.DestinationTag && :{tx.DestinationTag}} - + <> + + + {tx.DestinationTag && :{tx.DestinationTag}} + + {tx.CredentialIDs && tx.CredentialIDs.length > 0 && ( + + )} + ) } diff --git a/src/containers/shared/components/Transaction/AccountDelete/test/AccountDeleteSimple.test.tsx b/src/containers/shared/components/Transaction/AccountDelete/test/AccountDeleteSimple.test.tsx index 97a9c908f..0e8de4b1f 100644 --- a/src/containers/shared/components/Transaction/AccountDelete/test/AccountDeleteSimple.test.tsx +++ b/src/containers/shared/components/Transaction/AccountDelete/test/AccountDeleteSimple.test.tsx @@ -1,17 +1,21 @@ -import i18n from '../../../../../../i18n/testConfigEnglish' - -import { createSimpleWrapperFactory } from '../../test/createWrapperFactory' +import { + createSimpleWrapperFactory, + expectSimpleRowText, + expectSimpleRowLabel, +} from '../../test' import { Simple } from '../Simple' import mockAccountDelete from './mock_data/AccountDelete.json' import mockAccountDeleteWithDestinationTag from './mock_data/AccountDeleteWithDestinationTag.json' +import mockAccountDeleteWithCredentialIDs from './mock_data/AccountDeleteWithCredentialIDs.json' -const createWrapper = createSimpleWrapperFactory(Simple, i18n) +const createWrapper = createSimpleWrapperFactory(Simple) describe('AccountDelete: Simple', () => { it('renders', () => { const wrapper = createWrapper(mockAccountDelete) - expect(wrapper.find('.label')).toHaveText('destination') - expect(wrapper.find('.value')).toHaveText( + expectSimpleRowText( + wrapper, + 'destination', 'raT74sdzpxJUaubcBAQNS8aLqFMU85Rr5J', ) wrapper.unmount() @@ -19,10 +23,29 @@ describe('AccountDelete: Simple', () => { it('renders with destination tag', () => { const wrapper = createWrapper(mockAccountDeleteWithDestinationTag) - expect(wrapper.find('.label')).toHaveText('destination') - expect(wrapper.find('.value')).toHaveText( + expectSimpleRowText( + wrapper, + 'destination', 'rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn:123123', ) wrapper.unmount() }) + + it('renders with CredentialIDs', () => { + const wrapper = createWrapper(mockAccountDeleteWithCredentialIDs) + + expectSimpleRowText( + wrapper, + 'destination', + 'raT74sdzpxJUaubcBAQNS8aLqFMU85Rr5J', + ) + expectSimpleRowText( + wrapper, + 'credential-id-0', + '7B685088D546B9E8905D26206F452BB2F44D9A33C9BD9BCF280F7BA39015A955', + ) + expectSimpleRowLabel(wrapper, 'credential-id-0', 'credential_ids') + + wrapper.unmount() + }) }) diff --git a/src/containers/shared/components/Transaction/AccountDelete/test/mock_data/AccountDeleteWithCredentialIDs.json b/src/containers/shared/components/Transaction/AccountDelete/test/mock_data/AccountDeleteWithCredentialIDs.json new file mode 100644 index 000000000..d9712db7b --- /dev/null +++ b/src/containers/shared/components/Transaction/AccountDelete/test/mock_data/AccountDeleteWithCredentialIDs.json @@ -0,0 +1,62 @@ +{ + "tx": { + "Account": "raf19Ns8czi85kTfia3KJ9ndjSp9baTLyH", + "Destination": "raT74sdzpxJUaubcBAQNS8aLqFMU85Rr5J", + "CredentialIDs": ["7B685088D546B9E8905D26206F452BB2F44D9A33C9BD9BCF280F7BA39015A955"], + "Fee": "5000000", + "Flags": 2147483648, + "Sequence": 58118671, + "SigningPubKey": "0211DA2A9AEF04FB772776CD35291AF20DC09A6F0EF52C1A58E4ED43C8685D7093", + "TransactionType": "AccountDelete", + "TxnSignature": "304402202E61AC4B56BFFD2186D3E9BDECE7B68CD21B9486F691C5B31AA0E84E161E2C1C022048B70011A1D04566883EFEC3E988C31A853B89D18E0159315EF488DE89F7BA8B" + }, + "meta": { + "AffectedNodes": [ + { + "DeletedNode": { + "FinalFields": { + "Account": "raf19Ns8czi85kTfia3KJ9ndjSp9baTLyH", + "Balance": "0", + "Flags": 0, + "MessageKey": "020000000000000000000000007C50654DCAE0020232ABB4C236F7CE03CF2784B0", + "OwnerCount": 0, + "PreviousTxnID": "9A0AD95BA92118D66BDB3DCF86575BAEBE34D3F640146BD5A68D4AC355E56A21", + "PreviousTxnLgrSeq": 58233607, + "Sequence": 58118672 + }, + "LedgerEntryType": "AccountRoot", + "LedgerIndex": "1F8BBAE9AD8533D18C1A2DD03E22232D49A28CCF2383D80CEE50735E82900068", + "PreviousFields": { + "Balance": "19999988", + "Sequence": 58118671 + } + } + }, + { + "ModifiedNode": { + "FinalFields": { + "Account": "raT74sdzpxJUaubcBAQNS8aLqFMU85Rr5J", + "Balance": "11468518495", + "Flags": 0, + "OwnerCount": 0, + "Sequence": 58233435 + }, + "LedgerEntryType": "AccountRoot", + "LedgerIndex": "40D5C5FF088C4BC3CA79974DCB585DDD04E9D88FF732AE6E7E36F0FF1CDD4220", + "PreviousFields": { + "Balance": "11453518507" + }, + "PreviousTxnID": "9A0AD95BA92118D66BDB3DCF86575BAEBE34D3F640146BD5A68D4AC355E56A21", + "PreviousTxnLgrSeq": 58233607 + } + } + ], + "DeliveredAmount": "14999988", + "TransactionIndex": 34, + "TransactionResult": "tesSUCCESS", + "delivered_amount": "14999988" + }, + "hash": "91BCF2965F7381EC01EDA6A36948A23480B566A0DDD3B13610ED8FD59840956D", + "ledger_index": 58233707, + "date": "2020-09-16T23:18:20Z" +} \ No newline at end of file diff --git a/src/containers/shared/components/Transaction/CredentialIDs.tsx b/src/containers/shared/components/Transaction/CredentialIDs.tsx new file mode 100644 index 000000000..d4c9a101b --- /dev/null +++ b/src/containers/shared/components/Transaction/CredentialIDs.tsx @@ -0,0 +1,28 @@ +import { useTranslation } from 'react-i18next' +import { SimpleRow } from './SimpleRow' + +interface CredentialIDsProps { + credentialIDs: string[] +} + +export const CredentialIDs = ({ credentialIDs }: CredentialIDsProps) => { + const { t } = useTranslation() + + if (!credentialIDs || credentialIDs.length === 0) { + return null + } + + return ( + <> + {credentialIDs.map((id, index) => ( + + {id} + + ))} + + ) +} diff --git a/src/containers/shared/components/Transaction/DepositPreauth/Description.tsx b/src/containers/shared/components/Transaction/DepositPreauth/Description.tsx index e722ba14c..68c16fec5 100644 --- a/src/containers/shared/components/Transaction/DepositPreauth/Description.tsx +++ b/src/containers/shared/components/Transaction/DepositPreauth/Description.tsx @@ -1,27 +1,71 @@ -import { Trans } from 'react-i18next' +import { Trans, useTranslation } from 'react-i18next' import { Account } from '../../Account' import { TransactionDescriptionProps } from '../types' import { DepositPreauth } from './types' +import { convertHexToString } from '../../../../../rippled/lib/utils' +import { parser } from './parser' export const Description = ({ data, }: TransactionDescriptionProps) => { - const { tx } = data - return tx.Authorize ? ( -
- - It Authorizes - - to send payments to the account - -
- ) : ( -
- - It removes the authorization for - - to send payments to the account - -
- ) + const { t } = useTranslation() + const tx = parser(data.tx) + + if (tx.Authorize) { + return ( +
+ + It authorizes + + to send payments to the account + +
+ ) + } + + if (tx.Unauthorize) { + return ( +
+ + It removes the authorization for + + to send payments to the account + +
+ ) + } + + if (tx.AuthorizeCredentials && tx.AuthorizeCredentials.length > 0) { + return ( +
+

{t('deposit_auth_credentials')}

+
    + {tx.AuthorizeCredentials.map((cred) => ( +
  • + {convertHexToString(cred.CredentialType)} from{' '} + +
  • + ))} +
+
+ ) + } + + if (tx.UnauthorizeCredentials && tx.UnauthorizeCredentials.length > 0) { + return ( +
+

{t('deposit_unauth_credentials')}

+
    + {tx.UnauthorizeCredentials.map((cred) => ( +
  • + {convertHexToString(cred.CredentialType)} from{' '} + +
  • + ))} +
+
+ ) + } + + return null } diff --git a/src/containers/shared/components/Transaction/DepositPreauth/Simple.tsx b/src/containers/shared/components/Transaction/DepositPreauth/Simple.tsx index cd9f9d395..748195ab6 100644 --- a/src/containers/shared/components/Transaction/DepositPreauth/Simple.tsx +++ b/src/containers/shared/components/Transaction/DepositPreauth/Simple.tsx @@ -1,15 +1,82 @@ import { useTranslation } from 'react-i18next' import { SimpleRow } from '../SimpleRow' +import { SimpleGroup } from '../SimpleGroup' import { TransactionSimpleProps } from '../types' import { DepositPreauth } from './types' +import { convertHexToString } from '../../../../../rippled/lib/utils' export const Simple = ({ data }: TransactionSimpleProps) => { const { t } = useTranslation() - const { Authorize, Unauthorize } = data.instructions + const { + Authorize, + Unauthorize, + AuthorizeCredentials, + UnauthorizeCredentials, + } = data.instructions - return Authorize ? ( - {Authorize} - ) : ( - {Unauthorize} - ) + if (Authorize) { + return ( + + {Authorize} + + ) + } + + if (Unauthorize) { + return ( + + {Unauthorize} + + ) + } + + if (AuthorizeCredentials && AuthorizeCredentials.length > 0) { + return ( + <> +
{`${t('authorize')} ${t('accepted_credentials')}`}
+ {AuthorizeCredentials.map((cred, index) => ( + + + {cred.Issuer} + + + {convertHexToString(cred.CredentialType)} + + + ))} + + ) + } + + if (UnauthorizeCredentials && UnauthorizeCredentials.length > 0) { + return ( + <> +
{`${t('unauthorize')} ${t('accepted_credentials')}`}
+ {UnauthorizeCredentials.map((cred, index) => ( + + + {cred.Issuer} + + + {convertHexToString(cred.CredentialType)} + + + ))} + + ) + } + + return null } diff --git a/src/containers/shared/components/Transaction/DepositPreauth/TableDetail.tsx b/src/containers/shared/components/Transaction/DepositPreauth/TableDetail.tsx new file mode 100644 index 000000000..9d3d0320f --- /dev/null +++ b/src/containers/shared/components/Transaction/DepositPreauth/TableDetail.tsx @@ -0,0 +1,87 @@ +import { useTranslation } from 'react-i18next' +import { TransactionTableDetailProps } from '../types' +import { DepositPreauth } from './types' +import { Account } from '../../Account' +import { convertHexToString } from '../../../../../rippled/lib/utils' + +export const TableDetail = ({ + instructions, +}: TransactionTableDetailProps) => { + const { t } = useTranslation() + const { + Authorize, + Unauthorize, + AuthorizeCredentials, + UnauthorizeCredentials, + } = instructions + + if (Authorize) { + return ( +
+ {t('authorize')} + +
+ ) + } + + if (Unauthorize) { + return ( +
+ {t('unauthorize')} + +
+ ) + } + + if (AuthorizeCredentials && AuthorizeCredentials.length > 0) { + return ( +
+ + {t('authorize')} {t('accepted_credentials')} + +
+ {AuthorizeCredentials.map((cred) => ( +
+ + {convertHexToString(cred.CredentialType)} + + + + +
+ ))} +
+
+ ) + } + + if (UnauthorizeCredentials && UnauthorizeCredentials.length > 0) { + return ( +
+ + {t('unauthorize')} {t('accepted_credentials')} + +
+ {UnauthorizeCredentials.map((cred) => ( +
+ + {convertHexToString(cred.CredentialType)} + + + + +
+ ))} +
+
+ ) + } + + return null +} diff --git a/src/containers/shared/components/Transaction/DepositPreauth/index.ts b/src/containers/shared/components/Transaction/DepositPreauth/index.ts index 3e5cef1a8..cc645da8c 100644 --- a/src/containers/shared/components/Transaction/DepositPreauth/index.ts +++ b/src/containers/shared/components/Transaction/DepositPreauth/index.ts @@ -6,10 +6,14 @@ import { import { Simple } from './Simple' import { Description } from './Description' +import { TableDetail } from './TableDetail' +import { parser } from './parser' export const DepositPreauthTransaction: TransactionMapping = { Description, Simple, + TableDetail, + parser, action: TransactionAction.MODIFY, category: TransactionCategory.ACCOUNT, } diff --git a/src/containers/shared/components/Transaction/DepositPreauth/parser.ts b/src/containers/shared/components/Transaction/DepositPreauth/parser.ts new file mode 100644 index 000000000..5ae662af6 --- /dev/null +++ b/src/containers/shared/components/Transaction/DepositPreauth/parser.ts @@ -0,0 +1,43 @@ +import { CredentialAuth, DepositPreauth } from './types' + +// Transform the nested XRPL credential structure to flat CredentialAuth objects +const transformCredentials = (credentials: any[]): CredentialAuth[] => + credentials.map((item) => { + // Check if it's nested (has Credential wrapper) or already flat + if (item.Credential) { + return { + Issuer: item.Credential.Issuer, + CredentialType: item.Credential.CredentialType, + } + } + // Already flat, return as-is + return { + Issuer: item.Issuer, + CredentialType: item.CredentialType, + } + }) + +export const parser = (tx: any): DepositPreauth => { + // Handle AuthorizeCredentials (both nested and flat structures) + if (tx.AuthorizeCredentials) { + const { Authorize, Unauthorize, ...rest } = tx + const result = { + ...rest, + AuthorizeCredentials: transformCredentials(tx.AuthorizeCredentials), + } as DepositPreauth + return result + } + + // Handle UnauthorizeCredentials (both nested and flat structures) + if (tx.UnauthorizeCredentials) { + const { Authorize, Unauthorize, ...rest } = tx + const result = { + ...rest, + UnauthorizeCredentials: transformCredentials(tx.UnauthorizeCredentials), + } as DepositPreauth + return result + } + + // For Authorize/Unauthorize fields (string-based), pass through unchanged + return tx as DepositPreauth +} diff --git a/src/containers/shared/components/Transaction/DepositPreauth/test/DepositPreauthDescription.test.tsx b/src/containers/shared/components/Transaction/DepositPreauth/test/DepositPreauthDescription.test.tsx index fdaa7079e..ee6847485 100644 --- a/src/containers/shared/components/Transaction/DepositPreauth/test/DepositPreauthDescription.test.tsx +++ b/src/containers/shared/components/Transaction/DepositPreauth/test/DepositPreauthDescription.test.tsx @@ -2,6 +2,8 @@ import i18n from '../../../../../../i18n/testConfigEnglish' import mockDepositPreaut from './mock_data/DepositPreauth.json' import mockDepositPreauthUnauthorize from './mock_data/DepositPreauthUnauthorize.json' +import mockDepositPreauthAuthorizeCredentials from './mock_data/DepositPreauthAuthorizeCredentials.json' +import mockDepositPreauthUnauthorizeCredentials from './mock_data/DepositPreauthUnauthorizeCredentials.json' import { Description } from '../Description' import { createDescriptionWrapperFactory } from '../../test' @@ -24,4 +26,26 @@ describe('DepositPreauth: Description', () => { ) wrapper.unmount() }) + + it('renders description for authorize credentials', () => { + const wrapper = createWrapper(mockDepositPreauthAuthorizeCredentials) + const html = wrapper.html() + expect(html).toContain('It authorizes the following credentials:') + expect(html).toContain('KYC') + expect(html).toContain('rISABEL') + expect(html).toContain('ID') + expect(html).toContain('rTRUSTED') + wrapper.unmount() + }) + + it('renders description for unauthorize credentials', () => { + const wrapper = createWrapper(mockDepositPreauthUnauthorizeCredentials) + const html = wrapper.html() + expect(html).toContain( + 'It removes the authorization for the following credentials:', + ) + expect(html).toContain('KYC') + expect(html).toContain('rISABEL') + wrapper.unmount() + }) }) diff --git a/src/containers/shared/components/Transaction/DepositPreauth/test/DepositPreauthSimple.test.tsx b/src/containers/shared/components/Transaction/DepositPreauth/test/DepositPreauthSimple.test.tsx index 3793f16d2..d85380d05 100644 --- a/src/containers/shared/components/Transaction/DepositPreauth/test/DepositPreauthSimple.test.tsx +++ b/src/containers/shared/components/Transaction/DepositPreauth/test/DepositPreauthSimple.test.tsx @@ -1,17 +1,22 @@ -import i18n from '../../../../../../i18n/testConfigEnglish' - -import { createSimpleWrapperFactory } from '../../test/createWrapperFactory' +import { + createSimpleWrapperFactory, + expectSimpleRowText, + expectSimpleRowLabel, +} from '../../test' import { Simple } from '../Simple' import mockDepositPreauth from './mock_data/DepositPreauth.json' import mockDepositPreauthUnauthorize from './mock_data/DepositPreauthUnauthorize.json' +import mockDepositPreauthAuthorizeCredentials from './mock_data/DepositPreauthAuthorizeCredentials.json' +import mockDepositPreauthUnauthorizeCredentials from './mock_data/DepositPreauthUnauthorizeCredentials.json' -const createWrapper = createSimpleWrapperFactory(Simple, i18n) +const createWrapper = createSimpleWrapperFactory(Simple) describe('DepositPreauth: Simple', () => { it('renders authorized', () => { const wrapper = createWrapper(mockDepositPreauth) - expect(wrapper.find('.label')).toHaveText('authorize') - expect(wrapper.find('.value')).toHaveText( + expectSimpleRowText( + wrapper, + 'authorize', 'rDJFnv5sEfp42LMFiX3mVQKczpFTdxYDzM', ) wrapper.unmount() @@ -19,10 +24,48 @@ describe('DepositPreauth: Simple', () => { it('renders unauthorized', () => { const wrapper = createWrapper(mockDepositPreauthUnauthorize) - expect(wrapper.find('.label')).toHaveText('unauthorize') - expect(wrapper.find('.value')).toHaveText( + expectSimpleRowText( + wrapper, + 'unauthorize', 'rDJFnv5sEfp42LMFiX3mVQKczpFTdxYDzM', ) wrapper.unmount() }) + + it('renders authorize credentials', () => { + const wrapper = createWrapper(mockDepositPreauthAuthorizeCredentials) + + // Check that SimpleGroup is rendered + expect(wrapper.find('.group')).toHaveLength(2) + expect(wrapper.find('.groups-title')).toHaveLength(1) + + // Check first credential + expectSimpleRowText(wrapper, 'credential-issuer-0', 'rISABEL......') + expectSimpleRowLabel(wrapper, 'credential-issuer-0', 'credential_issuer') + expectSimpleRowText(wrapper, 'credential-type-0', 'KYC') // 4B5943 hex decodes to "KYC" + expectSimpleRowLabel(wrapper, 'credential-type-0', 'credential_type') + + // Check second credential + expectSimpleRowText(wrapper, 'credential-issuer-1', 'rTRUSTED.....') + expectSimpleRowLabel(wrapper, 'credential-issuer-1', 'credential_issuer') + expectSimpleRowText(wrapper, 'credential-type-1', 'ID') // 4944 hex decodes to "ID" + expectSimpleRowLabel(wrapper, 'credential-type-1', 'credential_type') + + wrapper.unmount() + }) + + it('renders unauthorize credentials', () => { + const wrapper = createWrapper(mockDepositPreauthUnauthorizeCredentials) + + // Check that SimpleGroup is rendered + expect(wrapper.find('.group')).toHaveLength(1) + expect(wrapper.find('.groups-title')).toHaveLength(1) + + expectSimpleRowText(wrapper, 'credential-issuer-0', 'rISABEL......') + expectSimpleRowLabel(wrapper, 'credential-issuer-0', 'credential_issuer') + expectSimpleRowText(wrapper, 'credential-type-0', 'KYC') // 4B5943 hex decodes to "KYC" + expectSimpleRowLabel(wrapper, 'credential-type-0', 'credential_type') + + wrapper.unmount() + }) }) diff --git a/src/containers/shared/components/Transaction/DepositPreauth/test/DepositPreauthTableDetail.test.tsx b/src/containers/shared/components/Transaction/DepositPreauth/test/DepositPreauthTableDetail.test.tsx new file mode 100644 index 000000000..40ba8880c --- /dev/null +++ b/src/containers/shared/components/Transaction/DepositPreauth/test/DepositPreauthTableDetail.test.tsx @@ -0,0 +1,58 @@ +import { createTableDetailWrapperFactory } from '../../test' +import { TableDetail } from '../TableDetail' +import mockDepositPreauth from './mock_data/DepositPreauth.json' +import mockDepositPreauthAuthorizeCredentials from './mock_data/DepositPreauthAuthorizeCredentials.json' +import mockDepositPreauthUnauthorize from './mock_data/DepositPreauthUnauthorize.json' +import mockDepositPreauthUnauthorizeCredentials from './mock_data/DepositPreauthUnauthorizeCredentials.json' +import i18n from '../../../../../../i18n/testConfigEnglish' + +const createWrapper = createTableDetailWrapperFactory(TableDetail, i18n) + +describe('DepositPreauthTableDetail', () => { + it('renders DepositPreauth with Authorize', () => { + const wrapper = createWrapper(mockDepositPreauth) + expect(wrapper.find('.deposit-preauth')).toExist() + expect(wrapper.find('.deposit-preauth .label')).toHaveText('authorize') + expect(wrapper.find('[data-testid="account"]')).toExist() + expect(wrapper.find('[data-testid="account"]').at(0)).toHaveText( + 'rDJFnv5sEfp42LMFiX3mVQKczpFTdxYDzM', + ) + wrapper.unmount() + }) + + it('renders DepositPreauth with Unauthorize', () => { + const wrapper = createWrapper(mockDepositPreauthUnauthorize) + expect(wrapper.find('.deposit-preauth')).toExist() + expect(wrapper.find('.deposit-preauth .label')).toHaveText('unauthorize') + expect(wrapper.find('[data-testid="account"]')).toExist() + expect(wrapper.find('[data-testid="account"]').at(0)).toHaveText( + 'rDJFnv5sEfp42LMFiX3mVQKczpFTdxYDzM', + ) + wrapper.unmount() + }) + + it('renders DepositPreauth with AuthorizeCredentials', () => { + const wrapper = createWrapper(mockDepositPreauthAuthorizeCredentials) + expect(wrapper.find('.deposit-preauth')).toExist() + expect(wrapper.find('.deposit-preauth .label')).toHaveText( + 'authorize Accepted Credentials', + ) + expect(wrapper.find('.credentials')).toExist() + expect(wrapper.find('.credential').length).toBe(2) + expect(wrapper.find('.credential-type').at(0)).toHaveText('KYC') + expect(wrapper.find('.credential-type').at(1)).toHaveText('ID') + wrapper.unmount() + }) + + it('renders DepositPreauth with UnauthorizeCredentials', () => { + const wrapper = createWrapper(mockDepositPreauthUnauthorizeCredentials) + expect(wrapper.find('.deposit-preauth')).toExist() + expect(wrapper.find('.deposit-preauth .label')).toHaveText( + 'unauthorize Accepted Credentials', + ) + expect(wrapper.find('.credentials')).toExist() + expect(wrapper.find('.credential').length).toBe(1) + expect(wrapper.find('.credential-type').at(0)).toHaveText('KYC') + wrapper.unmount() + }) +}) diff --git a/src/containers/shared/components/Transaction/DepositPreauth/test/mock_data/DepositPreauthAuthorizeCredentials.json b/src/containers/shared/components/Transaction/DepositPreauth/test/mock_data/DepositPreauthAuthorizeCredentials.json new file mode 100644 index 000000000..5d457823a --- /dev/null +++ b/src/containers/shared/components/Transaction/DepositPreauth/test/mock_data/DepositPreauthAuthorizeCredentials.json @@ -0,0 +1,88 @@ +{ + "hash": "B5D94C027C846171B2F5D4C2D126E88580BF369986A155C1890352F5BC4D7AF9", + "ledger_index": 37360499, + "date": "2018-03-20T12:48:10+00:00", + "tx": { + "Account": "rDd6FpNbeY2CrQajSmP178BmNGusmQiYMM", + "AuthorizeCredentials": [ + { + "Credential": { + "Issuer": "rISABEL......", + "CredentialType": "4B5943" + } + }, + { + "Credential": { + "Issuer": "rTRUSTED.....", + "CredentialType": "4944" + } + } + ], + "Fee": "10", + "Flags": 2147483648, + "Sequence": 65, + "SigningPubKey": "03EB1E2603E7571D6144684996C10DA75063D6E2F3B3FDABE38B857C1BE9578A55", + "TransactionType": "DepositPreauth", + "TxnSignature": "3045022100B0A5672E3E09FA3AF8CF1DCC1D8C881F58B39212D6FDC42CCF30E5400D0EFD9F02202DDD9517D9409D1D9A529B8AEA7DE13AE4CDF96BB6D18FA0D9732DBFC887348D" + }, + "meta": { + "AffectedNodes": [ + { + "ModifiedNode": { + "FinalFields": { + "Account": "rDd6FpNbeY2CrQajSmP178BmNGusmQiYMM", + "Balance": "9827999174", + "Flags": 0, + "OwnerCount": 8, + "Sequence": 66 + }, + "LedgerEntryType": "AccountRoot", + "LedgerIndex": "146AAAF7A266D8A92DFFEAB1A71B6523534F27820873F4E213014B23398867D2", + "PreviousFields": { + "Balance": "9827999184", + "OwnerCount": 7, + "Sequence": 65 + }, + "PreviousTxnID": "8107CEE99D556326ACD4662CA10A24550240D9F933E55435A0C0DB3B06DD343E", + "PreviousTxnLgrSeq": 11427673 + } + }, + { + "CreatedNode": { + "LedgerEntryType": "DepositPreauth", + "LedgerIndex": "C2D0317AD266B93CB3B36AEB0ABB673B0AFFAB134809CCACFD7158F539603C3A", + "NewFields": { + "Account": "rDd6FpNbeY2CrQajSmP178BmNGusmQiYMM", + "AuthorizeCredentials": [ + { + "Credential": { + "Issuer": "rISABEL......", + "CredentialType": "4B5943" + } + }, + { + "Credential": { + "Issuer": "rTRUSTED.....", + "CredentialType": "4944" + } + } + ] + } + } + }, + { + "ModifiedNode": { + "FinalFields": { + "Flags": 0, + "Owner": "rDd6FpNbeY2CrQajSmP178BmNGusmQiYMM", + "RootIndex": "CD08416851CA53E9649408118A4908E01E43436ED950886D1B1E66F4B68B82EC" + }, + "LedgerEntryType": "DirectoryNode", + "LedgerIndex": "CD08416851CA53E9649408118A4908E01E43436ED950886D1B1E66F4B68B82EC" + } + } + ], + "TransactionIndex": 0, + "TransactionResult": "tesSUCCESS" + } +} \ No newline at end of file diff --git a/src/containers/shared/components/Transaction/DepositPreauth/test/mock_data/DepositPreauthUnauthorizeCredentials.json b/src/containers/shared/components/Transaction/DepositPreauth/test/mock_data/DepositPreauthUnauthorizeCredentials.json new file mode 100644 index 000000000..cdeacdad8 --- /dev/null +++ b/src/containers/shared/components/Transaction/DepositPreauth/test/mock_data/DepositPreauthUnauthorizeCredentials.json @@ -0,0 +1,76 @@ +{ + "hash": "B5D94C027C846171B2F5D4C2D126E88580BF369986A155C1890352F5BC4D7AF9", + "ledger_index": 37360499, + "date": "2018-03-20T12:48:10+00:00", + "tx": { + "Account": "rDd6FpNbeY2CrQajSmP178BmNGusmQiYMM", + "UnauthorizeCredentials": [ + { + "Credential": { + "Issuer": "rISABEL......", + "CredentialType": "4B5943" + } + } + ], + "Fee": "10", + "Flags": 2147483648, + "Sequence": 65, + "SigningPubKey": "03EB1E2603E7571D6144684996C10DA75063D6E2F3B3FDABE38B857C1BE9578A55", + "TransactionType": "DepositPreauth", + "TxnSignature": "3045022100B0A5672E3E09FA3AF8CF1DCC1D8C881F58B39212D6FDC42CCF30E5400D0EFD9F02202DDD9517D9409D1D9A529B8AEA7DE13AE4CDF96BB6D18FA0D9732DBFC887348D" + }, + "meta": { + "AffectedNodes": [ + { + "ModifiedNode": { + "FinalFields": { + "Account": "rDd6FpNbeY2CrQajSmP178BmNGusmQiYMM", + "Balance": "9827999174", + "Flags": 0, + "OwnerCount": 8, + "Sequence": 66 + }, + "LedgerEntryType": "AccountRoot", + "LedgerIndex": "146AAAF7A266D8A92DFFEAB1A71B6523534F27820873F4E213014B23398867D2", + "PreviousFields": { + "Balance": "9827999184", + "OwnerCount": 7, + "Sequence": 65 + }, + "PreviousTxnID": "8107CEE99D556326ACD4662CA10A24550240D9F933E55435A0C0DB3B06DD343E", + "PreviousTxnLgrSeq": 11427673 + } + }, + { + "DeletedNode": { + "FinalFields": { + "Account": "rDd6FpNbeY2CrQajSmP178BmNGusmQiYMM", + "UnauthorizeCredentials": [ + { + "Credential": { + "Issuer": "rISABEL......", + "CredentialType": "4B5943" + } + } + ] + }, + "LedgerEntryType": "DepositPreauth", + "LedgerIndex": "C2D0317AD266B93CB3B36AEB0ABB673B0AFFAB134809CCACFD7158F539603C3A" + } + }, + { + "ModifiedNode": { + "FinalFields": { + "Flags": 0, + "Owner": "rDd6FpNbeY2CrQajSmP178BmNGusmQiYMM", + "RootIndex": "CD08416851CA53E9649408118A4908E01E43436ED950886D1B1E66F4B68B82EC" + }, + "LedgerEntryType": "DirectoryNode", + "LedgerIndex": "CD08416851CA53E9649408118A4908E01E43436ED950886D1B1E66F4B68B82EC" + } + } + ], + "TransactionIndex": 0, + "TransactionResult": "tesSUCCESS" + } +} \ No newline at end of file diff --git a/src/containers/shared/components/Transaction/DepositPreauth/types.ts b/src/containers/shared/components/Transaction/DepositPreauth/types.ts index b2e37bcd0..892561b20 100644 --- a/src/containers/shared/components/Transaction/DepositPreauth/types.ts +++ b/src/containers/shared/components/Transaction/DepositPreauth/types.ts @@ -1,13 +1,39 @@ +// Credential authorization object as per XLS-70 spec +export interface CredentialAuth { + Issuer: string + CredentialType: string +} + export interface DepositPreauthWithAuthorize { Authorize: string Unauthorize: never + AuthorizeCredentials: never + UnauthorizeCredentials: never } export interface DepositPreauthWithUnauthorize { Authorize: never Unauthorize: string + AuthorizeCredentials: never + UnauthorizeCredentials: never +} + +export interface DepositPreauthWithAuthorizeCredentials { + Authorize: never + Unauthorize: never + AuthorizeCredentials: CredentialAuth[] + UnauthorizeCredentials: never +} + +export interface DepositPreauthWithUnauthorizeCredentials { + Authorize: never + Unauthorize: never + AuthorizeCredentials: never + UnauthorizeCredentials: CredentialAuth[] } export type DepositPreauth = | DepositPreauthWithAuthorize | DepositPreauthWithUnauthorize + | DepositPreauthWithAuthorizeCredentials + | DepositPreauthWithUnauthorizeCredentials diff --git a/src/containers/shared/components/Transaction/EscrowFinish/Simple.tsx b/src/containers/shared/components/Transaction/EscrowFinish/Simple.tsx index e97fe0cba..23f99ef31 100644 --- a/src/containers/shared/components/Transaction/EscrowFinish/Simple.tsx +++ b/src/containers/shared/components/Transaction/EscrowFinish/Simple.tsx @@ -5,6 +5,7 @@ import { SimpleRow } from '../SimpleRow' import { Amount } from '../../Amount' import { RouteLink } from '../../../routing' import { TRANSACTION_ROUTE } from '../../../../App/routes' +import { CredentialIDs } from '../CredentialIDs' const Simple: TransactionSimpleComponent = (props: TransactionSimpleProps) => { const { t } = useTranslation() @@ -18,6 +19,7 @@ const Simple: TransactionSimpleComponent = (props: TransactionSimpleProps) => { condition, fulfillment, computationAllowance, + credentialIDs, } = data.instructions return ( @@ -72,6 +74,9 @@ const Simple: TransactionSimpleComponent = (props: TransactionSimpleProps) => { )} + {credentialIDs && credentialIDs.length > 0 && ( + + )} ) } diff --git a/src/containers/shared/components/Transaction/EscrowFinish/parser.ts b/src/containers/shared/components/Transaction/EscrowFinish/parser.ts index b3d16ac4a..2b4a1b724 100644 --- a/src/containers/shared/components/Transaction/EscrowFinish/parser.ts +++ b/src/containers/shared/components/Transaction/EscrowFinish/parser.ts @@ -21,5 +21,6 @@ export function parser(tx: EscrowFinish, meta: TransactionMetadata) { condition: escrow.Condition, fulfillment: tx.Fulfillment, computationAllowance: tx.ComputationAllowance, + credentialIDs: (tx as any).CredentialIDs, // Cast to any to include CredentialIDs } } diff --git a/src/containers/shared/components/Transaction/EscrowFinish/test/EscrowFinishSimple.test.tsx b/src/containers/shared/components/Transaction/EscrowFinish/test/EscrowFinishSimple.test.tsx index 18a893c1c..d98520191 100644 --- a/src/containers/shared/components/Transaction/EscrowFinish/test/EscrowFinishSimple.test.tsx +++ b/src/containers/shared/components/Transaction/EscrowFinish/test/EscrowFinishSimple.test.tsx @@ -3,6 +3,7 @@ import { createSimpleWrapperFactory } from '../../test/createWrapperFactory' import { Simple } from '../Simple' import mockEscrowFinish from './mock_data/EscrowFinish.json' import mockEscrowFinishCompAllow from './mock_data/EscrowFinishComputationAllowance.json' +import mockEscrowFinishCredentialIDs from './mock_data/EscrowFinishWithCredentialIDs.json' const createWrapper = createSimpleWrapperFactory(Simple) @@ -82,4 +83,20 @@ describe('EscrowFinishSimple', () => { ) wrapper.unmount() }) + + it('renders with CredentialIDs', () => { + const wrapper = createWrapper(mockEscrowFinishCredentialIDs) + expect(wrapper.find('[data-testid="escrow-amount"] .value')).toHaveText( + `\uE9000.0154 XRP`, + ) + expect(wrapper.find('[data-testid="credential-id-0"]')).toExist() + expect(wrapper.find('[data-testid="credential-id-1"]')).toExist() + expect(wrapper.find('[data-testid="credential-id-0"] .value')).toHaveText( + '7B685088D546B9E8905D26206F452BB2F44D9A33C9BD9BCF280F7BA39015A955', + ) + expect(wrapper.find('[data-testid="credential-id-1"] .value')).toHaveText( + '8B685088D546B9E8905D26206F452BB2F44D9A33C9BD9BCF280F7BA39015A956', + ) + wrapper.unmount() + }) }) diff --git a/src/containers/shared/components/Transaction/EscrowFinish/test/mock_data/EscrowFinishWithCredentialIDs.json b/src/containers/shared/components/Transaction/EscrowFinish/test/mock_data/EscrowFinishWithCredentialIDs.json new file mode 100644 index 000000000..a78d263bc --- /dev/null +++ b/src/containers/shared/components/Transaction/EscrowFinish/test/mock_data/EscrowFinishWithCredentialIDs.json @@ -0,0 +1,62 @@ +{ + "hash": "985F9CA6FD2256D5C59F8B2CC1F51BDB2F12EB7C58CB2CCA7E4A74161432B7C4", + "ledger_index": 37467862, + "date": "2018-03-25T00:45:12+00:00", + "tx": { + "TransactionType": "EscrowFinish", + "Flags": 2147483648, + "Fulfillment": "Fulfillment", + "Sequence": 31, + "OfferSequence": 28, + "LastLedgerSequence": 37467863, + "Fee": "12", + "SigningPubKey": "0380BB0305B1192D1F20E2E15C93E99434F8DF52ED72FC18FA4F69671B578691DF", + "TxnSignature": "30450221009254A6DE4960F185E19A6717B000EB542C1340BABB5E1C0447D743FB00A026A50220232DFE95819DA27B789DF47359C3EDD29FC539041EFF8526185D42485CF886A6", + "Account": "r4UDXF4nL7Tgss8uQxn39cCocd8GnGyXS8", + "Owner": "r4UDXF4nL7Tgss8uQxn39cCocd8GnGyXS8", + "CredentialIDs": [ + "7B685088D546B9E8905D26206F452BB2F44D9A33C9BD9BCF280F7BA39015A955", + "8B685088D546B9E8905D26206F452BB2F44D9A33C9BD9BCF280F7BA39015A956" + ] + }, + "meta": { + "TransactionIndex": 23, + "AffectedNodes": [ + { + "ModifiedNode": { + "LedgerEntryType": "AccountRoot", + "PreviousTxnLgrSeq": 37467849, + "PreviousTxnID": "C730DAD26F068100CDD7F5CF17CFB7EA729D3000BFDF92BBC36590C4FE906866", + "LedgerIndex": "70AD67C13AB61B6FCAF9CD65862478E079B05C772D65F60179EC8E7E7701740E", + "PreviousFields": { + "Sequence": 31, + "OwnerCount": 4, + "Balance": "59916121" + }, + "FinalFields": { + "Flags": 0, + "Sequence": 32, + "OwnerCount": 3, + "Balance": "59931509", + "Account": "r4UDXF4nL7Tgss8uQxn39cCocd8GnGyXS8" + } + } + }, + { + "DeletedNode": { + "LedgerEntryType": "Escrow", + "LedgerIndex": "89C78DC2F504E3C8CA91C7BE9EA53CA7F72F44A30F8F57CFA8CF8BA90ADC6826", + "FinalFields": { + "Destination": "r4UDXF4nL7Tgss8uQxn39cCocd8GnGyXS8", + "Amount": "15400", + "PreviousTxnLgrSeq": 37467849, + "Account": "r4UDXF4nL7Tgss8uQxn39cCocd8GnGyXS8", + "PreviousTxnID": "3E2E755FA75FF1020C39E2ECC407E9F1C0E49A7229EDD15FF93B9F869878F1CC" + } + } + } + ], + "TransactionResult": "tesSUCCESS", + "delivered_amount": "15400" + } +} diff --git a/src/containers/shared/components/Transaction/Payment/Simple.tsx b/src/containers/shared/components/Transaction/Payment/Simple.tsx index 2e13da0b2..5c120b6b5 100644 --- a/src/containers/shared/components/Transaction/Payment/Simple.tsx +++ b/src/containers/shared/components/Transaction/Payment/Simple.tsx @@ -4,6 +4,7 @@ import { Amount } from '../../Amount' import { PaymentInstructions } from './types' import { TransactionSimpleProps } from '../types' import { SimpleRow } from '../SimpleRow' +import { CredentialIDs } from '../CredentialIDs' export const Simple = (props: TransactionSimpleProps) => { const { data } = props @@ -18,7 +19,8 @@ export const Simple = (props: TransactionSimpleProps) => { } const renderPayment = () => { - const { max, destination, sourceTag, partial, domainID } = data.instructions + const { max, destination, sourceTag, partial, domainID, credentialIDs } = + data.instructions return ( <> @@ -48,6 +50,9 @@ export const Simple = (props: TransactionSimpleProps) => { + {credentialIDs && credentialIDs.length > 0 && ( + + )} ) } diff --git a/src/containers/shared/components/Transaction/Payment/TableDetail.tsx b/src/containers/shared/components/Transaction/Payment/TableDetail.tsx index 22ce1b6d8..8978595f1 100644 --- a/src/containers/shared/components/Transaction/Payment/TableDetail.tsx +++ b/src/containers/shared/components/Transaction/Payment/TableDetail.tsx @@ -8,13 +8,36 @@ export const TableDetail = ({ instructions, }: TransactionTableDetailProps) => { const { t } = useTranslation() - const { convert, amount, destination, partial, sourceTag, domainID } = - instructions + const { + convert, + amount, + destination, + partial, + sourceTag, + domainID, + credentialIDs, + } = instructions const renderPartial = () => (
{t('partial_payment_allowed')}
) + const renderCredentialIDs = () => { + if (!credentialIDs || credentialIDs.length === 0) { + return null + } + return ( +
+ {t('credential_ids')}: + {credentialIDs.map((id) => ( +
+ {id} +
+ ))} +
+ ) + } + if (convert) { return (
@@ -23,6 +46,7 @@ export const TableDetail = ({ {t('to')} {partial && renderPartial()} + {renderCredentialIDs()}
) } @@ -47,6 +71,7 @@ export const TableDetail = ({ {domainID} )} + {renderCredentialIDs()} {partial && renderPartial()} ) diff --git a/src/containers/shared/components/Transaction/Payment/parser.ts b/src/containers/shared/components/Transaction/Payment/parser.ts index 7e70aeea1..44bc5f82c 100644 --- a/src/containers/shared/components/Transaction/Payment/parser.ts +++ b/src/containers/shared/components/Transaction/Payment/parser.ts @@ -28,6 +28,7 @@ export const parser = (tx: any, meta: any): PaymentInstructions => { destination, partial, domainID: tx.DomainID, + credentialIDs: tx.CredentialIDs, } } @@ -38,5 +39,6 @@ export const parser = (tx: any, meta: any): PaymentInstructions => { sourceTag: tx.SourceTag, partial, domainID: tx.DomainID, + credentialIDs: tx.CredentialIDs, } } 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 88e452578..3bda80b96 100644 --- a/src/containers/shared/components/Transaction/Payment/test/PaymentSimple.test.tsx +++ b/src/containers/shared/components/Transaction/Payment/test/PaymentSimple.test.tsx @@ -13,6 +13,7 @@ import mockPaymentSendMax from './mock_data/PaymentWithSendMax.json' import mockPaymentSourceTag from './mock_data/PaymentWithSourceTag.json' import mockPaymentMPT from './mock_data/PaymentMPT.json' import mockPermDomainID from './mock_data/PaymentWithPermDomainID.json' +import mockPaymentWithCredentialIDs from './mock_data/PaymentWithCredentialIDs.json' jest.mock('react-query', () => ({ ...jest.requireActual('react-query'), @@ -163,4 +164,33 @@ describe('Payment: Simple', () => { wrapper.unmount() }) + + it('renders with CredentialIDs', () => { + const wrapper = createWrapper(mockPaymentWithCredentialIDs) + + expectSimpleRowText(wrapper, 'amount', `\uE9002,421.8268 XRP`) + expectSimpleRowLabel(wrapper, 'amount', `send`) + + expectSimpleRowText( + wrapper, + 'destination', + `rHoPwMC75KVUhBMeV3uDMybKG5JND74teh`, + ) + + // Check credential IDs as individual rows + expectSimpleRowText( + wrapper, + 'credential-id-0', + '7B685088D546B9E8905D26206F452BB2F44D9A33C9BD9BCF280F7BA39015A955', + ) + expectSimpleRowLabel(wrapper, 'credential-id-0', 'credential_ids') + expectSimpleRowText( + wrapper, + 'credential-id-1', + '8B685088D546B9E8905D26206F452BB2F44D9A33C9BD9BCF280F7BA39015A956', + ) + expectSimpleRowLabel(wrapper, 'credential-id-1', '') + + wrapper.unmount() + }) }) diff --git a/src/containers/shared/components/Transaction/Payment/test/PaymentTableDetail.test.tsx b/src/containers/shared/components/Transaction/Payment/test/PaymentTableDetail.test.tsx index 4d4647240..fa26f1184 100644 --- a/src/containers/shared/components/Transaction/Payment/test/PaymentTableDetail.test.tsx +++ b/src/containers/shared/components/Transaction/Payment/test/PaymentTableDetail.test.tsx @@ -7,6 +7,7 @@ import mockPaymentPartial from './mock_data/PaymentWithPartial.json' import mockPaymentSendMax from './mock_data/PaymentWithSendMax.json' import mockPaymentSourceTag from './mock_data/PaymentWithSourceTag.json' import mockPermDomainID from './mock_data/PaymentWithPermDomainID.json' +import mockPaymentCredentialIDs from './mock_data/PaymentWithCredentialIDs.json' const createWrapper = createTableDetailWrapperFactory(TableDetail) @@ -81,4 +82,22 @@ describe('Payment: TableDetail', () => { `domain_id: D3261DF48CDA3B860ED3FA99F02138856393CD44556E028D5CB66192A18A8D02`, ) }) + + it('renders with CredentialIDs', () => { + const wrapper = createWrapper(mockPaymentCredentialIDs) + + expect(wrapper.find('.credential-ids')).toExist() + expect(wrapper.find('.credential-ids .label')).toHaveText( + 'credential_ids: ', + ) + expect(wrapper.find('.credential-id').length).toBe(2) + expect(wrapper.find('.credential-id').at(0)).toHaveText( + '7B685088D546B9E8905D26206F452BB2F44D9A33C9BD9BCF280F7BA39015A955', + ) + expect(wrapper.find('.credential-id').at(1)).toHaveText( + '8B685088D546B9E8905D26206F452BB2F44D9A33C9BD9BCF280F7BA39015A956', + ) + + wrapper.unmount() + }) }) diff --git a/src/containers/shared/components/Transaction/Payment/test/mock_data/PaymentWithCredentialIDs.json b/src/containers/shared/components/Transaction/Payment/test/mock_data/PaymentWithCredentialIDs.json new file mode 100644 index 000000000..0c54b86cb --- /dev/null +++ b/src/containers/shared/components/Transaction/Payment/test/mock_data/PaymentWithCredentialIDs.json @@ -0,0 +1,54 @@ +{ + "hash": "F4AD54E6FE56B53F8FB8B422C4E9636D6AD56682EA45FF3C8AFBC66EF2D82612", + "ledger_index": 37432866, + "date": "2018-03-23T13:34:51+00:00", + "tx": { + "TransactionType": "Payment", + "Flags": 2147483648, + "Sequence": 31030, + "Amount": "2421826800", + "Fee": "150000", + "SigningPubKey": "0287A3059E24E773CB585A77D59771E54DDABEACD9503650269BA4E3413CA35036", + "TxnSignature": "30450221008D56168A5794D1F87D29F83D241477710AA32027F89D68925166477529D0CD50022043E2BFEEE543AA983BA036D3BC2303A6EB13DB7BC0770FBFD9E855DB04BBADED", + "Account": "rNQEMJA4PsoSrZRn9J6RajAYhcDzzhf8ok", + "Destination": "rHoPwMC75KVUhBMeV3uDMybKG5JND74teh", + "CredentialIDs": ["7B685088D546B9E8905D26206F452BB2F44D9A33C9BD9BCF280F7BA39015A955", "8B685088D546B9E8905D26206F452BB2F44D9A33C9BD9BCF280F7BA39015A956"] + }, + "meta": { + "TransactionIndex": 7, + "AffectedNodes": [ + { + "CreatedNode": { + "LedgerEntryType": "AccountRoot", + "LedgerIndex": "62B63A85C9753AD909B67225CBCC5271C3DDED972DDB89A74BC4347D1C62B4A0", + "NewFields": { + "Sequence": 231, + "Balance": "2421826800", + "Account": "rHoPwMC75KVUhBMeV3uDMybKG5JND74teh" + } + } + }, + { + "ModifiedNode": { + "LedgerEntryType": "AccountRoot", + "PreviousTxnLgrSeq": 37432853, + "PreviousTxnID": "884A9090391E30E7AD2789A20425E99192A227358DE4117D1469108DB3DFE5D7", + "LedgerIndex": "B8B66C89364DF501737D1AE9A5B4583E8945B8B27CB81FDAB39A50FBAF57B639", + "PreviousFields": { + "Sequence": 31030, + "Balance": "27121947538132" + }, + "FinalFields": { + "Flags": 131072, + "Sequence": 31031, + "OwnerCount": 0, + "Balance": "27119525561332", + "Account": "rNQEMJA4PsoSrZRn9J6RajAYhcDzzhf8ok" + } + } + } + ], + "TransactionResult": "tesSUCCESS", + "delivered_amount": "2421826800" + } +} \ No newline at end of file diff --git a/src/containers/shared/components/Transaction/Payment/types.ts b/src/containers/shared/components/Transaction/Payment/types.ts index 0d72ac13a..34eca3dbb 100644 --- a/src/containers/shared/components/Transaction/Payment/types.ts +++ b/src/containers/shared/components/Transaction/Payment/types.ts @@ -8,4 +8,5 @@ export interface PaymentInstructions { destination: string sourceTag?: number domainID?: string + credentialIDs?: string[] } diff --git a/src/containers/shared/components/Transaction/PaymentChannelClaim/Simple.tsx b/src/containers/shared/components/Transaction/PaymentChannelClaim/Simple.tsx index 890501665..6d39b593f 100644 --- a/src/containers/shared/components/Transaction/PaymentChannelClaim/Simple.tsx +++ b/src/containers/shared/components/Transaction/PaymentChannelClaim/Simple.tsx @@ -4,6 +4,7 @@ import { SimpleRow } from '../SimpleRow' import { Amount } from '../../Amount' import { TransactionSimpleProps } from '../types' import { PaymentChannelClaimInstructions } from './types' +import { CredentialIDs } from '../CredentialIDs' export const Simple = ({ data, @@ -19,6 +20,7 @@ export const Simple = ({ renew, close, deleted, + credentialIDs, } = data.instructions const dParts = destination.split(':') const sParts = source.split(':') @@ -72,6 +74,9 @@ export const Simple = ({ {t('payment_channel_closed')} )} + {credentialIDs && credentialIDs.length > 0 && ( + + )} ) } diff --git a/src/containers/shared/components/Transaction/PaymentChannelClaim/parser.ts b/src/containers/shared/components/Transaction/PaymentChannelClaim/parser.ts index c57f3fa4f..84a5cc255 100644 --- a/src/containers/shared/components/Transaction/PaymentChannelClaim/parser.ts +++ b/src/containers/shared/components/Transaction/PaymentChannelClaim/parser.ts @@ -28,6 +28,7 @@ export const parser = ( totalClaimed: tx.Balance ? formatAmount(tx.Balance) : undefined, renew: hasRenew(typeof tx.Flags === 'number' ? tx.Flags : 0) || undefined, close: hasClose(typeof tx.Flags === 'number' ? tx.Flags : 0) || undefined, + credentialIDs: (tx as any).CredentialIDs, // Cast to any to include CredentialIDs } if (node) { diff --git a/src/containers/shared/components/Transaction/PaymentChannelClaim/test/PaymentChannelClaimSimple.test.tsx b/src/containers/shared/components/Transaction/PaymentChannelClaim/test/PaymentChannelClaimSimple.test.tsx index 4a54173bf..b518857c3 100644 --- a/src/containers/shared/components/Transaction/PaymentChannelClaim/test/PaymentChannelClaimSimple.test.tsx +++ b/src/containers/shared/components/Transaction/PaymentChannelClaim/test/PaymentChannelClaimSimple.test.tsx @@ -6,6 +6,7 @@ import mockPaymentChannelClaim from './mock_data/PaymentChannelClaim.json' import mockPaymentChannelClaimClosed from './mock_data/PaymentChannelClaimClosed.json' import mockPaymentChannelClaimCloseDenied from './mock_data/PaymentChannelClaimCloseDenied.json' import mockPaymentChannelClaimWithDestinationTag from './mock_data/PaymentChannelClaimWithDestinationTag.json' +import mockPaymentChannelClaimWithCredentialIDs from './mock_data/PaymentChannelClaimWithCredentialIDs.json' import { expectSimpleRowLabel, expectSimpleRowNotToExist, @@ -120,4 +121,20 @@ describe('PaymentChannelClaim: Simple', () => { expectSimpleRowNotToExist(wrapper, 'closed') wrapper.unmount() }) + + it('renders tx with CredentialIDs', () => { + const wrapper = createWrapper(mockPaymentChannelClaimWithCredentialIDs) + expectSimpleRowText(wrapper, 'amount', '\uE90070.00 XRP') + expectSimpleRowText(wrapper, 'claimed', '\uE9000.01 XRP') + expectSimpleRowText(wrapper, 'total', '\uE90049.65716 XRP') + expect(wrapper.find('[data-testid="credential-id-0"]')).toExist() + expect(wrapper.find('[data-testid="credential-id-1"]')).toExist() + expect(wrapper.find('[data-testid="credential-id-0"] .value')).toHaveText( + '7B685088D546B9E8905D26206F452BB2F44D9A33C9BD9BCF280F7BA39015A955', + ) + expect(wrapper.find('[data-testid="credential-id-1"] .value')).toHaveText( + '8B685088D546B9E8905D26206F452BB2F44D9A33C9BD9BCF280F7BA39015A956', + ) + wrapper.unmount() + }) }) diff --git a/src/containers/shared/components/Transaction/PaymentChannelClaim/test/mock_data/PaymentChannelClaimWithCredentialIDs.json b/src/containers/shared/components/Transaction/PaymentChannelClaim/test/mock_data/PaymentChannelClaimWithCredentialIDs.json new file mode 100644 index 000000000..90ae03cc6 --- /dev/null +++ b/src/containers/shared/components/Transaction/PaymentChannelClaim/test/mock_data/PaymentChannelClaimWithCredentialIDs.json @@ -0,0 +1,70 @@ +{ + "hash": "B576A11C6AE8830A1C1B77C27567D62E39A03E62DBE8FC1A09D463C2400B808A", + "ledger_index": 37564494, + "date": "2018-03-29T01:38:11+00:00", + "tx": { + "TransactionType": "PaymentChannelClaim", + "Flags": 2147483648, + "Sequence": 1103, + "LastLedgerSequence": 37564495, + "Channel": "50107651E7163E294CE0EAD8A20BF7CC046304480FCC9C74A49FFAB3F46FB98E", + "Balance": "49657160", + "Fee": "12", + "PublicKey": "ED46FF956B8EEC4BA614B1A6B0B4343D623AE37A891A0461F1F51464CFC3442CF7", + "SigningPubKey": "03D488B5AA26C87CB14BF23D470520F592A0006A916F5FEEAAB48335D8811A2E9F", + "TxnSignature": "304402205E511EE2CA9BEE36F4078C24E8849A87565692938DB6C1998B449EE4A4A7BFF402201B9B78A1CE11522CCEB973FFA0BC55D8F561657A5383331EAADC6F0D67F2C347", + "Signature": "55C8169517E6353A7168B4F6BE202C0C4D0828CBC3556D4576A3FF67D117F678AEC624FC98417460FA172F313FD920030C8C50B38542BD8744F578A568FB730B", + "Account": "rK6g2UYc4GpQH8DYdPG7wywyQbxkJpQTTN", + "CredentialIDs": [ + "7B685088D546B9E8905D26206F452BB2F44D9A33C9BD9BCF280F7BA39015A955", + "8B685088D546B9E8905D26206F452BB2F44D9A33C9BD9BCF280F7BA39015A956" + ] + }, + "meta": { + "TransactionIndex": 10, + "AffectedNodes": [ + { + "ModifiedNode": { + "LedgerEntryType": "AccountRoot", + "PreviousTxnLgrSeq": 37563014, + "PreviousTxnID": "6540E08162D034074B36A130C3683147C5BAC4E081138A97AABC4425ED264DDB", + "LedgerIndex": "1779FCFCF53ACAF81150E49FCDA32AE74B3DB67FCA77C054E0DEA3DEC4EFE053", + "PreviousFields": { + "Sequence": 1103, + "Balance": "284620533" + }, + "FinalFields": { + "Flags": 0, + "Sequence": 1104, + "OwnerCount": 50, + "Balance": "284630521", + "Account": "rK6g2UYc4GpQH8DYdPG7wywyQbxkJpQTTN" + } + } + }, + { + "ModifiedNode": { + "LedgerEntryType": "PayChannel", + "PreviousTxnLgrSeq": 37539781, + "PreviousTxnID": "9CB070C9F40DB0D50D70E1EB1E4C69D8B48DBDDDE06263711D01595F68722936", + "LedgerIndex": "50107651E7163E294CE0EAD8A20BF7CC046304480FCC9C74A49FFAB3F46FB98E", + "PreviousFields": { + "Balance": "49647160" + }, + "FinalFields": { + "Flags": 0, + "SourceTag": 1002539517, + "SettleDelay": 3600, + "OwnerNode": "0000000000000001", + "Amount": "70000000", + "Balance": "49657160", + "PublicKey": "ED46FF956B8EEC4BA614B1A6B0B4343D623AE37A891A0461F1F51464CFC3442CF7", + "Account": "rnNzy3iPc7gPEAJbAdXwxY1UTBamBqTYhR", + "Destination": "rK6g2UYc4GpQH8DYdPG7wywyQbxkJpQTTN" + } + } + } + ], + "TransactionResult": "tesSUCCESS" + } +} diff --git a/src/containers/shared/components/Transaction/PaymentChannelClaim/types.ts b/src/containers/shared/components/Transaction/PaymentChannelClaim/types.ts index ef16c8fc0..41ad0d64d 100644 --- a/src/containers/shared/components/Transaction/PaymentChannelClaim/types.ts +++ b/src/containers/shared/components/Transaction/PaymentChannelClaim/types.ts @@ -11,4 +11,5 @@ export interface PaymentChannelClaimInstructions { renew?: boolean close?: boolean deleted?: boolean + credentialIDs?: string[] }