diff --git a/.github/.k8s/deploy.yml b/.github/.k8s/deploy.yml index a9775d7..f3e634e 100644 --- a/.github/.k8s/deploy.yml +++ b/.github/.k8s/deploy.yml @@ -63,6 +63,8 @@ spec: value: redis://redis.enstate.svc.cluster.local:6379 - name: RPC_URL value: https://eth.llamarpc.com,https://rpc.payload.de,https://rpc.ankr.com/eth + - name: UNIVERSAL_RESOLVER + value: 0xc0497E381f536Be9ce14B0dD3817cBcAe57d2F62 resources: requests: cpu: 100m diff --git a/.github/.k8s/deploy_goerli.yml b/.github/.k8s/deploy_goerli.yml new file mode 100644 index 0000000..774ea69 --- /dev/null +++ b/.github/.k8s/deploy_goerli.yml @@ -0,0 +1,112 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: redis-goerli + namespace: enstate +spec: + replicas: 1 + selector: + matchLabels: + app: redis-goerli + template: + metadata: + labels: + app: redis-goerli + spec: + containers: + - name: redis-goerli + image: redis:6.0.9-alpine + ports: + - containerPort: 6379 +--- +apiVersion: v1 +kind: Service +metadata: + name: redis-goerli + namespace: enstate +spec: + selector: + app: redis-goerli + ports: + - protocol: TCP + port: 6379 + targetPort: 6379 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: enstate-goerli + namespace: enstate +spec: + replicas: 2 + selector: + matchLabels: + app: enstate-goerli + template: + metadata: + labels: + app: enstate-goerli + spec: + containers: + - name: enstate-goerli + image: ghcr.io/v3xlabs/enstate:sha-749e1d2 + imagePullPolicy: Always + ports: + - containerPort: 3000 + env: + - name: OPENSEA_API_KEY + valueFrom: + secretKeyRef: + name: opensea-api-key + key: api-key + - name: REDIS_URL + value: redis://redis-goerli.enstate.svc.cluster.local:6379 + - name: RPC_URL + value: https://rpc.ankr.com/eth_goerli,https://ethereum-goerli.publicnode.com,https://goerli.gateway.tenderly.co + - name: UNIVERSAL_RESOLVER + value: 0x3952Be0b2186f8B113193a84b69bD71ad3fc1ae3 + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 100m + memory: 128Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: enstate-goerli + namespace: enstate +spec: + selector: + app: enstate-goerli + ports: + - protocol: TCP + port: 3000 + targetPort: 3000 +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: enstate-goerli + namespace: enstate + annotations: + cert-manager.io/issuer: le-http +spec: + ingressClassName: traefik + rules: + - host: goerli.enstate.rs + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: enstate-goerli + port: + number: 3000 + tls: + - hosts: + - goerli.enstate.rs + secretName: tls-goerli-enstate-ingress-http diff --git a/.github/.k8s/deploy_sepolia.yml b/.github/.k8s/deploy_sepolia.yml new file mode 100644 index 0000000..223f4d4 --- /dev/null +++ b/.github/.k8s/deploy_sepolia.yml @@ -0,0 +1,112 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: redis-sepolia + namespace: enstate +spec: + replicas: 1 + selector: + matchLabels: + app: redis-sepolia + template: + metadata: + labels: + app: redis-sepolia + spec: + containers: + - name: redis-sepolia + image: redis:6.0.9-alpine + ports: + - containerPort: 6379 +--- +apiVersion: v1 +kind: Service +metadata: + name: redis-sepolia + namespace: enstate +spec: + selector: + app: redis-sepolia + ports: + - protocol: TCP + port: 6379 + targetPort: 6379 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: enstate-sepolia + namespace: enstate +spec: + replicas: 2 + selector: + matchLabels: + app: enstate-sepolia + template: + metadata: + labels: + app: enstate-sepolia + spec: + containers: + - name: enstate-sepolia + image: ghcr.io/v3xlabs/enstate:sha-749e1d2 + imagePullPolicy: Always + ports: + - containerPort: 3000 + env: + - name: OPENSEA_API_KEY + valueFrom: + secretKeyRef: + name: opensea-api-key + key: api-key + - name: REDIS_URL + value: redis://redis-sepolia.enstate.svc.cluster.local:6379 + - name: RPC_URL + value: https://rpc.ankr.com/eth_sepolia,https://ethereum-sepolia.publicnode.com,https://sepolia.gateway.tenderly.co + - name: UNIVERSAL_RESOLVER + value: 0x21B000Fd62a880b2125A61e36a284BB757b76025 + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 100m + memory: 128Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: enstate-sepolia + namespace: enstate +spec: + selector: + app: enstate-sepolia + ports: + - protocol: TCP + port: 3000 + targetPort: 3000 +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: enstate-sepolia + namespace: enstate + annotations: + cert-manager.io/issuer: le-http +spec: + ingressClassName: traefik + rules: + - host: sepolia.enstate.rs + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: enstate-sepolia + port: + number: 3000 + tls: + - hosts: + - sepolia.enstate.rs + secretName: tls-sepolia-enstate-ingress-http diff --git a/.github/workflows/pr_check.yml b/.github/workflows/pr_check.yml index a39c070..639936a 100644 --- a/.github/workflows/pr_check.yml +++ b/.github/workflows/pr_check.yml @@ -40,5 +40,7 @@ jobs: - run: cargo check --target ${{ matrix.target }} --release working-directory: ${{ matrix.path }} test: + name: Test ENState 🧪 uses: ./.github/workflows/test.yml + secrets: inherit needs: [check] diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ad13403..de9e6e7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,6 +11,13 @@ jobs: strategy: matrix: suite: [server, worker] + include: + - suite: server + build: cargo build --release + env_file: .env + - suite: worker + build: bun install --global pnpm && pnpm install && pnpm build + env_file: .dev.vars steps: - uses: actions/checkout@v3 with: @@ -30,12 +37,23 @@ jobs: - run: bun install working-directory: test - - run: bun install --global wrangler - if: ${{ matrix.suite == 'worker' }} + - name: Set-up environment > ${{ matrix.env_file }} + shell: bash + env: + RPC_URL: ${{ secrets.RPC_URL }} + OPENSEA_API_KEY: ${{ secrets.OPENSEA_API_KEY }} + run: | + cat < ${{ matrix.env_file }} + RPC_URL=$RPC_URL + OPENSEA_API_KEY=$OPENSEA_API_KEY + UNIVERSAL_RESOLVER=0xc0497E381f536Be9ce14B0dD3817cBcAe57d2F62 + EOF + working-directory: ${{ matrix.suite }} + + - name: Build + run: ${{ matrix.build }} + working-directory: ${{ matrix.suite }} - name: Test run: bun test ${{ matrix.suite }} working-directory: test - env: - RPC_URL: https://rpc.ankr.com/eth - OPENSEA_API_KEY: ${{ secrets.OPENSEA_API_KEY }} diff --git a/server/.env.example b/server/.env.example index f5784e5..2e204b8 100644 --- a/server/.env.example +++ b/server/.env.example @@ -1,6 +1,7 @@ REDIS_URL=redis://localhost:6379 RPC_URL=https://rpc.ankr.com/eth OPENSEA_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxx +UNIVERSAL_RESOLVER=0xc0497E381f536Be9ce14B0dD3817cBcAe57d2F62 # Optionally you can specify a comma-seperated list PROFILE_RECORDS, however if not provided there are sensible defaults # PROFILE_RECORDS=com.discord,com.twitter diff --git a/server/src/state.rs b/server/src/state.rs index 2ef96d2..b5bb461 100644 --- a/server/src/state.rs +++ b/server/src/state.rs @@ -1,4 +1,5 @@ use enstate_shared::cache::{CacheLayer, PassthroughCacheLayer}; +use ethers_core::types::H160; use std::env; use std::sync::Arc; @@ -62,6 +63,11 @@ impl AppState { let opensea_api_key = env::var("OPENSEA_API_KEY").expect("OPENSEA_API_KEY should've been set"); + let universal_resolver = env::var("UNIVERSAL_RESOLVER") + .expect("UNIVERSAL_RESOLVER should've been set") + .parse::() + .expect("UNIVERSAL_RESOLVER should be a valid address"); + Self { service: ProfileService { cache, @@ -69,6 +75,7 @@ impl AppState { opensea_api_key, profile_records: Arc::from(profile_records), profile_chains: Arc::from(multicoin_chains), + universal_resolver, }, } } diff --git a/shared/src/models/eip155/mod.rs b/shared/src/models/eip155/mod.rs index d8bf06a..43db4d3 100644 --- a/shared/src/models/eip155/mod.rs +++ b/shared/src/models/eip155/mod.rs @@ -97,7 +97,7 @@ pub async fn resolve_eip155( // Example url: https://my.nft.metadata.test/1234/2257 // Content: json encoded {name: "", description: "", image: "", ...} let mut token_metadata_url = result - .get(0) + .first() // should never trigger .ok_or_else(|| EIP155Error::ImplementationError("".to_string()))? .to_string(); @@ -129,8 +129,11 @@ pub async fn resolve_eip155( #[cfg(test)] mod tests { use std::env; + use std::sync::Arc; use ethers::middleware::MiddlewareBuilder; + use ethers::providers::{Http, Provider}; + use ethers_ccip_read::CCIPReadMiddleware; use super::*; @@ -138,7 +141,7 @@ mod tests { async fn test_calldata_avatar_erc721() { let provider = Provider::::try_from("https://rpc.ankr.com/eth") .unwrap() - .wrap_into(CCIPReadMiddleware::new); + .wrap_into(|it| CCIPReadMiddleware::new(Arc::from(it))); let opensea_api_key = env::var("OPENSEA_API_KEY").unwrap().to_string(); let data = resolve_eip155( @@ -159,7 +162,7 @@ mod tests { async fn test_calldata_avatar_erc1155() { let provider = Provider::::try_from("https://rpc.ankr.com/eth") .unwrap() - .wrap_into(CCIPReadMiddleware::new); + .wrap_into(|it| CCIPReadMiddleware::new(Arc::from(it))); let opensea_api_key = env::var("OPENSEA_API_KEY").unwrap().to_string(); let data = resolve_eip155( @@ -184,7 +187,7 @@ mod tests { async fn test_calldata_avatar_erc1155_opensea() { let provider = Provider::::try_from("https://rpc.ankr.com/eth") .unwrap() - .wrap_into(CCIPReadMiddleware::new); + .wrap_into(|it| CCIPReadMiddleware::new(Arc::from(it))); let opensea_api_key = env::var("OPENSEA_API_KEY").unwrap().to_string(); let data = resolve_eip155( diff --git a/shared/src/models/profile/mod.rs b/shared/src/models/profile/mod.rs index 71ba63b..e14149e 100644 --- a/shared/src/models/profile/mod.rs +++ b/shared/src/models/profile/mod.rs @@ -4,6 +4,7 @@ use std::sync::Arc; use ethers::prelude::Http; use ethers::providers::Provider; use ethers_ccip_read::CCIPReadMiddleware; +use ethers_core::types::H160; use serde::{Deserialize, Serialize}; use crate::models::multicoin::cointype::coins::CoinType; @@ -53,4 +54,5 @@ pub struct ProfileService { pub opensea_api_key: String, pub profile_records: Arc<[String]>, pub profile_chains: Arc<[CoinType]>, + pub universal_resolver: H160, } diff --git a/shared/src/models/profile/name.rs b/shared/src/models/profile/name.rs index fa953df..9f3fdb6 100644 --- a/shared/src/models/profile/name.rs +++ b/shared/src/models/profile/name.rs @@ -101,10 +101,10 @@ impl ProfileService { let mut resolves = Vec::new(); for chunk in calldata.chunks(50) { - resolves.push(resolve_universal(name, chunk, &rpc).await?); + resolves.push(resolve_universal(name, chunk, &rpc, &self.universal_resolver).await?); } - let Some((_, resolver, ccip_urls)) = resolves.get(0) else { + let Some((_, resolver, ccip_urls)) = resolves.first() else { return Err(ProfileError::ImplementationError(String::new())); }; diff --git a/shared/src/models/universal_resolver/mod.rs b/shared/src/models/universal_resolver/mod.rs index 1e1bfac..0f7dffc 100644 --- a/shared/src/models/universal_resolver/mod.rs +++ b/shared/src/models/universal_resolver/mod.rs @@ -9,7 +9,7 @@ use ethers_ccip_read::{CCIPReadMiddlewareError, CCIPRequest}; use ethers_contract::abigen; use ethers_core::abi; use ethers_core::abi::{ParamType, Token}; -use lazy_static::lazy_static; +use ethers_core::types::H160; use crate::models::lookup::ENSLookup; use crate::models::profile::CCIPProvider; @@ -25,13 +25,6 @@ abigen!( ]"#, ); -lazy_static! { - // Setup address of universal resolver - static ref UNIVERSAL_RESOLVER_ADDRESS: Address = "0xc0497E381f536Be9ce14B0dD3817cBcAe57d2F62" - .parse::
() - .expect("UNIVERSAL_RESOLVER_ADDRESS should be a valid address"); -} - const MAGIC_UNIVERSAL_RESOLVER_ERROR_MESSAGE: &str = "execution reverted: UniversalResolver: Wildcard on non-extended resolvers is not supported"; @@ -39,6 +32,7 @@ pub async fn resolve_universal( name: &str, data: &[Box], provider: &CCIPProvider, + universal_resolver: &H160, ) -> Result<(Vec>, Address, Vec), ProfileError> { let name_hash = namehash(name); @@ -63,7 +57,7 @@ pub async fn resolve_universal( let transaction_data = [resolve_selector, encoded_data].concat(); // Setup the transaction - typed_transaction.set_to(*UNIVERSAL_RESOLVER_ADDRESS); + typed_transaction.set_to(*universal_resolver); typed_transaction.set_data(Bytes::from(transaction_data)); // Call the transaction @@ -201,6 +195,7 @@ mod tests { "antony.sh", &calldata, &CCIPReadMiddleware::new(Arc::new(provider)), + &Address::from_str("0xc0497E381f536Be9ce14B0dD3817cBcAe57d2F62").unwrap(), ) .await .unwrap(); diff --git a/test/tests/worker.spec.ts b/test/tests/worker.spec.ts index 6b64ee6..84100ff 100644 --- a/test/tests/worker.spec.ts +++ b/test/tests/worker.spec.ts @@ -15,7 +15,7 @@ let server: Subprocess<'ignore', 'pipe', 'inherit'> | undefined; beforeAll(async () => { console.log('Building worker...'); - server = Bun.spawn(['wrangler', 'dev', '--port', '3000'], { cwd: '../worker' }); + server = Bun.spawn(['pnpm', 'dev', '--port', '3000'], { cwd: '../worker' }); console.log('Waiting for server to start...'); @@ -26,10 +26,11 @@ beforeAll(async () => { console.log('Attempting heartbeat...'); await fetch('http://0.0.0.0:3000/'); console.log('Heartbeat succes!'); + await new Promise((resolve) => setTimeout(resolve, 2000)); break; } catch { - console.log('Waiting another 1s for heartbeat...'); - await new Promise((resolve) => setTimeout(resolve, 1000)); + console.log('Waiting another 5s for heartbeat...'); + await new Promise((resolve) => setTimeout(resolve, 5000)); continue; } } diff --git a/worker/.dev.vars.example b/worker/.dev.vars.example index 692cc5e..6c08b01 100644 --- a/worker/.dev.vars.example +++ b/worker/.dev.vars.example @@ -1 +1,2 @@ OPENSEA_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +UNIVERSAL_RESOLVER=0xc0497E381f536Be9ce14B0dD3817cBcAe57d2F62s diff --git a/worker/.env.example b/worker/.env.example deleted file mode 100644 index 692cc5e..0000000 --- a/worker/.env.example +++ /dev/null @@ -1 +0,0 @@ -OPENSEA_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx diff --git a/worker/.gitignore b/worker/.gitignore index 9457d11..7854e39 100644 --- a/worker/.gitignore +++ b/worker/.gitignore @@ -1,6 +1,7 @@ node_modules/ .wrangler target/ +dist/ .env .idea/ .dev.vars diff --git a/worker/package.json b/worker/package.json index d03556e..c12cfe0 100644 --- a/worker/package.json +++ b/worker/package.json @@ -3,6 +3,7 @@ "version": "0.0.0", "private": true, "scripts": { + "build": "wrangler build", "deploy": "wrangler deploy", "dev": "wrangler dev" }, diff --git a/worker/src/lib.rs b/worker/src/lib.rs index b30188b..42fd861 100644 --- a/worker/src/lib.rs +++ b/worker/src/lib.rs @@ -7,6 +7,7 @@ use enstate_shared::models::profile::ProfileService; use enstate_shared::models::records::Records; use enstate_shared::utils::factory::SimpleFactory; use ethers::prelude::{Http, Provider}; +use ethers::types::H160; use http::StatusCode; use lazy_static::lazy_static; use worker::{event, Context, Cors, Env, Headers, Method, Request, Response, Router}; @@ -46,12 +47,20 @@ async fn main(req: Request, env: Env, _ctx: Context) -> worker::Result let rpc = Provider::::try_from(rpc_url) .map_err(|_| http_simple_status_error(StatusCode::BAD_REQUEST))?; + let universal_resolver = env + .var("UNIVERSAL_RESOLVER") + .expect("UNIVERSAL_RESOLVER should've been set") + .to_string() + .parse::() + .expect("UNIVERSAL_RESOLVER should be a valid address"); + let service = ProfileService { cache, rpc: Box::new(SimpleFactory::from(Arc::new(rpc))), opensea_api_key: opensea_api_key.to_string(), profile_records: Arc::from(profile_records), profile_chains: Arc::from(profile_chains), + universal_resolver, }; // TODO (@antony1060): I don't like this, there needs to be a better way