From 47456f81600811219994fa4ad9003197f63b41a0 Mon Sep 17 00:00:00 2001 From: Yaroslav Grishajev Date: Wed, 31 Jul 2024 17:42:49 +0200 Subject: [PATCH 1/3] feat(auth): implements basic anonymous user auth refs #247 --- apps/api/package.json | 2 + apps/api/src/app.ts | 8 +- apps/api/src/auth/index.ts | 0 .../auth/services/ability/ability.service.ts | 37 ++++++ .../api/src/auth/services/auth.interceptor.ts | 48 ++++++++ apps/api/src/auth/services/auth.service.ts | 53 +++++++++ .../controllers/wallet/wallet.controller.ts | 18 +-- .../user-wallet/user-wallet.repository.ts | 65 ++++++----- .../services/tx-signer/tx-signer.service.ts | 8 +- .../wallet-initializer.service.ts | 6 +- .../src/core/providers/request.provider.ts | 13 --- .../src/core/repositories/base.repository.ts | 35 ++++++ .../execution-context.service.ts | 34 ++++++ .../request-context.interceptor.ts | 24 ++++ .../request-storage.interceptor.ts | 35 ------ .../src/core/types/hono-interceptor.type.ts | 2 +- .../lib/drizzle-ability/drizzle-ability.ts | 108 ++++++++++++++++++ .../user/controllers/user/user.controller.ts | 13 ++- .../user/providers/current-user.provider.ts | 16 --- .../user/repositories/user/user.repository.ts | 30 +++-- .../create-anonymous-user.router.ts | 2 +- .../get-anonymous-user.router.ts | 2 +- .../user/{routes => }/schemas/user.schema.ts | 0 .../test/functional/anonymous-user.spec.ts | 29 ++++- .../api/test/functional/create-wallet.spec.ts | 42 ++++++- .../functional/sign-and-broadcast-tx.spec.ts | 90 +++++++++------ apps/api/test/services/wallet.service.ts | 4 +- .../src/components/user/UserProviders.tsx | 2 +- .../src/services/auth/auth-http.service.ts | 30 ----- .../src/services/auth/auth.service.ts | 26 +++++ .../src/services/http/http.service.ts | 7 +- .../src/services/user/user-http.service.ts | 18 +++ package-lock.json | 44 +++++++ 33 files changed, 653 insertions(+), 198 deletions(-) create mode 100644 apps/api/src/auth/index.ts create mode 100644 apps/api/src/auth/services/ability/ability.service.ts create mode 100644 apps/api/src/auth/services/auth.interceptor.ts create mode 100644 apps/api/src/auth/services/auth.service.ts delete mode 100644 apps/api/src/core/providers/request.provider.ts create mode 100644 apps/api/src/core/repositories/base.repository.ts create mode 100644 apps/api/src/core/services/execution-context/execution-context.service.ts create mode 100644 apps/api/src/core/services/request-storage/request-context.interceptor.ts delete mode 100644 apps/api/src/core/services/request-storage/request-storage.interceptor.ts create mode 100644 apps/api/src/lib/drizzle-ability/drizzle-ability.ts delete mode 100644 apps/api/src/user/providers/current-user.provider.ts rename apps/api/src/user/{routes => }/schemas/user.schema.ts (100%) delete mode 100644 apps/deploy-web/src/services/auth/auth-http.service.ts create mode 100644 apps/deploy-web/src/services/auth/auth.service.ts create mode 100644 apps/deploy-web/src/services/user/user-http.service.ts diff --git a/apps/api/package.json b/apps/api/package.json index d1c4aebd8..906e2d038 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -33,6 +33,7 @@ "@akashnetwork/akashjs": "^0.10.0", "@akashnetwork/database": "*", "@akashnetwork/http-sdk": "*", + "@casl/ability": "^6.7.1", "@chain-registry/assets": "^0.7.1", "@cosmjs/amino": "^0.32.4", "@cosmjs/crypto": "^0.32.4", @@ -49,6 +50,7 @@ "@opentelemetry/instrumentation-pino": "^0.41.0", "@opentelemetry/sdk-node": "^0.52.1", "@sentry/node": "^7.55.2", + "@ucast/core": "^1.10.2", "async-sema": "^3.1.1", "axios": "^1.7.2", "commander": "^12.1.0", diff --git a/apps/api/src/app.ts b/apps/api/src/app.ts index c16e8f13a..efb6bf3fc 100644 --- a/apps/api/src/app.ts +++ b/apps/api/src/app.ts @@ -10,11 +10,11 @@ import { Hono } from "hono"; import { cors } from "hono/cors"; import { container } from "tsyringe"; +import { AuthInterceptor } from "@src/auth/services/auth.interceptor"; import { HonoErrorHandlerService } from "@src/core/services/hono-error-handler/hono-error-handler.service"; import { HttpLoggerService } from "@src/core/services/http-logger/http-logger.service"; import { LoggerService } from "@src/core/services/logger/logger.service"; -import { RequestStorageInterceptor } from "@src/core/services/request-storage/request-storage.interceptor"; -import { CurrentUserInterceptor } from "@src/user/services/current-user/current-user.interceptor"; +import { RequestContextInterceptor } from "@src/core/services/request-storage/request-context.interceptor"; import packageJson from "../package.json"; import { chainDb, syncUserSchema, userDb } from "./db/dbConnection"; import { apiRouter } from "./routers/apiRouter"; @@ -64,8 +64,8 @@ const scheduler = new Scheduler({ }); appHono.use(container.resolve(HttpLoggerService).intercept()); -appHono.use(container.resolve(RequestStorageInterceptor).intercept()); -appHono.use(container.resolve(CurrentUserInterceptor).intercept()); +appHono.use(container.resolve(RequestContextInterceptor).intercept()); +appHono.use(container.resolve(AuthInterceptor).intercept()); appHono.use( "*", sentry({ diff --git a/apps/api/src/auth/index.ts b/apps/api/src/auth/index.ts new file mode 100644 index 000000000..e69de29bb diff --git a/apps/api/src/auth/services/ability/ability.service.ts b/apps/api/src/auth/services/ability/ability.service.ts new file mode 100644 index 000000000..41e38bb3e --- /dev/null +++ b/apps/api/src/auth/services/ability/ability.service.ts @@ -0,0 +1,37 @@ +import { Ability } from "@casl/ability"; +import template from "lodash/template"; +import { singleton } from "tsyringe"; + +@singleton() +export class AbilityService { + private readonly createRegularUserRules = template( + JSON.stringify([ + { action: ["create", "read", "sign"], subject: "UserWallet", conditions: { userId: "${user.id}" } }, + { action: "read", subject: "User", conditions: { id: "${user.id}" } } + ]) + ); + private readonly createRegularAnonymousUserRules = template( + JSON.stringify([ + { action: ["create", "read", "sign"], subject: "UserWallet", conditions: { userId: "${user.id}" } }, + { action: "read", subject: "User", conditions: { id: "${user.id}", userId: null } } + ]) + ); + + getAbilityForUser(user: { userId: string }) { + const rules = this.createRegularUserRules({ user }); + return new Ability(JSON.parse(rules)); + } + + getAbilityForAnonymousUser(user: { id: string }) { + const rules = this.createRegularAnonymousUserRules({ user }); + return new Ability(JSON.parse(rules)); + } + + getEmptyAbility() { + return new Ability([]); + } + + getSuperUserAbility() { + return new Ability([{ action: "manage", subject: "all" }]); + } +} diff --git a/apps/api/src/auth/services/auth.interceptor.ts b/apps/api/src/auth/services/auth.interceptor.ts new file mode 100644 index 000000000..4152c5fa5 --- /dev/null +++ b/apps/api/src/auth/services/auth.interceptor.ts @@ -0,0 +1,48 @@ +import { Context, Next } from "hono"; +import { singleton } from "tsyringe"; + +import { AbilityService } from "@src/auth/services/ability/ability.service"; +import { AuthService } from "@src/auth/services/auth.service"; +import { ExecutionContextService } from "@src/core/services/execution-context/execution-context.service"; +import type { HonoInterceptor } from "@src/core/types/hono-interceptor.type"; +import { getCurrentUserId } from "@src/middlewares/userMiddleware"; +import { UserRepository } from "@src/user/repositories"; + +@singleton() +export class AuthInterceptor implements HonoInterceptor { + constructor( + private readonly abilityService: AbilityService, + private readonly userRepository: UserRepository, + private readonly executionContextService: ExecutionContextService, + private readonly authService: AuthService + ) {} + + intercept() { + return async (c: Context, next: Next) => { + const userId = getCurrentUserId(c); + + if (userId) { + const currentUser = await this.userRepository.findByUserId(userId); + + this.authService.currentUser = currentUser; + this.authService.ability = currentUser ? this.abilityService.getAbilityForUser(currentUser) : this.abilityService.getEmptyAbility(); + + return await next(); + } + const anonymousUserId = c.req.header("x-anonymous-user-id"); + + if (anonymousUserId) { + const currentUser = await this.userRepository.findAnonymousById(anonymousUserId); + + this.authService.currentUser = currentUser; + this.authService.ability = currentUser ? this.abilityService.getAbilityForAnonymousUser(currentUser) : this.abilityService.getEmptyAbility(); + + return await next(); + } + + this.authService.ability = this.abilityService.getEmptyAbility(); + + return await next(); + }; + } +} diff --git a/apps/api/src/auth/services/auth.service.ts b/apps/api/src/auth/services/auth.service.ts new file mode 100644 index 000000000..a77efd66f --- /dev/null +++ b/apps/api/src/auth/services/auth.service.ts @@ -0,0 +1,53 @@ +import { Ability } from "@casl/ability"; +import assert from "http-assert"; +import { container, Lifecycle, scoped } from "tsyringe"; + +import { ExecutionContextService } from "@src/core/services/execution-context/execution-context.service"; +import { UserOutput } from "@src/user/repositories"; + +@scoped(Lifecycle.ResolutionScoped) +export class AuthService { + constructor(private readonly executionContextService: ExecutionContextService) {} + + set currentUser(user: UserOutput) { + this.executionContextService.set("CURRENT_USER", user); + } + + get currentUser(): UserOutput { + return this.executionContextService.get("CURRENT_USER"); + } + + set ability(ability: Ability) { + this.executionContextService.set("ABILITY", ability); + } + + get ability(): Ability { + return this.executionContextService.get("ABILITY"); + } + + get isAuthenticated(): boolean { + return !!this.currentUser; + } + + throwUnlessCan(action: string, subject: string) { + assert(this.ability.can(action, subject), 403); + } +} + +export const Protected = (rules?: { action: string; subject: string }[]) => (target: any, propertyKey: string, descriptor: PropertyDescriptor) => { + const originalMethod = descriptor.value; + + descriptor.value = function protectedFunction(...args: any[]) { + const authService = container.resolve(AuthService); + + assert(authService.isAuthenticated, 401); + + if (rules) { + rules.forEach(rule => authService.throwUnlessCan(rule.action, rule.subject)); + } + + return originalMethod.apply(this, args); + }; + + return descriptor; +}; diff --git a/apps/api/src/billing/controllers/wallet/wallet.controller.ts b/apps/api/src/billing/controllers/wallet/wallet.controller.ts index 3e4e469e5..c413daef5 100644 --- a/apps/api/src/billing/controllers/wallet/wallet.controller.ts +++ b/apps/api/src/billing/controllers/wallet/wallet.controller.ts @@ -1,39 +1,43 @@ import type { EncodeObject } from "@cosmjs/proto-signing"; import pick from "lodash/pick"; -import { singleton } from "tsyringe"; +import { Lifecycle, scoped } from "tsyringe"; +import { AuthService, Protected } from "@src/auth/services/auth.service"; import type { WalletListOutputResponse, WalletOutputResponse } from "@src/billing/http-schemas/wallet.schema"; import { UserWalletRepository } from "@src/billing/repositories"; import type { CreateWalletRequestInput, SignTxRequestInput, SignTxResponseOutput } from "@src/billing/routes"; import { GetWalletQuery } from "@src/billing/routes/get-wallet-list/get-wallet-list.router"; -import { ManagedUserWalletService, WalletInitializerService } from "@src/billing/services"; +import { WalletInitializerService } from "@src/billing/services"; import { RefillService } from "@src/billing/services/refill/refill.service"; import { TxSignerService } from "@src/billing/services/tx-signer/tx-signer.service"; -// TODO: authorize endpoints below -@singleton() +@scoped(Lifecycle.ResolutionScoped) export class WalletController { constructor( - private readonly walletManager: ManagedUserWalletService, private readonly userWalletRepository: UserWalletRepository, private readonly walletInitializer: WalletInitializerService, private readonly signerService: TxSignerService, - private readonly refillService: RefillService + private readonly refillService: RefillService, + private readonly authService: AuthService ) {} + @Protected([{ action: "create", subject: "UserWallet" }]) async create({ data: { userId } }: CreateWalletRequestInput): Promise { return { data: await this.walletInitializer.initialize(userId) }; } + @Protected([{ action: "read", subject: "UserWallet" }]) async getWallets(query: GetWalletQuery): Promise { - const wallets = await this.userWalletRepository.find(query); + const wallets = await this.userWalletRepository.accessibleBy(this.authService.ability, "read").find(query); + return { data: wallets.map(wallet => pick(wallet, ["id", "userId", "address", "creditAmount"])) }; } + @Protected([{ action: "sign", subject: "UserWallet" }]) async signTx({ data: { userId, messages } }: SignTxRequestInput): Promise { return { data: await this.signerService.signAndBroadcast(userId, messages as EncodeObject[]) diff --git a/apps/api/src/billing/repositories/user-wallet/user-wallet.repository.ts b/apps/api/src/billing/repositories/user-wallet/user-wallet.repository.ts index 0c0060f13..5b4827751 100644 --- a/apps/api/src/billing/repositories/user-wallet/user-wallet.repository.ts +++ b/apps/api/src/billing/repositories/user-wallet/user-wallet.repository.ts @@ -4,6 +4,7 @@ import { singleton } from "tsyringe"; import { InjectUserWalletSchema, UserWalletSchema } from "@src/billing/providers"; import { ApiPgDatabase, InjectPg } from "@src/core/providers"; +import { AbilityParams, BaseRepository } from "@src/core/repositories/base.repository"; import { TxService } from "@src/core/services"; export type UserWalletInput = Partial; @@ -18,35 +19,37 @@ export interface ListOptions { } @singleton() -export class UserWalletRepository { - get cursor() { - return this.txManager.getPgTx() || this.pg; +export class UserWalletRepository extends BaseRepository { + constructor( + @InjectPg() protected readonly pg: ApiPgDatabase, + @InjectUserWalletSchema() protected readonly schema: UserWalletSchema, + protected readonly txManager: TxService + ) { + super(pg, schema, txManager, "UserWallet"); } - constructor( - @InjectPg() private readonly pg: ApiPgDatabase, - @InjectUserWalletSchema() private readonly userWallet: UserWalletSchema, - private readonly txManager: TxService - ) {} + accessibleBy(...abilityParams: AbilityParams) { + return new UserWalletRepository(this.pg, this.schema, this.txManager).withAbility(...abilityParams) as this; + } async create(input: Pick) { - return this.toOutput( - first( - await this.cursor - .insert(this.userWallet) - .values({ - userId: input.userId, - address: input.address - }) - .returning() - ) - ); + const value = { + userId: input.userId, + address: input.address + }; + + this.ability?.throwUnlessCanExecute(value); + + return this.toOutput(first(await this.cursor.insert(this.schema).values(value).returning())); } async updateById(id: UserWalletOutput["id"], payload: Partial, options?: { returning: true }): Promise; async updateById(id: UserWalletOutput["id"], payload: Partial): Promise; async updateById(id: UserWalletOutput["id"], payload: Partial, options?: { returning: boolean }): Promise { - const cursor = this.cursor.update(this.userWallet).set(payload).where(eq(this.userWallet.id, id)); + const cursor = this.cursor + .update(this.schema) + .set(payload) + .where(this.whereAccessibleBy(eq(this.schema.id, id))); if (options?.returning) { const items = await cursor.returning(); @@ -60,36 +63,40 @@ export class UserWalletRepository { async find(query?: Partial) { const fields = query && (Object.keys(query) as Array); - const where = fields?.length ? and(...fields.map(field => eq(this.userWallet[field], query[field]))) : undefined; + const where = fields?.length ? and(...fields.map(field => eq(this.schema[field], query[field]))) : undefined; return this.toOutputList( await this.cursor.query.userWalletSchema.findMany({ - where + where: this.whereAccessibleBy(where) }) ); } async findDrainingWallets(thresholds = { fee: 0, deployment: 0 }, options?: Pick) { + const where = or(lte(this.schema.deploymentAllowance, thresholds.deployment.toString()), lte(this.schema.feeAllowance, thresholds.fee.toString())); + return this.toOutputList( await this.cursor.query.userWalletSchema.findMany({ - where: or(lte(this.userWallet.deploymentAllowance, thresholds.deployment.toString()), lte(this.userWallet.feeAllowance, thresholds.fee.toString())), + where: this.whereAccessibleBy(where), limit: options?.limit || 10 }) ); } async findByUserId(userId: UserWalletOutput["userId"]) { - return this.toOutput(await this.cursor.query.userWalletSchema.findFirst({ where: eq(this.userWallet.userId, userId) })); + return this.toOutput(await this.cursor.query.userWalletSchema.findFirst({ where: this.whereAccessibleBy(eq(this.schema.userId, userId)) })); } private toOutputList(dbOutput: UserWalletSchema["$inferSelect"][]): UserWalletOutput[] { return dbOutput.map(item => this.toOutput(item)); } - private toOutput(dbOutput: UserWalletSchema["$inferSelect"]): UserWalletOutput { - return { - ...dbOutput, - creditAmount: parseFloat(dbOutput.deploymentAllowance) + parseFloat(dbOutput.feeAllowance) - }; + private toOutput(dbOutput?: UserWalletSchema["$inferSelect"]): UserWalletOutput { + return ( + dbOutput && { + ...dbOutput, + creditAmount: parseFloat(dbOutput.deploymentAllowance) + parseFloat(dbOutput.feeAllowance) + } + ); } } diff --git a/apps/api/src/billing/services/tx-signer/tx-signer.service.ts b/apps/api/src/billing/services/tx-signer/tx-signer.service.ts index 637d23cdd..6deeecd91 100644 --- a/apps/api/src/billing/services/tx-signer/tx-signer.service.ts +++ b/apps/api/src/billing/services/tx-signer/tx-signer.service.ts @@ -6,6 +6,7 @@ import assert from "http-assert"; import pick from "lodash/pick"; import { singleton } from "tsyringe"; +import { AuthService } from "@src/auth/services/auth.service"; import { BillingConfig, InjectBillingConfig } from "@src/billing/providers"; import { InjectTypeRegistry } from "@src/billing/providers/type-registry.provider"; import { UserWalletOutput, UserWalletRepository } from "@src/billing/repositories"; @@ -28,12 +29,13 @@ export class TxSignerService { @InjectTypeRegistry() private readonly registry: Registry, private readonly userWalletRepository: UserWalletRepository, private readonly masterWalletService: MasterWalletService, - private readonly balancesService: BalancesService + private readonly balancesService: BalancesService, + private readonly authService: AuthService ) {} async signAndBroadcast(userId: UserWalletOutput["userId"], messages: StringifiedEncodeObject[]) { - const userWallet = await this.userWalletRepository.findByUserId(userId); - assert(userWallet, 403, "User wallet not found"); + const userWallet = await this.userWalletRepository.accessibleBy(this.authService.ability, "sign").findByUserId(userId); + assert(userWallet, 403); const decodedMessages = this.decodeMessages(messages); diff --git a/apps/api/src/billing/services/wallet-initializer/wallet-initializer.service.ts b/apps/api/src/billing/services/wallet-initializer/wallet-initializer.service.ts index 305f80a13..7e96a658d 100644 --- a/apps/api/src/billing/services/wallet-initializer/wallet-initializer.service.ts +++ b/apps/api/src/billing/services/wallet-initializer/wallet-initializer.service.ts @@ -1,6 +1,7 @@ import pick from "lodash/pick"; import { singleton } from "tsyringe"; +import { AuthService } from "@src/auth/services/auth.service"; import { UserWalletInput, UserWalletRepository } from "@src/billing/repositories"; import { ManagedUserWalletService } from "@src/billing/services"; import { WithTransaction } from "@src/core/services"; @@ -9,12 +10,13 @@ import { WithTransaction } from "@src/core/services"; export class WalletInitializerService { constructor( private readonly walletManager: ManagedUserWalletService, - private readonly userWalletRepository: UserWalletRepository + private readonly userWalletRepository: UserWalletRepository, + private readonly authService: AuthService ) {} @WithTransaction() async initialize(userId: UserWalletInput["userId"]) { - const { id } = await this.userWalletRepository.create({ userId }); + const { id } = await this.userWalletRepository.accessibleBy(this.authService.ability, "create").create({ userId }); const wallet = await this.walletManager.createAndAuthorizeTrialSpending({ addressIndex: id }); const userWallet = await this.userWalletRepository.updateById( id, diff --git a/apps/api/src/core/providers/request.provider.ts b/apps/api/src/core/providers/request.provider.ts deleted file mode 100644 index 64e3a8b68..000000000 --- a/apps/api/src/core/providers/request.provider.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { container, inject } from "tsyringe"; - -import { RequestStorageInterceptor } from "@src/core/services/request-storage/request-storage.interceptor"; - -const REQUEST = "REQUEST"; - -container.register(REQUEST, { - useFactory: c => { - return c.resolve(RequestStorageInterceptor).context; - } -}); - -export const Request = () => inject(REQUEST); diff --git a/apps/api/src/core/repositories/base.repository.ts b/apps/api/src/core/repositories/base.repository.ts new file mode 100644 index 000000000..a02bc6be4 --- /dev/null +++ b/apps/api/src/core/repositories/base.repository.ts @@ -0,0 +1,35 @@ +import { AnyAbility } from "@casl/ability"; +import { PgTableWithColumns } from "drizzle-orm/pg-core/table"; +import { SQL } from "drizzle-orm/sql/sql"; + +import { ApiPgDatabase, InjectPg, TxService } from "@src/core"; +import { DrizzleAbility } from "@src/lib/drizzle-ability/drizzle-ability"; +import { InjectUserSchema } from "@src/user/providers"; + +export type AbilityParams = [AnyAbility, Parameters[0]]; + +export abstract class BaseRepository> { + protected ability?: DrizzleAbility; + + get cursor() { + return this.txManager.getPgTx() || this.pg; + } + + constructor( + @InjectPg() protected readonly pg: ApiPgDatabase, + @InjectUserSchema() protected readonly schema: T, + protected readonly txManager: TxService, + protected readonly entityName: string + ) {} + + protected withAbility(ability: AnyAbility, action: Parameters[0]) { + this.ability = new DrizzleAbility(this.schema, ability, action, this.entityName); + return this; + } + + protected whereAccessibleBy(where: SQL) { + return this.ability?.whereAccessibleBy(where) || where; + } + + abstract accessibleBy(...abilityParams: AbilityParams): this; +} diff --git a/apps/api/src/core/services/execution-context/execution-context.service.ts b/apps/api/src/core/services/execution-context/execution-context.service.ts new file mode 100644 index 000000000..48ab0d9a8 --- /dev/null +++ b/apps/api/src/core/services/execution-context/execution-context.service.ts @@ -0,0 +1,34 @@ +import { AsyncLocalStorage } from "node:async_hooks"; +import { singleton } from "tsyringe"; + +@singleton() +export class ExecutionContextService { + private readonly storage = new AsyncLocalStorage>(); + + private get context() { + const store = this.storage.getStore(); + + if (!store) { + throw new Error("No context available"); + } + + return store; + } + + set(key: string, value: any) { + this.context.set(key, value); + } + + get(key: string) { + return this.context.get(key); + } + + async runWithContext(cb: (...args: any[]) => Promise): Promise { + return await new Promise((resolve, reject) => { + this.storage.run(new Map(), () => { + this.storage.getStore(); + cb().then(resolve).catch(reject); + }); + }); + } +} diff --git a/apps/api/src/core/services/request-storage/request-context.interceptor.ts b/apps/api/src/core/services/request-storage/request-context.interceptor.ts new file mode 100644 index 000000000..0c2830ead --- /dev/null +++ b/apps/api/src/core/services/request-storage/request-context.interceptor.ts @@ -0,0 +1,24 @@ +import { Context, Next } from "hono"; +import { AsyncLocalStorage } from "node:async_hooks"; +import { singleton } from "tsyringe"; + +import { ExecutionContextService } from "@src/core/services/execution-context/execution-context.service"; +import type { HonoInterceptor } from "@src/core/types/hono-interceptor.type"; + +@singleton() +export class RequestContextInterceptor implements HonoInterceptor { + private readonly HTTP_CONTEXT_KEY = "HTTP_CONTEXT"; + + private readonly storage = new AsyncLocalStorage>(); + + constructor(private readonly executionContextService: ExecutionContextService) {} + + intercept() { + return async (c: Context, next: Next) => { + await this.executionContextService.runWithContext(async () => { + this.executionContextService.set(this.HTTP_CONTEXT_KEY, c); + await next(); + }); + }; + } +} diff --git a/apps/api/src/core/services/request-storage/request-storage.interceptor.ts b/apps/api/src/core/services/request-storage/request-storage.interceptor.ts deleted file mode 100644 index f812f97d4..000000000 --- a/apps/api/src/core/services/request-storage/request-storage.interceptor.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { Context, Next } from "hono"; -import { AsyncLocalStorage } from "node:async_hooks"; -import { singleton } from "tsyringe"; -import { v4 as uuid } from "uuid"; - -import type { HonoInterceptor } from "@src/core/types/hono-interceptor.type"; - -@singleton() -export class RequestStorageInterceptor implements HonoInterceptor { - private readonly CONTEXT_KEY = "CONTEXT"; - - private readonly storage = new AsyncLocalStorage>(); - - get context() { - return this.storage.getStore()?.get(this.CONTEXT_KEY); - } - - intercept() { - return async (c: Context, next: Next) => { - const requestId = c.req.header("X-Request-Id") || uuid(); - c.set("requestId", requestId); - - await this.runWithContext(c, next); - }; - } - - private async runWithContext(context: Context, cb: () => Promise) { - return await new Promise((resolve, reject) => { - this.storage.run(new Map(), () => { - this.storage.getStore().set(this.CONTEXT_KEY, context); - cb().then(resolve).catch(reject); - }); - }); - } -} diff --git a/apps/api/src/core/types/hono-interceptor.type.ts b/apps/api/src/core/types/hono-interceptor.type.ts index ae3a32e49..3f5310096 100644 --- a/apps/api/src/core/types/hono-interceptor.type.ts +++ b/apps/api/src/core/types/hono-interceptor.type.ts @@ -1,5 +1,5 @@ import type { MiddlewareHandler } from "hono"; export interface HonoInterceptor { - intercept(): MiddlewareHandler; + intercept(options?: any): MiddlewareHandler; } diff --git a/apps/api/src/lib/drizzle-ability/drizzle-ability.ts b/apps/api/src/lib/drizzle-ability/drizzle-ability.ts new file mode 100644 index 000000000..28a511493 --- /dev/null +++ b/apps/api/src/lib/drizzle-ability/drizzle-ability.ts @@ -0,0 +1,108 @@ +import { Abilities, AnyAbility, CanParameters, ForbiddenError, subject } from "@casl/ability"; +import { rulesToQuery } from "@casl/ability/extra"; +import { CompoundCondition, FieldCondition } from "@ucast/core"; +import { and, BinaryOperator, eq, gt, gte, isNull, lt, lte, ne, or } from "drizzle-orm"; +import { PgTableWithColumns } from "drizzle-orm/pg-core/table"; +import { SQL } from "drizzle-orm/sql/sql"; + +export class DrizzleAbility, A extends AnyAbility = AnyAbility> { + private readonly OPS: Record = { + eq: eq, + ne: ne, + gt: gt, + gte: gte, + lt: lt, + lte: lte + }; + + private readonly OPS_INVERTED: Record = { + eq: "ne", + ne: "eq", + gt: "lte", + gte: "lt", + lt: "gte", + lte: "gt", + in: "nin", + nin: "in" + }; + private readonly abilityClause = this.toDrizzleWhereClause(); + + constructor( + private readonly table: T, + private readonly ability: A, + private readonly action: CanParameters[0], + private readonly subjectType: CanParameters[1] + ) {} + + throwUnlessCanExecute(payload: Record) { + const params = [this.action, subject(this.subjectType as string, payload)] as unknown as Parameters; + ForbiddenError.from(this.ability).throwUnlessCan(...params); + } + + whereAccessibleBy(where: SQL) { + return this.abilityClause ? and(where, this.abilityClause) : where; + } + + private toDrizzleWhereClause() { + const params = [this.action, this.subjectType] as unknown as Parameters; + ForbiddenError.from(this.ability).throwUnlessCan(...params); + + const { $and = [], $or = [] } = rulesToQuery(this.ability, params[0], params[1], rule => { + if (!rule.ast) { + throw new Error("Unable to create knex query without AST"); + } + + if (rule.inverted) { + return { + ...rule.ast, + operator: this.OPS_INVERTED[rule.ast.operator] + }; + } + + return rule.ast; + }) as { $and: FieldCondition[]; $or: FieldCondition[] }; + + if (!$and.length && !$or.length) { + return; + } + + const conditions: (FieldCondition | CompoundCondition)[] = $and; + + if ($or.length) { + conditions.push(new CompoundCondition("or", $or)); + } + + return this.buildCondition(new CompoundCondition("and", conditions)); + } + + private buildCondition(condition: CompoundCondition> | FieldCondition): SQL | undefined { + if (!condition.operator || !("value" in condition)) { + throw new Error("Invalid condition structure"); + } + + if (condition instanceof CompoundCondition) { + switch (condition.operator.toLowerCase()) { + case "and": + return condition.value.length === 1 ? this.buildCondition(condition.value[0]) : and(...condition.value.map(value => this.buildCondition(value))); + case "or": + return condition.value.length === 1 ? this.buildCondition(condition.value[0]) : or(...condition.value.map(value => this.buildCondition(value))); + } + } + + if (condition instanceof FieldCondition) { + if (condition.value === null) { + return isNull(this.table[condition.field]); + } + + const op = this.OPS[condition.operator.toLowerCase()]; + + if (!op) { + throw new Error(`Unsupported operator: ${condition.operator}`); + } + + return op(this.table[condition.field], condition.value); + } + + throw new Error("Unsupported condition type"); + } +} diff --git a/apps/api/src/user/controllers/user/user.controller.ts b/apps/api/src/user/controllers/user/user.controller.ts index 657eebbe7..b8758da53 100644 --- a/apps/api/src/user/controllers/user/user.controller.ts +++ b/apps/api/src/user/controllers/user/user.controller.ts @@ -1,13 +1,17 @@ import assert from "http-assert"; import { singleton } from "tsyringe"; +import { AuthService, Protected } from "@src/auth/services/auth.service"; import { UserRepository } from "@src/user/repositories"; import { GetUserParams } from "@src/user/routes/get-anonymous-user/get-anonymous-user.router"; -import { AnonymousUserResponseOutput } from "@src/user/routes/schemas/user.schema"; +import { AnonymousUserResponseOutput } from "@src/user/schemas/user.schema"; @singleton() export class UserController { - constructor(private readonly userRepository: UserRepository) {} + constructor( + private readonly userRepository: UserRepository, + private readonly authService: AuthService + ) {} async create(): Promise { return { @@ -15,10 +19,11 @@ export class UserController { }; } + @Protected([{ action: "read", subject: "User" }]) async getById({ id }: GetUserParams): Promise { - const user = await this.userRepository.findAnonymousById(id); + const user = await this.userRepository.accessibleBy(this.authService.ability, "read").findById(id); - assert(user, 404, "User not found"); + assert(user, 404); return { data: user }; } diff --git a/apps/api/src/user/providers/current-user.provider.ts b/apps/api/src/user/providers/current-user.provider.ts deleted file mode 100644 index c593cdd31..000000000 --- a/apps/api/src/user/providers/current-user.provider.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { container, inject } from "tsyringe"; - -import { RequestStorageInterceptor } from "@src/core/services/request-storage/request-storage.interceptor"; -import { CURRENT_USER } from "@src/user/services/current-user/current-user.interceptor"; - -container.register(CURRENT_USER, { - useFactory: c => { - return c.resolve(RequestStorageInterceptor).context.get(CURRENT_USER); - } -}); - -export const CurrentUser = () => inject(CURRENT_USER); -export type CurrentUser = { - userId: string; - isAnonymous: boolean; -}; diff --git a/apps/api/src/user/repositories/user/user.repository.ts b/apps/api/src/user/repositories/user/user.repository.ts index a00e9a3bd..0ec67c1ae 100644 --- a/apps/api/src/user/repositories/user/user.repository.ts +++ b/apps/api/src/user/repositories/user/user.repository.ts @@ -3,25 +3,39 @@ import first from "lodash/first"; import { singleton } from "tsyringe"; import { ApiPgDatabase, InjectPg } from "@src/core/providers"; +import { AbilityParams, BaseRepository } from "@src/core/repositories/base.repository"; import { TxService } from "@src/core/services"; import { InjectUserSchema, UserSchema } from "@src/user/providers"; export type UserOutput = UserSchema["$inferSelect"]; @singleton() -export class UserRepository { +export class UserRepository extends BaseRepository { constructor( - @InjectPg() private readonly pg: ApiPgDatabase, - @InjectUserSchema() private readonly users: UserSchema, - private readonly txManager: TxService - ) {} + @InjectPg() protected readonly pg: ApiPgDatabase, + @InjectUserSchema() protected readonly schema: UserSchema, + protected readonly txManager: TxService + ) { + super(pg, schema, txManager, "User"); + } + + accessibleBy(...abilityParams: AbilityParams) { + return new UserRepository(this.pg, this.schema, this.txManager).withAbility(...abilityParams) as this; + } async create() { - const pg = this.txManager.getPgTx() || this.pg; - return first(await pg.insert(this.users).values({}).returning({ id: this.users.id })); + return first(await this.cursor.insert(this.schema).values({}).returning({ id: this.schema.id })); + } + + async findByUserId(userId: UserOutput["userId"]) { + return await this.cursor.query.userSchema.findFirst({ where: this.whereAccessibleBy(eq(this.schema.userId, userId)) }); + } + + async findById(id: UserOutput["id"]) { + return await this.cursor.query.userSchema.findFirst({ where: this.whereAccessibleBy(eq(this.schema.id, id)) }); } async findAnonymousById(id: UserOutput["id"]) { - return await this.pg.query.userSchema.findFirst({ where: and(eq(this.users.id, id), isNull(this.users.userId)), columns: { id: true } }); + return await this.cursor.query.userSchema.findFirst({ where: this.whereAccessibleBy(and(eq(this.schema.id, id), isNull(this.schema.userId))) }); } } diff --git a/apps/api/src/user/routes/create-anonymous-user/create-anonymous-user.router.ts b/apps/api/src/user/routes/create-anonymous-user/create-anonymous-user.router.ts index 98f86eb16..6e692b06a 100644 --- a/apps/api/src/user/routes/create-anonymous-user/create-anonymous-user.router.ts +++ b/apps/api/src/user/routes/create-anonymous-user/create-anonymous-user.router.ts @@ -2,7 +2,7 @@ import { createRoute, OpenAPIHono } from "@hono/zod-openapi"; import { container } from "tsyringe"; import { UserController } from "@src/user/controllers/user/user.controller"; -import { AnonymousUserResponseOutputSchema } from "@src/user/routes/schemas/user.schema"; +import { AnonymousUserResponseOutputSchema } from "@src/user/schemas/user.schema"; const route = createRoute({ method: "post", diff --git a/apps/api/src/user/routes/get-anonymous-user/get-anonymous-user.router.ts b/apps/api/src/user/routes/get-anonymous-user/get-anonymous-user.router.ts index 594d314eb..ddcf63ccc 100644 --- a/apps/api/src/user/routes/get-anonymous-user/get-anonymous-user.router.ts +++ b/apps/api/src/user/routes/get-anonymous-user/get-anonymous-user.router.ts @@ -3,7 +3,7 @@ import { container } from "tsyringe"; import { z } from "zod"; import { UserController } from "@src/user/controllers/user/user.controller"; -import { AnonymousUserResponseOutputSchema } from "@src/user/routes/schemas/user.schema"; +import { AnonymousUserResponseOutputSchema } from "@src/user/schemas/user.schema"; export const GetUserParamsSchema = z.object({ id: z.string() }); diff --git a/apps/api/src/user/routes/schemas/user.schema.ts b/apps/api/src/user/schemas/user.schema.ts similarity index 100% rename from apps/api/src/user/routes/schemas/user.schema.ts rename to apps/api/src/user/schemas/user.schema.ts diff --git a/apps/api/test/functional/anonymous-user.spec.ts b/apps/api/test/functional/anonymous-user.spec.ts index bf96cb623..d784f32ab 100644 --- a/apps/api/test/functional/anonymous-user.spec.ts +++ b/apps/api/test/functional/anonymous-user.spec.ts @@ -3,7 +3,7 @@ import { container } from "tsyringe"; import { app } from "@src/app"; import { ApiPgDatabase, POSTGRES_DB } from "@src/core"; import { USER_SCHEMA, UserSchema } from "@src/user/providers"; -import { AnonymousUserResponseOutput } from "@src/user/routes/schemas/user.schema"; +import { AnonymousUserResponseOutput } from "@src/user/schemas/user.schema"; describe("Users", () => { const schema = container.resolve(USER_SCHEMA); @@ -32,11 +32,36 @@ describe("Users", () => { it("should retrieve a user", async () => { const getUserResponse = await app.request(`/v1/anonymous-users/${user.id}`, { method: "GET", - headers: new Headers({ "Content-Type": "application/json" }) + headers: new Headers({ "Content-Type": "application/json", "x-anonymous-user-id": user.id }) }); const retrievedUser = await getUserResponse.json(); expect(retrievedUser).toMatchObject({ data: user }); }); + + it("should throw 401 provided no auth header", async () => { + const res = await app.request(`/v1/anonymous-users/${user.id}`, { + method: "GET", + headers: new Headers({ "Content-Type": "application/json" }) + }); + + expect(res.status).toBe(401); + expect(await res.json()).toMatchObject({ error: "UnauthorizedError", message: "Unauthorized" }); + }); + + it("should throw 404 provided a different user auth header", async () => { + const differentUserResponse = await app.request("/v1/anonymous-users", { + method: "POST", + headers: new Headers({ "Content-Type": "application/json" }) + }); + const { data: differentUser } = await differentUserResponse.json(); + const res = await app.request(`/v1/anonymous-users/${user.id}`, { + method: "GET", + headers: new Headers({ "Content-Type": "application/json", "x-anonymous-user-id": differentUser.id }) + }); + + expect(res.status).toBe(404); + expect(await res.json()).toMatchObject({ error: "NotFoundError", message: "Not Found" }); + }); }); }); diff --git a/apps/api/test/functional/create-wallet.spec.ts b/apps/api/test/functional/create-wallet.spec.ts index 1856b47f8..3d000792b 100644 --- a/apps/api/test/functional/create-wallet.spec.ts +++ b/apps/api/test/functional/create-wallet.spec.ts @@ -1,3 +1,4 @@ +import { faker } from "@faker-js/faker"; import { eq } from "drizzle-orm"; import { container } from "tsyringe"; @@ -28,15 +29,18 @@ describe("wallets", () => { const { data: { id: userId } } = await userResponse.json(); - const res = await app.request("/v1/wallets", { + const headers = new Headers({ "Content-Type": "application/json", "x-anonymous-user-id": userId }); + const createWalletResponse = await app.request("/v1/wallets", { method: "POST", body: JSON.stringify({ data: { userId } }), - headers: new Headers({ "Content-Type": "application/json" }) + headers }); + const getWalletsResponse = await app.request(`/v1/wallets?userId=${userId}`, { headers }); const userWallet = await userWalletsTable.findFirst({ where: eq(userWalletSchema.userId, userId) }); - expect(res.status).toBe(200); - expect(await res.json()).toMatchObject({ + expect(createWalletResponse.status).toBe(200); + expect(getWalletsResponse.status).toBe(200); + expect(await createWalletResponse.json()).toMatchObject({ data: { id: expect.any(Number), userId, @@ -44,6 +48,16 @@ describe("wallets", () => { address: expect.any(String) } }); + expect(await getWalletsResponse.json()).toMatchObject({ + data: [ + { + id: expect.any(Number), + userId, + creditAmount: expect.any(Number), + address: expect.any(String) + } + ] + }); expect(userWallet).toMatchObject({ id: expect.any(Number), userId, @@ -52,5 +66,25 @@ describe("wallets", () => { feeAllowance: `${config.TRIAL_FEES_ALLOWANCE_AMOUNT}.00` }); }); + + it("should throw 401 provided no auth header ", async () => { + const createWalletResponse = await app.request("/v1/wallets", { + method: "POST", + body: JSON.stringify({ data: { userId: faker.string.uuid() } }), + headers: new Headers({ "Content-Type": "application/json" }) + }); + + expect(createWalletResponse.status).toBe(401); + expect(await createWalletResponse.json()).toMatchObject({ error: "UnauthorizedError", message: "Unauthorized" }); + }); + }); + + describe("GET /v1/wallets", () => { + it("should throw 401 provided no auth header ", async () => { + const getWalletsResponse = await app.request(`/v1/wallets?userId=${faker.string.uuid()}`); + + expect(getWalletsResponse.status).toBe(401); + expect(await getWalletsResponse.json()).toMatchObject({ error: "UnauthorizedError", message: "Unauthorized" }); + }); }); }); diff --git a/apps/api/test/functional/sign-and-broadcast-tx.spec.ts b/apps/api/test/functional/sign-and-broadcast-tx.spec.ts index 45b94aeab..8c771c395 100644 --- a/apps/api/test/functional/sign-and-broadcast-tx.spec.ts +++ b/apps/api/test/functional/sign-and-broadcast-tx.spec.ts @@ -1,5 +1,6 @@ import { certificateManager } from "@akashnetwork/akashjs/build/certificates/certificate-manager"; import type { Registry } from "@cosmjs/proto-signing"; +import { WalletService } from "@test/services/wallet.service"; import { container } from "tsyringe"; import { app } from "@src/app"; @@ -8,13 +9,14 @@ import { TYPE_REGISTRY } from "@src/billing/providers/type-registry.provider"; import { ApiPgDatabase, POSTGRES_DB } from "@src/core"; import { USER_SCHEMA, UserSchema } from "@src/user/providers"; -jest.setTimeout(20000); +jest.setTimeout(30000); describe("Tx Sign", () => { const registry = container.resolve(TYPE_REGISTRY); const db = container.resolve(POSTGRES_DB); const userWalletSchema = container.resolve(USER_WALLET_SCHEMA); const userSchema = container.resolve(USER_SCHEMA); + const walletService = new WalletService(app); afterEach(async () => { await Promise.all([db.delete(userWalletSchema), db.delete(userSchema)]); @@ -22,49 +24,69 @@ describe("Tx Sign", () => { describe("POST /v1/tx", () => { it("should create a wallet for a user", async () => { - const userResponse = await app.request("/v1/anonymous-users", { + const { user, wallet } = await walletService.createUserAndWallet(); + const res = await app.request("/v1/tx", { method: "POST", - headers: new Headers({ "Content-Type": "application/json" }) + body: await createMessagePayload(user.id, wallet.address), + headers: new Headers({ "Content-Type": "application/json", "x-anonymous-user-id": user.id }) }); - const { - data: { id: userId } - } = await userResponse.json(); - const walletResponse = await app.request("/v1/wallets", { + + expect(res.status).toBe(200); + expect(await res.json()).toMatchObject({ data: { code: 0, transactionHash: expect.any(String) } }); + }); + + it("should throw 401 provided no auth header", async () => { + const { user, wallet } = await walletService.createUserAndWallet(); + const res = await app.request("/v1/tx", { method: "POST", - body: JSON.stringify({ - data: { userId } - }), + body: await createMessagePayload(user.id, wallet.address), headers: new Headers({ "Content-Type": "application/json" }) }); - const { data: wallet } = await walletResponse.json(); - const { cert, publicKey } = certificateManager.generatePEM(wallet.address); - const message = { - typeUrl: "/akash.cert.v1beta3.MsgCreateCertificate", - value: { - owner: wallet.address, - cert: Buffer.from(cert).toString("base64"), - pubkey: Buffer.from(publicKey).toString("base64") - } - }; - const res = await app.request("/v1/tx", { + expect(res.status).toBe(401); + expect(await res.json()).toMatchObject({ error: "UnauthorizedError", message: "Unauthorized" }); + }); + + it("should throw 403 provided a different user auth header", async () => { + const { user, wallet } = await walletService.createUserAndWallet(); + const differentUserResponse = await app.request("/v1/anonymous-users", { method: "POST", - body: JSON.stringify({ - data: { - userId: userId, - messages: [ - { - typeUrl: message.typeUrl, - value: Buffer.from(registry.encode(message)).toString("base64") - } - ] - } - }), headers: new Headers({ "Content-Type": "application/json" }) }); + const { data: differentUser } = await differentUserResponse.json(); + const res = await app.request("/v1/tx", { + method: "POST", + body: await createMessagePayload(user.id, wallet.address), + headers: new Headers({ "Content-Type": "application/json", "x-anonymous-user-id": differentUser.id }) + }); - expect(res.status).toBe(200); - expect(await res.json()).toMatchObject({ data: { code: 0, transactionHash: expect.any(String) } }); + expect(res.status).toBe(403); + expect(await res.json()).toMatchObject({ error: "ForbiddenError", message: "Forbidden" }); }); }); + + async function createMessagePayload(userId: string, address: string) { + const { cert, publicKey } = certificateManager.generatePEM(address); + + const message = { + typeUrl: "/akash.cert.v1beta3.MsgCreateCertificate", + value: { + owner: address, + cert: Buffer.from(cert).toString("base64"), + pubkey: Buffer.from(publicKey).toString("base64") + } + }; + + return JSON.stringify({ + data: { + userId: userId, + messages: [ + { + typeUrl: message.typeUrl, + value: Buffer.from(registry.encode(message)).toString("base64") + } + ] + } + }); + } }); diff --git a/apps/api/test/services/wallet.service.ts b/apps/api/test/services/wallet.service.ts index a2c07c65a..a01da5617 100644 --- a/apps/api/test/services/wallet.service.ts +++ b/apps/api/test/services/wallet.service.ts @@ -14,7 +14,7 @@ export class WalletService { body: JSON.stringify({ data: { userId: user.id } }), - headers: new Headers({ "Content-Type": "application/json" }) + headers: new Headers({ "Content-Type": "application/json", "x-anonymous-user-id": user.id }) }); const { data: wallet } = await walletResponse.json(); @@ -23,7 +23,7 @@ export class WalletService { async getWalletByUserId(userId: string): Promise<{ id: number; address: string; creditAmount: number }> { const walletResponse = await this.app.request(`/v1/wallets?userId=${userId}`, { - headers: new Headers({ "Content-Type": "application/json" }) + headers: new Headers({ "Content-Type": "application/json", "x-anonymous-user-id": userId }) }); const { data } = await walletResponse.json(); diff --git a/apps/deploy-web/src/components/user/UserProviders.tsx b/apps/deploy-web/src/components/user/UserProviders.tsx index 45edf4a37..597d1fc6e 100644 --- a/apps/deploy-web/src/components/user/UserProviders.tsx +++ b/apps/deploy-web/src/components/user/UserProviders.tsx @@ -3,7 +3,7 @@ import { UserProvider } from "@auth0/nextjs-auth0/client"; import { UserInitLoader } from "@src/components/user/UserInitLoader"; import { envConfig } from "@src/config/env.config"; import { AnonymousUserProvider } from "@src/context/AnonymousUserProvider/AnonymousUserProvider"; -import { authHttpService } from "@src/services/auth/auth-http.service"; +import { authHttpService } from "@src/services/user/user-http.service"; import { FCWithChildren } from "@src/types/component"; export const UserProviders: FCWithChildren = ({ children }) => diff --git a/apps/deploy-web/src/services/auth/auth-http.service.ts b/apps/deploy-web/src/services/auth/auth-http.service.ts deleted file mode 100644 index 394b1b070..000000000 --- a/apps/deploy-web/src/services/auth/auth-http.service.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { HttpService } from "@akashnetwork/http-sdk"; -import type { UserProfile } from "@auth0/nextjs-auth0/client"; -import { AxiosHeaders } from "axios"; - -import { ANONYMOUS_USER_KEY } from "@src/utils/constants"; - -export class AuthHttpService extends HttpService { - constructor() { - super(); - this.getProfile = this.getProfile.bind(this); - } - - async getProfile(url: string) { - try { - const user = localStorage.getItem(ANONYMOUS_USER_KEY); - const anonymousUserId = user ? JSON.parse(user).id : undefined; - const headers = new AxiosHeaders(); - if (anonymousUserId) { - headers.set("X-User-Id", anonymousUserId); - } - - return this.extractData(await this.get(url, { headers: headers.toJSON() })); - } catch (error) { - console.warn("DEBUG error", error); - throw error; - } - } -} - -export const authHttpService = new AuthHttpService(); diff --git a/apps/deploy-web/src/services/auth/auth.service.ts b/apps/deploy-web/src/services/auth/auth.service.ts new file mode 100644 index 000000000..688206e6a --- /dev/null +++ b/apps/deploy-web/src/services/auth/auth.service.ts @@ -0,0 +1,26 @@ +import { InternalAxiosRequestConfig } from "axios"; + +import { ANONYMOUS_USER_KEY } from "@src/utils/constants"; + +export class AuthService { + private anonymousUserId: string | undefined; + + constructor() { + this.withAnonymousUserHeader = this.withAnonymousUserHeader.bind(this); + } + + withAnonymousUserHeader(config: InternalAxiosRequestConfig) { + if (!this.anonymousUserId) { + const user = localStorage.getItem(ANONYMOUS_USER_KEY); + this.anonymousUserId = user ? JSON.parse(user).id : undefined; + } + + if (this.anonymousUserId) { + config.headers.set("x-anonymous-user-id", this.anonymousUserId); + } + + return config; + } +} + +export const authService = new AuthService(); diff --git a/apps/deploy-web/src/services/http/http.service.ts b/apps/deploy-web/src/services/http/http.service.ts index 71ff102fa..6bb05fc52 100644 --- a/apps/deploy-web/src/services/http/http.service.ts +++ b/apps/deploy-web/src/services/http/http.service.ts @@ -1,10 +1,15 @@ import { ManagedWalletHttpService, TxHttpService, UserHttpService } from "@akashnetwork/http-sdk"; +import { authService } from "@src/services/auth/auth.service"; import { BASE_API_URL } from "@src/utils/constants"; import { customRegistry } from "@src/utils/customRegistry"; const apiConfig = { baseURL: BASE_API_URL }; export const userHttpService = new UserHttpService(apiConfig); -export const txHttpService = new TxHttpService(customRegistry, apiConfig); export const managedWalletHttpService = new ManagedWalletHttpService(apiConfig); +export const txHttpService = new TxHttpService(customRegistry, apiConfig); + +userHttpService.interceptors.request.use(authService.withAnonymousUserHeader); +managedWalletHttpService.interceptors.request.use(authService.withAnonymousUserHeader); +txHttpService.interceptors.request.use(authService.withAnonymousUserHeader); diff --git a/apps/deploy-web/src/services/user/user-http.service.ts b/apps/deploy-web/src/services/user/user-http.service.ts new file mode 100644 index 000000000..e9bbb4996 --- /dev/null +++ b/apps/deploy-web/src/services/user/user-http.service.ts @@ -0,0 +1,18 @@ +import { HttpService } from "@akashnetwork/http-sdk"; +import type { UserProfile } from "@auth0/nextjs-auth0/client"; + +import { authService } from "@src/services/auth/auth.service"; + +export class UserHttpService extends HttpService { + constructor() { + super(); + this.getProfile = this.getProfile.bind(this); + this.interceptors.request.use(authService.withAnonymousUserHeader); + } + + async getProfile(url: string) { + return this.extractData(await this.get(url)); + } +} + +export const authHttpService = new UserHttpService(); diff --git a/package-lock.json b/package-lock.json index 255ec2488..b4cf74035 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,6 +32,7 @@ "@akashnetwork/akashjs": "^0.10.0", "@akashnetwork/database": "*", "@akashnetwork/http-sdk": "*", + "@casl/ability": "^6.7.1", "@chain-registry/assets": "^0.7.1", "@cosmjs/amino": "^0.32.4", "@cosmjs/crypto": "^0.32.4", @@ -48,6 +49,7 @@ "@opentelemetry/instrumentation-pino": "^0.41.0", "@opentelemetry/sdk-node": "^0.52.1", "@sentry/node": "^7.55.2", + "@ucast/core": "^1.10.2", "async-sema": "^3.1.1", "axios": "^1.7.2", "commander": "^12.1.0", @@ -3154,6 +3156,17 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, + "node_modules/@casl/ability": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/@casl/ability/-/ability-6.7.1.tgz", + "integrity": "sha512-e+Vgrehd1/lzOSwSqKHtmJ6kmIuZbGBlM2LBS5IuYGGKmVHuhUuyh3XgTn1VIw9+TO4gqU+uptvxfIRBUEdJuw==", + "dependencies": { + "@ucast/mongo2js": "^1.3.0" + }, + "funding": { + "url": "https://github.com/stalniy/casl/blob/master/BACKERS.md" + } + }, "node_modules/@celo/base": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/@celo/base/-/base-3.2.0.tgz", @@ -19891,6 +19904,37 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@ucast/core": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/@ucast/core/-/core-1.10.2.tgz", + "integrity": "sha512-ons5CwXZ/51wrUPfoduC+cO7AS1/wRb0ybpQJ9RrssossDxVy4t49QxWoWgfBDvVKsz9VXzBk9z0wqTdZ+Cq8g==" + }, + "node_modules/@ucast/js": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@ucast/js/-/js-3.0.4.tgz", + "integrity": "sha512-TgG1aIaCMdcaEyckOZKQozn1hazE0w90SVdlpIJ/er8xVumE11gYAtSbw/LBeUnA4fFnFWTcw3t6reqseeH/4Q==", + "dependencies": { + "@ucast/core": "^1.0.0" + } + }, + "node_modules/@ucast/mongo": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/@ucast/mongo/-/mongo-2.4.3.tgz", + "integrity": "sha512-XcI8LclrHWP83H+7H2anGCEeDq0n+12FU2mXCTz6/Tva9/9ddK/iacvvhCyW6cijAAOILmt0tWplRyRhVyZLsA==", + "dependencies": { + "@ucast/core": "^1.4.1" + } + }, + "node_modules/@ucast/mongo2js": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@ucast/mongo2js/-/mongo2js-1.3.4.tgz", + "integrity": "sha512-ahazOr1HtelA5AC1KZ9x0UwPMqqimvfmtSm/PRRSeKKeE5G2SCqTgwiNzO7i9jS8zA3dzXpKVPpXMkcYLnyItA==", + "dependencies": { + "@ucast/core": "^1.6.1", + "@ucast/js": "^3.0.0", + "@ucast/mongo": "^2.4.0" + } + }, "node_modules/@ungap/structured-clone": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", From 7bf2a1442b551eb2081f6371607d4ad46d4395ff Mon Sep 17 00:00:00 2001 From: Yaroslav Grishajev Date: Tue, 6 Aug 2024 11:58:14 +0200 Subject: [PATCH 2/3] feat(billing): adjust migrations and env for deployment refs #247 --- ..._professor.sql => 0000_cloudy_banshee.sql} | 8 +- apps/api/drizzle/0001_absurd_pretty_boy.sql | 4 + apps/api/drizzle/meta/0000_snapshot.json | 9 +- apps/api/drizzle/meta/0001_snapshot.json | 171 ++++ apps/api/drizzle/meta/_journal.json | 11 +- apps/api/package.json | 1 + apps/api/src/billing/config/env.config.ts | 3 +- .../master-signing-client.service.ts | 2 +- apps/api/src/core/config/env.config.ts | 3 +- .../src/core/providers/postgres.provider.ts | 2 +- apps/api/webpack.prod.js | 8 +- .../SettingsProviderContext.tsx | 6 +- apps/deploy-web/src/utils/constants.ts | 34 +- apps/deploy-web/src/utils/localStorage.ts | 4 +- package-lock.json | 881 +++++++++++------- 15 files changed, 790 insertions(+), 357 deletions(-) rename apps/api/drizzle/{0000_purple_the_professor.sql => 0000_cloudy_banshee.sql} (84%) create mode 100644 apps/api/drizzle/0001_absurd_pretty_boy.sql create mode 100644 apps/api/drizzle/meta/0001_snapshot.json diff --git a/apps/api/drizzle/0000_purple_the_professor.sql b/apps/api/drizzle/0000_cloudy_banshee.sql similarity index 84% rename from apps/api/drizzle/0000_purple_the_professor.sql rename to apps/api/drizzle/0000_cloudy_banshee.sql index b6b96c71e..ad6ee5e9b 100644 --- a/apps/api/drizzle/0000_purple_the_professor.sql +++ b/apps/api/drizzle/0000_cloudy_banshee.sql @@ -1,5 +1,3 @@ -CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; ---> statement-breakpoint CREATE TABLE IF NOT EXISTS "user_wallets" ( "id" serial PRIMARY KEY NOT NULL, "user_id" uuid, @@ -10,9 +8,9 @@ CREATE TABLE IF NOT EXISTS "user_wallets" ( ); --> statement-breakpoint CREATE TABLE IF NOT EXISTS "userSetting" ( - "id" uuid PRIMARY KEY DEFAULT uuid_generate_v4() NOT NULL, - "userId" varchar(255), - "username" varchar(255), + "id" uuid PRIMARY KEY NOT NULL, + "userId" varchar(255) NOT NULL, + "username" varchar(255) NOT NULL, "email" varchar(255), "emailVerified" boolean DEFAULT false NOT NULL, "stripeCustomerId" varchar(255), diff --git a/apps/api/drizzle/0001_absurd_pretty_boy.sql b/apps/api/drizzle/0001_absurd_pretty_boy.sql new file mode 100644 index 000000000..023969492 --- /dev/null +++ b/apps/api/drizzle/0001_absurd_pretty_boy.sql @@ -0,0 +1,4 @@ +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; --> statement-breakpoint +ALTER TABLE "userSetting" ALTER COLUMN "id" SET DEFAULT uuid_generate_v4();--> statement-breakpoint +ALTER TABLE "userSetting" ALTER COLUMN "userId" DROP NOT NULL;--> statement-breakpoint +ALTER TABLE "userSetting" ALTER COLUMN "username" DROP NOT NULL; \ No newline at end of file diff --git a/apps/api/drizzle/meta/0000_snapshot.json b/apps/api/drizzle/meta/0000_snapshot.json index bd0230879..bb6541275 100644 --- a/apps/api/drizzle/meta/0000_snapshot.json +++ b/apps/api/drizzle/meta/0000_snapshot.json @@ -1,5 +1,5 @@ { - "id": "f5b9582d-1b53-4ea5-b8f9-b613f08fb892", + "id": "f35df021-9973-4687-b10f-32057c615b66", "prevId": "00000000-0000-0000-0000-000000000000", "version": "7", "dialect": "postgresql", @@ -74,20 +74,19 @@ "name": "id", "type": "uuid", "primaryKey": true, - "notNull": true, - "default": "uuid_generate_v4()" + "notNull": true }, "userId": { "name": "userId", "type": "varchar(255)", "primaryKey": false, - "notNull": false + "notNull": true }, "username": { "name": "username", "type": "varchar(255)", "primaryKey": false, - "notNull": false + "notNull": true }, "email": { "name": "email", diff --git a/apps/api/drizzle/meta/0001_snapshot.json b/apps/api/drizzle/meta/0001_snapshot.json new file mode 100644 index 000000000..2bf0f14ed --- /dev/null +++ b/apps/api/drizzle/meta/0001_snapshot.json @@ -0,0 +1,171 @@ +{ + "id": "84417858-3b28-48ef-ae81-3d19629cfd4a", + "prevId": "f35df021-9973-4687-b10f-32057c615b66", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.user_wallets": { + "name": "user_wallets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "address": { + "name": "address", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "deployment_allowance": { + "name": "deployment_allowance", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0.00'" + }, + "fee_allowance": { + "name": "fee_allowance", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0.00'" + } + }, + "indexes": {}, + "foreignKeys": { + "user_wallets_user_id_userSetting_id_fk": { + "name": "user_wallets_user_id_userSetting_id_fk", + "tableFrom": "user_wallets", + "tableTo": "userSetting", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.userSetting": { + "name": "userSetting", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuid_generate_v4()" + }, + "userId": { + "name": "userId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "username": { + "name": "username", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "emailVerified": { + "name": "emailVerified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "stripeCustomerId": { + "name": "stripeCustomerId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "bio": { + "name": "bio", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "subscribedToNewsletter": { + "name": "subscribedToNewsletter", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "youtubeUsername": { + "name": "youtubeUsername", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "twitterUsername": { + "name": "twitterUsername", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "githubUsername": { + "name": "githubUsername", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "userSetting_userId_unique": { + "name": "userSetting_userId_unique", + "nullsNotDistinct": false, + "columns": [ + "userId" + ] + }, + "userSetting_username_unique": { + "name": "userSetting_username_unique", + "nullsNotDistinct": false, + "columns": [ + "username" + ] + } + } + } + }, + "enums": {}, + "schemas": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/apps/api/drizzle/meta/_journal.json b/apps/api/drizzle/meta/_journal.json index 8af505d0c..b031fc9e7 100644 --- a/apps/api/drizzle/meta/_journal.json +++ b/apps/api/drizzle/meta/_journal.json @@ -5,8 +5,15 @@ { "idx": 0, "version": "7", - "when": 1721656603590, - "tag": "0000_purple_the_professor", + "when": 1722870109760, + "tag": "0000_cloudy_banshee", + "breakpoints": true + }, + { + "idx": 1, + "version": "7", + "when": 1722870150411, + "tag": "0001_absurd_pretty_boy", "breakpoints": true } ] diff --git a/apps/api/package.json b/apps/api/package.json index 906e2d038..0e6abe1f9 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -99,6 +99,7 @@ "@types/uuid": "^8.3.1", "@typescript-eslint/eslint-plugin": "^7.12.0", "alias-hq": "^5.1.6", + "copy-webpack-plugin": "^12.0.2", "drizzle-kit": "^0.22.7", "eslint": "^8.57.0", "eslint-config-next": "^14.2.3", diff --git a/apps/api/src/billing/config/env.config.ts b/apps/api/src/billing/config/env.config.ts index 0b138ee32..33c81db8e 100644 --- a/apps/api/src/billing/config/env.config.ts +++ b/apps/api/src/billing/config/env.config.ts @@ -13,7 +13,8 @@ const envSchema = z.object({ DEPLOYMENT_ALLOWANCE_REFILL_THRESHOLD: z.number({ coerce: true }), FEE_ALLOWANCE_REFILL_AMOUNT: z.number({ coerce: true }), DEPLOYMENT_ALLOWANCE_REFILL_AMOUNT: z.number({ coerce: true }), - ALLOWANCE_REFILL_BATCH_SIZE: z.number({ coerce: true }).default(10) + ALLOWANCE_REFILL_BATCH_SIZE: z.number({ coerce: true }).default(10), + MASTER_WALLET_BATCHING_INTERVAL_MS: z.number().optional().default(1000) }); export const envConfig = envSchema.parse(process.env); diff --git a/apps/api/src/billing/services/master-signing-client/master-signing-client.service.ts b/apps/api/src/billing/services/master-signing-client/master-signing-client.service.ts index 7943cc5c9..3415a9d01 100644 --- a/apps/api/src/billing/services/master-signing-client/master-signing-client.service.ts +++ b/apps/api/src/billing/services/master-signing-client/master-signing-client.service.ts @@ -34,7 +34,7 @@ export class MasterSigningClientService { async (batchedMessages: readonly EncodeObject[][]) => { return this.executeTxBatch(batchedMessages); }, - { cache: false, batchScheduleFn: callback => setTimeout(callback, 100) } + { cache: false, batchScheduleFn: callback => setTimeout(callback, this.config.MASTER_WALLET_BATCHING_INTERVAL_MS) } ); constructor( diff --git a/apps/api/src/core/config/env.config.ts b/apps/api/src/core/config/env.config.ts index 669c0b872..5af6b95db 100644 --- a/apps/api/src/core/config/env.config.ts +++ b/apps/api/src/core/config/env.config.ts @@ -6,7 +6,8 @@ const envSchema = z.object({ LOG_FORMAT: z.enum(["json", "pretty"]).optional().default("json"), // TODO: make required once billing is in prod POSTGRES_DB_URI: z.string().optional(), - POSTGRES_MAX_CONNECTIONS: z.number({ coerce: true }).optional().default(20) + POSTGRES_MAX_CONNECTIONS: z.number({ coerce: true }).optional().default(20), + DRIZZLE_MIGRATIONS_FOLDER: z.string().optional().default("./drizzle") }); export const envConfig = envSchema.parse(process.env); diff --git a/apps/api/src/core/providers/postgres.provider.ts b/apps/api/src/core/providers/postgres.provider.ts index 215bd2769..81be0b62d 100644 --- a/apps/api/src/core/providers/postgres.provider.ts +++ b/apps/api/src/core/providers/postgres.provider.ts @@ -18,7 +18,7 @@ const schema = { ...userSchemas, ...billingSchemas }; const drizzleOptions = { logger: new DefaultLogger({ writer: new PostgresLoggerService() }), schema }; const pgMigrationDatabase = drizzle(migrationClient, drizzleOptions); -export const migratePG = () => migrate(pgMigrationDatabase, { migrationsFolder: "./drizzle" }); +export const migratePG = () => migrate(pgMigrationDatabase, { migrationsFolder: config.DRIZZLE_MIGRATIONS_FOLDER }); const pgDatabase = drizzle(appClient, drizzleOptions); diff --git a/apps/api/webpack.prod.js b/apps/api/webpack.prod.js index f57ca73d8..87cddcff3 100644 --- a/apps/api/webpack.prod.js +++ b/apps/api/webpack.prod.js @@ -1,6 +1,7 @@ const path = require("path"); const webpack = require("webpack"); const nodeExternals = require("webpack-node-externals"); +const CopyPlugin = require("copy-webpack-plugin"); const hq = require("alias-hq"); const { NODE_ENV = "production" } = process.env; @@ -30,7 +31,12 @@ module.exports = { optimization: { minimize: false }, - plugins: [new webpack.IgnorePlugin({ resourceRegExp: /^pg-native$/ })], + plugins: [ + new webpack.IgnorePlugin({ resourceRegExp: /^pg-native$/ }), + new CopyPlugin({ + patterns: [{ from: "drizzle", to: "drizzle" }] + }) + ], node: { __dirname: true }, diff --git a/apps/deploy-web/src/context/SettingsProvider/SettingsProviderContext.tsx b/apps/deploy-web/src/context/SettingsProvider/SettingsProviderContext.tsx index 3bbbb6217..d5ebb68e4 100644 --- a/apps/deploy-web/src/context/SettingsProvider/SettingsProviderContext.tsx +++ b/apps/deploy-web/src/context/SettingsProvider/SettingsProviderContext.tsx @@ -8,7 +8,7 @@ import { queryClient } from "@src/queries"; import { initiateNetworkData, networks } from "@src/store/networkStore"; import { NodeStatus } from "@src/types/node"; import { mainnetNodes } from "@src/utils/apiUtils"; -import { mainnetId } from "@src/utils/constants"; +import { defaultNetworkId } from "@src/utils/constants"; import { initAppTypes } from "@src/utils/init"; import { migrateLocalStorage } from "@src/utils/localStorage"; @@ -58,7 +58,7 @@ export const SettingsProvider: FC<{ children: ReactNode }> = ({ children }) => { const [isSettingsInit, setIsSettingsInit] = useState(false); const [isRefreshingNodeStatus, setIsRefreshingNodeStatus] = useState(false); const { getLocalStorageItem, setLocalStorageItem } = useLocalStorage(); - const [selectedNetworkId, setSelectedNetworkId] = useState(mainnetId); + const [selectedNetworkId, setSelectedNetworkId] = useState(defaultNetworkId); const { isCustomNode, customNode, nodes, apiEndpoint, rpcEndpoint } = settings; usePreviousRoute(); @@ -77,7 +77,7 @@ export const SettingsProvider: FC<{ children: ReactNode }> = ({ children }) => { // Init app types based on the selected network id initAppTypes(); - const _selectedNetworkId = localStorage.getItem("selectedNetworkId") || mainnetId; + const _selectedNetworkId = localStorage.getItem("selectedNetworkId") || defaultNetworkId; setSelectedNetworkId(_selectedNetworkId); diff --git a/apps/deploy-web/src/utils/constants.ts b/apps/deploy-web/src/utils/constants.ts index 31da68b3f..07984d8cb 100644 --- a/apps/deploy-web/src/utils/constants.ts +++ b/apps/deploy-web/src/utils/constants.ts @@ -1,6 +1,28 @@ +const ENV = { + API_MAINNET_BASE_URL: + typeof window === "undefined" + ? process.env.API_MAINNET_BASE_URL || process.env.NEXT_PUBLIC_API_MAINNET_BASE_URL + : process.env.NEXT_PUBLIC_API_MAINNET_BASE_URL, + API_TESTNET_BASE_URL: + typeof window === "undefined" + ? process.env.API_TESTNET_BASE_URL || process.env.NEXT_PUBLIC_API_TESTNET_BASE_URL + : process.env.NEXT_PUBLIC_API_TESTNET_BASE_URL, + API_SANDBOX_BASE_URL: + typeof window === "undefined" + ? process.env.API_SANDBOX_BASE_URL || process.env.NEXT_PUBLIC_API_SANDBOX_BASE_URL + : process.env.NEXT_PUBLIC_API_SANDBOX_BASE_URL, + API_BASE_URL: typeof window === "undefined" ? process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL : process.env.NEXT_PUBLIC_API_BASE_URL, + STATS_APP_URL: typeof window === "undefined" ? process.env.STATS_APP_URL || process.env.NEXT_PUBLIC_STATS_APP_URL : process.env.NEXT_PUBLIC_STATS_APP_URL, + PROVIDER_PROXY_URL: + typeof window === "undefined" ? process.env.PROVIDER_PROXY_URL || process.env.NEXT_PUBLIC_PROVIDER_PROXY_URL : process.env.NEXT_PUBLIC_PROVIDER_PROXY_URL, + DEFAULT_NETWORK_ID: + typeof window === "undefined" ? process.env.DEFAULT_NETWORK_ID || process.env.NEXT_PUBLIC_DEFAULT_NETWORK_ID : process.env.NEXT_PUBLIC_DEFAULT_NETWORK_ID +}; + export const mainnetId = "mainnet"; export const testnetId = "testnet"; export const sandboxId = "sandbox"; +export const defaultNetworkId = ENV.DEFAULT_NETWORK_ID || mainnetId; export const selectedRangeValues: { [key: string]: number } = { "7D": 7, @@ -67,28 +89,28 @@ export const readableDenoms = { }; function getApiMainnetUrl() { - if (process.env.API_MAINNET_BASE_URL) return process.env.API_MAINNET_BASE_URL; + if (ENV.API_MAINNET_BASE_URL) return ENV.API_MAINNET_BASE_URL; if (typeof window === "undefined") return "http://localhost:3080"; if (productionHostnames.includes(window.location?.hostname)) return productionMainnetApiUrl; return "http://localhost:3080"; } function getApiTestnetUrl() { - if (process.env.API_TESTNET_BASE_URL) return process.env.API_TESTNET_BASE_URL; + if (ENV.API_TESTNET_BASE_URL) return ENV.API_TESTNET_BASE_URL; if (typeof window === "undefined") return "http://localhost:3080"; if (productionHostnames.includes(window.location?.hostname)) return productionTestnetApiUrl; return "http://localhost:3080"; } function getApiSandboxUrl() { - if (process.env.API_SANDBOX_BASE_URL) return process.env.API_SANDBOX_BASE_URL; + if (ENV.API_SANDBOX_BASE_URL) return ENV.API_SANDBOX_BASE_URL; if (typeof window === "undefined") return "http://localhost:3080"; if (productionHostnames.includes(window.location?.hostname)) return productionSandboxApiUrl; return "http://localhost:3080"; } function getApiUrl() { - if (process.env.API_BASE_URL) return process.env.API_BASE_URL; + if (ENV.API_BASE_URL) return ENV.API_BASE_URL; if (typeof window === "undefined") return "http://localhost:3080"; if (productionHostnames.includes(window.location?.hostname)) { try { @@ -103,14 +125,14 @@ function getApiUrl() { } function getStatsAppUrl() { - if (process.env.STATS_APP_URL) return process.env.STATS_APP_URL; + if (ENV.STATS_APP_URL) return ENV.STATS_APP_URL; if (typeof window === "undefined") return "http://localhost:3001"; if (productionHostnames.includes(window.location?.hostname)) return productionStatsAppUrl; return "http://localhost:3001"; } function getProviderProxyHttpUrl() { - if (process.env.PROVIDER_PROXY_URL) return process.env.PROVIDER_PROXY_URL; + if (ENV.PROVIDER_PROXY_URL) return ENV.PROVIDER_PROXY_URL; if (typeof window === "undefined") return "http://localhost:3040"; if (window.location?.hostname === "deploybeta.cloudmos.io") return "https://deployproxybeta.cloudmos.io"; if (productionHostnames.includes(window.location?.hostname)) return "https://providerproxy.cloudmos.io"; diff --git a/apps/deploy-web/src/utils/localStorage.ts b/apps/deploy-web/src/utils/localStorage.ts index 414c382d1..fa684a718 100644 --- a/apps/deploy-web/src/utils/localStorage.ts +++ b/apps/deploy-web/src/utils/localStorage.ts @@ -3,7 +3,7 @@ import getConfig from "next/config"; import { gt, neq } from "semver"; -import { mainnetId } from "./constants"; +import { defaultNetworkId } from "./constants"; const { publicRuntimeConfig } = getConfig(); const migrations = { @@ -35,7 +35,7 @@ export const migrateLocalStorage = () => { localStorage.setItem("latestUpdatedVersion", currentVersion); if (!localStorage.getItem("selectedNetworkId")) { - localStorage.setItem("selectedNetworkId", mainnetId); + localStorage.setItem("selectedNetworkId", defaultNetworkId); } }; diff --git a/package-lock.json b/package-lock.json index b4cf74035..034184de8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -98,6 +98,7 @@ "@types/uuid": "^8.3.1", "@typescript-eslint/eslint-plugin": "^7.12.0", "alias-hq": "^5.1.6", + "copy-webpack-plugin": "^12.0.2", "drizzle-kit": "^0.22.7", "eslint": "^8.57.0", "eslint-config-next": "^14.2.3", @@ -433,9 +434,9 @@ } }, "apps/deploy-web/node_modules/@keplr-wallet/types": { - "version": "0.12.113", - "resolved": "https://registry.npmjs.org/@keplr-wallet/types/-/types-0.12.113.tgz", - "integrity": "sha512-xin9BotERBPq2KcubSTCe7rLtnLbXorZdODX8yzke7IrDBMidq3YKdubNU3C/kfPw77n0rhQZibMY/W9lh3oZQ==", + "version": "0.12.119", + "resolved": "https://registry.npmjs.org/@keplr-wallet/types/-/types-0.12.119.tgz", + "integrity": "sha512-J0uuKR89S14UDwMHn1eFueKkLcStmenjPg5DNxzRhe4mt8rg9uL+bNkwANrnPrbtB/tv5QQ6+tE5Hr7JyC55RQ==", "dev": true, "dependencies": { "long": "^4.0.0" @@ -448,9 +449,9 @@ "dev": true }, "apps/deploy-web/node_modules/axios": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", - "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.3.tgz", + "integrity": "sha512-Ar7ND9pU99eJ9GpoGQKhKf58GpUOgnzuaB7ueNQ5BMi0p+LZ5oaEnfF999fAArcTIBwXTCHAmGcHOZJaWPq9Nw==", "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", @@ -572,9 +573,9 @@ } }, "apps/indexer/node_modules/axios": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", - "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.3.tgz", + "integrity": "sha512-Ar7ND9pU99eJ9GpoGQKhKf58GpUOgnzuaB7ueNQ5BMi0p+LZ5oaEnfF999fAArcTIBwXTCHAmGcHOZJaWPq9Nw==", "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", @@ -732,9 +733,9 @@ "dev": true }, "apps/provider-proxy/node_modules/axios": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", - "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.3.tgz", + "integrity": "sha512-Ar7ND9pU99eJ9GpoGQKhKf58GpUOgnzuaB7ueNQ5BMi0p+LZ5oaEnfF999fAArcTIBwXTCHAmGcHOZJaWPq9Nw==", "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", @@ -804,9 +805,9 @@ } }, "apps/stats-web/node_modules/axios": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", - "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.3.tgz", + "integrity": "sha512-Ar7ND9pU99eJ9GpoGQKhKf58GpUOgnzuaB7ueNQ5BMi0p+LZ5oaEnfF999fAArcTIBwXTCHAmGcHOZJaWPq9Nw==", "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", @@ -1009,9 +1010,9 @@ } }, "node_modules/@apollo/client": { - "version": "3.11.1", - "resolved": "https://registry.npmjs.org/@apollo/client/-/client-3.11.1.tgz", - "integrity": "sha512-fVuAi7ufRt2apIEYV18upvykw5JD+CwHAThxZkclby4phWCXtO4LD39Z0sk0+4i+j7oZ+jOofEkO1XGDDomZvQ==", + "version": "3.11.3", + "resolved": "https://registry.npmjs.org/@apollo/client/-/client-3.11.3.tgz", + "integrity": "sha512-bocd9vQc9PSq4MG6129HKF8ujUirVod98he9OEWpyOs5CXnUC81zNRaUshA4wPSflR6mnx0yRuRankp1V8A+og==", "peer": true, "dependencies": { "@graphql-typed-document-node/core": "^3.1.1", @@ -1372,9 +1373,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.7.tgz", - "integrity": "sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", + "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==", "engines": { "node": ">=6.9.0" } @@ -1447,9 +1448,9 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz", - "integrity": "sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", + "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", "engines": { "node": ">=6.9.0" } @@ -2628,16 +2629,16 @@ } }, "node_modules/@babel/plugin-transform-react-jsx": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.24.7.tgz", - "integrity": "sha512-+Dj06GDZEFRYvclU6k4bme55GKBEWUmByM/eoKuqg4zTNQHiApWRhQph5fxQB2wAEFvRzL1tOEj1RJ19wJrhoA==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.25.2.tgz", + "integrity": "sha512-KQsqEAVBpU82NM/B/N9j9WOdphom1SZH3R+2V7INrQUH+V9EBFwZsEJl8eBIVeQE62FxJCc70jzEZwqU7RcVqA==", "peer": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.24.7", "@babel/helper-module-imports": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.8", "@babel/plugin-syntax-jsx": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/types": "^7.25.2" }, "engines": { "node": ">=6.9.0" @@ -3138,11 +3139,11 @@ } }, "node_modules/@babel/types": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.7.tgz", - "integrity": "sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.2.tgz", + "integrity": "sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==", "dependencies": { - "@babel/helper-string-parser": "^7.24.7", + "@babel/helper-string-parser": "^7.24.8", "@babel/helper-validator-identifier": "^7.24.7", "to-fast-properties": "^2.0.0" }, @@ -3358,51 +3359,51 @@ "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" }, "node_modules/@chain-registry/client": { - "version": "1.48.31", - "resolved": "https://registry.npmjs.org/@chain-registry/client/-/client-1.48.31.tgz", - "integrity": "sha512-+6UH4oN3c0n99SEps9FoxFuZrcUK9ZA1nv8+G2IzJYwql3IevzDCNCw9WvLgRKHkPMT+9I/h6OJU3czNYhuMrQ==", + "version": "1.48.41", + "resolved": "https://registry.npmjs.org/@chain-registry/client/-/client-1.48.41.tgz", + "integrity": "sha512-yvqEztdXpFE0p6ap0lOZwgiQsNrk6I02bqmHeXmC+EbDXe/Bkfv1KeQYuFPzwdwtdXKFhWFAi1i155Jmdjl1KA==", "dependencies": { - "@chain-registry/types": "^0.45.31", - "@chain-registry/utils": "^1.46.31", + "@chain-registry/types": "^0.45.41", + "@chain-registry/utils": "^1.46.41", "bfs-path": "^1.0.2", "cross-fetch": "^3.1.5" } }, "node_modules/@chain-registry/client/node_modules/@chain-registry/types": { - "version": "0.45.31", - "resolved": "https://registry.npmjs.org/@chain-registry/types/-/types-0.45.31.tgz", - "integrity": "sha512-91b5sgi5uw1zuIxjhEDc6SO+Oa1eaYA/Mf4jvaxFx19iO80d0dpG0eDvAU1vDorWmibhoJAk1B/0Qnm6cZQ9Jg==" + "version": "0.45.41", + "resolved": "https://registry.npmjs.org/@chain-registry/types/-/types-0.45.41.tgz", + "integrity": "sha512-OCCuqFE5xmjxwe0ardEe+ozKS0NeDOIgqpfjA7TeTQAWkRexty1j5PXZW/1/73C0wJ1/b/GXILsXwSi44w5MzA==" }, "node_modules/@chain-registry/cosmostation": { - "version": "1.66.38", - "resolved": "https://registry.npmjs.org/@chain-registry/cosmostation/-/cosmostation-1.66.38.tgz", - "integrity": "sha512-K/dCgWX5Z0rYY5TvKrmEX9H8GQ8wn+JVIBQB9Mnq7dCTTS6aL3UR7onGN79wEm8qClYQY52qoiazvXcrwVlA4A==", + "version": "1.66.51", + "resolved": "https://registry.npmjs.org/@chain-registry/cosmostation/-/cosmostation-1.66.51.tgz", + "integrity": "sha512-ZuZlXT+YEBxB6Th/DVfOBGCCDH9Tf2sOi2RR9nGaJTL3dlxx+mp+glj1Yn6pCoa5RLwJvAG4WT30getCGI40pQ==", "dependencies": { - "@chain-registry/types": "^0.45.31", - "@chain-registry/utils": "^1.46.31", + "@chain-registry/types": "^0.45.41", + "@chain-registry/utils": "^1.46.41", "@cosmostation/extension-client": "0.1.15" } }, "node_modules/@chain-registry/cosmostation/node_modules/@chain-registry/types": { - "version": "0.45.31", - "resolved": "https://registry.npmjs.org/@chain-registry/types/-/types-0.45.31.tgz", - "integrity": "sha512-91b5sgi5uw1zuIxjhEDc6SO+Oa1eaYA/Mf4jvaxFx19iO80d0dpG0eDvAU1vDorWmibhoJAk1B/0Qnm6cZQ9Jg==" + "version": "0.45.41", + "resolved": "https://registry.npmjs.org/@chain-registry/types/-/types-0.45.41.tgz", + "integrity": "sha512-OCCuqFE5xmjxwe0ardEe+ozKS0NeDOIgqpfjA7TeTQAWkRexty1j5PXZW/1/73C0wJ1/b/GXILsXwSi44w5MzA==" }, "node_modules/@chain-registry/keplr": { - "version": "1.68.38", - "resolved": "https://registry.npmjs.org/@chain-registry/keplr/-/keplr-1.68.38.tgz", - "integrity": "sha512-xYg2rmEwF4Ci1MHf5eryVa5jv5eLYjBfgC6p6/pJwx3tr3uAWzElmVVQHJocPEuWxGGthUV5jGlYp2jbU/bO2A==", + "version": "1.68.51", + "resolved": "https://registry.npmjs.org/@chain-registry/keplr/-/keplr-1.68.51.tgz", + "integrity": "sha512-ALzxpos4YhISRw2VQqwL9kKFmI11ypOJccwif32TEBiLFX0A4hJ+HhSbpeocbO5ZNc2mBHBlVCJMAreCFVYS1g==", "dependencies": { - "@chain-registry/types": "^0.45.31", + "@chain-registry/types": "^0.45.41", "@keplr-wallet/cosmos": "0.12.28", "@keplr-wallet/crypto": "0.12.28", "semver": "^7.5.0" } }, "node_modules/@chain-registry/keplr/node_modules/@chain-registry/types": { - "version": "0.45.31", - "resolved": "https://registry.npmjs.org/@chain-registry/types/-/types-0.45.31.tgz", - "integrity": "sha512-91b5sgi5uw1zuIxjhEDc6SO+Oa1eaYA/Mf4jvaxFx19iO80d0dpG0eDvAU1vDorWmibhoJAk1B/0Qnm6cZQ9Jg==" + "version": "0.45.41", + "resolved": "https://registry.npmjs.org/@chain-registry/types/-/types-0.45.41.tgz", + "integrity": "sha512-OCCuqFE5xmjxwe0ardEe+ozKS0NeDOIgqpfjA7TeTQAWkRexty1j5PXZW/1/73C0wJ1/b/GXILsXwSi44w5MzA==" }, "node_modules/@chain-registry/types": { "version": "0.41.4", @@ -3410,19 +3411,19 @@ "integrity": "sha512-LeW2nXd0dmnI+TC4L2xC3RNooUthUfou1yFcdsinSW6EP9qEwo2BwwnWWAcMG+dsPvXfM4AfmisVBrxbX2OKqw==" }, "node_modules/@chain-registry/utils": { - "version": "1.46.31", - "resolved": "https://registry.npmjs.org/@chain-registry/utils/-/utils-1.46.31.tgz", - "integrity": "sha512-GEn4qpWTtFdLo1b6ny3D4K744Poasuc0lAMIoM+xWgMi7x46pcyY9U/5SL849NRmOzmEUhpXCX5C7mehuNpsEA==", + "version": "1.46.41", + "resolved": "https://registry.npmjs.org/@chain-registry/utils/-/utils-1.46.41.tgz", + "integrity": "sha512-LMcUoz/AK47UGpf6pxKFvmCSd8lUgYqiPhlBPHa70Xf8N99oIqhYduTCG8YgkeUia3zseNIAfJTp/QVu9pXkdw==", "dependencies": { - "@chain-registry/types": "^0.45.31", + "@chain-registry/types": "^0.45.41", "bignumber.js": "9.1.2", "sha.js": "^2.4.11" } }, "node_modules/@chain-registry/utils/node_modules/@chain-registry/types": { - "version": "0.45.31", - "resolved": "https://registry.npmjs.org/@chain-registry/types/-/types-0.45.31.tgz", - "integrity": "sha512-91b5sgi5uw1zuIxjhEDc6SO+Oa1eaYA/Mf4jvaxFx19iO80d0dpG0eDvAU1vDorWmibhoJAk1B/0Qnm6cZQ9Jg==" + "version": "0.45.41", + "resolved": "https://registry.npmjs.org/@chain-registry/types/-/types-0.45.41.tgz", + "integrity": "sha512-OCCuqFE5xmjxwe0ardEe+ozKS0NeDOIgqpfjA7TeTQAWkRexty1j5PXZW/1/73C0wJ1/b/GXILsXwSi44w5MzA==" }, "node_modules/@coinbase/wallet-sdk": { "version": "3.9.3", @@ -4336,9 +4337,9 @@ } }, "node_modules/@cosmos-kit/core/node_modules/@chain-registry/types": { - "version": "0.45.31", - "resolved": "https://registry.npmjs.org/@chain-registry/types/-/types-0.45.31.tgz", - "integrity": "sha512-91b5sgi5uw1zuIxjhEDc6SO+Oa1eaYA/Mf4jvaxFx19iO80d0dpG0eDvAU1vDorWmibhoJAk1B/0Qnm6cZQ9Jg==" + "version": "0.45.41", + "resolved": "https://registry.npmjs.org/@chain-registry/types/-/types-0.45.41.tgz", + "integrity": "sha512-OCCuqFE5xmjxwe0ardEe+ozKS0NeDOIgqpfjA7TeTQAWkRexty1j5PXZW/1/73C0wJ1/b/GXILsXwSi44w5MzA==" }, "node_modules/@cosmos-kit/core/node_modules/@cosmjs/encoding": { "version": "0.32.4", @@ -4376,9 +4377,9 @@ } }, "node_modules/@cosmos-kit/cosmostation-extension": { - "version": "2.12.2", - "resolved": "https://registry.npmjs.org/@cosmos-kit/cosmostation-extension/-/cosmostation-extension-2.12.2.tgz", - "integrity": "sha512-8+DTbm8t3PkHPoQ2c+vssrCR5rIqt6mPedyxGxsd1d4/H8RiZhkxtGIen+oDaGlLe62V6CD7AkLQ+I9HkSNzQA==", + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/@cosmos-kit/cosmostation-extension/-/cosmostation-extension-2.13.0.tgz", + "integrity": "sha512-yqIveox4wRQxZ32Pu+IzlKYiryLJciW9xW2sggaHaaAK+HOvQIlK29kHYIpBPKoVDfhWtbJM5pLYuhnA9/J2Wg==", "dependencies": { "@chain-registry/cosmostation": "^1.66.2", "@cosmos-kit/core": "^2.13.1", @@ -4414,9 +4415,9 @@ } }, "node_modules/@cosmos-kit/keplr-extension/node_modules/@keplr-wallet/types": { - "version": "0.12.113", - "resolved": "https://registry.npmjs.org/@keplr-wallet/types/-/types-0.12.113.tgz", - "integrity": "sha512-xin9BotERBPq2KcubSTCe7rLtnLbXorZdODX8yzke7IrDBMidq3YKdubNU3C/kfPw77n0rhQZibMY/W9lh3oZQ==", + "version": "0.12.119", + "resolved": "https://registry.npmjs.org/@keplr-wallet/types/-/types-0.12.119.tgz", + "integrity": "sha512-J0uuKR89S14UDwMHn1eFueKkLcStmenjPg5DNxzRhe4mt8rg9uL+bNkwANrnPrbtB/tv5QQ6+tE5Hr7JyC55RQ==", "dependencies": { "long": "^4.0.0" } @@ -4455,9 +4456,9 @@ } }, "node_modules/@cosmos-kit/keplr-mobile/node_modules/@chain-registry/types": { - "version": "0.45.31", - "resolved": "https://registry.npmjs.org/@chain-registry/types/-/types-0.45.31.tgz", - "integrity": "sha512-91b5sgi5uw1zuIxjhEDc6SO+Oa1eaYA/Mf4jvaxFx19iO80d0dpG0eDvAU1vDorWmibhoJAk1B/0Qnm6cZQ9Jg==" + "version": "0.45.41", + "resolved": "https://registry.npmjs.org/@chain-registry/types/-/types-0.45.41.tgz", + "integrity": "sha512-OCCuqFE5xmjxwe0ardEe+ozKS0NeDOIgqpfjA7TeTQAWkRexty1j5PXZW/1/73C0wJ1/b/GXILsXwSi44w5MzA==" }, "node_modules/@cosmos-kit/leap-extension": { "version": "2.12.2", @@ -4484,9 +4485,9 @@ } }, "node_modules/@cosmos-kit/leap-extension/node_modules/@chain-registry/types": { - "version": "0.45.31", - "resolved": "https://registry.npmjs.org/@chain-registry/types/-/types-0.45.31.tgz", - "integrity": "sha512-91b5sgi5uw1zuIxjhEDc6SO+Oa1eaYA/Mf4jvaxFx19iO80d0dpG0eDvAU1vDorWmibhoJAk1B/0Qnm6cZQ9Jg==" + "version": "0.45.41", + "resolved": "https://registry.npmjs.org/@chain-registry/types/-/types-0.45.41.tgz", + "integrity": "sha512-OCCuqFE5xmjxwe0ardEe+ozKS0NeDOIgqpfjA7TeTQAWkRexty1j5PXZW/1/73C0wJ1/b/GXILsXwSi44w5MzA==" }, "node_modules/@cosmos-kit/react": { "version": "2.18.0", @@ -4523,14 +4524,14 @@ } }, "node_modules/@cosmos-kit/react-lite/node_modules/@chain-registry/types": { - "version": "0.45.31", - "resolved": "https://registry.npmjs.org/@chain-registry/types/-/types-0.45.31.tgz", - "integrity": "sha512-91b5sgi5uw1zuIxjhEDc6SO+Oa1eaYA/Mf4jvaxFx19iO80d0dpG0eDvAU1vDorWmibhoJAk1B/0Qnm6cZQ9Jg==" + "version": "0.45.41", + "resolved": "https://registry.npmjs.org/@chain-registry/types/-/types-0.45.41.tgz", + "integrity": "sha512-OCCuqFE5xmjxwe0ardEe+ozKS0NeDOIgqpfjA7TeTQAWkRexty1j5PXZW/1/73C0wJ1/b/GXILsXwSi44w5MzA==" }, "node_modules/@cosmos-kit/react/node_modules/@chain-registry/types": { - "version": "0.45.31", - "resolved": "https://registry.npmjs.org/@chain-registry/types/-/types-0.45.31.tgz", - "integrity": "sha512-91b5sgi5uw1zuIxjhEDc6SO+Oa1eaYA/Mf4jvaxFx19iO80d0dpG0eDvAU1vDorWmibhoJAk1B/0Qnm6cZQ9Jg==" + "version": "0.45.41", + "resolved": "https://registry.npmjs.org/@chain-registry/types/-/types-0.45.41.tgz", + "integrity": "sha512-OCCuqFE5xmjxwe0ardEe+ozKS0NeDOIgqpfjA7TeTQAWkRexty1j5PXZW/1/73C0wJ1/b/GXILsXwSi44w5MzA==" }, "node_modules/@cosmos-kit/walletconnect": { "version": "2.10.1", @@ -6252,13 +6253,13 @@ } }, "node_modules/@floating-ui/react": { - "version": "0.26.20", - "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.20.tgz", - "integrity": "sha512-RixKJJG92fcIsVoqrFr4Onpzh7hlOx4U7NV4aLhMLmtvjZ5oTB/WzXaANYUZATKqXvvW7t9sCxtzejip26N5Ag==", + "version": "0.26.21", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.21.tgz", + "integrity": "sha512-7P5ncDIiYd6RrwpCDbKyFzvabM014QlzlumtDbK3Bck0UueC+Rp8BLS34qcGBcN1pZCTodl4QNnCVmKv4tSxfQ==", "peer": true, "dependencies": { "@floating-ui/react-dom": "^2.1.1", - "@floating-ui/utils": "^0.2.5", + "@floating-ui/utils": "^0.2.6", "tabbable": "^6.0.0" }, "peerDependencies": { @@ -6279,9 +6280,9 @@ } }, "node_modules/@floating-ui/utils": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.5.tgz", - "integrity": "sha512-sTcG+QZ6fdEUObICavU+aB3Mp8HY4n14wYHdxK4fXjPmv3PXZZeY5RaguJmGyeH/CJQhX3fqKUtS4qc1LoHwhQ==" + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.6.tgz", + "integrity": "sha512-0KI3zGxIUs1KDR/pjQPdJH4Z8nGBm0yJ5WRoRfdw1Kzeh45jkIfA0rmD0kBF6fKHH+xaH7g8y4jIXyAV5MGK3g==" }, "node_modules/@formatjs/ecma402-abstract": { "version": "2.0.0", @@ -6578,9 +6579,9 @@ } }, "node_modules/@injectivelabs/core-proto-ts": { - "version": "0.0.27", - "resolved": "https://registry.npmjs.org/@injectivelabs/core-proto-ts/-/core-proto-ts-0.0.27.tgz", - "integrity": "sha512-jGGjBp0Jp051S5nnvkzgfceHGwUlS05DsbC7coYI5MjFpoWnNKN4DXJb1vo3kYvWUMTYUjLEUIB+3hC1t4SuDA==", + "version": "0.0.28", + "resolved": "https://registry.npmjs.org/@injectivelabs/core-proto-ts/-/core-proto-ts-0.0.28.tgz", + "integrity": "sha512-AHzRa8Us48HjciHS9KmZEgLuMBByeujZSyZv8xHOZku97pYapO7grIaLtLH41iMSD0bOAWqfV1SBgywYkOrDug==", "peer": true, "dependencies": { "@injectivelabs/grpc-web": "^0.0.1", @@ -6880,9 +6881,9 @@ } }, "node_modules/@injectivelabs/sdk-ts/node_modules/axios": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", - "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.3.tgz", + "integrity": "sha512-Ar7ND9pU99eJ9GpoGQKhKf58GpUOgnzuaB7ueNQ5BMi0p+LZ5oaEnfF999fAArcTIBwXTCHAmGcHOZJaWPq9Nw==", "peer": true, "dependencies": { "follow-redirects": "^1.15.6", @@ -6936,9 +6937,9 @@ } }, "node_modules/@injectivelabs/test-utils/node_modules/axios": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", - "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.3.tgz", + "integrity": "sha512-Ar7ND9pU99eJ9GpoGQKhKf58GpUOgnzuaB7ueNQ5BMi0p+LZ5oaEnfF999fAArcTIBwXTCHAmGcHOZJaWPq9Nw==", "peer": true, "dependencies": { "follow-redirects": "^1.15.6", @@ -6976,9 +6977,9 @@ } }, "node_modules/@injectivelabs/utils/node_modules/axios": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", - "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.3.tgz", + "integrity": "sha512-Ar7ND9pU99eJ9GpoGQKhKf58GpUOgnzuaB7ueNQ5BMi0p+LZ5oaEnfF999fAArcTIBwXTCHAmGcHOZJaWPq9Nw==", "peer": true, "dependencies": { "follow-redirects": "^1.15.6", @@ -6987,9 +6988,9 @@ } }, "node_modules/@interchain-ui/react": { - "version": "1.23.28", - "resolved": "https://registry.npmjs.org/@interchain-ui/react/-/react-1.23.28.tgz", - "integrity": "sha512-xLKZQA9x45DB0QOk/fkKZAqDGnaW+s4aDQ5A6gOGAuiH/KxmgBIZZJtWEQ8+ZirLUzSj2TEZbs/1qbcqd8QqlA==", + "version": "1.23.29", + "resolved": "https://registry.npmjs.org/@interchain-ui/react/-/react-1.23.29.tgz", + "integrity": "sha512-t9aPw0BBmf/ePLGvBARAeoFZWy1vO+K7Ch9d5jtpqp5V07A2jCtMKwCNNbhBkllGmKAjZWvF/gTuj1ozz+W3xg==", "peer": true, "dependencies": { "@floating-ui/core": "^1.6.4", @@ -7001,6 +7002,7 @@ "@react-aria/listbox": "^3.12.1", "@react-aria/overlays": "^3.22.1", "@react-aria/utils": "^3.24.1", + "@tanstack/react-virtual": "^3.8.3", "@vanilla-extract/css": "^1.15.3", "@vanilla-extract/dynamic": "^2.1.1", "@vanilla-extract/recipes": "^0.5.3", @@ -8284,31 +8286,31 @@ "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" }, "node_modules/@keplr-wallet/provider": { - "version": "0.12.113", - "resolved": "https://registry.npmjs.org/@keplr-wallet/provider/-/provider-0.12.113.tgz", - "integrity": "sha512-u/hmsbh3CcHqq9Yfmda1c3DdLgsFguCu28NLkKNe+VQ+cczekncmHT/dVJrF9GfzZWdv9Y3+gtEmp5wE353IHg==", + "version": "0.12.119", + "resolved": "https://registry.npmjs.org/@keplr-wallet/provider/-/provider-0.12.119.tgz", + "integrity": "sha512-NfA6+HqzUdlCld5wbqMRu6fg3rcAWdXp8WbaujTp6RKyGQj3C5ex4pG81SzLmgq+Idjma/CrFCNqdg7SMwwbpA==", "dependencies": { - "@keplr-wallet/router": "0.12.113", - "@keplr-wallet/types": "0.12.113", + "@keplr-wallet/router": "0.12.119", + "@keplr-wallet/types": "0.12.119", "buffer": "^6.0.3", "deepmerge": "^4.2.2", "long": "^4.0.0" } }, "node_modules/@keplr-wallet/provider-extension": { - "version": "0.12.113", - "resolved": "https://registry.npmjs.org/@keplr-wallet/provider-extension/-/provider-extension-0.12.113.tgz", - "integrity": "sha512-xbw2cBSv3dQdS1UECiBXpZ52GqZOJGIQnTZ14Y8U2sl5GK4g6CKhQTXlh7lZnUyNKAoZqazqDBGuIjb8CMcP9A==", + "version": "0.12.119", + "resolved": "https://registry.npmjs.org/@keplr-wallet/provider-extension/-/provider-extension-0.12.119.tgz", + "integrity": "sha512-KSErxeSHUTU34f1pfJ/0JA5cwQPPRAw7pw4fyePZwNMnAiw3FuRmYCTgMDTFb+6JIjArHvrbGdOHR45/kP9I0A==", "dependencies": { - "@keplr-wallet/types": "0.12.113", + "@keplr-wallet/types": "0.12.119", "deepmerge": "^4.2.2", "long": "^4.0.0" } }, "node_modules/@keplr-wallet/provider-extension/node_modules/@keplr-wallet/types": { - "version": "0.12.113", - "resolved": "https://registry.npmjs.org/@keplr-wallet/types/-/types-0.12.113.tgz", - "integrity": "sha512-xin9BotERBPq2KcubSTCe7rLtnLbXorZdODX8yzke7IrDBMidq3YKdubNU3C/kfPw77n0rhQZibMY/W9lh3oZQ==", + "version": "0.12.119", + "resolved": "https://registry.npmjs.org/@keplr-wallet/types/-/types-0.12.119.tgz", + "integrity": "sha512-J0uuKR89S14UDwMHn1eFueKkLcStmenjPg5DNxzRhe4mt8rg9uL+bNkwANrnPrbtB/tv5QQ6+tE5Hr7JyC55RQ==", "dependencies": { "long": "^4.0.0" } @@ -8319,9 +8321,9 @@ "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" }, "node_modules/@keplr-wallet/provider/node_modules/@keplr-wallet/types": { - "version": "0.12.113", - "resolved": "https://registry.npmjs.org/@keplr-wallet/types/-/types-0.12.113.tgz", - "integrity": "sha512-xin9BotERBPq2KcubSTCe7rLtnLbXorZdODX8yzke7IrDBMidq3YKdubNU3C/kfPw77n0rhQZibMY/W9lh3oZQ==", + "version": "0.12.119", + "resolved": "https://registry.npmjs.org/@keplr-wallet/types/-/types-0.12.119.tgz", + "integrity": "sha512-J0uuKR89S14UDwMHn1eFueKkLcStmenjPg5DNxzRhe4mt8rg9uL+bNkwANrnPrbtB/tv5QQ6+tE5Hr7JyC55RQ==", "dependencies": { "long": "^4.0.0" } @@ -8332,9 +8334,9 @@ "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" }, "node_modules/@keplr-wallet/router": { - "version": "0.12.113", - "resolved": "https://registry.npmjs.org/@keplr-wallet/router/-/router-0.12.113.tgz", - "integrity": "sha512-yFpzL4Js2BwUwfumqzgtFcOgJvrEWblRUirSNNvR555a17ilNtL/r6vJcekp7AVwhEgPDDf9FDRpHvCcGn1j4Q==" + "version": "0.12.119", + "resolved": "https://registry.npmjs.org/@keplr-wallet/router/-/router-0.12.119.tgz", + "integrity": "sha512-nMsGGBGKr9sX0cgLVKbBKBcaesmrNgcHHshgIL1Ic9oS/13VOnM+aJMsqzS8MBaCfnIuqMorvOgUJVZllFDaSQ==" }, "node_modules/@keplr-wallet/simple-fetch": { "version": "0.12.28", @@ -8490,12 +8492,12 @@ "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" }, "node_modules/@keplr-wallet/wc-client": { - "version": "0.12.113", - "resolved": "https://registry.npmjs.org/@keplr-wallet/wc-client/-/wc-client-0.12.113.tgz", - "integrity": "sha512-ZfGbFGDq09TiVpeUpe5kH3cl2Lhsv/pUAj+mL/jqsZcmy3uk48kophqeoBKXaJ1hWLQFvsli9gbWe06x5VwfaA==", + "version": "0.12.119", + "resolved": "https://registry.npmjs.org/@keplr-wallet/wc-client/-/wc-client-0.12.119.tgz", + "integrity": "sha512-r6HHGJo5KqL8IPCGlrplbaEyKsCKRPnmYm10Kjua2eTgLpS2c6tACinaJ+jjqfMkYZFd/NEDXSLLrWa90L4LyA==", "dependencies": { - "@keplr-wallet/provider": "0.12.113", - "@keplr-wallet/types": "0.12.113", + "@keplr-wallet/provider": "0.12.119", + "@keplr-wallet/types": "0.12.119", "buffer": "^6.0.3", "deepmerge": "^4.2.2", "long": "^3 || ^4 || ^5" @@ -8506,9 +8508,9 @@ } }, "node_modules/@keplr-wallet/wc-client/node_modules/@keplr-wallet/types": { - "version": "0.12.113", - "resolved": "https://registry.npmjs.org/@keplr-wallet/types/-/types-0.12.113.tgz", - "integrity": "sha512-xin9BotERBPq2KcubSTCe7rLtnLbXorZdODX8yzke7IrDBMidq3YKdubNU3C/kfPw77n0rhQZibMY/W9lh3oZQ==", + "version": "0.12.119", + "resolved": "https://registry.npmjs.org/@keplr-wallet/types/-/types-0.12.119.tgz", + "integrity": "sha512-J0uuKR89S14UDwMHn1eFueKkLcStmenjPg5DNxzRhe4mt8rg9uL+bNkwANrnPrbtB/tv5QQ6+tE5Hr7JyC55RQ==", "dependencies": { "long": "^4.0.0" } @@ -8719,9 +8721,9 @@ } }, "node_modules/@leapwallet/elements": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/@leapwallet/elements/-/elements-1.3.4.tgz", - "integrity": "sha512-oC8hyhmCGwHwEfA686aP09JcaD/wskqEL5ngcGpZZJfaZOhywus+U1ZrEuqMw+zYNOoUMYPQ2YpId24KhVMvQw==", + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@leapwallet/elements/-/elements-1.3.5.tgz", + "integrity": "sha512-7rFYElOIZeSgYCq/zaKwkK9yvi5NuhdcFH8vqZuIcR0yIjMuSOSkmbZlgzBvX27KzHtABGNp04nsWwpI3icDqw==", "hasInstallScript": true, "dependencies": { "@cosmjs/amino": "0.32.3", @@ -8734,8 +8736,8 @@ "@cosmjs/tendermint-rpc": "0.32.3", "@keplr-wallet/types": "0.12.100", "@leapwallet/cosmos-social-login-capsule-provider": "0.0.31-beta.2", - "@leapwallet/elements-core": "1.3.4", - "@leapwallet/elements-hooks": "1.3.4", + "@leapwallet/elements-core": "1.3.5", + "@leapwallet/elements-hooks": "1.3.5", "@leapwallet/react-ui": "1.10.2", "@phosphor-icons/react": "2.1.5", "@rainbow-me/rainbowkit": "1.2.0", @@ -8790,9 +8792,9 @@ } }, "node_modules/@leapwallet/elements-core": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/@leapwallet/elements-core/-/elements-core-1.3.4.tgz", - "integrity": "sha512-lRy8cJOtLlb9UkAD+J3991dLn88hIuvv4qPTY1APoI+CVM9q3nNMVCjvaZSCEGaB4b5qcSyoLkJ4s/oqu6Euow==", + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@leapwallet/elements-core/-/elements-core-1.3.5.tgz", + "integrity": "sha512-CNfh7ECu6wHaoa5j5NGqxO5LKP7koRJQqEBzZom2JGnux+yvRBxi5fSNhFRnUatM7U7oSj3Z980qapMzf8eMig==", "dependencies": { "@cosmjs/amino": "0.32.3", "@cosmjs/cosmwasm-stargate": "0.32.3", @@ -8951,16 +8953,16 @@ } }, "node_modules/@leapwallet/elements-hooks": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/@leapwallet/elements-hooks/-/elements-hooks-1.3.4.tgz", - "integrity": "sha512-Xjmy/VL5MM1H9qpuVjeB/cX4pr5HfJPxFMB5i747uvmPlZFtQI180lBn254lvL7wMumsP6IrwefNKel4bWsG7A==", + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@leapwallet/elements-hooks/-/elements-hooks-1.3.5.tgz", + "integrity": "sha512-UAShOKRHK7bF9duh10qwlm9GGFf+CO7JiEhJp4Dp/sENV9yXgm/b6iC1ns+gL9LxIOENx67blyEXk4l+Wco8PA==", "dependencies": { "@cosmjs/amino": "0.32.3", "@cosmjs/encoding": "0.32.3", "@cosmjs/math": "0.32.3", "@cosmjs/proto-signing": "0.32.3", "@cosmjs/stargate": "0.32.3", - "@leapwallet/elements-core": "1.3.4", + "@leapwallet/elements-core": "1.3.5", "@leapwallet/name-matcha": "1.6.0", "bech32": "2.0.0", "bignumber.js": "9.1.1", @@ -9532,9 +9534,9 @@ } }, "node_modules/@leapwallet/elements/node_modules/axios": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", - "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.3.tgz", + "integrity": "sha512-Ar7ND9pU99eJ9GpoGQKhKf58GpUOgnzuaB7ueNQ5BMi0p+LZ5oaEnfF999fAArcTIBwXTCHAmGcHOZJaWPq9Nw==", "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", @@ -9563,9 +9565,9 @@ } }, "node_modules/@leapwallet/name-matcha": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@leapwallet/name-matcha/-/name-matcha-1.9.0.tgz", - "integrity": "sha512-vtzr56NjzlQ1ZQCxIXd+OhrKZ3ureytz7gVgk7NcUNhrGEe3SNbJ7xNXjMvVKeZTymEt0DoHChp17yPCE0YHNQ==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@leapwallet/name-matcha/-/name-matcha-1.10.0.tgz", + "integrity": "sha512-BujFORLZztG24TMiiBbmkGmFSVwbZKs9K8ZDpUQSk2qgPQ+ELuuINXbMtuRJC9HCOA9YrHy33OfYt1ZfETB2CQ==", "dependencies": { "@cosmjs/cosmwasm-stargate": "0.30.0", "bech32": "1.1.4", @@ -9850,9 +9852,9 @@ "integrity": "sha512-swKHYCOZUGyVt4ge0u8a7AwNcA//h4nx5wIi0sruGye1IJ5Cva0GyK9L2/WdX+kWVTKp92ZiEo1df31lrWGPgA==" }, "node_modules/@lit-labs/ssr-dom-shim": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.2.0.tgz", - "integrity": "sha512-yWJKmpGE6lUURKAaIltoPIE/wrbY3TEkqQt+X0m+7fQNnAv0keydnYvbiJFP1PnMhizmIWRWOG5KLhYyc/xl+g==" + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.2.1.tgz", + "integrity": "sha512-wx4aBmgeGvFmOKucFKY+8VFJSYZxs9poN3SDNQFF6lT6NrQUnHiPB2PWz2sc4ieEcAaYYzN+1uWahEeTq2aRIQ==" }, "node_modules/@lit/reactive-element": { "version": "1.6.3", @@ -10193,9 +10195,9 @@ } }, "node_modules/@mui/icons-material": { - "version": "5.16.4", - "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.16.4.tgz", - "integrity": "sha512-j9/CWctv6TH6Dou2uR2EH7UOgu79CW/YcozxCYVLJ7l03pCsiOlJ5sBArnWJxJ+nGkFwyL/1d1k8JEPMDR125A==", + "version": "5.16.6", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.16.6.tgz", + "integrity": "sha512-ceNGjoXheH9wbIFa1JHmSc9QVjJUvh18KvHrR4/FkJCSi9HXJ+9ee1kUhCOEFfuxNF8UB6WWVrIUOUgRd70t0A==", "dependencies": { "@babel/runtime": "^7.23.9" }, @@ -10263,9 +10265,9 @@ } }, "node_modules/@mui/material-nextjs": { - "version": "5.16.4", - "resolved": "https://registry.npmjs.org/@mui/material-nextjs/-/material-nextjs-5.16.4.tgz", - "integrity": "sha512-m2fY/bdfvpUXkjv2k5cwqd42FJZ8QRuZ1MoWt6RW480yIVi4ZRFpccBnJjiC4rXIeslmd/jizHi65Hbz/L/AKQ==", + "version": "5.16.6", + "resolved": "https://registry.npmjs.org/@mui/material-nextjs/-/material-nextjs-5.16.6.tgz", + "integrity": "sha512-Y64ybP5Pmy+GCUcu3SuMnc25CqFwBQkRn6XNXyhvc1mhR48Iq0oFKjoO5ncqfhm58OwPClIRW2tecP/PTdGNJw==", "dependencies": { "@babel/runtime": "^7.23.9" }, @@ -11596,11 +11598,6 @@ "url": "https://opencollective.com/parcel" } }, - "node_modules/@parcel/watcher-wasm/node_modules/napi-wasm": { - "version": "1.1.0", - "inBundle": true, - "license": "MIT" - }, "node_modules/@parcel/watcher-win32-arm64": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.4.1.tgz", @@ -11696,12 +11693,12 @@ } }, "node_modules/@playwright/test": { - "version": "1.45.3", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.45.3.tgz", - "integrity": "sha512-UKF4XsBfy+u3MFWEH44hva1Q8Da28G6RFtR2+5saw+jgAFQV5yYnB1fu68Mz7fO+5GJF3wgwAIs0UelU8TxFrA==", + "version": "1.46.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.46.0.tgz", + "integrity": "sha512-/QYft5VArOrGRP5pgkrfKksqsKA6CEFyGQ/gjNe6q0y4tZ1aaPfq4gIjudr1s3D+pXyrPRdsy4opKDrjBabE5w==", "devOptional": true, "dependencies": { - "playwright": "1.45.3" + "playwright": "1.46.0" }, "bin": { "playwright": "cli.js" @@ -14646,9 +14643,9 @@ } }, "node_modules/@react-native-community/cli-doctor/node_modules/yaml": { - "version": "2.4.5", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.5.tgz", - "integrity": "sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.0.tgz", + "integrity": "sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw==", "peer": true, "bin": { "yaml": "bin.mjs" @@ -15353,30 +15350,30 @@ } }, "node_modules/@react-native/assets-registry": { - "version": "0.74.85", - "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.74.85.tgz", - "integrity": "sha512-59YmIQxfGDw4aP9S/nAM+sjSFdW8fUP6fsqczCcXgL2YVEjyER9XCaUT0J1K+PdHep8pi05KUgIKUds8P3jbmA==", + "version": "0.74.87", + "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.74.87.tgz", + "integrity": "sha512-1XmRhqQchN+pXPKEKYdpJlwESxVomJOxtEnIkbo7GAlaN2sym84fHEGDXAjLilih5GVPpcpSmFzTy8jx3LtaFg==", "peer": true, "engines": { "node": ">=18" } }, "node_modules/@react-native/babel-plugin-codegen": { - "version": "0.74.85", - "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.74.85.tgz", - "integrity": "sha512-48TSDclRB5OMXiImiJkLxyCfRyLsqkCgI8buugCZzvXcYslfV7gCvcyFyQldtcOmerV+CK4RAj7QS4hmB5Mr8Q==", + "version": "0.74.87", + "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.74.87.tgz", + "integrity": "sha512-+vJYpMnENFrwtgvDfUj+CtVJRJuUnzAUYT0/Pb68Sq9RfcZ5xdcCuUgyf7JO+akW2VTBoJY427wkcxU30qrWWw==", "peer": true, "dependencies": { - "@react-native/codegen": "0.74.85" + "@react-native/codegen": "0.74.87" }, "engines": { "node": ">=18" } }, "node_modules/@react-native/babel-preset": { - "version": "0.74.85", - "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.74.85.tgz", - "integrity": "sha512-yMHUlN8INbK5BBwiBuQMftdWkpm1IgCsoJTKcGD2OpSgZhwwm8RUSvGhdRMzB2w7bsqqBmaEMleGtW6aCR7B9w==", + "version": "0.74.87", + "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.74.87.tgz", + "integrity": "sha512-hyKpfqzN2nxZmYYJ0tQIHG99FQO0OWXp/gVggAfEUgiT+yNKas1C60LuofUsK7cd+2o9jrpqgqW4WzEDZoBlTg==", "peer": true, "dependencies": { "@babel/core": "^7.20.0", @@ -15419,7 +15416,7 @@ "@babel/plugin-transform-typescript": "^7.5.0", "@babel/plugin-transform-unicode-regex": "^7.0.0", "@babel/template": "^7.0.0", - "@react-native/babel-plugin-codegen": "0.74.85", + "@react-native/babel-plugin-codegen": "0.74.87", "babel-plugin-transform-flow-enums": "^0.0.2", "react-refresh": "^0.14.0" }, @@ -15431,9 +15428,9 @@ } }, "node_modules/@react-native/codegen": { - "version": "0.74.85", - "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.74.85.tgz", - "integrity": "sha512-N7QwoS4Hq/uQmoH83Ewedy6D0M7xbQsOU3OMcQf0eY3ltQ7S2hd9/R4UTalQWRn1OUJfXR6OG12QJ4FStKgV6Q==", + "version": "0.74.87", + "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.74.87.tgz", + "integrity": "sha512-GMSYDiD+86zLKgMMgz9z0k6FxmRn+z6cimYZKkucW4soGbxWsbjUAZoZ56sJwt2FJ3XVRgXCrnOCgXoH/Bkhcg==", "peer": true, "dependencies": { "@babel/parser": "^7.20.0", @@ -15645,15 +15642,15 @@ } }, "node_modules/@react-native/community-cli-plugin": { - "version": "0.74.85", - "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.74.85.tgz", - "integrity": "sha512-ODzND33eA2owAY3g9jgCdqB+BjAh8qJ7dvmSotXgrgDYr3MJMpd8gvHTIPe2fg4Kab+wk8uipRhrE0i0RYMwtQ==", + "version": "0.74.87", + "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.74.87.tgz", + "integrity": "sha512-EgJG9lSr8x3X67dHQKQvU6EkO+3ksVlJHYIVv6U/AmW9dN80BEFxgYbSJ7icXS4wri7m4kHdgeq2PQ7/3vvrTQ==", "peer": true, "dependencies": { "@react-native-community/cli-server-api": "13.6.9", "@react-native-community/cli-tools": "13.6.9", - "@react-native/dev-middleware": "0.74.85", - "@react-native/metro-babel-transformer": "0.74.85", + "@react-native/dev-middleware": "0.74.87", + "@react-native/metro-babel-transformer": "0.74.87", "chalk": "^4.0.0", "execa": "^5.1.1", "metro": "^0.80.3", @@ -15738,22 +15735,22 @@ } }, "node_modules/@react-native/debugger-frontend": { - "version": "0.74.85", - "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.74.85.tgz", - "integrity": "sha512-gUIhhpsYLUTYWlWw4vGztyHaX/kNlgVspSvKe2XaPA7o3jYKUoNLc3Ov7u70u/MBWfKdcEffWq44eSe3j3s5JQ==", + "version": "0.74.87", + "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.74.87.tgz", + "integrity": "sha512-MN95DJLYTv4EqJc+9JajA3AJZSBYJz2QEJ3uWlHrOky2vKrbbRVaW1ityTmaZa2OXIvNc6CZwSRSE7xCoHbXhQ==", "peer": true, "engines": { "node": ">=18" } }, "node_modules/@react-native/dev-middleware": { - "version": "0.74.85", - "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.74.85.tgz", - "integrity": "sha512-BRmgCK5vnMmHaKRO+h8PKJmHHH3E6JFuerrcfE3wG2eZ1bcSr+QTu8DAlpxsDWvJvHpCi8tRJGauxd+Ssj/c7w==", + "version": "0.74.87", + "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.74.87.tgz", + "integrity": "sha512-7TmZ3hTHwooYgIHqc/z87BMe1ryrIqAUi+AF7vsD+EHCGxHFdMjSpf1BZ2SUPXuLnF2cTiTfV2RwhbPzx0tYIA==", "peer": true, "dependencies": { "@isaacs/ttlcache": "^1.4.1", - "@react-native/debugger-frontend": "0.74.85", + "@react-native/debugger-frontend": "0.74.87", "@rnx-kit/chromium-edge-launcher": "^1.0.0", "chrome-launcher": "^0.15.2", "connect": "^3.6.5", @@ -15795,31 +15792,31 @@ } }, "node_modules/@react-native/gradle-plugin": { - "version": "0.74.85", - "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.74.85.tgz", - "integrity": "sha512-1VQSLukJzaVMn1MYcs8Weo1nUW8xCas2XU1KuoV7OJPk6xPnEBFJmapmEGP5mWeEy7kcTXJmddEgy1wwW0tcig==", + "version": "0.74.87", + "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.74.87.tgz", + "integrity": "sha512-T+VX0N1qP+U9V4oAtn7FTX7pfsoVkd1ocyw9swYXgJqU2fK7hC9famW7b3s3ZiufPGPr1VPJe2TVGtSopBjL6A==", "peer": true, "engines": { "node": ">=18" } }, "node_modules/@react-native/js-polyfills": { - "version": "0.74.85", - "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.74.85.tgz", - "integrity": "sha512-gp4Rg9le3lVZeW7Cie6qLfekvRKZuhJ3LKgi1SFB4N154z1wIclypAJXVXgWBsy8JKJfTwRI+sffC4qZDlvzrg==", + "version": "0.74.87", + "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.74.87.tgz", + "integrity": "sha512-M5Evdn76CuVEF0GsaXiGi95CBZ4IWubHqwXxV9vG9CC9kq0PSkoM2Pn7Lx7dgyp4vT7ccJ8a3IwHbe+5KJRnpw==", "peer": true, "engines": { "node": ">=18" } }, "node_modules/@react-native/metro-babel-transformer": { - "version": "0.74.85", - "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.74.85.tgz", - "integrity": "sha512-JIrXqEwhTvWPtGArgMptIPGstMdXQIkwSjKVYt+7VC4a9Pw1GurIWanIJheEW6ZuCVvTc0VZkwglFz9JVjzDjA==", + "version": "0.74.87", + "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.74.87.tgz", + "integrity": "sha512-UsJCO24sNax2NSPBmV1zLEVVNkS88kcgAiYrZHtYSwSjpl4WZ656tIeedBfiySdJ94Hr3kQmBYLipV5zk0NI1A==", "peer": true, "dependencies": { "@babel/core": "^7.20.0", - "@react-native/babel-preset": "0.74.85", + "@react-native/babel-preset": "0.74.87", "hermes-parser": "0.19.1", "nullthrows": "^1.1.1" }, @@ -15831,9 +15828,9 @@ } }, "node_modules/@react-native/normalize-colors": { - "version": "0.74.85", - "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.74.85.tgz", - "integrity": "sha512-pcE4i0X7y3hsAE0SpIl7t6dUc0B0NZLd1yv7ssm4FrLhWG+CGyIq4eFDXpmPU1XHmL5PPySxTAjEMiwv6tAmOw==", + "version": "0.74.87", + "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.74.87.tgz", + "integrity": "sha512-Xh7Nyk/MPefkb0Itl5Z+3oOobeG9lfLb7ZOY2DKpFnoCE1TzBmib9vMNdFaLdSxLIP+Ec6icgKtdzYg8QUPYzA==", "peer": true }, "node_modules/@react-spring/animated": { @@ -16605,9 +16602,9 @@ } }, "node_modules/@rnx-kit/chromium-edge-launcher/node_modules/@types/node": { - "version": "18.19.42", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.42.tgz", - "integrity": "sha512-d2ZFc/3lnK2YCYhos8iaNIYu9Vfhr92nHiyJHRltXWjXUBjEE+A4I58Tdbnw4VhggSW+2j5y5gTrLs4biNnubg==", + "version": "18.19.43", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.43.tgz", + "integrity": "sha512-Mw/YlgXnyJdEwLoFv2dpuJaDFriX+Pc+0qOBJ57jC1H6cDxIj2xc5yUrdtArDVG0m+KV6622a4p2tenEqB3C/g==", "peer": true, "dependencies": { "undici-types": "~5.26.4" @@ -16908,9 +16905,9 @@ } }, "node_modules/@safe-global/safe-apps-provider/node_modules/viem": { - "version": "2.17.11", - "resolved": "https://registry.npmjs.org/viem/-/viem-2.17.11.tgz", - "integrity": "sha512-4dqMQyLVx0dWzuzVNPKzru6qrzHc4opD1WxeL/+NEtQaHcVGfE6f2uAqfoo0k0wwzWgLXbYLkODZ3s/3GDFXYA==", + "version": "2.18.8", + "resolved": "https://registry.npmjs.org/viem/-/viem-2.18.8.tgz", + "integrity": "sha512-Fi5d9fd/LBiVtJ5eV2c99yrdt4dJH5Vbkf2JajwCqHYuV4ErSk/sm+L6Ru3rzT67rfRHSOQibTZxByEBua/WLw==", "funding": [ { "type": "github", @@ -16925,6 +16922,7 @@ "@scure/bip39": "1.3.0", "abitype": "1.0.5", "isows": "1.0.4", + "webauthn-p256": "0.0.5", "ws": "8.17.1" }, "peerDependencies": { @@ -17364,6 +17362,18 @@ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==" }, + "node_modules/@sindresorhus/merge-streams": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", + "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@sinonjs/commons": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", @@ -17570,21 +17580,21 @@ } }, "node_modules/@skip-router/core/node_modules/@keplr-wallet/types": { - "version": "0.12.113", - "resolved": "https://registry.npmjs.org/@keplr-wallet/types/-/types-0.12.113.tgz", - "integrity": "sha512-xin9BotERBPq2KcubSTCe7rLtnLbXorZdODX8yzke7IrDBMidq3YKdubNU3C/kfPw77n0rhQZibMY/W9lh3oZQ==", + "version": "0.12.119", + "resolved": "https://registry.npmjs.org/@keplr-wallet/types/-/types-0.12.119.tgz", + "integrity": "sha512-J0uuKR89S14UDwMHn1eFueKkLcStmenjPg5DNxzRhe4mt8rg9uL+bNkwANrnPrbtB/tv5QQ6+tE5Hr7JyC55RQ==", "peer": true, "dependencies": { "long": "^4.0.0" } }, "node_modules/@skip-router/core/node_modules/@keplr-wallet/unit": { - "version": "0.12.113", - "resolved": "https://registry.npmjs.org/@keplr-wallet/unit/-/unit-0.12.113.tgz", - "integrity": "sha512-njmStu/YL8H0WhtlcMhJsKa3wFQhpMFRjuy1K07MrMwvYbdEHNFnmGUCPWBWU6Xylhjc1/1scMeAdo4tmhuCmg==", + "version": "0.12.119", + "resolved": "https://registry.npmjs.org/@keplr-wallet/unit/-/unit-0.12.119.tgz", + "integrity": "sha512-PAv9NdPV0NRhmw3d8mzY70Y4WMiia7Bg6Ri1mnTKHVFU+ZDxZLSJpupplWUBGt6ww+uYZMzsWgfFAysaQYG0kw==", "peer": true, "dependencies": { - "@keplr-wallet/types": "0.12.113", + "@keplr-wallet/types": "0.12.119", "big-integer": "^1.6.48", "utility-types": "^3.10.0" } @@ -17650,9 +17660,9 @@ } }, "node_modules/@skip-router/core/node_modules/axios": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", - "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.3.tgz", + "integrity": "sha512-Ar7ND9pU99eJ9GpoGQKhKf58GpUOgnzuaB7ueNQ5BMi0p+LZ5oaEnfF999fAArcTIBwXTCHAmGcHOZJaWPq9Nw==", "peer": true, "dependencies": { "follow-redirects": "^1.15.6", @@ -17692,9 +17702,9 @@ "peer": true }, "node_modules/@skip-router/core/node_modules/viem": { - "version": "2.17.11", - "resolved": "https://registry.npmjs.org/viem/-/viem-2.17.11.tgz", - "integrity": "sha512-4dqMQyLVx0dWzuzVNPKzru6qrzHc4opD1WxeL/+NEtQaHcVGfE6f2uAqfoo0k0wwzWgLXbYLkODZ3s/3GDFXYA==", + "version": "2.18.8", + "resolved": "https://registry.npmjs.org/viem/-/viem-2.18.8.tgz", + "integrity": "sha512-Fi5d9fd/LBiVtJ5eV2c99yrdt4dJH5Vbkf2JajwCqHYuV4ErSk/sm+L6Ru3rzT67rfRHSOQibTZxByEBua/WLw==", "funding": [ { "type": "github", @@ -17710,6 +17720,7 @@ "@scure/bip39": "1.3.0", "abitype": "1.0.5", "isows": "1.0.4", + "webauthn-p256": "0.0.5", "ws": "8.17.1" }, "peerDependencies": { @@ -17772,9 +17783,9 @@ } }, "node_modules/@solana-mobile/mobile-wallet-adapter-protocol-web3js/node_modules/@react-native/virtualized-lists": { - "version": "0.74.85", - "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.74.85.tgz", - "integrity": "sha512-jx2Zw0qlZteoQ+0KxRc7s4drsljLBEP534FaNZ950e9+CN9nVkLsV6rigcTjDR8wjKMSBWhKf0C0C3egYz7Ehg==", + "version": "0.74.87", + "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.74.87.tgz", + "integrity": "sha512-lsGxoFMb0lyK/MiplNKJpD+A1EoEUumkLrCjH4Ht+ZlG8S0BfCxmskLZ6qXn3BiDSkLjfjI/qyZ3pnxNBvkXpQ==", "peer": true, "dependencies": { "invariant": "^2.2.4", @@ -17922,22 +17933,22 @@ "peer": true }, "node_modules/@solana-mobile/mobile-wallet-adapter-protocol-web3js/node_modules/react-native": { - "version": "0.74.3", - "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.74.3.tgz", - "integrity": "sha512-UFutCC6WEw6HkxlcpQ2BemKqi0JkwrgDchYB5Svi8Sp4Xwt4HA6LGEjNQgZ+3KM44bjyFRpofQym0uh0jACGng==", + "version": "0.74.5", + "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.74.5.tgz", + "integrity": "sha512-Bgg2WvxaGODukJMTZFTZBNMKVaROHLwSb8VAGEdrlvKwfb1hHg/3aXTUICYk7dwgAnb+INbGMwnF8yeAgIUmqw==", "peer": true, "dependencies": { "@jest/create-cache-key-function": "^29.6.3", "@react-native-community/cli": "13.6.9", "@react-native-community/cli-platform-android": "13.6.9", "@react-native-community/cli-platform-ios": "13.6.9", - "@react-native/assets-registry": "0.74.85", - "@react-native/codegen": "0.74.85", - "@react-native/community-cli-plugin": "0.74.85", - "@react-native/gradle-plugin": "0.74.85", - "@react-native/js-polyfills": "0.74.85", - "@react-native/normalize-colors": "0.74.85", - "@react-native/virtualized-lists": "0.74.85", + "@react-native/assets-registry": "0.74.87", + "@react-native/codegen": "0.74.87", + "@react-native/community-cli-plugin": "0.74.87", + "@react-native/gradle-plugin": "0.74.87", + "@react-native/js-polyfills": "0.74.87", + "@react-native/normalize-colors": "0.74.87", + "@react-native/virtualized-lists": "0.74.87", "abort-controller": "^3.0.0", "anser": "^1.4.9", "ansi-regex": "^5.0.0", @@ -18064,9 +18075,9 @@ } }, "node_modules/@solana-mobile/wallet-adapter-mobile/node_modules/@react-native/virtualized-lists": { - "version": "0.74.85", - "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.74.85.tgz", - "integrity": "sha512-jx2Zw0qlZteoQ+0KxRc7s4drsljLBEP534FaNZ950e9+CN9nVkLsV6rigcTjDR8wjKMSBWhKf0C0C3egYz7Ehg==", + "version": "0.74.87", + "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.74.87.tgz", + "integrity": "sha512-lsGxoFMb0lyK/MiplNKJpD+A1EoEUumkLrCjH4Ht+ZlG8S0BfCxmskLZ6qXn3BiDSkLjfjI/qyZ3pnxNBvkXpQ==", "optional": true, "peer": true, "dependencies": { @@ -18195,9 +18206,9 @@ "peer": true }, "node_modules/@solana-mobile/wallet-adapter-mobile/node_modules/react-native": { - "version": "0.74.3", - "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.74.3.tgz", - "integrity": "sha512-UFutCC6WEw6HkxlcpQ2BemKqi0JkwrgDchYB5Svi8Sp4Xwt4HA6LGEjNQgZ+3KM44bjyFRpofQym0uh0jACGng==", + "version": "0.74.5", + "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.74.5.tgz", + "integrity": "sha512-Bgg2WvxaGODukJMTZFTZBNMKVaROHLwSb8VAGEdrlvKwfb1hHg/3aXTUICYk7dwgAnb+INbGMwnF8yeAgIUmqw==", "optional": true, "peer": true, "dependencies": { @@ -18205,13 +18216,13 @@ "@react-native-community/cli": "13.6.9", "@react-native-community/cli-platform-android": "13.6.9", "@react-native-community/cli-platform-ios": "13.6.9", - "@react-native/assets-registry": "0.74.85", - "@react-native/codegen": "0.74.85", - "@react-native/community-cli-plugin": "0.74.85", - "@react-native/gradle-plugin": "0.74.85", - "@react-native/js-polyfills": "0.74.85", - "@react-native/normalize-colors": "0.74.85", - "@react-native/virtualized-lists": "0.74.85", + "@react-native/assets-registry": "0.74.87", + "@react-native/codegen": "0.74.87", + "@react-native/community-cli-plugin": "0.74.87", + "@react-native/gradle-plugin": "0.74.87", + "@react-native/js-polyfills": "0.74.87", + "@react-native/normalize-colors": "0.74.87", + "@react-native/virtualized-lists": "0.74.87", "abort-controller": "^3.0.0", "anser": "^1.4.9", "ansi-regex": "^5.0.0", @@ -19025,6 +19036,23 @@ "react-dom": ">=16.8" } }, + "node_modules/@tanstack/react-virtual": { + "version": "3.8.4", + "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.8.4.tgz", + "integrity": "sha512-Dq0VQr3QlTS2qL35g360QaJWBt7tCn/0xw4uZ0dHXPLO1Ak4Z4nVX4vuj1Npg1b/jqNMDToRtR5OIxM2NXRBWg==", + "peer": true, + "dependencies": { + "@tanstack/virtual-core": "3.8.4" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/@tanstack/table-core": { "version": "8.17.3", "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.17.3.tgz", @@ -19037,6 +19065,16 @@ "url": "https://github.com/sponsors/tannerlinsley" } }, + "node_modules/@tanstack/virtual-core": { + "version": "3.8.4", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.8.4.tgz", + "integrity": "sha512-iO5Ujgw3O1yIxWDe9FgUPNkGjyT657b1WNX52u+Wv1DyBFEpdCdGkuVaky0M3hHFqNWjAmHWTn4wgj9rTr7ZQg==", + "peer": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@textea/json-viewer": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/@textea/json-viewer/-/json-viewer-3.4.1.tgz", @@ -19606,9 +19644,9 @@ } }, "node_modules/@types/react-simple-maps": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/react-simple-maps/-/react-simple-maps-3.0.5.tgz", - "integrity": "sha512-jDmZ64YYVHVOXnRPUpsz2iDTLR1IlsXs4xHfzYJqKrQpo5HU10eHFm7IbK4FfOHC5+EOI5RokBunrDjf639GNg==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/react-simple-maps/-/react-simple-maps-3.0.6.tgz", + "integrity": "sha512-hR01RXt6VvsE41FxDd+Bqm1PPGdKbYjCYVtCgh38YeBPt46z3SwmWPWu2L3EdCAP6bd6VYEgztucihRw1C0Klg==", "dev": true, "dependencies": { "@types/d3-geo": "^2", @@ -19705,9 +19743,9 @@ "integrity": "sha512-e2PNXoXLr6Z+dbfx5zSh9TRlXJrELycxiaXznp4S5+D2M3b9bqJEitNHA5923jhnB2zzFiZHa2f0SI1HoIahpg==" }, "node_modules/@types/ws": { - "version": "8.5.11", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.11.tgz", - "integrity": "sha512-4+q7P5h3SpJxaBft0Dzpbr6lmMaqh0Jr2tbhJZ/luAwvD7ohSCniYkwz/pLxuT2h0EOa6QADgJj1Ko+TzRfZ+w==", + "version": "8.5.12", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.12.tgz", + "integrity": "sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==", "dev": true, "dependencies": { "@types/node": "*" @@ -19963,9 +20001,9 @@ } }, "node_modules/@usecapsule/user-management-client/node_modules/axios": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", - "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.3.tgz", + "integrity": "sha512-Ar7ND9pU99eJ9GpoGQKhKf58GpUOgnzuaB7ueNQ5BMi0p+LZ5oaEnfF999fAArcTIBwXTCHAmGcHOZJaWPq9Nw==", "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", @@ -19973,9 +20011,9 @@ } }, "node_modules/@usecapsule/user-management-client/node_modules/qs": { - "version": "6.12.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.12.3.tgz", - "integrity": "sha512-AWJm14H1vVaO/iNZ4/hO+HyaTehuy9nRqVdkTqlJt0HWvBiBIEXFmb4C0DGeYo3Xes9rrEW+TxHsaigCbN5ICQ==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "dependencies": { "side-channel": "^1.0.6" }, @@ -20676,12 +20714,11 @@ } }, "node_modules/@walletconnect/legacy-modal/node_modules/qrcode": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.3.tgz", - "integrity": "sha512-puyri6ApkEHYiVl4CFzo1tDkAZ+ATcnbJrJ6RiBM1Fhctdn/ix9MTE3hRph33omisEbC/2fcfemsseiKgBPKZg==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz", + "integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==", "dependencies": { "dijkstrajs": "^1.0.1", - "encode-utf8": "^1.0.3", "pngjs": "^5.0.0", "yargs": "^15.3.1" }, @@ -21976,6 +22013,45 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, "node_modules/ajv-keywords": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", @@ -22529,9 +22605,9 @@ } }, "node_modules/auth0": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/auth0/-/auth0-4.7.0.tgz", - "integrity": "sha512-i8GvzdRlg8TcgRXsFEtejy+5dUOeWbnEYbU6CAkH+l8FgClGEmZpBInOJzWMkcwNae4q5AlBsCATK6nAvLtVGw==", + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/auth0/-/auth0-4.8.0.tgz", + "integrity": "sha512-FtHGzIgvnEY0ibyDo3wLa6kzcaqyl3rjRYOhdk2ZlJGLydFV7N2bvOGR7LE2cIsoqoS+vUMqSmu1dcuQqiDYBg==", "dependencies": { "jose": "^4.13.2", "undici-types": "^6.15.0", @@ -22542,9 +22618,9 @@ } }, "node_modules/auth0/node_modules/undici-types": { - "version": "6.19.4", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.4.tgz", - "integrity": "sha512-BiKTnNSkjSEYzxd0X3KQ/ZNoA8/aFlS598kds3PuZ55csLy3fFqGap0aP84Ekb/VWYG5um4MgilNs3kAx4LHMg==" + "version": "6.19.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.5.tgz", + "integrity": "sha512-VQUzGd+K73uDi/pTqzDBbxZneciOuMRjF0r/Lep2zr/GOnU+cUvfgRu4T5k4TWJfpGdSK5nrzVDoQVoEIAFbmg==" }, "node_modules/autoprefixer": { "version": "10.4.19", @@ -22769,6 +22845,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/babel-loader/node_modules/schema-utils": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", + "dependencies": { + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 8.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, "node_modules/babel-loader/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -23613,17 +23706,17 @@ } }, "node_modules/chain-registry": { - "version": "1.63.38", - "resolved": "https://registry.npmjs.org/chain-registry/-/chain-registry-1.63.38.tgz", - "integrity": "sha512-FvCxOqNL4Ec/WZGx2DepaPaoqoA7GIAN6JAXhL7Z+dFUt6XFArPlivVrN/G/fCRztG79DsIBX0OQw49+THkINg==", + "version": "1.63.51", + "resolved": "https://registry.npmjs.org/chain-registry/-/chain-registry-1.63.51.tgz", + "integrity": "sha512-hw0VwRw4rE2GqdNZVxSy5C3bsj6ijkjaCmfOd7QH7lsFJKgP33jL4LfDiSpSuTHGQok79E8UJVR8Bqbx1dd9Pg==", "dependencies": { - "@chain-registry/types": "^0.45.31" + "@chain-registry/types": "^0.45.41" } }, "node_modules/chain-registry/node_modules/@chain-registry/types": { - "version": "0.45.31", - "resolved": "https://registry.npmjs.org/@chain-registry/types/-/types-0.45.31.tgz", - "integrity": "sha512-91b5sgi5uw1zuIxjhEDc6SO+Oa1eaYA/Mf4jvaxFx19iO80d0dpG0eDvAU1vDorWmibhoJAk1B/0Qnm6cZQ9Jg==" + "version": "0.45.41", + "resolved": "https://registry.npmjs.org/@chain-registry/types/-/types-0.45.41.tgz", + "integrity": "sha512-OCCuqFE5xmjxwe0ardEe+ozKS0NeDOIgqpfjA7TeTQAWkRexty1j5PXZW/1/73C0wJ1/b/GXILsXwSi44w5MzA==" }, "node_modules/chalk": { "version": "2.4.2", @@ -24488,9 +24581,9 @@ } }, "node_modules/cookie-es": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-1.2.1.tgz", - "integrity": "sha512-ilTPDuxhZX44BSzzRB58gvSY2UevZKQM9fjisn7Z+NJ92CtSU6kO1+22ZN/agbEJANFjK85EiJJbi/gQv18OXA==" + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-1.2.2.tgz", + "integrity": "sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==" }, "node_modules/cookie-signature": { "version": "1.0.6", @@ -24520,6 +24613,74 @@ "toggle-selection": "^1.0.6" } }, + "node_modules/copy-webpack-plugin": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-12.0.2.tgz", + "integrity": "sha512-SNwdBeHyII+rWvee/bTnAYyO8vfVdcSTud4EIb6jcZ8inLeWucJE0DnxXQBjlQ5zlteuuvooGQy3LIyGxhvlOA==", + "dev": true, + "dependencies": { + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.1", + "globby": "^14.0.0", + "normalize-path": "^3.0.0", + "schema-utils": "^4.2.0", + "serialize-javascript": "^6.0.2" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + } + }, + "node_modules/copy-webpack-plugin/node_modules/globby": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.2.tgz", + "integrity": "sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw==", + "dev": true, + "dependencies": { + "@sindresorhus/merge-streams": "^2.1.0", + "fast-glob": "^3.3.2", + "ignore": "^5.2.4", + "path-type": "^5.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/copy-webpack-plugin/node_modules/path-type": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz", + "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/copy-webpack-plugin/node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/core-js-compat": { "version": "3.37.1", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.37.1.tgz", @@ -27286,9 +27447,9 @@ } }, "node_modules/ethereum-bloom-filters": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ethereum-bloom-filters/-/ethereum-bloom-filters-1.1.0.tgz", - "integrity": "sha512-J1gDRkLpuGNvWYzWslBQR9cDV4nd4kfvVTE/Wy4Kkm4yb3EYRSlyi0eB/inTsSTTVyA0+HyzHgbr95Fn/Z1fSw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/ethereum-bloom-filters/-/ethereum-bloom-filters-1.2.0.tgz", + "integrity": "sha512-28hyiE7HVsWubqhpVLVmZXFd4ITeHi+BUu05o9isf0GUpMtzBUi+8/gFrGaGYzvGAJQmJ3JKj77Mk9G98T84rA==", "dependencies": { "@noble/hashes": "^1.4.0" } @@ -27371,9 +27532,9 @@ } }, "node_modules/ethers": { - "version": "6.13.1", - "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.13.1.tgz", - "integrity": "sha512-hdJ2HOxg/xx97Lm9HdCWk949BfYqYWpyw4//78SiwOLgASyfrNszfMUNB2joKjvGUdwhHfaiMMFFwacVVoLR9A==", + "version": "6.13.2", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.13.2.tgz", + "integrity": "sha512-9VkriTTed+/27BGuY1s0hf441kqwHJ1wtN2edksEtiRvXx+soxRX3iSXTfFqq2+YwrOqbDoTHjIhQnjJRlzKmg==", "funding": [ { "type": "individual", @@ -27877,9 +28038,9 @@ "integrity": "sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==" }, "node_modules/fast-xml-parser": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.0.tgz", - "integrity": "sha512-kLY3jFlwIYwBNDojclKsNAC12sfD6NwW74QB2CoNGPvtVxjliYehVunB3HYyNi+n4Tt1dAcgwYvmKF/Z18flqg==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", + "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", "funding": [ { "type": "github", @@ -28322,9 +28483,9 @@ } }, "node_modules/fp-ts": { - "version": "2.16.8", - "resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-2.16.8.tgz", - "integrity": "sha512-nmDtNqmMZkOxu0M5hkrS9YA15/KPkYkILb6Axg9XBAoUoYEtzg+LFmVWqZrl9FNttsW0qIUpx9RCA9INbv+Bxw==", + "version": "2.16.9", + "resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-2.16.9.tgz", + "integrity": "sha512-+I2+FnVB+tVaxcYyQkHUq7ZdKScaBlX53A41mxQtpIccsfyv8PzdzP7fzp2AY832T4aoK6UZ5WRX/ebGd8uZuQ==", "peer": true }, "node_modules/fraction.js": { @@ -28597,9 +28758,9 @@ "peer": true }, "node_modules/github-buttons": { - "version": "2.28.0", - "resolved": "https://registry.npmjs.org/github-buttons/-/github-buttons-2.28.0.tgz", - "integrity": "sha512-KsCbYiA+MiHO3ytzdGvGt/GNde4GfG9BrrLxxc+ut2snBF9IAjrn2F5mNgHHEXdG/CfFIHOMV8Uxy4LNhxZwUA==" + "version": "2.28.1", + "resolved": "https://registry.npmjs.org/github-buttons/-/github-buttons-2.28.1.tgz", + "integrity": "sha512-ePs69Gn/EC02Vrt+UeVvULxfBb6KGbu43mbDOTOB6R7W94FXPGsiUChhbbkCnKauAPLfXr4F1o0wCA4NE4g8eA==" }, "node_modules/github-from-package": { "version": "0.0.0", @@ -28798,9 +28959,9 @@ } }, "node_modules/graz/node_modules/@keplr-wallet/types": { - "version": "0.12.113", - "resolved": "https://registry.npmjs.org/@keplr-wallet/types/-/types-0.12.113.tgz", - "integrity": "sha512-xin9BotERBPq2KcubSTCe7rLtnLbXorZdODX8yzke7IrDBMidq3YKdubNU3C/kfPw77n0rhQZibMY/W9lh3oZQ==", + "version": "0.12.119", + "resolved": "https://registry.npmjs.org/@keplr-wallet/types/-/types-0.12.119.tgz", + "integrity": "sha512-J0uuKR89S14UDwMHn1eFueKkLcStmenjPg5DNxzRhe4mt8rg9uL+bNkwANrnPrbtB/tv5QQ6+tE5Hr7JyC55RQ==", "dependencies": { "long": "^4.0.0" } @@ -36137,12 +36298,11 @@ } }, "node_modules/next-qrcode/node_modules/qrcode": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.3.tgz", - "integrity": "sha512-puyri6ApkEHYiVl4CFzo1tDkAZ+ATcnbJrJ6RiBM1Fhctdn/ix9MTE3hRph33omisEbC/2fcfemsseiKgBPKZg==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz", + "integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==", "dependencies": { "dijkstrajs": "^1.0.1", - "encode-utf8": "^1.0.3", "pngjs": "^5.0.0", "yargs": "^15.3.1" }, @@ -38107,12 +38267,12 @@ } }, "node_modules/playwright": { - "version": "1.45.3", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.45.3.tgz", - "integrity": "sha512-QhVaS+lpluxCaioejDZ95l4Y4jSFCsBvl2UZkpeXlzxmqS+aABr5c82YmfMHrL6x27nvrvykJAFpkzT2eWdJww==", + "version": "1.46.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.46.0.tgz", + "integrity": "sha512-XYJ5WvfefWONh1uPAUAi0H2xXV5S3vrtcnXe6uAOgdGi3aSpqOSXX08IAjXW34xitfuOJsvXU5anXZxPSEQiJw==", "devOptional": true, "dependencies": { - "playwright-core": "1.45.3" + "playwright-core": "1.46.0" }, "bin": { "playwright": "cli.js" @@ -38125,9 +38285,9 @@ } }, "node_modules/playwright-core": { - "version": "1.45.3", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.45.3.tgz", - "integrity": "sha512-+ym0jNbcjikaOwwSZycFbwkWgfruWvYlJfThKYAlImbxUgdWFO2oW70ojPm4OpE4t6TAo2FY/smM+hpVTtkhDA==", + "version": "1.46.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.46.0.tgz", + "integrity": "sha512-9Y/d5UIwuJk8t3+lhmMSAJyNP1BUC/DqP3cQJDQQL/oWqAiuPTLgy7Q5dzglmTLwcBRdetzgNM/gni7ckfTr6A==", "devOptional": true, "bin": { "playwright-core": "cli.js" @@ -38434,9 +38594,9 @@ "devOptional": true }, "node_modules/preact": { - "version": "10.23.0", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.23.0.tgz", - "integrity": "sha512-Pox0jeY4q6PGkFB5AsXni+zHxxx/sAYFIFZzukW4nIpoJLRziRX0xC4WjZENlkSrDQvqVgZcaZzZ/NL8/A+H/w==", + "version": "10.23.1", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.23.1.tgz", + "integrity": "sha512-O5UdRsNh4vdZaTieWe3XOgSpdMAmkIYBCT3VhQDlKrzyCm8lUYsk0fmVEvoQQifoOjFRTaHZO69ylrzTW2BH+A==", "funding": { "type": "opencollective", "url": "https://opencollective.com/preact" @@ -40399,22 +40559,58 @@ } }, "node_modules/schema-utils": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", - "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", + "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", + "dev": true, "dependencies": { - "@types/json-schema": "^7.0.5", - "ajv": "^6.12.4", - "ajv-keywords": "^3.5.2" + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" }, "engines": { - "node": ">= 8.9.0" + "node": ">= 12.13.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" } }, + "node_modules/schema-utils/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/schema-utils/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/schema-utils/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, "node_modules/scrypt-js": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", @@ -43253,9 +43449,9 @@ "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==" }, "node_modules/tss-react": { - "version": "4.9.10", - "resolved": "https://registry.npmjs.org/tss-react/-/tss-react-4.9.10.tgz", - "integrity": "sha512-uQj+r8mOKy0tv+/GAIzViVG81w/WeTCOF7tjsDyNjlicnWbxtssYwTvVjWT4lhWh5FSznDRy6RFp0BDdoLbxyg==", + "version": "4.9.12", + "resolved": "https://registry.npmjs.org/tss-react/-/tss-react-4.9.12.tgz", + "integrity": "sha512-bsREJF9opq8OEZKibw7Awao2g16ZSlsBavmIxzKVF7EMcJ1d0Uc/Z2lJEKlZwYoct6g1C0dxd5XI+RwPhTrlUA==", "dependencies": { "@emotion/cache": "*", "@emotion/serialize": "*", @@ -43705,6 +43901,18 @@ "node": ">=4" } }, + "node_modules/unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/unified": { "version": "10.1.2", "resolved": "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz", @@ -44664,6 +44872,21 @@ "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.12.1.tgz", "integrity": "sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw==" }, + "node_modules/webauthn-p256": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/webauthn-p256/-/webauthn-p256-0.0.5.tgz", + "integrity": "sha512-drMGNWKdaixZNobeORVIqq7k5DsRC9FnG201K2QjeOoQLmtSDaSsVZdkg6n5jUALJKcAG++zBPJXmv6hy0nWFg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "dependencies": { + "@noble/curves": "^1.4.0", + "@noble/hashes": "^1.4.0" + } + }, "node_modules/webextension-polyfill": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/webextension-polyfill/-/webextension-polyfill-0.10.0.tgz", From 4817a936cf552012e220fc11c564cda6e56a1dbf Mon Sep 17 00:00:00 2001 From: Yaroslav Grishajev Date: Tue, 6 Aug 2024 13:20:28 +0200 Subject: [PATCH 3/3] refactor(auth): type ability templates refs #247 --- .../auth/services/ability/ability.service.ts | 48 ++++++++++--------- .../api/src/auth/services/auth.interceptor.ts | 8 ++-- .../services/tx-signer/tx-signer.service.ts | 2 +- .../lib/drizzle-ability/drizzle-ability.ts | 2 +- .../functional/sign-and-broadcast-tx.spec.ts | 6 +-- 5 files changed, 33 insertions(+), 33 deletions(-) diff --git a/apps/api/src/auth/services/ability/ability.service.ts b/apps/api/src/auth/services/ability/ability.service.ts index 41e38bb3e..321a27357 100644 --- a/apps/api/src/auth/services/ability/ability.service.ts +++ b/apps/api/src/auth/services/ability/ability.service.ts @@ -1,37 +1,39 @@ -import { Ability } from "@casl/ability"; +import { Ability, RawRule } from "@casl/ability"; +import type { TemplateExecutor } from "lodash"; import template from "lodash/template"; import { singleton } from "tsyringe"; +type Role = "REGULAR_USER" | "REGULAR_ANONYMOUS_USER" | "SUPER_USER"; + @singleton() export class AbilityService { - private readonly createRegularUserRules = template( - JSON.stringify([ + readonly EMPTY_ABILITY = new Ability([]); + + private readonly RULES: Record = { + REGULAR_USER: [ { action: ["create", "read", "sign"], subject: "UserWallet", conditions: { userId: "${user.id}" } }, { action: "read", subject: "User", conditions: { id: "${user.id}" } } - ]) - ); - private readonly createRegularAnonymousUserRules = template( - JSON.stringify([ + ], + REGULAR_ANONYMOUS_USER: [ { action: ["create", "read", "sign"], subject: "UserWallet", conditions: { userId: "${user.id}" } }, - { action: "read", subject: "User", conditions: { id: "${user.id}", userId: null } } - ]) - ); - - getAbilityForUser(user: { userId: string }) { - const rules = this.createRegularUserRules({ user }); - return new Ability(JSON.parse(rules)); - } + { action: "read", subject: "User", conditions: { id: "${user.id}" } } + ], + SUPER_USER: [{ action: "manage", subject: "all" }] + }; - getAbilityForAnonymousUser(user: { id: string }) { - const rules = this.createRegularAnonymousUserRules({ user }); - return new Ability(JSON.parse(rules)); - } + private readonly templates = (Object.keys(this.RULES) as Role[]).reduce( + (acc, role) => { + acc[role] = template(JSON.stringify(this.RULES[role])); + return acc; + }, + {} as Record + ); - getEmptyAbility() { - return new Ability([]); + getAbilityFor(role: Role, user: { userId: string }) { + return this.toAbility(this.templates[role]({ user })); } - getSuperUserAbility() { - return new Ability([{ action: "manage", subject: "all" }]); + private toAbility(raw: string) { + return new Ability(JSON.parse(raw)); } } diff --git a/apps/api/src/auth/services/auth.interceptor.ts b/apps/api/src/auth/services/auth.interceptor.ts index 4152c5fa5..750acc981 100644 --- a/apps/api/src/auth/services/auth.interceptor.ts +++ b/apps/api/src/auth/services/auth.interceptor.ts @@ -3,7 +3,6 @@ import { singleton } from "tsyringe"; import { AbilityService } from "@src/auth/services/ability/ability.service"; import { AuthService } from "@src/auth/services/auth.service"; -import { ExecutionContextService } from "@src/core/services/execution-context/execution-context.service"; import type { HonoInterceptor } from "@src/core/types/hono-interceptor.type"; import { getCurrentUserId } from "@src/middlewares/userMiddleware"; import { UserRepository } from "@src/user/repositories"; @@ -13,7 +12,6 @@ export class AuthInterceptor implements HonoInterceptor { constructor( private readonly abilityService: AbilityService, private readonly userRepository: UserRepository, - private readonly executionContextService: ExecutionContextService, private readonly authService: AuthService ) {} @@ -25,7 +23,7 @@ export class AuthInterceptor implements HonoInterceptor { const currentUser = await this.userRepository.findByUserId(userId); this.authService.currentUser = currentUser; - this.authService.ability = currentUser ? this.abilityService.getAbilityForUser(currentUser) : this.abilityService.getEmptyAbility(); + this.authService.ability = currentUser ? this.abilityService.getAbilityFor("REGULAR_USER", currentUser) : this.abilityService.EMPTY_ABILITY; return await next(); } @@ -35,12 +33,12 @@ export class AuthInterceptor implements HonoInterceptor { const currentUser = await this.userRepository.findAnonymousById(anonymousUserId); this.authService.currentUser = currentUser; - this.authService.ability = currentUser ? this.abilityService.getAbilityForAnonymousUser(currentUser) : this.abilityService.getEmptyAbility(); + this.authService.ability = currentUser ? this.abilityService.getAbilityFor("REGULAR_ANONYMOUS_USER", currentUser) : this.abilityService.EMPTY_ABILITY; return await next(); } - this.authService.ability = this.abilityService.getEmptyAbility(); + this.authService.ability = this.abilityService.EMPTY_ABILITY; return await next(); }; diff --git a/apps/api/src/billing/services/tx-signer/tx-signer.service.ts b/apps/api/src/billing/services/tx-signer/tx-signer.service.ts index 6deeecd91..53b2a160d 100644 --- a/apps/api/src/billing/services/tx-signer/tx-signer.service.ts +++ b/apps/api/src/billing/services/tx-signer/tx-signer.service.ts @@ -35,7 +35,7 @@ export class TxSignerService { async signAndBroadcast(userId: UserWalletOutput["userId"], messages: StringifiedEncodeObject[]) { const userWallet = await this.userWalletRepository.accessibleBy(this.authService.ability, "sign").findByUserId(userId); - assert(userWallet, 403); + assert(userWallet, 404, "UserWallet Not Found"); const decodedMessages = this.decodeMessages(messages); diff --git a/apps/api/src/lib/drizzle-ability/drizzle-ability.ts b/apps/api/src/lib/drizzle-ability/drizzle-ability.ts index 28a511493..1e6c0eb2a 100644 --- a/apps/api/src/lib/drizzle-ability/drizzle-ability.ts +++ b/apps/api/src/lib/drizzle-ability/drizzle-ability.ts @@ -49,7 +49,7 @@ export class DrizzleAbility, A extends AnyAbil const { $and = [], $or = [] } = rulesToQuery(this.ability, params[0], params[1], rule => { if (!rule.ast) { - throw new Error("Unable to create knex query without AST"); + throw new Error("Unable to create query without AST"); } if (rule.inverted) { diff --git a/apps/api/test/functional/sign-and-broadcast-tx.spec.ts b/apps/api/test/functional/sign-and-broadcast-tx.spec.ts index 8c771c395..f07a51f6a 100644 --- a/apps/api/test/functional/sign-and-broadcast-tx.spec.ts +++ b/apps/api/test/functional/sign-and-broadcast-tx.spec.ts @@ -47,7 +47,7 @@ describe("Tx Sign", () => { expect(await res.json()).toMatchObject({ error: "UnauthorizedError", message: "Unauthorized" }); }); - it("should throw 403 provided a different user auth header", async () => { + it("should throw 404 provided a different user auth header", async () => { const { user, wallet } = await walletService.createUserAndWallet(); const differentUserResponse = await app.request("/v1/anonymous-users", { method: "POST", @@ -60,8 +60,8 @@ describe("Tx Sign", () => { headers: new Headers({ "Content-Type": "application/json", "x-anonymous-user-id": differentUser.id }) }); - expect(res.status).toBe(403); - expect(await res.json()).toMatchObject({ error: "ForbiddenError", message: "Forbidden" }); + expect(res.status).toBe(404); + expect(await res.json()).toMatchObject({ error: "NotFoundError", message: "UserWallet Not Found" }); }); });