@carbonx3/cx3.js

中文文档
0.3.8发布于 2026/11/01UNLICENSEDTypeScriptESM + CJS近 30 天下载 1966

Official Node.js SDK (CX3js) for CarbonX3 Green Chain: OAuth2 app access, local secp256k1 signing, ABI-bound contracts and token deploys over REST + JSON

原始使用文档
文档目录21 节
  1. Features
  2. Requirements
  3. Installation
  4. Create an application
  5. Quick start
  6. Authentication
  7. Reading chain data
  8. Contracts
  9. Transactions and fees
  10. Waiting for a transaction
  11. Deploying tokens
  12. Private tokens
  13. Signing
  14. Error handling
  15. Browser applications
  16. Environment variables
  17. TypeScript
  18. Upgrading from 0.2.x
  19. Upgrading from 0.1.x
  20. Documentation
  21. License

@carbonx3/cx3.js

npm version Node.js version Types

The official Node.js SDK (CX3js) for CarbonX3 Green Chain.

CX3js connects server-side applications to CarbonX3 Green Chain through the CX3Api gateway. It signs in as an OAuth2 application, signs transactions locally with secp256k1, binds contract ABIs to plain JavaScript methods and deploys the Green Chain token standards. Requests are REST + JSON with named parameters, not binary calldata.

Network status: CarbonX3 Green Chain currently runs as a development network (devnet; provider.getStatus() returns its current chain id). Devnet CX3, tokens and contracts are test data with no monetary value and may be cleared when the devnet is reset.

Features

  • OAuth2 application access: client credentials from CX3WebUI; the access token is requested, cached and renewed for you.
  • Local signing: private keys stay in your process, and every write carries the wallet's secp256k1 signature.
  • ABI-bound contracts: token.balanceOf(owner) and token.transfer(to, amount), with the ABI loaded from the chain.
  • Token deploys: CX30, CX31, CX300, CX315 and CX3192, plus the green standards CX337–CX341.
  • Private tokens: deploy them, and check authorized wallets before anything is signed.
  • Public chain reads: chain status, wallet tokens and activity, CarbonFi pools and CX3WASM interfaces (WIT).
  • TypeScript declarations included; works with both require and import.

Requirements

  • Node.js 20.19 or later (native fetch).
  • An application (client ID and client secret) created in CX3WebUI, as described below.

Installation

npm install @carbonx3/cx3.js

Create an application

  1. Open CX3WebUI → Settings → API → Applications and connect the wallet the application acts for.
  2. Name the application, choose its scopes and click Create App. The wallet signs the request.
  3. Store the client ID (cx3app_…) and the client secret (cx3sec_…, shown only once) in your server's environment.
ScopeGrants
readBalances, nonces, contracts, ABIs, events, transfers, transactions and read-only calls
writeWallet-signed contract sends and native CX3 transfers
deployContract deploys, plus pause, unpause and delete (owner-signed)

A wallet can hold up to five applications. Revoking an application on the same page stops its access tokens within a minute.

Quick start

const { Cx3Provider, Cx3Wallet } = require("@carbonx3/cx3.js");

const provider = new Cx3Provider("https://cx3api.carbonx3.com", {
  clientId: process.env.CX3_CLIENT_ID,         // cx3app_…
  clientSecret: process.env.CX3_CLIENT_SECRET  // cx3sec_…
});
const wallet = new Cx3Wallet(process.env.CX3_PRIVATE_KEY, provider);

async function main() {
  // Native CX3 balance
  console.log(await provider.getBalance(wallet.address));

  // Token contract; its ABI is loaded from the chain
  const token = await provider.getContract(process.env.CONTRACT_ADDRESS, null, wallet);
  console.log(await token.balanceOf(wallet.address));

  // Wallet-signed token transfer (BigInt or decimal string)
  const tx = await token.transfer("0xRecipient…", 10n ** 18n);
  await tx.wait();

  // Native CX3 transfer
  await wallet.transferNative("0xRecipient…", 1.5);
}

main().catch((err) => {
  console.error(err.code || err.name, err.message);
  process.exitCode = 1;
});

ES modules work too: import { Cx3Provider, Cx3Wallet } from "@carbonx3/cx3.js";. The remaining examples assume an async context.

