@parity/product-sdk-terminal 0.0.0-dev.312.0 → 0.0.0-dev.421.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 +62 -16
- package/dist/chunk-EORPX5XL.js +267 -0
- package/dist/chunk-EORPX5XL.js.map +1 -0
- package/dist/chunk-GHOFKVUG.js +8 -133
- package/dist/chunk-GHOFKVUG.js.map +1 -1
- package/dist/{host-C3KVvc1j.d.ts → host-_G9M0isV.d.ts} +36 -10
- package/dist/host.d.ts +1 -1
- package/dist/host.js +2 -14
- package/dist/host.js.map +1 -1
- package/dist/index.d.ts +64 -37
- package/dist/index.js +131 -874
- package/dist/index.js.map +1 -1
- package/dist/testing.d.ts +2 -2
- package/dist/testing.js +24 -234
- package/dist/testing.js.map +1 -1
- package/package.json +7 -8
- package/dist/chunk-YS3GW2XW.js +0 -1508
- package/dist/chunk-YS3GW2XW.js.map +0 -1
package/README.md
CHANGED
|
@@ -50,7 +50,7 @@ const sessions = await waitForSessions(adapter, 2000);
|
|
|
50
50
|
// 5. Sign messages via the paired wallet
|
|
51
51
|
if (sessions.length > 0) {
|
|
52
52
|
const session = sessions[0];
|
|
53
|
-
const signer = createSessionSigner(session, adapter);
|
|
53
|
+
const signer = await createSessionSigner(session, adapter);
|
|
54
54
|
// use signer with polkadot-api transactions
|
|
55
55
|
}
|
|
56
56
|
```
|
|
@@ -63,7 +63,7 @@ Creates a terminal adapter backed by the host-papp SDK.
|
|
|
63
63
|
|
|
64
64
|
**Options:**
|
|
65
65
|
- `appId` -- unique app identifier (used as storage namespace)
|
|
66
|
-
- `endpoints?` -- statement store WebSocket endpoints (defaults to
|
|
66
|
+
- `endpoints?` -- statement store WebSocket endpoints (defaults to `StatementStoreNetworks.paseo`)
|
|
67
67
|
- `hostMetadata?` -- optional host environment info
|
|
68
68
|
- `storageDir?` -- override the on-disk session directory (defaults to `~/.polkadot-apps/`). Useful in tests and containerised environments.
|
|
69
69
|
|
|
@@ -71,34 +71,78 @@ Creates a terminal adapter backed by the host-papp SDK.
|
|
|
71
71
|
- `appId` -- the value you passed in (re-exposed so `createSessionSigner` can pull the productId from the adapter)
|
|
72
72
|
- `sso` -- auth component (`.authenticate()`, `.abortAuthentication()`, status subscriptions)
|
|
73
73
|
- `sessions` -- session manager (signing, disconnect)
|
|
74
|
-
- `destroy()` -- disconnect the WebSocket and release resources. Idempotent.
|
|
74
|
+
- `destroy()` -- disconnect the WebSocket and release resources. Idempotent. Drops `@novasamatech/statement-store`'s benign `Statement subscription error: Client destroyed` line, which it emits synchronously while disconnecting a live subscription — only that line; every other `console.error` passes through.
|
|
75
75
|
|
|
76
|
-
### `
|
|
76
|
+
### `StatementStoreNetworks`
|
|
77
|
+
|
|
78
|
+
The statement store endpoints terminal can pair against, keyed by network.
|
|
79
|
+
|
|
80
|
+
| Key | Network | Endpoint |
|
|
81
|
+
| --- | --- | --- |
|
|
82
|
+
| `paseo` | Paseo Next v2 | `wss://paseo-people-next-system-rpc.polkadot.io` |
|
|
83
|
+
| `previewnet` | zombienet, a step ahead of paseo | whatever host-papp's `SS_PREVIEW_STAGE_ENDPOINTS` resolves to, today `wss://previewnet.substrate.dev/people` |
|
|
84
|
+
|
|
85
|
+
Keyed the same way as `BULLETIN_RPCS` in `@parity/product-sdk-host`. There is deliberately no
|
|
86
|
+
`devnet` key: that people chain has a statement store, but no endpoint for it is published here, and
|
|
87
|
+
no phone build pairs against it. Pass `endpoints` explicitly to reach one.
|
|
88
|
+
|
|
89
|
+
Pairing only works when the terminal and the phone are on the **same** people chain, and the phone
|
|
90
|
+
picks its chain per build flavour. The Polkadot app's nightly build uses `paseo`, which is why that
|
|
91
|
+
is the default; a preview-flavour phone needs `endpoints: StatementStoreNetworks.previewnet`.
|
|
92
|
+
|
|
93
|
+
`SS_STABLE_STAGE_ENDPOINTS` is also exported. It is internal to the Parity network and does not
|
|
94
|
+
answer from outside it.
|
|
95
|
+
|
|
96
|
+
### `isBenignTeardownError(error): boolean`
|
|
97
|
+
|
|
98
|
+
Whether `error` is the benign teardown noise above (`Client destroyed`, matched on the message so it never swallows another package's `DestroyedError`). `destroy()` already drops it during its own teardown; export it so a consumer with its own `console.error` guard can drop the same line without reinventing the match.
|
|
99
|
+
|
|
100
|
+
### `createSessionSigner(session, adapter, publicKey?, options?): Promise<PolkadotSigner>`
|
|
77
101
|
|
|
78
102
|
Creates a `PolkadotSigner` backed by a QR-paired mobile wallet session, using the session's **default account** (`derivationIndex: 0`) under the adapter's `appId`. This is the right entry point for ~all CLI flows.
|
|
79
103
|
|
|
104
|
+
Async because the signer needs the product's subtree public key (RFC-0022), which only the paired wallet holds. That is one consent-free round trip the first time a product is seen; afterwards it is served from a cache on disk.
|
|
105
|
+
|
|
80
106
|
```ts
|
|
81
107
|
const [session] = adapter.sessions.sessions.read();
|
|
82
|
-
const signer = createSessionSigner(session, adapter);
|
|
108
|
+
const signer = await createSessionSigner(session, adapter);
|
|
83
109
|
await contract.publish.tx(domain, cid, { signer, origin });
|
|
84
110
|
```
|
|
85
111
|
|
|
86
|
-
### `createSessionSignerForAccount(session, ref): PolkadotSigner
|
|
112
|
+
### `createSessionSignerForAccount(session, ref, options?): Promise<PolkadotSigner>`
|
|
87
113
|
|
|
88
114
|
Escape hatch for signing as a non-default sub-account of a paired session, or as a `productId` that differs from the adapter's `appId`. Most callers don't need this.
|
|
89
115
|
|
|
90
116
|
`ref` is `{ productId: string; derivationIndex: number }`:
|
|
91
117
|
- `productId` -- dotNS-style identifier of the requesting product. In normal usage this equals the adapter's `appId`; pass a different value only if you have an explicit reason.
|
|
92
|
-
- `derivationIndex` --
|
|
118
|
+
- `derivationIndex` -- child-key index within the product's subtree. `0` is the default account; non-zero indices reach additional sub-accounts of the same product.
|
|
93
119
|
|
|
94
120
|
```ts
|
|
95
|
-
const subSigner = createSessionSignerForAccount(session, {
|
|
121
|
+
const subSigner = await createSessionSignerForAccount(session, {
|
|
96
122
|
productId: "my-product",
|
|
97
123
|
derivationIndex: 3,
|
|
98
124
|
});
|
|
99
125
|
```
|
|
100
126
|
|
|
101
|
-
|
|
127
|
+
Both factories accept `SessionSignerOptions`, extending `ProductSubtreeOptions` with optional `txExtVersion` (default `0`). For example, pass `{ txExtVersion: 1 }` as the final `options` argument. The extension bytes supplied to `signTx` must match that version. PAPI 2.1.6's standard transaction builder encodes version `0`; this option does not change its encoder.
|
|
128
|
+
|
|
129
|
+
> **Wire format note:** `@novasamatech/host-papp` expects `productAccountId: [productId, { tag: "Index", value: derivationIndex }]` in `SigningRawRequest`. Both functions above hide that tuple — pass an adapter for the default case or a named-fields object for the escape hatch.
|
|
130
|
+
|
|
131
|
+
### `getProductSubtreePublicKey(session, productId, options?): Promise<Uint8Array>`
|
|
132
|
+
|
|
133
|
+
Fetches the sr25519 public key of `//product//{productId}` from the paired wallet (RFC-0022). Both signer factories call it for you; reach for it directly to warm the cache before going offline, or to derive a product address without building a signer.
|
|
134
|
+
|
|
135
|
+
The request is consent-free, so it raises no prompt on the phone, but it does need the phone reachable. The result is cached in memory and on disk, so only a cold cache reaches the wallet.
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
await getProductSubtreePublicKey(session, adapter.appId, { appId: adapter.appId });
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
`options` is `ProductSubtreeOptions`:
|
|
142
|
+
- `appId` -- names the cache file (`~/.polkadot-apps/${appId}_ProductSubtrees.json`). Defaults to `productId`. **Pass your app id.** Anything that sweeps `~/.polkadot-apps/` by app-id prefix, such as a sign-out that clears local state, only finds the file if it is named after the app.
|
|
143
|
+
- `storageDir` -- overrides `~/.polkadot-apps/`, matching `createTerminalAdapter({ storageDir })`.
|
|
144
|
+
|
|
145
|
+
The cache file is written with mode `0600` and keyed by session and product, so re-pairing or switching wallet account does not reuse a stale key.
|
|
102
146
|
|
|
103
147
|
### `renderQrCode(data, options?): Promise<string>`
|
|
104
148
|
|
|
@@ -238,20 +282,22 @@ For consumers moving from `@polkadot-apps/terminal` v0.2.0 / v0.3.0. Existing se
|
|
|
238
282
|
| Package name | `@polkadot-apps/terminal` | `@parity/product-sdk-terminal` |
|
|
239
283
|
| WASM loader hook | `--import @polkadot-apps/terminal/register` (required) | **removed** — no `--import` needed (host-papp ≥0.7.9 dropped `verifiablejs`) |
|
|
240
284
|
| `createTerminalAdapter` | `async` — returned `Promise<TerminalAdapter>` | **sync** — returns `TerminalAdapter` directly. Drop the `await`. |
|
|
241
|
-
| Default account signer | `createSessionSigner(session)` | `createSessionSigner(session, adapter)` — pass the adapter as second arg |
|
|
242
|
-
| Non-default sub-account signer | not exposed | `createSessionSignerForAccount(session, { productId, derivationIndex })` |
|
|
285
|
+
| Default account signer | `createSessionSigner(session)` | `await createSessionSigner(session, adapter)` — pass the adapter as second arg, and await it |
|
|
286
|
+
| Non-default sub-account signer | not exposed | `await createSessionSignerForAccount(session, { productId, derivationIndex })` |
|
|
243
287
|
| Override session storage dir | not supported (hard-coded `~/.polkadot-apps/`) | `createTerminalAdapter({ ..., storageDir })` option |
|
|
244
288
|
| E2E test helper for sessions | none | `createTestSession` from `@parity/product-sdk-terminal/testing` |
|
|
245
289
|
| Node version | any (bundled `ws`) | **≥21** (uses global `WebSocket`) |
|
|
246
|
-
| `destroy()` shutdown noise | emitted `Statement subscription error` to stderr |
|
|
290
|
+
| `destroy()` shutdown noise | emitted `Statement subscription error` to stderr | only the benign `Client destroyed` line dropped (synchronously, during disconnect); other `console.error` untouched |
|
|
247
291
|
|
|
248
292
|
### Why the signer API changed
|
|
249
293
|
|
|
250
|
-
`@novasamatech/host-papp` 0.7 replaced `SigningRawRequest.address` with `productAccountId: [productId, derivationIndex]
|
|
294
|
+
`@novasamatech/host-papp` 0.7 replaced `SigningRawRequest.address` with `productAccountId: [productId, derivationIndex]`, and 0.10 made the index a tagged `Index | Raw` selector. The wire format requires both fields, so a session-only argument is no longer enough — the signer needs to know *which sub-account of which product is asking*. We split that into two functions to keep the common case ergonomic:
|
|
251
295
|
|
|
252
|
-
- `createSessionSigner(session, adapter)` for the default account (uses `[adapter.appId, 0]`)
|
|
296
|
+
- `createSessionSigner(session, adapter)` for the default account (uses `[adapter.appId, { tag: "Index", value: 0 }]`)
|
|
253
297
|
- `createSessionSignerForAccount(session, { productId, derivationIndex })` for everything else
|
|
254
298
|
|
|
299
|
+
Both return a promise since RFC-0022: a product account hangs off `//product//{productId}`, whose two hard junctions no public key can cross, so the subtree key has to be fetched from the paired wallet once per product.
|
|
300
|
+
|
|
255
301
|
The single-argument `createSessionSigner(session)` from `@polkadot-apps/terminal` no longer works against host-papp 0.7 regardless of which package you use.
|
|
256
302
|
|
|
257
303
|
### Migration steps
|
|
@@ -259,7 +305,7 @@ The single-argument `createSessionSigner(session)` from `@polkadot-apps/terminal
|
|
|
259
305
|
1. **Replace the dep**: `pnpm remove @polkadot-apps/terminal && pnpm add @parity/product-sdk-terminal`
|
|
260
306
|
2. **Remove the `--import @.../register` flag** from your `node` / `tsx` invocations or `package.json` scripts — the WASM loader hook no longer exists (and is no longer needed).
|
|
261
307
|
3. **Drop `await`** in front of `createTerminalAdapter(...)` calls.
|
|
262
|
-
4. **Update each `createSessionSigner` call site**: change `createSessionSigner(session)` → `createSessionSigner(session, adapter)`.
|
|
308
|
+
4. **Update each `createSessionSigner` call site**: change `createSessionSigner(session)` → `await createSessionSigner(session, adapter)`.
|
|
263
309
|
5. **Verify Node version** is ≥21 (`node --version`).
|
|
264
310
|
|
|
265
311
|
If your existing sessions don't appear after migrating, double-check that the `appId` is identical to what you used in `@polkadot-apps/terminal` — the on-disk file names depend on it.
|
|
@@ -300,7 +346,7 @@ const sessions = await waitForSessions(adapter);
|
|
|
300
346
|
|
|
301
347
|
After login and attestation, the paired wallet can sign both transactions and raw messages via the statement store. The `PolkadotSigner` returned by `createSessionSigner` routes each path to the right host-papp method:
|
|
302
348
|
|
|
303
|
-
- **Transactions** (`signTx
|
|
349
|
+
- **Transactions** (`signTx`, used by `submitAndWatch`, `signSubmitAndWatch`, and contract calls) go through `session.createTransaction`. The signer forwards the account, call data, `txExtVersion`, and every extension's encoded `extra` and `additionalSigned` bytes, including unknown extensions such as `AsPgas`. The wallet decodes the transaction for display and returns the signed extrinsic.
|
|
304
350
|
- **Raw bytes** (`signBytes`) go through `session.signRaw` with the `Bytes` tag. Mobile applies the standard `<Bytes>...</Bytes>` anti-phishing wrap before signing — appropriate for arbitrary data, the same behavior `signRaw` has across all Polkadot wallets.
|
|
305
351
|
|
|
306
352
|
> **Note on previous bugs.** Versions prior to `0.1.1` routed *all* signing through `signRaw` with no tag distinction, producing `BadProof`-rejected signatures. Versions `0.1.1` through `0.2.x` then used `polkadot-api/pjs-signer`, which threw `PJS does not support this signed-extension: <name>` on any extension outside its eight built-in mappers (notably `AsPgas`). The current path bypasses both pitfalls.
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { createLogger } from '@parity/product-sdk-logger';
|
|
2
|
+
import { fromHex, toHex } from '@polkadot-api/utils';
|
|
3
|
+
import { readFile, mkdir, writeFile, rename } from 'fs/promises';
|
|
4
|
+
import { homedir } from 'os';
|
|
5
|
+
import { join, dirname } from 'path';
|
|
6
|
+
import { numberToBytesLE, bytesToNumberLE } from '@noble/curves/utils.js';
|
|
7
|
+
import { createDerive, sr25519Derive, sr25519 } from '@polkadot-labs/hdkd-helpers';
|
|
8
|
+
import { getPolkadotSigner } from 'polkadot-api/signer';
|
|
9
|
+
|
|
10
|
+
// src/host.ts
|
|
11
|
+
var log = createLogger("terminal");
|
|
12
|
+
var DEFAULT_STORAGE_DIR = join(homedir(), ".polkadot-apps");
|
|
13
|
+
var CACHE_FILE_MODE = 384;
|
|
14
|
+
function sanitizeAppId(appId) {
|
|
15
|
+
return appId.replace(/[^a-zA-Z0-9_.-]/g, "_");
|
|
16
|
+
}
|
|
17
|
+
function cacheFilePath(appId, kind, storageDir) {
|
|
18
|
+
return join(storageDir ?? DEFAULT_STORAGE_DIR, `${sanitizeAppId(appId)}_${kind}.json`);
|
|
19
|
+
}
|
|
20
|
+
async function loadJsonCache(path, version, label) {
|
|
21
|
+
let raw;
|
|
22
|
+
try {
|
|
23
|
+
raw = await readFile(path, "utf-8");
|
|
24
|
+
} catch (e) {
|
|
25
|
+
if (e?.code === "ENOENT") return null;
|
|
26
|
+
throw e;
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
const parsed = JSON.parse(raw);
|
|
30
|
+
if (parsed?.version !== version) {
|
|
31
|
+
log.warn(`${label} schema mismatch; starting fresh`, { path });
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
return parsed;
|
|
35
|
+
} catch (e) {
|
|
36
|
+
log.warn(`${label} parse failed; starting fresh`, { path, error: String(e) });
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async function saveJsonCache(path, value) {
|
|
41
|
+
await mkdir(dirname(path), { recursive: true });
|
|
42
|
+
const tmp = `${path}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
43
|
+
await writeFile(tmp, JSON.stringify(value, null, 2), { mode: CACHE_FILE_MODE });
|
|
44
|
+
await rename(tmp, path);
|
|
45
|
+
}
|
|
46
|
+
var locks = /* @__PURE__ */ new Map();
|
|
47
|
+
function withFileLock(path, fn) {
|
|
48
|
+
const prev = locks.get(path) ?? Promise.resolve();
|
|
49
|
+
const next = prev.catch(() => {
|
|
50
|
+
}).then(fn);
|
|
51
|
+
locks.set(
|
|
52
|
+
path,
|
|
53
|
+
next.catch(() => {
|
|
54
|
+
})
|
|
55
|
+
);
|
|
56
|
+
return next;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// src/host-cache.ts
|
|
60
|
+
function cachePath(appId, storageDir) {
|
|
61
|
+
return cacheFilePath(appId, "AllowanceKeys", storageDir);
|
|
62
|
+
}
|
|
63
|
+
function emptyCache() {
|
|
64
|
+
return { version: 2, entries: {} };
|
|
65
|
+
}
|
|
66
|
+
async function loadCache(appId, storageDir) {
|
|
67
|
+
const path = cachePath(appId, storageDir);
|
|
68
|
+
const cache = await loadJsonCache(path, 2, "allowance cache");
|
|
69
|
+
if (!cache || typeof cache.entries !== "object" || cache.entries === null) {
|
|
70
|
+
return emptyCache();
|
|
71
|
+
}
|
|
72
|
+
return cache;
|
|
73
|
+
}
|
|
74
|
+
async function saveCache(appId, cache, storageDir) {
|
|
75
|
+
await saveJsonCache(cachePath(appId, storageDir), cache);
|
|
76
|
+
}
|
|
77
|
+
function smartContractDest(dest) {
|
|
78
|
+
return dest.tag === "Index" ? `Index::${dest.value}` : `Raw::${toHex(dest.value)}`;
|
|
79
|
+
}
|
|
80
|
+
function cacheKey(resource) {
|
|
81
|
+
if (resource.tag === "SmartContractAllowance") {
|
|
82
|
+
return `${resource.tag}::${smartContractDest(resource.value)}`;
|
|
83
|
+
}
|
|
84
|
+
return resource.tag;
|
|
85
|
+
}
|
|
86
|
+
function pickOnExistingPolicy(cache, resources) {
|
|
87
|
+
if (resources.length === 0) return "Ignore";
|
|
88
|
+
const allSlotTableAndCached = resources.every((r) => {
|
|
89
|
+
if (r.tag !== "BulletInAllowance" && r.tag !== "StatementStoreAllowance") return false;
|
|
90
|
+
return Object.hasOwn(cache.entries, cacheKey(r));
|
|
91
|
+
});
|
|
92
|
+
return allSlotTableAndCached ? "Increase" : "Ignore";
|
|
93
|
+
}
|
|
94
|
+
function mergeOutcomes(cache, requested, outcomes) {
|
|
95
|
+
if (requested.length !== outcomes.length) {
|
|
96
|
+
throw new Error(
|
|
97
|
+
`mergeOutcomes: length mismatch \u2014 requested ${requested.length}, got ${outcomes.length}`
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
const entries = { ...cache.entries };
|
|
101
|
+
let mutated = false;
|
|
102
|
+
for (let i = 0; i < outcomes.length; i++) {
|
|
103
|
+
const req = requested[i];
|
|
104
|
+
const out = outcomes[i];
|
|
105
|
+
if (out.tag !== "Allocated") continue;
|
|
106
|
+
const inner = out.value;
|
|
107
|
+
if (inner.tag !== req.tag) {
|
|
108
|
+
throw new Error(
|
|
109
|
+
`mergeOutcomes: variant mismatch at index ${i} \u2014 requested ${req.tag}, got Allocated<${inner.tag}>`
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
const key = cacheKey(req);
|
|
113
|
+
switch (inner.tag) {
|
|
114
|
+
case "BulletInAllowance":
|
|
115
|
+
case "StatementStoreAllowance":
|
|
116
|
+
entries[key] = {
|
|
117
|
+
tag: inner.tag,
|
|
118
|
+
slotAccountKey: toHex(inner.value.slotAccountKey)
|
|
119
|
+
};
|
|
120
|
+
mutated = true;
|
|
121
|
+
break;
|
|
122
|
+
case "SmartContractAllowance":
|
|
123
|
+
if (req.tag === "SmartContractAllowance") {
|
|
124
|
+
entries[key] = {
|
|
125
|
+
tag: "SmartContractAllowance",
|
|
126
|
+
dest: smartContractDest(req.value)
|
|
127
|
+
};
|
|
128
|
+
mutated = true;
|
|
129
|
+
}
|
|
130
|
+
break;
|
|
131
|
+
case "AutoSigning":
|
|
132
|
+
entries[key] = {
|
|
133
|
+
tag: "AutoSigning",
|
|
134
|
+
ringVrfDomainEntropy: toHex(inner.value.ringVrfDomainEntropy),
|
|
135
|
+
productRootPrivateKey: toHex(inner.value.productRootPrivateKey)
|
|
136
|
+
};
|
|
137
|
+
mutated = true;
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return mutated ? { version: 2, entries } : cache;
|
|
142
|
+
}
|
|
143
|
+
function readCacheEntry(cache, resource) {
|
|
144
|
+
return cache.entries[cacheKey(resource)] ?? null;
|
|
145
|
+
}
|
|
146
|
+
function withCacheLock(appId, storageDir, fn) {
|
|
147
|
+
return withFileLock(cachePath(appId, storageDir), fn);
|
|
148
|
+
}
|
|
149
|
+
function canonicalSr25519SecretToEd25519Bytes(secret) {
|
|
150
|
+
const ed25519Scalar = numberToBytesLE(bytesToNumberLE(secret.subarray(0, 32)) << 3n, 32);
|
|
151
|
+
const out = new Uint8Array(64);
|
|
152
|
+
out.set(ed25519Scalar, 0);
|
|
153
|
+
out.set(secret.subarray(32, 64), 32);
|
|
154
|
+
return out;
|
|
155
|
+
}
|
|
156
|
+
function buildKeypair(secret) {
|
|
157
|
+
if (secret.length === 32) {
|
|
158
|
+
const derive = createDerive({ seed: secret, curve: sr25519, derive: sr25519Derive });
|
|
159
|
+
const keypair = derive("");
|
|
160
|
+
return { publicKey: keypair.publicKey, sign: (data) => keypair.sign(data) };
|
|
161
|
+
}
|
|
162
|
+
if (secret.length === 64) {
|
|
163
|
+
const ed25519Secret = canonicalSr25519SecretToEd25519Bytes(secret);
|
|
164
|
+
return {
|
|
165
|
+
publicKey: sr25519.getPublicKey(ed25519Secret),
|
|
166
|
+
sign: (data) => sr25519.sign(data, ed25519Secret)
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
throw new Error(
|
|
170
|
+
`createSlotAccountSigner: unexpected slotAccountKey length ${secret.length}. Expected 32 (mini-secret) or 64 (expanded secret).`
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
function buildSignerFromEntry(entry) {
|
|
174
|
+
if (entry.tag !== "BulletInAllowance" && entry.tag !== "StatementStoreAllowance") {
|
|
175
|
+
throw new Error(
|
|
176
|
+
`createSlotAccountSigner: ${entry.tag} does not carry a slot account key. Slot-table signing is defined for BulletInAllowance and StatementStoreAllowance only.`
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
const secret = fromHex(entry.slotAccountKey);
|
|
180
|
+
const { publicKey, sign } = buildKeypair(secret);
|
|
181
|
+
return getPolkadotSigner(publicKey, "Sr25519", async (data) => sign(data));
|
|
182
|
+
}
|
|
183
|
+
async function createSlotAccountSigner(adapter, resource, productId) {
|
|
184
|
+
const cache = await loadCache(productId ?? adapter.appId, adapter.storageDir);
|
|
185
|
+
const entry = readCacheEntry(cache, resource);
|
|
186
|
+
if (!entry) return null;
|
|
187
|
+
return buildSignerFromEntry(entry);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// src/host.ts
|
|
191
|
+
var log2 = createLogger("terminal");
|
|
192
|
+
async function requestResourceAllocation(session, adapter, resources, options = {}) {
|
|
193
|
+
const productId = options.productId ?? adapter.appId;
|
|
194
|
+
return withCacheLock(productId, adapter.storageDir, async () => {
|
|
195
|
+
const cache = await loadCache(productId, adapter.storageDir);
|
|
196
|
+
const onExisting = options.onExisting ?? pickOnExistingPolicy(cache, resources);
|
|
197
|
+
log2.debug("requestResourceAllocation", {
|
|
198
|
+
productId,
|
|
199
|
+
resources: resources.map((r) => r.tag),
|
|
200
|
+
onExisting,
|
|
201
|
+
autoPolicy: options.onExisting === void 0
|
|
202
|
+
});
|
|
203
|
+
const outcomes = await sendResourceAllocation(session, productId, resources, onExisting);
|
|
204
|
+
const next = mergeOutcomes(cache, resources, outcomes);
|
|
205
|
+
if (next !== cache) {
|
|
206
|
+
await saveCache(productId, next, adapter.storageDir);
|
|
207
|
+
}
|
|
208
|
+
return outcomes;
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
async function sendResourceAllocation(session, productId, resources, onExisting) {
|
|
212
|
+
const result = await session.requestResourceAllocation({
|
|
213
|
+
callingProductId: productId,
|
|
214
|
+
resources,
|
|
215
|
+
onExisting
|
|
216
|
+
});
|
|
217
|
+
if (result.isErr()) {
|
|
218
|
+
throw new Error(`requestResourceAllocation failed: ${result.error.message}`);
|
|
219
|
+
}
|
|
220
|
+
return result.value;
|
|
221
|
+
}
|
|
222
|
+
async function getCachedAllocation(adapter, resource, productId) {
|
|
223
|
+
const cache = await loadCache(productId ?? adapter.appId, adapter.storageDir);
|
|
224
|
+
return readCacheEntry(cache, resource);
|
|
225
|
+
}
|
|
226
|
+
async function ensureSlotAccountSigner(session, adapter, resource, productId) {
|
|
227
|
+
const resolvedProductId = productId ?? adapter.appId;
|
|
228
|
+
return withCacheLock(resolvedProductId, adapter.storageDir, async () => {
|
|
229
|
+
const cache = await loadCache(resolvedProductId, adapter.storageDir);
|
|
230
|
+
const hit = readCacheEntry(cache, resource);
|
|
231
|
+
if (hit) return buildSignerFromEntry(hit);
|
|
232
|
+
log2.debug("ensureSlotAccountSigner: cache miss, allocating", { resource: resource.tag });
|
|
233
|
+
const onExisting = pickOnExistingPolicy(cache, [resource]);
|
|
234
|
+
const outcomes = await sendResourceAllocation(
|
|
235
|
+
session,
|
|
236
|
+
resolvedProductId,
|
|
237
|
+
[resource],
|
|
238
|
+
onExisting
|
|
239
|
+
);
|
|
240
|
+
const next = mergeOutcomes(cache, [resource], outcomes);
|
|
241
|
+
if (next !== cache) {
|
|
242
|
+
await saveCache(resolvedProductId, next, adapter.storageDir);
|
|
243
|
+
}
|
|
244
|
+
const outcome = outcomes[0];
|
|
245
|
+
if (!outcome) {
|
|
246
|
+
throw new Error(
|
|
247
|
+
`ensureSlotAccountSigner: protocol violation \u2014 empty outcomes for ${resource.tag}`
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
if (outcome.tag !== "Allocated") {
|
|
251
|
+
throw new Error(
|
|
252
|
+
`ensureSlotAccountSigner: allocation ${outcome.tag} for ${resource.tag}`
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
const freshEntry = readCacheEntry(next, resource);
|
|
256
|
+
if (!freshEntry) {
|
|
257
|
+
throw new Error(
|
|
258
|
+
`ensureSlotAccountSigner: allocation succeeded but cache lookup returned null for ${resource.tag}`
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
return buildSignerFromEntry(freshEntry);
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export { cacheFilePath, createSlotAccountSigner, ensureSlotAccountSigner, getCachedAllocation, loadJsonCache, requestResourceAllocation, saveJsonCache, sendResourceAllocation, withFileLock };
|
|
266
|
+
//# sourceMappingURL=chunk-EORPX5XL.js.map
|
|
267
|
+
//# sourceMappingURL=chunk-EORPX5XL.js.map
|