@hyperlane-xyz/sdk
Version:
The official SDK for the Hyperlane Network
412 lines • 19 kB
JavaScript
import { ProtocolType, assert, eqAddress, } from '@hyperlane-xyz/utils';
import { isStarknetFeeToken } from '../utils/starknet.js';
import { TokenAmount } from './TokenAmount.js';
import { TokenConnectionType } from './TokenConnection.js';
import { PROTOCOL_TO_HYP_NATIVE_STANDARD, PROTOCOL_TO_NATIVE_STANDARD, TOKEN_COLLATERALIZED_STANDARDS, TOKEN_HYP_STANDARDS, TOKEN_MULTI_CHAIN_STANDARDS, TOKEN_NFT_STANDARDS, TOKEN_STANDARD_TO_PROTOCOL, TokenStandard, XERC20_STANDARDS, } from './TokenStandard.js';
import { CwHypCollateralAdapter, CwHypNativeAdapter, CwHypSyntheticAdapter, CwNativeTokenAdapter, CwTokenAdapter, } from './adapters/CosmWasmTokenAdapter.js';
import { CosmNativeHypCollateralAdapter, CosmNativeHypSyntheticAdapter, } from './adapters/CosmosModuleTokenAdapter.js';
import { CosmIbcToWarpTokenAdapter, CosmIbcTokenAdapter, CosmNativeTokenAdapter, } from './adapters/CosmosTokenAdapter.js';
import { EvmHypCollateralFiatAdapter, EvmHypNativeAdapter, EvmHypRebaseCollateralAdapter, EvmHypSyntheticAdapter, EvmHypSyntheticRebaseAdapter, EvmHypXERC20Adapter, EvmHypXERC20LockboxAdapter, EvmMovableCollateralAdapter, EvmNativeTokenAdapter, EvmTokenAdapter, } from './adapters/EvmTokenAdapter.js';
import { M0PortalLiteTokenAdapter } from './adapters/M0PortalLiteTokenAdapter.js';
import { RadixHypCollateralAdapter, RadixHypSyntheticAdapter, RadixNativeTokenAdapter, RadixTokenAdapter, } from './adapters/RadixTokenAdapter.js';
import { SealevelHypCollateralAdapter, SealevelHypNativeAdapter, SealevelHypSyntheticAdapter, SealevelNativeTokenAdapter, SealevelTokenAdapter, } from './adapters/SealevelTokenAdapter.js';
import { StarknetHypCollateralAdapter, StarknetHypFeeAdapter, StarknetHypNativeAdapter, StarknetHypSyntheticAdapter, StarknetTokenAdapter, } from './adapters/StarknetTokenAdapter.js';
import { PROTOCOL_TO_DEFAULT_NATIVE_TOKEN } from './nativeTokenMetadata.js';
export class Token {
protocol;
constructor(args) {
Object.assign(this, args);
this.protocol = TOKEN_STANDARD_TO_PROTOCOL[this.standard];
}
/**
* Creates a Token for the native currency on the given chain.
* Will use the default native token for the given protocol if
* nothing specific is set in the ChainMetadata.
*/
static FromChainMetadataNativeToken(chainMetadata) {
const { protocol, name: chainName, logoURI } = chainMetadata;
const nativeToken = chainMetadata.nativeToken || PROTOCOL_TO_DEFAULT_NATIVE_TOKEN[protocol];
return new Token({
chainName,
standard: PROTOCOL_TO_NATIVE_STANDARD[protocol],
addressOrDenom: nativeToken.denom ?? '',
decimals: nativeToken.decimals,
symbol: nativeToken.symbol,
name: nativeToken.name,
logoURI,
});
}
/**
* Returns a TokenAdapter for the token and multiProvider
* @throws If multiProvider does not contain this token's chain.
* @throws If token is an NFT (TODO NFT Adapter support)
*/
getAdapter(multiProvider) {
const { standard, chainName, addressOrDenom } = this;
assert(!this.isNft(), 'NFT adapters not yet supported');
assert(multiProvider.tryGetChainMetadata(chainName), `Token chain ${chainName} not found in multiProvider`);
if (standard === TokenStandard.ERC20) {
return new EvmTokenAdapter(chainName, multiProvider, {
token: addressOrDenom,
});
}
else if (standard === TokenStandard.EvmNative) {
return new EvmNativeTokenAdapter(chainName, multiProvider, {});
}
else if (standard === TokenStandard.SealevelSpl ||
standard === TokenStandard.SealevelSpl2022) {
return new SealevelTokenAdapter(chainName, multiProvider, {
token: addressOrDenom,
});
}
else if (standard === TokenStandard.SealevelNative) {
return new SealevelNativeTokenAdapter(chainName, multiProvider, {});
}
else if (standard === TokenStandard.CosmosIcs20) {
throw new Error('Cosmos ICS20 token adapter not yet supported');
}
else if (standard === TokenStandard.CosmosNative) {
return new CosmNativeTokenAdapter(chainName, multiProvider, {}, { ibcDenom: addressOrDenom });
}
else if (standard === TokenStandard.CW20) {
return new CwTokenAdapter(chainName, multiProvider, {
token: addressOrDenom,
});
}
else if (standard === TokenStandard.CWNative) {
return new CwNativeTokenAdapter(chainName, multiProvider, {}, addressOrDenom);
}
else if (standard === TokenStandard.StarknetNative) {
return new StarknetTokenAdapter(chainName, multiProvider, {
tokenAddress: addressOrDenom,
});
}
else if (standard === TokenStandard.RadixNative) {
return new RadixNativeTokenAdapter(chainName, multiProvider, {
token: addressOrDenom,
});
}
else if (this.isHypToken()) {
return this.getHypAdapter(multiProvider);
}
else if (this.isIbcToken()) {
// Passing in a stub connection here because it's not required
// for an IBC adapter to fulfill the ITokenAdapter interface
return this.getIbcAdapter(multiProvider, {
token: this,
sourcePort: 'transfer',
sourceChannel: 'channel-0',
type: TokenConnectionType.Ibc,
});
}
else {
throw new Error(`No adapter found for token standard: ${standard}`);
}
}
/**
* Returns a HypTokenAdapter for the token and multiProvider
* @throws If not applicable to this token's standard.
* @throws If multiProvider does not contain this token's chain.
* @throws If token is an NFT (TODO NFT Adapter support)
*/
getHypAdapter(multiProvider, destination) {
const { standard, chainName, addressOrDenom, collateralAddressOrDenom } = this;
const chainMetadata = multiProvider.tryGetChainMetadata(chainName);
const mailbox = chainMetadata?.mailbox;
assert(this.isMultiChainToken(), `Token standard ${standard} not applicable to hyp adapter`);
assert(!this.isNft(), 'NFT adapters not yet supported');
assert(chainMetadata, `Token chain ${chainName} not found in multiProvider`);
if (standard === TokenStandard.EvmHypNative) {
return new EvmHypNativeAdapter(chainName, multiProvider, {
token: addressOrDenom,
});
}
else if (standard === TokenStandard.EvmHypCollateral ||
standard === TokenStandard.EvmHypOwnerCollateral) {
return new EvmMovableCollateralAdapter(chainName, multiProvider, {
token: addressOrDenom,
});
}
else if (standard === TokenStandard.EvmHypRebaseCollateral) {
return new EvmHypRebaseCollateralAdapter(chainName, multiProvider, {
token: addressOrDenom,
});
}
else if (standard === TokenStandard.EvmHypCollateralFiat) {
return new EvmHypCollateralFiatAdapter(chainName, multiProvider, {
token: addressOrDenom,
});
}
else if (standard === TokenStandard.EvmHypSynthetic) {
return new EvmHypSyntheticAdapter(chainName, multiProvider, {
token: addressOrDenom,
});
}
else if (standard === TokenStandard.EvmHypSyntheticRebase) {
return new EvmHypSyntheticRebaseAdapter(chainName, multiProvider, {
token: addressOrDenom,
});
}
else if (standard === TokenStandard.EvmHypXERC20 ||
standard === TokenStandard.EvmHypVSXERC20) {
return new EvmHypXERC20Adapter(chainName, multiProvider, {
token: addressOrDenom,
});
}
else if (standard === TokenStandard.EvmHypXERC20Lockbox ||
standard === TokenStandard.EvmHypVSXERC20Lockbox) {
return new EvmHypXERC20LockboxAdapter(chainName, multiProvider, {
token: addressOrDenom,
});
}
else if (standard === TokenStandard.SealevelHypNative) {
assert(mailbox, `Mailbox required for Sealevel hyp tokens`);
return new SealevelHypNativeAdapter(chainName, multiProvider, {
warpRouter: addressOrDenom,
mailbox,
});
}
else if (standard === TokenStandard.SealevelHypCollateral) {
assert(mailbox, `Mailbox required for Sealevel hyp tokens`);
assert(collateralAddressOrDenom, `collateralAddressOrDenom required for Sealevel hyp collateral tokens`);
return new SealevelHypCollateralAdapter(chainName, multiProvider, {
warpRouter: addressOrDenom,
token: collateralAddressOrDenom,
mailbox,
});
}
else if (standard === TokenStandard.SealevelHypSynthetic) {
assert(mailbox, `Mailbox required for Sealevel hyp tokens`);
assert(collateralAddressOrDenom, `collateralAddressOrDenom required for Sealevel hyp collateral tokens`);
return new SealevelHypSyntheticAdapter(chainName, multiProvider, {
warpRouter: addressOrDenom,
token: collateralAddressOrDenom,
mailbox,
});
}
else if (standard === TokenStandard.CwHypNative) {
return new CwHypNativeAdapter(chainName, multiProvider, {
warpRouter: addressOrDenom,
});
}
else if (standard === TokenStandard.CwHypCollateral) {
assert(collateralAddressOrDenom, 'collateralAddressOrDenom required for CwHypCollateral');
return new CwHypCollateralAdapter(chainName, multiProvider, {
warpRouter: addressOrDenom,
token: collateralAddressOrDenom,
});
}
else if (standard === TokenStandard.CwHypSynthetic) {
assert(collateralAddressOrDenom, 'collateralAddressOrDenom required for CwHypSyntheticAdapter');
return new CwHypSyntheticAdapter(chainName, multiProvider, {
warpRouter: addressOrDenom,
token: collateralAddressOrDenom,
});
}
else if (standard === TokenStandard.CosmosIbc) {
assert(destination, 'destination required for IBC token adapters');
const connection = this.getConnectionForChain(destination);
assert(connection, `No connection found for chain ${destination}`);
return this.getIbcAdapter(multiProvider, connection);
}
else if (standard === TokenStandard.CosmNativeHypCollateral) {
return new CosmNativeHypCollateralAdapter(chainName, multiProvider, {
token: addressOrDenom,
});
}
else if (standard === TokenStandard.CosmNativeHypSynthetic) {
return new CosmNativeHypSyntheticAdapter(chainName, multiProvider, {
token: addressOrDenom,
});
}
else if (isStarknetFeeToken(chainName, addressOrDenom)) {
return new StarknetHypFeeAdapter(chainName, multiProvider, {
warpRouter: addressOrDenom,
});
}
else if (standard === TokenStandard.StarknetHypNative) {
return new StarknetHypNativeAdapter(chainName, multiProvider, {
warpRouter: addressOrDenom,
});
}
else if (standard === TokenStandard.StarknetHypSynthetic) {
return new StarknetHypSyntheticAdapter(chainName, multiProvider, {
warpRouter: addressOrDenom,
});
}
else if (standard === TokenStandard.StarknetHypCollateral) {
return new StarknetHypCollateralAdapter(chainName, multiProvider, {
warpRouter: addressOrDenom,
});
}
else if (standard === TokenStandard.RadixHypCollateral) {
return new RadixHypCollateralAdapter(chainName, multiProvider, {
token: addressOrDenom,
});
}
else if (standard === TokenStandard.RadixHypSynthetic) {
return new RadixHypSyntheticAdapter(chainName, multiProvider, {
token: addressOrDenom,
});
}
else if (standard === TokenStandard.EvmM0PortalLite) {
assert(collateralAddressOrDenom, 'collateralAddressOrDenom (mToken address) required for M0PortalLite');
return new M0PortalLiteTokenAdapter(multiProvider, chainName, addressOrDenom, // portal address
collateralAddressOrDenom);
}
else {
throw new Error(`No hyp adapter found for token standard: ${standard}`);
}
}
getIbcAdapter(multiProvider, connection) {
if (connection.type === TokenConnectionType.Ibc) {
const { sourcePort, sourceChannel } = connection;
return new CosmIbcTokenAdapter(this.chainName, multiProvider, {}, { ibcDenom: this.addressOrDenom, sourcePort, sourceChannel });
}
else if (connection.type === TokenConnectionType.IbcHyperlane) {
const { sourcePort, sourceChannel, intermediateChainName, intermediateIbcDenom, intermediateRouterAddress, } = connection;
const destinationRouterAddress = connection.token.addressOrDenom;
return new CosmIbcToWarpTokenAdapter(this.chainName, multiProvider, {
intermediateRouterAddress,
destinationRouterAddress,
}, {
ibcDenom: this.addressOrDenom,
sourcePort,
sourceChannel,
intermediateIbcDenom,
intermediateChainName,
});
}
else {
throw new Error(`Unsupported IBC connection type: ${connection.type}`);
}
}
/**
* Convenience method to create an adapter and return an account balance
*/
async getBalance(multiProvider, address) {
const adapter = this.getAdapter(multiProvider);
const balance = await adapter.getBalance(address);
return new TokenAmount(balance, this);
}
amount(amount) {
return new TokenAmount(amount, this);
}
isNft() {
return TOKEN_NFT_STANDARDS.includes(this.standard);
}
isNative() {
return Object.values(PROTOCOL_TO_NATIVE_STANDARD).includes(this.standard);
}
isHypNative() {
return Object.values(PROTOCOL_TO_HYP_NATIVE_STANDARD).includes(this.standard);
}
isCollateralized() {
return TOKEN_COLLATERALIZED_STANDARDS.includes(this.standard);
}
isHypToken() {
return TOKEN_HYP_STANDARDS.includes(this.standard);
}
isXerc20() {
return XERC20_STANDARDS.includes(this.standard);
}
isIbcToken() {
return this.standard === TokenStandard.CosmosIbc;
}
isMultiChainToken() {
return TOKEN_MULTI_CHAIN_STANDARDS.includes(this.standard);
}
getConnections() {
return this.connections || [];
}
getConnectionForChain(chain) {
// A token cannot have > 1 connected token for the same chain
return this.getConnections().filter((t) => t.token.chainName === chain)[0];
}
addConnection(connection) {
this.connections = [...(this.connections || []), connection];
return this;
}
removeConnection(token) {
const index = this.connections?.findIndex((t) => t.token.equals(token));
if (index && index >= 0)
this.connections?.splice(index, 1);
return this;
}
/**
* Returns true if tokens refer to the same asset
*/
equals(token) {
if (!token)
return false;
return (this.protocol === token.protocol &&
this.chainName === token.chainName &&
this.standard === token.standard &&
this.decimals === token.decimals &&
this.addressOrDenom.toLowerCase() ===
token.addressOrDenom.toLowerCase() &&
this.collateralAddressOrDenom?.toLowerCase() ===
token.collateralAddressOrDenom?.toLowerCase());
}
/**
* Two tokens may not be equal but may still represent the same underlying asset
* The cases for this include:
* 1) A HypCollateral contract token and its wrapped token (eg. EvmHypCollateral and ERC20)
* 2) A HypNative contract and its native currency (eg. EvmHypNative and Ether)
* 3) An IBC token and its native equivalent
* This is useful during fee estimation to determine if a TokenAmount for the transfer and the fee
* are actually fungible (represent the same asset).
* @returns true if the tokens represent the same underlying asset
*/
isFungibleWith(token) {
if (!token || token.chainName !== this.chainName)
return false;
if (this.equals(token))
return true;
if (this.isCollateralized()) {
if (this.collateralAddressOrDenom &&
eqAddress(this.collateralAddressOrDenom, token.addressOrDenom)) {
return true;
}
if (!this.collateralAddressOrDenom &&
(token.isNative() || token.isHypNative())) {
return true;
}
}
if (this.standard === TokenStandard.CosmosIbc &&
token.standard === TokenStandard.CosmosNative &&
this.addressOrDenom.toLowerCase() === token.addressOrDenom.toLowerCase()) {
return true;
}
return false;
}
}
export function getCollateralTokenAdapter({ chainName, multiProvider, tokenAddress, }) {
const protocolType = multiProvider.getProtocol(chainName);
// ERC20s
if (protocolType === ProtocolType.Ethereum) {
return new EvmTokenAdapter(chainName, multiProvider, {
token: tokenAddress,
});
}
// SPL and SPL2022
else if (protocolType === ProtocolType.Sealevel) {
return new SealevelTokenAdapter(chainName, multiProvider, {
token: tokenAddress,
});
}
else if (protocolType === ProtocolType.Starknet) {
return new StarknetTokenAdapter(chainName, multiProvider, {
tokenAddress,
});
}
else if (protocolType === ProtocolType.Radix) {
return new RadixTokenAdapter(chainName, multiProvider, {
token: tokenAddress,
});
}
else {
throw new Error(`Unsupported protocol ${protocolType} for retrieving collateral token adapter on chain ${chainName}`);
}
}
//# sourceMappingURL=Token.js.map