diff --git a/packages/ccc/package.json b/packages/ccc/package.json
index 6d47bd3e..dfe643d6 100644
--- a/packages/ccc/package.json
+++ b/packages/ccc/package.json
@@ -57,6 +57,7 @@
"@ckb-ccc/nip07": "workspace:*",
"@ckb-ccc/okx": "workspace:*",
"@ckb-ccc/uni-sat": "workspace:*",
- "@ckb-ccc/utxo-global": "workspace:*"
+ "@ckb-ccc/utxo-global": "workspace:*",
+ "@ckb-ccc/rei": "workspace:*"
}
}
diff --git a/packages/ccc/src/assets/rei.svg.ts b/packages/ccc/src/assets/rei.svg.ts
new file mode 100644
index 00000000..1d994b61
--- /dev/null
+++ b/packages/ccc/src/assets/rei.svg.ts
@@ -0,0 +1,5 @@
+import { encodeSvgToImgSrc } from "./utils.js";
+
+export const REI_SVG = encodeSvgToImgSrc(
+ ' ',
+);
diff --git a/packages/ccc/src/barrel.ts b/packages/ccc/src/barrel.ts
index f2c98541..f0ac8f42 100644
--- a/packages/ccc/src/barrel.ts
+++ b/packages/ccc/src/barrel.ts
@@ -3,6 +3,7 @@ export * from "@ckb-ccc/eip6963";
export * from "@ckb-ccc/joy-id";
export * from "@ckb-ccc/nip07";
export * from "@ckb-ccc/okx";
+export * from "@ckb-ccc/rei";
export * from "@ckb-ccc/uni-sat";
export * from "@ckb-ccc/utxo-global";
export * from "./signersController.js";
diff --git a/packages/ccc/src/signersController.ts b/packages/ccc/src/signersController.ts
index a2031be5..ed4ac6e1 100644
--- a/packages/ccc/src/signersController.ts
+++ b/packages/ccc/src/signersController.ts
@@ -3,6 +3,7 @@ import { Eip6963 } from "@ckb-ccc/eip6963";
import { JoyId } from "@ckb-ccc/joy-id";
import { Nip07 } from "@ckb-ccc/nip07";
import { Okx } from "@ckb-ccc/okx";
+import { Rei } from "@ckb-ccc/rei";
import { UniSat } from "@ckb-ccc/uni-sat";
import { UtxoGlobal } from "@ckb-ccc/utxo-global";
import { ETH_SVG } from "./assets/eth.svg.js";
@@ -10,6 +11,7 @@ import { JOY_ID_SVG } from "./assets/joy-id.svg.js";
import { METAMASK_SVG } from "./assets/metamask.svg.js";
import { NOSTR_SVG } from "./assets/nostr.svg.js";
import { OKX_SVG } from "./assets/okx.svg.js";
+import { REI_SVG } from "./assets/rei.svg.js";
import { UNI_SAT_SVG } from "./assets/uni-sat.svg.js";
import { UTXO_GLOBAL_SVG } from "./assets/utxo-global.svg.js";
@@ -97,6 +99,15 @@ export class SignersController {
configs,
);
+ await this.addSigners(
+ wallets,
+ "Rei Wallet",
+ REI_SVG,
+ Rei.getReiSigners(client),
+ onUpdate,
+ configs,
+ );
+
await this.addSigners(
wallets,
"JoyID Passkey",
diff --git a/packages/rei/.npmignore b/packages/rei/.npmignore
new file mode 100644
index 00000000..0e812402
--- /dev/null
+++ b/packages/rei/.npmignore
@@ -0,0 +1,12 @@
+node_modules/
+misc/
+
+tsconfig.json
+tsconfig.*.json
+eslint.config.mjs
+.prettierrc
+.prettierignore
+
+tsconfig.tsbuildinfo
+tsconfig.*.tsbuildinfo
+.github/
diff --git a/packages/rei/.prettierignore b/packages/rei/.prettierignore
new file mode 100644
index 00000000..7d789074
--- /dev/null
+++ b/packages/rei/.prettierignore
@@ -0,0 +1,13 @@
+node_modules/
+
+dist/
+dist.commoonjs/
+
+.npmignore
+.prettierrc
+tsconfig.json
+eslint.config.mjs
+.prettierrc
+
+tsconfig.tsbuildinfo
+.github/
diff --git a/packages/rei/.prettierrc b/packages/rei/.prettierrc
new file mode 100644
index 00000000..6390af08
--- /dev/null
+++ b/packages/rei/.prettierrc
@@ -0,0 +1,5 @@
+{
+ "singleQuote": false,
+ "trailingComma": "all",
+ "plugins": ["prettier-plugin-organize-imports"]
+}
diff --git a/packages/rei/README.md b/packages/rei/README.md
new file mode 100644
index 00000000..72da5e16
--- /dev/null
+++ b/packages/rei/README.md
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+ CCC's support for REI Wallet
+
+
+
+
+
+
+
+
+
+
+
+ "CCC - CKBers' Codebase" is the next step of "Common Chains Connector".
+
+ Empower yourself with CCC to discover the unlimited potential of CKB.
+
+ Interoperate with wallets from different chain ecosystems.
+
+ Fully enabling CKB's Turing completeness and cryptographic freedom power.
+
+
+## Preview
+
+
+
+
+
+
+
+This project is still under active development, and we are looking forward to your feedback. [Try its demo now here](https://app.ckbccc.com/). It showcases how to use CCC for some basic scenarios in CKB.
+
+
diff --git a/packages/rei/eslint.config.mjs b/packages/rei/eslint.config.mjs
new file mode 100644
index 00000000..b42a690d
--- /dev/null
+++ b/packages/rei/eslint.config.mjs
@@ -0,0 +1,29 @@
+// @ts-check
+
+import eslint from "@eslint/js";
+import tseslint from "typescript-eslint";
+import eslintPluginPrettierRecommended from "eslint-plugin-prettier";
+
+import { fileURLToPath } from "url";
+import { dirname } from "path";
+
+export default tseslint.config({
+ files: ["./src/**/*.ts"],
+ extends: [
+ eslint.configs.recommended,
+ ...tseslint.configs.recommendedTypeChecked,
+ ],
+ rules: {
+ "@typescript-eslint/require-await": "off",
+ "@typescript-eslint/no-redundant-type-constituents": "off",
+ "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
+ "@typescript-eslint/unbound-method": ["error", { "ignoreStatic": true }],
+ },
+ plugins: { prettier: eslintPluginPrettierRecommended },
+ languageOptions: {
+ parserOptions: {
+ project: true,
+ tsconfigRootDir: dirname(fileURLToPath(import.meta.url)),
+ },
+ },
+});
diff --git a/packages/rei/misc/basedirs/dist.commonjs/package.json b/packages/rei/misc/basedirs/dist.commonjs/package.json
new file mode 100644
index 00000000..5bbefffb
--- /dev/null
+++ b/packages/rei/misc/basedirs/dist.commonjs/package.json
@@ -0,0 +1,3 @@
+{
+ "type": "commonjs"
+}
diff --git a/packages/rei/misc/basedirs/dist/package.json b/packages/rei/misc/basedirs/dist/package.json
new file mode 100644
index 00000000..aead43de
--- /dev/null
+++ b/packages/rei/misc/basedirs/dist/package.json
@@ -0,0 +1,3 @@
+{
+ "type": "module"
+}
\ No newline at end of file
diff --git a/packages/rei/package.json b/packages/rei/package.json
new file mode 100644
index 00000000..7016507e
--- /dev/null
+++ b/packages/rei/package.json
@@ -0,0 +1,55 @@
+{
+ "name": "@ckb-ccc/rei",
+ "version": "0.0.12-alpha.0",
+ "description": "CCC - CKBer's Codebase. Common Chains Connector's support for Rei",
+ "license": "MIT",
+ "private": false,
+ "homepage": "https://github.com/ckb-ecofund/ccc",
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/ckb-ecofund/ccc.git"
+ },
+ "main": "dist.commonjs/index.js",
+ "module": "dist/index.js",
+ "exports": {
+ ".": {
+ "import": "./dist/index.js",
+ "default": "./dist.commonjs/index.js"
+ },
+ "./barrel": {
+ "import": "./dist/barrel.js",
+ "default": "./dist.commonjs/barrel.js"
+ },
+ "./advanced": {
+ "import": "./dist/advanced.js",
+ "default": "./dist.commonjs/advanced.js"
+ },
+ "./advancedBarrel": {
+ "import": "./dist/advancedBarrel.js",
+ "default": "./dist.commonjs/advancedBarrel.js"
+ }
+ },
+ "scripts": {
+ "build": "rimraf ./dist && rimraf ./dist.commonjs && tsc && tsc --project tsconfig.commonjs.json && copyfiles -u 2 misc/basedirs/**/* .",
+ "lint": "eslint",
+ "format": "prettier --write . && eslint --fix"
+ },
+ "devDependencies": {
+ "@eslint/js": "^9.1.1",
+ "copyfiles": "^2.4.1",
+ "eslint": "^9.1.0",
+ "eslint-config-prettier": "^9.1.0",
+ "eslint-plugin-prettier": "^5.1.3",
+ "prettier": "^3.2.5",
+ "prettier-plugin-organize-imports": "^3.2.4",
+ "rimraf": "^5.0.5",
+ "typescript": "^5.4.5",
+ "typescript-eslint": "^7.7.0"
+ },
+ "publishConfig": {
+ "access": "public"
+ },
+ "dependencies": {
+ "@ckb-ccc/core": "workspace:*"
+ }
+}
diff --git a/packages/rei/src/advanced.ts b/packages/rei/src/advanced.ts
new file mode 100644
index 00000000..755a25d9
--- /dev/null
+++ b/packages/rei/src/advanced.ts
@@ -0,0 +1 @@
+export * as ReiA from "./advancedBarrel.js";
diff --git a/packages/rei/src/advancedBarrel.ts b/packages/rei/src/advancedBarrel.ts
new file mode 100644
index 00000000..4af13dde
--- /dev/null
+++ b/packages/rei/src/advancedBarrel.ts
@@ -0,0 +1,103 @@
+import { ccc } from "@ckb-ccc/core";
+
+/**
+ * Interface representing a provider for interacting with accounts and signing messages.
+ * @interface
+ */
+export interface Provider {
+ /**
+ * Sends a request to the provider.
+ * @type {RequestMethod}
+ */
+ request: RequestMethod;
+
+ /**
+ * Adds an event listener to the provider.
+ * @type {OnMethod}
+ */
+ on: OnMethod;
+
+ /**
+ * Checks if rei wallet is connected.
+ * @returns A promise that resolves to true if connected, false otherwise.
+ */
+ isConnected(): Promise;
+
+ /**
+ * Removes an event listener from the provider.
+ * @param {string} eventName - The name of the event to remove the listener from.
+ * @param {(...args: unknown[]) => unknown} listener - The listener function to remove.
+ * @returns {Provider} The provider instance.
+ */
+ off(eventName: string, listener: (...args: unknown[]) => unknown): void;
+}
+
+/**
+ * Interface representing a method to add event listeners to the provider.
+ * @interface
+ */
+export interface OnMethod {
+ /**
+ * Adds an event listener to the provider.
+ * @param {string} eventName - The name of the event.
+ * @param {(...args: unknown[]) => unknown} listener - The listener function.
+ * @returns {Provider} The provider instance.
+ */
+ (eventName: string, listener: (...args: unknown[]) => unknown): void;
+}
+
+export interface RequestMethod {
+ /**
+ * Requests the accounts from the provider.
+ * @param request - The request object.
+ * @param request.method - The method name.
+ * @param request.data - The optional method parameters.
+ * @returns A promise that resolves to an array of account addresses.
+ */
+ (request: {
+ method: "ckb_requestAccounts";
+ data?: undefined;
+ }): Promise;
+
+ /**
+ * Signs a message with the personal account.
+ * @param request - The request object.
+ * @param request.method - The method name.
+ * @param request.data - The method parameters.
+ * @returns A promise that resolves to the signed message.
+ */
+ (request: {
+ method: "ckb_signMessage";
+ data: { message: string };
+ }): Promise;
+
+ /**
+ * Gets the identity of the signer.
+ * @param request - The request object.
+ * @param request.method - The method name.
+ * @returns A promise that resolves to the signed message.
+ */
+ (request: { method: "ckb_getPublicKey" }): Promise;
+
+ /**
+ * Get network.
+ * @param request - The request object.
+ * @param request.method - The method name.
+ * @param request.data - The method parameters.
+ * @returns A promise that resolves to the signed message.
+ */
+ (request: {
+ method: "ckb_switchNetwork";
+ data: string;
+ }): Promise<{ type: string; network: string }>;
+
+ /**
+ * Signs transaction .
+ * @param request - The request object.
+ * @param request.method - The method name.
+ * @param request.data - The method parameters.
+ * @returns A promise that resolves to the signed message.
+ */
+
+ (request: { method: "ckb_signTransaction"; data: any }): Promise;
+}
diff --git a/packages/rei/src/barrel.ts b/packages/rei/src/barrel.ts
new file mode 100644
index 00000000..5bad6479
--- /dev/null
+++ b/packages/rei/src/barrel.ts
@@ -0,0 +1,2 @@
+export * from "./signer.js";
+export * from "./signersFactory.js";
diff --git a/packages/rei/src/index.ts b/packages/rei/src/index.ts
new file mode 100644
index 00000000..7195b783
--- /dev/null
+++ b/packages/rei/src/index.ts
@@ -0,0 +1 @@
+export * as Rei from "./barrel.js";
diff --git a/packages/rei/src/signer.ts b/packages/rei/src/signer.ts
new file mode 100644
index 00000000..1518039d
--- /dev/null
+++ b/packages/rei/src/signer.ts
@@ -0,0 +1,180 @@
+import { ccc } from "@ckb-ccc/core";
+import { cccA } from "@ckb-ccc/core/advanced";
+
+import { Provider } from "./advancedBarrel.js";
+
+/**
+ * Class representing a CKB signer that extends Signer from @ckb-ccc/core.
+ * @public
+ */
+export class ReiSigner extends ccc.Signer {
+ /**
+ * Creates an instance of Signer.
+ * @param client - The client instance.
+ * @param provider - The provider instance.
+ */
+ constructor(
+ client: ccc.Client,
+ public readonly provider: Provider,
+ ) {
+ super(client);
+ }
+
+ /**
+ * Register a listener to be called when this signer is replaced.
+ *
+ * @returns A function for unregister
+ */
+
+ onReplaced(listener: () => void): () => void {
+ const stop: (() => void)[] = [];
+ const replacer = async () => {
+ listener();
+ stop[0]?.();
+ };
+ stop.push(() => {
+ this.provider.off("accountsChanged", replacer);
+ this.provider.off("chainChanged", replacer);
+ });
+ this.provider.on("accountsChanged", replacer);
+ this.provider.on("chainChanged", replacer);
+
+ return stop[0];
+ }
+
+ /**
+ * Gets the signer type.
+ * @returns The type of the signer.
+ */
+ get type(): ccc.SignerType {
+ return ccc.SignerType.CKB;
+ }
+
+ /**
+ * Gets the sign type.
+ * @returns The sign type.
+ */
+ get signType(): ccc.SignerSignType {
+ return ccc.SignerSignType.CkbSecp256k1;
+ }
+
+ /**
+ * Connects to the provider by requesting authentication.
+ * @returns A promise that resolves when the connection is established.
+ */
+ async connect(): Promise {
+ const prefixClient = this.client.addressPrefix;
+ const netChange = await this._getNetworkChanged();
+ if (netChange) {
+ const data = prefixClient === "ckb" ? "mainnet" : "testnet";
+ await this.provider.request({ method: "ckb_switchNetwork", data });
+ }
+ }
+
+ /**
+ * Checks if the signer is connected.
+ * @returns A promise that resolves to true if connected, false otherwise.
+ */
+ async isConnected(): Promise {
+ const connected = await this.provider.isConnected();
+ if (!connected || (await this._getNetworkChanged())) {
+ return false;
+ }
+ return connected;
+ }
+
+ async _getNetworkChanged(): Promise {
+ const prefixClient = this.client.addressPrefix;
+
+ const address = await this.getInternalAddress();
+ const { prefix } = cccA.addressPayloadFromString(address);
+ return prefixClient !== prefix;
+ }
+
+ /**
+ * Gets the internal address.
+ * @returns A promise that resolves to the internal address.
+ */
+ async getInternalAddress(): Promise {
+ return this.provider.request({ method: "ckb_requestAccounts" });
+ }
+
+ /**
+ * Gets the address object.
+ * @returns A promise that resolves to the address object.
+ */
+ async getAddressObj(): Promise {
+ return ccc.Address.fromString(await this.getInternalAddress(), this.client);
+ }
+
+ /**
+ * Gets the address objects.
+ * @returns A promise that resolves to an array of address objects.
+ */
+ async getAddressObjs(): Promise {
+ return [await this.getAddressObj()];
+ }
+
+ /**
+ * Gets the identity of the signer.
+ * @returns A promise that resolves to the identity.
+ */
+ async getIdentity(): Promise {
+ return this.provider.request({
+ method: "ckb_getPublicKey",
+ });
+ }
+
+ /**
+ * Signs a raw message with the personal account.
+ * @param message - The message to sign.
+ * @returns A promise that resolves to the signed message.
+ */
+ async signMessageRaw(message: string): Promise {
+ return this.provider.request({
+ method: "ckb_signMessage",
+ data: { message },
+ });
+ }
+
+ /**
+ * prepare a transaction before signing.
+ *
+ * @param txLike - The transaction to prepare, represented as a TransactionLike object.
+ * @returns A promise that resolves to the prepared Transaction object.
+ */
+
+ async prepareTransaction(
+ txLike: ccc.TransactionLike,
+ ): Promise {
+ const tx = ccc.Transaction.from(txLike);
+ await tx.addCellDepsOfKnownScripts(
+ this.client,
+ ccc.KnownScript.Secp256k1Blake160,
+ );
+ return ccc.reduceAsync(
+ await this.getAddressObjs(),
+ (tx: ccc.Transaction, { script }) =>
+ tx.prepareSighashAllWitness(script, 65, this.client),
+ tx,
+ );
+ }
+
+ /**
+ * Signs a transaction without preparing information for it.
+ *
+ * @param txLike - The transaction to sign, represented as a TransactionLike object.
+ * @returns A promise that resolves to the signed Transaction object.
+ */
+
+ async signOnlyTransaction(
+ txLike: ccc.TransactionLike,
+ ): Promise {
+ const txFormat = cccA.JsonRpcTransformers.transactionFrom(txLike);
+
+ return this.provider.request({
+ method: "ckb_signTransaction",
+ data: { txSkeleton: txFormat },
+ });
+ }
+}
diff --git a/packages/rei/src/signersFactory.ts b/packages/rei/src/signersFactory.ts
new file mode 100644
index 00000000..e64015ec
--- /dev/null
+++ b/packages/rei/src/signersFactory.ts
@@ -0,0 +1,23 @@
+import { ccc } from "@ckb-ccc/core";
+import { Provider } from "./advancedBarrel.js";
+import { ReiSigner } from "./signer.js";
+
+/**
+ * Retrieves the Rei signer if available.
+ * @param client - The client instance.
+ * @returns The Signer instance if the Rei provider is available, otherwise undefined.
+ */
+export function getReiSigners(client: ccc.Client): ccc.SignerInfo[] {
+ const windowRef = window as { ckb?: Provider };
+
+ if (typeof windowRef.ckb === "undefined") {
+ return [];
+ }
+
+ return [
+ {
+ signer: new ReiSigner(client, windowRef.ckb),
+ name: "CKB",
+ },
+ ];
+}
diff --git a/packages/rei/tsconfig.base.json b/packages/rei/tsconfig.base.json
new file mode 100644
index 00000000..7e5ac952
--- /dev/null
+++ b/packages/rei/tsconfig.base.json
@@ -0,0 +1,22 @@
+{
+ "compilerOptions": {
+ "target": "es2020",
+ "incremental": true,
+ "allowJs": true,
+ "importHelpers": false,
+ "declaration": true,
+ "declarationMap": true,
+ "experimentalDecorators": true,
+ "useDefineForClassFields": false,
+ "esModuleInterop": true,
+ "strict": true,
+ "noImplicitAny": true,
+ "strictBindCallApply": true,
+ "strictNullChecks": true,
+ "alwaysStrict": true,
+ "noFallthroughCasesInSwitch": true,
+ "forceConsistentCasingInFileNames": true,
+ "skipLibCheck": true
+ },
+ "include": ["src/**/*"]
+}
diff --git a/packages/rei/tsconfig.commonjs.json b/packages/rei/tsconfig.commonjs.json
new file mode 100644
index 00000000..76a25e98
--- /dev/null
+++ b/packages/rei/tsconfig.commonjs.json
@@ -0,0 +1,8 @@
+{
+ "extends": "./tsconfig.base.json",
+ "compilerOptions": {
+ "module": "NodeNext",
+ "moduleResolution": "NodeNext",
+ "outDir": "./dist.commonjs"
+ }
+}
diff --git a/packages/rei/tsconfig.json b/packages/rei/tsconfig.json
new file mode 100644
index 00000000..df22faec
--- /dev/null
+++ b/packages/rei/tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "extends": "./tsconfig.base.json",
+ "compilerOptions": {
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
+ "outDir": "./dist",
+ }
+}
diff --git a/packages/rei/typedoc.json b/packages/rei/typedoc.json
new file mode 100644
index 00000000..0162c4f2
--- /dev/null
+++ b/packages/rei/typedoc.json
@@ -0,0 +1,6 @@
+{
+ "$schema": "https://typedoc.org/schema.json",
+ "entryPoints": ["./src/index.ts", "./src/advanced.ts"],
+ "extends": ["../../typedoc.base.json"],
+ "name": "@ckb-ccc rei"
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 2884b945..4f954d84 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -53,6 +53,9 @@ importers:
'@ckb-ccc/okx':
specifier: workspace:*
version: link:../okx
+ '@ckb-ccc/rei':
+ specifier: workspace:*
+ version: link:../rei
'@ckb-ccc/uni-sat':
specifier: workspace:*
version: link:../uni-sat
@@ -690,6 +693,43 @@ importers:
specifier: ^7.7.0
version: 7.7.0(eslint@9.1.0)(typescript@5.4.5)
+ packages/rei:
+ dependencies:
+ '@ckb-ccc/core':
+ specifier: workspace:*
+ version: link:../core
+ devDependencies:
+ '@eslint/js':
+ specifier: ^9.1.1
+ version: 9.1.1
+ copyfiles:
+ specifier: ^2.4.1
+ version: 2.4.1
+ eslint:
+ specifier: ^9.1.0
+ version: 9.1.0
+ eslint-config-prettier:
+ specifier: ^9.1.0
+ version: 9.1.0(eslint@9.1.0)
+ eslint-plugin-prettier:
+ specifier: ^5.1.3
+ version: 5.1.3(@types/eslint@9.6.0)(eslint-config-prettier@9.1.0(eslint@9.1.0))(eslint@9.1.0)(prettier@3.2.5)
+ prettier:
+ specifier: ^3.2.5
+ version: 3.2.5
+ prettier-plugin-organize-imports:
+ specifier: ^3.2.4
+ version: 3.2.4(prettier@3.2.5)(typescript@5.4.5)
+ rimraf:
+ specifier: ^5.0.5
+ version: 5.0.5
+ typescript:
+ specifier: ^5.4.5
+ version: 5.4.5
+ typescript-eslint:
+ specifier: ^7.7.0
+ version: 7.7.0(eslint@9.1.0)(typescript@5.4.5)
+
packages/uni-sat:
dependencies:
'@ckb-ccc/core':