+
+
{getTransactionIcon(tx, address)}
-
- {tx.data.amount.map((amt, index) => {
- const assetInfo = denomToAsset(env.chain, amt.denom);
- let metadata = metadatas?.metadatas.find(m => m.base === amt.denom);
-
- if (amt.denom.startsWith('ibc/')) {
- if (assetInfo) {
- metadata = {
- description: assetInfo?.description ?? '',
- denom_units:
- assetInfo?.denom_units?.map(unit => ({
- ...unit,
- aliases: unit.aliases || [],
- })) ?? [],
- base: assetInfo?.base ?? '',
- display: assetInfo?.display ?? '',
- name: assetInfo?.name ?? '',
- symbol: assetInfo?.symbol ?? '',
- uri: assetInfo?.logo_URIs?.svg ?? assetInfo?.logo_URIs?.png ?? '',
- uri_hash: assetInfo?.logo_URIs?.svg ?? assetInfo?.logo_URIs?.png ?? '',
- };
- } else {
- // assetInfo is undefined
- metadata = {
- description: '',
- denom_units: [],
- base: '',
- display: '',
- name: '',
- symbol: '',
- uri: '',
- uri_hash: '',
- };
- }
- }
-
- return
;
- })}
-
-
-
- {getTransactionMessage(tx, address)}
-
-
- {tx.data.amount.map((amt, index) => {
- const metadata = metadatas?.metadatas.find(m => m.base === amt.denom);
- let display = metadata?.display ?? metadata?.symbol ?? '';
-
- if (amt.denom.startsWith('ibc/')) {
- const assetInfo = denomToAsset(env.chain, amt.denom);
- if (assetInfo?.traces && assetInfo.traces.length > 0) {
- if (assetInfo.traces[0].counterparty?.base_denom) {
- display = assetInfo.traces[0].counterparty.base_denom.slice(1);
- }
- }
- }
-
- return metadata?.display?.startsWith('factory')
- ? metadata?.display?.split('/').pop()?.toUpperCase()
- : display.length > 4
- ? display.slice(0, 4).toUpperCase() + '...'
- : display.toUpperCase();
- })}
-
+
+ {formatDateShort(tx.timestamp)}
+
+
+
+ {getTransactionMessage(tx, address, metadatas?.metadatas)}
+
-
e.stopPropagation()}
- >
- {tx.data.from_address.startsWith('manifest1') ? (
-
- ) : (
-
- {tx.data.from_address}
+ {tx.message_index < 10000 ? (
+ tx.sender === address ? (
+
+ Incl.:{' '}
+ {tx.fee &&
+ formatLargeNumber(
+ Number(shiftDigits(tx.fee.amount?.[0]?.amount, -6))
+ ) +
+ ' ' +
+ formatDenom(tx.fee.amount?.[0]?.denom)}{' '}
+ fee
- )}
-
+ ) : null
+ ) : (
+
+ Fee incl. in proposal #{tx.proposal_ids} execution
+
+ )}
-
-
- {formatDateShort(tx.formatted_date)}
-
-
- {getTransactionPlusMinus(tx, address)}
- {tx.data.amount
- .map(amt => {
- const metadata = metadatas?.metadatas.find(m => m.base === amt.denom);
- const exponent = Number(metadata?.denom_units[1]?.exponent) || 6;
- const amount = Number(shiftDigits(amt.amount, -exponent));
- let baseDenom = formatDenom(amt.denom);
-
- if (amt.denom.startsWith('ibc/')) {
- const assetInfo = denomToAsset(env.chain, amt.denom);
- if (assetInfo?.traces && assetInfo.traces.length > 0) {
- if (assetInfo.traces[0].counterparty?.base_denom) {
- baseDenom = assetInfo.traces[0].counterparty.base_denom.slice(1);
- }
- }
- }
-
- return `${formatLargeNumber(amount)} ${baseDenom.toUpperCase()}`;
- })
- .join(', ')}
-
-
+ {/* Example of placing date/ID on the right side on larger screens:
+
+ Tx ID: {tx.id}
+
+ */}
))}
@@ -337,7 +177,7 @@ export function HistoryBox({
)}
{totalPages > 1 && (
-
+
@@ -392,7 +232,7 @@ export function HistoryBox({
)}
-
+
);
}
diff --git a/components/bank/components/index.ts b/components/bank/components/index.ts
index ea097264..615509d3 100644
--- a/components/bank/components/index.ts
+++ b/components/bank/components/index.ts
@@ -1,13 +1,3 @@
export * from './sendBox';
export * from './tokenList';
export * from './historyBox';
-
-export function formatDenom(denom: string): string {
- const cleanDenom = denom.replace(/^factory\/[^/]+\//, '');
-
- if (cleanDenom.startsWith('u')) {
- return cleanDenom.slice(1).toUpperCase();
- }
-
- return cleanDenom;
-}
diff --git a/components/bank/handlers/bank/index.ts b/components/bank/handlers/bank/index.ts
new file mode 100644
index 00000000..a23f3999
--- /dev/null
+++ b/components/bank/handlers/bank/index.ts
@@ -0,0 +1,2 @@
+export * from './msgSendHandler';
+export * from './msgMultiSendHandler';
diff --git a/components/bank/handlers/bank/msgMultiSendHandler.tsx b/components/bank/handlers/bank/msgMultiSendHandler.tsx
new file mode 100644
index 00000000..b94f92bc
--- /dev/null
+++ b/components/bank/handlers/bank/msgMultiSendHandler.tsx
@@ -0,0 +1,73 @@
+import { MsgMultiSend } from '@liftedinit/manifestjs/dist/codegen/cosmos/bank/v1beta1/tx';
+import { createSenderReceiverHandler } from '@/components/bank/handlers/createSenderReceiverHandler';
+import { registerHandler } from '@/components/bank/handlers/handlerRegistry';
+import { BankIcon } from '@/components/icons/BankIcon';
+import { format } from 'react-string-format';
+import { TruncatedAddressWithCopy } from '@/components/react/addressCopy';
+import { MetadataSDKType } from '@liftedinit/manifestjs/dist/codegen/cosmos/bank/v1beta1/bank';
+import { formatAmount, formatDenom, formatLargeNumber } from '@/utils';
+import React from 'react';
+
+const createMessage = (
+ template: string,
+ amount: string,
+ denom: string,
+ numReceivers: number,
+ color: string,
+ metadata?: MetadataSDKType[],
+ sender?: string
+) => {
+ const formattedAmount = formatLargeNumber(formatAmount(amount, denom, metadata));
+ const formattedDenom = formatDenom(denom);
+ const coloredAmount = (
+
+ {formattedAmount} {formattedDenom}
+
+ );
+ const coloredDenom =
{formattedDenom};
+ const message = format(
+ template,
+ coloredAmount,
+ numReceivers,
+ coloredDenom,
+ sender ?
: 'an unknown address'
+ );
+ return
{message};
+};
+
+export const MsgMultiSendHandler = createSenderReceiverHandler({
+ iconSender: BankIcon,
+ successSender: (tx, _, metadata) => {
+ return createMessage(
+ 'You sent {0} equally divided between {1} addresses',
+ tx.metadata?.inputs?.[0]?.coins?.[0]?.amount,
+ tx.metadata?.inputs?.[0]?.coins?.[0]?.denom,
+ tx.metadata?.outputs?.length,
+ 'red',
+ metadata
+ );
+ },
+ failSender: (tx, _, metadata) => {
+ return createMessage(
+ 'You failed to send {0} equally divided between {1} addresses',
+ tx.metadata?.inputs?.[0]?.coins?.[0]?.amount,
+ tx.metadata?.inputs?.[0]?.coins?.[0]?.denom,
+ tx.metadata?.outputs?.length,
+ 'red',
+ metadata
+ );
+ },
+ successReceiver: (tx, _, metadata) => {
+ return createMessage(
+ 'You received {2} tokens from {3}',
+ tx.metadata?.inputs?.[0]?.coins?.[0]?.amount,
+ tx.metadata?.inputs?.[0]?.coins?.[0]?.denom,
+ tx.metadata?.outputs?.length,
+ 'green',
+ metadata,
+ tx.sender
+ );
+ },
+});
+
+registerHandler(MsgMultiSend.typeUrl, MsgMultiSendHandler);
diff --git a/components/bank/handlers/bank/msgSendHandler.tsx b/components/bank/handlers/bank/msgSendHandler.tsx
new file mode 100644
index 00000000..42928442
--- /dev/null
+++ b/components/bank/handlers/bank/msgSendHandler.tsx
@@ -0,0 +1,41 @@
+import { BankIcon } from '@/components/icons/BankIcon';
+import { createSenderReceiverHandler } from '../createSenderReceiverHandler';
+import { registerHandler } from '@/components/bank/handlers/handlerRegistry';
+import { MsgSend } from '@liftedinit/manifestjs/dist/codegen/cosmos/bank/v1beta1/tx';
+import { createTokenMessage } from '@/components';
+
+export const MsgSendHandler = createSenderReceiverHandler({
+ iconSender: BankIcon,
+ successSender: (tx, _, metadata) => {
+ return createTokenMessage(
+ 'You sent {0} to {1}',
+ tx.metadata?.amount?.[0]?.amount,
+ tx.metadata?.amount?.[0]?.denom,
+ tx.metadata?.toAddress,
+ 'red',
+ metadata
+ );
+ },
+ failSender: (tx, _, metadata) => {
+ return createTokenMessage(
+ 'You failed to send {0} to {1}',
+ tx.metadata?.amount?.[0]?.amount,
+ tx.metadata?.amount?.[0]?.denom,
+ tx.metadata?.toAddress,
+ 'red',
+ metadata
+ );
+ },
+ successReceiver: (tx, _, metadata) => {
+ return createTokenMessage(
+ 'You received {0} from {1}',
+ tx.metadata?.amount?.[0]?.amount,
+ tx.metadata?.amount?.[0]?.denom,
+ tx.sender,
+ 'green',
+ metadata
+ );
+ },
+});
+
+registerHandler(MsgSend.typeUrl, MsgSendHandler);
diff --git a/components/bank/handlers/createMessageUtils.tsx b/components/bank/handlers/createMessageUtils.tsx
new file mode 100644
index 00000000..625a414b
--- /dev/null
+++ b/components/bank/handlers/createMessageUtils.tsx
@@ -0,0 +1,46 @@
+import React from 'react';
+import { formatAmount, formatDenom, formatLargeNumber } from '@/utils';
+import { format } from 'react-string-format';
+import { TruncatedAddressWithCopy } from '@/components/react/addressCopy';
+import { MetadataSDKType } from '@liftedinit/manifestjs/dist/codegen/cosmos/bank/v1beta1/bank';
+
+export const createTokenMessage = (
+ template: string,
+ amount: string,
+ denom: string,
+ address: string,
+ color: string,
+ metadata?: MetadataSDKType[]
+) => {
+ const formattedAmount = formatLargeNumber(formatAmount(amount, denom, metadata));
+ const formattedDenom = formatDenom(denom);
+ // coloredAmount is {0}
+ const coloredAmount = (
+
+ {formattedAmount} {formattedDenom}
+
+ );
+ const message = format(
+ template,
+ coloredAmount,
+ address ?
: 'an unknown address'
+ );
+ return
{message};
+};
+
+export const createValidatorMessage = (
+ template: string,
+ validatorAddress: string,
+ sender?: string
+) => {
+ const message = format(
+ template,
+ validatorAddress ? (
+
+ ) : (
+ 'unknown'
+ ),
+ sender ?
: 'an unknown address'
+ );
+ return
{message};
+};
diff --git a/components/bank/handlers/createSenderReceiverHandler.tsx b/components/bank/handlers/createSenderReceiverHandler.tsx
new file mode 100644
index 00000000..a5c5bd53
--- /dev/null
+++ b/components/bank/handlers/createSenderReceiverHandler.tsx
@@ -0,0 +1,56 @@
+import React from 'react';
+import { MetadataSDKType } from '@liftedinit/manifestjs/dist/codegen/cosmos/bank/v1beta1/bank';
+import { QuestionIcon } from '@/components/icons/QuestionIcon';
+import { TxMessage } from '../types';
+
+export function createSenderReceiverHandler({
+ iconSender,
+ iconReceiver,
+ successSender,
+ failSender,
+ successReceiver,
+ failReceiver,
+}: {
+ iconSender: React.ComponentType;
+ iconReceiver?: React.ComponentType;
+ successSender:
+ | string
+ | ((tx: TxMessage, address: string, metadata?: MetadataSDKType[]) => React.ReactNode);
+ failSender:
+ | string
+ | ((tx: TxMessage, address: string, metadata?: MetadataSDKType[]) => React.ReactNode);
+ successReceiver:
+ | string
+ | ((tx: TxMessage, address: string, metadata?: MetadataSDKType[]) => React.ReactNode);
+ failReceiver?: string | ((tx: TxMessage, address: string) => React.ReactNode);
+}) {
+ return (tx: TxMessage, address: string, metadata?: MetadataSDKType[]) => {
+ const isSender = tx.sender === address;
+ const hasError = !!tx.error;
+
+ iconSender = iconSender ?? QuestionIcon;
+ iconReceiver = iconReceiver ?? iconSender ?? QuestionIcon;
+
+ const resolveMessage = (
+ msg:
+ | React.ReactNode
+ | ((tx: TxMessage, address: string, metadata?: MetadataSDKType[]) => React.ReactNode)
+ ) => (typeof msg === 'function' ? msg(tx, address, metadata) : msg);
+
+ const successSenderMsg = resolveMessage(successSender);
+ const failSenderMsg = resolveMessage(failSender);
+ const successReceiverMsg = resolveMessage(successReceiver);
+ const failReceiverMsg = resolveMessage(failReceiver ?? 'Anomaly detected');
+
+ return {
+ icon: isSender ? iconSender : iconReceiver,
+ message: hasError
+ ? isSender
+ ? failSenderMsg
+ : failReceiverMsg
+ : isSender
+ ? successSenderMsg
+ : successReceiverMsg,
+ };
+ };
+}
diff --git a/components/bank/handlers/defaultHandler.tsx b/components/bank/handlers/defaultHandler.tsx
new file mode 100644
index 00000000..339be9d7
--- /dev/null
+++ b/components/bank/handlers/defaultHandler.tsx
@@ -0,0 +1,9 @@
+import { createSenderReceiverHandler } from '@/components/bank/handlers/createSenderReceiverHandler';
+import { QuestionIcon } from '@/components/icons/QuestionIcon';
+
+export const DefaultHandler = createSenderReceiverHandler({
+ iconSender: QuestionIcon,
+ successSender: 'Unknown transaction type',
+ failSender: 'Unknown transaction type',
+ successReceiver: 'Unknown transaction type',
+});
diff --git a/components/bank/handlers/group/index.ts b/components/bank/handlers/group/index.ts
new file mode 100644
index 00000000..eb34735c
--- /dev/null
+++ b/components/bank/handlers/group/index.ts
@@ -0,0 +1,14 @@
+export * from './msgExecHandler';
+export * from './msgCreateGroupWithPolicyHandler';
+export * from './msgSubmitProposalHandler';
+export * from './msgVoteHandler';
+export * from './msgWithdrawProposalHandler';
+export * from './msgUpdateGroupMetadataHandler';
+export * from './msgUpdateGroupPolicyMetadataHandler';
+export * from './msgUpdateGroupPolicyDecisionPolicyHandler';
+export * from './msgLeaveGroupHandler';
+export * from './msgUpdateGroupMembersHandler';
+export * from './msgCreateGroupHandler';
+export * from './msgCreateGroupPolicyHandler';
+export * from './msgUpdateGroupAdminHandler';
+export * from './msgUpdateGroupPolicyAdminHandler';
diff --git a/components/bank/handlers/group/metadata.ts b/components/bank/handlers/group/metadata.ts
new file mode 100644
index 00000000..896a0b74
--- /dev/null
+++ b/components/bank/handlers/group/metadata.ts
@@ -0,0 +1,29 @@
+import { truncateString } from '@/utils';
+import { ThresholdDecisionPolicy } from '@liftedinit/manifestjs/dist/codegen/cosmos/group/v1/types';
+import { PercentageDecisionPolicy } from 'cosmjs-types/cosmos/group/v1/types';
+
+export function getGroupTitle(metadata: string): string | undefined {
+ let title = '';
+
+ try {
+ const parsed = JSON.parse(metadata);
+ title = parsed.title || title;
+ } catch (e) {}
+
+ if (title === '') {
+ return undefined;
+ }
+
+ return truncateString(title, 24);
+}
+
+export function getGroupPolicy(policyType: string): string {
+ switch (policyType) {
+ case ThresholdDecisionPolicy.typeUrl:
+ return 'threshold';
+ case PercentageDecisionPolicy.typeUrl:
+ return 'percentage';
+ default:
+ return 'unknown';
+ }
+}
diff --git a/components/bank/handlers/group/msgCreateGroupHandler.tsx b/components/bank/handlers/group/msgCreateGroupHandler.tsx
new file mode 100644
index 00000000..32f11518
--- /dev/null
+++ b/components/bank/handlers/group/msgCreateGroupHandler.tsx
@@ -0,0 +1,32 @@
+import React from 'react';
+import { createSenderReceiverHandler } from '@/components/bank/handlers/createSenderReceiverHandler';
+import { GroupsIcon } from '@/components/icons/GroupsIcon';
+import { format } from 'react-string-format';
+import { MsgCreateGroup } from '@liftedinit/manifestjs/dist/codegen/cosmos/group/v1/tx';
+import { registerHandler } from '@/components/bank/handlers/handlerRegistry';
+
+const createMessage = (template: string, numMembers: number) => {
+ const message = format(template, numMembers);
+ return
{message};
+};
+
+export const MsgCreateGroupHandler = createSenderReceiverHandler({
+ iconSender: GroupsIcon,
+ successSender: tx => {
+ return createMessage('You created a group with {0} members', tx.metadata?.members?.length);
+ },
+ failSender: tx => {
+ return createMessage(
+ 'You failed to create a group with {0} members',
+ tx.metadata?.members?.length
+ );
+ },
+ successReceiver: tx => {
+ return createMessage(
+ 'You were added to a group with {0} members',
+ tx.metadata?.members?.length
+ );
+ },
+});
+
+registerHandler(MsgCreateGroup.typeUrl, MsgCreateGroupHandler);
diff --git a/components/bank/handlers/group/msgCreateGroupPolicyHandler.tsx b/components/bank/handlers/group/msgCreateGroupPolicyHandler.tsx
new file mode 100644
index 00000000..9d73d712
--- /dev/null
+++ b/components/bank/handlers/group/msgCreateGroupPolicyHandler.tsx
@@ -0,0 +1,39 @@
+import { registerHandler } from '@/components/bank/handlers/handlerRegistry';
+import { GroupsIcon } from '@/components/icons/GroupsIcon';
+import { MsgCreateGroupPolicy } from '@liftedinit/manifestjs/dist/codegen/cosmos/group/v1/tx';
+import { createSenderReceiverHandler } from '@/components/bank/handlers/createSenderReceiverHandler';
+import { format } from 'react-string-format';
+import { getGroupPolicy } from '@/components/bank/handlers/group/metadata';
+
+const createMessage = (template: string, policyType: string, groupId: string) => {
+ const policy = getGroupPolicy(policyType);
+ const message = format(template, policy, groupId);
+ return
{message};
+};
+
+export const MsgCreateGroupPolicyHandler = createSenderReceiverHandler({
+ iconSender: GroupsIcon,
+ successSender: tx => {
+ return createMessage(
+ 'You created a {0} decision policy for group #{1}',
+ tx.metadata?.decisionPolicy?.['@type'],
+ tx.metadata?.groupId
+ );
+ },
+ failSender: tx => {
+ return createMessage(
+ 'You failed to create a {0} decision policy for group #{1}',
+ tx.metadata?.decisionPolicy?.['@type'],
+ tx.metadata?.groupId
+ );
+ },
+ successReceiver: tx => {
+ return createMessage(
+ 'A {0} decision policy was created for group #{1}',
+ tx.metadata?.decisionPolicy?.['@type'],
+ tx.metadata?.groupId
+ );
+ },
+});
+
+registerHandler(MsgCreateGroupPolicy.typeUrl, MsgCreateGroupPolicyHandler);
diff --git a/components/bank/handlers/group/msgCreateGroupWithPolicyHandler.tsx b/components/bank/handlers/group/msgCreateGroupWithPolicyHandler.tsx
new file mode 100644
index 00000000..b27c8edd
--- /dev/null
+++ b/components/bank/handlers/group/msgCreateGroupWithPolicyHandler.tsx
@@ -0,0 +1,24 @@
+import { GroupsIcon } from '@/components/icons/GroupsIcon';
+import { createSenderReceiverHandler } from '../createSenderReceiverHandler';
+import { registerHandler } from '@/components/bank/handlers/handlerRegistry';
+import { MsgCreateGroupWithPolicy } from '@liftedinit/manifestjs/dist/codegen/cosmos/group/v1/tx';
+import { getGroupTitle } from '@/components/bank/handlers/group/metadata';
+import { format } from 'react-string-format';
+
+const createMessage = (template: string, metadata: any) => {
+ const title = getGroupTitle(metadata);
+ const named = title ? `named: ${title}` : 'with an unknown name';
+ const message = format(template, named);
+ return
{message};
+};
+
+export const MsgCreateGroupWithPolicyHandler = createSenderReceiverHandler({
+ iconSender: GroupsIcon,
+ successSender: tx => createMessage('You created a new group {0}', tx.metadata?.groupMetadata),
+ failSender: tx =>
+ createMessage('You failed to create a new group {0}', tx.metadata?.groupMetadata),
+ successReceiver: tx =>
+ createMessage('You were mentioned in a new group {0}', tx.metadata?.groupMetadata),
+});
+
+registerHandler(MsgCreateGroupWithPolicy.typeUrl, MsgCreateGroupWithPolicyHandler);
diff --git a/components/bank/handlers/group/msgExecHandler.tsx b/components/bank/handlers/group/msgExecHandler.tsx
new file mode 100644
index 00000000..cd594539
--- /dev/null
+++ b/components/bank/handlers/group/msgExecHandler.tsx
@@ -0,0 +1,27 @@
+import { GroupsIcon } from '@/components/icons/GroupsIcon';
+import { MsgExec } from '@liftedinit/manifestjs/dist/codegen/cosmos/group/v1/tx';
+import { createSenderReceiverHandler } from '../createSenderReceiverHandler';
+import { registerHandler } from '../handlerRegistry';
+import { TruncatedAddressWithCopy } from '@/components/react/addressCopy';
+import { format } from 'react-string-format';
+
+const createMessage = (template: string, id: string, sender?: string) => {
+ const message = format(
+ template,
+ id ?? 'unknown',
+ sender ?
: 'an unknown address'
+ );
+ return
{message};
+};
+
+export const MsgExecHandler = createSenderReceiverHandler({
+ iconSender: GroupsIcon,
+ successSender: tx => createMessage('You executed proposal #{0}', tx.proposal_ids?.[0]), // TODO Link to proposal
+ failSender: tx => createMessage('You failed to execute proposal #{0}', tx.proposal_ids?.[0]), // TODO Link to proposal
+ successReceiver: tx =>
+ createMessage('Proposal #{0} was executed by {1}', tx.proposal_ids?.[0], tx.sender), // TODO Link to proposal
+ failReceiver: tx =>
+ createMessage('Proposal #{0} failed to be executed by {1}', tx.proposal_ids?.[0], tx.sender), // TODO Link to proposal
+});
+
+registerHandler(MsgExec.typeUrl, MsgExecHandler);
diff --git a/components/bank/handlers/group/msgLeaveGroupHandler.tsx b/components/bank/handlers/group/msgLeaveGroupHandler.tsx
new file mode 100644
index 00000000..10730f2d
--- /dev/null
+++ b/components/bank/handlers/group/msgLeaveGroupHandler.tsx
@@ -0,0 +1,13 @@
+import { GroupsIcon } from '@/components/icons/GroupsIcon';
+import { createSenderReceiverHandler } from '../createSenderReceiverHandler';
+import { registerHandler } from '@/components/bank/handlers/handlerRegistry';
+import { MsgLeaveGroup } from '@liftedinit/manifestjs/dist/codegen/cosmos/group/v1/tx';
+
+export const MsgLeaveGroupHandler = createSenderReceiverHandler({
+ iconSender: GroupsIcon,
+ successSender: tx => `You left group #${tx.metadata?.groupId}`,
+ failSender: tx => `You failed to leave group #${tx.metadata?.groupId}`,
+ successReceiver: tx => `Group #${tx.metadata?.groupId} had a member leave`,
+});
+
+registerHandler(MsgLeaveGroup.typeUrl, MsgLeaveGroupHandler);
diff --git a/components/bank/handlers/group/msgSubmitProposalHandler.tsx b/components/bank/handlers/group/msgSubmitProposalHandler.tsx
new file mode 100644
index 00000000..2ccc2f63
--- /dev/null
+++ b/components/bank/handlers/group/msgSubmitProposalHandler.tsx
@@ -0,0 +1,22 @@
+import { GroupsIcon } from '@/components/icons/GroupsIcon';
+import { createSenderReceiverHandler } from '../createSenderReceiverHandler';
+import { registerHandler } from '@/components/bank/handlers/handlerRegistry';
+import { MsgSubmitProposal } from '@liftedinit/manifestjs/dist/codegen/cosmos/group/v1/tx';
+import { TruncatedAddressWithCopy } from '@/components/react/addressCopy';
+import { format } from 'react-string-format';
+
+const createMessage = (template: string, ids: string, sender: string) => {
+ const message = format(template, ids,
);
+ return
{message};
+};
+
+export const MsgSubmitProposalHandler = createSenderReceiverHandler({
+ iconSender: GroupsIcon,
+ successSender: tx =>
+ createMessage('You submitted proposal #{0} to {1}', tx.proposal_ids?.[0], tx.sender), // TODO Link to proposal
+ failSender: 'You failed to submit a proposal',
+ successReceiver: tx =>
+ createMessage('Proposal #{0} was submitted by {1}', tx.proposal_ids?.[0], tx.sender), // TODO Link to proposal
+});
+
+registerHandler(MsgSubmitProposal.typeUrl, MsgSubmitProposalHandler);
diff --git a/components/bank/handlers/group/msgUpdateGroupAdminHandler.tsx b/components/bank/handlers/group/msgUpdateGroupAdminHandler.tsx
new file mode 100644
index 00000000..814b17d1
--- /dev/null
+++ b/components/bank/handlers/group/msgUpdateGroupAdminHandler.tsx
@@ -0,0 +1,42 @@
+import { registerHandler } from '@/components/bank/handlers/handlerRegistry';
+import { MsgUpdateGroupAdmin } from '@liftedinit/manifestjs/dist/codegen/cosmos/group/v1/tx';
+import { format } from 'react-string-format';
+import { createSenderReceiverHandler } from '@/components/bank/handlers/createSenderReceiverHandler';
+import { GroupsIcon } from '@/components/icons/GroupsIcon';
+import { TruncatedAddressWithCopy } from '@/components/react/addressCopy';
+
+const createMessage = (template: string, groupId: number, newAdmin: string) => {
+ const message = format(
+ template,
+ groupId,
+ newAdmin ?
: 'an unknown address'
+ );
+ return
{message};
+};
+
+export const MsgUpdateGroupAdminHandler = createSenderReceiverHandler({
+ iconSender: GroupsIcon,
+ successSender: tx => {
+ return createMessage(
+ 'You updated the administrator of group #{0} to {1}',
+ tx.metadata?.groupId,
+ tx.metadata?.newAdmin
+ );
+ },
+ failSender: tx => {
+ return createMessage(
+ 'You failed to update the administrator of group #{0} to {1}',
+ tx.metadata?.groupId,
+ tx.metadata?.newAdmin
+ );
+ },
+ successReceiver: tx => {
+ return createMessage(
+ 'You were made administrator of group #{0}',
+ tx.metadata?.groupId,
+ tx.metadata?.newAdmin
+ );
+ },
+});
+
+registerHandler(MsgUpdateGroupAdmin.typeUrl, MsgUpdateGroupAdminHandler);
diff --git a/components/bank/handlers/group/msgUpdateGroupMembersHandler.tsx b/components/bank/handlers/group/msgUpdateGroupMembersHandler.tsx
new file mode 100644
index 00000000..ed71092f
--- /dev/null
+++ b/components/bank/handlers/group/msgUpdateGroupMembersHandler.tsx
@@ -0,0 +1,19 @@
+import { GroupsIcon } from '@/components/icons/GroupsIcon';
+import { createSenderReceiverHandler } from '../createSenderReceiverHandler';
+import { registerHandler } from '@/components/bank/handlers/handlerRegistry';
+import { MsgUpdateGroupMembers } from '@liftedinit/manifestjs/dist/codegen/cosmos/group/v1/tx';
+import { format } from 'react-string-format';
+
+const createMessage = (template: string, groupId: string) => {
+ const message = format(template, groupId);
+ return
{message};
+};
+
+export const MsgUpdateGroupMembersHandler = createSenderReceiverHandler({
+ iconSender: GroupsIcon,
+ successSender: tx => createMessage('You updated the members of group #{0}', tx.metadata?.groupId),
+ failSender: 'You failed to update group members',
+ successReceiver: tx => createMessage('Group #{0} had its members updated', tx.metadata.groupId),
+});
+
+registerHandler(MsgUpdateGroupMembers.typeUrl, MsgUpdateGroupMembersHandler);
diff --git a/components/bank/handlers/group/msgUpdateGroupMetadataHandler.tsx b/components/bank/handlers/group/msgUpdateGroupMetadataHandler.tsx
new file mode 100644
index 00000000..275e2fc2
--- /dev/null
+++ b/components/bank/handlers/group/msgUpdateGroupMetadataHandler.tsx
@@ -0,0 +1,13 @@
+import { GroupsIcon } from '@/components/icons/GroupsIcon';
+import { createSenderReceiverHandler } from '../createSenderReceiverHandler';
+import { registerHandler } from '@/components/bank/handlers/handlerRegistry';
+import { MsgUpdateGroupMetadata } from '@liftedinit/manifestjs/dist/codegen/cosmos/group/v1/tx';
+
+export const MsgUpdateGroupMetadataHandler = createSenderReceiverHandler({
+ iconSender: GroupsIcon,
+ successSender: tx => <>You updated the metadata of group #{tx.metadata?.groupId}>,
+ failSender: tx => <>You failed to update the metadata of group #{tx.metadata?.groupId}>,
+ successReceiver: tx => <>Group #{tx.metadata?.groupId} had its metadata updated>,
+});
+
+registerHandler(MsgUpdateGroupMetadata.typeUrl, MsgUpdateGroupMetadataHandler);
diff --git a/components/bank/handlers/group/msgUpdateGroupPolicyAdminHandler.tsx b/components/bank/handlers/group/msgUpdateGroupPolicyAdminHandler.tsx
new file mode 100644
index 00000000..38e80c9c
--- /dev/null
+++ b/components/bank/handlers/group/msgUpdateGroupPolicyAdminHandler.tsx
@@ -0,0 +1,53 @@
+import { registerHandler } from '@/components/bank/handlers/handlerRegistry';
+import { GroupsIcon } from '@/components/icons/GroupsIcon';
+import { MsgUpdateGroupPolicyAdmin } from '@liftedinit/manifestjs/dist/codegen/cosmos/group/v1/tx';
+import { createSenderReceiverHandler } from '@/components/bank/handlers/createSenderReceiverHandler';
+import { format } from 'react-string-format';
+import { TruncatedAddressWithCopy } from '@/components/react/addressCopy';
+
+const createMessage = (
+ template: string,
+ groupPolicyAddr: string,
+ newAdmin: string,
+ sender?: string
+) => {
+ const message = format(
+ template,
+ groupPolicyAddr ? (
+
+ ) : (
+ 'an unknown address'
+ ),
+ newAdmin ?
: 'an unknown address',
+ sender ?
: 'an unknown address'
+ );
+ return
{message};
+};
+
+export const MsgUpdateGroupPolicyAdminHandler = createSenderReceiverHandler({
+ iconSender: GroupsIcon,
+ successSender: tx => {
+ return createMessage(
+ 'You updated the group policy {0} administrator to {1}',
+ tx.metadata?.groupPolicyAddress,
+ tx.metadata?.newAdmin
+ );
+ },
+ failSender: tx => {
+ return createMessage(
+ 'You failed to update the group policy {0} administrator to {1}',
+ tx.metadata?.groupPolicyAddress,
+ tx.metadata?.newAdmin
+ );
+ },
+ successReceiver: tx => {
+ return createMessage(
+ 'You were made administrator of group policy {0} by {2}',
+ tx.metadata?.groupPolicyAddress,
+ tx.metadata?.newAdmin,
+ tx.sender
+ );
+ },
+});
+
+registerHandler(MsgUpdateGroupPolicyAdmin.typeUrl, MsgUpdateGroupPolicyAdminHandler);
diff --git a/components/bank/handlers/group/msgUpdateGroupPolicyDecisionPolicyHandler.tsx b/components/bank/handlers/group/msgUpdateGroupPolicyDecisionPolicyHandler.tsx
new file mode 100644
index 00000000..ae5a9fa5
--- /dev/null
+++ b/components/bank/handlers/group/msgUpdateGroupPolicyDecisionPolicyHandler.tsx
@@ -0,0 +1,32 @@
+import { GroupsIcon } from '@/components/icons/GroupsIcon';
+import { createSenderReceiverHandler } from '../createSenderReceiverHandler';
+import { registerHandler } from '@/components/bank/handlers/handlerRegistry';
+import { MsgUpdateGroupPolicyDecisionPolicy } from '@liftedinit/manifestjs/dist/codegen/cosmos/group/v1/tx';
+import { TruncatedAddressWithCopy } from '@/components/react/addressCopy';
+import { format } from 'react-string-format';
+
+const createMessage = (template: string, policyAddress: string) => {
+ const message = format(
+ template,
+ policyAddress ?
: 'unknown'
+ );
+ return
{message};
+};
+
+export const MsgUpdateGroupPolicyDecisionPolicyHandler = createSenderReceiverHandler({
+ iconSender: GroupsIcon,
+ successSender: tx =>
+ createMessage('You updated the decision policy of group {0}', tx.metadata?.groupPolicyAddress),
+ failSender: tx =>
+ createMessage(
+ 'You failed to update the decision policy of group {0}',
+ tx.metadata?.groupPolicyAddress
+ ),
+ successReceiver: tx =>
+ createMessage('Group {0} had its decision policy updated', tx.metadata.groupPolicyAddress),
+});
+
+registerHandler(
+ MsgUpdateGroupPolicyDecisionPolicy.typeUrl,
+ MsgUpdateGroupPolicyDecisionPolicyHandler
+);
diff --git a/components/bank/handlers/group/msgUpdateGroupPolicyMetadataHandler.tsx b/components/bank/handlers/group/msgUpdateGroupPolicyMetadataHandler.tsx
new file mode 100644
index 00000000..9d3372fc
--- /dev/null
+++ b/components/bank/handlers/group/msgUpdateGroupPolicyMetadataHandler.tsx
@@ -0,0 +1,27 @@
+import { GroupsIcon } from '@/components/icons/GroupsIcon';
+import { createSenderReceiverHandler } from '../createSenderReceiverHandler';
+import { registerHandler } from '@/components/bank/handlers/handlerRegistry';
+import { MsgUpdateGroupPolicyMetadata } from '@liftedinit/manifestjs/dist/codegen/cosmos/group/v1/tx';
+import { TruncatedAddressWithCopy } from '@/components/react/addressCopy';
+
+const createMessage = (prefix: string, policyAddress: string, suffix?: string) => {
+ return (
+
+ {prefix}{' '}
+ {policyAddress ? : 'unknown'}{' '}
+ {suffix}
+
+ );
+};
+
+export const MsgUpdateGroupPolicyMetadataHandler = createSenderReceiverHandler({
+ iconSender: GroupsIcon,
+ successSender: tx =>
+ createMessage('You updated the policy metadata of group', tx.metadata?.groupPolicyAddress),
+ failSender: tx =>
+ createMessage('You failed to update policy metadata of group', tx.metadata?.groupPolicyAddress),
+ successReceiver: tx =>
+ createMessage('Group', tx.metadata.groupPolicyAddress, 'had its policy metadata updated'),
+});
+
+registerHandler(MsgUpdateGroupPolicyMetadata.typeUrl, MsgUpdateGroupPolicyMetadataHandler);
diff --git a/components/bank/handlers/group/msgVoteHandler.tsx b/components/bank/handlers/group/msgVoteHandler.tsx
new file mode 100644
index 00000000..9172465b
--- /dev/null
+++ b/components/bank/handlers/group/msgVoteHandler.tsx
@@ -0,0 +1,38 @@
+import { GroupsIcon } from '@/components/icons/GroupsIcon';
+import { formatVote } from '@/utils';
+import { createSenderReceiverHandler } from '../createSenderReceiverHandler';
+import { registerHandler } from '@/components/bank/handlers/handlerRegistry';
+import { MsgVote } from '@liftedinit/manifestjs/dist/codegen/cosmos/group/v1/tx';
+import { TruncatedAddressWithCopy } from '@/components/react/addressCopy';
+import { format } from 'react-string-format';
+
+const createMessage = (template: string, vote: string, ids: string, sender?: string) => {
+ const message = format(
+ template,
+ formatVote(vote),
+ ids,
+ sender ?
: 'an unknown address'
+ );
+ return
{message};
+};
+
+export const MsgVoteHandler = createSenderReceiverHandler({
+ iconSender: GroupsIcon,
+ successSender: tx =>
+ createMessage('You voted {0} on proposal #{1}', tx.metadata?.option, tx.proposal_ids?.[0]), // TODO Link to proposal
+ failSender: tx =>
+ createMessage(
+ 'You failed to vote {0} on proposal #{1}',
+ tx.metadata?.option,
+ tx.proposal_ids?.[0]
+ ), // TODO Link to proposal
+ successReceiver: tx =>
+ createMessage(
+ 'Proposal #{1} was voted on {0} by {2}',
+ tx.metadata?.option,
+ tx.proposal_ids?.[0],
+ tx.sender
+ ), // TODO Link to proposal
+});
+
+registerHandler(MsgVote.typeUrl, MsgVoteHandler);
diff --git a/components/bank/handlers/group/msgWithdrawProposalHandler.tsx b/components/bank/handlers/group/msgWithdrawProposalHandler.tsx
new file mode 100644
index 00000000..6e0ae86d
--- /dev/null
+++ b/components/bank/handlers/group/msgWithdrawProposalHandler.tsx
@@ -0,0 +1,25 @@
+import { GroupsIcon } from '@/components/icons/GroupsIcon';
+import { createSenderReceiverHandler } from '../createSenderReceiverHandler';
+import { registerHandler } from '@/components/bank/handlers/handlerRegistry';
+import { MsgWithdrawProposal } from '@liftedinit/manifestjs/dist/codegen/cosmos/group/v1/tx';
+import { TruncatedAddressWithCopy } from '@/components/react/addressCopy';
+import { format } from 'react-string-format';
+
+const createMessage = (template: string, ids: string, sender?: string) => {
+ const message = format(
+ template,
+ ids,
+ sender ?
: 'an unknown address'
+ );
+ return
{message};
+};
+
+export const MsgWithdrawProposalHandler = createSenderReceiverHandler({
+ iconSender: GroupsIcon,
+ successSender: tx => createMessage('You withdrew proposal #{0}', tx.proposal_ids?.[0]), // TODO Link to proposal
+ failSender: tx => createMessage('You failed to withdraw proposal #{0}', tx.proposal_ids?.[0]), // TODO Link to proposal
+ successReceiver: tx =>
+ createMessage('Proposal #{0} was withdrawn by {1}', tx.proposal_ids?.[0], tx.sender), // TODO Link to proposal
+});
+
+registerHandler(MsgWithdrawProposal.typeUrl, MsgWithdrawProposalHandler);
diff --git a/components/bank/handlers/handlerRegistry.ts b/components/bank/handlers/handlerRegistry.ts
new file mode 100644
index 00000000..51f0abdb
--- /dev/null
+++ b/components/bank/handlers/handlerRegistry.ts
@@ -0,0 +1,24 @@
+import React from 'react';
+import { QuestionIcon } from '@/components/icons';
+import { MetadataSDKType } from '@liftedinit/manifestjs/dist/codegen/cosmos/bank/v1beta1/bank';
+import { TxMessage } from '../types';
+import { DefaultHandler } from '@/components/bank/handlers/defaultHandler';
+
+export type Handler = (
+ tx: TxMessage,
+ address: string,
+ metadata?: MetadataSDKType[]
+) => {
+ icon: React.ComponentType;
+ message: React.ReactNode;
+};
+
+const handlerRegistry: { [key: string]: Handler } = {};
+
+export function registerHandler(typeUrl: string, handler: Handler) {
+ handlerRegistry[typeUrl] = handler;
+}
+
+export function getHandler(typeUrl: string): Handler {
+ return handlerRegistry[typeUrl] || DefaultHandler;
+}
diff --git a/components/bank/handlers/ibc/index.ts b/components/bank/handlers/ibc/index.ts
new file mode 100644
index 00000000..25511874
--- /dev/null
+++ b/components/bank/handlers/ibc/index.ts
@@ -0,0 +1 @@
+export * from './msgTransferHandler';
diff --git a/components/bank/handlers/ibc/msgTransferHandler.tsx b/components/bank/handlers/ibc/msgTransferHandler.tsx
new file mode 100644
index 00000000..5cf46d06
--- /dev/null
+++ b/components/bank/handlers/ibc/msgTransferHandler.tsx
@@ -0,0 +1,38 @@
+import { TransferIcon } from '@/components/icons/TransferIcon';
+import { createSenderReceiverHandler } from '../createSenderReceiverHandler';
+import { registerHandler } from '@/components/bank/handlers/handlerRegistry';
+import { MsgTransfer } from '@liftedinit/manifestjs/dist/codegen/ibc/applications/transfer/v1/tx';
+import { createTokenMessage } from '@/components';
+
+export const MsgTransferHandler = createSenderReceiverHandler({
+ iconSender: TransferIcon,
+ successSender: (tx, _, metadata) =>
+ createTokenMessage(
+ 'You sent {0} to {1} via IBC',
+ tx.metadata?.token?.amount,
+ tx.metadata?.token?.denom,
+ tx.metadata?.receiver,
+ 'red',
+ metadata
+ ),
+ failSender: (tx, _, metadata) =>
+ createTokenMessage(
+ 'You failed to send {0} to {1} via IBC',
+ tx.metadata?.token?.amount,
+ tx.metadata?.token?.denom,
+ tx.metadata?.receiver,
+ 'red',
+ metadata
+ ),
+ successReceiver: (tx, _, metadata) =>
+ createTokenMessage(
+ 'You received {0} from {1} via IBC',
+ tx.metadata?.token?.amount,
+ tx.metadata?.token?.denom,
+ tx.sender,
+ 'green',
+ metadata
+ ),
+});
+
+registerHandler(MsgTransfer.typeUrl, MsgTransferHandler);
diff --git a/components/bank/handlers/index.ts b/components/bank/handlers/index.ts
new file mode 100644
index 00000000..c14d6474
--- /dev/null
+++ b/components/bank/handlers/index.ts
@@ -0,0 +1,9 @@
+export * from './bank';
+export * from './ibc';
+export * from './tokenfactory';
+export * from './manifest';
+export * from './group';
+export * from './upgrade';
+export * from './poa';
+export { createTokenMessage } from '@/components/bank/handlers/createMessageUtils';
+export { createValidatorMessage } from '@/components/bank/handlers/createMessageUtils';
diff --git a/components/bank/handlers/manifest/index.ts b/components/bank/handlers/manifest/index.ts
new file mode 100644
index 00000000..10f49cde
--- /dev/null
+++ b/components/bank/handlers/manifest/index.ts
@@ -0,0 +1,2 @@
+export * from './msgPayoutHandler';
+export * from './msgBurnHeldBalanceHandler';
diff --git a/components/bank/handlers/manifest/msgBurnHeldBalanceHandler.tsx b/components/bank/handlers/manifest/msgBurnHeldBalanceHandler.tsx
new file mode 100644
index 00000000..23dfd3c9
--- /dev/null
+++ b/components/bank/handlers/manifest/msgBurnHeldBalanceHandler.tsx
@@ -0,0 +1,30 @@
+import { BurnIcon } from '@/components/icons/BurnIcon';
+import { createSenderReceiverHandler } from '../createSenderReceiverHandler';
+import { registerHandler } from '@/components/bank/handlers/handlerRegistry';
+import { MsgBurnHeldBalance } from '@liftedinit/manifestjs/dist/codegen/liftedinit/manifest/v1/tx';
+import { createTokenMessage } from '@/components';
+
+export const MsgBurnHeldBalanceHandler = createSenderReceiverHandler({
+ iconSender: BurnIcon,
+ successSender: (tx, _, metadata) =>
+ createTokenMessage(
+ 'You burned {0} from {1}',
+ tx.metadata?.burnCoins?.[0]?.amount,
+ tx.metadata?.burnCoins?.[0]?.denom,
+ tx.sender,
+ 'red',
+ metadata
+ ),
+ failSender: 'You failed to burn tokens',
+ successReceiver: (tx, _, metadata) =>
+ createTokenMessage(
+ 'You were burned {0} by {1}',
+ tx.metadata?.burnCoins?.[0]?.amount,
+ tx.metadata?.burnCoins?.[0]?.denom,
+ tx.sender,
+ 'red',
+ metadata
+ ),
+});
+
+registerHandler(MsgBurnHeldBalance.typeUrl, MsgBurnHeldBalanceHandler);
diff --git a/components/bank/handlers/manifest/msgPayoutHandler.tsx b/components/bank/handlers/manifest/msgPayoutHandler.tsx
new file mode 100644
index 00000000..8997a56c
--- /dev/null
+++ b/components/bank/handlers/manifest/msgPayoutHandler.tsx
@@ -0,0 +1,45 @@
+import { MintIcon } from '@/components/icons/MintIcon';
+import { createSenderReceiverHandler } from '../createSenderReceiverHandler';
+import { registerHandler } from '@/components/bank/handlers/handlerRegistry';
+import { MsgPayout } from '@liftedinit/manifestjs/dist/codegen/liftedinit/manifest/v1/tx';
+import { TruncatedAddressWithCopy } from '@/components/react/addressCopy';
+import { createTokenMessage } from '@/components';
+import { format } from 'react-string-format';
+
+export const MsgPayoutHandler = createSenderReceiverHandler({
+ iconSender: MintIcon,
+ successSender: (tx, _, metadata) => {
+ return tx.metadata?.payoutPairs?.length > 1
+ ? format('You minted tokens to {0} addresses', tx.metadata?.payoutPairs?.length)
+ : createTokenMessage(
+ 'You minted {0} to {1}',
+ tx.metadata?.payoutPairs?.[0]?.coin?.amount,
+ tx.metadata?.payoutPairs?.[0]?.coin?.denom,
+ tx.metadata?.payoutPairs?.[0]?.address,
+ 'green',
+ metadata
+ );
+ },
+ failSender: 'You failed to mint tokens',
+ successReceiver: (tx, _, metadata) => {
+ return tx.metadata?.payoutPairs?.length > 1
+ ? format(
+ 'You were minted tokens by {0}',
+ tx.sender ? (
+
+ ) : (
+ 'an unknown address'
+ )
+ )
+ : createTokenMessage(
+ 'You were minted {0} by {1}',
+ tx.metadata?.payoutPairs?.[0]?.coin?.amount,
+ tx.metadata?.payoutPairs?.[0]?.coin?.denom,
+ tx.sender,
+ 'green',
+ metadata
+ );
+ },
+});
+
+registerHandler(MsgPayout.typeUrl, MsgPayoutHandler);
diff --git a/components/bank/handlers/poa/index.ts b/components/bank/handlers/poa/index.ts
new file mode 100644
index 00000000..48428433
--- /dev/null
+++ b/components/bank/handlers/poa/index.ts
@@ -0,0 +1,4 @@
+export * from './msgSetPowerHandler';
+export * from './msgCreateValidatorHandler';
+export * from './msgRemovePendingValidatorHandler';
+export * from './msgRemoveValidatorHandler';
diff --git a/components/bank/handlers/poa/msgCreateValidatorHandler.tsx b/components/bank/handlers/poa/msgCreateValidatorHandler.tsx
new file mode 100644
index 00000000..919974af
--- /dev/null
+++ b/components/bank/handlers/poa/msgCreateValidatorHandler.tsx
@@ -0,0 +1,21 @@
+import { AdminsIcon } from '@/components/icons/AdminsIcon';
+import { createSenderReceiverHandler } from '../createSenderReceiverHandler';
+import { registerHandler } from '@/components/bank/handlers/handlerRegistry';
+import { MsgCreateValidator } from '@liftedinit/manifestjs/dist/codegen/strangelove_ventures/poa/v1/tx';
+import { createValidatorMessage } from '@/components';
+
+export const MsgCreateValidatorHandler = createSenderReceiverHandler({
+ iconSender: AdminsIcon,
+ successSender: tx =>
+ createValidatorMessage('You created validator {0}', tx.metadata?.validatorAddress),
+ failSender: tx =>
+ createValidatorMessage('You failed to create validator {0}', tx.metadata?.validatorAddress),
+ successReceiver: tx =>
+ createValidatorMessage(
+ 'Validator {0} was created by {1}',
+ tx.metadata.validatorAddress,
+ tx.sender
+ ),
+});
+
+registerHandler(MsgCreateValidator.typeUrl, MsgCreateValidatorHandler);
diff --git a/components/bank/handlers/poa/msgRemovePendingValidatorHandler.tsx b/components/bank/handlers/poa/msgRemovePendingValidatorHandler.tsx
new file mode 100644
index 00000000..ad0cde94
--- /dev/null
+++ b/components/bank/handlers/poa/msgRemovePendingValidatorHandler.tsx
@@ -0,0 +1,24 @@
+import { AdminsIcon } from '@/components/icons/AdminsIcon';
+import { createSenderReceiverHandler } from '../createSenderReceiverHandler';
+import { registerHandler } from '@/components/bank/handlers/handlerRegistry';
+import { MsgRemovePending } from '@liftedinit/manifestjs/dist/codegen/strangelove_ventures/poa/v1/tx';
+import { createValidatorMessage } from '@/components';
+
+export const MsgRemovePendingValidatorHandler = createSenderReceiverHandler({
+ iconSender: AdminsIcon,
+ successSender: tx =>
+ createValidatorMessage('You removed pending validator {0}', tx.metadata?.validatorAddress),
+ failSender: tx =>
+ createValidatorMessage(
+ 'You failed to remove pending validator {0}',
+ tx.metadata?.validatorAddress
+ ),
+ successReceiver: tx =>
+ createValidatorMessage(
+ 'Validator {0} was removed from pending by {1}',
+ tx.metadata?.validatorAddress,
+ tx.sender
+ ),
+});
+
+registerHandler(MsgRemovePending.typeUrl, MsgRemovePendingValidatorHandler);
diff --git a/components/bank/handlers/poa/msgRemoveValidatorHandler.tsx b/components/bank/handlers/poa/msgRemoveValidatorHandler.tsx
new file mode 100644
index 00000000..47f2769c
--- /dev/null
+++ b/components/bank/handlers/poa/msgRemoveValidatorHandler.tsx
@@ -0,0 +1,21 @@
+import { AdminsIcon } from '@/components/icons/AdminsIcon';
+import { createSenderReceiverHandler } from '../createSenderReceiverHandler';
+import { registerHandler } from '@/components/bank/handlers/handlerRegistry';
+import { MsgRemoveValidator } from '@liftedinit/manifestjs/dist/codegen/strangelove_ventures/poa/v1/tx';
+import { createValidatorMessage } from '@/components';
+
+export const MsgRemoveValidatorHandler = createSenderReceiverHandler({
+ iconSender: AdminsIcon,
+ successSender: tx =>
+ createValidatorMessage('You removed validator {0}', tx.metadata?.validatorAddress),
+ failSender: tx =>
+ createValidatorMessage('You failed to remove validator {0}', tx.metadata?.validatorAddress),
+ successReceiver: tx =>
+ createValidatorMessage(
+ 'Validator {0} was removed by {1}',
+ tx.metadata?.validatorAddress,
+ tx.sender
+ ),
+});
+
+registerHandler(MsgRemoveValidator.typeUrl, MsgRemoveValidatorHandler);
diff --git a/components/bank/handlers/poa/msgSetPowerHandler.tsx b/components/bank/handlers/poa/msgSetPowerHandler.tsx
new file mode 100644
index 00000000..4513d4eb
--- /dev/null
+++ b/components/bank/handlers/poa/msgSetPowerHandler.tsx
@@ -0,0 +1,42 @@
+import { AdminsIcon } from '@/components/icons/AdminsIcon';
+import { createSenderReceiverHandler } from '../createSenderReceiverHandler';
+import { registerHandler } from '@/components/bank/handlers/handlerRegistry';
+import { MsgSetPower } from '@liftedinit/manifestjs/dist/codegen/strangelove_ventures/poa/v1/tx';
+import { TruncatedAddressWithCopy } from '@/components/react/addressCopy';
+import { format } from 'react-string-format';
+
+const createMessage = (template: string, validatorAddress: string, power: number) => {
+ const message = format(
+ template,
+ validatorAddress ? (
+
+ ) : (
+ 'unknown'
+ ),
+ power
+ );
+ return
{message};
+};
+export const MsgSetPowerHandler = createSenderReceiverHandler({
+ iconSender: AdminsIcon,
+ successSender: tx =>
+ createMessage(
+ 'You set the validator {0} power to {1}',
+ tx.metadata?.validatorAddress,
+ tx.metadata?.power
+ ),
+ failSender: tx =>
+ createMessage(
+ 'You failed to set the validator {0} power to {1}',
+ tx.metadata?.validatorAddress,
+ tx.metadata?.power
+ ),
+ successReceiver: tx =>
+ createMessage(
+ 'Validator {0} had its power set to {1}',
+ tx.metadata?.validatorAddress,
+ tx.metadata?.power
+ ),
+});
+
+registerHandler(MsgSetPower.typeUrl, MsgSetPowerHandler);
diff --git a/components/bank/handlers/tokenfactory/index.ts b/components/bank/handlers/tokenfactory/index.ts
new file mode 100644
index 00000000..5397f761
--- /dev/null
+++ b/components/bank/handlers/tokenfactory/index.ts
@@ -0,0 +1,5 @@
+export * from './msgMintHandler';
+export * from './msgBurnHandler';
+export * from './msgChangeAdminHandler';
+export * from './msgCreateDenomHandler';
+export * from './msgSetDenomMetadataHandler';
diff --git a/components/bank/handlers/tokenfactory/msgBurnHandler.tsx b/components/bank/handlers/tokenfactory/msgBurnHandler.tsx
new file mode 100644
index 00000000..d6b255bd
--- /dev/null
+++ b/components/bank/handlers/tokenfactory/msgBurnHandler.tsx
@@ -0,0 +1,38 @@
+import { FactoryIcon } from '@/components/icons/FactoryIcon';
+import { createSenderReceiverHandler } from '../createSenderReceiverHandler';
+import { registerHandler } from '@/components/bank/handlers/handlerRegistry';
+import { MsgBurn } from '@liftedinit/manifestjs/dist/codegen/osmosis/tokenfactory/v1beta1/tx';
+import { createTokenMessage } from '@/components';
+
+export const MsgBurnHandler = createSenderReceiverHandler({
+ iconSender: FactoryIcon,
+ successSender: (tx, _, metadata) =>
+ createTokenMessage(
+ 'You burned {0} from {1}',
+ tx.metadata?.amount?.amount,
+ tx.metadata?.amount?.denom,
+ tx.metadata?.burnFromAddress,
+ 'red',
+ metadata
+ ),
+ failSender: (tx, _, metadata) =>
+ createTokenMessage(
+ 'You failed to burn {0} from {1}',
+ tx.metadata?.amount?.amount,
+ tx.metadata?.amount?.denom,
+ tx.metadata?.burnFromAddress,
+ 'red',
+ metadata
+ ),
+ successReceiver: (tx, _, metadata) =>
+ createTokenMessage(
+ 'You were burned {0} by {1}',
+ tx.metadata?.amount?.amount,
+ tx.metadata?.amount?.denom,
+ tx.sender,
+ 'red',
+ metadata
+ ),
+});
+
+registerHandler(MsgBurn.typeUrl, MsgBurnHandler);
diff --git a/components/bank/handlers/tokenfactory/msgChangeAdminHandler.tsx b/components/bank/handlers/tokenfactory/msgChangeAdminHandler.tsx
new file mode 100644
index 00000000..4144e180
--- /dev/null
+++ b/components/bank/handlers/tokenfactory/msgChangeAdminHandler.tsx
@@ -0,0 +1,40 @@
+import { TransferIcon } from '@/components/icons/TransferIcon';
+import { createSenderReceiverHandler } from '../createSenderReceiverHandler';
+import { formatDenom } from '@/utils';
+import { registerHandler } from '@/components/bank/handlers/handlerRegistry';
+import { MsgChangeAdmin } from '@liftedinit/manifestjs/dist/codegen/osmosis/tokenfactory/v1beta1/tx';
+import { TruncatedAddressWithCopy } from '@/components/react/addressCopy';
+import { format } from 'react-string-format';
+
+const createMessage = (template: string, newAdmin: string, denom: string) => {
+ const message = format(
+ template,
+ newAdmin ?
: 'unknown',
+ denom ? formatDenom(denom) : 'unknown'
+ );
+ return
{message};
+};
+
+export const MsgChangeAdminHandler = createSenderReceiverHandler({
+ iconSender: TransferIcon,
+ successSender: tx =>
+ createMessage(
+ 'You changed the administrator of the {1} token to {0}',
+ tx.metadata?.newAdmin,
+ tx.metadata?.denom
+ ),
+ failSender: tx =>
+ createMessage(
+ 'You failed to change the administrator of the {1} token to {0}',
+ tx.metadata?.newAdmin,
+ tx.metadata?.denom
+ ),
+ successReceiver: tx =>
+ createMessage(
+ 'The administrator of the {1} token was changed to {0}',
+ tx.metadata?.newAdmin,
+ tx.metadata?.denom
+ ),
+});
+
+registerHandler(MsgChangeAdmin.typeUrl, MsgChangeAdminHandler);
diff --git a/components/bank/handlers/tokenfactory/msgCreateDenomHandler.tsx b/components/bank/handlers/tokenfactory/msgCreateDenomHandler.tsx
new file mode 100644
index 00000000..deaa87cc
--- /dev/null
+++ b/components/bank/handlers/tokenfactory/msgCreateDenomHandler.tsx
@@ -0,0 +1,27 @@
+import { FactoryIcon } from '@/components/icons/FactoryIcon';
+import { createSenderReceiverHandler } from '../createSenderReceiverHandler';
+import { formatDenom } from '@/utils';
+import { registerHandler } from '@/components/bank/handlers/handlerRegistry';
+import { MsgCreateDenom } from '@liftedinit/manifestjs/dist/codegen/osmosis/tokenfactory/v1beta1/tx';
+import { format } from 'react-string-format';
+import { TruncatedAddressWithCopy } from '@/components/react/addressCopy';
+
+const createMessage = (template: string, sender: string, subdenom: string) => {
+ const message = format(
+ template,
+ formatDenom(`factory/${sender}/${subdenom}`),
+ sender ?
: 'an unknown address'
+ );
+ return
{message};
+};
+export const MsgCreateDenomHandler = createSenderReceiverHandler({
+ iconSender: FactoryIcon,
+ successSender: tx =>
+ createMessage('You created the {0} denomination', tx.sender, tx.metadata?.subdenom),
+ failSender: tx =>
+ createMessage('You failed to create the {0} denomination', tx.sender, tx.metadata?.subdenom),
+ successReceiver: tx =>
+ createMessage('The {0} denomination was created by {1}', tx.sender, tx.metadata?.subdenom),
+});
+
+registerHandler(MsgCreateDenom.typeUrl, MsgCreateDenomHandler);
diff --git a/components/bank/handlers/tokenfactory/msgMintHandler.tsx b/components/bank/handlers/tokenfactory/msgMintHandler.tsx
new file mode 100644
index 00000000..11687657
--- /dev/null
+++ b/components/bank/handlers/tokenfactory/msgMintHandler.tsx
@@ -0,0 +1,40 @@
+import { FactoryIcon } from '@/components/icons/FactoryIcon';
+import { formatAmount, formatDenom, formatLargeNumber } from '@/utils';
+import { createSenderReceiverHandler } from '../createSenderReceiverHandler';
+import { registerHandler } from '@/components/bank/handlers/handlerRegistry';
+import { MsgMint } from '@liftedinit/manifestjs/dist/codegen/osmosis/tokenfactory/v1beta1/tx';
+import { TruncatedAddressWithCopy } from '@/components/react/addressCopy';
+import { createTokenMessage } from '@/components';
+
+export const MsgMintHandler = createSenderReceiverHandler({
+ iconSender: FactoryIcon,
+ successSender: (tx, _, metadata) =>
+ createTokenMessage(
+ 'You minted {0} to {1}',
+ tx.metadata?.amount?.amount,
+ tx.metadata?.amount?.denom,
+ tx.metadata?.mintToAddress,
+ 'green',
+ metadata
+ ),
+ failSender: (tx, _, metadata) =>
+ createTokenMessage(
+ 'You failed to mint {0} to {1}',
+ tx.metadata?.amount?.amount,
+ tx.metadata?.amount?.denom,
+ tx.metadata?.mintToAddress,
+ 'red',
+ metadata
+ ),
+ successReceiver: (tx, _, metadata) =>
+ createTokenMessage(
+ 'You were minted {0} from {1}',
+ tx.metadata?.amount?.amount,
+ tx.metadata?.amount?.denom,
+ tx.sender,
+ 'green',
+ metadata
+ ),
+});
+
+registerHandler(MsgMint.typeUrl, MsgMintHandler);
diff --git a/components/bank/handlers/tokenfactory/msgSetDenomMetadataHandler.tsx b/components/bank/handlers/tokenfactory/msgSetDenomMetadataHandler.tsx
new file mode 100644
index 00000000..332215c8
--- /dev/null
+++ b/components/bank/handlers/tokenfactory/msgSetDenomMetadataHandler.tsx
@@ -0,0 +1,21 @@
+import { FactoryIcon } from '@/components/icons/FactoryIcon';
+import { createSenderReceiverHandler } from '../createSenderReceiverHandler';
+import { formatDenom } from '@/utils';
+import { registerHandler } from '@/components/bank/handlers/handlerRegistry';
+import { MsgSetDenomMetadata } from '@liftedinit/manifestjs/dist/codegen/osmosis/tokenfactory/v1beta1/tx';
+import { format } from 'react-string-format';
+
+const createMessage = (template: string, base: string) => {
+ const message = format(template, formatDenom(base));
+ return
{message};
+};
+export const MsgSetDenomMetadataHandler = createSenderReceiverHandler({
+ iconSender: FactoryIcon,
+ successSender: tx => createMessage('You set the metadata of denomination {0}', tx.metadata?.base),
+ failSender: tx =>
+ createMessage('You failed to set the metadata of denomination {0}', tx.metadata?.base),
+ successReceiver: tx =>
+ createMessage('The {0} denomination had its metadata set', tx.metadata?.base),
+});
+
+registerHandler(MsgSetDenomMetadata.typeUrl, MsgSetDenomMetadataHandler);
diff --git a/components/bank/handlers/upgrade/index.ts b/components/bank/handlers/upgrade/index.ts
new file mode 100644
index 00000000..055dff99
--- /dev/null
+++ b/components/bank/handlers/upgrade/index.ts
@@ -0,0 +1,2 @@
+export * from './msgSoftwareUpgradeHandler';
+export * from './msgCancelUpgradeHandler';
diff --git a/components/bank/handlers/upgrade/msgCancelUpgradeHandler.tsx b/components/bank/handlers/upgrade/msgCancelUpgradeHandler.tsx
new file mode 100644
index 00000000..9dd1d956
--- /dev/null
+++ b/components/bank/handlers/upgrade/msgCancelUpgradeHandler.tsx
@@ -0,0 +1,24 @@
+import { ArrowUpIcon } from '@/components/icons/ArrowUpIcon';
+import { createSenderReceiverHandler } from '../createSenderReceiverHandler';
+import { registerHandler } from '@/components/bank/handlers/handlerRegistry';
+import { MsgCancelUpgrade } from '@liftedinit/manifestjs/dist/codegen/cosmos/upgrade/v1beta1/tx';
+import { TruncatedAddressWithCopy } from '@/components/react/addressCopy';
+import { format } from 'react-string-format';
+
+const createMessage = (template: string, sender: string) => {
+ const message = format(
+ template,
+ sender ?
: 'an unknown address'
+ );
+ return
{message};
+};
+
+export const MsgCancelUpgradeHandler = createSenderReceiverHandler({
+ iconSender: ArrowUpIcon,
+ successSender: 'You successfully cancelled the chain upgrade',
+ failSender: 'You failed to cancel chain software upgrade',
+ successReceiver: tx =>
+ createMessage('The chain software upgrade was cancelled by {0}', tx.sender),
+});
+
+registerHandler(MsgCancelUpgrade.typeUrl, MsgCancelUpgradeHandler);
diff --git a/components/bank/handlers/upgrade/msgSoftwareUpgradeHandler.tsx b/components/bank/handlers/upgrade/msgSoftwareUpgradeHandler.tsx
new file mode 100644
index 00000000..4bc2d6ac
--- /dev/null
+++ b/components/bank/handlers/upgrade/msgSoftwareUpgradeHandler.tsx
@@ -0,0 +1,41 @@
+import { ArrowUpIcon } from '@/components/icons/ArrowUpIcon';
+import { createSenderReceiverHandler } from '../createSenderReceiverHandler';
+import { registerHandler } from '@/components/bank/handlers/handlerRegistry';
+import { MsgSoftwareUpgrade } from '@liftedinit/manifestjs/dist/codegen/cosmos/upgrade/v1beta1/tx';
+import { format } from 'react-string-format';
+import { TruncatedAddressWithCopy } from '@/components/react/addressCopy';
+
+const createMessage = (template: string, planName: string, planHeight: string, sender?: string) => {
+ const message = format(
+ template,
+ planName,
+ planHeight,
+ sender ?
: 'an unknown address'
+ );
+ return
{message};
+};
+
+export const MsgSoftwareUpgradeHandler = createSenderReceiverHandler({
+ iconSender: ArrowUpIcon,
+ successSender: tx =>
+ createMessage(
+ 'You scheduled a chain upgrade to {0} at block {1}',
+ tx.metadata?.plan?.name,
+ tx.metadata?.plan?.height
+ ),
+ failSender: tx =>
+ createMessage(
+ 'You failed to schedule a chain software upgrade to {0} at block {1}',
+ tx.metadata?.plan?.name,
+ tx.metadata.plan?.height
+ ),
+ successReceiver: tx =>
+ createMessage(
+ 'A chain upgrade to {0} is scheduled at block {1} by {2}',
+ tx.metadata?.plan?.name,
+ tx.metadata?.plan?.height,
+ tx.sender
+ ),
+});
+
+registerHandler(MsgSoftwareUpgrade.typeUrl, MsgSoftwareUpgradeHandler);
diff --git a/components/bank/index.ts b/components/bank/index.ts
index 83d6eb9c..28a431e4 100644
--- a/components/bank/index.ts
+++ b/components/bank/index.ts
@@ -1,3 +1,4 @@
export * from './forms';
export * from './components';
export * from './modals';
+export * from './handlers';
diff --git a/components/bank/modals/txInfo.tsx b/components/bank/modals/txInfo.tsx
index c1f4547a..da75952f 100644
--- a/components/bank/modals/txInfo.tsx
+++ b/components/bank/modals/txInfo.tsx
@@ -1,17 +1,19 @@
-import React from 'react';
+import React, { useMemo } from 'react';
import { TruncatedAddressWithCopy } from '@/components/react/addressCopy';
-import { formatDenom, TransactionGroup } from '@/components';
+import { objectSyntax } from '@/components';
import { FaExternalLinkAlt } from 'react-icons/fa';
-import { denomToAsset, shiftDigits } from '@/utils';
import env from '@/config/env';
+import { useTheme } from '@/contexts';
+import { TxMessage } from '@/components/bank/types';
+import { isJsonString } from '@/utils/json';
interface TxInfoModalProps {
- tx: TransactionGroup;
-
+ tx: TxMessage;
modalId: string;
}
export default function TxInfoModal({ tx, modalId }: TxInfoModalProps) {
+ const { theme } = useTheme();
function formatDate(dateString: string): string {
const date = new Date(dateString);
return date.toLocaleString('en-US', {
@@ -24,6 +26,14 @@ export default function TxInfoModal({ tx, modalId }: TxInfoModalProps) {
});
}
+ function isBase64(str: string): boolean {
+ try {
+ return btoa(atob(str)) === str;
+ } catch (err) {
+ return false;
+ }
+ }
+
return (