Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
3b0c07c
chore: 🤖 bump SDK to v31.0.0-beta.7
prashantasdeveloper Aug 14, 2026
25cd4fb
feat: 🎸 remove subsidize account endpoint
prashantasdeveloper Aug 14, 2026
cfcda5b
feat: 🎸 remove instruction affirmation withdrawal endpoints
prashantasdeveloper Aug 14, 2026
7ed4bb8
feat: 🎸 remove CDD claims endpoint
prashantasdeveloper Aug 14, 2026
43a863a
fix: 🐛 replace TransferError members removed in v31
prashantasdeveloper Aug 14, 2026
f02191b
feat: 🎸 return pending Instruction from transfer funds
prashantasdeveloper Aug 14, 2026
f2f4f48
fix: 🐛 grant test admins DID Registrar status on chain v8
prashantasdeveloper Aug 14, 2026
4dd6064
test: 💍 update expected transaction groups for v31
prashantasdeveloper Aug 14, 2026
9bfbaaf
docs: ✏️ update supported chain versions
prashantasdeveloper Aug 14, 2026
46de6a8
feat: 🎸 surface reserved and frozen fields on Account balance
prashantasdeveloper Aug 14, 2026
85d2654
docs: ✏️ remove stale CDD claim wording from transferPolyx
prashantasdeveloper Aug 14, 2026
75f0a2c
fix: 🐛 make createCdd and expiry optional on RegisterIdentityDto
prashantasdeveloper Aug 14, 2026
cebcf10
docs: ✏️ remove stale chain v7/v8 wording from endpoint descriptions
prashantasdeveloper Aug 14, 2026
a99bb63
chore: 🤖 remove unreferenced ScopeClaimProofDto
prashantasdeveloper Aug 14, 2026
204289c
feat: 🎸 add portfolio-level asset pre-approval
prashantasdeveloper Aug 14, 2026
cee6786
feat: 🎸 add instruction lock, unlock and relock status endpoints
prashantasdeveloper Aug 14, 2026
b4e034b
feat: 🎸 add Instruction leg status endpoint
prashantasdeveloper Aug 14, 2026
42d8762
feat: 🎸 add DID registrar registration endpoint
prashantasdeveloper Aug 14, 2026
e5f94e7
feat: 🎸 add Venue signer count endpoint
prashantasdeveloper Aug 14, 2026
0cb5015
feat: 🎸 add issued-in-funding-round endpoint
prashantasdeveloper Aug 14, 2026
63e6167
feat: 🎸 add next checkpoint endpoint
prashantasdeveloper Aug 14, 2026
eb3a610
feat: 🎸 add Corporate Action documents endpoint
prashantasdeveloper Aug 14, 2026
49aafd2
chore: 🤖 add missing getDocuments to MockCorporateActionsService
prashantasdeveloper Aug 14, 2026
0a735b8
feat: 🎸 add ticker registration config endpoint
prashantasdeveloper Aug 14, 2026
c4313e4
feat: 🎸 support creating SettleAfterLock Instructions
prashantasdeveloper Aug 14, 2026
ed6f14b
chore: 🤖 bump SDK to v31.0.0 (stable)
prashantasdeveloper Aug 19, 2026
e5079c8
fix: 🐛 require expiresAt on off-chain affirmation receipts
prashantasdeveloper Aug 19, 2026
0a5f89a
refactor: 💡 dedupe swagger decorators on portfolio pre-approval endpo…
prashantasdeveloper Aug 19, 2026
a6515b0
chore: 🤖 throw NotFoundException from getNextCheckpoint instead of null
prashantasdeveloper Aug 24, 2026
1a8e6f8
chore: 🤖 preserve null on TickerRegistrationConfigModel.registrationL…
prashantasdeveloper Aug 24, 2026
9bbf11b
chore: 🤖 restore missing 400 doc for registerIdentity expiry validation
prashantasdeveloper Aug 24, 2026
35cda7a
chore: 🤖 remove dead code left over from CDD claims removal
prashantasdeveloper Aug 24, 2026
659df7e
chore: 🤖 apply prettier formatting
prashantasdeveloper Aug 24, 2026
d83b488
chore: 🤖 exclude dist/ from docker build context
prashantasdeveloper Aug 24, 2026
18bb030
chore: 🤖 bump SDK to v31.1.0-beta.8
prashantasdeveloper Aug 27, 2026
9b8ffec
feat: 🎸 issue NFTs directly to an Account
prashantasdeveloper Aug 27, 2026
e5337fa
fix: 🐛 support an API key for the middleware V2 connection
prashantasdeveloper Aug 27, 2026
4f2f0ca
docs: ✏️ NFT issuance's account must be the signing Account
prashantasdeveloper Sep 1, 2026
0f0e32f
fix: 🐛 gate developer, auth and signer management endpoints
prashantasdeveloper Sep 17, 2026
b3f79be
ci: add ECR publish alongside Docker Hub
debelio Jul 7, 2026
0a24090
refactor: 💡 resolve SonarCloud issues
prashantasdeveloper Sep 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.git
.env*
node_modules/
node_modules/
/dist
68 changes: 68 additions & 0 deletions .github/workflows/build-publish-image.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
name: Build and publish image to Docker Hub and ECR

