From 5ea00e427d426caeb6e1a85760dbffb362a0afd4 Mon Sep 17 00:00:00 2001 From: Yaroslav Grishajev Date: Tue, 29 Oct 2024 19:34:27 +0100 Subject: [PATCH] feat(wallet): implement multiple master wallets and clients So that those could be used separately for managed wallets, uakt and usdc top up refs #395 --- apps/api/env/.env.functional.test | 2 ++ apps/api/src/billing/config/env.config.ts | 2 ++ apps/api/src/billing/providers/index.ts | 1 + .../providers/signing-client.provider.ts | 24 +++++++++++++++++++ .../src/billing/providers/wallet.provider.ts | 16 +++++++++++++ .../services/balances/balances.service.ts | 3 ++- .../managed-user-wallet.service.ts | 10 +++++--- .../master-signing-client.service.ts | 18 +++++++------- .../master-wallet/master-wallet.service.ts | 8 ++----- .../services/tx-signer/tx-signer.service.ts | 3 ++- apps/api/src/billing/types/wallet.type.ts | 1 + apps/api/test/functional/app.spec.ts | 2 +- .../test/functional/create-deployment.spec.ts | 3 ++- 13 files changed, 71 insertions(+), 22 deletions(-) create mode 100644 apps/api/src/billing/providers/signing-client.provider.ts create mode 100644 apps/api/src/billing/providers/wallet.provider.ts create mode 100644 apps/api/src/billing/types/wallet.type.ts diff --git a/apps/api/env/.env.functional.test b/apps/api/env/.env.functional.test index 554f5bcff..e742ff368 100644 --- a/apps/api/env/.env.functional.test +++ b/apps/api/env/.env.functional.test @@ -2,6 +2,8 @@ AKASH_SANDBOX_DATABASE_CS=postgres://postgres:password@localhost:5432/console-ak USER_DATABASE_CS=postgres://postgres:password@localhost:5432/console-users POSTGRES_DB_URI=postgres://postgres:password@localhost:5432/console-users MASTER_WALLET_MNEMONIC="motion isolate mother convince snack twenty tumble boost elbow bundle modify balcony" +UAKT_TOP_UP_MASTER_WALLET_MNEMONIC="motion isolate mother convince snack twenty tumble boost elbow bundle modify balcony" +USDC_TOP_UP_MASTER_WALLET_MNEMONIC="motion isolate mother convince snack twenty tumble boost elbow bundle modify balcony" NETWORK=sandbox RPC_NODE_ENDPOINT=https://rpc.sandbox-01.aksh.pw:443 TRIAL_DEPLOYMENT_ALLOWANCE_AMOUNT=20000000 diff --git a/apps/api/src/billing/config/env.config.ts b/apps/api/src/billing/config/env.config.ts index 986a870c4..cf0d1366d 100644 --- a/apps/api/src/billing/config/env.config.ts +++ b/apps/api/src/billing/config/env.config.ts @@ -2,6 +2,8 @@ import { z } from "zod"; const envSchema = z.object({ MASTER_WALLET_MNEMONIC: z.string(), + UAKT_TOP_UP_MASTER_WALLET_MNEMONIC: z.string(), + USDC_TOP_UP_MASTER_WALLET_MNEMONIC: z.string(), NETWORK: z.enum(["mainnet", "testnet", "sandbox"]), RPC_NODE_ENDPOINT: z.string(), TRIAL_ALLOWANCE_EXPIRATION_DAYS: z.number({ coerce: true }).default(14), diff --git a/apps/api/src/billing/providers/index.ts b/apps/api/src/billing/providers/index.ts index c3709ef99..648403be1 100644 --- a/apps/api/src/billing/providers/index.ts +++ b/apps/api/src/billing/providers/index.ts @@ -1,4 +1,5 @@ import "./config.provider"; import "./http-sdk.provider"; +import "./wallet.provider"; export * from "./config.provider"; diff --git a/apps/api/src/billing/providers/signing-client.provider.ts b/apps/api/src/billing/providers/signing-client.provider.ts new file mode 100644 index 000000000..4e7fe8f44 --- /dev/null +++ b/apps/api/src/billing/providers/signing-client.provider.ts @@ -0,0 +1,24 @@ +import { container, inject } from "tsyringe"; + +import { config } from "@src/billing/config"; +import { TYPE_REGISTRY } from "@src/billing/providers/type-registry.provider"; +import { MANAGED_MASTER_WALLET, UAKT_TOP_UP_MASTER_WALLET, USDC_TOP_UP_MASTER_WALLET } from "@src/billing/providers/wallet.provider"; +import { MasterSigningClientService } from "@src/billing/services"; +import { MasterWalletType } from "@src/billing/types/wallet.type"; + +export const MANAGED_MASTER_SIGNING_CLIENT = "MANAGED_MASTER_SIGNING_CLIENT"; +container.register(MANAGED_MASTER_SIGNING_CLIENT, { + useFactory: c => new MasterSigningClientService(config, c.resolve(MANAGED_MASTER_WALLET), c.resolve(TYPE_REGISTRY), MANAGED_MASTER_SIGNING_CLIENT) +}); + +export const UAKT_TOP_UP_MASTER_SIGNING_CLIENT = "UAKT_TOP_UP_MASTER_SIGNING_CLIENT"; +container.register(UAKT_TOP_UP_MASTER_SIGNING_CLIENT, { + useFactory: c => new MasterSigningClientService(config, c.resolve(UAKT_TOP_UP_MASTER_WALLET), c.resolve(TYPE_REGISTRY), UAKT_TOP_UP_MASTER_SIGNING_CLIENT) +}); + +export const USDC_TOP_UP_MASTER_SIGNING_CLIENT = "USDC_TOP_UP_MASTER_SIGNING_CLIENT"; +container.register(USDC_TOP_UP_MASTER_SIGNING_CLIENT, { + useFactory: c => new MasterSigningClientService(config, c.resolve(USDC_TOP_UP_MASTER_WALLET), c.resolve(TYPE_REGISTRY), USDC_TOP_UP_MASTER_SIGNING_CLIENT) +}); + +export const InjectSigningClient = (walletType: MasterWalletType) => inject(`${walletType}_MASTER_SIGNING_CLIENT`); diff --git a/apps/api/src/billing/providers/wallet.provider.ts b/apps/api/src/billing/providers/wallet.provider.ts new file mode 100644 index 000000000..9122436b3 --- /dev/null +++ b/apps/api/src/billing/providers/wallet.provider.ts @@ -0,0 +1,16 @@ +import { container, inject } from "tsyringe"; + +import { config } from "@src/billing/config"; +import { MasterWalletService } from "@src/billing/services/master-wallet/master-wallet.service"; +import { MasterWalletType } from "@src/billing/types/wallet.type"; + +export const MANAGED_MASTER_WALLET = "MANAGED_MASTER_WALLET"; +container.register(MANAGED_MASTER_WALLET, { useFactory: () => new MasterWalletService(config.MASTER_WALLET_MNEMONIC) }); + +export const UAKT_TOP_UP_MASTER_WALLET = "TOP_UP_UAKT_MASTER_WALLET"; +container.register(UAKT_TOP_UP_MASTER_WALLET, { useFactory: () => new MasterWalletService(config.UAKT_TOP_UP_MASTER_WALLET_MNEMONIC) }); + +export const USDC_TOP_UP_MASTER_WALLET = "TOP_UP_USDC_MASTER_WALLET"; +container.register(USDC_TOP_UP_MASTER_WALLET, { useFactory: () => new MasterWalletService(config.USDC_TOP_UP_MASTER_WALLET_MNEMONIC) }); + +export const InjectWallet = (walletType: MasterWalletType) => inject(`${walletType}_MASTER_WALLET`); diff --git a/apps/api/src/billing/services/balances/balances.service.ts b/apps/api/src/billing/services/balances/balances.service.ts index b8ae542e5..78013de13 100644 --- a/apps/api/src/billing/services/balances/balances.service.ts +++ b/apps/api/src/billing/services/balances/balances.service.ts @@ -2,6 +2,7 @@ import { AllowanceHttpService } from "@akashnetwork/http-sdk"; import { singleton } from "tsyringe"; import { BillingConfig, InjectBillingConfig } from "@src/billing/providers"; +import { InjectWallet } from "@src/billing/providers/wallet.provider"; import { UserWalletInput, UserWalletOutput, UserWalletRepository } from "@src/billing/repositories"; import { MasterWalletService } from "@src/billing/services"; @@ -10,7 +11,7 @@ export class BalancesService { constructor( @InjectBillingConfig() private readonly config: BillingConfig, private readonly userWalletRepository: UserWalletRepository, - private readonly masterWalletService: MasterWalletService, + @InjectWallet("MANAGED") private readonly masterWalletService: MasterWalletService, private readonly allowanceHttpService: AllowanceHttpService ) {} diff --git a/apps/api/src/billing/services/managed-user-wallet/managed-user-wallet.service.ts b/apps/api/src/billing/services/managed-user-wallet/managed-user-wallet.service.ts index 83eefb943..623a5c1e9 100644 --- a/apps/api/src/billing/services/managed-user-wallet/managed-user-wallet.service.ts +++ b/apps/api/src/billing/services/managed-user-wallet/managed-user-wallet.service.ts @@ -6,6 +6,8 @@ import add from "date-fns/add"; import { singleton } from "tsyringe"; import { BillingConfig, InjectBillingConfig } from "@src/billing/providers"; +import { InjectSigningClient } from "@src/billing/providers/signing-client.provider"; +import { InjectWallet } from "@src/billing/providers/wallet.provider"; import { MasterSigningClientService } from "@src/billing/services/master-signing-client/master-signing-client.service"; import { MasterWalletService } from "@src/billing/services/master-wallet/master-wallet.service"; import { RpcMessageService, SpendingAuthorizationMsgOptions } from "@src/billing/services/rpc-message-service/rpc-message.service"; @@ -34,11 +36,13 @@ export class ManagedUserWalletService { constructor( @InjectBillingConfig() private readonly config: BillingConfig, - private readonly masterWalletService: MasterWalletService, - private readonly masterSigningClientService: MasterSigningClientService, + @InjectWallet("MANAGED") private readonly masterWalletService: MasterWalletService, + @InjectSigningClient("MANAGED") private readonly masterSigningClientService: MasterSigningClientService, private readonly rpcMessageService: RpcMessageService, private readonly allowanceHttpService: AllowanceHttpService - ) {} + ) { + console.log("DEBUG masterSigningClientService", masterSigningClientService); + } async createAndAuthorizeTrialSpending({ addressIndex }: { addressIndex: number }) { const { address } = await this.createWallet({ addressIndex }); 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 733fd9c02..135acad54 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 @@ -9,10 +9,8 @@ import { Sema } from "async-sema"; import { TxRaw } from "cosmjs-types/cosmos/tx/v1beta1/tx"; import DataLoader from "dataloader"; import assert from "http-assert"; -import { singleton } from "tsyringe"; -import { BillingConfig, InjectBillingConfig } from "@src/billing/providers"; -import { InjectTypeRegistry } from "@src/billing/providers/type-registry.provider"; +import { BillingConfig } from "@src/billing/providers"; import { BatchSigningStargateClient } from "@src/billing/services/batch-signing-stargate-client/batch-signing-stargate-client"; import { MasterWalletService } from "@src/billing/services/master-wallet/master-wallet.service"; import { LoggerService } from "@src/core"; @@ -22,8 +20,9 @@ interface ShortAccountInfo { sequence: number; } -@singleton() export class MasterSigningClientService { + private readonly FEES_DENOM = "uakt"; + private clientAsPromised: Promise; private readonly semaphore = new Sema(1); @@ -39,12 +38,13 @@ export class MasterSigningClientService { { cache: false, batchScheduleFn: callback => setTimeout(callback, this.config.MASTER_WALLET_BATCHING_INTERVAL_MS) } ); - private readonly logger = new LoggerService({ context: MasterWalletService.name }); + private readonly logger = new LoggerService({ context: this.loggerContext }); constructor( - @InjectBillingConfig() private readonly config: BillingConfig, + private readonly config: BillingConfig, private readonly masterWalletService: MasterWalletService, - @InjectTypeRegistry() private readonly registry: Registry + private readonly registry: Registry, + private readonly loggerContext = MasterSigningClientService.name ) { this.clientAsPromised = this.initClient(); } @@ -110,7 +110,7 @@ export class MasterSigningClientService { while (txIndex < messages.length) { txes.push( - await client.sign(masterAddress, messages[txIndex], await this.estimateFee(messages[txIndex], this.config.DEPLOYMENT_GRANT_DENOM, { mock: true }), "", { + await client.sign(masterAddress, messages[txIndex], await this.estimateFee(messages[txIndex], this.FEES_DENOM, { mock: true }), "", { accountNumber: this.accountInfo.accountNumber, sequence: this.accountInfo.sequence++, chainId: this.chainId @@ -137,7 +137,7 @@ export class MasterSigningClientService { private async estimateFee(messages: readonly EncodeObject[], denom: string, options?: { mock?: boolean }) { if (options?.mock) { return { - amount: [{ denom: "uakt", amount: "15000" }], + amount: [{ denom: this.FEES_DENOM, amount: "15000" }], gas: "500000" }; } diff --git a/apps/api/src/billing/services/master-wallet/master-wallet.service.ts b/apps/api/src/billing/services/master-wallet/master-wallet.service.ts index d02f53d19..8163b226a 100644 --- a/apps/api/src/billing/services/master-wallet/master-wallet.service.ts +++ b/apps/api/src/billing/services/master-wallet/master-wallet.service.ts @@ -1,17 +1,13 @@ import { DirectSecp256k1HdWallet, OfflineDirectSigner } from "@cosmjs/proto-signing"; import { SignDoc } from "cosmjs-types/cosmos/tx/v1beta1/tx"; -import { singleton } from "tsyringe"; -import { BillingConfig, InjectBillingConfig } from "@src/billing/providers"; - -@singleton() export class MasterWalletService implements OfflineDirectSigner { private readonly PREFIX = "akash"; private readonly instanceAsPromised: Promise; - constructor(@InjectBillingConfig() private readonly config: BillingConfig) { - this.instanceAsPromised = DirectSecp256k1HdWallet.fromMnemonic(this.config.MASTER_WALLET_MNEMONIC, { prefix: this.PREFIX }); + constructor(mnemonic: string) { + this.instanceAsPromised = DirectSecp256k1HdWallet.fromMnemonic(mnemonic, { prefix: this.PREFIX }); } async getAccounts() { 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 a0bbc24b5..5bb118c50 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 @@ -9,6 +9,7 @@ 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 { InjectWallet } from "@src/billing/providers/wallet.provider"; import { UserWalletOutput, UserWalletRepository } from "@src/billing/repositories"; import { MasterWalletService } from "@src/billing/services"; import { BalancesService } from "@src/billing/services/balances/balances.service"; @@ -30,7 +31,7 @@ export class TxSignerService { @InjectBillingConfig() private readonly config: BillingConfig, @InjectTypeRegistry() private readonly registry: Registry, private readonly userWalletRepository: UserWalletRepository, - private readonly masterWalletService: MasterWalletService, + @InjectWallet("MANAGED") private readonly masterWalletService: MasterWalletService, private readonly balancesService: BalancesService, private readonly authService: AuthService, private readonly chainErrorService: ChainErrorService, diff --git a/apps/api/src/billing/types/wallet.type.ts b/apps/api/src/billing/types/wallet.type.ts new file mode 100644 index 000000000..756845782 --- /dev/null +++ b/apps/api/src/billing/types/wallet.type.ts @@ -0,0 +1 @@ +export type MasterWalletType = "MANAGED" | "USDC_TOP_UP" | "UAKT_TOP_UP"; diff --git a/apps/api/test/functional/app.spec.ts b/apps/api/test/functional/app.spec.ts index a7aa14746..d2ef9d198 100644 --- a/apps/api/test/functional/app.spec.ts +++ b/apps/api/test/functional/app.spec.ts @@ -17,7 +17,7 @@ describe("app", () => { rss: expect.stringMatching(/^[0-9.]+ (M|G)B$/) }, tasks: [], - version: expect.stringMatching(/^[0-9]+.[0-9]+.[0-9]+$/) + version: expect.stringMatching(/^[0-9]+.[0-9]+.[0-9]+(-beta\.[0-9]+)?$/) }); }); }); diff --git a/apps/api/test/functional/create-deployment.spec.ts b/apps/api/test/functional/create-deployment.spec.ts index 45ce27658..6f78d8e9c 100644 --- a/apps/api/test/functional/create-deployment.spec.ts +++ b/apps/api/test/functional/create-deployment.spec.ts @@ -11,6 +11,7 @@ import { container } from "tsyringe"; import { app } from "@src/app"; import { config } from "@src/billing/config"; import { TYPE_REGISTRY } from "@src/billing/providers/type-registry.provider"; +import { MANAGED_MASTER_WALLET } from "@src/billing/providers/wallet.provider"; import { MasterWalletService } from "@src/billing/services"; jest.setTimeout(30000); @@ -21,7 +22,7 @@ const yml = fs.readFileSync(path.resolve(__dirname, "../mocks/hello-world-sdl.ym describe("Tx Sign", () => { const registry = container.resolve(TYPE_REGISTRY); const walletService = new WalletTestingService(app); - const masterWalletService = container.resolve(MasterWalletService); + const masterWalletService = container.resolve(MANAGED_MASTER_WALLET); const dbService = container.resolve(DbTestingService); afterEach(async () => {