@parity/product-sdk-terminal 0.2.0 → 0.3.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/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,71 +272,86 @@ if (void 0) {
272
272
  var fakeLazyClient2;
273
273
 
274
274
  // src/signer.ts
275
- import { getPolkadotSignerFromPjs } from "polkadot-api/pjs-signer";
276
- import { fromHex, toHex } from "@polkadot-api/utils";
277
- function asHex(v) {
278
- return v.startsWith("0x") ? v : `0x${v}`;
279
- }
280
- function makeSignPayloadCallback(session, productAccountId) {
281
- return async (payload) => {
282
- const result = await session.signPayload({
283
- productAccountId,
284
- blockHash: asHex(payload.blockHash),
285
- blockNumber: asHex(payload.blockNumber),
286
- era: asHex(payload.era),
287
- genesisHash: asHex(payload.genesisHash),
288
- method: asHex(payload.method),
289
- nonce: asHex(payload.nonce),
290
- specVersion: asHex(payload.specVersion),
291
- tip: asHex(payload.tip),
292
- transactionVersion: asHex(payload.transactionVersion),
293
- signedExtensions: payload.signedExtensions,
294
- version: payload.version,
295
- // PJS types `assetId` as `number | object`. In practice the
296
- // ChargeAssetTxPayment mapper always emits a hex string when
297
- // present — we trust the mapper rather than runtime-checking,
298
- // matching the reference in `@novasamatech/product-sdk`.
299
- assetId: payload.assetId !== void 0 ? payload.assetId : void 0,
300
- metadataHash: payload.metadataHash ? asHex(payload.metadataHash) : void 0,
301
- mode: payload.mode,
302
- withSignedTransaction: payload.withSignedTransaction
303
- });
304
- if (result.isErr()) {
305
- 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
+ }
306
300
  }
307
- return {
308
- signature: toHex(result.value.signature),
309
- signedTransaction: result.value.signedTransaction ? toHex(result.value.signedTransaction) : void 0
310
- };
311
301
  };
312
302
  }
313
- function makeSignRawCallback(session, productAccountId) {
314
- return async (payload) => {
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
+ }
326
+ function makeRawBytesSignCallback(session, productAccountId) {
327
+ return async (data) => {
315
328
  const result = await session.signRaw({
316
329
  productAccountId,
317
- data: { tag: "Bytes", value: fromHex(payload.data) }
330
+ data: { tag: "Bytes", value: data }
318
331
  });
319
332
  if (result.isErr()) {
320
333
  throw new Error(`Mobile signing rejected: ${result.error.message}`);
321
334
  }
322
- return {
323
- id: 0,
324
- signature: toHex(result.value.signature)
325
- };
335
+ return result.value.signature;
326
336
  };
327
337
  }
328
338
  function buildSessionSigner(session, ref) {
329
- const accountId = new Uint8Array(session.remoteAccount.accountId);
330
339
  const productAccountId = [ref.productId, ref.derivationIndex];
331
- const accountIdHex = asHex(toHex(accountId));
332
- return getPolkadotSignerFromPjs(
333
- accountIdHex,
334
- makeSignPayloadCallback(session, productAccountId),
335
- makeSignRawCallback(session, productAccountId)
336
- );
340
+ const publicKey = ref.publicKey ?? new Uint8Array(session.remoteAccount.accountId);
341
+ return {
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).
350
+ signBytes: makeRawBytesSignCallback(session, productAccountId)
351
+ };
337
352
  }