on:
push:
tags:
- v*

permissions:
id-token: write
contents: read

env:
AWS_REGION: us-west-2
ECR_REPOSITORY: polymesh/polymesh-rest-api

jobs:
publish:
name: Build and push image
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Check out the repo
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7

- name: Set package version
run: ./scripts/set-package-version.sh

- name: Set up QEMU
uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0

- name: Log in to Docker Hub
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
with:
username: ${{ secrets.ASSOCIATION_DOCKER_USERNAME }}
password: ${{ secrets.ASSOCIATION_DOCKER_TOKEN }}

- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@254c19bd240aabef8777f48595e9d2d7b972184b # v6.2.1
with:
role-to-assume: ${{ vars.ECR_PUSH_ROLE_ARN }}
aws-region: ${{ env.AWS_REGION }}

- name: Log in to Amazon ECR
id: ecr-login
uses: aws-actions/amazon-ecr-login@d539f0932e70871a027e9d5a9d8fc38589180a64 # v2.1.6

- name: Extract metadata (tags, labels) for Docker Hub
id: meta
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
with:
images: ${{ secrets.ASSOCIATION_DOCKER_HUB_REPO }}

- name: Build and push image
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
provenance: true
sbom: true
tags: |
${{ steps.meta.outputs.tags }}
${{ steps.ecr-login.outputs.registry }}/${{ env.ECR_REPOSITORY }}:${{ github.ref_name }}
${{ steps.ecr-login.outputs.registry }}/${{ env.ECR_REPOSITORY }}:${{ github.sha }}
labels: ${{ steps.meta.outputs.labels }}
40 changes: 0 additions & 40 deletions .github/workflows/docker-image.yml

This file was deleted.

7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

A REST API wrapper for the Polymesh blockchain.

This version is compatible with chain versions 7.3.x - 8.x
This version is compatible with chain versions 8.0.x - 8.1.x. Chain v7 is no longer supported — the SDK throws on initialization if pointed at a v7 node.

## Setup

Expand Down Expand Up @@ -65,6 +65,7 @@ $ yarn test:cov
PORT=## port in which the server will listen. Defaults to 3000 ##
POLYMESH_NODE_URL=## websocket URL for a Polymesh node ##
POLYMESH_MIDDLEWARE_V2_URL=## URL for an instance of the Polymesh GraphQL Middleware Native SubQuery service ##
POLYMESH_MIDDLEWARE_V2_API_KEY=## optional API key for the Middleware V2 GraphQL service, if it requires authentication ##
POLYMESH_MIDDLEWARE_URL=## URL for an instance of the Polymesh GraphQL Middleware service @deprecated in favour of POLYMESH_MIDDLEWARE_V2_URL##
POLYMESH_MIDDLEWARE_API_KEY=## API key for the Middleware GraphQL service ##
LOCAL_SIGNERS=## list of comma separated IDs to refer to the corresponding mnemonic ##
Expand All @@ -73,7 +74,7 @@ LOCAL_MNEMONICS=## list of comma separated mnemonics for the signer service (eac
# Below are optional params that enable some features. The above should be good to get started with

