diff --git a/dist/index.d.mts b/dist/index.d.mts index bde7658..4816e93 100644 --- a/dist/index.d.mts +++ b/dist/index.d.mts @@ -1458,8 +1458,11 @@ declare class AggregatorClient extends grpc.Client implements IAggregatorClient type Environment = "production" | "development" | "staging"; declare const AUTH_KEY_HEADER = "authKey"; +interface RequestOptions { + authKey: string; +} interface GetKeyResponse { - key: string; + authKey: string; } interface ClientOption { endpoint: string; @@ -1493,16 +1496,16 @@ declare class BaseClient { readonly rpcClient: AggregatorClient; protected metadata: Metadata; constructor(opts: ClientOption); - setAuthKey(key: string): void; - getAuthKey(): string | undefined; - isAuthenticated(): boolean; + isAuthKeyValid(key: string): boolean; authWithAPIKey(apiKey: string, expiredAtEpoch: number): Promise; authWithSignature(address: string, signature: string, expiredAtEpoch: number): Promise; - protected _callRPC(method: string, request: TRequest | any): Promise; + protected _callRPC(method: string, request: TRequest | any, options?: RequestOptions): Promise; } declare class Client extends BaseClient { constructor(config: ClientOption); - getAddresses(address: string): Promise; + getAddresses(address: string, { authKey }: { + authKey: string; + }): Promise; createTask({ address, oracleContract, tokenContract, }: { address: string; tokenContract: string; @@ -1514,4 +1517,4 @@ declare class Client extends BaseClient { deleteTask(id: string): Promise; } -export { AUTH_KEY_HEADER, type BalanceResp, type ClientOption, type CreateTaskResponse, type Environment, type GetAddressesResponse, type GetKeyResponse, type ListTasksResponse, type Task, type TransactionResp, Client as default, getKeyRequestMessage }; +export { AUTH_KEY_HEADER, type BalanceResp, type ClientOption, type CreateTaskResponse, type Environment, type GetAddressesResponse, type GetKeyResponse, type ListTasksResponse, type RequestOptions, type Task, type TransactionResp, Client as default, getKeyRequestMessage }; diff --git a/dist/index.d.ts b/dist/index.d.ts index bde7658..4816e93 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -1458,8 +1458,11 @@ declare class AggregatorClient extends grpc.Client implements IAggregatorClient type Environment = "production" | "development" | "staging"; declare const AUTH_KEY_HEADER = "authKey"; +interface RequestOptions { + authKey: string; +} interface GetKeyResponse { - key: string; + authKey: string; } interface ClientOption { endpoint: string; @@ -1493,16 +1496,16 @@ declare class BaseClient { readonly rpcClient: AggregatorClient; protected metadata: Metadata; constructor(opts: ClientOption); - setAuthKey(key: string): void; - getAuthKey(): string | undefined; - isAuthenticated(): boolean; + isAuthKeyValid(key: string): boolean; authWithAPIKey(apiKey: string, expiredAtEpoch: number): Promise; authWithSignature(address: string, signature: string, expiredAtEpoch: number): Promise; - protected _callRPC(method: string, request: TRequest | any): Promise; + protected _callRPC(method: string, request: TRequest | any, options?: RequestOptions): Promise; } declare class Client extends BaseClient { constructor(config: ClientOption); - getAddresses(address: string): Promise; + getAddresses(address: string, { authKey }: { + authKey: string; + }): Promise; createTask({ address, oracleContract, tokenContract, }: { address: string; tokenContract: string; @@ -1514,4 +1517,4 @@ declare class Client extends BaseClient { deleteTask(id: string): Promise; } -export { AUTH_KEY_HEADER, type BalanceResp, type ClientOption, type CreateTaskResponse, type Environment, type GetAddressesResponse, type GetKeyResponse, type ListTasksResponse, type Task, type TransactionResp, Client as default, getKeyRequestMessage }; +export { AUTH_KEY_HEADER, type BalanceResp, type ClientOption, type CreateTaskResponse, type Environment, type GetAddressesResponse, type GetKeyResponse, type ListTasksResponse, type RequestOptions, type Task, type TransactionResp, Client as default, getKeyRequestMessage }; diff --git a/dist/index.js b/dist/index.js index d2f3538..7759986 100644 --- a/dist/index.js +++ b/dist/index.js @@ -4529,25 +4529,14 @@ var BaseClient = class { ); this.metadata = new import_grpc_js.Metadata(); } - setAuthKey(key) { - this.metadata.add(AUTH_KEY_HEADER, key); - } - getAuthKey() { - const authKey = this.metadata.get(AUTH_KEY_HEADER); - return authKey?.[0]?.toString(); - } - isAuthenticated() { - const authKey = this.getAuthKey(); - if (!authKey) { - return false; - } + isAuthKeyValid(key) { try { - const [, payload] = authKey.split("."); + const [, payload] = key.split("."); const decodedPayload = JSON.parse(atob(payload)); const currentTimestamp = Math.floor(Date.now() / 1e3); return decodedPayload.exp > currentTimestamp; } catch (error) { - console.error("Error validating JWT token:", error); + console.error("Error validating auth key:", error); return false; } } @@ -4557,8 +4546,7 @@ var BaseClient = class { request.setExpiredAt(expiredAtEpoch); request.setSignature(apiKey); const result = await this._callRPC("getKey", request); - this.setAuthKey(result.getKey()); - return { key: result.getKey() }; + return { authKey: result.getKey() }; } // This flow can be used where the signature is generate from outside, such as in front-end and pass in async authWithSignature(address, signature, expiredAtEpoch) { @@ -4570,14 +4558,17 @@ var BaseClient = class { "getKey", request ); - this.setAuthKey(result.getKey()); - return { key: result.getKey() }; + return { authKey: result.getKey() }; } - _callRPC(method, request) { + _callRPC(method, request, options) { + const metadata = import_lodash.default.cloneDeep(this.metadata); + if (options?.authKey) { + metadata.set(AUTH_KEY_HEADER, options.authKey); + } return new Promise((resolve, reject) => { this.rpcClient[method].bind(this.rpcClient)( request, - this.metadata, + metadata, (error, response) => { if (error) reject(error); else resolve(response); @@ -4590,10 +4581,10 @@ var Client = class extends BaseClient { constructor(config) { super(config); } - async getAddresses(address) { + async getAddresses(address, { authKey }) { const request = new AddressRequest(); request.setOwner(address); - const result = await this._callRPC("getSmartAccountAddress", request); + const result = await this._callRPC("getSmartAccountAddress", request, { authKey }); return { owner: address, smart_account_address: result.getSmartAccountAddress() diff --git a/dist/index.mjs b/dist/index.mjs index 70f9ab5..f308505 100644 --- a/dist/index.mjs +++ b/dist/index.mjs @@ -4517,25 +4517,14 @@ var BaseClient = class { ); this.metadata = new Metadata(); } - setAuthKey(key) { - this.metadata.add(AUTH_KEY_HEADER, key); - } - getAuthKey() { - const authKey = this.metadata.get(AUTH_KEY_HEADER); - return authKey?.[0]?.toString(); - } - isAuthenticated() { - const authKey = this.getAuthKey(); - if (!authKey) { - return false; - } + isAuthKeyValid(key) { try { - const [, payload] = authKey.split("."); + const [, payload] = key.split("."); const decodedPayload = JSON.parse(atob(payload)); const currentTimestamp = Math.floor(Date.now() / 1e3); return decodedPayload.exp > currentTimestamp; } catch (error) { - console.error("Error validating JWT token:", error); + console.error("Error validating auth key:", error); return false; } } @@ -4545,8 +4534,7 @@ var BaseClient = class { request.setExpiredAt(expiredAtEpoch); request.setSignature(apiKey); const result = await this._callRPC("getKey", request); - this.setAuthKey(result.getKey()); - return { key: result.getKey() }; + return { authKey: result.getKey() }; } // This flow can be used where the signature is generate from outside, such as in front-end and pass in async authWithSignature(address, signature, expiredAtEpoch) { @@ -4558,14 +4546,17 @@ var BaseClient = class { "getKey", request ); - this.setAuthKey(result.getKey()); - return { key: result.getKey() }; + return { authKey: result.getKey() }; } - _callRPC(method, request) { + _callRPC(method, request, options) { + const metadata = _.cloneDeep(this.metadata); + if (options?.authKey) { + metadata.set(AUTH_KEY_HEADER, options.authKey); + } return new Promise((resolve, reject) => { this.rpcClient[method].bind(this.rpcClient)( request, - this.metadata, + metadata, (error, response) => { if (error) reject(error); else resolve(response); @@ -4578,10 +4569,10 @@ var Client = class extends BaseClient { constructor(config) { super(config); } - async getAddresses(address) { + async getAddresses(address, { authKey }) { const request = new AddressRequest(); request.setOwner(address); - const result = await this._callRPC("getSmartAccountAddress", request); + const result = await this._callRPC("getSmartAccountAddress", request, { authKey }); return { owner: address, smart_account_address: result.getSmartAccountAddress() diff --git a/src/index.ts b/src/index.ts index 63c7688..6980e76 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,7 +7,7 @@ import { AggregatorClient } from "../grpc_codegen/avs_grpc_pb"; import * as avs_pb from "../grpc_codegen/avs_pb"; import { BoolValue } from "google-protobuf/google/protobuf/wrappers_pb"; import Task from "./task"; -import { AUTH_KEY_HEADER } from "./types"; +import { AUTH_KEY_HEADER, RequestOptions } from "./types"; // Move interfaces to a separate file, e.g., types.ts import { @@ -18,7 +18,6 @@ import { ListTasksResponse, } from "./types"; - class BaseClient { readonly endpoint: string; @@ -35,32 +34,17 @@ class BaseClient { this.metadata = new Metadata(); } - public setAuthKey(key: string): void { - this.metadata.add(AUTH_KEY_HEADER, key); - } - - public getAuthKey(): string | undefined { - const authKey = this.metadata.get(AUTH_KEY_HEADER); - return authKey?.[0]?.toString(); - } - - public isAuthenticated(): boolean { - const authKey = this.getAuthKey(); - - if (!authKey) { - return false; - } - + public isAuthKeyValid(key: string): boolean { try { // Decode the JWT token (without verifying the signature) - const [, payload] = authKey.split("."); + const [, payload] = key.split("."); const decodedPayload = JSON.parse(atob(payload)); // Check if the token has expired const currentTimestamp = Math.floor(Date.now() / 1000); return decodedPayload.exp > currentTimestamp; } catch (error) { - console.error("Error validating JWT token:", error); + console.error("Error validating auth key:", error); return false; } } @@ -80,8 +64,7 @@ class BaseClient { avs_pb.GetKeyReq >("getKey", request); - this.setAuthKey(result.getKey()); - return { key: result.getKey() }; + return { authKey: result.getKey() }; } // This flow can be used where the signature is generate from outside, such as in front-end and pass in @@ -101,19 +84,26 @@ class BaseClient { request ); - this.setAuthKey(result.getKey()); - - return { key: result.getKey() }; + return { authKey: result.getKey() }; } protected _callRPC( method: string, - request: TRequest | any + request: TRequest | any, + options?: RequestOptions ): Promise { + // Clone the existing metadata from the client + const metadata = _.cloneDeep(this.metadata); + + // Add the auth key to the metadata as a header + if (options?.authKey) { + metadata.set(AUTH_KEY_HEADER, options.authKey); + } + return new Promise((resolve, reject) => { (this.rpcClient as any)[method].bind(this.rpcClient)( request, - this.metadata, + metadata, (error: any, response: TResponse) => { if (error) reject(error); else resolve(response); @@ -128,14 +118,17 @@ export default class Client extends BaseClient { super(config); } - async getAddresses(address: string): Promise { + async getAddresses( + address: string, + { authKey }: { authKey: string } + ): Promise { const request = new avs_pb.AddressRequest(); request.setOwner(address); const result = await this._callRPC< avs_pb.AddressResp, avs_pb.AddressRequest - >("getSmartAccountAddress", request); + >("getSmartAccountAddress", request, { authKey }); return { owner: address, diff --git a/src/types.ts b/src/types.ts index 1a96d05..c6b8b37 100644 --- a/src/types.ts +++ b/src/types.ts @@ -3,8 +3,12 @@ export type Environment = "production" | "development" | "staging"; export const AUTH_KEY_HEADER = "authKey"; +export interface RequestOptions { + authKey: string; +} + export interface GetKeyResponse { - key: string; + authKey: string; } export interface ClientOption { diff --git a/tests/getAddresses.test.ts b/tests/getAddresses.test.ts new file mode 100644 index 0000000..5c985e9 --- /dev/null +++ b/tests/getAddresses.test.ts @@ -0,0 +1,97 @@ +import { describe, beforeAll, test, expect } from "@jest/globals"; +import Client from "../dist/index.js"; +import dotenv from "dotenv"; +import path from "path"; +import { getAddress, generateSignature, requireEnvVar } from "./utils"; + +// Update the dotenv configuration +dotenv.config({ path: path.resolve(__dirname, "..", ".env.test") }); + +// Get environment variables with type safety +const { + TEST_API_KEY, + TEST_PRIVATE_KEY, + TOKEN_CONTRACT, + ORACLE_CONTRACT, + ENDPOINT, +} = { + TEST_API_KEY: requireEnvVar("TEST_API_KEY"), + TEST_PRIVATE_KEY: requireEnvVar("TEST_PRIVATE_KEY"), + TOKEN_CONTRACT: requireEnvVar("TOKEN_CONTRACT"), + ORACLE_CONTRACT: requireEnvVar("ORACLE_CONTRACT"), + ENDPOINT: requireEnvVar("ENDPOINT"), +} as const; + +// Define EXPIRED_AT as a constant +const EXPIRED_AT = Math.floor(Date.now() / 1000) + 24 * 60 * 60; // 24 hours from now + +describe("getAddresses Tests", () => { + let ownerAddress: string; + let client: Client; + + beforeAll(async () => { + ownerAddress = await getAddress(TEST_PRIVATE_KEY); + console.log("Client endpoint:", ENDPOINT, "\nOwner address:", ownerAddress); + + // Initialize the client with test credentials + client = new Client({ + endpoint: ENDPOINT, + }); + }); + + describe("Auth with Signature", () => { + let authKey: string; + let smartWallet: string; + + beforeAll(async () => { + console.log("Authenticating with signature ..."); + const signature = await generateSignature(TEST_PRIVATE_KEY, EXPIRED_AT); + const res = await client.authWithSignature( + ownerAddress, + signature, + EXPIRED_AT + ); + authKey = res.authKey; + }); + + test("should get addresses when authenticated with signature", async () => { + const result = await client.getAddresses(ownerAddress, { authKey }); + expect(result.smart_account_address).toBeDefined(); + smartWallet = result.smart_account_address; + console.log("Smart wallet address:", smartWallet); + }); + + test("should return empty when getting address with smartWallet using signature", async () => { + const result = await client.getAddresses(smartWallet, { authKey }); + expect(result.smart_account_address).toBe(undefined); + }); + }); + + describe("Auth with API key", () => { + let authKey: string; + let smartWallet: string; + + beforeAll(async () => { + console.log("Authenticating with API key ..."); + const res = await client.authWithAPIKey(TEST_API_KEY, EXPIRED_AT); + authKey = res.authKey; + }); + + test("should get addresses when authenticated with API key", async () => { + const result = await client.getAddresses(ownerAddress, { authKey }); + expect(result.smart_account_address).toBeDefined(); + smartWallet = result.smart_account_address; + }); + + test("should return empty when getting address with smartWallet using API key", async () => { + const result = await client.getAddresses(smartWallet, { authKey }); + expect(result.smart_account_address).toBe(undefined); + }); + }); + + test("should throw error when getting address without authentication", async () => { + await expect( + client.getAddresses(ownerAddress, { authKey: "" }) + ).rejects.toThrow("missing auth header"); + }); +});