Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@
"require": "./lib/lifi.js",
"import": "./lib/lifi.mjs"
},
"./monad": {
"types": "./lib/monad.d.ts",
"require": "./lib/monad.js",
"import": "./lib/monad.mjs"
},
"./ntt": {
"types": "./lib/ntt.d.ts",
"require": "./lib/ntt.js",
Expand Down
21 changes: 21 additions & 0 deletions src/components/SampleApp/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@
cctpV2FastExecutorRoute,
} from 'exports/executor';
import { createLiFiRouteWithConfig } from 'exports/lifi';
import {
monadBridgeExecutorRoute,
monadBridgeManualRoute,
} from 'exports/monad';
import type { WormholeConnectTheme } from 'theme';

const MAX_URL_SIZE = 30_000; // 30kb (HTTP header limit is set to 32kb)
Expand Down Expand Up @@ -97,6 +101,10 @@
window.cctpV2FastExecutorRoute = cctpV2FastExecutorRoute;
/* @ts-ignore */
window.executorTokenBridgeRoute = routes.executorTokenBridgeRoute;
/* @ts-ignore */
window.monadBridgeExecutorRoute = monadBridgeExecutorRoute;
/* @ts-ignore */
window.monadBridgeManualRoute = monadBridgeManualRoute;

return eval(
`(function() { return ${config} })()`,
Expand Down Expand Up @@ -216,8 +224,8 @@
localStorage.setItem(LOCAL_STORAGE_KEY_BG, input);
};

useEffect(emitCustomConfig, []);

Check warning on line 227 in src/components/SampleApp/index.tsx

View workflow job for this annotation

GitHub Actions / ESLint

React Hook useEffect has missing dependencies: 'customConfigInput' and 'isLoadingCustomConfig'. Either include them or remove the dependency array
useEffect(emitCustomTheme, []);

Check warning on line 228 in src/components/SampleApp/index.tsx

View workflow job for this annotation

GitHub Actions / ESLint

React Hook useEffect has a missing dependency: 'customThemeInput'. Either include it or remove the dependency array. You can also replace multiple useState variables with useReducer if 'setCustomTheme' needs the current value of 'customThemeInput'