DEVELOPER_SUDO_MNEMONIC=## a mnemonic that has `sudo` privileges for a chain. Defaults to `//Alice` ##
DEVELOPER_UTILS=## set to `true` to enable developer testing endpoints ##
DEVELOPER_UTILS=## set to `true` to enable developer testing endpoints. Never enable this in production ##

# Vault Signer:
VAULT_URL=## The URL of a Vault transit engine##
Expand All @@ -93,6 +94,8 @@ NOTIFICATIONS_LEGITIMACY_SECRET=## A secret used to create HMAC signatures ##
# Auth:
AUTH_STRATEGY=## list of comma separated auth strategies to use e.g. (`apiKey,open`) ##
API_KEYS=## list of comma separated api keys to initialize the `apiKey` strategy with ##
AUTH_MANAGEMENT_ENABLED=## `true` or `false` to expose the user and API key management endpoints (`/users/create`, `/auth/api-key/*`). Defaults to `false` when `AUTH_STRATEGY` includes `open`, otherwise `true` ##
SIGNER_MANAGEMENT_ENABLED=## `true` or `false` to expose the endpoint that adds signing keys (`POST /signer`). Defaults to `false` when `AUTH_STRATEGY` includes `open`, otherwise `true` ##
# Datastore:
REST_POSTGRES_HOST=## Domain or IP of DB instance ##
REST_POSTGRES_PORT=## Port the DB is listening (usually 5432) ##
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@
"@polymeshassociation/fireblocks-signing-manager": "^3.0.0",
"@polymeshassociation/hashicorp-vault-signing-manager": "^4.1.0",
"@polymeshassociation/local-signing-manager": "^4.1.1",
"@polymeshassociation/polymesh-sdk": "v30.0.0",
"@polymeshassociation/polymesh-types": "^7.4.0",
"@polymeshassociation/polymesh-sdk": "31.1.0-beta.8",
"@polymeshassociation/polymesh-types": "^7.5.0",
"@polymeshassociation/signing-manager-types": "^3.7.1",
"class-transformer": "0.5.1",
"class-validator": "^0.14.0",
Expand Down
2 changes: 2 additions & 0 deletions src/accounts/accounts.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ describe('AccountsController', () => {
free: new BigNumber(10),
locked: new BigNumber(1),
total: new BigNumber(11),
reserved: new BigNumber(1),
frozen: new BigNumber(0),
};
mockAccountsService.getAccountBalance.mockResolvedValue(mockResult);

Expand Down
19 changes: 9 additions & 10 deletions src/accounts/accounts.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { ModifyPermissionsDto } from '~/accounts/dto/modify-permissions.dto';
import { RevokePermissionsDto } from '~/accounts/dto/revoke-permissions.dto';
import { TransactionHistoryFiltersDto } from '~/accounts/dto/transaction-history-filters.dto';
import { TransferPolyxDto } from '~/accounts/dto/transfer-polyx.dto';
import { AccountBalanceModel } from '~/accounts/models/account-balance.model';
import {
AccountCollectionModel,
createAccountCollectionModel,
Expand All @@ -36,7 +37,6 @@ import { AccountDetailsModel } from '~/accounts/models/account-details.model';
import { MultiSigDetailsModel } from '~/accounts/models/multi-sig-details.model';
import { PermissionsModel } from '~/accounts/models/permissions.model';
import { AssetBalanceModel } from '~/assets/models/asset-balance.model';
import { BalanceModel } from '~/assets/models/balance.model';
import { ApiArrayResponse, ApiTransactionResponse } from '~/common/decorators/';
import { TransactionBaseDto } from '~/common/dto/transaction-base-dto';
import { ExtrinsicModel } from '~/common/models/extrinsic.model';
Expand Down Expand Up @@ -106,7 +106,8 @@ export class AccountsController {

@ApiOperation({
summary: 'Get POLYX balance of an Account',
description: 'This endpoint provides the free, locked and total POLYX balance of an Account',
description:
'This endpoint provides the free, locked and total POLYX balance of an Account. Note: as of chain v8, `locked` also includes funds on hold (e.g. bonded for staking), which is reflected in the `reserved` and `frozen` fields',
})
@ApiParam({
name: 'account',
Expand All @@ -116,12 +117,12 @@ export class AccountsController {
})
@ApiOkResponse({
description: 'Free, locked and total POLYX balance of the Account',
type: BalanceModel,
type: AccountBalanceModel,
})
@Get(':account/balance')
async getAccountBalance(@Param() { account }: AccountParamsDto): Promise<BalanceModel> {
async getAccountBalance(@Param() { account }: AccountParamsDto): Promise<AccountBalanceModel> {
const accountBalance = await this.accountsService.getAccountBalance(account);
return new BalanceModel(accountBalance);
return new AccountBalanceModel(accountBalance);
}

@ApiOperation({
Expand All @@ -136,7 +137,6 @@ export class AccountsController {
description:
'<ul>' +
"<li>The destination Account doesn't have an associated Identity</li>" +
'<li>The receiver Identity has an invalid CDD claim</li>' +
'<li>Insufficient free balance</li>' +
'</ul>',
})
Expand Down Expand Up @@ -232,8 +232,7 @@ export class AccountsController {

@ApiOperation({
summary: 'Get pending subsidies for an Account',
description:
'Returns pending subsidy requests awaiting acceptance by the beneficiary account on chain v8',
description: 'Returns pending subsidy requests awaiting acceptance by the beneficiary account',
})
@ApiParam({
name: 'account',
Expand All @@ -258,7 +257,7 @@ export class AccountsController {

@ApiOperation({
summary: 'Get fungible asset balances held by an Account',
description: 'Returns balances of all fungible assets held in the Account on chain v8',
description: 'Returns balances of all fungible assets held in the Account',
})
@ApiParam({
name: 'account',
Expand Down Expand Up @@ -291,7 +290,7 @@ export class AccountsController {

@ApiOperation({
summary: 'Get NFT collections held by an Account',
description: 'Returns NFT collections and holdings for the Account on chain v8',
description: 'Returns NFT collections and holdings for the Account',
})
@ApiParam({
name: 'account',
Expand Down
32 changes: 32 additions & 0 deletions src/accounts/models/account-balance.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/* istanbul ignore file */

import { ApiProperty } from '@nestjs/swagger';
import { BigNumber } from '@polymeshassociation/polymesh-sdk';

import { BalanceModel } from '~/assets/models/balance.model';
import { FromBigNumber } from '~/common/decorators/transformation';

export class AccountBalanceModel extends BalanceModel {
@ApiProperty({
type: 'string',
description:
'POLYX placed on hold by the protocol, e.g. bonded for staking. Not part of `free` and cannot be spent until released',
example: '100',
})
@FromBigNumber()
readonly reserved: BigNumber;

@ApiProperty({
type: 'string',
description:
'Minimum balance (out of `total`) that must remain in the Account due to freezes/locks (e.g. vesting). May overlap with `reserved`',
example: '0',
})
@FromBigNumber()
readonly frozen: BigNumber;

constructor(model: AccountBalanceModel) {
super(model);
Object.assign(this, model);
}
}
9 changes: 7 additions & 2 deletions src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { PolymeshModule } from '~/polymesh/polymesh.module';
import { PortfoliosModule } from '~/portfolios/portfolios.module';
import { ScheduleModule } from '~/schedule/schedule.module';
import { SettlementsModule } from '~/settlements/settlements.module';
import { SignerManagementModule } from '~/signing/signer-management.module';
import { SigningModule } from '~/signing/signing.module';
import { SubscriptionsModule } from '~/subscriptions/subscriptions.module';
import { SubsidyModule } from '~/subsidy/subsidy.module';
Expand All @@ -47,6 +48,7 @@ import { UsersModule } from '~/users/users.module';
POLYMESH_NODE_URL: Joi.string().required(),
POLYMESH_MIDDLEWARE_URL: Joi.string(),
POLYMESH_MIDDLEWARE_API_KEY: Joi.string(),
POLYMESH_MIDDLEWARE_V2_API_KEY: Joi.string(),
SUBSCRIPTIONS_TTL: Joi.number().default(60000),
SUBSCRIPTIONS_MAX_HANDSHAKE_TRIES: Joi.number().default(5),
SUBSCRIPTIONS_HANDSHAKE_RETRY_INTERVAL: Joi.number().default(5000),
Expand All @@ -59,6 +61,8 @@ import { UsersModule } from '~/users/users.module';
VAULT_URL: Joi.string().allow(''),
DEVELOPER_SUDO_MNEMONIC: Joi.string().default('//Alice'),
DEVELOPER_UTILS: Joi.bool().default(false),
AUTH_MANAGEMENT_ENABLED: Joi.bool(),
SIGNER_MANAGEMENT_ENABLED: Joi.bool(),
API_KEYS: Joi.string().default(''),
AUTH_STRATEGY: Joi.string().default(() => {
if (process.env.NODE_ENV === 'production') {
Expand Down Expand Up @@ -97,8 +101,9 @@ import { UsersModule } from '~/users/users.module';
NotificationsModule,
ScheduleModule,
NetworkModule,
AuthModule,
UsersModule,
AuthModule.register(),
UsersModule.register(),
SignerManagementModule.register(),
DeveloperTestingModule.register(),
MetadataModule,
SubsidyModule,
Expand Down
45 changes: 44 additions & 1 deletion src/assets/assets.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { AssetsService } from '~/assets/assets.service';
import { AssetDocumentDto } from '~/assets/dto/asset-document.dto';
import { SetStatsDto } from '~/assets/dto/transfer-restrictions/set-stats.dto';
import { SetTransferRestrictionsDto } from '~/assets/dto/transfer-restrictions/set-transfer-restrictions.dto';
import { IssuedInFundingRoundModel } from '~/assets/models/issued-in-funding-round.model';
import { StatAccreditedClaimModel } from '~/assets/models/stat-claim-accredited.model';
import { StatAffiliateClaimModel } from '~/assets/models/stat-claim-affiliate.model';
import { StatJurisdictionClaimModel } from '~/assets/models/stat-claim-jurisdiction.model';
Expand All @@ -36,7 +37,7 @@ import { ProcessMode } from '~/common/types';
import { MetadataService } from '~/metadata/metadata.service';
import { PortfolioDto } from '~/portfolios/dto/portfolio.dto';
import { processedTxResult, testValues } from '~/test-utils/consts';
import { MockAsset, MockAuthorizationRequest } from '~/test-utils/mocks';
import { MockAsset, MockAuthorizationRequest, MockInstruction } from '~/test-utils/mocks';
import { MockAssetService, mockMetadataServiceProvider } from '~/test-utils/service-mocks';

const { signer, did, txResult, assetId } = testValues;
Expand Down Expand Up @@ -437,6 +438,33 @@ describe('AssetsController', () => {
expect(result).toEqual(processedTxResult);
expect(mockAssetsService.transferFunds).toHaveBeenCalledWith(body);
});

it('should return the pending Instruction when the transfer does not settle immediately', async () => {
const from = new AssetHolderDto({
type: AssetHolderType.account,
address: 'fromAddress',
});
const to = new AssetHolderDto({
type: AssetHolderType.account,
address: 'toAddress',
});
const amount = new BigNumber(100);
const body = { signer, from, to, asset: assetId, amount };

const mockInstruction = new MockInstruction();

mockAssetsService.transferFunds.mockResolvedValue({
...txResult,
result: mockInstruction,
});

const result = await controller.transferFunds(body);

expect(result).toEqual({
...processedTxResult,
instruction: mockInstruction,
});
});
});

describe('approveAllowance', () => {
Expand Down Expand Up @@ -519,6 +547,21 @@ describe('AssetsController', () => {
});
});

describe('getIssuedInFundingRound', () => {
it('should call the service and return the amount issued in the funding round', async () => {
mockAssetsService.getIssuedInFundingRound.mockResolvedValue(new BigNumber(1000));

const result = await controller.getIssuedInFundingRound({
asset: assetId,
round: 'Series A',
});

expect(result).toEqual(
new IssuedInFundingRoundModel({ fundingRound: 'Series A', issued: new BigNumber(1000) })
);
});
});

describe('addRequiredMediators', () => {
it('should call the service and return the results', async () => {
const mediators = ['someDid'];
Expand Down
Loading
Loading