338
- function createSessionSigner(session, adapter) {
339
- return buildSessionSigner(session, { productId: adapter.appId, derivationIndex: 0 });
353
+ function createSessionSigner(session, adapter, publicKey) {
354
+ return buildSessionSigner(session, { productId: adapter.appId, derivationIndex: 0, publicKey });
340
355
  }
341
356
  function createSessionSignerForAccount(session, ref) {
342
357
  return buildSessionSigner(session, ref);
@@ -355,16 +370,27 @@ if (void 0) {
355
370
  opts.signRaw ?? (async () => {
356
371
  throw new Error("signRaw not stubbed in this test");
357
372
  })
373
+ ),
374
+ createTransaction: vi.fn(
375
+ opts.createTransaction ?? (async () => {
376
+ throw new Error("createTransaction not stubbed in this test");
377
+ })
358
378
  )
359
379
  };
380
+ }, ext = function(identifier, value, additionalSigned) {
381
+ return {
382
+ identifier,
383
+ value: new Uint8Array(value),
384
+ additionalSigned: new Uint8Array(additionalSigned)
385
+ };
360
386
  }, fakeAdapter = function(appId) {
361
387
  return { appId };
362
388
  };
363
- makeSession2 = makeSession, fakeAdapter2 = fakeAdapter;
389
+ makeSession2 = makeSession, ext2 = ext, fakeAdapter2 = fakeAdapter;
364
390
  const { describe, test, expect, vi } = void 0;
365
391
  const { ok, err } = await null;
