-
Notifications
You must be signed in to change notification settings - Fork 1
Lib: Add support to get tokens by chain ID #201
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| --- | ||
| "@mimicprotocol/lib-ts": patch | ||
| "@mimicprotocol/cli": patch | ||
| "@mimicprotocol/test-ts": patch | ||
| --- | ||
|
|
||
| Added Tokens class |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| import { ChainId } from '../types' | ||
|
|
||
| import { BlockchainToken } from './BlockchainToken' | ||
|
|
||
| /** | ||
| * Represents a token provider that can resolve to a specific BlockchainToken instance | ||
| * based on the provided chain ID. | ||
| * This allows for convenient access to common tokens across different chains. | ||
| * Supports both ERC20Token (EVM chains) and SPLToken (Solana). | ||
| */ | ||
| export class TokenProvider { | ||
| private _symbol: string | ||
| private _registry: Map<ChainId, BlockchainToken> | ||
|
|
||
| /** | ||
| * Creates a new TokenProvider instance. | ||
| * @param symbol - The token symbol (e.g., "USDC", "USDT") | ||
| */ | ||
| constructor(symbol: string) { | ||
| this._symbol = symbol | ||
| this._registry = new Map<ChainId, BlockchainToken>() | ||
| } | ||
|
|
||
| /** | ||
| * Registers a token for a specific chain. | ||
| * @param chainId - The chain ID to register the token for | ||
| * @param token - The BlockchainToken instance for this chain (ERC20Token or SPLToken) | ||
| * @returns The TokenProvider instance for method chaining | ||
| */ | ||
| register(chainId: ChainId, token: BlockchainToken): TokenProvider { | ||
| this._registry.set(chainId, token) | ||
| return this | ||
| } | ||
|
|
||
| /** | ||
| * Resolves the token provider to a specific BlockchainToken instance for the given chain. | ||
| * @param chainId - The chain ID to resolve the token for | ||
| * @returns The BlockchainToken instance for the specified chain | ||
| * @throws Error if the token is not supported on the requested chain | ||
| */ | ||
| on(chainId: i32): BlockchainToken { | ||
| if (!this.isSupported(chainId)) throw new Error(`Token ${this._symbol} is not registered on chain ${chainId}`) | ||
| return this._registry.get(chainId) | ||
| } | ||
|
|
||
| /** | ||
| * Checks if the token is supported on the given chain. | ||
| * @param chainId - The chain ID to check | ||
| * @returns True if the token is supported on the chain, false otherwise | ||
| */ | ||
| isSupported(chainId: i32): bool { | ||
| return this._registry.has(chainId) | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,129 @@ | ||
| import { Arbitrum, Base, BaseSepolia, Ethereum, Gnosis, Optimism, Sonic } from '../chains' | ||
|
|
||
| import { TokenProvider } from './TokenProvider' | ||
|
|
||
| /** | ||
| * Token providers that can resolve to chain-specific BlockchainToken instances. | ||
| * Use the `.on(chainId)` method to get the token for a specific chain. | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * import { Tokens, Ethereum } from '@mimicprotocol/lib-ts' | ||
| * | ||
| * const usdc = Tokens.USDC.on(inputs.chainId) | ||
| * const weth = Tokens.WETH.on(Ethereum.CHAIN_ID) | ||
| * ``` | ||
| */ | ||
| export class Tokens { | ||
| private static _instance: Tokens | null = null | ||
|
|
||
| private readonly usdc: TokenProvider = new TokenProvider('USDC') | ||
| private readonly usdt: TokenProvider = new TokenProvider('USDT') | ||
| private readonly dai: TokenProvider = new TokenProvider('DAI') | ||
| private readonly wbtc: TokenProvider = new TokenProvider('WBTC') | ||
| private readonly weth: TokenProvider = new TokenProvider('WETH') | ||
| private readonly eth: TokenProvider = new TokenProvider('ETH') | ||
| private readonly xdai: TokenProvider = new TokenProvider('xDAI') | ||
| private readonly sonic: TokenProvider = new TokenProvider('SONIC') | ||
| private readonly wxdai: TokenProvider = new TokenProvider('WXDAI') | ||
| private readonly wsonic: TokenProvider = new TokenProvider('WSONIC') | ||
|
|
||
| private constructor() { | ||
| // Ethereum | ||
| this.usdc.register(Ethereum.CHAIN_ID, Ethereum.USDC) | ||
| this.usdt.register(Ethereum.CHAIN_ID, Ethereum.USDT) | ||
| this.dai.register(Ethereum.CHAIN_ID, Ethereum.DAI) | ||
| this.wbtc.register(Ethereum.CHAIN_ID, Ethereum.WBTC) | ||
| this.weth.register(Ethereum.CHAIN_ID, Ethereum.WETH) | ||
| this.eth.register(Ethereum.CHAIN_ID, Ethereum.ETH) | ||
|
|
||
| // Arbitrum | ||
| this.usdc.register(Arbitrum.CHAIN_ID, Arbitrum.USDC) | ||
| this.usdt.register(Arbitrum.CHAIN_ID, Arbitrum.USDT) | ||
| this.dai.register(Arbitrum.CHAIN_ID, Arbitrum.DAI) | ||
| this.wbtc.register(Arbitrum.CHAIN_ID, Arbitrum.WBTC) | ||
| this.weth.register(Arbitrum.CHAIN_ID, Arbitrum.WETH) | ||
| this.eth.register(Arbitrum.CHAIN_ID, Arbitrum.ETH) | ||
|
|
||
| // Base | ||
| this.usdc.register(Base.CHAIN_ID, Base.USDC) | ||
| this.usdt.register(Base.CHAIN_ID, Base.USDT) | ||
| this.dai.register(Base.CHAIN_ID, Base.DAI) | ||
| this.wbtc.register(Base.CHAIN_ID, Base.WBTC) | ||
| this.weth.register(Base.CHAIN_ID, Base.WETH) | ||
| this.eth.register(Base.CHAIN_ID, Base.ETH) | ||
|
|
||
| // Optimism | ||
| this.usdc.register(Optimism.CHAIN_ID, Optimism.USDC) | ||
| this.usdt.register(Optimism.CHAIN_ID, Optimism.USDT) | ||
| this.dai.register(Optimism.CHAIN_ID, Optimism.DAI) | ||
| this.wbtc.register(Optimism.CHAIN_ID, Optimism.WBTC) | ||
| this.weth.register(Optimism.CHAIN_ID, Optimism.WETH) | ||
| this.eth.register(Optimism.CHAIN_ID, Optimism.ETH) | ||
|
|
||
| // Gnosis | ||
| this.usdc.register(Gnosis.CHAIN_ID, Gnosis.USDC) | ||
| this.usdt.register(Gnosis.CHAIN_ID, Gnosis.USDT) | ||
| this.wbtc.register(Gnosis.CHAIN_ID, Gnosis.WBTC) | ||
| this.weth.register(Gnosis.CHAIN_ID, Gnosis.WETH) | ||
| this.xdai.register(Gnosis.CHAIN_ID, Gnosis.xDAI) | ||
| this.wxdai.register(Gnosis.CHAIN_ID, Gnosis.WXDAI) | ||
|
|
||
| // Sonic | ||
| this.usdc.register(Sonic.CHAIN_ID, Sonic.USDC) | ||
| this.usdt.register(Sonic.CHAIN_ID, Sonic.USDT) | ||
| this.weth.register(Sonic.CHAIN_ID, Sonic.WETH) | ||
| this.sonic.register(Sonic.CHAIN_ID, Sonic.SONIC) | ||
| this.wsonic.register(Sonic.CHAIN_ID, Sonic.WSONIC) | ||
|
|
||
| // BaseSepolia | ||
| this.eth.register(BaseSepolia.CHAIN_ID, BaseSepolia.ETH) | ||
| } | ||
|
|
||
| private static getInstance(): Tokens { | ||
| if (Tokens._instance === null) { | ||
| Tokens._instance = new Tokens() | ||
| } | ||
| return Tokens._instance! | ||
| } | ||
|
|
||
| static get USDC(): TokenProvider { | ||
| return Tokens.getInstance().usdc | ||
| } | ||
|
|
||
| static get USDT(): TokenProvider { | ||
| return Tokens.getInstance().usdt | ||
| } | ||
|
|
||
| static get DAI(): TokenProvider { | ||
| return Tokens.getInstance().dai | ||
| } | ||
|
|
||
| static get WBTC(): TokenProvider { | ||
| return Tokens.getInstance().wbtc | ||
| } | ||
|
|
||
| static get WETH(): TokenProvider { | ||
| return Tokens.getInstance().weth | ||
| } | ||
|
|
||
| static get ETH(): TokenProvider { | ||
| return Tokens.getInstance().eth | ||
| } | ||
|
|
||
| static get XDAI(): TokenProvider { | ||
| return Tokens.getInstance().xdai | ||
| } | ||
|
|
||
| static get SONIC(): TokenProvider { | ||
| return Tokens.getInstance().sonic | ||
| } | ||
|
|
||
| static get WXDAI(): TokenProvider { | ||
| return Tokens.getInstance().wxdai | ||
| } | ||
|
|
||
| static get WSONIC(): TokenProvider { | ||
| return Tokens.getInstance().wsonic | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| import { ERC20Token, TokenProvider } from '../../src/tokens' | ||
| import { ChainId } from '../../src/types' | ||
|
|
||
| describe('TokenProvider', () => { | ||
| describe('register', () => { | ||
| describe('when registering a token for a chain', () => { | ||
| it('should register the token successfully', () => { | ||
| const provider = new TokenProvider('TEST') | ||
| const token = ERC20Token.native(ChainId.ETHEREUM) | ||
| provider.register(ChainId.ETHEREUM, token) | ||
|
|
||
| expect(provider.isSupported(ChainId.ETHEREUM)).toBe(true) | ||
| }) | ||
|
|
||
| it('should allow method chaining', () => { | ||
| const provider = new TokenProvider('TEST') | ||
| const token1 = ERC20Token.native(ChainId.ETHEREUM) | ||
| const token2 = ERC20Token.native(ChainId.ARBITRUM) | ||
|
|
||
| provider.register(ChainId.ETHEREUM, token1).register(ChainId.ARBITRUM, token2) | ||
|
|
||
| expect(provider.isSupported(ChainId.ETHEREUM)).toBe(true) | ||
| expect(provider.isSupported(ChainId.ARBITRUM)).toBe(true) | ||
| }) | ||
| }) | ||
| }) | ||
|
|
||
| describe('on', () => { | ||
| describe('when token is registered for the chain', () => { | ||
| it('should return the registered token', () => { | ||
| const provider = new TokenProvider('TEST') | ||
| const token = ERC20Token.native(ChainId.ETHEREUM) | ||
| provider.register(ChainId.ETHEREUM, token) | ||
|
|
||
| const resolved = provider.on(ChainId.ETHEREUM) | ||
|
|
||
| expect(resolved.address.toHexString()).toBe(token.address.toHexString()) | ||
| expect(resolved.chainId).toBe(ChainId.ETHEREUM) | ||
| expect(resolved.symbol).toBe(token.symbol) | ||
| expect(resolved.decimals).toBe(token.decimals) | ||
| }) | ||
| }) | ||
|
|
||
| describe('when token is not registered for the chain', () => { | ||
| it('should throw an error', () => { | ||
| expect(() => { | ||
| const provider = new TokenProvider('TEST') | ||
| provider.on(ChainId.ETHEREUM) | ||
| }).toThrow() | ||
| }) | ||
| }) | ||
| }) | ||
|
|
||
| describe('isSupported', () => { | ||
| describe('when token is registered for the chain', () => { | ||
| it('should return true', () => { | ||
| const provider = new TokenProvider('TEST') | ||
| const token = ERC20Token.native(ChainId.ETHEREUM) | ||
| provider.register(ChainId.ETHEREUM, token) | ||
|
|
||
| expect(provider.isSupported(ChainId.ETHEREUM)).toBe(true) | ||
| }) | ||
| }) | ||
|
|
||
| describe('when token is not registered for the chain', () => { | ||
| it('should return false', () => { | ||
| const provider = new TokenProvider('TEST') | ||
|
|
||
| expect(provider.isSupported(ChainId.ETHEREUM)).toBe(false) | ||
| expect(provider.isSupported(ChainId.ARBITRUM)).toBe(false) | ||
| }) | ||
| }) | ||
| }) | ||
| }) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| import { Arbitrum, Base, BaseSepolia, Ethereum, Gnosis, Optimism, Sonic } from '../../src/chains' | ||
| import { BlockchainToken, Tokens } from '../../src/tokens' | ||
|
|
||
| function expectTokenMatches(resolved: BlockchainToken, expected: BlockchainToken): void { | ||
| expect(resolved.chainId).toBe(expected.chainId) | ||
| expect(resolved.symbol).toBe(expected.symbol) | ||
| expect(resolved.address.toHexString()).toBe(expected.address.toHexString()) | ||
| expect(resolved.decimals).toBe(expected.decimals) | ||
| } | ||
|
|
||
| describe('Tokens', () => { | ||
| describe('USDC', () => { | ||
| describe('when accessing the static getter', () => { | ||
| it('should return a TokenProvider instance', () => { | ||
| const provider = Tokens.USDC | ||
|
|
||
| expect(provider).not.toBeNull() | ||
| expect(provider.isSupported(Ethereum.CHAIN_ID)).toBe(true) | ||
| expect(provider.isSupported(BaseSepolia.CHAIN_ID)).toBe(false) | ||
| }) | ||
| }) | ||
|
|
||
| describe('when resolving token for a chain', () => { | ||
| describe('when token is supported on chain', () => { | ||
| it('should return token for Ethereum', () => { | ||
| const token = Tokens.USDC.on(Ethereum.CHAIN_ID) | ||
| expectTokenMatches(token, Ethereum.USDC) | ||
| }) | ||
|
|
||
| it('should return token for Arbitrum', () => { | ||
| const token = Tokens.USDC.on(Arbitrum.CHAIN_ID) | ||
| expectTokenMatches(token, Arbitrum.USDC) | ||
| }) | ||
|
|
||
| it('should return token for Base', () => { | ||
| const token = Tokens.USDC.on(Base.CHAIN_ID) | ||
| expectTokenMatches(token, Base.USDC) | ||
| }) | ||
|
|
||
| it('should return token for Optimism', () => { | ||
| const token = Tokens.USDC.on(Optimism.CHAIN_ID) | ||
| expectTokenMatches(token, Optimism.USDC) | ||
| }) | ||
|
|
||
| it('should return token for Gnosis', () => { | ||
| const token = Tokens.USDC.on(Gnosis.CHAIN_ID) | ||
| expectTokenMatches(token, Gnosis.USDC) | ||
| }) | ||
|
|
||
| it('should return token for Sonic', () => { | ||
| const token = Tokens.USDC.on(Sonic.CHAIN_ID) | ||
| expectTokenMatches(token, Sonic.USDC) | ||
| }) | ||
| }) | ||
|
|
||
| describe('when token is not supported on chain', () => { | ||
| it('should throw an error', () => { | ||
| expect(() => { | ||
| Tokens.USDC.on(BaseSepolia.CHAIN_ID) | ||
| }).toThrow() | ||
| }) | ||
| }) | ||
| }) | ||
| }) | ||
| }) |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.