@parity/product-sdk-terminal 0.2.1 → 0.3.1

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/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createNodeStorageAdapter
3
- } from "./chunk-5MCLSFKQ.js";
3
+ } from "./chunk-GHOFKVUG.js";
4
4
 
5
5
  // src/adapter.ts
6
6
  import {
@@ -26,7 +26,6 @@ function createTerminalAdapter(options) {
26
26
  const statementStore = createPapiStatementStoreAdapter(trackedLazyClient);
27
27
  const adapter = createPappAdapter({
28
28
  appId: options.appId,
29
- metadata: options.metadataUrl,
30
29
  hostMetadata: options.hostMetadata,
31
30
  adapters: {
32
31
  storage,
@@ -38,6 +37,7 @@ function createTerminalAdapter(options) {
38
37
  return {
39
38
  ...adapter,
40
39
  appId: options.appId,
40
+ storageDir: options.storageDir,
41
41
  destroy() {
42
42
  if (destroyPromise) return destroyPromise;
43
43
  destroyPromise = teardown(adapter.sessions, trackedLazyClient);
@@ -272,20 +272,57 @@ if (void 0) {
272
272
  var fakeLazyClient2;
273
273
 
274
274
  // src/signer.ts
275
- import { toHex } from "@polkadot-api/utils";
276
- import { getPolkadotSigner } from "polkadot-api/signer";
277
- function makeTxSignCallback(session, productAccountId) {
278
- return async (toSign) => {
279
- const result = await session.signRaw({
280
- productAccountId,
281
- data: { tag: "Payload", value: toHex(toSign) }
282
- });
283
- if (result.isErr()) {
284
- throw new Error(`Mobile signing rejected: ${result.error.message}`);
275
+ import { decAnyMetadata, unifyMetadata } from "@polkadot-api/substrate-bindings";
276
+ function buildCreateTransactionRequest(productAccountId, callData, signedExtensions, txExtVersion) {
277
+ const checkGenesis = signedExtensions.CheckGenesis;
278
+ if (!checkGenesis) {
279
+ throw new Error(
280
+ "Cannot build transaction: chain did not provide the CheckGenesis signed extension"
281
+ );
282
+ }
283
+ return {
284
+ payload: {
285
+ tag: "v1",
286
+ value: {
287
+ signer: productAccountId,
288
+ // CheckGenesis carries the genesis hash as its additionalSigned (implicit) data.
289
+ genesisHash: checkGenesis.additionalSigned,
290
+ callData,
291
+ extensions: Object.values(signedExtensions).map(
292
+ ({ identifier, value, additionalSigned }) => ({
293
+ id: identifier,
294
+ extra: value,
295
+ additionalSigned
296
+ })
297
+ ),
298
+ txExtVersion
299
+ }
285
300
  }
286
- return result.value.signature;
287
301
  };
288
302
  }
303
+ function txExtVersionFromMetadata(metadata) {
304
+ const decoded = unifyMetadata(decAnyMetadata(metadata));
305
+ const latestVersion = decoded.extrinsic.version.reduce((max, v) => Math.max(max, v), 0);
306
+ return latestVersion === 4 ? 0 : latestVersion;
307
+ }
308
+ async function requestSignedTransaction(session, request) {
309
+ const result = await session.createTransaction(request);
310
+ if (result.isErr()) {
311
+ throw new Error(`Mobile transaction signing rejected: ${result.error.message}`);
312
+ }
313
+ return result.value;
314
+ }
315
+ function makeTxSignTx(session, productAccountId) {
316
+ return async (callData, signedExtensions, metadata) => requestSignedTransaction(
317
+ session,
318
+ buildCreateTransactionRequest(
319
+ productAccountId,
320
+ callData,
321
+ signedExtensions,
322
+ txExtVersionFromMetadata(metadata)
323
+ )
324
+ );
325
+ }
289
326
  function makeRawBytesSignCallback(session, productAccountId) {
290
327
  return async (data) => {
291
328
  const result = await session.signRaw({
@@ -300,19 +337,21 @@ function makeRawBytesSignCallback(session, productAccountId) {
300
337
  }
301
338
  function buildSessionSigner(session, ref) {
302
339
  const productAccountId = [ref.productId, ref.derivationIndex];
303
- const publicKey = new Uint8Array(session.remoteAccount.accountId);
304
- const base = getPolkadotSigner(
305
- publicKey,
306
- "Sr25519",
307
- makeTxSignCallback(session, productAccountId)
308
- );
340
+ const publicKey = ref.publicKey ?? new Uint8Array(session.remoteAccount.accountId);
309
341
  return {
310
- ...base,
342
+ publicKey,
343
+ // Transaction signing → host-papp `createTransaction`: the wallet builds
344
+ // and signs the extrinsic, so it can show a decoded tx (no `<Bytes>`
345
+ // envelope) and unknown signed extensions (e.g. `AsPgas`) survive.
346
+ signTx: makeTxSignTx(session, productAccountId),
347
+ // Arbitrary-byte signing keeps the `Bytes` tag so the wallet applies the
348
+ // `<Bytes>...</Bytes>` anti-phishing envelope exactly once on its side
349
+ // (correct for non-extrinsic user data).
311
350
  signBytes: makeRawBytesSignCallback(session, productAccountId)
312
351
  };
313
352
  }
314
- function createSessionSigner(session, adapter) {
315
- return buildSessionSigner(session, { productId: adapter.appId, derivationIndex: 0 });
353
+ function createSessionSigner(session, adapter, publicKey) {
354
+ return buildSessionSigner(session, { productId: adapter.appId, derivationIndex: 0, publicKey });
316
355
  }
317
356
  function createSessionSignerForAccount(session, ref) {
318
357
  return buildSessionSigner(session, ref);
@@ -331,17 +370,27 @@ if (void 0) {
331
370
  opts.signRaw ?? (async () => {
332
371
  throw new Error("signRaw not stubbed in this test");
333
372
  })
373
+ ),
374
+ createTransaction: vi.fn(
375
+ opts.createTransaction ?? (async () => {
376
+ throw new Error("createTransaction not stubbed in this test");
377
+ })
334
378
  )
335
379
  };
380
+ }, ext = function(identifier, value, additionalSigned) {
381
+ return {
382
+ identifier,
383
+ value: new Uint8Array(value),
384
+ additionalSigned: new Uint8Array(additionalSigned)
385
+ };
336
386
  }, fakeAdapter = function(appId) {
337
387
  return { appId };
338
388
  };
339
- makeSession2 = makeSession, fakeAdapter2 = fakeAdapter;
389
+ makeSession2 = makeSession, ext2 = ext, fakeAdapter2 = fakeAdapter;
340
390
  const { describe, test, expect, vi } = void 0;
341
391
  const { ok, err } = await null;
342
- const { fromHex } = await null;
343
392
  describe("createSessionSigner", () => {
344
- test("exposes Sr25519 public key matching remoteAccount.accountId", () => {
393
+ test("falls back to remoteAccount.accountId when no product key is passed", () => {
345
394
  const bytes = Array.from({ length: 32 }, (_, i) => i);
346
395
  const signer = createSessionSigner(
347
396
  makeSession({ accountIdBytes: bytes }),
@@ -349,6 +398,17 @@ if (void 0) {
349
398
  );
350
399
  expect(signer.publicKey).toEqual(new Uint8Array(bytes));
351
400
  });
401
+ test("uses the host-supplied product-account public key when provided", () => {
402
+ const walletBytes = Array.from({ length: 32 }, (_, i) => i);
403
+ const productKey = new Uint8Array(32).fill(171);
404
+ const signer = createSessionSigner(
405
+ makeSession({ accountIdBytes: walletBytes }),
406
+ fakeAdapter("test-app"),
407
+ productKey
408
+ );
409
+ expect(signer.publicKey).toEqual(productKey);
410
+ expect(signer.publicKey).not.toEqual(new Uint8Array(walletBytes));
411
+ });
352
412
  test("signBytes routes through session.signRaw with the Bytes tag", async () => {
353
413
  const sig = new Uint8Array([9, 8, 7, 6, 5, 4, 3, 2, 1]);
354
414
  const captured = [];
@@ -387,75 +447,103 @@ if (void 0) {
387
447
  );
388
448
  });
389
449
  });
390
- describe("makeTxSignCallback \u2014 tx signing path (the AsPgas fix)", () => {
391
- test("forwards bytes-to-sign under the Payload tag with the right productAccountId", async () => {
392
- const captured = [];
393
- const session = makeSession({
394
- signRaw: async (req2) => {
395
- captured.push(req2);
396
- return ok({ signature: new Uint8Array([170, 187]) });
450
+ describe("buildCreateTransactionRequest \u2014 tx payload (the AsPgas fix)", () => {
451
+ const checkGenesis = ext("CheckGenesis", [], [17, 34, 51]);
452
+ test("wraps the payload as v1 with signer, callData, and txExtVersion", () => {
453
+ const callData = new Uint8Array([202, 17]);
454
+ const req = buildCreateTransactionRequest(
455
+ ["my-app", 3],
456
+ callData,
457
+ { CheckGenesis: checkGenesis },
458
+ 5
459
+ );
460
+ expect(req.payload.tag).toBe("v1");
461
+ expect(req.payload.value.signer).toEqual(["my-app", 3]);
462
+ expect(req.payload.value.callData).toEqual(callData);
463
+ expect(req.payload.value.txExtVersion).toBe(5);
464
+ });
465
+ test("takes the genesis hash from CheckGenesis.additionalSigned", () => {
466
+ const req = buildCreateTransactionRequest(
467
+ ["my-app", 0],
468
+ new Uint8Array([0]),
469
+ { CheckGenesis: checkGenesis },
470
+ 0
471
+ );
472
+ expect(req.payload.value.genesisHash).toEqual(new Uint8Array([17, 34, 51]));
473
+ });
474
+ test("maps every signed extension to { id, extra, additionalSigned }", () => {
475
+ const req = buildCreateTransactionRequest(
476
+ ["my-app", 0],
477
+ new Uint8Array([0]),
478
+ { CheckGenesis: checkGenesis, CheckNonce: ext("CheckNonce", [7], []) },
479
+ 0
480
+ );
481
+ expect(req.payload.value.extensions).toEqual([
482
+ {
483
+ id: "CheckGenesis",
484
+ extra: new Uint8Array([]),
485
+ additionalSigned: new Uint8Array([17, 34, 51])
486
+ },
487
+ {
488
+ id: "CheckNonce",
489
+ extra: new Uint8Array([7]),
490
+ additionalSigned: new Uint8Array([])
397
491
  }
398
- });
399
- const callback = makeTxSignCallback(session, ["my-app", 0]);
400
- await callback(new Uint8Array([222, 173, 190, 239]));
401
- expect(captured).toHaveLength(1);
402
- const req = captured[0];
403
- expect(req.productAccountId).toEqual(["my-app", 0]);
404
- expect(req.data.tag).toBe("Payload");
405
- expect(req.data.value).toBe("0xdeadbeef");
492
+ ]);
406
493
  });
407
- test("never calls session.signPayload (legacy PJS-shape regression guard)", async () => {
408
- const session = makeSession({
409
- signRaw: async () => ok({ signature: new Uint8Array([1]) })
494
+ test("preserves an unknown signed extension verbatim (e.g. AsPgas)", () => {
495
+ const req = buildCreateTransactionRequest(
496
+ ["my-app", 0],
497
+ new Uint8Array([0]),
498
+ { CheckGenesis: checkGenesis, AsPgas: ext("AsPgas", [222, 173], [190, 239]) },
499
+ 0
500
+ );
501
+ expect(req.payload.value.extensions).toContainEqual({
502
+ id: "AsPgas",
503
+ extra: new Uint8Array([222, 173]),
504
+ additionalSigned: new Uint8Array([190, 239])
410
505
  });
411
- const callback = makeTxSignCallback(session, ["my-app", 0]);
412
- await callback(new Uint8Array([1, 2, 3]));
413
- const sessionWithSpies = session;
414
- expect(sessionWithSpies.signPayload.mock.calls).toHaveLength(0);
415
- expect(sessionWithSpies.signRaw.mock.calls).toHaveLength(1);
416
506
  });
417
- test("returns the raw signature bytes from the mobile response", async () => {
418
- const sig = new Uint8Array([171, 205, 239]);
419
- const session = makeSession({
420
- signRaw: async () => ok({ signature: sig })
421
- });
422
- const callback = makeTxSignCallback(session, ["my-app", 0]);
423
- const out = await callback(new Uint8Array([0]));
424
- expect(out).toBe(sig);
507
+ test("throws a clear error when CheckGenesis is absent", () => {
508
+ expect(
509
+ () => buildCreateTransactionRequest(["my-app", 0], new Uint8Array([0]), {}, 0)
510
+ ).toThrow(/CheckGenesis/);
425
511
  });
426
- test("preserves arbitrary signed-extension payload bytes verbatim", async () => {
427
- const customExtensionBytes = new Uint8Array([
428
- // arbitrary scale-encoded extension payload — content
429
- // doesn't matter, only that it round-trips exactly
430
- 1,
431
- 2,
432
- 3,
433
- 255,
434
- 254,
435
- 253,
436
- 66,
437
- 67,
438
- 68
439
- ]);
512
+ });
513
+ describe("requestSignedTransaction \u2014 SSO round-trip", () => {
514
+ const request = buildCreateTransactionRequest(
515
+ ["my-app", 0],
516
+ new Uint8Array([0]),
517
+ { CheckGenesis: ext("CheckGenesis", [], [1]) },
518
+ 0
519
+ );
520
+ test("returns the signed extrinsic bytes from the mobile response", async () => {
521
+ const signed = new Uint8Array([171, 205, 239]);
522
+ const session = makeSession({ createTransaction: async () => ok(signed) });
523
+ const out = await requestSignedTransaction(session, request);
524
+ expect(out).toBe(signed);
525
+ });
526
+ test("calls session.createTransaction \u2014 never signRaw/signPayload \u2014 with the request", async () => {
440
527
  const captured = [];
441
528
  const session = makeSession({
442
- signRaw: async (req2) => {
443
- captured.push(req2);
444
- return ok({ signature: new Uint8Array([0]) });
529
+ createTransaction: async (req) => {
530
+ captured.push(req);
531
+ return ok(new Uint8Array([1]));
445
532
  }
446
533
  });
447
- const callback = makeTxSignCallback(session, ["my-app", 0]);
448
- await callback(customExtensionBytes);
449
- const req = captured[0];
450
- expect(fromHex(req.data.value)).toEqual(customExtensionBytes);
534
+ await requestSignedTransaction(session, request);
535
+ expect(captured).toEqual([request]);
536
+ const spies = session;
537
+ expect(spies.createTransaction.mock.calls).toHaveLength(1);
538
+ expect(spies.signPayload.mock.calls).toHaveLength(0);
539
+ expect(spies.signRaw.mock.calls).toHaveLength(0);
451
540
  });
452
541
  test("throws a clear error when the mobile rejects", async () => {
453
542
  const session = makeSession({
454
- signRaw: async () => err({ message: "user declined" })
543
+ createTransaction: async () => err({ message: "user declined" })
455
544
  });
456
- const callback = makeTxSignCallback(session, ["my-app", 0]);
457
- await expect(callback(new Uint8Array([0]))).rejects.toThrow(
458
- "Mobile signing rejected: user declined"
545
+ await expect(requestSignedTransaction(session, request)).rejects.toThrow(
546
+ "Mobile transaction signing rejected: user declined"
459
547
  );
460
548
  });
461
549
  });
@@ -525,6 +613,7 @@ if (void 0) {
525
613
  });
526
614
  }
527
615
  var makeSession2;
616
+ var ext2;
528
617
  var fakeAdapter2;
529
618
 
530
619
  // src/sessions.ts