@jamscript/client 0.1.0-rc.1 → 0.1.0-rc.2

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 CHANGED
@@ -8,10 +8,10 @@ and using JamScript Ownership authentication.
8
8
 
9
9
  ## Install
10
10
 
11
- The first public release is planned as an RC:
11
+ The current prerelease is:
12
12
 
13
13
  ```bash
14
- npm install @jamscript/client@0.1.0-rc.1
14
+ npm install @jamscript/client@0.1.0-rc.2
15
15
  ```
16
16
 
17
17
  The package is network-neutral. Deployment and transport configuration are
@@ -102,6 +102,66 @@ proof-verified responses from the configured provider.
102
102
  primitives. The package does not depend on `matrix-js-sdk`; applications own
103
103
  their Matrix transport and device lifecycle.
104
104
 
105
+ ## Ownership signers
106
+
107
+ Ownership adapters never hold private keys. They delegate signing to the
108
+ external wallet or provider and return the canonical JamScript authorization
109
+ proof expected by SignedActionV2.
110
+
111
+ ### EVM
112
+
113
+ EVM wallets are called through EIP-1193 `eth_signTypedData_v4`. The typed data
114
+ uses the JamScript `networkDomain` as its EIP-712 salt; the wallet's selected
115
+ EVM chain does not select the JamScript network.
116
+
117
+ ```ts
118
+ import { EvmOwnershipSigner } from "@jamscript/client";
119
+
120
+ const signer = new EvmOwnershipSigner(window.ethereum, accountAddress);
121
+ ```
122
+
123
+ The adapter does not use `personal_sign`, implement EIP-712 hashing, recover
124
+ secp256k1 keys, or normalize signatures. Those responsibilities remain with
125
+ the wallet and JamScript runtime.
126
+
127
+ ### Polkadot
128
+
129
+ Pass the AccountId32 obtained from the official SS58 decoder, the explicit
130
+ injected-account key scheme, and the extension signer:
131
+
132
+ ```ts
133
+ import {
134
+ PolkadotOwnershipSigner,
135
+ decodePolkadotAccountId,
136
+ } from "@jamscript/client";
137
+
138
+ const signer = new PolkadotOwnershipSigner({
139
+ accountId: decodePolkadotAccountId(account.address),
140
+ address: account.address,
141
+ scheme: account.type,
142
+ signer: injector.signer,
143
+ });
144
+ ```
145
+
146
+ SS58 is only a display/transport format. Ownership stores AccountId32. The
147
+ adapter prefixes the raw extension signature with JamScript's scheme byte for
148
+ ed25519, sr25519, or ecdsa; applications do not construct that envelope.
149
+
150
+ ### Solana
151
+
152
+ Solana Wallet Standard accounts map to generic Ed25519 Ownership. Only the
153
+ `solana:signMessage` feature is used; JamScript does not submit a Solana
154
+ transaction or require a Solana RPC endpoint.
155
+
156
+ ```ts
157
+ import { SolanaOwnershipSigner } from "@jamscript/client";
158
+
159
+ const signer = new SolanaOwnershipSigner(account, wallet.features);
160
+ ```
161
+
162
+ The full Wallet Standard discovery package belongs in the browser application.
163
+ The client core depends only on the small official sign-message feature types.
164
+
105
165
  ## Runtime support
106
166
 
107
167
  The package is ESM-only and is intended for modern browsers, Vite-based
@@ -109,14 +169,14 @@ applications, and current Node.js consumers. Runtime source does not import
109
169
  Node built-ins, and cryptographic/network dependencies remain external npm
110
170
  dependencies so the application bundler can tree-shake them.
111
171
 
112
- The first RC targets the JamScript CLI/runtime v0.1.0-rc.5 generation, Ownership
172
+ This RC targets the JamScript CLI/runtime v0.1.0-rc.5 generation, Ownership
113
173
  v1, SignedActionV1, SignedActionV2, and the current MiniJAM Stage-1 deployment
114
174
  target. MiniJAM is a supported deployment target, not the package boundary.
115
175
 
116
176
  ## Package status
117
177
 
118
- This is the first public release candidate. The client has an independent
119
- version cycle from the JamScript CLI, backend, and network releases.
178
+ This is a public release candidate. The client has an independent version
179
+ cycle from the JamScript CLI, backend, and network releases.
120
180
 
121
181
  Detailed protocol and integration documentation is available in the