366
392
  describe("createSessionSigner", () => {
367
- test("exposes Sr25519 public key matching remoteAccount.accountId", () => {
393
+ test("falls back to remoteAccount.accountId when no product key is passed", () => {
368
394
  const bytes = Array.from({ length: 32 }, (_, i) => i);
369
395
  const signer = createSessionSigner(
370
396
  makeSession({ accountIdBytes: bytes }),
@@ -372,7 +398,18 @@ if (void 0) {
372
398
  );
373
399
  expect(signer.publicKey).toEqual(new Uint8Array(bytes));
374
400
  });
375
- test("signBytes routes through session.signRaw (anti-phishing wrap path)", async () => {
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
+ });
412
+ test("signBytes routes through session.signRaw with the Bytes tag", async () => {
376
413
  const sig = new Uint8Array([9, 8, 7, 6, 5, 4, 3, 2, 1]);
377
414
  const captured = [];
378
415
  const session = makeSession({
@@ -388,9 +425,9 @@ if (void 0) {
388
425
  const req = captured[0];
389
426
  expect(req.productAccountId).toEqual(["test-app", 0]);
390
427
  expect(req.data.tag).toBe("Bytes");
391
- expect(req.data.value).toBeInstanceOf(Uint8Array);
428
+ expect(req.data.value).toEqual(new Uint8Array([1, 2, 3]));
392
429
  });
393
- test("signBytes does NOT call session.signPayload (regression guard for BadProof bug)", async () => {
430
+ test("signBytes never calls session.signPayload (legacy-path regression guard)", async () => {
394
431
  const session = makeSession({
395
432
  signRaw: async () => ok({ signature: new Uint8Array([1]) })
396
433
  });
@@ -400,7 +437,7 @@ if (void 0) {
400
437
  expect(sessionWithSpies.signPayload.mock.calls).toHaveLength(0);
401
438
  expect(sessionWithSpies.signRaw.mock.calls).toHaveLength(1);
402
439
  });
403
- test("signRaw throws with a clear error when the mobile rejects", async () => {
440
+ test("signBytes throws with a clear error when the mobile rejects", async () => {
404
441
  const session = makeSession({
405
442
  signRaw: async () => err({ message: "user declined" })
406
443
  });
@@ -410,148 +447,108 @@ if (void 0) {
410
447
  );
411
448
  });
412
449
  });
413
- describe("makeSignPayloadCallback \u2014 tx signing path (the BadProof fix)", () => {
414
- function pjsPayload(overrides = {}) {
415
- return {
416
- address: `0x${"00".repeat(32)}`,
417
- blockHash: `0x${"11".repeat(32)}`,
418
- blockNumber: "0x12345678",
419
- era: "0xc501",
420
- genesisHash: `0x${"22".repeat(32)}`,
421
- method: "0xabcdef",
422
- nonce: "0x00000001",
423
- specVersion: "0x000003e8",
424
- tip: `0x${"0".repeat(32)}`,
425
- transactionVersion: "0x00000001",
426
- signedExtensions: ["CheckMortality", "CheckNonce"],
427
- version: 4,
428
- ...overrides
429
- };
430
- }
431
- test("forwards request to session.signPayload with the right productAccountId", async () => {
432
- const captured = [];
433
- const session = makeSession({
434
- signPayload: async (req2) => {
435
- captured.push(req2);
436
- return ok({
437
- signature: new Uint8Array([170, 187]),
438
- signedTransaction: void 0
439
- });
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([])
440
491
  }
492
+ ]);
493
+ });
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])
441
505
  });
442
- const callback = makeSignPayloadCallback(session, ["my-app", 0]);
443
- await callback(pjsPayload());
444
- expect(captured).toHaveLength(1);
445
- const req = captured[0];
446
- expect(req.productAccountId).toEqual(["my-app", 0]);
447
- });
448
- test("does NOT call session.signRaw (BadProof regression guard)", async () => {
449
- const session = makeSession({
450
- signPayload: async () => ok({ signature: new Uint8Array([1]), signedTransaction: void 0 })
451
- });
452
- const callback = makeSignPayloadCallback(session, ["my-app", 0]);
453
- await callback(pjsPayload());
454
- const sessionWithSpies = session;
455
- expect(sessionWithSpies.signPayload.mock.calls).toHaveLength(1);
456
- expect(sessionWithSpies.signRaw.mock.calls).toHaveLength(0);
457
506
  });
458
- test("translates every PJS hex field into a 0x-prefixed string", async () => {
459
- const captured = [];
460
- const session = makeSession({
461
- signPayload: async (req2) => {
462
- captured.push(req2);
463
- return ok({
464
- signature: new Uint8Array([0]),
465
- signedTransaction: void 0
466
- });
467
- }
468
- });
469
- const callback = makeSignPayloadCallback(session, ["my-app", 0]);
470
- await callback(
471
- pjsPayload({
472
- // Mix of already-prefixed and unprefixed inputs to exercise asHex.
473
- blockHash: `0x${"ab".repeat(32)}`,
474
- nonce: "1234abcd"
475
- // unprefixed — asHex must add 0x
476
- })
477
- );
478
- const req = captured[0];
479
- for (const field of [
480
- "blockHash",
481
- "blockNumber",
482
- "era",
483
- "genesisHash",
484
- "method",
485
- "nonce",
486
- "specVersion",
487
- "tip",
488
- "transactionVersion"
489
- ]) {
490
- expect(req[field], `${field} must be 0x-prefixed`).toMatch(/^0x[0-9a-fA-F]*$/);
491
- }
507
+ test("throws a clear error when CheckGenesis is absent", () => {
508
+ expect(
509
+ () => buildCreateTransactionRequest(["my-app", 0], new Uint8Array([0]), {}, 0)
510
+ ).toThrow(/CheckGenesis/);
492
511
  });
493
- test("forwards optional fields when present, omits when absent", async () => {
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 () => {
494
527
  const captured = [];
495
528
  const session = makeSession({
496
- signPayload: async (req2) => {
497
- captured.push(req2);
498
- return ok({
499
- signature: new Uint8Array([0]),
500
- signedTransaction: void 0
501
- });
529
+ createTransaction: async (req) => {
530
+ captured.push(req);
531
+ return ok(new Uint8Array([1]));
502
532
  }
503
533
  });
504
- const callback = makeSignPayloadCallback(session, ["my-app", 0]);
505
- await callback(
506
- pjsPayload({
507
- metadataHash: "0xdeadbeef",
508
- mode: 1,
509
- withSignedTransaction: true,
510
- assetId: "0xfeed"
511
- })
512
- );
513
- const req = captured[0];
514
- expect(req.metadataHash).toBe("0xdeadbeef");
515
- expect(req.mode).toBe(1);
516
- expect(req.withSignedTransaction).toBe(true);
517
- expect(req.assetId).toBe("0xfeed");
518
- });
519
- test("hex-encodes signature and signedTransaction in the PJS return shape", async () => {
520
- const session = makeSession({
521
- signPayload: async () => ok({
522
- signature: new Uint8Array([171, 205]),
523
- signedTransaction: new Uint8Array([1, 2, 3])
524
- })
525
- });
526
- const callback = makeSignPayloadCallback(session, ["my-app", 0]);
527
- const result = await callback(pjsPayload());
528
- expect(result.signature).toBe("0xabcd");
529
- expect(result.signedTransaction).toBe("0x010203");
530
- });
531
- test("omits signedTransaction when host-papp returns undefined", async () => {
532
- const session = makeSession({
533
- signPayload: async () => ok({
534
- signature: new Uint8Array([0]),
535
- signedTransaction: void 0
536
- })
537
- });
538
- const callback = makeSignPayloadCallback(session, ["my-app", 0]);
539
- const result = await callback(pjsPayload());
540
- expect(result.signature).toBe("0x00");
541
- expect(result.signedTransaction).toBeUndefined();
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);
542
540
  });
543
541
  test("throws a clear error when the mobile rejects", async () => {
544
542
  const session = makeSession({
545
- signPayload: async () => err({ message: "user declined" })
543
+ createTransaction: async () => err({ message: "user declined" })
546
544
  });
547
- const callback = makeSignPayloadCallback(session, ["my-app", 0]);
548
- await expect(callback(pjsPayload())).rejects.toThrow(
549
- "Mobile signing rejected: user declined"
545
+ await expect(requestSignedTransaction(session, request)).rejects.toThrow(
546
+ "Mobile transaction signing rejected: user declined"
550
547
  );
551
548
  });
552
549
  });
553
- describe("makeSignRawCallback", () => {
554
- test("forwards request to session.signRaw with the right productAccountId", async () => {
550
+ describe("makeRawBytesSignCallback", () => {
551
+ test("forwards data verbatim under the Bytes tag with the right productAccountId", async () => {
555
552
  const captured = [];
556
553
  const session = makeSession({
557
554
  signRaw: async (req2) => {
@@ -559,30 +556,22 @@ if (void 0) {
559
556
  return ok({ signature: new Uint8Array([255]) });
560
557
  }
561
558
  });
562
- const callback = makeSignRawCallback(session, ["my-app", 5]);
563
- await callback({
564
- address: `0x${"00".repeat(32)}`,
565
- data: "0xdeadbeef",
566
- type: "bytes"
567
- });
559
+ const callback = makeRawBytesSignCallback(session, ["my-app", 5]);
560
+ await callback(new Uint8Array([222, 173, 190, 239]));
568
561
  expect(captured).toHaveLength(1);
569
562
  const req = captured[0];
570
563
  expect(req.productAccountId).toEqual(["my-app", 5]);
571
564
  expect(req.data.tag).toBe("Bytes");
572
565
  expect(Array.from(req.data.value)).toEqual([222, 173, 190, 239]);
573
566
  });
574
- test("hex-encodes signature in the PJS return shape with id: 0", async () => {
567
+ test("returns the raw signature bytes from the mobile response", async () => {
568
+ const sig = new Uint8Array([66]);
575
569
  const session = makeSession({
576
- signRaw: async () => ok({ signature: new Uint8Array([66]) })
577
- });
578
- const callback = makeSignRawCallback(session, ["my-app", 0]);
579
- const result = await callback({
580
- address: `0x${"00".repeat(32)}`,
581
- data: "0x00",
582
- type: "bytes"
570
+ signRaw: async () => ok({ signature: sig })
583
571
  });
584
- expect(result.id).toBe(0);
585
- expect(result.signature).toBe("0x42");
572
+ const callback = makeRawBytesSignCallback(session, ["my-app", 0]);
573
+ const out = await callback(new Uint8Array([0]));
574
+ expect(out).toBe(sig);
586
575
  });
587
576
  });
588
577
  describe("createSessionSignerForAccount", () => {
@@ -624,6 +613,7 @@ if (void 0) {
624
613
  });
625
614
  }
626
615
  var makeSession2;
616
+ var ext2;
627
617
  var fakeAdapter2;
628
618
 
629
619
  // src/sessions.ts