1.17.x Upgrade and Migration Guide


Overview#

With the forthcoming Blocklet Server v1.17.x release, we have implemented optimizations across various security scenarios for Blocklet Server. However, these enhancements also bring with them some breaking changes:

  • Removed some Blocklet environment variables (BLOCKLET_APP_SK, BLOCKLET_APP_PSK).
  • @blocklet/sdk's getWallet() returned object removed the wallet.secretKey / wallet.sk properties;
  • Most signing and verification functions in @blocklet/sdk and @ocap/wallet have transitioned from synchronous to asynchronous.
  • The export methods for several packages have shifted from CommonJS default exports to ES Module named exports.
  • In the following scenarios, the signing/verification private key has been changed from the original BLOCKLET_APP_SK to BLOCKLET_APP_ASK: cross-Component calls, Service API calls, Server API calls, Notifications, or WebSocket communications.
  • Some environment variables have been added:
    • BLOCKLET_APP_ASK: A new environment variable, designed for signing in the following scenarios: Cross-Component calls / Service API or Server API invocations / Notification or WebSocket communications
    • BLOCKLET_APP_PPK: The original BLOCKLET_APP_PSK's public key

This guide breaks down these changes into distinct migration steps, illustrative replacement examples, and critical regression checks, streamlining gradual modifications and comprehensive manual verification.

Before making modifications, please ensure that all related dependencies have been upgraded to the latest version (version: 1.17.1).

This document can be copied to AI for migration and modification.

List of Major Breaking Changes#

1. Environment Variable Changes#

Changes: BLOCKLET_APP_SK and BLOCKLET_APP_PSK have been removed. Any code relying on these private keys must now sign via the wallet interface of @blocklet/sdk or export the public key for verification.

Migration Highlights:

  • If your code directly signs using a private key, refactor it to use `getWallet()` to retrieve the wallet and utilize its asynchronous signing method (refer to the example).
  • To derive a sub-wallet using a private key, use `await getWallet().deriveWallet()`.
  • For verification purposes only, use the public key `getWallet().publicKey` or `process.env.BLOCKLET_APP_PK` as the replacement.

SK Signature:

Old code:

const wallet = fromSecretKey(process.env.BLOCKLET_APP_SK);
const signature = wallet.sign();

New Code:

const { getWallet } = require('@blocklet/sdk/lib/wallet');
const wallet = await getWallet();
const signature = await wallet.sign();

JWT Signature:

Old:

const jwtToken = jwt.sign(payload, process.env.BLOCKLET_APP_SK);

New:

const { getWallet } = require('@blocklet/sdk/lib/wallet');
const wallet = await getWallet();
const jwtToken = await wallet.signJWT(payload);

SK Verification:

 Previous:

const wallet = fromSecretKey(process.env.BLOCKLET_APP_SK);
const isValid = wallet.verify(data);

New:

const wallet = fromSecretKey(process.env.BLOCKLET_APP_PK);
const isValid = wallet.verify(data);

fromAppDid:

Old:

// BLOCKLET_APP_SK
const wallet = fromAppDid(address, process.env.BLOCKLET_APP_SK, { role: types.RoleType.ROLE_ACCOUNT }, x)

// BLOCKLET_APP_PSK
const wallet = fromAppDid(address, process.env.BLOCKLET_APP_PSK, { role: types.RoleType.ROLE_ACCOUNT }, x)

New:

const { deriveWallet } = require('@blocklet/sdk/lib/wallet');

// BLOCKLET_APP_SK
const wallet = await deriveWallet(address, { role: types.RoleType.ROLE_ACCOUNT }, x);

// BLOCKLET_APP_PSK
const wallet = await deriveWallet(address, { role: types.RoleType.ROLE_ACCOUNT }, x, 'psk');

2. Changes to the getWallet() return object#

Changes: Removed properties such as wallet.secretKey / wallet.sk; all signing/verification methods are now asynchronous.

Key Migration Points:

  • All calls to `wallet.sign()`, `wallet.verify()`, etc., must be `await`ed.
  • If your code relies on `wallet.secretKey` for signing or derivation, please consult the modifications detailed under 1. Environment Variable Changes.

Example:

Old:

const { getWallet } = require('@blocklet/sdk/lib/wallet');

const wallet = getWallet();
const sig = wallet.sign(data);

console.log(wallet.secretKey) // sk....

New:

const { getWallet } = require('@blocklet/sdk/lib/wallet');

const wallet = await getWallet();
const sig = await wallet.sign(data);

console.log(wallet.secretKey) // undefined 

3. Multiple Exports Are Now Named (ESM) Exports#

Changes: Some packages have been changed from default or CommonJS-style exports to named exports. The import syntax requires adjustment.

Common Replacement Patterns:

- import parse from '@blocklet/meta/lib/parse';
+ import { parse } from '@blocklet/meta/lib/parse';

- const env = require('@blocklet/sdk/lib/env');
+ const { env } = require('@blocklet/sdk/lib/env');