122
182
  [JamScript repository](https://github.com/ArcheLabs/JamScript/tree/main/docs).
package/dist/index.d.ts CHANGED
@@ -8,3 +8,6 @@ export * from "./runtime.js";
8
8
  export * from "./signer.js";
9
9
  export * from "./state-provider.js";
10
10
  export * from "./matrix.js";
11
+ export * from "./signers/evm.js";
12
+ export * from "./signers/polkadot.js";
13
+ export * from "./signers/solana.js";
package/dist/index.js CHANGED
@@ -8,3 +8,6 @@ export * from "./runtime.js";
8
8
  export * from "./signer.js";
9
9
  export * from "./state-provider.js";
10
10
  export * from "./matrix.js";
11
+ export * from "./signers/evm.js";
12
+ export * from "./signers/polkadot.js";
13
+ export * from "./signers/solana.js";
@@ -0,0 +1,38 @@
1
+ import { type Ownership } from "../crypto.js";
2
+ import type { JamScriptOwnershipSignRequest, OwnershipSigner } from "../signer.js";
3
+ export type Eip1193Provider = {
4
+ request(args: {
5
+ method: string;
6
+ params?: readonly unknown[] | object;
7
+ }): Promise<unknown>;
8
+ };
9
+ export type EvmTypedData = {
10
+ types: {
11
+ EIP712Domain: readonly {
12
+ name: string;
13
+ type: string;
14
+ }[];
15
+ JamScriptAction: readonly {
16
+ name: string;
17
+ type: string;
18
+ }[];
19
+ };
20
+ primaryType: "JamScriptAction";
21
+ domain: {
22
+ name: "JamScript";
23
+ version: "1";
24
+ salt: string;
25
+ };
26
+ message: {
27
+ commitment: string;
28
+ };
29
+ };
30
+ export declare function createEvmTypedData(request: JamScriptOwnershipSignRequest): EvmTypedData;
31
+ export declare class EvmOwnershipSigner implements OwnershipSigner {
32
+ private readonly provider;
33
+ private readonly address;
34
+ private readonly ownership;
35
+ constructor(provider: Eip1193Provider, address: string);
36
+ getController(): Promise<Ownership>;
37
+ signJamScriptAction(request: JamScriptOwnershipSignRequest): Promise<Uint8Array>;
38
+ }
@@ -0,0 +1,82 @@
1
+ import { actionCommitmentV2, OWNERSHIP_KIND, parseHex, toHex } from "../crypto.js";
2
+ const EIP712_DOMAIN_TYPES = [
3
+ { name: "name", type: "string" },
4
+ { name: "version", type: "string" },
5
+ { name: "salt", type: "bytes32" },
6
+ ];
7
+ const JAMSCRIPT_ACTION_TYPES = [
8
+ { name: "commitment", type: "bytes32" },
9
+ ];
10
+ export function createEvmTypedData(request) {
11
+ return {
12
+ types: {
13
+ EIP712Domain: EIP712_DOMAIN_TYPES,
14
+ JamScriptAction: JAMSCRIPT_ACTION_TYPES,
15
+ },
16
+ primaryType: "JamScriptAction",
17
+ domain: {
18
+ name: "JamScript",
19
+ version: "1",
20
+ salt: toHex(request.networkDomain),
21
+ },
22
+ message: {
23
+ commitment: toHex(actionCommitmentV2(request)),
24
+ },
25
+ };
26
+ }
27
+ export class EvmOwnershipSigner {
28
+ provider;
29
+ address;
30
+ ownership;
31
+ constructor(provider, address) {
32
+ this.provider = provider;
33
+ this.address = address;
34
+ let publicKey;
35
+ try {
36
+ publicKey = parseHex(address, 20);
37
+ }
38
+ catch (cause) {
39
+ throw new Error("EVM_ACCOUNT_INVALID", { cause });
40
+ }
41
+ this.ownership = { version: 1, kind: OWNERSHIP_KIND.SECP256K1_KECCAK20, public: publicKey };
42
+ }
43
+ async getController() {
44
+ return { ...this.ownership, public: this.ownership.public.slice() };
45
+ }
46
+ async signJamScriptAction(request) {
47
+ const typedData = createEvmTypedData(request);
48
+ let result;
49
+ try {
50
+ result = await this.provider.request({
51
+ method: "eth_signTypedData_v4",
52
+ params: [this.address, JSON.stringify(typedData)],
53
+ });
54
+ }
55
+ catch (cause) {
56
+ if (isUnsupportedTypedDataError(cause)) {
57
+ throw new Error("EVM_TYPED_DATA_UNSUPPORTED", { cause });
58
+ }
59
+ throw cause;
60
+ }
61
+ if (typeof result !== "string")
62
+ throw new Error("EVM_SIGNATURE_INVALID");
63
+ const signature = parseSignature(result);
64
+ if (![0, 1, 27, 28].includes(signature[64]))
65
+ throw new Error("EVM_SIGNATURE_INVALID");
66
+ return signature;
67
+ }
68
+ }
69
+ function parseSignature(value) {
70
+ try {
71
+ return parseHex(value, 65);
72
+ }
73
+ catch (cause) {
74
+ throw new Error("EVM_SIGNATURE_INVALID", { cause });
75
+ }
76
+ }
77
+ function isUnsupportedTypedDataError(error) {
78
+ if (!error || typeof error !== "object")
79
+ return false;
80
+ const value = error;
81
+ return value.code === -32601 || value.code === 4200 || (typeof value.message === "string" && /not supported|unsupported/i.test(value.message));
82
+ }
@@ -0,0 +1,25 @@
1
+ import { type Ownership } from "../crypto.js";
2
+ import type { JamScriptOwnershipSignRequest, OwnershipSigner } from "../signer.js";
3
+ export type PolkadotSignatureScheme = "ed25519" | "sr25519" | "ecdsa";
4
+ export type PolkadotInjectedSigner = {
5
+ signRaw(input: {
6
+ address: string;
7
+ data: string;
8
+ type: "bytes";
9
+ }): Promise<{
10
+ signature: string;
11
+ }>;
12
+ };
13
+ export declare function decodePolkadotAccountId(address: string): Uint8Array;
14
+ export declare class PolkadotOwnershipSigner implements OwnershipSigner {
15
+ private readonly options;
16
+ private readonly ownership;
17
+ constructor(options: {
18
+ accountId: Uint8Array;
19
+ address: string;
20
+ scheme: PolkadotSignatureScheme;
21
+ signer: PolkadotInjectedSigner;
22
+ });
23
+ getController(): Promise<Ownership>;
24
+ signJamScriptAction(request: JamScriptOwnershipSignRequest): Promise<Uint8Array>;
25
+ }
@@ -0,0 +1,71 @@
1
+ import { OWNERSHIP_KIND } from "../crypto.js";
2
+ import { decodeAddress } from "@polkadot/util-crypto";
3
+ export function decodePolkadotAccountId(address) {
4
+ try {
5
+ const accountId = decodeAddress(address);
6
+ if (accountId.length !== 32)
7
+ throw new Error("decoded account is not AccountId32");
8
+ return Uint8Array.from(accountId);
9
+ }
10
+ catch (cause) {
11
+ throw new Error("POLKADOT_ACCOUNT_INVALID", { cause });
12
+ }
13
+ }
14
+ const SCHEME_BYTES = {
15
+ ed25519: 0,
16
+ sr25519: 1,
17
+ ecdsa: 2,
18
+ };
19
+ const SIGNATURE_LENGTHS = {
20
+ ed25519: 64,
21
+ sr25519: 64,
22
+ ecdsa: 65,
23
+ };
24
+ export class PolkadotOwnershipSigner {
25
+ options;
26
+ ownership;
27
+ constructor(options) {
28
+ this.options = options;
29
+ if (options.accountId.length !== 32)
30
+ throw new Error("POLKADOT_ACCOUNT_INVALID");
31
+ if (!Object.hasOwn(SCHEME_BYTES, options.scheme))
32
+ throw new Error("POLKADOT_SCHEME_UNSUPPORTED");
33
+ if (options.address.length === 0)
34
+ throw new Error("POLKADOT_ACCOUNT_INVALID");
35
+ this.ownership = {
36
+ version: 1,
37
+ kind: OWNERSHIP_KIND.MULTICRYPTO_ACCOUNT32,
38
+ public: options.accountId.slice(),
39
+ };
40
+ }
41
+ async getController() {
42
+ return { ...this.ownership, public: this.ownership.public.slice() };
43
+ }
44
+ async signJamScriptAction(request) {
45
+ const result = await this.options.signer.signRaw({
46
+ address: this.options.address,
47
+ data: bytesToHex(request.message),
48
+ type: "bytes",
49
+ });
50
+ let signature;
51
+ try {
52
+ signature = hexToBytes(result.signature);
53
+ }
54
+ catch (cause) {
55
+ throw new Error("POLKADOT_SIGNATURE_INVALID", { cause });
56
+ }
57
+ if (signature.length !== SIGNATURE_LENGTHS[this.options.scheme]) {
58
+ throw new Error("POLKADOT_SIGNATURE_INVALID");
59
+ }
60
+ return Uint8Array.of(SCHEME_BYTES[this.options.scheme], ...signature);
61
+ }
62
+ }
63
+ function bytesToHex(bytes) {
64
+ return "0x" + Array.from(bytes, (value) => value.toString(16).padStart(2, "0")).join("");
65
+ }
66
+ function hexToBytes(value) {
67
+ const hex = value.startsWith("0x") ? value.slice(2) : value;
68
+ if (hex.length % 2 !== 0 || !/^[0-9a-f]+$/i.test(hex))
69
+ throw new Error("invalid signature hex");
70
+ return Uint8Array.from(hex.match(/../g) ?? [], (pair) => Number.parseInt(pair, 16));
71
+ }
@@ -0,0 +1,12 @@
1
+ import type { SolanaSignMessageFeature } from "@solana/wallet-standard-features";
2
+ import type { WalletAccount } from "@wallet-standard/base";
3
+ import { type Ownership } from "../crypto.js";
4
+ import type { JamScriptOwnershipSignRequest, OwnershipSigner } from "../signer.js";
5
+ export declare class SolanaOwnershipSigner implements OwnershipSigner {
6
+ private readonly account;
7
+ private readonly signMessage;
8
+ private readonly ownership;
9
+ constructor(account: WalletAccount, signMessage: SolanaSignMessageFeature);
10
+ getController(): Promise<Ownership>;
11
+ signJamScriptAction(request: JamScriptOwnershipSignRequest): Promise<Uint8Array>;
12
+ }
@@ -0,0 +1,35 @@
1
+ import { SolanaSignMessage } from "@solana/wallet-standard-features";
2
+ import { OWNERSHIP_KIND } from "../crypto.js";
3
+ export class SolanaOwnershipSigner {
4
+ account;
5
+ signMessage;
6
+ ownership;
7
+ constructor(account, signMessage) {
8
+ this.account = account;
9
+ this.signMessage = signMessage;
10
+ if (account.publicKey.length !== 32)
11
+ throw new Error("SOLANA_PUBLIC_KEY_INVALID");
12
+ this.ownership = {
13
+ version: 1,
14
+ kind: OWNERSHIP_KIND.ED25519_KEY,
15
+ public: Uint8Array.from(account.publicKey),
16
+ };
17
+ }
18
+ async getController() {
19
+ return { ...this.ownership, public: this.ownership.public.slice() };
20
+ }
21
+ async signJamScriptAction(request) {
22
+ const feature = this.signMessage?.[SolanaSignMessage];
23
+ if (!feature || typeof feature.signMessage !== "function") {
24
+ throw new Error("SOLANA_SIGN_MESSAGE_UNSUPPORTED");
25
+ }
26
+ const [output] = await feature.signMessage({ account: this.account, message: request.message });
27
+ if (!output || !sameBytes(output.signedMessage, request.message) || output.signature.length !== 64 || (output.signatureType !== undefined && output.signatureType !== "ed25519")) {
28
+ throw new Error("SOLANA_SIGNATURE_INVALID");
29
+ }
30
+ return Uint8Array.from(output.signature);
31
+ }
32
+ }
33
+ function sameBytes(left, right) {
34
+ return left.length === right.length && left.every((value, index) => value === right[index]);
35
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jamscript/client",
3
- "version": "0.1.0-rc.1",
3
+ "version": "0.1.0-rc.2",
4
4
  "description": "TypeScript client for JamScript services, managed state and Ownership.",
5
5
  "private": false,
6
6
  "type": "module",
@@ -27,7 +27,9 @@
27
27
  "dependencies": {
28
28
  "@polkadot-api/substrate-bindings": "^0.21.0",
29
29
  "@polkadot/util": "^14.0.1",
30
- "@polkadot/util-crypto": "^14.0.1"
30
+ "@polkadot/util-crypto": "^14.0.1",
31
+ "@wallet-standard/base": "^1.1.1",
32
+ "@solana/wallet-standard-features": "^1.5.0"
31
33
  },
32
34
  "devDependencies": {
33
35
  "typescript": "^5.9.2"