@carbonx3/cx3-iife

中文文档
0.2.9发布于 2026/11/01UNLICENSEDCommonJS零依赖近 30 天下载 1721

Official browser script (window.cx3) for CarbonX3 Green Chain: CX3Wallet connect and signing (browser extension, or the phone app by QR code), reads through your own same-origin proxy

原始使用文档
文档目录15 节
  1. Features
  2. Installation
  3. From a CDN (recommended)
  4. Self-hosted
  5. With a bundler
  6. Requirements
  7. Quick start
  8. Your proxy (application credentials)
  9. API
  10. Sending
  11. Phone wallet (QR code)
  12. Private tokens
  13. Errors
  14. Documentation
  15. License

@carbonx3/cx3-iife

npm version jsDelivr

The official browser script (CX3 IIFE) for CarbonX3 Green Chain.

One <script> file adds window.cx3 (alias window.CX3) to a web page. The page connects the visitor's CX3 Wallet (the browser extension, or the phone app by QR code), has the wallet sign and send transactions, and reads chain data through a small proxy on your own server. There is no build step and no dependency.

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

For Node.js servers use the SDK instead: @carbonx3/cx3.js.

Features

  • Wallet connect and signing through the CX3Wallet extension: the private key never reaches the page.
  • Phone wallet by QR code: cx3.connectPhone() shows a QR code; the visitor scans it with the CX3 Wallet app and approves. From then on every send is approved and signed on the phone, and the key never leaves it. A built-in dialog is included, or draw the code in your own with cx3.qrSvg().
  • Reads through your same-origin proxy: balances, the token catalogue, transfers, a wallet's tokens and activity, CarbonFi pools, ABI and WIT.
  • Contract helper: cx3.contract(address).balanceOf(owner), .transfer(to, amount), .call(...), .send(...).
  • Private tokens: sender and recipient are checked before the wallet asks for approval.
  • Transaction tracking: tx.wait() polls until the transaction is confirmed or failed for good. A send that is pending (queued or dropped from a block) is followed until it is final, so the page never asks for a second signature.
  • Wallet events: cx3.on("accountsChanged", fn) tells the page when the user removes your site in the wallet, so it shows "Connect wallet" again without a reload.
  • No credentials in the browser: setRpc drops Authorization and X-Api-Key headers and has no login option.

Installation

Every version has its own fixed address. Load the exact version your page was tested with:

<script src="https://cdn.jsdelivr.net/npm/@carbonx3/[email protected]/min/cx3-0.2.9.min.js"
        integrity="sha384-erEAYMO5XonBabdhCYNtLWkrGwnht9Me0rKYwq0GlF31pNaOabEzWp645FAkQHtE"
        crossorigin="anonymous"></script>

The integrity value makes the browser refuse the file if a single byte differs. The same file is on unpkg: https://unpkg.com/@carbonx3/[email protected]/min/cx3-0.2.9.min.js.

Self-hosted

Download cx3-0.2.9.min.js, put it next to your page and load it:

<script src="cx3-0.2.9.min.js"></script>

With a bundler

npm install @carbonx3/cx3-iife
import "@carbonx3/cx3-iife"; // sets window.cx3

The file is a plain browser script: it assigns window.cx3 and exports nothing.