Affected Packages and Common Exports (Please search and replace globally in the codebase):

  • @blocklet/sdk: BlockletAuthenticator, createConnectHandlers, Database, authMiddleware, fallback, sessionMiddleware, sitemap, userMiddleware, WalletAuthenticator, WalletHandlers, getWallet, getPkWallet, createRemoteWallet, deriveWallet, getPermanentWallet, getEthereumWallet, getAccessWallet, env, BlockletService, etc.
  • @blocklet/meta:toBlockletDid, validateBlockletEntry, getComponentProcessId, hasReservedKey, getBlockletInfo, parseNavigation, parse, urlPathFriendly, verifyMultiSig, getApplicationWallet, checkBlockletEnvironment, env.
  • @blocklet/env: Previously, env was directly default-exported; it now requires destructuring from the blockletEnv or env named exports.

It is recommended to use your IDE's project-wide grep/replace functionality or make file-by-file adjustments, ensuring that import statements and their exported names remain consistent.

4. Synchronous -> Asynchronous: `await` and `async` are required at the call site.#

Changes: The following functions/methods are now asynchronous (only frequently used ones are listed). Please add `await` to the call sites and mark the functions containing these calls as `async`.

4.1 @blocklet/sdk

  • verify() / sign() / getSignData()(lib/util/verify-sign.ts)
  • signResponse() / verifyResponse() (in lib/security)
  • getDelegation() (lib/connect/shared.ts)
  • verifyBlockletSig()(lib/middlewares/blocklet.ts)
  • subscribe() / unsubscribe() (lib/service/eventbus.ts)
  • ensureClient() / on()(lib/service/notification.ts)

4.2 @ocap/wallet

All methods of wallet instances generated by the library (or returned by `getWallet()`) are now asynchronous:

  • wallet.sign()
  • wallet.verify()
  • wallet.ethSign()
  • wallet.ethVerify()

4.3 Other Libraries (Common)

  • @arcblock/jwt:jwt.sign(), jwt.verify(), jwt.signV2() now require await.
  • @blocklet/meta:signResponse(), verifyResponse() now require await.
  • @asset/vc:create(), verify(), verifyPresentation(), createCredentialList(), verifyCredentialList() – all return awaitable Promises.
  • @blocklet/js-sdk:verifyResponse() -> await.
  • @blocklet/store / @blocklet/cli: sign() now requires `await`.
  • @blocklet/server-js: _getAuthHeaders(), getAuthHeaders(), signWithAccessKey() -> await.

5. Key Considerations for Signing and Invocation Scenarios#

In the following scenarios (cross-component calls, service API invocations, notifications, or WebSockets), the signing private key, previously `process.env.BLOCKLET_APP_SK`, should now be the secretKey from the access wallet.

Migration Key Points:

  • To use @blocklet/sdk's getAccessWallet(), obtain the access wallet (which includes the signing key). The secretKey should then be explicitly passed within the options parameter of sign(), verify(), or getSignData(). Example:
import { sign, verify, getSignData } from '@blocklet/sdk/lib/util/verify-sign';
import { getAccessWallet } from '@blocklet/sdk/lib/wallet';

const accessWallet = await getAccessWallet();

// sign
const result = await axios({
  url: '/service/api',
  data: json,
  headers: {
    'x-app-id': accessWallet.address,
    'x-app-pk': accessWallet.publicKey,
    'x-component-sig': await sign(json, { appSk: accessWallet.secretKey }),
    'x-component-did': process.env.BLOCKLET_COMPONENT_DID
  },
});

// verify
const isValid = await verify(data, signature, { appSk: accessWallet.secretKey });

// getSignData
const signData = await getSignData(data, { appSk: accessWallet.secretKey });

Code Migration Checklist (Prioritized)#

  1. From the repository root, run the upgrade command and install dependencies. Create a separate branch.
  2. Perform a global search-and-replace operation to update import methods, transitioning from CommonJS default exports to named exports.
  3. A comprehensive search for usages such as `getWallet()`, `fromSecretKey()`, and `fromAppDid()`.
    • Usage of private key attributes (secretKey / sk) must be replaced with `getWallet()` for signing, or with `getAccessWallet()` combined with `appSk`.
    • Change all sign() / verify() / getSignData() and similar calls to use await.
  1. For calls spanning components, services, notifications, and WebSockets, ensure to use `getAccessWallet()` and pass `appSk` to the signing method.
  2. After modification, run TypeScript/Node builds and unit/integration tests to identify any missing synchronous calls or import errors.
  3. Manual file-by-file review: Pay special attention to code paths related to middleware, service, eventbus, notification, and connect.

Frequently Asked Questions and Troubleshooting Tips#

  • Bad secret key size
    • Reason: The dependencies were not upgraded to the latest version.
  • Various verification failures
    • Confirm that the signature utilizes the access wallet's secretKey.
  • Other components failed to start
    • Please upgrade all components in your application to the latest version, then perform a complete shutdown and restart.