diff --git a/core/lib/dal/sqlx-data.json b/core/lib/dal/sqlx-data.json index 68c1731fd0f6..ee6b28a54ef4 100644 --- a/core/lib/dal/sqlx-data.json +++ b/core/lib/dal/sqlx-data.json @@ -8671,6 +8671,48 @@ }, "query": "\n UPDATE prover_jobs_fri\n SET\n status = 'sent_to_server',\n updated_at = NOW()\n WHERE\n l1_batch_number = $1\n " }, + "af74cb82724bdf9fa4f716cf2ee07a011c3ad3f13a2ed37c4bcce515786fc867": { + "describe": { + "columns": [ + { + "name": "l1_address", + "ordinal": 0, + "type_info": "Bytea" + }, + { + "name": "l2_address", + "ordinal": 1, + "type_info": "Bytea" + }, + { + "name": "name", + "ordinal": 2, + "type_info": "Varchar" + }, + { + "name": "symbol", + "ordinal": 3, + "type_info": "Varchar" + }, + { + "name": "decimals", + "ordinal": 4, + "type_info": "Int4" + } + ], + "nullable": [ + false, + false, + false, + false, + false + ], + "parameters": { + "Left": [] + } + }, + "query": "\n SELECT\n l1_address,\n l2_address,\n NAME,\n symbol,\n decimals\n FROM\n tokens\n ORDER BY\n symbol\n " + }, "afc24bd1407dba82cd3dc9e7ee71ac4ab2d73bda6022700aeb0a630a2563a4b4": { "describe": { "columns": [], diff --git a/core/lib/dal/src/tokens_web3_dal.rs b/core/lib/dal/src/tokens_web3_dal.rs index 6c990f964083..6e40cfdbd7d8 100644 --- a/core/lib/dal/src/tokens_web3_dal.rs +++ b/core/lib/dal/src/tokens_web3_dal.rs @@ -1,4 +1,7 @@ -use zksync_types::{tokens::TokenPrice, Address}; +use zksync_types::{ + tokens::{TokenInfo, TokenMetadata, TokenPrice}, + Address, +}; use crate::{models::storage_token::StorageTokenPrice, SqlxError, StorageProcessor}; @@ -8,6 +11,40 @@ pub struct TokensWeb3Dal<'a, 'c> { } impl TokensWeb3Dal<'_, '_> { + pub async fn get_well_known_tokens(&mut self) -> Result, SqlxError> { + { + let records = sqlx::query!( + r#" + SELECT + l1_address, + l2_address, + NAME, + symbol, + decimals + FROM + tokens + ORDER BY + symbol + "# + ) + .fetch_all(self.storage.conn()) + .await?; + let result: Vec = records + .into_iter() + .map(|record| TokenInfo { + l1_address: Address::from_slice(&record.l1_address), + l2_address: Address::from_slice(&record.l2_address), + metadata: TokenMetadata { + name: record.name, + symbol: record.symbol, + decimals: record.decimals as u8, + }, + }) + .collect(); + Ok(result) + } + } + pub async fn get_token_price( &mut self, l2_address: &Address, diff --git a/core/lib/web3_decl/src/namespaces/zks.rs b/core/lib/web3_decl/src/namespaces/zks.rs index 473a7192e284..1a8922dcb28b 100644 --- a/core/lib/web3_decl/src/namespaces/zks.rs +++ b/core/lib/web3_decl/src/namespaces/zks.rs @@ -12,6 +12,8 @@ use zksync_types::{ Address, L1BatchNumber, MiniblockNumber, H256, U256, U64, }; +use crate::types::Token; + #[cfg_attr( all(feature = "client", feature = "server"), rpc(server, client, namespace = "zks") @@ -43,6 +45,8 @@ pub trait ZksNamespace { #[method(name = "L1ChainId")] async fn l1_chain_id(&self) -> RpcResult; + #[method(name = "getConfirmedTokens")] + async fn get_confirmed_tokens(&self, from: u32, limit: u8) -> RpcResult>; #[method(name = "getTokenPrice")] async fn get_token_price(&self, token_address: Address) -> RpcResult; diff --git a/core/lib/web3_decl/src/types.rs b/core/lib/web3_decl/src/types.rs index 2b690e76b4ec..61a3e10397c1 100644 --- a/core/lib/web3_decl/src/types.rs +++ b/core/lib/web3_decl/src/types.rs @@ -26,6 +26,17 @@ pub use zksync_types::{ }, }; +/// Token in the zkSync network +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Token { + pub l1_address: Address, + pub l2_address: Address, + pub name: String, + pub symbol: String, + pub decimals: u8, +} + /// Helper structure used to parse deserialized `Ethereum` transaction. #[derive(Clone, Debug, Eq, PartialEq)] pub struct TransactionCalldata { diff --git a/core/lib/zksync_core/src/api_server/web3/backend_jsonrpsee/namespaces/zks.rs b/core/lib/zksync_core/src/api_server/web3/backend_jsonrpsee/namespaces/zks.rs index 14b911311c8d..01afdd5a357f 100644 --- a/core/lib/zksync_core/src/api_server/web3/backend_jsonrpsee/namespaces/zks.rs +++ b/core/lib/zksync_core/src/api_server/web3/backend_jsonrpsee/namespaces/zks.rs @@ -13,6 +13,7 @@ use zksync_types::{ use zksync_web3_decl::{ jsonrpsee::core::{async_trait, RpcResult}, namespaces::zks::ZksNamespaceServer, + types::Token, }; use crate::{ @@ -48,6 +49,12 @@ impl ZksNamespaceServer for ZksNa Ok(self.l1_chain_id_impl()) } + async fn get_confirmed_tokens(&self, from: u32, limit: u8) -> RpcResult> { + self.get_confirmed_tokens_impl(from, limit) + .await + .map_err(into_jsrpc_error) + } + async fn get_token_price(&self, token_address: Address) -> RpcResult { self.get_token_price_impl(token_address) .await diff --git a/core/lib/zksync_core/src/api_server/web3/namespaces/zks.rs b/core/lib/zksync_core/src/api_server/web3/namespaces/zks.rs index 2dbadcb776b0..f8f13e96af23 100644 --- a/core/lib/zksync_core/src/api_server/web3/namespaces/zks.rs +++ b/core/lib/zksync_core/src/api_server/web3/namespaces/zks.rs @@ -21,7 +21,7 @@ use zksync_types::{ use zksync_utils::{address_to_h256, ratio_to_big_decimal_normalized}; use zksync_web3_decl::{ error::Web3Error, - types::{Address, H256}, + types::{Address, Token, H256}, }; use crate::{ @@ -141,6 +141,41 @@ impl ZksNamespace { U64::from(*self.state.api_config.l1_chain_id) } + #[tracing::instrument(skip(self))] + pub async fn get_confirmed_tokens_impl( + &self, + from: u32, + limit: u8, + ) -> Result, Web3Error> { + const METHOD_NAME: &str = "get_confirmed_tokens"; + + let method_latency = API_METRICS.start_call(METHOD_NAME); + let tokens = self + .state + .connection_pool + .access_storage_tagged("api") + .await + .unwrap() + .tokens_web3_dal() + .get_well_known_tokens() + .await + .map_err(|err| internal_error(METHOD_NAME, err))? + .into_iter() + .skip(from as usize) + .take(limit.into()) + .map(|token_info| Token { + l1_address: token_info.l1_address, + l2_address: token_info.l2_address, + name: token_info.metadata.name, + symbol: token_info.metadata.symbol, + decimals: token_info.metadata.decimals, + }) + .collect(); + + method_latency.observe(); + Ok(tokens) + } + #[tracing::instrument(skip(self))] pub async fn get_token_price_impl(&self, l2_token: Address) -> Result { const METHOD_NAME: &str = "get_token_price"; diff --git a/core/tests/ts-integration/tests/api/web3.test.ts b/core/tests/ts-integration/tests/api/web3.test.ts index bda0aa6a730d..000b61276be3 100644 --- a/core/tests/ts-integration/tests/api/web3.test.ts +++ b/core/tests/ts-integration/tests/api/web3.test.ts @@ -201,6 +201,14 @@ describe('web3 API compatibility tests', () => { }); }); + test('Should test various token methods', async () => { + const tokens = await alice.provider.getConfirmedTokens(); + expect(tokens).not.toHaveLength(0); // Should not be an empty array. + + const price = await alice.provider.getTokenPrice(l2Token); + expect(+price!).toEqual(expect.any(Number)); + }); + test('Should check transactions from API / Legacy tx', async () => { const LEGACY_TX_TYPE = 0; const legacyTx = await alice.sendTransaction({