@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 +65 -5
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/signers/evm.d.ts +38 -0
- package/dist/signers/evm.js +82 -0
- package/dist/signers/polkadot.d.ts +25 -0
- package/dist/signers/polkadot.js +71 -0
- package/dist/signers/solana.d.ts +12 -0
- package/dist/signers/solana.js +35 -0
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -8,10 +8,10 @@ and using JamScript Ownership authentication.
|
|
|
8
8
|
|
|
9
9
|
## Install
|
|
10
10
|
|
|
11
|
-
The
|
|
11
|
+
The current prerelease is:
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
npm install @jamscript/client@0.1.0-rc.
|
|
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
|
-
|
|
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
|
|
119
|
-
|
|
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
package/dist/index.js
CHANGED
|
@@ -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.
|
|
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"
|