@moon-x/solana-adapter 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,57 @@
1
+ # @moon-x/solana-adapter
2
+
3
+ Chain-aware Solana signing helpers for MoonX **ephemeral signers**.
4
+
5
+ The low-level `signWithEphemeralSigner` primitive in `@moon-x/core` signs
6
+ arbitrary raw bytes for an ed25519 key — which means the integrator is
7
+ responsible for producing the exact bytes (serializing the transaction
8
+ message) and for attaching the returned signature to the transaction. This
9
+ adapter does both: you pass **intent** and get back a ready-to-broadcast
10
+ artifact.
11
+
12
+ > Solana differs from EVM: ed25519 signs the raw message bytes directly —
13
+ > there is no keccak / pre-hashing step. The adapter handles that correctly
14
+ > so you don't have to think about it.
15
+
16
+ ## Install
17
+
18
+ ```sh
19
+ pnpm add @moon-x/solana-adapter @solana/web3.js
20
+ ```
21
+
22
+ `@solana/web3.js` is a peer dependency, so your app's single instance is the
23
+ one used.
24
+
25
+ ## Usage
26
+
27
+ ```ts
28
+ import { createSolanaSigner } from "@moon-x/solana-adapter";
29
+
30
+ const signer = createSolanaSigner({
31
+ apiPubHex, // from provisioning
32
+ apiPrivHex, // never leaves your backend
33
+ walletId, // the Solana wallet the agent was provisioned for
34
+ address, // base58 wallet pubkey (needed to attach the signature)
35
+ baseUrl, // wallets backend URL
36
+ publishableKey, // moon_pk_…
37
+ });
38
+
39
+ // Arbitrary message → 64-byte ed25519 signature
40
+ const sig = await signer.signMessage("gm");
41
+
42
+ // Transaction (legacy or versioned) → wire-ready serialized bytes
43
+ const raw = await signer.signTransaction(transaction);
44
+ await connection.sendRawTransaction(raw);
45
+ ```
46
+
47
+ ## Scheme safety
48
+
49
+ The adapter enforces that the target wallet is ed25519. Pointing it at a
50
+ secp256k1 (EVM) wallet throws `SolanaSchemeMismatchError` instead of producing
51
+ a valid-but-wrong signature — use `@moon-x/evm-adapter` for those.
52
+
53
+ ## Escape hatch
54
+
55
+ For signing flows this adapter doesn't model, drop down to
56
+ `signWithEphemeralSigner` from `@moon-x/core/sdk` and build the payload
57
+ yourself.
@@ -0,0 +1,65 @@
1
+ import { Transaction, VersionedTransaction } from '@solana/web3.js';
2
+
3
+ /**
4
+ * Connection + identity for a Solana ephemeral signer. `apiPrivHex` is
5
+ * consumed locally to sign the request envelope and never crosses the
6
+ * network.
7
+ */
8
+ interface SolanaSignerConfig {
9
+ /** Lowercase-hex 32-byte Ed25519 agent public key (no 0x prefix). */
10
+ apiPubHex: string;
11
+ /** Lowercase-hex 32-byte Ed25519 agent private key (no 0x prefix). */
12
+ apiPrivHex: string;
13
+ /**
14
+ * Wallet UUID (from `user.wallets[i].id`) to sign on behalf of. Must be
15
+ * a Solana (ed25519) wallet the agent was provisioned for — a scheme
16
+ * mismatch is detected and rejected on the first sign.
17
+ */
18
+ walletId: string;
19
+ /**
20
+ * Base58 Solana address (public key) of the wallet. Required to attach
21
+ * the produced signature to a transaction — Solana signatures are keyed
22
+ * by signer pubkey.
23
+ */
24
+ address: string;
25
+ /** Wallets backend base URL (no trailing slash). */
26
+ baseUrl: string;
27
+ /** MoonX publishable key (`moon_pk_…`). */
28
+ publishableKey: string;
29
+ /** Optional fetch override — e.g. for Next.js route handlers or test stubs. */
30
+ fetchImpl?: typeof fetch;
31
+ }
32
+ declare class SolanaSchemeMismatchError extends Error {
33
+ constructor(scheme: string);
34
+ }
35
+ /**
36
+ * Chain-aware Solana signer backed by a MoonX ephemeral signer. Construct
37
+ * once per (agent, wallet) and call the intent-level methods.
38
+ *
39
+ * All chain logic (message-bytes + signature attachment) lives in
40
+ * @moon-x/signing-codec/solana — this class is just the ephemeral-signer
41
+ * *backend* that supplies the `Ed25519SignFn`. The same signing-codec is
42
+ * driven by the user/MPC backend in the iframe.
43
+ */
44
+ declare class MoonXSolanaSigner {
45
+ private readonly cfg;
46
+ constructor(cfg: SolanaSignerConfig);
47
+ /** Ephemeral-signer backend: sign raw bytes, return the 64-byte sig. */
48
+ private readonly sign;
49
+ /**
50
+ * Sign an arbitrary message. Accepts a UTF-8 string or raw bytes and
51
+ * returns the 64-byte ed25519 signature, verifiable against the wallet's
52
+ * public key (e.g. via `nacl.sign.detached.verify` or Web Crypto).
53
+ */
54
+ signMessage(message: string | Uint8Array): Promise<Uint8Array>;
55
+ /**
56
+ * Sign a transaction (legacy `Transaction` or `VersionedTransaction`)
57
+ * and return the wire-ready serialized bytes — pass straight to
58
+ * `connection.sendRawTransaction`.
59
+ */
60
+ signTransaction(transaction: Transaction | VersionedTransaction): Promise<Uint8Array>;
61
+ }
62
+ /** Convenience factory mirroring the class constructor. */
63
+ declare function createSolanaSigner(cfg: SolanaSignerConfig): MoonXSolanaSigner;
64
+
65
+ export { MoonXSolanaSigner, SolanaSchemeMismatchError, type SolanaSignerConfig, createSolanaSigner };
@@ -0,0 +1,65 @@
1
+ import { Transaction, VersionedTransaction } from '@solana/web3.js';
2
+
3
+ /**
4
+ * Connection + identity for a Solana ephemeral signer. `apiPrivHex` is
5
+ * consumed locally to sign the request envelope and never crosses the
6
+ * network.
7
+ */
8
+ interface SolanaSignerConfig {
9
+ /** Lowercase-hex 32-byte Ed25519 agent public key (no 0x prefix). */
10
+ apiPubHex: string;
11
+ /** Lowercase-hex 32-byte Ed25519 agent private key (no 0x prefix). */
12
+ apiPrivHex: string;
13
+ /**
14
+ * Wallet UUID (from `user.wallets[i].id`) to sign on behalf of. Must be
15
+ * a Solana (ed25519) wallet the agent was provisioned for — a scheme
16
+ * mismatch is detected and rejected on the first sign.
17
+ */
18
+ walletId: string;
19
+ /**
20
+ * Base58 Solana address (public key) of the wallet. Required to attach
21
+ * the produced signature to a transaction — Solana signatures are keyed
22
+ * by signer pubkey.
23
+ */
24
+ address: string;
25
+ /** Wallets backend base URL (no trailing slash). */
26
+ baseUrl: string;
27
+ /** MoonX publishable key (`moon_pk_…`). */
28
+ publishableKey: string;
29
+ /** Optional fetch override — e.g. for Next.js route handlers or test stubs. */
30
+ fetchImpl?: typeof fetch;
31
+ }
32
+ declare class SolanaSchemeMismatchError extends Error {
33
+ constructor(scheme: string);
34
+ }
35
+ /**
36
+ * Chain-aware Solana signer backed by a MoonX ephemeral signer. Construct
37
+ * once per (agent, wallet) and call the intent-level methods.
38
+ *
39
+ * All chain logic (message-bytes + signature attachment) lives in
40
+ * @moon-x/signing-codec/solana — this class is just the ephemeral-signer
41
+ * *backend* that supplies the `Ed25519SignFn`. The same signing-codec is
42
+ * driven by the user/MPC backend in the iframe.
43
+ */
44
+ declare class MoonXSolanaSigner {
45
+ private readonly cfg;
46
+ constructor(cfg: SolanaSignerConfig);
47
+ /** Ephemeral-signer backend: sign raw bytes, return the 64-byte sig. */
48
+ private readonly sign;
49
+ /**
50
+ * Sign an arbitrary message. Accepts a UTF-8 string or raw bytes and
51
+ * returns the 64-byte ed25519 signature, verifiable against the wallet's
52
+ * public key (e.g. via `nacl.sign.detached.verify` or Web Crypto).
53
+ */
54
+ signMessage(message: string | Uint8Array): Promise<Uint8Array>;
55
+ /**
56
+ * Sign a transaction (legacy `Transaction` or `VersionedTransaction`)
57
+ * and return the wire-ready serialized bytes — pass straight to
58
+ * `connection.sendRawTransaction`.
59
+ */
60
+ signTransaction(transaction: Transaction | VersionedTransaction): Promise<Uint8Array>;
61
+ }
62
+ /** Convenience factory mirroring the class constructor. */
63
+ declare function createSolanaSigner(cfg: SolanaSignerConfig): MoonXSolanaSigner;
64
+
65
+ export { MoonXSolanaSigner, SolanaSchemeMismatchError, type SolanaSignerConfig, createSolanaSigner };
package/dist/index.js ADDED
@@ -0,0 +1,141 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var src_exports = {};
22
+ __export(src_exports, {
23
+ MoonXSolanaSigner: () => MoonXSolanaSigner,
24
+ SolanaSchemeMismatchError: () => SolanaSchemeMismatchError,
25
+ createSolanaSigner: () => createSolanaSigner
26
+ });
27
+ module.exports = __toCommonJS(src_exports);
28
+
29
+ // src/signer.ts
30
+ var import_sdk = require("@moon-x/core/sdk");
31
+
32
+ // ../signing-codec/dist/solana.mjs
33
+ var import_web3 = require("@solana/web3.js");
34
+ function isVersioned(tx) {
35
+ return "version" in tx;
36
+ }
37
+ function solanaTransactionMessageBytes(tx) {
38
+ return isVersioned(tx) ? tx.message.serialize() : tx.serializeMessage();
39
+ }
40
+ async function signSolanaMessage(sign, message) {
41
+ const bytes = typeof message === "string" ? new TextEncoder().encode(message) : message;
42
+ return sign(bytes);
43
+ }
44
+ function attachSolanaSignature(tx, signature, signerAddress) {
45
+ const pubkey = new import_web3.PublicKey(signerAddress);
46
+ if (isVersioned(tx)) {
47
+ const clone2 = import_web3.VersionedTransaction.deserialize(tx.serialize());
48
+ clone2.addSignature(pubkey, signature);
49
+ return clone2.serialize();
50
+ }
51
+ const clone = import_web3.Transaction.from(
52
+ tx.serialize({ requireAllSignatures: false, verifySignatures: false })
53
+ );
54
+ clone.addSignature(pubkey, Buffer.from(signature));
55
+ return clone.serialize({
56
+ requireAllSignatures: false,
57
+ verifySignatures: false
58
+ });
59
+ }
60
+ async function signSolanaTransaction(sign, tx, signerAddress) {
61
+ const sig = await sign(solanaTransactionMessageBytes(tx));
62
+ return attachSolanaSignature(tx, sig, signerAddress);
63
+ }
64
+
65
+ // src/signer.ts
66
+ var SolanaSchemeMismatchError = class extends Error {
67
+ constructor(scheme) {
68
+ super(
69
+ `wallet returned scheme "${scheme}", not eddsa_ed25519 \u2014 this is not a Solana wallet. Use @moon-x/evm-adapter for secp256k1 wallets.`
70
+ );
71
+ this.name = "SolanaSchemeMismatchError";
72
+ }
73
+ };
74
+ function bytesToHex(bytes) {
75
+ let out = "";
76
+ for (const b of bytes) out += b.toString(16).padStart(2, "0");
77
+ return out;
78
+ }
79
+ function hexToBytes(hex) {
80
+ const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
81
+ const out = new Uint8Array(clean.length / 2);
82
+ for (let i = 0; i < out.length; i++) {
83
+ out[i] = parseInt(clean.slice(i * 2, i * 2 + 2), 16);
84
+ }
85
+ return out;
86
+ }
87
+ function requireEd25519(result) {
88
+ if (result.scheme !== "eddsa_ed25519") {
89
+ throw new SolanaSchemeMismatchError(result.scheme);
90
+ }
91
+ const sigHex = result.signature.signature;
92
+ if (!sigHex) {
93
+ throw new Error(
94
+ "ephemeral signer returned an ed25519 scheme without a signature"
95
+ );
96
+ }
97
+ return hexToBytes(sigHex);
98
+ }
99
+ var MoonXSolanaSigner = class {
100
+ constructor(cfg) {
101
+ this.cfg = cfg;
102
+ /** Ephemeral-signer backend: sign raw bytes, return the 64-byte sig. */
103
+ this.sign = async (message) => {
104
+ const result = await (0, import_sdk.signWithEphemeralSigner)({
105
+ apiPubHex: this.cfg.apiPubHex,
106
+ apiPrivHex: this.cfg.apiPrivHex,
107
+ walletId: this.cfg.walletId,
108
+ rawSigningPayloadHex: bytesToHex(message),
109
+ baseUrl: this.cfg.baseUrl,
110
+ publishableKey: this.cfg.publishableKey,
111
+ fetchImpl: this.cfg.fetchImpl
112
+ });
113
+ return requireEd25519(result);
114
+ };
115
+ }
116
+ /**
117
+ * Sign an arbitrary message. Accepts a UTF-8 string or raw bytes and
118
+ * returns the 64-byte ed25519 signature, verifiable against the wallet's
119
+ * public key (e.g. via `nacl.sign.detached.verify` or Web Crypto).
120
+ */
121
+ signMessage(message) {
122
+ return signSolanaMessage(this.sign, message);
123
+ }
124
+ /**
125
+ * Sign a transaction (legacy `Transaction` or `VersionedTransaction`)
126
+ * and return the wire-ready serialized bytes — pass straight to
127
+ * `connection.sendRawTransaction`.
128
+ */
129
+ signTransaction(transaction) {
130
+ return signSolanaTransaction(this.sign, transaction, this.cfg.address);
131
+ }
132
+ };
133
+ function createSolanaSigner(cfg) {
134
+ return new MoonXSolanaSigner(cfg);
135
+ }
136
+ // Annotate the CommonJS export names for ESM import in node:
137
+ 0 && (module.exports = {
138
+ MoonXSolanaSigner,
139
+ SolanaSchemeMismatchError,
140
+ createSolanaSigner
141
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,118 @@
1
+ // src/signer.ts
2
+ import {
3
+ signWithEphemeralSigner
4
+ } from "@moon-x/core/sdk";
5
+
6
+ // ../signing-codec/dist/solana.mjs
7
+ import {
8
+ PublicKey,
9
+ Transaction,
10
+ VersionedTransaction
11
+ } from "@solana/web3.js";
12
+ function isVersioned(tx) {
13
+ return "version" in tx;
14
+ }
15
+ function solanaTransactionMessageBytes(tx) {
16
+ return isVersioned(tx) ? tx.message.serialize() : tx.serializeMessage();
17
+ }
18
+ async function signSolanaMessage(sign, message) {
19
+ const bytes = typeof message === "string" ? new TextEncoder().encode(message) : message;
20
+ return sign(bytes);
21
+ }
22
+ function attachSolanaSignature(tx, signature, signerAddress) {
23
+ const pubkey = new PublicKey(signerAddress);
24
+ if (isVersioned(tx)) {
25
+ const clone2 = VersionedTransaction.deserialize(tx.serialize());
26
+ clone2.addSignature(pubkey, signature);
27
+ return clone2.serialize();
28
+ }
29
+ const clone = Transaction.from(
30
+ tx.serialize({ requireAllSignatures: false, verifySignatures: false })
31
+ );
32
+ clone.addSignature(pubkey, Buffer.from(signature));
33
+ return clone.serialize({
34
+ requireAllSignatures: false,
35
+ verifySignatures: false
36
+ });
37
+ }
38
+ async function signSolanaTransaction(sign, tx, signerAddress) {
39
+ const sig = await sign(solanaTransactionMessageBytes(tx));
40
+ return attachSolanaSignature(tx, sig, signerAddress);
41
+ }
42
+
43
+ // src/signer.ts
44
+ var SolanaSchemeMismatchError = class extends Error {
45
+ constructor(scheme) {
46
+ super(
47
+ `wallet returned scheme "${scheme}", not eddsa_ed25519 \u2014 this is not a Solana wallet. Use @moon-x/evm-adapter for secp256k1 wallets.`
48
+ );
49
+ this.name = "SolanaSchemeMismatchError";
50
+ }
51
+ };
52
+ function bytesToHex(bytes) {
53
+ let out = "";
54
+ for (const b of bytes) out += b.toString(16).padStart(2, "0");
55
+ return out;
56
+ }
57
+ function hexToBytes(hex) {
58
+ const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
59
+ const out = new Uint8Array(clean.length / 2);
60
+ for (let i = 0; i < out.length; i++) {
61
+ out[i] = parseInt(clean.slice(i * 2, i * 2 + 2), 16);
62
+ }
63
+ return out;
64
+ }
65
+ function requireEd25519(result) {
66
+ if (result.scheme !== "eddsa_ed25519") {
67
+ throw new SolanaSchemeMismatchError(result.scheme);
68
+ }
69
+ const sigHex = result.signature.signature;
70
+ if (!sigHex) {
71
+ throw new Error(
72
+ "ephemeral signer returned an ed25519 scheme without a signature"
73
+ );
74
+ }
75
+ return hexToBytes(sigHex);
76
+ }
77
+ var MoonXSolanaSigner = class {
78
+ constructor(cfg) {
79
+ this.cfg = cfg;
80
+ /** Ephemeral-signer backend: sign raw bytes, return the 64-byte sig. */
81
+ this.sign = async (message) => {
82
+ const result = await signWithEphemeralSigner({
83
+ apiPubHex: this.cfg.apiPubHex,
84
+ apiPrivHex: this.cfg.apiPrivHex,
85
+ walletId: this.cfg.walletId,
86
+ rawSigningPayloadHex: bytesToHex(message),
87
+ baseUrl: this.cfg.baseUrl,
88
+ publishableKey: this.cfg.publishableKey,
89
+ fetchImpl: this.cfg.fetchImpl
90
+ });
91
+ return requireEd25519(result);
92
+ };
93
+ }
94
+ /**
95
+ * Sign an arbitrary message. Accepts a UTF-8 string or raw bytes and
96
+ * returns the 64-byte ed25519 signature, verifiable against the wallet's
97
+ * public key (e.g. via `nacl.sign.detached.verify` or Web Crypto).
98
+ */
99
+ signMessage(message) {
100
+ return signSolanaMessage(this.sign, message);
101
+ }
102
+ /**
103
+ * Sign a transaction (legacy `Transaction` or `VersionedTransaction`)
104
+ * and return the wire-ready serialized bytes — pass straight to
105
+ * `connection.sendRawTransaction`.
106
+ */
107
+ signTransaction(transaction) {
108
+ return signSolanaTransaction(this.sign, transaction, this.cfg.address);
109
+ }
110
+ };
111
+ function createSolanaSigner(cfg) {
112
+ return new MoonXSolanaSigner(cfg);
113
+ }
114
+ export {
115
+ MoonXSolanaSigner,
116
+ SolanaSchemeMismatchError,
117
+ createSolanaSigner
118
+ };
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@moon-x/solana-adapter",
3
+ "version": "0.1.0",
4
+ "description": "Chain-aware Solana signing helpers for MoonX ephemeral signers. Serializes the message bytes and assembles ready-to-broadcast transactions/signatures so integrators never hand-roll payload construction.",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md"
18
+ ],
19
+ "keywords": [
20
+ "moonx",
21
+ "moonpay",
22
+ "solana",
23
+ "ed25519",
24
+ "web3.js",
25
+ "signer",
26
+ "wallet",
27
+ "sdk"
28
+ ],
29
+ "license": "UNLICENSED",
30
+ "publishConfig": {
31
+ "access": "public"
32
+ },
33
+ "dependencies": {
34
+ "@moon-x/core": "0.7.0"
35
+ },
36
+ "peerDependencies": {
37
+ "@solana/web3.js": "^1.98.0"
38
+ },
39
+ "devDependencies": {
40
+ "@noble/curves": "1.8.0",
41
+ "@solana/web3.js": "^1.98.4",
42
+ "bs58": "6.0.0",
43
+ "tsup": "8.5.1",
44
+ "typescript": "5.9.3",
45
+ "vitest": "1.6.1",
46
+ "@moon-x/signing-codec": "0.1.0"
47
+ },
48
+ "engines": {
49
+ "node": ">=18"
50
+ },
51
+ "scripts": {
52
+ "build": "tsup",
53
+ "dev": "tsup --watch",
54
+ "test": "vitest run",
55
+ "test:watch": "vitest",
56
+ "clean": "rm -rf dist .turbo node_modules"
57
+ }
58
+ }