Requirements

  • A modern browser (fetch, Promise).
  • CX3Wallet 0.1.74 or later for connecting, signing and accountsChanged. The extension connects on every https site and on your own machine during development; the user approves each site once, and every send or signature asks again. Iframes get no wallet (cx3.wait() resolves false).
  • CX3 Wallet app (Android) 0.2.91 or later for the phone wallet (connectPhone).
  • A developer token (your application's OAuth2 credentials, held by your proxy). While it is valid, every wallet can connect to your page; with a proxy set by setRpc the wallet asks GET <proxy>/api/v1/whoami?address=… on your origin when it connects, and a missing, invalid or revoked token stops the connect.
  • For reads: a same-origin proxy on your server that holds your application's credentials (see below).

Quick start

<button type="button" id="connect">Connect wallet</button>
<p id="address"></p>

<script src="https://cdn.jsdelivr.net/npm/@carbonx3/[email protected]/min/cx3-0.2.9.min.js"
        integrity="sha384-erEAYMO5XonBabdhCYNtLWkrGwnht9Me0rKYwq0GlF31pNaOabEzWp645FAkQHtE"
        crossorigin="anonymous"></script>
<script>
  const cx3 = window.cx3;
  cx3.setRpc(location.origin + "/my-dapp", { apiPrefix: "/api/v1" }); // your proxy

  document.getElementById("connect").onclick = async () => {
    if (!(await cx3.wait(2500))) {
      document.getElementById("address").textContent = "CX3Wallet not found";
      return;
    }
    const me = await cx3.connect();
    document.getElementById("address").textContent = me;

    const balance = await cx3.getBalance(me);
    const page = await cx3.listContracts({ limit: 50 });
    console.log(balance, page.contracts);
  };

  // The user removed this site in CX3Wallet (Settings → Connected Sites → Remove)
  cx3.on("accountsChanged", (accounts) => {
    if (accounts.length === 0) document.getElementById("address").textContent = "";
  });
</script>

Your proxy (application credentials)

A page never holds credentials. Create an application in CX3WebUI → Settings → API → Applications and keep its client ID and client secret on your server. The proxy trades them at POST https://cx3api.carbonx3.com/oauth/token (grant_type=client_credentials) for a one-hour access token, caches it, and calls the gateway with Authorization: Bearer <token>. The page sees neither the secret nor the token.

The proxy answers under apiPrefix (default /api/v1) and forwards only the paths your page uses:

window.cx3 callProxy pathGateway route
getBalanceGET getBalanceAndNonce?address=GET /wallet/getBalanceAndNonce?address=
listContractsGET contracts?limit=&offset=GET /contracts/list
listTransfersGET contracts/{address}/transfersGET /contracts/:address/transfers
getAbi · getContractInfoGET contracts/{address}/abi · GET contract/{address}GET /contracts/:address/abi · GET /contracts/:address
call, balanceOfPOST contract/callPOST /contracts/:address/call
bindApiKeyGET whoami?address=GET /wallet/api-key/bind?address=
waitForTransaction · getStatusGET tx/{hash}/status · GET getLastBlockGET /tx/:hash/status · GET /getLastBlock
getWitGET contracts/{address}/witGET /dapp/v1/contracts/:address/wit
getTokens · getActivityGET accounts/{address}/tokens · /activityGET /dapp/v1/accounts/:address/tokens · /activity
getDexPairs · getLpBalanceGET dex/pairs · dex/pairs/{id}/lp/{address}GET /dapp/v1/dex/pairs · /dex/pairs/:id/lp/:address
connectPhonePOST link/pair · GET link/pair/{32 hex} · POST link/pair/{32 hex}/bindPOST /wallet-link/pair · GET /wallet-link/pair/:id · POST /wallet-link/pair/:id/bind
restorePhone, disconnect (phone)GET link · DELETE linkGET /wallet-link/link · DELETE /wallet-link/link
sends with the phonePOST link/sign · GET link/sign/{24 hex} · DELETE link/sign/{24 hex}POST /wallet-link/link/sign · GET / DELETE /wallet-link/link/sign/:id
a contract send the phone signedPOST contract-send/{address}POST /dapp/v1/contracts/:address/send

The gateway's contract send waits up to 25 seconds for the block, and the script gives contract-send 35 seconds (other calls 12 seconds), so give that path a proxy timeout above 25 seconds and pass on the gateway's status code and JSON body unchanged (202, 409, 503 and 504 carry requestId, txHash and hint). Both status routes (/tx/:hash/status, /dapp/v1/tx/:hash/status) answer a dropped send as pending with dropped and finalAfter.

Writes with the extension never go through the proxy: the wallet signs and sends them. With the phone, the app signs and the relay carries the request (see below). A complete PHP proxy example is in the CX3 IIFE guide.

API

window.cx3What it does
VERSIONThe script version, e.g. "0.2.9"
setRpc(url, { apiPrefix }) / getRpc()Set / read your proxy's base URL
wait(maxMs)Wait until CX3Wallet is ready (default 2500 ms); resolves true / false
isAvailable() / wallet()Is the wallet there / the injected window.carbonx3
connect() / accounts() / disconnect()Connect the extension (sets cx3.address), list accounts, drop the session (with the phone: ends its link)
connectPhone({ onQr, signal, texts })Pair the CX3 Wallet app by QR code (built-in dialog, or onQr(uri, expiresAt) for your own); resolves with the phone's address
restorePhone()After a reload: the phone linked earlier in this tab, if the link is still active; its address or ""
viaWhich wallet signs: "extension", "phone" or ""
qrSvg(text, { size, margin, dark, light, label }) / qrMatrix(text)A QR code as an <svg> element / as rows of booleans
on("accountsChanged", fn) / off("accountsChanged", fn)fn(accounts) gets [] when the user removes your site in the wallet (Connected Sites → Remove), in the extension or in the phone app; cx3.address is cleared. Show "Connect wallet" again without a reload. Needs CX3Wallet 0.1.69+
addressThe connected address (lower-case) or ""
request(method, params)Raw wallet request (cx3_*)
getBalance(address?)Native CX3 balance
listContracts({ limit, offset, viewer, owner, standard, live })Token catalogue (limit up to 500)
listTransfers(address, { limit, offset })A token's transfers
getTokens(address?) / getActivity(address?, { limit, before })A wallet's tokens with balances / its history
getDexPairs() / getLpBalance(pairId, address?)CarbonFi pools / a wallet's LP balance
call(address, method, params, sender?)Read-only contract call
getAbi(address) / getWit(address)A contract's ABI / the WIT of a CX3WASM contract
getStatus()Chain head { height, hash, timestamp }
getContractInfo(address)Registry record
isPrivateToken(address) / isTokenAuthorized(token, wallet)Private token checks
contract(address)Helper: balanceOf, transfer, call, send, transfers, isPrivate, isAuthorized
sendTransaction({ to, amount }, opts?)Native CX3 transfer, signed in the wallet; amount in CX3, at most 6 decimals (phone: opts.onSeen() when the app got the request)
contractSend(address, method, params, opts?)Contract transaction, signed in the wallet (opts.privateCheck, opts.onSeen)
waitForTransaction(hash, { timeoutMs, intervalMs, finalAfter }) / tx.wait()Poll until confirmed or failed for good: by default 30 s, or until finalAfter plus 30 s for a dropped send; every 1 s (every 5 s while dropped). timeoutMs is your own limit
bindApiKey(address?)Is the connected wallet your own, the one your application belongs to? For owner-only pages; visitors' wallets answer wallet_mismatch and may still use the app

Parameters are named JSON, not ABI calldata: contract(token).transfer(to, amount) sends { to, amount }. Amounts are the raw integer strings the contract expects ("1000000000000000000" is 1 token at 18 decimals).

Sending

await cx3.connect();

const tx = await cx3.contract("0x…token").transfer("0x…recipient", "1000000");
await tx.wait();                      // confirmed, or throws TX_FAILED / TX_TRACK_TIMEOUT

const native = await cx3.sendTransaction({ to: "0x…recipient", amount: "10" });
await native.wait();

The wallet shows the recipient, amount and fee, and the user approves or rejects every send.

A send answer has txHash, status, dropped, finalAfter, requestId and hint:

  • Pending is not failed. status is "pending" (or "submitted" for a repeat of the same signed send) when the transaction is queued but not in a block yet: TX_TIMEOUT (the node's 25-second wait ended first) or a send the node trimmed from its block (HTTP 202, dropped: true). success stays true, and tx.wait() follows it.
  • Dropped sends. Until finalAfter (ms epoch, about 12 minutes after the signature) another validator may still include a dropped send, so tx.wait() keeps waiting until then by default. After it the status says failed and tx.wait() rejects with TX_FAILED.
  • TX_TRACK_TIMEOUT means still pending, not failed (err.dropped, err.finalAfter). Never ask the user to sign the same payment again while it can still run: a new signature is a second transaction.

Phone wallet (QR code)

A visitor without the browser extension can use the CX3 Wallet app on their phone. The page shows a QR code, the user scans it in the app (QR scanner) and approves the connection. The pairing goes through your proxy to the CarbonX3 gateway's relay, so your developer token decides which app the phone sees: the app shows your application's name and your site.

cx3.setRpc(location.origin + "/my-dapp", { apiPrefix: "/api/v1" }); // your proxy, with the link routes

// the built-in dialog: QR code, "Waiting For Approval In The App", Cancel
const me = await cx3.connectPhone();          // cx3.via === "phone", cx3.address === me

// sends now go to the phone: the user approves them in the app
const tx = await cx3.contract("0x…token").transfer("0x…recipient", "1000000", {
  onSeen: () => console.log("the app received the request")
});
await tx.wait();

Your own dialog instead of the built-in one:

const box = document.getElementById("qr");
const stop = { cancelled: false };            // set stop.cancelled = true (or pass an AbortSignal) to give up
const me = await cx3.connectPhone({
  signal: stop,
  onQr: (uri, expiresAt) => {
    box.replaceChildren(cx3.qrSvg(uri, { size: 220 }));  // an <svg>: no images, no inline styles
  }
});
  • After a reload: await cx3.restorePhone() gives the phone's address while its link is still active (the link key stays in sessionStorage, this tab only), or "".
  • When the user removes your site in the app: the script checks the link every few seconds and calls your accountsChanged listeners with [], as the extension does.
  • cx3.disconnect() ends the link on the gateway too. cx3.connect() (the extension) takes over from a linked phone.
  • Built-in dialog texts: connectPhone({ texts: { title, hint, preparing, waiting, valid, cancel } }), for example in another language. The dialog is styled through the DOM, so a Content-Security-Policy without 'unsafe-inline' allows it.
  • Your proxy forwards the link/… and contract-send/… paths in the table above, with the X-CX3-Pair-Key and X-CX3-Link-Key headers the script sends, and DELETE for link and link/sign/{id}. The pairing's origin should be set by your server (it is the site the app shows), not taken from the browser.

Private tokens

Before the wallet asks for approval of a transfer, transferFrom or mint of a private token, the script asks the token whether the sender and the recipient are authorized. If one is not, the call rejects with PRIVATE_TOKEN_SENDER or PRIVATE_TOKEN_RECIPIENT and the wallet shows nothing. { privateCheck: false } as the last argument skips the check; the contract still decides on the chain.

getTokens() lists public tokens only: the gateway gives a wallet's private tokens only with that wallet's own signed proof, which CX3Wallet sends for its own lists and never to a page.

Errors

ConditionResult
No setRpcError: RPC not set (CX3.setRpc)
No walletError: CX3Wallet not found
Proxy without valid credentialsHTTP 401 oauth_required (or api_key_required)
Application revokedHTTP 401 invalid_token
Scope too smallHTTP 403 insufficient_scope
Not the application's walletwallet_mismatch
Method missing on the contractMETHOD_NOT_FOUND
call of a write methodCALL_REQUIRES_VIEW (use contractSend)
Private token, wallet not authorizedPRIVATE_TOKEN_SENDER / PRIVATE_TOKEN_RECIPIENT
Transaction failed for good (a dropped send only after its finalAfter)TX_FAILED
tx.wait() stopped while the transaction was still pending (not a failure)TX_TRACK_TIMEOUT
QR code expired (5 minutes) / dialog closed / bad relay answerPAIR_EXPIRED / PAIR_CANCELLED / PAIR_INVALID
Rejected in the phone app / no answer in about 2 minutesUSER_REJECTED / REQUEST_EXPIRED
The app removed the site (the page is no longer connected)REQUEST_ENDED
Phone send with a bad recipient or amount / more than 6 decimals (CX3 has 6)INVALID_RECIPIENT / INVALID_AMOUNT / INVALID_AMOUNT_PRECISION
Phone contract send: queued, not in a block within the node's 25 s (with a hash it is pending, not an error)TX_TIMEOUT
Phone contract send: the gateway could not record it (503); not sent, err.retry() sends the same signed request again laterLEDGER_UNAVAILABLE / LEDGER_FULL
Phone contract send: this signed request was already received (409); its outcome is open, never sign it againREPLAYED_REQUEST
Phone contract send: no answer in 35 s / no connection (outcome open; err.retry())RPC_TIMEOUT / NETWORK_ERROR
The chain refused the transaction: its data is too large or nested too deeply, or its nonce is not the next oneDATA_TOO_LARGE / DATA_TOO_DEEP / INVALID_NONCE_ORDER
The chain refused the gas limit: below the minimum, or above a contract send's maximumGAS_LIMIT_TOO_LOW / GAS_LIMIT_EXCEEDED

Coded errors carry the code in err.code. A contract send's error, with the phone or the extension, also has err.status (HTTP), err.requestId, err.hint, err.txHash and err.body (the gateway's answer); a TX_TIMEOUT that names its hash is not an error on either path but a pending tx. err.retry() is there only where sending the very same signed request again is safe (not sent, or no answer): the gateway keys it by its request id, so it never becomes a second transaction, and it is accepted for a few minutes after the signature.

Show error text with textContent, never innerHTML.

Documentation

License

UNLICENSED: proprietary software of Mersolution Technology Ltd. Use is subject to the CX3Script Terms of Use (item 03): you may load it in, and serve it unmodified with, applications, including commercial ones, that connect to CarbonX3 Green Chain, from your own server or a CDN at a pinned version with its integrity hash; you may not modify it, sell or republish it on its own, or use it with other networks.

Third-party code: the QR encoder is based on QR Code Generator for JavaScript, Copyright (c) 2009 Kazuhiko Arase, MIT license; its notice stays in the file. "QR Code" is a registered trademark of DENSO WAVE INCORPORATED.

版本与发布记录

@carbonx3/cx3-iife 共发布 11 个版本,首次发布于 2026/10/17;过去 12 个月发布了 11 个版本。

版本发布日期距今
0.2.9latest2026/11/018 天前
0.2.82026/10/2911 天前
0.2.72026/10/2416 天前
0.2.62026/10/2318 天前
0.2.52026/10/1921 天前
0.2.42026/10/1921 天前

关于 @carbonx3/cx3-iife 的常见问题

@carbonx3/cx3-iife 是什么?

Official browser script (window.cx3) for CarbonX3 Green Chain: CX3Wallet connect and signing (browser extension, or the phone app by QR code), reads through your own same-origin proxy

如何安装 @carbonx3/cx3-iife?

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

@carbonx3/cx3-iife 支持 TypeScript 吗?

该包的 package.json 没有声明类型入口。需要类型提示时,可以在 npm 上查找社区维护的 @types/carbonx3__cx3-iife 是否存在。

@carbonx3/cx3-iife 支持 ESM 还是 CommonJS?

以 CommonJS 形式发布,可以直接 require();在 ES Module 项目中也可以用 import 引入(Node.js 会把 module.exports 作为默认导出)。

@carbonx3/cx3-iife 可以商用吗?

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

@carbonx3/cx3-iife 的下载量有多少?

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

@carbonx3/cx3-iife 还在维护吗?

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