Authentication

OAuth2 application (recommended). The provider exchanges clientId and clientSecret at POST /oauth/token (client credentials grant) for a one-hour access token and sends it as Authorization: Bearer …. It renews the token a minute before it expires and, if the gateway answers invalid_token, fetches a fresh one and retries once. The secret is sent only to the token endpoint. scope: ["read"] requests fewer scopes than the application has. When the options are omitted, the provider reads CX3_CLIENT_ID and CX3_CLIENT_SECRET.

The access token identifies the application; every write is still signed by the wallet. A route outside the application's scopes answers 403 insufficient_scope, and routes reserved for CarbonX3's own services answer 403 ROUTE_NOT_ALLOWED. Each application has its own rate limit (300 requests per minute by default, 429 RATE_LIMITED).

provider.bindApiKey(address) (or wallet.bindApiKey()) confirms that a wallet is the one the application belongs to: { ok: true }, otherwise wallet_mismatch.

Wallet API key (legacy). A cx3wk_… key from the same settings page still works while applications move to OAuth2. Pass it as apiKey (or set CX3_API_KEY); it is sent as X-Api-Key and carries every scope of its wallet.

const provider = new Cx3Provider("https://cx3api.carbonx3.com", { apiKey: process.env.CX3_API_KEY });

Development node. new Cx3Provider(devNodeUrl, { devNode: true }) talks to a development node that runs without authentication (its /api/v1 routes).

Credentials are required. Without an application, an API key or devNode: true, the constructor throws CREDENTIALS_REQUIRED.

No HTTP Basic. HTTP Basic is the internal login between Green Chain nodes and CarbonX3 services, not an application credential, so the SDK never sends it: the auth option throws BASIC_AUTH_UNSUPPORTED, and headers cannot carry Authorization or X-Api-Key.

Reading chain data

Public chain data, served by the gateway to every application (a development node answers DAPP_ONLY):

const status = await provider.getStatus();                               // { height, hash, timestamp, chainId, genesisHash }
const { tokens } = await provider.getTokens(wallet.address);             // every token with this wallet's balance
const page = await provider.getActivity(wallet.address, { limit: 20 });  // newest first, no block rewards
const older = await provider.getActivity(wallet.address, { before: page.nextBefore });
const { pairs } = await provider.getDexPairs();                          // CarbonFi pools
const lp = await provider.getLpBalance(pairs[0].id, wallet.address);     // LP balance in one pool
const { wit } = await provider.getWit(contractAddress);                  // CX3WASM interface (WIT)

With the read scope:

MethodReturns
getBalance(address), getBalanceAndNonce(address), getNonce(address), getPendingNonce(address)Native CX3 balance and nonces
getContractInfo(address), getAbi(address), listContracts({ limit, offset, owner, standard, type, live })Contract registry entries and ABIs
call(address, method, params)A read-only call; the result is in value
getEvents(address, { type, limit, offset }), getTransfers(address, { limit, offset }), getHolders(address, { limit, offset })Contract history
getAddressTransactions(address, limit), getTxStatus(txHash)Transactions
getRequest(requestId, from)What became of a signed contract send (public; see Waiting for a transaction)

Contracts

const token = await provider.getContract(address, null, wallet);  // ABI from the chain

await token.balanceOf(owner);                        // view → read-only call
await token.call("balanceOf", { address: owner });   // named parameters (CX30)
const tx = await token.transfer(to, amount);         // transaction → wallet-signed send
await tx.wait();

Supported ABI shapes:

  1. Standard interface JSON: { methods: { balanceOf: { type: "view", inputs: [...] } } }
  2. Compiler flat ABI: [{ type: "function", name, inputs, stateMutability }]
  3. CX3WASM compiler ABI: { functions: [{ name, params, view }] }

provider.getContract(address) loads the ABI the chain calls the contract with: shape 2 for contracts compiled to CX3JS, shape 3 for CX3WASM. Built-in chain standards (ABI inputs ["params"]) take one named object: await lp.transfer({ to, amount }). A contract handle created with a provider instead of a wallet can only read; its writes throw NO_SIGNER.

Transactions and fees