return (
<main style={{ background: backgroundColor }}>
Expand Down Expand Up @@ -332,6 +340,19 @@
{'(ExecutorTokenBridgeRoute.Config) -> RouteConstructor'}
</i>
</li>
<li>
<pre>monadBridgeExecutorRoute</pre>
<i>
{
'(MultiTokenNttExecutorRoute.Config) -> RouteConstructor'
}
</i>
</li>
<li>
<pre>monadBridgeManualRoute</pre>
<i>{'(MultiTokenNttRoute.Config) -> RouteConstructor'}</i>
</li>
<li></li>
</ul>
</div>
<div>
Expand Down
8 changes: 8 additions & 0 deletions src/config/testnet/chains.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,14 @@ export const TESTNET_CHAINS: ChainsConfig = {
symbol: 'MNT',
sdkName: 'Mantle',
},
Monad: {
displayName: 'Monad',
explorerUrl: 'https://testnet.monadexplorer.com/',
explorerName: 'Monad Explorer',
icon: 'Monad',
symbol: 'MON',
sdkName: 'Monad',
},
Worldchain: {
displayName: 'World Chain',
explorerUrl: 'https://worldchain-sepolia.explorer.alchemy.com/',
Expand Down
2 changes: 2 additions & 0 deletions src/config/testnet/rpcs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const {
REACT_APP_SEIEVM_TESTNET_RPC,
REACT_APP_PLUME_RPC,
REACT_APP_XRPLEVM_TESTNET_RPC,
REACT_APP_MONAD_TESTNET_RPC,
} = import.meta.env;

export const TESTNET_RPC_MAPPING = {
Expand Down Expand Up @@ -53,4 +54,5 @@ export const TESTNET_RPC_MAPPING = {
...populateRpcField('Seievm', REACT_APP_SEIEVM_TESTNET_RPC),
...populateRpcField('Plume', REACT_APP_PLUME_RPC),
...populateRpcField('XRPLEVM', REACT_APP_XRPLEVM_TESTNET_RPC),
...populateRpcField('Monad', REACT_APP_MONAD_TESTNET_RPC),
};
78 changes: 78 additions & 0 deletions src/config/testnet/tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,33 @@ export const TESTNET_TOKENS: TokenConfig[] = [
address: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',
},
},
{
symbol: 'USDT',
icon: TokenIcon.USDT,
decimals: 6,
tokenId: {
chain: 'Sepolia',
address: '0xaA8E23Fb1079EA71e0a56F48a2aA51851D8433D0',
},
},
{
symbol: 'WBTC',
icon: TokenIcon.WBTC,
decimals: 8,
tokenId: {
chain: 'Sepolia',
address: '0x52eea312378ef46140ebe67de8a143ba2304fd7c',
},
},
{
symbol: 'UNI',
icon: 'https://assets.coingecko.com/coins/images/12504/standard/uniswap-logo.png',
decimals: 18,
tokenId: {
chain: 'Sepolia',
address: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984',
},
},
{
symbol: 'ETH',
icon: TokenIcon.ETH,
Expand Down Expand Up @@ -353,6 +380,57 @@ export const TESTNET_TOKENS: TokenConfig[] = [
decimals: 18,
icon: TokenIcon.BTC,
},
{
symbol: 'MON',
icon: TokenIcon.MONAD,
decimals: 18,
tokenId: { chain: 'Monad', address: 'native' },
},
{
symbol: 'WMON',
icon: TokenIcon.MONAD,
decimals: 18,
tokenId: {
chain: 'Monad',
address: '0x760AfE86e5de5fa0Ee542fc7B7B713e1c5425701',
},
},
{
symbol: 'wUSDC',
icon: TokenIcon.USDC,
decimals: 6,
tokenId: {
chain: 'Monad',
address: '0x4804916f7c16c20E1a557feb8E3E29418DDC54DC',
},
},
{
symbol: 'wUSDT',
icon: TokenIcon.USDT,
decimals: 6,
tokenId: {
chain: 'Monad',
address: '0x72111141639bfAa9A1Ad7A638F546e2397f0dc7a',
},
},
{
symbol: 'wWETH',
icon: TokenIcon.ETH,
decimals: 18,
tokenId: {
chain: 'Monad',
address: '0xA296f47E8Ff895Ed7A092b4a9498bb13C46ac768',
},
},
{
symbol: 'wWBTC',
icon: TokenIcon.WBTC,
decimals: 8,
tokenId: {
chain: 'Monad',
address: '0x5B3540B0a48F8b30A655402eE5dcD213fd2B4BEa',
},
},
{
symbol: 'ETH',
tokenId: {
Expand Down
1 change: 1 addition & 0 deletions src/config/testnet/wrappedTokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ export const TESTNET_WRAPPED_TOKENS: WrappedTokenAddresses = {
Klaytn: '0x48D99C16638E6FFD2ED60375cD35ef6c139e5f41',
Moonbeam: '0x5babD0d267Adf2081626654E51d1cABF8c477440',
Sepolia: '0x824CB8fC742F8D3300d29f16cA8beE94471169f5',
Monad: '0x369CD1E20Fa7ea1F8e6dc0759709bA0bD978abE7',
},
'4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU': {
Bsc: '0x51a3cc54eA30Da607974C5D07B8502599801AC08',
Expand Down
70 changes: 68 additions & 2 deletions src/config/tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ import {
isNative,
chainToPlatform,
UniversalAddress,
isUnattestedTokenId,
} from '@wormhole-foundation/sdk';
import type { TokenIcon, TokenConfig, WrappedTokenAddresses } from './types';
import config, { getWormholeContextV2 } from './index';
import { isValidSuiType } from '@wormhole-foundation/sdk-sui';
import { fetchTokenMetadata } from 'utils/coingecko';
import { MultiTokenNttExecutorRoute } from '@wormhole-foundation/sdk-route-ntt';

const TOKEN_CACHE_VERSION = 1;

Expand Down Expand Up @@ -59,6 +61,7 @@ type TokenConstructorProps = {
icon?: TokenIcon | string;
tokenBridgeOriginalTokenId?: TokenId;
coingeckoId?: string;
isUnattested?: boolean;
};

export class Token extends TokenIdLazy {
Expand All @@ -81,6 +84,10 @@ export class Token extends TokenIdLazy {
// Used when filtering out unknown/unverified tokens
isBuiltin?: boolean;

// If this is an unattested token (i.e. a token that has not yet been created),
// isUnattested will be true. Unattested tokens do not get persisted to local storage.
isUnattested?: boolean;
Comment thread
emreboga marked this conversation as resolved.

constructor({
chain,
address,
Expand All @@ -90,6 +97,7 @@ export class Token extends TokenIdLazy {
icon,
tokenBridgeOriginalTokenId,
coingeckoId,
isUnattested,
}: TokenConstructorProps) {
super(chain, address);
this.decimals = decimals;
Expand All @@ -98,6 +106,7 @@ export class Token extends TokenIdLazy {
this.icon = icon;
this.tokenBridgeOriginalTokenId = tokenBridgeOriginalTokenId;
this.coingeckoWebId = coingeckoId;
this.isUnattested = isUnattested;
}

get display(): string {
Expand Down Expand Up @@ -450,11 +459,38 @@ export class TokenCache extends TokenMapping<Token> {
);
}

if (isUnattestedTokenId(tokenId)) {
const original = this.get(tokenId.originalTokenId);
console.debug(`Token ${tokenId} is unattested`);

// An unattested token does not yet exist on-chain,
// so we use the original token's metadata
const t = new Token({
chain: tokenId.chain,
address: canonicalAddress(tokenId),
decimals: tokenId.decimals,
symbol: original?.symbol || '',
name: original?.name,
icon: original?.icon,
isUnattested: true,
});

this.add(t);

return t;
}

const wh = await getWormholeContextV2();
const chain = wh.getChain(tokenId.chain);
const decimals = await chain.getDecimals(tokenId.address);

const metadata = await fetchTokenMetadata(tokenId);
let metadata: any = {};
try {
const fetchedMetadata = await fetchTokenMetadata(tokenId);
metadata = fetchedMetadata || {};
} catch (error) {
console.error('Error fetching token metadata:', error);
}

let symbol = metadata?.symbol?.toUpperCase() || '';
let name = metadata?.name || '';
Expand Down Expand Up @@ -507,6 +543,31 @@ export class TokenCache extends TokenMapping<Token> {
}
}

// TODO: as a future improvement, we could add a isWrappedTokenRoute or similar to the route interface
// and check all routes to see if any of them recognize this token as a wrapped token.

if (!tokenBridgeOriginalTokenId && (!symbol || !image)) {
// Check if this is a Monad Bridge wrapped token
try {
const monadRoute = config.routes.get('MonadBridgeExecutorRoute');
if (monadRoute?.isSupportedChain(tokenId.chain)) {
const route = new monadRoute.rc(wh);
if (route instanceof MultiTokenNttExecutorRoute) {
if (await route.isWrappedToken(tokenId)) {
const originalTokenId = await route.getOriginalToken(tokenId);
const originalToken = this.get(originalTokenId);
if (originalToken) {
image = image ?? originalToken.icon;
symbol = symbol ?? originalToken.symbol;
}
}
}
}
} catch {
// Ignore errors so we can still add the token to the cache
}
}

const t = new Token({
chain: tokenId.chain,
address: canonicalAddress(tokenId),
Expand All @@ -530,7 +591,12 @@ export class TokenCache extends TokenMapping<Token> {
tokens: {},
};
this.forEach((tokenId, token) => {
asJson.tokens[tokenKey(tokenId)] = token.toJson();
// Skip persisting unattested tokens.
// Unattested tokens don't yet exist on-chain, and their metadata should be fetched
// directly from on-chain, which serves as the source of truth.
if (!token.isUnattested) {
asJson.tokens[tokenKey(tokenId)] = token.toJson();
}
});

const jsonString = JSON.stringify(asJson);
Expand Down
1 change: 1 addition & 0 deletions src/config/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export enum TokenIcon {
'HYPE',
'XRP',
'CTC',
'MONAD',
}

// Used in bridging components
Expand Down
3 changes: 2 additions & 1 deletion src/exports/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export * as config from 'config/types';
import { DEFAULT_ROUTES } from 'routes/operator';
import { routes } from '@wormhole-foundation/sdk';

import type { Chain } from '@wormhole-foundation/sdk';
import type { Chain, Network } from '@wormhole-foundation/sdk';

import { Token } from 'config/tokens';
import { TransferWallet } from 'utils/wallet';
Expand Down Expand Up @@ -41,6 +41,7 @@ export {

// Types
Chain,
Network,
WormholeConnectTheme,
Token,

Expand Down
5 changes: 5 additions & 0 deletions src/exports/monad.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export {
monadBridgeExecutorRoute,
monadBridgeManualRoute,
getContractsForNetwork,
} from '../routes/monad';
9 changes: 8 additions & 1 deletion src/exports/ntt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,11 @@ const nttRoutes = (
];
};

export { nttAutomaticRoute, nttExecutorRoute, nttManualRoute, nttRoutes };
export {
nttAutomaticRoute,
nttExecutorRoute,
nttManualRoute,
nttRoutes,
NttRoute,
NttExecutorRoute,
};
Loading
Loading