diff --git a/Makefile b/Makefile index 71927722a..ef66ce6d4 100644 --- a/Makefile +++ b/Makefile @@ -8,11 +8,12 @@ include ./packages/policy-engine-shared/Makefile include ./packages/transaction-request-intent/Makefile include ./packages/signature/Makefile -# For more terminal color codes, head over to https://opensource.com/article/19/9/linux-terminal-colors +# For more terminal color codes, head over to +# https://opensource.com/article/19/9/linux-terminal-colors TERM_NO_COLOR := \033[0m TERM_GREEN := \033[0;32m -# == Install == +# === Install === install: npm install @@ -20,7 +21,7 @@ install: install/ci: npm ci -# == Setup == +# === Setup === setup: make install @@ -32,7 +33,7 @@ setup: @echo "" @echo "${TERM_GREEN}Run 'make armory/start/dev' or/and 'make policy-engine/start/dev' to get them running.${TERM_NO_COLOR}" -# == Docker == +# === Docker === docker/stop: docker-compose stop @@ -40,7 +41,7 @@ docker/stop: docker/up: docker-compose up --detach -# == Code format == +# === Code format === format: npx nx format:write --all @@ -55,3 +56,23 @@ lint: lint/check: npx nx run-many --target lint + +# === Testing === + +test/type: + npx nx run-many --target test:type --all + +test/unit: + npx nx run-many --target test:unit --all + +test/integration: + npx nx run-many --target test:integration --all + +test/e2e: + npx nx run-many --target test:e2e --all + +test: + make test/type + make test/unit + make test/integration + make test/e2e diff --git a/README.md b/README.md index 549e01bf9..ca7e85e69 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,23 @@ make docker/up make docker/stop ``` +## Testing + +To run tests across all existing projects, you can use the following commands: + +```bash +# Run all tests +make test + +make test/type +make test/unit +make test/integration +make test/e2e +``` + +These commands utilize the NX CLI's run-many feature to execute the specified +targets (test or test:type) across all projects in the monorepo. + ## Formatting We use [Prettier](https://prettier.io/) and [ESLint](https://eslint.org/) to diff --git a/apps/armory/Makefile b/apps/armory/Makefile index f399325f7..b31cd9edd 100644 --- a/apps/armory/Makefile +++ b/apps/armory/Makefile @@ -113,6 +113,7 @@ armory/test/e2e/watch: make armory/test/e2e ARGS=--watch armory/test: + make armory/test/type make armory/test/unit make armory/test/integration make armory/test/e2e diff --git a/apps/armory/src/__test__/fixture/price.fixture.ts b/apps/armory/src/__test__/fixture/price.fixture.ts index df6db254a..70967d11b 100644 --- a/apps/armory/src/__test__/fixture/price.fixture.ts +++ b/apps/armory/src/__test__/fixture/price.fixture.ts @@ -1,4 +1,4 @@ -import { AssetType, Namespace, Prices, assetIdSchema } from '@narval/policy-engine-shared' +import { AssetId, AssetType, Namespace, Prices } from '@narval/policy-engine-shared' import { sample } from 'lodash' import { times } from 'lodash/fp' import { z } from 'zod' @@ -15,7 +15,7 @@ export const fiatIdSchema = z.custom<`fiat:${string}`>((value) => { export const priceSchema = z.record(fiatIdSchema, z.number().min(0)) -export const pricesSchema = z.record(assetIdSchema, priceSchema) +export const pricesSchema = z.record(AssetId, priceSchema) const fiatIdGenerator = Generator({ schema: fiatIdSchema, @@ -23,7 +23,7 @@ const fiatIdGenerator = Generator({ }) const assetIdGenerator = Generator({ - schema: assetIdSchema, + schema: AssetId, output: () => sample([ ...Array.from(CHAINS.values()).map(({ coin }) => coin.id), diff --git a/apps/armory/src/data-feed/core/service/historical-transfer-feed.service.ts b/apps/armory/src/data-feed/core/service/historical-transfer-feed.service.ts index 624eb1c41..a3bed15a7 100644 --- a/apps/armory/src/data-feed/core/service/historical-transfer-feed.service.ts +++ b/apps/armory/src/data-feed/core/service/historical-transfer-feed.service.ts @@ -2,14 +2,30 @@ import { Feed, HistoricalTransfer, JwtString } from '@narval/policy-engine-share import { Alg, Payload, SigningAlg, hash, hexToBase64Url, privateKeyToJwk, signJwt } from '@narval/signature' import { Injectable } from '@nestjs/common' import { ConfigService } from '@nestjs/config' -import { mapValues, omit } from 'lodash/fp' +import { omit } from 'lodash/fp' import { privateKeyToAccount } from 'viem/accounts' import { Config } from '../../../armory.config' import { DataFeed } from '../../../data-feed/core/type/data-feed.type' import { AuthorizationRequest } from '../../../orchestration/core/type/domain.type' +import { FiatId, Price } from '../../../shared/core/type/price.type' import { Transfer } from '../../../shared/core/type/transfer-tracking.type' import { TransferTrackingService } from '../../../transfer-tracking/core/service/transfer-tracking.service' +const buildHistoricalTranferRates = (rates: Price): Record => { + return Object.keys(rates).reduce( + (acc, currency) => { + const price = rates[currency as FiatId] + + if (price) { + acc[currency] = price.toString() + } + + return acc + }, + {} as Record + ) +} + @Injectable() export class HistoricalTransferFeedService implements DataFeed { static SOURCE_ID = 'armory/historical-transfer-feed' @@ -74,7 +90,7 @@ export class HistoricalTransferFeedService implements DataFeed value.toString(), transfer.rates) + rates: buildHistoricalTranferRates(transfer.rates) })) } } diff --git a/apps/armory/src/orchestration/core/service/authorization-request.service.ts b/apps/armory/src/orchestration/core/service/authorization-request.service.ts index 15c33c8fd..a643c9687 100644 --- a/apps/armory/src/orchestration/core/service/authorization-request.service.ts +++ b/apps/armory/src/orchestration/core/service/authorization-request.service.ts @@ -152,10 +152,12 @@ export class AuthorizationRequestService { to: intent.to, token: intent.token, chainId: authzRequest.request.transactionRequest.chainId, - initiatedBy: authzRequest.authentication, // TODO: Get real initiator? -- this used to reference publicKey but should actually pull data out of a decoded JWT + // TODO: (@mattschoch) Get real initiator? -- this used to reference publicKey but + // should actually pull data out of a decoded JWT + initiatedBy: authzRequest.authentication, createdAt: new Date(), amount: BigInt(intent.amount), - rates: transferPrices[intent.token] + rates: transferPrices[intent.token] || {} } await this.transferTrackingService.track(transfer) diff --git a/apps/armory/src/shared/core/type/price.type.ts b/apps/armory/src/shared/core/type/price.type.ts index 11fb0d5c5..ba4cd8609 100644 --- a/apps/armory/src/shared/core/type/price.type.ts +++ b/apps/armory/src/shared/core/type/price.type.ts @@ -4,6 +4,6 @@ export type FiatId = `fiat:${string}` // Not sure about using a plain number wouldn't result in precision loss with // crypto-to-crypto rates. -export type Price = Record +export type Price = Record -export type Prices = Record +export type Prices = Record diff --git a/apps/armory/src/transfer-tracking/core/service/__test__/e2e/transfer-tracking.service.spec.ts b/apps/armory/src/transfer-tracking/core/service/__test__/e2e/transfer-tracking.service.spec.ts index 77d1251d3..83270d0b9 100644 --- a/apps/armory/src/transfer-tracking/core/service/__test__/e2e/transfer-tracking.service.spec.ts +++ b/apps/armory/src/transfer-tracking/core/service/__test__/e2e/transfer-tracking.service.spec.ts @@ -60,7 +60,7 @@ describe(TransferTrackingService.name, () => { expect(first(models)).toEqual({ ...transfer, amount: transfer.amount.toString(), - rates: mapValues((value) => value.toString(), transfer.rates) + rates: mapValues((value) => value?.toString(), transfer.rates) }) }) diff --git a/apps/policy-engine/Makefile b/apps/policy-engine/Makefile index 5a606cbae..b9a68a9b3 100644 --- a/apps/policy-engine/Makefile +++ b/apps/policy-engine/Makefile @@ -115,6 +115,7 @@ policy-engine/test/e2e/watch: make policy-engine/test/e2e ARGS=--watch policy-engine/test: + make policy-engine/test/type make policy-engine/test/unit make policy-engine/test/integration make policy-engine/test/e2e diff --git a/apps/policy-engine/README.md b/apps/policy-engine/README.md index 06464fafb..6f9b0af54 100644 --- a/apps/policy-engine/README.md +++ b/apps/policy-engine/README.md @@ -23,6 +23,9 @@ make policy-engine/start/dev ## Testing ```bash +# Run all tests +make policy-engine/test + make policy-engine/test/type make policy-engine/test/unit make policy-engine/test/integration diff --git a/apps/policy-engine/src/engine/__test__/e2e/evaluation.spec.ts b/apps/policy-engine/src/engine/__test__/e2e/evaluation.spec.ts index d4877e50d..c8a945280 100644 --- a/apps/policy-engine/src/engine/__test__/e2e/evaluation.spec.ts +++ b/apps/policy-engine/src/engine/__test__/e2e/evaluation.spec.ts @@ -1,6 +1,6 @@ import { ConfigModule, ConfigService } from '@narval/config-module' import { EncryptionModuleOptionProvider } from '@narval/encryption-module' -import { Action, Criterion, Decision, FIXTURE, Then } from '@narval/policy-engine-shared' +import { Action, Criterion, Decision, EvaluationRequest, FIXTURE, Then } from '@narval/policy-engine-shared' import { Alg, PrivateKey, privateKeyToJwk, secp256k1PrivateKeyToJwk } from '@narval/signature' import { HttpStatus, INestApplication } from '@nestjs/common' import { Test, TestingModule } from '@nestjs/testing' @@ -16,7 +16,11 @@ import { InMemoryKeyValueRepository } from '../../../shared/module/key-value/per import { TestPrismaService } from '../../../shared/module/persistence/service/test-prisma.service' import { getEntityStore, getPolicyStore } from '../../../shared/testing/data-store.testing' import { getTestRawAesKeyring } from '../../../shared/testing/encryption.testing' -import { generateSignMessageRequest, generateSignTransactionRequest } from '../../../shared/testing/evaluation.testing' +import { + generateSignMessageRequest, + generateSignRawRequest, + generateSignTransactionRequest +} from '../../../shared/testing/evaluation.testing' import { Client } from '../../../shared/type/domain.type' import { ClientService } from '../../core/service/client.service' import { EngineSignerConfigService } from '../../core/service/engine-signer-config.service' @@ -177,9 +181,13 @@ describe('Evaluation', () => { }) describe('when sign message ', () => { - it('evaluates a forbid', async () => { - const payload = await generateSignMessageRequest() + let payload: EvaluationRequest + + beforeEach(async () => { + payload = await generateSignMessageRequest() + }) + it('evaluates a forbid', async () => { const { status, body } = await request(app.getHttpServer()) .post('/evaluations') .set(REQUEST_HEADER_CLIENT_ID, client.clientId) @@ -214,7 +222,69 @@ describe('Evaluation', () => { ) ) - const payload = await generateSignMessageRequest() + const { status, body } = await request(app.getHttpServer()) + .post('/evaluations') + .set(REQUEST_HEADER_CLIENT_ID, client.clientId) + .set(REQUEST_HEADER_CLIENT_SECRET, client.clientSecret) + .send(payload) + + expect(body).toMatchObject({ + decision: Decision.PERMIT, + request: payload.request, + accessToken: { + value: expect.any(String) + }, + approvals: { + missing: [], + required: [], + satisfied: [] + } + }) + expect(status).toEqual(HttpStatus.OK) + }) + }) + + describe('when sign raw', () => { + let payload: EvaluationRequest + + beforeEach(async () => { + payload = await generateSignRawRequest() + }) + + it('evaluates a forbid', async () => { + const { status, body } = await request(app.getHttpServer()) + .post('/evaluations') + .set(REQUEST_HEADER_CLIENT_ID, client.clientId) + .set(REQUEST_HEADER_CLIENT_SECRET, client.clientSecret) + .send(payload) + + expect(body).toEqual({ + decision: Decision.FORBID, + request: payload.request + }) + expect(status).toEqual(HttpStatus.OK) + }) + + it('evaluates a permit', async () => { + await clientService.savePolicyStore( + client.clientId, + await getPolicyStore( + [ + { + id: 'test-permit-policy', + then: Then.PERMIT, + description: 'test permit policy', + when: [ + { + criterion: Criterion.CHECK_ACTION, + args: [Action.SIGN_RAW] + } + ] + } + ], + privateKey + ) + ) const { status, body } = await request(app.getHttpServer()) .post('/evaluations') diff --git a/apps/policy-engine/src/engine/evaluation-request.dto.ts b/apps/policy-engine/src/engine/evaluation-request.dto.ts deleted file mode 100644 index dd240e071..000000000 --- a/apps/policy-engine/src/engine/evaluation-request.dto.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { SignMessageRequestDataDto, SignTransactionRequestDataDto } from '@narval/nestjs-shared' -import { AccountId, Action, FiatCurrency } from '@narval/policy-engine-shared' -import { ApiExtraModels, ApiProperty, getSchemaPath } from '@nestjs/swagger' -import { Type } from 'class-transformer' -import { IsDefined, IsOptional, ValidateNested } from 'class-validator' - -export class HistoricalTransferDto { - amount: string - from: AccountId - to: string - chainId: number - token: string - rates: { [keyof in FiatCurrency]: string } - initiatedBy: string - timestamp: number -} - -@ApiExtraModels(SignTransactionRequestDataDto, SignMessageRequestDataDto) -export class EvaluationRequestDto { - @IsDefined() - @ApiProperty() - authentication: string - - @IsOptional() - @ApiProperty({ - isArray: true - }) - approvals?: string[] - - @ValidateNested() - @Type((opts) => { - return opts?.object.request.action === Action.SIGN_TRANSACTION - ? SignTransactionRequestDataDto - : SignMessageRequestDataDto - }) - @IsDefined() - @ApiProperty({ - oneOf: [{ $ref: getSchemaPath(SignTransactionRequestDataDto) }, { $ref: getSchemaPath(SignMessageRequestDataDto) }] - }) - request: SignTransactionRequestDataDto | SignMessageRequestDataDto - - @IsOptional() - @ValidateNested() - @ApiProperty() - transfers?: HistoricalTransferDto[] -} diff --git a/apps/policy-engine/src/engine/http/rest/controller/evaluation.controller.ts b/apps/policy-engine/src/engine/http/rest/controller/evaluation.controller.ts index 7d9eb9634..f4d54fed0 100644 --- a/apps/policy-engine/src/engine/http/rest/controller/evaluation.controller.ts +++ b/apps/policy-engine/src/engine/http/rest/controller/evaluation.controller.ts @@ -2,7 +2,7 @@ import { Body, Controller, HttpCode, HttpStatus, Post, UseGuards } from '@nestjs import { ClientId } from '../../../../shared/decorator/client-id.decorator' import { ClientSecretGuard } from '../../../../shared/guard/client-secret.guard' import { EvaluationService } from '../../../core/service/evaluation.service' -import { EvaluationRequestDto } from '../../../evaluation-request.dto' +import { EvaluationRequestDto } from '../dto/evaluation-request.dto' @Controller('/evaluations') @UseGuards(ClientSecretGuard) diff --git a/apps/policy-engine/src/engine/http/rest/dto/evaluation-request.dto.ts b/apps/policy-engine/src/engine/http/rest/dto/evaluation-request.dto.ts new file mode 100644 index 000000000..85d7dff59 --- /dev/null +++ b/apps/policy-engine/src/engine/http/rest/dto/evaluation-request.dto.ts @@ -0,0 +1,4 @@ +import { EvaluationRequest } from '@narval/policy-engine-shared' +import { createZodDto } from 'nestjs-zod' + +export class EvaluationRequestDto extends createZodDto(EvaluationRequest) {} diff --git a/apps/policy-engine/src/open-policy-agent/core/open-policy-agent.engine.ts b/apps/policy-engine/src/open-policy-agent/core/open-policy-agent.engine.ts index 9cce0baa4..baaf3fd27 100644 --- a/apps/policy-engine/src/open-policy-agent/core/open-policy-agent.engine.ts +++ b/apps/policy-engine/src/open-policy-agent/core/open-policy-agent.engine.ts @@ -25,7 +25,7 @@ import { toData, toInput } from './util/evaluation.util' import { getRegoRuleTemplatePath } from './util/rego-transpiler.util' import { build, getRegoCorePath } from './util/wasm-build.util' -const SUPPORTED_ACTIONS: Action[] = [Action.SIGN_MESSAGE, Action.SIGN_TRANSACTION] +const SUPPORTED_ACTIONS: Action[] = [Action.SIGN_MESSAGE, Action.SIGN_TRANSACTION, Action.SIGN_RAW] export class OpenPolicyAgentEngine implements Engine { private policies: Policy[] diff --git a/apps/policy-engine/src/open-policy-agent/core/schema/open-policy-agent.schema.ts b/apps/policy-engine/src/open-policy-agent/core/schema/open-policy-agent.schema.ts index e82230672..5648d238f 100644 --- a/apps/policy-engine/src/open-policy-agent/core/schema/open-policy-agent.schema.ts +++ b/apps/policy-engine/src/open-policy-agent/core/schema/open-policy-agent.schema.ts @@ -1,4 +1,4 @@ -import { approvalRequirementSchema } from '@narval/policy-engine-shared' +import { ApprovalRequirement } from '@narval/policy-engine-shared' import { z } from 'zod' export const resultSchema = z.object({ @@ -10,8 +10,8 @@ export const resultSchema = z.object({ policyName: z.string(), policyId: z.string(), type: z.enum(['permit', 'forbid']), - approvalsSatisfied: z.array(approvalRequirementSchema), - approvalsMissing: z.array(approvalRequirementSchema) + approvalsSatisfied: z.array(ApprovalRequirement), + approvalsMissing: z.array(ApprovalRequirement) }) ) .optional() diff --git a/apps/policy-engine/src/open-policy-agent/core/util/__test__/unit/evaluation.util.spec.ts b/apps/policy-engine/src/open-policy-agent/core/util/__test__/unit/evaluation.util.spec.ts index 4708af36a..478dbdf52 100644 --- a/apps/policy-engine/src/open-policy-agent/core/util/__test__/unit/evaluation.util.spec.ts +++ b/apps/policy-engine/src/open-policy-agent/core/util/__test__/unit/evaluation.util.spec.ts @@ -3,11 +3,13 @@ import { EvaluationRequest, FIXTURE, SignMessageAction, + SignRawAction, SignTransactionAction } from '@narval/policy-engine-shared' import { InputType, decode } from '@narval/transaction-request-intent' import { generateSignMessageRequest, + generateSignRawRequest, generateSignTransactionRequest } from '../../../../../shared/testing/evaluation.testing' import { OpenPolicyAgentException } from '../../../exception/open-policy-agent.exception' @@ -45,7 +47,7 @@ describe('toInput', () => { it('maps action', () => { const input = toInput({ evaluation, principal, approvals }) - expect(input.action).toEqual(evaluation.request.action) + expect(input.action).toEqual(Action.SIGN_TRANSACTION) }) it('maps principal', () => { @@ -97,7 +99,7 @@ describe('toInput', () => { it('maps action', () => { const input = toInput({ evaluation, principal, approvals }) - expect(input.action).toEqual(evaluation.request.action) + expect(input.action).toEqual(Action.SIGN_MESSAGE) }) it('maps principal', () => { @@ -119,6 +121,39 @@ describe('toInput', () => { expect(input.approvals).toEqual(approvals) }) }) + + describe(`when action is ${Action.SIGN_RAW}`, () => { + let evaluation: EvaluationRequest + + beforeEach(async () => { + evaluation = await generateSignRawRequest() + }) + + it('maps action', () => { + const input = toInput({ evaluation, principal, approvals }) + + expect(input.action).toEqual(Action.SIGN_RAW) + }) + + it('maps principal', () => { + const input = toInput({ evaluation, principal, approvals }) + + expect(input.principal).toEqual(principal) + }) + + it('maps resource', () => { + const input = toInput({ evaluation, principal, approvals }) + const request = evaluation.request as SignRawAction + + expect(input.resource).toEqual({ uid: request.resourceId }) + }) + + it('maps approvals', () => { + const input = toInput({ evaluation, principal, approvals }) + + expect(input.approvals).toEqual(approvals) + }) + }) }) describe('toData', () => { diff --git a/apps/policy-engine/src/open-policy-agent/core/util/evaluation.util.ts b/apps/policy-engine/src/open-policy-agent/core/util/evaluation.util.ts index f60029367..f377ac5ff 100644 --- a/apps/policy-engine/src/open-policy-agent/core/util/evaluation.util.ts +++ b/apps/policy-engine/src/open-policy-agent/core/util/evaluation.util.ts @@ -1,53 +1,87 @@ -import { Action, CredentialEntity, Entities, EvaluationRequest } from '@narval/policy-engine-shared' +import { + Action, + CredentialEntity, + Entities, + EvaluationRequest, + Request, + SignMessageAction, + SignRawAction, + SignTransactionAction +} from '@narval/policy-engine-shared' import { InputType, safeDecode } from '@narval/transaction-request-intent' import { HttpStatus } from '@nestjs/common' import { indexBy } from 'lodash/fp' import { OpenPolicyAgentException } from '../exception/open-policy-agent.exception' import { Data, Input, UserGroup, WalletGroup } from '../type/open-policy-agent.type' -export const toInput = (params: { - evaluation: EvaluationRequest - principal: CredentialEntity - approvals?: CredentialEntity[] -}): Input => { - const { evaluation, principal, approvals } = params - const { action } = evaluation.request +type Mapping = (request: R, principal: CredentialEntity, approvals?: CredentialEntity[]) => Input - if (action === Action.SIGN_TRANSACTION) { - const result = safeDecode({ - input: { - type: InputType.TRANSACTION_REQUEST, - txRequest: evaluation.request.transactionRequest - } - }) - - if (result.success) { - return { - action, - principal, - approvals, - intent: result.intent, - transactionRequest: evaluation.request.transactionRequest, - resource: { uid: evaluation.request.resourceId } - } +const toSignTransaction: Mapping = (request, principal, approvals): Input => { + const result = safeDecode({ + input: { + type: InputType.TRANSACTION_REQUEST, + txRequest: request.transactionRequest } + }) - throw new OpenPolicyAgentException({ - message: 'Invalid transaction request intent', - suggestedHttpStatusCode: HttpStatus.BAD_REQUEST, - context: { error: result.error } - }) - } - - if (action === Action.SIGN_MESSAGE) { + if (result.success) { return { - action, + action: Action.SIGN_TRANSACTION, principal, approvals, - resource: { uid: evaluation.request.resourceId } + intent: result.intent, + transactionRequest: request.transactionRequest, + resource: { uid: request.resourceId } } } + throw new OpenPolicyAgentException({ + message: 'Invalid transaction request intent', + suggestedHttpStatusCode: HttpStatus.BAD_REQUEST, + context: { error: result.error } + }) +} + +const toSignMessageOrSignRaw = (params: { + action: Action + request: SignMessageAction | SignRawAction + principal: CredentialEntity + approvals?: CredentialEntity[] +}): Input => { + const { action, request, principal, approvals } = params + return { + action, + principal, + approvals, + resource: { uid: request.resourceId } + } +} + +const toSignMessage: Mapping = (request, principal, approvals): Input => + toSignMessageOrSignRaw({ action: Action.SIGN_MESSAGE, request, principal, approvals }) + +const toSignRaw: Mapping = (request, principal, approvals): Input => + toSignMessageOrSignRaw({ action: Action.SIGN_RAW, request, principal, approvals }) + +export const toInput = (params: { + evaluation: EvaluationRequest + principal: CredentialEntity + approvals?: CredentialEntity[] +}): Input => { + const { evaluation } = params + const { action } = evaluation.request + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const mappers = new Map>([ + [Action.SIGN_TRANSACTION, toSignTransaction], + [Action.SIGN_MESSAGE, toSignMessage], + [Action.SIGN_RAW, toSignRaw] + ]) + const mapper = mappers.get(action) + + if (mapper) { + return mapper(evaluation.request, params.principal, params.approvals) + } + throw new OpenPolicyAgentException({ message: 'Unsupported evaluation request action', suggestedHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, diff --git a/apps/policy-engine/src/shared/testing/evaluation.testing.ts b/apps/policy-engine/src/shared/testing/evaluation.testing.ts index a5bbd051e..7f7430c7b 100644 --- a/apps/policy-engine/src/shared/testing/evaluation.testing.ts +++ b/apps/policy-engine/src/shared/testing/evaluation.testing.ts @@ -1,5 +1,6 @@ import { Action, EvaluationRequest, FIXTURE, Request, TransactionRequest } from '@narval/policy-engine-shared' import { Alg, Payload, hash, privateKeyToJwk, signJwt } from '@narval/signature' +import { randomBytes } from 'crypto' import { UNSAFE_PRIVATE_KEY } from 'packages/policy-engine-shared/src/lib/dev.fixture' import { v4 as uuid } from 'uuid' import { toHex } from 'viem' @@ -32,7 +33,7 @@ export const generateSignTransactionRequest = async (): Promise = approvals: [bobSignature, carolSignature] } } + +export const generateSignRawRequest = async (): Promise => { + const request: Request = { + action: Action.SIGN_RAW, + nonce: uuid(), + resourceId: FIXTURE.WALLET.Engineering.id, + rawMessage: toHex(randomBytes(42)) + } + + const { aliceSignature, bobSignature, carolSignature } = await sign(request) + + return { + authentication: aliceSignature, + request, + approvals: [bobSignature, carolSignature] + } +} diff --git a/apps/vault/Makefile b/apps/vault/Makefile index 860da7d68..11e0ba105 100644 --- a/apps/vault/Makefile +++ b/apps/vault/Makefile @@ -115,6 +115,7 @@ vault/test/e2e/watch: make vault/test/e2e ARGS=--watch vault/test: + make vault/test/type make vault/test/unit make vault/test/integration make vault/test/e2e diff --git a/apps/vault/README.md b/apps/vault/README.md index 8d9dc2d2b..dbba69bb9 100644 --- a/apps/vault/README.md +++ b/apps/vault/README.md @@ -19,6 +19,9 @@ make vault/start/dev ## Testing ```bash +# Run all tests +make vault/test + make vault/test/type make vault/test/unit make vault/test/integration diff --git a/packages/nestjs-shared/project.json b/packages/nestjs-shared/project.json index ad4a398e8..8814ac275 100644 --- a/packages/nestjs-shared/project.json +++ b/packages/nestjs-shared/project.json @@ -11,6 +11,12 @@ "lintFilePatterns": ["packages/nestjs-shared/**/*.ts"] } }, + "test:type": { + "executor": "nx:run-commands", + "options": { + "command": "make nestjs-shared/test/type" + } + }, "test:unit": { "executor": "@nx/jest:jest", "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], diff --git a/packages/policy-engine-shared/project.json b/packages/policy-engine-shared/project.json index 50c292c95..30668a326 100644 --- a/packages/policy-engine-shared/project.json +++ b/packages/policy-engine-shared/project.json @@ -11,6 +11,12 @@ "lintFilePatterns": ["packages/policy-engine-shared/**/*.ts"] } }, + "test:type": { + "executor": "nx:run-commands", + "options": { + "command": "make policy-engine-shared/test/type" + } + }, "test:unit": { "executor": "@nx/jest:jest", "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], diff --git a/packages/policy-engine-shared/src/index.ts b/packages/policy-engine-shared/src/index.ts index eaac8f8fd..7f9fad4f8 100644 --- a/packages/policy-engine-shared/src/index.ts +++ b/packages/policy-engine-shared/src/index.ts @@ -1,6 +1,5 @@ export * from './lib/schema/address.schema' export * from './lib/schema/data-store.schema' -export * from './lib/schema/domain.schema' export * from './lib/schema/entity.schema' export * from './lib/schema/hex.schema' export * from './lib/schema/policy.schema' diff --git a/packages/policy-engine-shared/src/lib/schema/domain.schema.ts b/packages/policy-engine-shared/src/lib/schema/domain.schema.ts deleted file mode 100644 index b9ed7aa4b..000000000 --- a/packages/policy-engine-shared/src/lib/schema/domain.schema.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { z } from 'zod' -import { EntityType } from '../type/domain.type' - -export const approvalRequirementSchema = z.object({ - approvalCount: z.number().min(0), - /** - * The number of requried approvals - */ - approvalEntityType: z.nativeEnum(EntityType), - /** - * List of entities IDs that must satisfy the requirements. - */ - entityIds: z.array(z.string()), - countPrincipal: z.boolean() -}) diff --git a/packages/policy-engine-shared/src/lib/schema/entity.schema.ts b/packages/policy-engine-shared/src/lib/schema/entity.schema.ts index 64512546e..0b29a1a8f 100644 --- a/packages/policy-engine-shared/src/lib/schema/entity.schema.ts +++ b/packages/policy-engine-shared/src/lib/schema/entity.schema.ts @@ -1,6 +1,6 @@ import { publicKeySchema } from '@narval/signature' import { z } from 'zod' -import { accountIdSchema, assetIdSchema } from '../util/caip.util' +import { AccountId, AssetId } from '../util/caip.util' import { addressSchema } from './address.schema' export const userRoleSchema = z.nativeEnum({ @@ -69,14 +69,14 @@ export const walletGroupMemberEntitySchema = z.object({ }) export const addressBookAccountEntitySchema = z.object({ - id: accountIdSchema, + id: AccountId, address: addressSchema, chainId: z.number(), classification: accountClassificationSchema }) export const tokenEntitySchema = z.object({ - id: assetIdSchema, + id: AssetId, address: addressSchema, symbol: z.string().nullable(), chainId: z.number(), diff --git a/packages/policy-engine-shared/src/lib/schema/policy.schema.ts b/packages/policy-engine-shared/src/lib/schema/policy.schema.ts index 04966998e..77891fc85 100644 --- a/packages/policy-engine-shared/src/lib/schema/policy.schema.ts +++ b/packages/policy-engine-shared/src/lib/schema/policy.schema.ts @@ -3,7 +3,7 @@ import { z } from 'zod' import { Action } from '../type/action.type' import { EntityType, FiatCurrency, IdentityOperators, ValueOperators } from '../type/domain.type' import { AccountType, UserRole } from '../type/entity.type' -import { accountIdSchema, assetIdSchema } from '../util/caip.util' +import { AccountId, AssetId } from '../util/caip.util' import { addressSchema } from './address.schema' import { hexSchema } from './hex.schema' @@ -84,7 +84,7 @@ export const amountConditionSchema = z.object({ }) export const erc1155AmountConditionSchema = z.object({ - tokenId: assetIdSchema, + tokenId: AssetId, operator: z.nativeEnum(ValueOperators), value: z.string() }) @@ -121,9 +121,9 @@ export const spendingLimitTimeWindowSchema = z.object({ }) export const spendingLimitFiltersSchema = z.object({ - tokens: z.array(assetIdSchema).min(1).optional(), + tokens: z.array(AssetId).min(1).optional(), users: z.array(z.string().min(1)).min(1).optional(), - resources: z.array(accountIdSchema).min(1).optional(), + resources: z.array(AccountId).min(1).optional(), chains: z.array(z.string().min(1)).min(1).optional(), userGroups: z.array(z.string().min(1)).min(1).optional(), walletGroups: z.array(z.string().min(1)).min(1).optional() @@ -194,7 +194,7 @@ export const intentTypeCriterionSchema = z.object({ export const destinationIdCriterionSchema = z.object({ criterion: z.literal(criterionSchema.enum.CHECK_DESTINATION_ID), - args: z.array(accountIdSchema).min(1) + args: z.array(AccountId).min(1) }) export const destinationAddressCriterionSchema = z.object({ @@ -214,17 +214,17 @@ export const destinationClassificationCriterionSchema = z.object({ export const intentContractCriterionSchema = z.object({ criterion: z.literal(criterionSchema.enum.CHECK_INTENT_CONTRACT), - args: z.array(accountIdSchema).min(1) + args: z.array(AccountId).min(1) }) export const intentTokenCriterionSchema = z.object({ criterion: z.literal(criterionSchema.enum.CHECK_INTENT_TOKEN), - args: z.array(assetIdSchema).min(1) + args: z.array(AssetId).min(1) }) export const intentSpenderCriterionSchema = z.object({ criterion: z.literal(criterionSchema.enum.CHECK_INTENT_SPENDER), - args: z.array(accountIdSchema).min(1) + args: z.array(AccountId).min(1) }) export const intentChainIdCriterionSchema = z.object({ @@ -244,12 +244,12 @@ export const intentAmountCriterionSchema = z.object({ export const erc721TokenIdCriterionSchema = z.object({ criterion: z.literal(criterionSchema.enum.CHECK_ERC721_TOKEN_ID), - args: z.array(assetIdSchema).min(1) + args: z.array(AssetId).min(1) }) export const erc1155TokenIdCriterionSchema = z.object({ criterion: z.literal(criterionSchema.enum.CHECK_ERC1155_TOKEN_ID), - args: z.array(assetIdSchema).min(1) + args: z.array(AssetId).min(1) }) export const erc1155TransfersCriterionSchema = z.object({ diff --git a/packages/policy-engine-shared/src/lib/type/action.type.ts b/packages/policy-engine-shared/src/lib/type/action.type.ts index f5c1e1a95..40d2b969a 100644 --- a/packages/policy-engine-shared/src/lib/type/action.type.ts +++ b/packages/policy-engine-shared/src/lib/type/action.type.ts @@ -3,73 +3,15 @@ import { z } from 'zod' import { addressSchema } from '../schema/address.schema' import { hexSchema } from '../schema/hex.schema' import { isHexString } from '../util/typeguards' -import { Address, JwtString } from './domain.type' -import { - AccountClassification, - AccountType, - CredentialEntity, - UserGroupMemberEntity, - UserRole, - UserWalletEntity, - WalletGroupMemberEntity -} from './entity.type' export const Action = { - CREATE_ORGANIZATION: 'CREATE_ORGANIZATION', - - CREATE_USER: 'CREATE_USER', - UPDATE_USER: 'UPDATE_USER', - CREATE_CREDENTIAL: 'CREATE_CREDENTIAL', - ASSIGN_USER_GROUP: 'ASSIGN_USER_GROUP', - ASSIGN_WALLET_GROUP: 'ASSIGN_WALLET_GROUP', - ASSIGN_USER_WALLET: 'ASSIGN_USER_WALLET', - - DELETE_USER: 'user:delete', - - REGISTER_WALLET: 'REGISTER_WALLET', - CREATE_ADDRESS_BOOK_ACCOUNT: 'CREATE_ADDRESS_BOOK_ACCOUNT', - EDIT_WALLET: 'wallet:edit', - UNASSIGN_WALLET: 'wallet:unassign', - REGISTER_TOKENS: 'REGISTER_TOKENS', - - EDIT_USER_GROUP: 'user-group:edit', - DELETE_USER_GROUP: 'user-group:delete', - - CREATE_WALLET_GROUP: 'wallet-group:create', - DELETE_WALLET_GROUP: 'wallet-group:delete', - - SET_POLICY_RULES: 'setPolicyRules', - SIGN_TRANSACTION: 'signTransaction', SIGN_RAW: 'signRaw', SIGN_MESSAGE: 'signMessage', SIGN_TYPED_DATA: 'signTypedData' } as const - export type Action = (typeof Action)[keyof typeof Action] - -// DOMAIN - -/** - * Action Types; these correspond to each Action - */ -export type BaseAction = { - action: Action - nonce: string -} - -export type BaseAdminRequest = { - /** - * The initiator signature of the request using `hashRequest` method to ensure - * SHA256 format. - */ - authentication: JwtString - - /** - * Approval from the ENGINE; this is the attestation generated by an Evaluation of the action, and now the ENGINE is the consumer of the attestation to do a data change. - */ - approvals: JwtString[] -} +export const ActionSchema = z.nativeEnum(Action) export const AccessList = z.array( z.object({ @@ -79,13 +21,11 @@ export const AccessList = z.array( ) export type AccessList = z.infer -export const ActionSchema = z.nativeEnum(Action) - -export const BaseActionSchema = z.object({ +export const BaseAction = z.object({ action: ActionSchema, nonce: z.string() }) -export type BaseActionSchema = z.infer +export type BaseAction = z.infer export const TransactionRequest = z.object({ chainId: z.number(), @@ -116,8 +56,9 @@ export const Eip712TypedData = z.object({ types: z.record( z.array( z.object({ - name: z.string(), // - type: z.string() // TODO: make this more specific to the solidity types allowed + name: z.string(), + // TODO: make this more specific to the solidity types allowed + type: z.string() }) ) ), @@ -126,8 +67,7 @@ export const Eip712TypedData = z.object({ }) export type Eip712TypedData = z.infer -export const SignTransactionAction = z.intersection( - BaseActionSchema, +export const SignTransactionAction = BaseAction.merge( z.object({ action: z.literal(Action.SIGN_TRANSACTION), resourceId: z.string(), @@ -136,7 +76,8 @@ export const SignTransactionAction = z.intersection( ) export type SignTransactionAction = z.infer -// Matching viem's SignableMessage options https://viem.sh/docs/actions/wallet/signMessage#message +// Matching viem's SignableMessage options +// See https://viem.sh/docs/actions/wallet/signMessage#message export const SignableMessage = z.union([ z.string(), z.object({ @@ -145,8 +86,7 @@ export const SignableMessage = z.union([ ]) export type SignableMessage = z.infer -export const SignMessageAction = z.intersection( - BaseActionSchema, +export const SignMessageAction = BaseAction.merge( z.object({ action: z.literal(Action.SIGN_MESSAGE), resourceId: z.string(), @@ -155,12 +95,12 @@ export const SignMessageAction = z.intersection( ) export type SignMessageAction = z.infer -export const SignTypedDataAction = z.intersection( - BaseActionSchema, +export const SignTypedDataAction = BaseAction.merge( z.object({ action: z.literal(Action.SIGN_TYPED_DATA), resourceId: z.string(), - // Accept typedData as a JSON object, or a Stringified JSON object, or a hex-encoded stringified json object + // Accept typedData as a JSON object, or a stringified JSON, or a + // hex-encoded stringified JSON. typedData: z.preprocess((val) => { if (typeof val === 'string') { try { @@ -176,8 +116,7 @@ export const SignTypedDataAction = z.intersection( ) export type SignTypedDataAction = z.infer -export const SignRawAction = z.intersection( - BaseActionSchema, +export const SignRawAction = BaseAction.merge( z.object({ action: z.literal(Action.SIGN_RAW), resourceId: z.string(), @@ -185,119 +124,3 @@ export const SignRawAction = z.intersection( }) ) export type SignRawAction = z.infer - -export type CreateOrganizationAction = BaseAction & { - action: typeof Action.CREATE_ORGANIZATION - organization: { - uid: string - credential: CredentialEntity - } -} - -export type CreateOrganizationRequest = BaseAdminRequest & { - request: CreateOrganizationAction -} - -export type CreateUserAction = BaseAction & { - action: typeof Action.CREATE_USER - user: { - uid: string - role: UserRole - credential?: CredentialEntity - } -} - -export type CreateUserRequest = BaseAdminRequest & { - request: CreateUserAction -} - -export type UpdateUserAction = BaseAction & { - action: typeof Action.UPDATE_USER - user: { - uid: string - role: UserRole - } -} - -export type UpdateUserRequest = BaseAdminRequest & { - request: UpdateUserAction -} - -export type CreateCredentialAction = BaseAction & { - action: typeof Action.CREATE_CREDENTIAL - credential: CredentialEntity -} - -export type CreateCredentialRequest = BaseAdminRequest & { - request: CreateCredentialAction -} - -export type AssignUserGroupAction = BaseAction & { - action: typeof Action.ASSIGN_USER_GROUP - data: UserGroupMemberEntity -} - -export type AssignUserGroupRequest = BaseAdminRequest & { - request: AssignUserGroupAction -} - -export type RegisterWalletAction = BaseAction & { - action: typeof Action.REGISTER_WALLET - wallet: { - uid: string - address: Address - accountType: AccountType - chainId?: number - } -} - -export type RegisterWalletRequest = BaseAdminRequest & { - request: RegisterWalletAction -} - -export type AssignWalletGroupAction = BaseAction & { - action: typeof Action.ASSIGN_WALLET_GROUP - data: WalletGroupMemberEntity -} - -export type AssignWalletGroupRequest = BaseAdminRequest & { - request: AssignWalletGroupAction -} - -export type AssignUserWalletAction = BaseAction & { - action: typeof Action.ASSIGN_USER_WALLET - data: UserWalletEntity -} - -export type AssignUserWalletRequest = BaseAdminRequest & { - request: AssignUserWalletAction -} - -export type CreateAddressBookAccountAction = BaseAction & { - action: typeof Action.CREATE_ADDRESS_BOOK_ACCOUNT - account: { - uid: string - address: Address - chainId: number - classification: AccountClassification - } -} - -export type CreateAddressBookAccountRequest = BaseAdminRequest & { - request: CreateAddressBookAccountAction -} - -export type RegisterTokensAction = BaseAction & { - action: typeof Action.REGISTER_TOKENS - tokens: { - uid: string - address: Address - chainId: number - symbol: string - decimals: number - }[] -} - -export type RegisterTokensRequest = BaseAdminRequest & { - request: RegisterTokensAction -} diff --git a/packages/policy-engine-shared/src/lib/type/domain.type.ts b/packages/policy-engine-shared/src/lib/type/domain.type.ts index e273c22ee..4a7d20397 100644 --- a/packages/policy-engine-shared/src/lib/type/domain.type.ts +++ b/packages/policy-engine-shared/src/lib/type/domain.type.ts @@ -1,13 +1,6 @@ -import { z } from 'zod' -import { approvalRequirementSchema } from '../schema/domain.schema' -import { AssetId } from '../util/caip.util' -import { - CreateOrganizationAction, - type SignMessageAction, - type SignRawAction, - type SignTransactionAction, - type SignTypedDataAction -} from './action.type' +import { ZodTypeAny, z } from 'zod' +import { AccountId } from '../util/caip.util' +import { SignMessageAction, SignRawAction, SignTransactionAction, SignTypedDataAction } from './action.type' export enum Decision { PERMIT = 'Permit', @@ -51,39 +44,32 @@ export enum IdentityOperators { IN = 'in' } -export type JwtString = string +export const JwtString = z.string().min(1) +export type JwtString = z.infer -export type HistoricalTransfer = { - /** - * Amount in the smallest unit of the token (eg. wei for ETH). - */ - amount: string - from: string - /** - * In case we want spending limit per destination address - */ - to: string - chainId: number - token: string +export const HistoricalTransfer = z.object({ + amount: z.string().describe('Amount in the smallest unit of the token (eg. wei for ETH)'), + from: z.string().min(1), + to: z.string().min(1).describe('In case we want spending limit per destination address'), + chainId: z.number().min(1), + token: z.string().min(1), /** * @example * { fiat:usd: '0.01', fiat:eur: '0.02' } */ - rates: Record - /** - * UID of the user who initiated the transfer. - */ - initiatedBy: string - /** - * Unix timestamp in milliseconds. - */ - timestamp: number -} + rates: z.record(z.string()), + initiatedBy: z.string().min(1).describe('ID of the user who initiated the transfer'), + timestamp: z.number().describe('Unix timestamp in milliseconds') +}) +export type HistoricalTransfer = z.infer + +const Price = z.record(z.string(), z.number()) + +export const Prices = z + .record(AccountId, Price) + .describe('Represents a collection of prices for different assets present in the authorization request') /** - * Represents a collection of prices for different assets present in the - * authorization request. - * * @example The price of USDC and MATIC in USD and ETH. * { * 'eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48': { @@ -96,14 +82,22 @@ export type HistoricalTransfer = { * } * } */ -export type Prices = Record> - -export type Request = - | SignTransactionAction - | SignMessageAction - | SignTypedDataAction - | SignRawAction - | CreateOrganizationAction +export type Prices = z.infer + +export const Request = z.discriminatedUnion('action', [ + SignTransactionAction, + SignMessageAction, + SignTypedDataAction, + SignRawAction +]) +export type Request = z.infer + +export const Feed = (dataSchema: Data) => + z.object({ + source: z.string(), + sig: JwtString.nullable(), + data: dataSchema + }) /** * The feeds represent arbitrary data collected by the Armory and @@ -125,36 +119,30 @@ export type Feed = { data: Data } -/** - * The action being authorized. - * - * This must include all the data being authorized, and nothing except the data - * being authorized. This is the data that will be hashed and signed. - */ -export type EvaluationRequest = { - /** - * JWT signature of the request property. - */ - authentication: JwtString - /** - * The authorization request. - */ - request: Request - /** - * JWT signatures of the request property. - */ - approvals?: JwtString[] - // TODO: Delete transfers. It was replaced by `feeds`. - transfers?: HistoricalTransfer[] - prices?: Prices - /** - * Arbitrary data feeds that are necessary for some policies. These may - * include, for instance, prices and approved transfers. - */ - feeds?: Feed[] -} - -export type ApprovalRequirement = z.infer +export const EvaluationRequest = z + .object({ + authentication: JwtString.describe('JWT signature of the request property'), + request: Request.describe('The request to be authorized'), + approvals: z.array(JwtString).optional(), + prices: Prices.optional(), + transfers: z.array(HistoricalTransfer).optional(), + feeds: z + .array(Feed(z.unknown())) + .optional() + .describe( + 'Arbitrary data feeds that are necessary for some policies. These may include, for instance, prices and approved transfers' + ) + }) + .describe('The action being authorized') +export type EvaluationRequest = z.infer + +export const ApprovalRequirement = z.object({ + approvalCount: z.number().min(0), + approvalEntityType: z.nativeEnum(EntityType).describe('The number of requried approvals'), + entityIds: z.array(z.string()).describe('List of entities IDs that must satisfy the requirements'), + countPrincipal: z.boolean() +}) +export type ApprovalRequirement = z.infer export type AccessToken = { value: string // JWT @@ -173,6 +161,6 @@ export type EvaluationResponse = { transactionRequestIntent?: unknown } -export type Hex = `0x${string}` // DOMAIN +export type Hex = `0x${string}` -export type Address = `0x${string}` // DOMAIN +export type Address = `0x${string}` diff --git a/packages/policy-engine-shared/src/lib/util/caip.util.ts b/packages/policy-engine-shared/src/lib/util/caip.util.ts index 110d04862..d6e6116a6 100644 --- a/packages/policy-engine-shared/src/lib/util/caip.util.ts +++ b/packages/policy-engine-shared/src/lib/util/caip.util.ts @@ -35,10 +35,52 @@ export enum AssetType { SLIP44 = 'slip44' } +const NonCollectableAssetId = z.custom<`${Namespace}:${number}/${AssetType}:${string}`>((value) => { + const parse = z.string().safeParse(value) + + if (parse.success) { + return isAssetId(parse.data) + } + + return false +}) + +const CollectableAssetId = z.custom<`${Namespace}:${number}/${AssetType}:${string}/${string}`>((value) => { + const parse = z.string().safeParse(value) + + if (parse.success) { + return isAssetId(parse.data) + } + + return false +}) + +const CoinAssetId = z.custom<`${Namespace}:${number}/${AssetType.SLIP44}:${number}`>((value) => { + const parse = z.string().safeParse(value) + + if (parse.success) { + return isAssetId(parse.data) + } + + return false +}) + +export const AssetId = z.union([NonCollectableAssetId, CollectableAssetId, CoinAssetId]) + /** * @see https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-10.md */ -export type AccountId = `${Namespace}:${number}:${string}` +export const AccountId = z.custom<`${Namespace}:${number}:${string}`>((value) => { + const parse = z.string().safeParse(value) + + if (parse.success) { + return isAccountId(parse.data) + } + + return false +}) + +export type AccountId = z.infer export type Account = { chainId: number @@ -46,16 +88,12 @@ export type Account = { namespace: Namespace } -type NonCollectableAssetId = `${Namespace}:${number}/${AssetType}:${string}` -type CollectableAssetId = `${Namespace}:${number}/${AssetType}:${string}/${string}` -type CoinAssetId = `${Namespace}:${number}/${AssetType.SLIP44}:${number}` - /** * @see https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-19.md * @see https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-20.md * @see https://github.com/satoshilabs/slips/blob/master/slip-0044.md */ -export type AssetId = NonCollectableAssetId | CollectableAssetId | CoinAssetId +export type AssetId = z.infer export type Token = Account & { assetType: AssetType @@ -470,45 +508,3 @@ export const getAssetId = (value: string): AssetId => unsafeParse(safeG // // Zod Schema // - -const nonCollectableAssetIdSchema = z.custom((value) => { - const parse = z.string().safeParse(value) - - if (parse.success) { - return isAssetId(parse.data) - } - - return false -}) - -const collectableAssetIdSchema = z.custom((value) => { - const parse = z.string().safeParse(value) - - if (parse.success) { - return isAssetId(parse.data) - } - - return false -}) - -const coinAssetIdSchema = z.custom((value) => { - const parse = z.string().safeParse(value) - - if (parse.success) { - return isAssetId(parse.data) - } - - return false -}) - -export const assetIdSchema = z.union([nonCollectableAssetIdSchema, collectableAssetIdSchema, coinAssetIdSchema]) - -export const accountIdSchema = z.custom((value) => { - const parse = z.string().safeParse(value) - - if (parse.success) { - return isAccountId(parse.data) - } - - return false -}) diff --git a/packages/signature/project.json b/packages/signature/project.json index e567e1ec6..9703b071c 100644 --- a/packages/signature/project.json +++ b/packages/signature/project.json @@ -11,6 +11,12 @@ "lintFilePatterns": ["packages/signature/**/*.ts"] } }, + "test:type": { + "executor": "nx:run-commands", + "options": { + "command": "make signature/test/type" + } + }, "test:unit": { "executor": "@nx/jest:jest", "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], diff --git a/packages/transaction-request-intent/project.json b/packages/transaction-request-intent/project.json index 0ff1fafe9..48817b229 100644 --- a/packages/transaction-request-intent/project.json +++ b/packages/transaction-request-intent/project.json @@ -11,6 +11,12 @@ "lintFilePatterns": ["packages/transaction-request-intent/**/*.ts"] } }, + "test:type": { + "executor": "nx:run-commands", + "options": { + "command": "make transaction-request-intent/test/type" + } + }, "test:unit": { "executor": "@nx/jest:jest", "outputs": ["{workspaceRoot}/coverage/{projectRoot}"],