Gas is paid in CX3 to the validator that produces the block: gas used × gas price (devnet default 0.0001 CX3 per gas).

  • A native transfer uses 21,000 gas: 2.1 CX3. The sender needs the amount plus the fee.
  • A contract send or deploy through the gateway is relayed by the chain's own deploy account: the call still runs as your wallet (the chain checks your signature again), and your wallet pays no gas for it. The call's gas limit (100,000 by default) bounds the node's trial run when the call is sent (a call that needs more is refused); in the block the VM runs the call under its own per-call limit.

Too little balance for a native transfer fails with INSUFFICIENT_BALANCE. A native transfer's gas limit and gas price are 0 (the chain's defaults: 21,000 and 0.0001) or 21,000–100,000 and at least the chain floor of 0.0001 (GAS_LIMIT_TOO_LOW, GAS_LIMIT_TOO_HIGH, GAS_PRICE_TOO_LOW), and its amount is greater than 0 with at most 6 decimal places, because CX3 has 6. transferNative checks the amount before signing (INVALID_AMOUNT, INVALID_AMOUNT_PRECISION).

Waiting for a transaction

A contract send waits for its block by default (the node waits up to 25 seconds); with { wait: 0 } it returns at once. tx.wait() polls the transaction status until it is confirmed or failed for good:

  • Pending is not failed. tx.status is "pending" for { wait: 0 }, for a queued send (HTTP 202 queued: true, code TX_TIMEOUT: not in a block within the node's wait) and for a dropped send. tx.success stays true, and tx.wait() follows the transaction. A queued or dropped deploy is status: "pending" too: the token factory gives result.contract only after result.wait() sees it confirmed.
  • Dropped sends. A send the node trimmed from its block comes back as HTTP 202 with tx.dropped === true and tx.finalAfter (ms epoch, about 12 minutes after the signed timestamp). Until then another validator may still include it, so by default tx.wait() keeps waiting until finalAfter plus 30 seconds. After that the status says failed, and tx.wait() rejects with TX_FAILED.
  • Only a final failure rejects with TX_FAILED. TX_TRACK_TIMEOUT (your own timeoutMs, otherwise 60 seconds or until finalAfter for a dropped send) means the transaction is still pending: its receipt has status: "pending", dropped and finalAfter. A network error, a 429 or a 5xx answer while polling does not end the wait.
  • Never sign the same call again while it can still run. A new signature is a second call. Every send answer carries tx.requestId, and provider.getRequest(tx.requestId, wallet.address) tells what became of it: executed, pending, submitted or unknown.
const tx = await token.transfer(to, amount);
if (tx.dropped) console.log("Not in a block yet; final after", new Date(tx.finalAfter));
const receipt = await tx.wait();  // confirmed, or throws TX_FAILED / TX_TRACK_TIMEOUT

Deploying tokens

const { Cx3TokenFactory } = require("@carbonx3/cx3.js");

const factory = new Cx3TokenFactory(wallet);  // the application needs the deploy scope
const result = await factory.deployCX30({ name: "My Token", symbol: "MTK", initialSupply: "0" });
console.log(result.contractAddress);          // result.contract is a ready Contract handle
HelperStandard
deployCX30Fungible token
deployCX31Unit / accounting token
deployCX300Real-world asset
deployCX315Loyalty
deployCX3192Soulbound identity
deployCX337 … deployCX341Green standards: CBAM proof, sustainability report, supply chain, materiality assessment, green claims

A deploy the node trimmed from its block answers HTTP 202 with result.dropped === true, status: "pending", finalAfter and, when known, its contractAddress (otherwise null): it is not deployed yet, so result.contract is not set. await result.wait() follows it as tx.wait() follows a send: once it is confirmed, status becomes "confirmed" and the factory adds result.contract (and result.authorized for a private token); after finalAfter it rejects with TX_FAILED. Do not deploy the same contract again before then. wallet.deployContract() results have the same dropped, finalAfter and wait().

The helpers deploy to CX3WASM by default. The chain renders CX30 and CX337–CX341 from the fields; CX31, CX300, CX315 and CX3192 take their CX3Script source as sourceCode (otherwise the chain answers STANDARD_NOT_WASM_DEPLOYABLE).

Token symbols are capital letters A-Z and digits 0-9 only. The SDK checks this before signing (INVALID_TOKEN_SYMBOL; see isValidTokenSymbol), and the chain keeps one contract per symbol (409 DUPLICATE_TOKEN_SYMBOL).

Private tokens

A private token is sent, received and seen only by its owner and its authorized wallets. The token contract enforces this on the chain, and the SDK follows the same rules:

  • Lists: getTokens shows a private token only to its owner, its authorized wallets and its holders, and only with that wallet's own signed proof (header X-CX3-Wallet-Proof, since 2026-10-19; CX3Wallet sends it for its own lists), so an application gets the public tokens; listContracts() shows it only when the application's wallet is its owner or an authorized wallet. Its holders and raw state answer 403 PRIVATE_TOKEN to every application except the owner's.
  • Sends: before a transfer, transferFrom or mint of a private token, the wallet asks the token whether both ends are authorized. If one is not, it throws PRIVATE_TOKEN_SENDER or PRIVATE_TOKEN_RECIPIENT instead of signing a send the block would refuse. { privateCheck: false } skips the check.
  • Checks: provider.isPrivateToken(token) and provider.isTokenAuthorized(token, wallet), which returns true for the owner or a listed wallet, false otherwise and null when the node does not answer.
const res = await factory.deployCX30({
  name: "Team Credit",
  symbol: "TEAMC",
  initialSupply: "1000000",
  visibility: "private",
  authorizedWallets: ["0x…", "0x…"]  // up to 200; one signed addToWhitelist each after the deploy
});
// res.authorized → [{ wallet, success, txHash }]
await res.contract.addToWhitelist("0xNew…", "");  // later, owner only; removeFromWhitelist removes one

A private CX31, CX300, CX315 or CX3192 takes its CX3WebUI source as sourceCode; the green standards have no private variant. Private keeps a token out of other wallets' lists and hands, but it does not make it secret: blocks, transfers and contract calls stay readable on the chain, as on CX3Scan.

Signing

Digests are UTF-8 strings, hashed with SHA-256 and signed with secp256k1, byte-identical to what the Green Chain node verifies. Each kind starts with its own prefix (signing domains), so a signature of one kind never verifies as another; chainId and genesisHash are the network's (provider.getChainIdentity(), fetched by the wallet methods themselves) and addresses are lower-case 0x + 40 hex:

  • Deploy: CX3_DEPLOY_V1| + JSON.stringify([chainId, genesisHash, owner, standard, name, symbol, decimals, initialSupply, timestamp])
  • Contract send: CX3_CALL_V1| + JSON.stringify([chainId, genesisHash, contractAddress, method, params, fromAddress, timestamp]). The chain checks this signature again inside the relayed transaction: a call runs as your wallet only with it.
  • Owner action: CX3_OWNER_V1| + JSON.stringify([chainId, genesisHash, action, address, owner, timestamp])
  • Site message: 0x19 + "CX3 Signed Message:\n" + UTF-8 length + message (signPersonalMessage), the way the CX3Wallet extension and app sign cx3_sign_message
  • Native transfer: JSON.stringify({ type, fromAddress, toAddress, amount, nonce, timestamp, data, gasLimit, gasPrice, chainId, genesisHash }), with 40-hex addresses and no 0x; chainId and genesisHash (0x + 64 hex) are the network's, from provider.getChainIdentity()

BigInt values in params are converted to decimal strings before signing, and exactly those params are sent. Deploy, contract-send and owner signatures carry a timestamp and are accepted within ±5 minutes of the gateway's clock. A native transfer is admitted only within 60 seconds after its timestamp (the pool drops it after 60 seconds), and a block takes it only within 10 minutes of its timestamp. The genesis hash ties a native transfer to one network and one genesis: after a chain reset, a transfer signed before it no longer verifies. An address is 0x + the last 20 bytes of sha256(uncompressedPublicKey).

The same building blocks are exported: buildDeployDigest, buildSendDigest, buildOwnerActionDigest (each takes the identity as its last argument), buildTransferPayload, personalMessageBytes, signMessage (signs a digest as given), signPersonalMessage, privateKeyToAddress and createPrivateKey.

Error handling

Every error is a Cx3Error with a code. Cx3HttpError adds the HTTP status and the response body (with requestId for a contract send); Cx3TxError adds the transaction receipt. A send or native transfer that the node refuses with HTTP 200 returns tx.success === false with the code in tx.error, and tx.wait() rejects with a Cx3TxError of that code.

const { Cx3HttpError } = require("@carbonx3/cx3.js");

try {
  await token.transfer(to, amount);
} catch (err) {
  if (err instanceof Cx3HttpError && err.status === 403) console.error("Not allowed:", err.code);
  else throw err;
}
CodeMeaning
CREDENTIALS_REQUIREDNo application, API key or devNode was given
INVALID_CLIENT_CREDENTIALSThe client ID or secret is not in the cx3app_… / cx3sec_… format
invalid_clientThe token endpoint refused the application (wrong secret, or revoked)
insufficient_scope, ROUTE_NOT_ALLOWEDThe route needs another scope, or is not open to applications (403)
RATE_LIMITEDThe application's rate limit was reached (429)
INSUFFICIENT_BALANCE, GAS_PRICE_TOO_LOWA native transfer's sender cannot pay the amount plus gas, or the gas price is below the chain floor
INVALID_AMOUNT, INVALID_AMOUNT_PRECISIONA native amount is not greater than 0, or has more than 6 decimal places (CX3 has 6); transferNative checks both before signing
GAS_LIMIT_TOO_LOW, GAS_LIMIT_TOO_HIGHA native transfer's gas limit is outside 21,000–100,000
GAS_LIMIT_EXCEEDEDA contract send's gasLimit is above the chain's maximum (400, max in the body)
INVALID_NONCE_ORDERA native transfer's nonce is not the next one (expectedNonce in the answer)
DATA_TOO_LARGE, DATA_TOO_DEEPA transaction's data is too large or nested too deeply
INVALID_TOKEN_SYMBOL, DUPLICATE_TOKEN_SYMBOLThe symbol is not A-Z / 0-9, or is already in use (409)
PRIVATE_TOKEN_SENDER, PRIVATE_TOKEN_RECIPIENT, PRIVATE_TOKENPrivate token rules (see above)
TX_TIMEOUTThe node's 25-second wait ended before a block: queued, not final. A send answer with this code is pending and tx.wait() follows it; without a tx hash the outcome is open (look up its requestId)
TX_FAILEDThe transaction failed for good (a dropped send only after its finalAfter)
TX_TRACK_TIMEOUTtx.wait() stopped while the transaction was still pending: not a failure, do not sign it again
REPLAYED_REQUESTThis signed request was already received or already ran (409); the answer does not say how it ended. Look it up with provider.getRequest(err.body.requestId, wallet.address) and never sign it again
REQUEST_LOOKUP_UNAVAILABLEgetRequest could not ask the chain (503): try again later, do not sign again
LEDGER_UNAVAILABLE, LEDGER_FULLThe gateway's /dapp/v1 send could not record the request (503): it was not sent, and the same signed body may be sent again later, without a new signature
BASIC_AUTH_UNSUPPORTEDThe auth option was used
NO_CHAIN_IDENTITYThe network's chain id or genesis hash is unavailable; a native transfer is not signed without them

Browser applications

This package is for Node.js servers. Web pages use the CX3 IIFE (window.cx3) together with the CX3Wallet extension: @carbonx3/cx3-iife on npm and jsDelivr, or download it; see the CX3 IIFE guide. Keep the application's credentials on your server behind a same-origin proxy, and never put a client secret, an API key or a private key in browser JavaScript.

Environment variables

VariableUsed for
CX3_CLIENT_ID, CX3_CLIENT_SECRETApplication credentials when the options are omitted
CX3_API_KEYLegacy wallet API key
CX3_NODE_URLGateway URL for getDefaultProvider()

TypeScript

Type declarations (index.d.ts) ship with the package.

Upgrading from 0.2.x

Version 0.3.0 signs native transfers for the network's new signing format (0.3.1 to 0.3.4 change only this README, the file headers and comments, not the code). In 0.3.5, bindApiKey with a wallet API key asks the gateway's /wallet/api-key/bind (before, it asked /whoami, which the gateway refuses, so it always failed). In 0.3.6, wait() follows a send or deploy the gateway reports as not final (dropped) until its finalAfter instead of failing after 60 s, a TX_TIMEOUT answer with a hash is pending, getRequest() looks up a signed request, and transferNative refuses more than 6 decimals. 0.3.7 changes only this README: a native transfer's gas price floor is 0.0001 (the 0.3.6 README said 0.000001). 0.3.8 changes only this README too: the CX3 IIFE download link names cx3-0.2.9.min.js.

  • The genesis hash is signed. transferNative asks the network for its chain id and genesis hash (provider.getChainIdentity()) unless both are given as opts.chainId and opts.genesisHash. 0.2.x transfers no longer verify on the chain.
  • No fallback chain id. buildTransferPayload throws NO_CHAIN_IDENTITY without chainId and genesisHash; the CX3_CHAIN_ID variable and the chainIdBind option are gone.
  • Typed digests. Contract sends, deploys and owner actions sign CX3_CALL_V1 / CX3_DEPLOY_V1 / CX3_OWNER_V1 digests bound to the network (see Signing); the |-joined digests of 0.2.x are refused. signSend, signDeploy and signOwnerAction take an optional { chainId, genesisHash }, else they ask the provider (kept for a minute). The build*Digest helpers need the identity as their last argument.
  • Site messages. signPersonalMessage signs the way the wallets sign cx3_sign_message (with the 0x19 prefix); signMessage still signs a digest as it is.

Upgrading from 0.1.x

CX3js is in beta (0.x), so a minor version may contain breaking changes. Version 0.2.0:

  • Credentials are required. The provider talks to the CX3Api gateway as an OAuth2 application (or with a wallet API key) and throws CREDENTIALS_REQUIRED without one. A development node without authentication needs devNode: true; 0.1.x used node routes under /api/v1 by default.
  • HTTP Basic was removed. The auth option throws BASIC_AUTH_UNSUPPORTED, and headers cannot carry Authorization or X-Api-Key.
  • Node.js 20.19 or later is required (@noble/secp256k1 3.x).
  • Fixes: BigInt params are signed and sent as decimal strings, and transferNative sends the gas fields it signs.
  • New: public chain reads (getStatus, getTokens, getActivity, getDexPairs, getLpBalance, getWit), private tokens and isValidTokenSymbol.

Documentation

License

UNLICENSED: proprietary software of Mersolution Technology Ltd. Use is subject to the CX3Script Terms of Use (item 03): you may install it and ship it unmodified with applications, including commercial ones, that connect to CarbonX3 Green Chain; you may not modify it, sell or republish it on its own, or use it with other networks.

版本与发布记录

@carbonx3/cx3.js 共发布 12 个版本,首次发布于 2026/09/06;过去 12 个月发布了 12 个版本。

版本发布日期距今
0.3.8latest2026/11/018 天前
0.3.72026/11/018 天前
0.3.62026/10/2911 天前
0.3.52026/10/2713 天前
0.3.42026/10/2416 天前
0.3.32026/10/1723 天前

关于 @carbonx3/cx3.js 的常见问题

@carbonx3/cx3.js 是什么?

Official Node.js SDK (CX3js) for CarbonX3 Green Chain: OAuth2 app access, local secp256k1 signing, ABI-bound contracts and token deploys over REST + JSON

如何安装 @carbonx3/cx3.js?

在项目目录运行 npm install @carbonx3/cx3.js,或使用 pnpm add / yarn add / bun add。

@carbonx3/cx3.js 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@carbonx3/cx3.js 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@carbonx3/cx3.js 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=20.19.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@carbonx3/cx3.js 可以商用吗?

@carbonx3/cx3.js 使用 UNLICENSED 许可。作者声明为 UNLICENSED,即不授予公开使用许可(与公共领域的 Unlicense 不同)。使用前需获得作者授权。以上为概括,不构成法律意见,请以许可原文为准。

@carbonx3/cx3.js 的下载量有多少?

根据 npm 官方下载统计,@carbonx3/cx3.js 在 2026/10/09 – 2026/11/07 这 30 天内被下载 1,966 次,日均约 66 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@carbonx3/cx3.js 还在维护吗?

最新版本 0.3.8 发布于 2026/11/01(8 天前);至今共发布 12 个版本,其中过去 12 个月发布了 12 个;首次发布于 2026/09/06。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。