Skip to content

Commit cfa3cca

Browse files
committed
feat: parse coins into proper assets
1 parent 0682334 commit cfa3cca

File tree

1 file changed

+128
-3
lines changed

1 file changed

+128
-3
lines changed

src/features/cosmos/cosmos-connect-button.tsx

+128-3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,40 @@ import { Density } from '@penumbra-zone/ui/Density';
77
import { chainsInPenumbraRegistry } from '@/features/cosmos/chain-provider.tsx';
88
import { useRegistry } from '@/shared/api/registry.ts';
99
import { useBalances } from '@/features/cosmos/use-augmented-balances.ts';
10+
import { ValueViewComponent } from '@penumbra-zone/ui/ValueView';
11+
import {
12+
AssetId,
13+
Metadata,
14+
ValueView,
15+
} from '@penumbra-zone/protobuf/penumbra/core/asset/v1/asset_pb';
16+
import { toValueView } from '@/shared/utils/value-view';
17+
import { Balance } from '@/features/cosmos/types';
18+
import { useQuery } from '@tanstack/react-query';
19+
import { sha256HashStr } from '@penumbra-zone/crypto-web/sha256';
20+
import { Chain } from '@penumbra-labs/registry';
21+
22+
// Generates penumbra token addresses on counterparty chains.
23+
// IBC address derived from sha256 has of path: https://tutorials.cosmos.network/tutorials/6-ibc-dev/
24+
const generatePenumbraIbcDenoms = async (chains: Chain[]): Promise<string[]> => {
25+
const ibcAddrs: string[] = [];
26+
for (const c of chains) {
27+
const ibcStr = `transfer/${c.counterpartyChannelId}/upenumbra`;
28+
const encoder = new TextEncoder();
29+
const encodedString = encoder.encode(ibcStr);
30+
31+
const hash = await sha256HashStr(encodedString);
32+
ibcAddrs.push(`ibc/${hash.toUpperCase()}`);
33+
}
34+
return ibcAddrs;
35+
};
36+
37+
const usePenumbraIbcDenoms = (chains: Chain[]) => {
38+
return useQuery({
39+
queryKey: ['penumbraIbcDenoms', chains],
40+
queryFn: async () => generatePenumbraIbcDenoms(chains),
41+
enabled: chains.length > 0,
42+
});
43+
};
1044

1145
interface CosmosConnectButtonProps {
1246
actionType?: ButtonProps['actionType'];
@@ -23,22 +57,113 @@ const CosmosConnectButtonInner = observer(
2357
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- Osmosis is always available
2458
const { address, disconnect, openView, isWalletConnected } = chains['osmosis']!;
2559

60+
// Get Penumbra IBC denoms
61+
const { data: penumbraIbcDenoms = [] } = usePenumbraIbcDenoms(registry?.ibcConnections ?? []);
62+
2663
const handleConnect = () => {
2764
openView();
2865
};
2966

3067
const { balances } = useBalances();
3168

69+
// Helper function to convert Cosmos balance to ValueView
70+
const balanceToValueView = (balance: Balance): ValueView => {
71+
try {
72+
// Find matching asset in registry
73+
if (!registry) {
74+
throw new Error('Registry not available');
75+
}
76+
77+
// Try to match by IBC denom or symbol
78+
let metadata: Metadata | undefined;
79+
80+
// For IBC denoms - check if it's a Penumbra asset
81+
if (balance.denom.startsWith('ibc/')) {
82+
const isPenumbraAsset = penumbraIbcDenoms.includes(balance.denom);
83+
84+
if (isPenumbraAsset) {
85+
// If it's a Penumbra asset, get the Penumbra token metadata
86+
const allAssets = registry.getAllAssets();
87+
// Find staking token (Penumbra) metadata
88+
const penumbraMetadata = allAssets.find(
89+
asset =>
90+
asset.symbol.toUpperCase() === 'UM' || asset.symbol.toUpperCase() === 'PENUMBRA',
91+
);
92+
93+
if (penumbraMetadata) {
94+
metadata = penumbraMetadata;
95+
}
96+
} else {
97+
// Try to find other IBC assets
98+
const allAssets = registry.getAllAssets();
99+
// Since we can't directly access properties of asset, we need to check symbol
100+
// against what we expect from the balance
101+
if (balance.symbol) {
102+
const matchingAsset = allAssets.find(
103+
asset => asset.symbol.toLowerCase() === balance.symbol?.toLowerCase(),
104+
);
105+
106+
if (matchingAsset) {
107+
metadata = matchingAsset;
108+
}
109+
}
110+
}
111+
}
112+
// For native denoms
113+
else if (balance.symbol) {
114+
// Get all assets and find one with matching symbol
115+
const allAssets = registry.getAllAssets();
116+
const matchingAsset = allAssets.find(
117+
asset => asset.symbol.toLowerCase() === balance.symbol?.toLowerCase(),
118+
);
119+
120+
if (matchingAsset) {
121+
metadata = matchingAsset;
122+
}
123+
}
124+
125+
if (metadata) {
126+
// Use the toValueView utility to create a KnownAssetId ValueView
127+
const amountInBaseUnits = parseInt(balance.amount);
128+
129+
return toValueView({
130+
amount: amountInBaseUnits,
131+
metadata,
132+
});
133+
}
134+
135+
// If no metadata found, create a basic ValueView with an UnknownAssetId
136+
// Create a placeholder assetId
137+
const assetId = new AssetId({ inner: new Uint8Array() });
138+
139+
return toValueView({
140+
amount: parseInt(balance.amount),
141+
assetId,
142+
});
143+
} catch (error) {
144+
console.error('Error creating ValueView:', error);
145+
// Create a fallback ValueView with UnknownAssetId
146+
const assetId = new AssetId({ inner: new Uint8Array() });
147+
return toValueView({
148+
amount: parseInt(balance.amount) || 0,
149+
assetId,
150+
});
151+
}
152+
};
153+
32154
return (
33155
<Density variant={variant === 'default' ? 'sparse' : 'compact'}>
34156
{isWalletConnected && address ? (
35157
<>
36158
<Button actionType={actionType} onClick={() => void disconnect()}>
37159
{`${address.slice(0, 8)}...${address.slice(-4)}`}
38160
</Button>
39-
<pre className={'text-white overflow-scroll'}>
40-
{balances.map(b => JSON.stringify(b, undefined, 4))}
41-
</pre>
161+
{balances.map((balance, index) => (
162+
<ValueViewComponent
163+
key={`${balance.chain}-${balance.denom}-${index}`}
164+
valueView={balanceToValueView(balance)}
165+
/>
166+
))}
42167
</>
43168
) : (
44169
<Button icon={Wallet2} actionType={actionType} onClick={handleConnect}>

0 commit comments

Comments
 (0)