@unconfirmed/sui-effect 0.1.1 → 0.1.3

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.
Files changed (74) hide show
  1. package/AGENTS.md +34 -11
  2. package/CHANGELOG.md +77 -0
  3. package/LLMS.md +630 -704
  4. package/README.md +175 -9
  5. package/dist/domain/bcs.d.ts.map +1 -1
  6. package/dist/domain/bcs.js +25 -9
  7. package/dist/domain/bcs.js.map +1 -1
  8. package/dist/domain/errors.d.ts +178 -40
  9. package/dist/domain/errors.d.ts.map +1 -1
  10. package/dist/domain/errors.js +271 -37
  11. package/dist/domain/errors.js.map +1 -1
  12. package/dist/domain/executed.d.ts +71 -2
  13. package/dist/domain/executed.d.ts.map +1 -1
  14. package/dist/domain/executed.js +210 -9
  15. package/dist/domain/executed.js.map +1 -1
  16. package/dist/domain/journal-entry.d.ts +37 -37
  17. package/dist/domain/journal-entry.js +1 -1
  18. package/dist/domain/schemas.d.ts +172 -65
  19. package/dist/domain/schemas.d.ts.map +1 -1
  20. package/dist/domain/schemas.js +131 -32
  21. package/dist/domain/schemas.js.map +1 -1
  22. package/dist/index.d.ts +1 -1
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +1 -1
  25. package/dist/index.js.map +1 -1
  26. package/dist/internal.d.ts +1 -1
  27. package/dist/internal.d.ts.map +1 -1
  28. package/dist/internal.js +1 -1
  29. package/dist/internal.js.map +1 -1
  30. package/dist/script.d.ts +1 -1
  31. package/dist/script.d.ts.map +1 -1
  32. package/dist/script.js +1 -1
  33. package/dist/script.js.map +1 -1
  34. package/dist/services/Script.d.ts +42 -0
  35. package/dist/services/Script.d.ts.map +1 -1
  36. package/dist/services/Script.js +112 -77
  37. package/dist/services/Script.js.map +1 -1
  38. package/dist/services/Signer.d.ts +32 -7
  39. package/dist/services/Signer.d.ts.map +1 -1
  40. package/dist/services/Signer.js +69 -10
  41. package/dist/services/Signer.js.map +1 -1
  42. package/dist/services/SubmitConfig.d.ts +3 -22
  43. package/dist/services/SubmitConfig.d.ts.map +1 -1
  44. package/dist/services/SubmitConfig.js +54 -9
  45. package/dist/services/SubmitConfig.js.map +1 -1
  46. package/dist/services/Sui.d.ts +42 -1
  47. package/dist/services/Sui.d.ts.map +1 -1
  48. package/dist/services/Sui.js +46 -17
  49. package/dist/services/Sui.js.map +1 -1
  50. package/dist/services/SuiCore.d.ts.map +1 -1
  51. package/dist/services/SuiCore.js +47 -33
  52. package/dist/services/SuiCore.js.map +1 -1
  53. package/dist/services/SuiCoreFake.d.ts +47 -4
  54. package/dist/services/SuiCoreFake.d.ts.map +1 -1
  55. package/dist/services/SuiCoreFake.js +193 -22
  56. package/dist/services/SuiCoreFake.js.map +1 -1
  57. package/dist/services/Tx.d.ts +236 -402
  58. package/dist/services/Tx.d.ts.map +1 -1
  59. package/dist/services/Tx.js +205 -10
  60. package/dist/services/Tx.js.map +1 -1
  61. package/dist/testing.d.ts +1 -0
  62. package/dist/testing.d.ts.map +1 -1
  63. package/dist/testing.js +9 -0
  64. package/dist/testing.js.map +1 -1
  65. package/dist/tx.d.ts +1 -1
  66. package/dist/tx.d.ts.map +1 -1
  67. package/dist/tx.js +1 -1
  68. package/dist/tx.js.map +1 -1
  69. package/docs/extensions.md +608 -19
  70. package/examples/extension-template/src/Escrow.ts +1 -1
  71. package/examples/extension-template/src/errors.ts +29 -0
  72. package/examples/extension-template/src/schema.ts +8 -17
  73. package/examples/extension-template/test/escrow.test.ts +44 -0
  74. package/package.json +1 -1
package/LLMS.md CHANGED
@@ -17,7 +17,7 @@ contract for building an extension package on top of it.
17
17
  ## `@unconfirmed/sui-effect`
18
18
 
19
19
 
20
- 82 exported symbols.
20
+ 84 exported symbols.
21
21
 
22
22
  ### `Balance` (const)
23
23
 
@@ -229,14 +229,43 @@ The return values and mutated references of one command. Mirrors `SuiClientTypes
229
229
 
230
230
  ```ts
231
231
  export declare class DecodeError extends DecodeError_base {
232
- readonly issue: string
233
- readonly objectId: ObjectId | undefined
234
- readonly expectedType: string | undefined
232
+ /** The one actionable line `SuiError.describe` produces for this error. */
233
+ get message(): string;
235
234
  }
236
235
  ```
237
236
 
238
237
  BCS content or a schema boundary did not decode.
239
238
 
239
+ `kind` says which of the three (`"type"`, `"bytes"`, `"shape"`) and is what a
240
+ consumer branches on; `issue` is the sentence for a human and is not stable.
241
+ It defaults to `"shape"` when neither a constructor nor an encoded value
242
+ carries one, so an extension that builds a `DecodeError` with no `kind` still
243
+ compiles and still answers the question conservatively.
244
+
245
+ ### `DecodeKind` (const)
246
+
247
+ ```ts
248
+ declare const DecodeKind: Schema
249
+ // decodes to: "bytes" | "type" | "shape"
250
+ ```
251
+
252
+ Which of the three things that can go wrong at a decode boundary went wrong.
253
+
254
+ - `"type"`: the object, the field or the event **is not of the expected Move
255
+ type**. The bytes were never parsed. This is the one a caller answers with
256
+ "that is not one of mine" — a 404 for a foreign object, a `filter` over a
257
+ heterogeneous list — and the one it is safe to swallow.
258
+ - `"bytes"`: the type matched and the **BCS parse failed**, or left trailing
259
+ bytes. Either the layout this package was built with is not the layout the
260
+ package on chain writes, or the object is corrupt. Never safe to swallow.
261
+ - `"shape"`: a **domain schema** refused a value that was already parsed or
262
+ that came from the node as JSON — a missing field in a node response, a
263
+ number that is not a timestamp, a simulation with no such command. A bug
264
+ here is in this library, the node, or the caller's expectations.
265
+
266
+ Branch on this, never on `DecodeError`'s `issue`: `issue` is a human
267
+ sentence and its wording changes between releases.
268
+
240
269
  ### `Digest` (const)
241
270
 
242
271
  ```ts
@@ -258,8 +287,8 @@ declare const DynamicField: Schema
258
287
  // readonly type: string;
259
288
  // readonly bcs: Uint8Array<ArrayBufferLike>;
260
289
  // };
261
- // readonly fieldId: ObjectId;
262
290
  // readonly type: string;
291
+ // readonly fieldId: ObjectId;
263
292
  // readonly name: {
264
293
  // readonly type: string;
265
294
  // readonly bcs: Uint8Array<ArrayBufferLike>;
@@ -279,8 +308,8 @@ declare const DynamicFieldEntry: Schema
279
308
  // decodes to:
280
309
  // {
281
310
  // readonly $kind: "DynamicField" | "DynamicObject";
282
- // readonly fieldId: ObjectId;
283
311
  // readonly type: string;
312
+ // readonly fieldId: ObjectId;
284
313
  // readonly name: {
285
314
  // readonly type: string;
286
315
  // readonly bcs: Uint8Array<ArrayBufferLike>;
@@ -305,10 +334,18 @@ The BCS-encoded name of a dynamic field. Mirrors `SuiClientTypes.DynamicFieldNam
305
334
 
306
335
  ```ts
307
336
  declare const Event: Schema
308
- // decodes to: { readonly packageId: ObjectId; readonly module: string; readonly sender: SuiAddress; readonly eventType: string; readonly bcs: Uint8Array<ArrayBufferLike>; }
337
+ // decodes to: { readonly sender: SuiAddress; readonly bcs: Uint8Array<ArrayBufferLike>; readonly packageId: ObjectId; readonly module: string; readonly eventType: string; readonly json?: unknown; }
309
338
  ```
310
339
 
311
- An emitted Move event. Mirrors `SuiClientTypes.Event`; `json` is dropped on purpose.
340
+ An emitted Move event. Mirrors `SuiClientTypes.Event` with the branded ids
341
+ this package uses.
342
+
343
+ The decode a caller wants is `SuiSchema.decode(codec, event.bcs)`, which
344
+ gives a typed value; `json` is the node's own rendering and is **not**
345
+ something to build on — it is absent on most transports and its shape follows
346
+ whatever the node feels like. It is kept only when the source carried it,
347
+ which in practice means a relay or sponsor envelope decoded through
348
+ `Executed.fromPartial`, where it may be the only form of the event there is.
312
349
 
313
350
  ### `Executed` (class)
314
351
 
@@ -364,6 +401,16 @@ export declare class Executed extends Executed_base {
364
401
  * Never fails.
365
402
  */
366
403
  wrapped(): ReadonlyArray<ChangedRef>;
404
+ /**
405
+ * Whether this change wrote an object, treating `Unknown` as "the envelope
406
+ * did not say".
407
+ *
408
+ * A node always reports the output state; a reduced envelope from a relay or
409
+ * a sponsor often reports nothing but the id and the id operation, and
410
+ * {@link Executed.fromPartial} leaves what it was not told as `Unknown`
411
+ * rather than inventing `ObjectWrite`. Reading `Unknown` as "not an object
412
+ * write" would make `created()` silently empty for exactly those envelopes.
413
+ */
367
414
  /**
368
415
  * Packages this transaction published (`PackageWrite` plus `Created`), as
369
416
  * full refs like every other accessor. A package's `type` is the literal
@@ -376,6 +423,53 @@ export declare class Executed extends Executed_base {
376
423
  balanceChange(address: SuiAddress, coinType: CoinType): bigint;
377
424
  /** Computation plus storage less the storage rebate, in MIST. Can be negative. Never fails. */
378
425
  get gasUsedTotal(): bigint;
426
+ /**
427
+ * An `Executed` from the SDK's own `TransactionResult`, read with
428
+ * {@link EXECUTE_INCLUDE}.
429
+ *
430
+ * This is what `Tx.submit` uses, exported so a caller holding a result from
431
+ * somewhere else — `client.core.executeTransaction`, a sponsor's SDK call —
432
+ * can get the accessors without re-implementing the decode.
433
+ *
434
+ * Fails with: `ExecutionFailed` (the transaction applied and failed),
435
+ * `DecodeError` (the response does not carry the include set).
436
+ *
437
+ * @since 0.1.2
438
+ */
439
+ static readonly fromTransactionResult: (result: SuiClientTypes.TransactionResult<typeof EXECUTE_INCLUDE>) => Effect.Effect<Executed, ExecutionFailed | DecodeError>;
440
+ /**
441
+ * An `Executed` from a **reduced** execute envelope: what a relay, a sponsor
442
+ * or another service hands back, over JSON, after submitting on your behalf.
443
+ *
444
+ * Such an envelope is rarely the SDK's full include set. Everything optional
445
+ * is filled in with "the node did not say" rather than refused:
446
+ *
447
+ * - `changedObjects` entries need only `objectId` and `idOperation`; the
448
+ * version, digest and owner on either side default to `null`, and
449
+ * `outputState` defaults to `ObjectWrite` (`DoesNotExist` for a
450
+ * `Deleted`), so {@link created} and {@link deleted} classify correctly
451
+ * from the id operation alone.
452
+ * - `objectTypes` defaults to `{}`. **The type filters need it**:
453
+ * `created(type)`, `mutated(type)` and `expectCreated(type)` can only
454
+ * match a change whose type the envelope carried, so without
455
+ * `objectTypes` they return nothing. `created()` with no argument, and
456
+ * {@link createdWhere}, still list every created id.
457
+ * - `balanceChanges` and `events` default to `[]`, `checkpoint` and
458
+ * `timestampMs` to `null`, `gasUsed` to zeros, and `effects.status` to
459
+ * success.
460
+ * - **JSON spellings are accepted** where the SDK's types are not JSON:
461
+ * `bcs` as base64 or as an array of byte values as well as a
462
+ * `Uint8Array`, and every `u64` (versions, balances, gas, `checkpoint`)
463
+ * as a number or a `bigint` as well as the decimal string the wire uses.
464
+ *
465
+ * An envelope with no usable digest, or whose values are the wrong shape
466
+ * rather than merely absent, fails: absence is filled in, nonsense is not.
467
+ *
468
+ * Fails with: `DecodeError`.
469
+ *
470
+ * @since 0.1.2
471
+ */
472
+ static readonly fromPartial: (envelope: unknown) => Effect.Effect<Executed, DecodeError>;
379
473
  /**
380
474
  * The single object of this type the transaction created.
381
475
  *
@@ -389,220 +483,22 @@ export declare class Executed extends Executed_base {
389
483
  A transaction the network executed, built from the fixed execute include set:
390
484
  effects, events, balance changes and object types.
391
485
 
486
+ **`events` is `ReadonlyArray<Event>`, not `SuiClientTypes.Event[]`.** It is
487
+ that type minus `json`: `packageId`, `module`, `sender`, `eventType` and
488
+ `bcs`, with the branded ids this package uses. The SDK's `json` is dropped on
489
+ purpose — it is the node's own rendering, it is absent on most transports,
490
+ and the decode a caller wants is `SuiSchema.decode(codec, event.bcs)`, which
491
+ gives a typed value rather than a shape that changes with the node. Code
492
+ typed against the SDK's `Event[]` therefore does not accept these; take
493
+ `ReadonlyArray<Event>` from `@unconfirmed/sui-effect`, or map the fields you
494
+ need.
495
+
392
496
  ### `ExecutionFailed` (class)
393
497
 
394
498
  ```ts
395
499
  export declare class ExecutionFailed extends ExecutionFailed_base {
396
- readonly digest: Digest
397
- readonly reason: {
398
- readonly $kind: "MoveAbort";
399
- readonly MoveAbort: {
400
- readonly abortCode: bigint;
401
- readonly location?: {
402
- readonly function?: number | undefined;
403
- readonly module?: string | undefined;
404
- readonly package?: string | undefined;
405
- readonly functionName?: string | undefined;
406
- readonly instruction?: number | undefined;
407
- } | undefined;
408
- readonly cleverError?: {
409
- readonly errorCode?: number | undefined;
410
- readonly lineNumber?: number | undefined;
411
- readonly constantName?: string | undefined;
412
- readonly constantType?: string | undefined;
413
- readonly value?: string | undefined;
414
- } | undefined;
415
- };
416
- } | {
417
- readonly $kind: "SizeError";
418
- readonly SizeError: {
419
- readonly name: string;
420
- readonly size: number;
421
- readonly maxSize: number;
422
- };
423
- } | {
424
- readonly $kind: "CommandArgumentError";
425
- readonly CommandArgumentError: {
426
- readonly argument: number;
427
- readonly name: string;
428
- };
429
- } | {
430
- readonly $kind: "TypeArgumentError";
431
- readonly TypeArgumentError: {
432
- readonly typeArgument: number;
433
- readonly name: string;
434
- };
435
- } | {
436
- readonly $kind: "PackageUpgradeError";
437
- readonly PackageUpgradeError: {
438
- readonly name: string;
439
- readonly digest?: string | undefined;
440
- readonly packageId?: string | undefined;
441
- };
442
- } | {
443
- readonly $kind: "IndexError";
444
- readonly IndexError: {
445
- readonly index?: number | undefined;
446
- readonly subresult?: number | undefined;
447
- };
448
- } | {
449
- readonly $kind: "CoinDenyListError";
450
- readonly CoinDenyListError: {
451
- readonly coinType: string;
452
- readonly name: string;
453
- readonly address?: string | undefined;
454
- };
455
- } | {
456
- readonly $kind: "CongestedObjects";
457
- readonly CongestedObjects: {
458
- readonly name: string;
459
- readonly objects: readonly string[];
460
- };
461
- } | {
462
- readonly $kind: "ObjectIdError";
463
- readonly ObjectIdError: {
464
- readonly objectId: string;
465
- readonly name?: string | undefined;
466
- };
467
- } | {
468
- readonly $kind: "Unknown";
469
- }
470
- readonly effects: {
471
- readonly version: number;
472
- readonly status: {
473
- readonly success: boolean;
474
- };
475
- readonly gasUsed: {
476
- readonly computationCost: Mist;
477
- readonly storageCost: Mist;
478
- readonly storageRebate: Mist;
479
- readonly nonRefundableStorageFee: Mist;
480
- };
481
- readonly transactionDigest: Digest;
482
- readonly gasObject: {
483
- readonly objectId: ObjectId;
484
- readonly inputState: "Unknown" | "DoesNotExist" | "Exists";
485
- readonly inputVersion: Version | null;
486
- readonly inputDigest: string | null;
487
- readonly inputOwner: {
488
- readonly $kind: "AddressOwner";
489
- readonly AddressOwner: SuiAddress;
490
- } | {
491
- readonly $kind: "ObjectOwner";
492
- readonly ObjectOwner: ObjectId;
493
- } | {
494
- readonly $kind: "Shared";
495
- readonly Shared: {
496
- readonly initialSharedVersion: Version;
497
- };
498
- } | {
499
- readonly $kind: "Immutable";
500
- readonly Immutable: true;
501
- } | {
502
- readonly $kind: "ConsensusAddressOwner";
503
- readonly ConsensusAddressOwner: {
504
- readonly startVersion: Version;
505
- readonly owner: SuiAddress;
506
- };
507
- } | {
508
- readonly $kind: "Unknown";
509
- } | null;
510
- readonly outputState: "Unknown" | "DoesNotExist" | "ObjectWrite" | "PackageWrite" | "AccumulatorWriteV1";
511
- readonly outputVersion: Version | null;
512
- readonly outputDigest: string | null;
513
- readonly outputOwner: {
514
- readonly $kind: "AddressOwner";
515
- readonly AddressOwner: SuiAddress;
516
- } | {
517
- readonly $kind: "ObjectOwner";
518
- readonly ObjectOwner: ObjectId;
519
- } | {
520
- readonly $kind: "Shared";
521
- readonly Shared: {
522
- readonly initialSharedVersion: Version;
523
- };
524
- } | {
525
- readonly $kind: "Immutable";
526
- readonly Immutable: true;
527
- } | {
528
- readonly $kind: "ConsensusAddressOwner";
529
- readonly ConsensusAddressOwner: {
530
- readonly startVersion: Version;
531
- readonly owner: SuiAddress;
532
- };
533
- } | {
534
- readonly $kind: "Unknown";
535
- } | null;
536
- readonly idOperation: "None" | "Unknown" | "Created" | "Deleted";
537
- } | null;
538
- readonly eventsDigest: string | null;
539
- readonly dependencies: readonly string[];
540
- readonly lamportVersion: Version | null;
541
- readonly changedObjects: readonly {
542
- readonly objectId: ObjectId;
543
- readonly inputState: "Unknown" | "DoesNotExist" | "Exists";
544
- readonly inputVersion: Version | null;
545
- readonly inputDigest: string | null;
546
- readonly inputOwner: {
547
- readonly $kind: "AddressOwner";
548
- readonly AddressOwner: SuiAddress;
549
- } | {
550
- readonly $kind: "ObjectOwner";
551
- readonly ObjectOwner: ObjectId;
552
- } | {
553
- readonly $kind: "Shared";
554
- readonly Shared: {
555
- readonly initialSharedVersion: Version;
556
- };
557
- } | {
558
- readonly $kind: "Immutable";
559
- readonly Immutable: true;
560
- } | {
561
- readonly $kind: "ConsensusAddressOwner";
562
- readonly ConsensusAddressOwner: {
563
- readonly startVersion: Version;
564
- readonly owner: SuiAddress;
565
- };
566
- } | {
567
- readonly $kind: "Unknown";
568
- } | null;
569
- readonly outputState: "Unknown" | "DoesNotExist" | "ObjectWrite" | "PackageWrite" | "AccumulatorWriteV1";
570
- readonly outputVersion: Version | null;
571
- readonly outputDigest: string | null;
572
- readonly outputOwner: {
573
- readonly $kind: "AddressOwner";
574
- readonly AddressOwner: SuiAddress;
575
- } | {
576
- readonly $kind: "ObjectOwner";
577
- readonly ObjectOwner: ObjectId;
578
- } | {
579
- readonly $kind: "Shared";
580
- readonly Shared: {
581
- readonly initialSharedVersion: Version;
582
- };
583
- } | {
584
- readonly $kind: "Immutable";
585
- readonly Immutable: true;
586
- } | {
587
- readonly $kind: "ConsensusAddressOwner";
588
- readonly ConsensusAddressOwner: {
589
- readonly startVersion: Version;
590
- readonly owner: SuiAddress;
591
- };
592
- } | {
593
- readonly $kind: "Unknown";
594
- } | null;
595
- readonly idOperation: "None" | "Unknown" | "Created" | "Deleted";
596
- }[];
597
- readonly unchangedConsensusObjects: readonly {
598
- readonly kind: "Unknown" | "ReadOnlyRoot" | "MutateConsensusStreamEnded" | "ReadConsensusStreamEnded" | "Cancelled" | "PerEpochConfig";
599
- readonly objectId: ObjectId;
600
- readonly version: Version | null;
601
- readonly digest: string | null;
602
- }[];
603
- readonly auxiliaryDataDigest: string | null;
604
- }
605
- readonly command: number | undefined
500
+ /** The one actionable line `SuiError.describe` produces for this error. */
501
+ get message(): string;
606
502
  }
607
503
  ```
608
504
 
@@ -710,8 +606,8 @@ representation of an on-chain failure.
710
606
 
711
607
  ```ts
712
608
  export declare class ExtensionNotReady extends ExtensionNotReady_base {
713
- readonly extension: string
714
- readonly member: string
609
+ /** The one actionable line `SuiError.describe` produces for this error. */
610
+ get message(): string;
715
611
  }
716
612
  ```
717
613
 
@@ -749,8 +645,8 @@ What can go wrong reading one object with a schema.
749
645
 
750
646
  ```ts
751
647
  export declare class GraphQLUnavailable extends GraphQLUnavailable_base {
752
- readonly method: string
753
- readonly reason: string
648
+ /** The one actionable line `SuiError.describe` produces for this error. */
649
+ get message(): string;
754
650
  }
755
651
  ```
756
652
 
@@ -786,7 +682,8 @@ the documented retry idiom to send again on no evidence at all.
786
682
 
787
683
  ```ts
788
684
  export declare class JournalError extends JournalError_base {
789
- readonly cause: unknown
685
+ /** The one actionable line `SuiError.describe` produces for this error. */
686
+ get message(): string;
790
687
  }
791
688
  ```
792
689
 
@@ -854,8 +751,8 @@ The network a client is pointed at. Mirrors `SuiClientTypes.Network`.
854
751
 
855
752
  ```ts
856
753
  export declare class NetworkMismatch extends NetworkMismatch_base {
857
- readonly expected: string
858
- readonly actual: string
754
+ /** The one actionable line `SuiError.describe` produces for this error. */
755
+ get message(): string;
859
756
  }
860
757
  ```
861
758
 
@@ -865,8 +762,8 @@ The chain identifier the node reported is not the one the layer was built for.
865
762
 
866
763
  ```ts
867
764
  export declare class NotApplied extends NotApplied_base {
868
- readonly digest: Digest
869
- readonly evidence: "expired" | "inputConsumed"
765
+ /** The one actionable line `SuiError.describe` produces for this error. */
766
+ get message(): string;
870
767
  }
871
768
  ```
872
769
 
@@ -900,8 +797,8 @@ cannot drift.
900
797
 
901
798
  ```ts
902
799
  export declare class ObjectDeleted extends ObjectDeleted_base {
903
- readonly objectId: ObjectId
904
- readonly version: Version | undefined
800
+ /** The one actionable line `SuiError.describe` produces for this error. */
801
+ get message(): string;
905
802
  }
906
803
  ```
907
804
 
@@ -967,8 +864,17 @@ The failures of an object lookup.
967
864
 
968
865
  ```ts
969
866
  export declare class ObjectNotFound extends ObjectNotFound_base {
970
- readonly objectId: ObjectId
971
- readonly version: Version | undefined
867
+ /**
868
+ * The one actionable line `SuiError.describe` produces for this error.
869
+ *
870
+ * `Schema.TaggedError` gives every class the `Error` constructor and no
871
+ * message of its own, so `error.message` was the empty string — and a
872
+ * consumer that surfaces `.message` (a log line, a UI, another library's
873
+ * error formatter) showed nothing at all. A getter rather than a schema
874
+ * field, so it is always in step with `describe`, costs nothing to
875
+ * construct, and stays out of `SuiError.toJson`'s encoding.
876
+ */
877
+ get message(): string;
972
878
  }
973
879
  ```
974
880
 
@@ -1029,8 +935,8 @@ readable object like any other.
1029
935
 
1030
936
  ```ts
1031
937
  export declare class ObjectUnavailable extends ObjectUnavailable_base {
1032
- readonly objectId: ObjectId
1033
- readonly version: Version | undefined
938
+ /** The one actionable line `SuiError.describe` produces for this error. */
939
+ get message(): string;
1034
940
  }
1035
941
  ```
1036
942
 
@@ -1045,6 +951,22 @@ export type Outcome = "applied" | "not_applied" | "unknown";
1045
951
  What a failure says about the transaction it came from, on the axis a caller
1046
952
  or a wrapper script acts on.
1047
953
 
954
+ ### `OutcomePhase` (type)
955
+
956
+ ```ts
957
+ export type OutcomePhase = "pre-submit" | "post-submit";
958
+ ```
959
+
960
+ Where in the lifecycle a failure was caught, which is the only thing that
961
+ can classify an error the taxonomy does not own.
962
+
963
+ `"post-submit"` (the default, and the 0.1.1 behaviour) is "bytes may have
964
+ gone out": a tag nobody here recognises proves nothing, so the answer is
965
+ `"unknown"`. `"pre-submit"` is a failure caught while **building, simulating
966
+ or signing** — an extension's own `PriceTooLow`, a validation error from the
967
+ caller's code — where nothing has been sent by construction and the honest
968
+ answer is `"not_applied"`.
969
+
1048
970
  ### `Owner` (const)
1049
971
 
1050
972
  ```ts
@@ -1178,7 +1100,8 @@ The expiration the transaction was built with rides along, because it is what
1178
1100
 
1179
1101
  ```ts
1180
1102
  export declare class SigningError extends SigningError_base {
1181
- readonly cause: unknown
1103
+ /** The one actionable line `SuiError.describe` produces for this error. */
1104
+ get message(): string;
1182
1105
  }
1183
1106
  ```
1184
1107
 
@@ -1327,11 +1250,12 @@ declare const Simulation: Schema
1327
1250
  // readonly auxiliaryDataDigest: string | null;
1328
1251
  // };
1329
1252
  // readonly events: readonly {
1253
+ // readonly sender: SuiAddress;
1254
+ // readonly bcs: Uint8Array<ArrayBufferLike>;
1330
1255
  // readonly packageId: ObjectId;
1331
1256
  // readonly module: string;
1332
- // readonly sender: SuiAddress;
1333
1257
  // readonly eventType: string;
1334
- // readonly bcs: Uint8Array<ArrayBufferLike>;
1258
+ // readonly json?: unknown;
1335
1259
  // }[];
1336
1260
  // readonly balanceChanges: readonly {
1337
1261
  // readonly coinType: CoinType;
@@ -1460,46 +1384,8 @@ decode so `0x2::sui::SUI` and its padded form compare equal.
1460
1384
 
1461
1385
  ```ts
1462
1386
  export declare class SubmissionUnknown extends SubmissionUnknown_base {
1463
- readonly cause: unknown
1464
- readonly digest: Digest
1465
- readonly signed: {
1466
- readonly digest: Digest;
1467
- readonly bytes: Uint8Array<ArrayBufferLike>;
1468
- readonly sender: SuiAddress;
1469
- readonly signatures: readonly Signature[];
1470
- readonly expiration?: {
1471
- readonly $kind: "None";
1472
- readonly None: true;
1473
- } | {
1474
- readonly $kind: "Epoch";
1475
- readonly Epoch: bigint;
1476
- } | {
1477
- readonly $kind: "ValidDuring";
1478
- readonly ValidDuring: {
1479
- readonly minEpoch: bigint | null;
1480
- readonly maxEpoch: bigint | null;
1481
- readonly minTimestamp: bigint | null;
1482
- readonly maxTimestamp: bigint | null;
1483
- readonly chain: string;
1484
- readonly nonce: number;
1485
- };
1486
- } | {
1487
- readonly $kind: "Validity";
1488
- readonly Validity: {
1489
- readonly allowedProposers: {
1490
- readonly epoch: bigint;
1491
- readonly proposers: readonly number[];
1492
- } | null;
1493
- readonly minEpoch: bigint | null;
1494
- readonly maxEpoch: bigint | null;
1495
- readonly minTimestamp: bigint | null;
1496
- readonly maxTimestamp: bigint | null;
1497
- readonly chain: string;
1498
- readonly nonce: number;
1499
- };
1500
- } | undefined;
1501
- readonly chain?: string | undefined;
1502
- } | undefined
1387
+ /** The one actionable line `SuiError.describe` produces for this error. */
1388
+ get message(): string;
1503
1389
  }
1504
1390
  ```
1505
1391
 
@@ -1826,6 +1712,26 @@ export interface SuiLayerOptions {
1826
1712
  * network are regenerated, so set this when the program must pin one.
1827
1713
  */
1828
1714
  readonly chainId?: string;
1715
+ /**
1716
+ * How to retry the one `getChainIdentifier` this layer makes, when it fails
1717
+ * with a **retryable** `TransportError`.
1718
+ *
1719
+ * It matters more than one round trip usually would, because of what holds
1720
+ * the result: a `ManagedRuntime` — which is how a browser app, a Worker or a
1721
+ * Durable Object keeps one runtime per isolate — **memoizes the layer build,
1722
+ * failure included, for its whole lifetime**. One unlucky request at boot and
1723
+ * every later use of that runtime fails with the same stale
1724
+ * `TransportError`, until something disposes it. A short schedule here is the
1725
+ * cheap half of the cure; disposing the runtime when its build failed is the
1726
+ * other half (see the guide's "Application consumers" section), and
1727
+ * `layerNoDepsPinned` avoids the read altogether when the chain id is already
1728
+ * known.
1729
+ *
1730
+ * Unset, the read is made once, as it always was.
1731
+ *
1732
+ * @since 0.1.2
1733
+ */
1734
+ readonly retry?: Schedule.Schedule<unknown, TransportError>;
1829
1735
  }
1830
1736
  ```
1831
1737
 
@@ -2063,6 +1969,27 @@ export interface SuiService {
2063
1969
  *
2064
1970
  * Fails with: `ObjectNotFound`, `ObjectDeleted`, `ObjectUnavailable`,
2065
1971
  * `DecodeError`, `TransportError`.
1972
+ *
1973
+ * @since 0.1.2
1974
+ */
1975
+ readonly getObjectsStrict: {
1976
+ <S>(ids: ReadonlyArray<ObjectId>, opts: {
1977
+ readonly schema: Schema.Codec<S, Uint8Array>;
1978
+ readonly expectedType?: string;
1979
+ }): Effect.Effect<ReadonlyArray<SuiObject<S>>, BatchItemError | TransportError>;
1980
+ (ids: ReadonlyArray<ObjectId>, opts?: {
1981
+ readonly schema?: undefined;
1982
+ readonly expectedType?: string;
1983
+ }): Effect.Effect<ReadonlyArray<SuiObject<Uint8Array>>, BatchItemError | TransportError>;
1984
+ };
1985
+ /**
1986
+ * {@link getObjectsStrict} under its 0.1.0 name.
1987
+ *
1988
+ * `getObjectsOrFail` reads, out of context, as though failing were the point
1989
+ * — `getObjects(ids).orFail()` — rather than as the strictness of the read.
1990
+ * The two are the same function.
1991
+ *
1992
+ * @deprecated Use `getObjectsStrict`. This alias stays for 0.1.x.
2066
1993
  */
2067
1994
  readonly getObjectsOrFail: {
2068
1995
  <S>(ids: ReadonlyArray<ObjectId>, opts: {
@@ -2357,7 +2284,8 @@ The failures of a transaction lookup.
2357
2284
 
2358
2285
  ```ts
2359
2286
  export declare class TransactionNotFound extends TransactionNotFound_base {
2360
- readonly digest: Digest
2287
+ /** The one actionable line `SuiError.describe` produces for this error. */
2288
+ get message(): string;
2361
2289
  }
2362
2290
  ```
2363
2291
 
@@ -2398,6 +2326,8 @@ export declare class TransportError extends TransportError_base {
2398
2326
  * ```
2399
2327
  */
2400
2328
  static readonly fromUnknown: (method: string, cause: unknown, retryable?: boolean) => TransportError;
2329
+ /** The one actionable line `SuiError.describe` produces for this error. */
2330
+ get message(): string;
2401
2331
  }
2402
2332
  ```
2403
2333
 
@@ -2428,9 +2358,8 @@ Mirrors `SuiClientTypes.UnchangedConsensusObject`.
2428
2358
 
2429
2359
  ```ts
2430
2360
  export declare class UnexpectedEffects extends UnexpectedEffects_base {
2431
- readonly digest: Digest
2432
- readonly expected: string
2433
- readonly found: readonly ObjectId[]
2361
+ /** The one actionable line `SuiError.describe` produces for this error. */
2362
+ get message(): string;
2434
2363
  }
2435
2364
  ```
2436
2365
 
@@ -2577,7 +2506,7 @@ object has no output version, and nothing can be consumed without both.
2577
2506
  ## `@unconfirmed/sui-effect/tx`
2578
2507
 
2579
2508
 
2580
- 37 exported symbols.
2509
+ 42 exported symbols.
2581
2510
 
2582
2511
  ### `Built` (const)
2583
2512
 
@@ -2873,13 +2802,43 @@ export type ReconcileInput = Digest | Signed | SubmissionUnknown;
2873
2802
 
2874
2803
  What `Tx.reconcile` can be asked about.
2875
2804
 
2876
- ### `Reconciled` (type)
2805
+ ### `Reconciled` (const)
2877
2806
 
2878
2807
  ```ts
2879
- export type Reconciled = Executed | ExecutionFailed | NotApplied | SubmissionUnknown;
2808
+ declare const Reconciled: Schema
2809
+ // decodes to:
2810
+ // {
2811
+ // readonly _tag: "NotApplied";
2812
+ // readonly error: NotApplied;
2813
+ // } | {
2814
+ // readonly _tag: "Executed";
2815
+ // readonly executed: Executed;
2816
+ // } | {
2817
+ // readonly _tag: "ExecutionFailed";
2818
+ // readonly error: ExecutionFailed;
2819
+ // } | {
2820
+ // readonly _tag: "SubmissionUnknown";
2821
+ // readonly error: SubmissionUnknown;
2822
+ // }
2880
2823
  ```
2881
2824
 
2882
- What one entry of `Tx.reconcileAll` settled to.
2825
+ What one entry of `Tx.reconcileAll` settled to, as a tagged union.
2826
+
2827
+ Every case carries **one** discriminator in the same place: `_tag` is
2828
+ `"Executed"`, `"ExecutionFailed"`, `"NotApplied"` or `"SubmissionUnknown"`,
2829
+ and the payload is `executed` for the first and `error` for the other three.
2830
+ Before 0.1.2 this was a bare union of an `Executed` (which has no `_tag`) and
2831
+ three errors (which do), so the only way to tell a success from a failure was
2832
+ `"_tag" in entry` — a shape nothing could `Schema.match` or serialize.
2833
+
2834
+ ### `ReconciledOutcome` (type)
2835
+
2836
+ ```ts
2837
+ export type ReconciledOutcome = Executed | ExecutionFailed | NotApplied | SubmissionUnknown;
2838
+ ```
2839
+
2840
+ The shape `Tx.reconcileAll` returned in 0.1.0 and 0.1.1: the four outcomes as
2841
+ a bare union, with the successful one carrying no discriminator at all.
2883
2842
 
2884
2843
  ### `RemoteSigner` (interface)
2885
2844
 
@@ -2921,13 +2880,18 @@ export type SignatureScheme = "ED25519" | "Secp256k1" | "Secp256r1" | "MultiSig"
2921
2880
 
2922
2881
  The signature schemes a `Signer` built by this module can carry.
2923
2882
 
2924
- ### `Signed` (type)
2883
+ ### `Signed` (interface)
2925
2884
 
2926
2885
  ```ts
2927
- export type Signed = SignedTransaction;
2886
+ export interface Signed extends SignedTransaction {
2887
+ }
2928
2888
  ```
2929
2889
 
2930
- Signed bytes: everything `executeTransaction` needs, plus what `reconcile` needs.
2890
+ Signed bytes: everything `executeTransaction` needs, plus what `reconcile`
2891
+ needs.
2892
+
2893
+ An interface rather than a type alias so the name survives into `.d.ts`,
2894
+ editor hover and `LLMS.md`; structurally it is `SignedTransaction`.
2931
2895
 
2932
2896
  ### `Signer` (interface)
2933
2897
 
@@ -2963,16 +2927,11 @@ caller's error union does not grow a branch per credential kind.
2963
2927
  ```ts
2964
2928
  declare const SubmitConfig: Context.Reference<SubmitConfigService> & {
2965
2929
  defaults: SubmitConfigService;
2930
+ layer: (overrides: Partial<SubmitConfigService>) => Layer.Layer<never>;
2931
+ with: (overrides: Partial<SubmitConfigService>) => <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>;
2966
2932
  }
2967
2933
  ```
2968
2934
 
2969
- The lifecycle settings.
2970
-
2971
- Because this is a `Context.Reference` and not a service, it never appears in
2972
- an `R`: a one-shot script gets the defaults with no wiring, and an
2973
- application overrides what it cares about with
2974
- `Effect.provideService(effect, SubmitConfig, { ...SubmitConfig.defaults, validFor: "30 seconds" })`.
2975
-
2976
2935
  ### `SubmitConfigService` (interface)
2977
2936
 
2978
2937
  ```ts
@@ -3094,7 +3053,7 @@ Every decision `Tx.build`, `Tx.submit` and `Tx.reconcile` read from context.
3094
3053
  ### `SubmitError` (type)
3095
3054
 
3096
3055
  ```ts
3097
- export type SubmitError = ExecutionFailed | NotApplied | SubmissionUnknown | JournalError;
3056
+ export type SubmitError = ExecutionFailed | NotApplied | SubmissionUnknown | JournalError | TransportError;
3098
3057
  ```
3099
3058
 
3100
3059
  Everything `submit` can fail with, as one name.
@@ -3102,6 +3061,30 @@ Everything `submit` can fail with, as one name.
3102
3061
  An extension that wraps a submission spells its own errors plus this, rather
3103
3062
  than repeating four tags that will grow with the taxonomy.
3104
3063
 
3064
+ ### `SubmitViaError` (type)
3065
+
3066
+ ```ts
3067
+ export type SubmitViaError = ExecutionFailed | NotApplied | SubmissionUnknown | JournalError;
3068
+ ```
3069
+
3070
+ Everything `submitVia` can fail with, before the sender's own errors.
3071
+
3072
+ ### `SubmitViaReply` (type)
3073
+
3074
+ ```ts
3075
+ export type SubmitViaReply = unknown;
3076
+ ```
3077
+
3078
+ What a third party answers when it has submitted your bytes.
3079
+
3080
+ Three shapes are understood, in this order: an SDK `TransactionResult` (the
3081
+ service ran `executeTransaction` and passed the whole thing on), a reduced
3082
+ execute envelope (anything with a `digest` or an `effects`, decoded through
3083
+ `Executed.fromPartial`), and a bare digest string. Anything else — a
3084
+ `void`, an acknowledgement with no digest — means "ask the chain", and
3085
+ `submitVia` reconciles by the digest it already has, which is the digest of
3086
+ the bytes it handed over.
3087
+
3105
3088
  ### `TransactionExpiration` (const)
3106
3089
 
3107
3090
  Re-exported from `@unconfirmed/sui-effect`.
@@ -3113,200 +3096,17 @@ declare const Tx: {
3113
3096
  readonly build: (input: Transaction | Recipe, opts: {
3114
3097
  readonly sender: SuiAddress;
3115
3098
  readonly gasOwner?: SuiAddress;
3116
- }) => Effect.Effect<{
3117
- readonly digest: Digest;
3118
- readonly sender: SuiAddress;
3119
- readonly bytes: Uint8Array<ArrayBufferLike>;
3120
- readonly chain?: string | undefined;
3121
- readonly expiration?: {
3122
- readonly $kind: "None";
3123
- readonly None: true;
3124
- } | {
3125
- readonly $kind: "Epoch";
3126
- readonly Epoch: bigint;
3127
- } | {
3128
- readonly $kind: "ValidDuring";
3129
- readonly ValidDuring: {
3130
- readonly minEpoch: bigint | null;
3131
- readonly maxEpoch: bigint | null;
3132
- readonly minTimestamp: bigint | null;
3133
- readonly maxTimestamp: bigint | null;
3134
- readonly chain: string;
3135
- readonly nonce: number;
3136
- };
3137
- } | {
3138
- readonly $kind: "Validity";
3139
- readonly Validity: {
3140
- readonly allowedProposers: {
3141
- readonly epoch: bigint;
3142
- readonly proposers: readonly number[];
3143
- } | null;
3144
- readonly minEpoch: bigint | null;
3145
- readonly maxEpoch: bigint | null;
3146
- readonly minTimestamp: bigint | null;
3147
- readonly maxTimestamp: bigint | null;
3148
- readonly chain: string;
3149
- readonly nonce: number;
3150
- };
3151
- } | undefined;
3152
- readonly gasOwner?: SuiAddress | undefined;
3153
- }, TransportError | SimulationFailed | BuildError, Sui>;
3154
- readonly sign: (built: {
3155
- readonly digest: Digest;
3156
- readonly sender: SuiAddress;
3157
- readonly bytes: Uint8Array<ArrayBufferLike>;
3158
- readonly chain?: string | undefined;
3159
- readonly expiration?: {
3160
- readonly $kind: "None";
3161
- readonly None: true;
3162
- } | {
3163
- readonly $kind: "Epoch";
3164
- readonly Epoch: bigint;
3165
- } | {
3166
- readonly $kind: "ValidDuring";
3167
- readonly ValidDuring: {
3168
- readonly minEpoch: bigint | null;
3169
- readonly maxEpoch: bigint | null;
3170
- readonly minTimestamp: bigint | null;
3171
- readonly maxTimestamp: bigint | null;
3172
- readonly chain: string;
3173
- readonly nonce: number;
3174
- };
3175
- } | {
3176
- readonly $kind: "Validity";
3177
- readonly Validity: {
3178
- readonly allowedProposers: {
3179
- readonly epoch: bigint;
3180
- readonly proposers: readonly number[];
3181
- } | null;
3182
- readonly minEpoch: bigint | null;
3183
- readonly maxEpoch: bigint | null;
3184
- readonly minTimestamp: bigint | null;
3185
- readonly maxTimestamp: bigint | null;
3186
- readonly chain: string;
3187
- readonly nonce: number;
3188
- };
3189
- } | undefined;
3190
- readonly gasOwner?: SuiAddress | undefined;
3191
- }, signer: Signer) => Effect.Effect<{
3192
- readonly digest: Digest;
3193
- readonly sender: SuiAddress;
3194
- readonly signatures: readonly Signature[];
3195
- readonly bytes: Uint8Array<ArrayBufferLike>;
3196
- readonly chain?: string | undefined;
3197
- readonly expiration?: {
3198
- readonly $kind: "None";
3199
- readonly None: true;
3200
- } | {
3201
- readonly $kind: "Epoch";
3202
- readonly Epoch: bigint;
3203
- } | {
3204
- readonly $kind: "ValidDuring";
3205
- readonly ValidDuring: {
3206
- readonly minEpoch: bigint | null;
3207
- readonly maxEpoch: bigint | null;
3208
- readonly minTimestamp: bigint | null;
3209
- readonly maxTimestamp: bigint | null;
3210
- readonly chain: string;
3211
- readonly nonce: number;
3212
- };
3213
- } | {
3214
- readonly $kind: "Validity";
3215
- readonly Validity: {
3216
- readonly allowedProposers: {
3217
- readonly epoch: bigint;
3218
- readonly proposers: readonly number[];
3219
- } | null;
3220
- readonly minEpoch: bigint | null;
3221
- readonly maxEpoch: bigint | null;
3222
- readonly minTimestamp: bigint | null;
3223
- readonly maxTimestamp: bigint | null;
3224
- readonly chain: string;
3225
- readonly nonce: number;
3226
- };
3227
- } | undefined;
3228
- }, SigningError, never>;
3229
- readonly cosign: (signed: {
3230
- readonly digest: Digest;
3231
- readonly sender: SuiAddress;
3232
- readonly signatures: readonly Signature[];
3233
- readonly bytes: Uint8Array<ArrayBufferLike>;
3234
- readonly chain?: string | undefined;
3235
- readonly expiration?: {
3236
- readonly $kind: "None";
3237
- readonly None: true;
3238
- } | {
3239
- readonly $kind: "Epoch";
3240
- readonly Epoch: bigint;
3241
- } | {
3242
- readonly $kind: "ValidDuring";
3243
- readonly ValidDuring: {
3244
- readonly minEpoch: bigint | null;
3245
- readonly maxEpoch: bigint | null;
3246
- readonly minTimestamp: bigint | null;
3247
- readonly maxTimestamp: bigint | null;
3248
- readonly chain: string;
3249
- readonly nonce: number;
3250
- };
3251
- } | {
3252
- readonly $kind: "Validity";
3253
- readonly Validity: {
3254
- readonly allowedProposers: {
3255
- readonly epoch: bigint;
3256
- readonly proposers: readonly number[];
3257
- } | null;
3258
- readonly minEpoch: bigint | null;
3259
- readonly maxEpoch: bigint | null;
3260
- readonly minTimestamp: bigint | null;
3261
- readonly maxTimestamp: bigint | null;
3262
- readonly chain: string;
3263
- readonly nonce: number;
3264
- };
3265
- } | undefined;
3266
- }, signer: Signer) => Effect.Effect<{
3267
- readonly digest: Digest;
3268
- readonly sender: SuiAddress;
3269
- readonly signatures: readonly Signature[];
3270
- readonly bytes: Uint8Array<ArrayBufferLike>;
3271
- readonly chain?: string | undefined;
3272
- readonly expiration?: {
3273
- readonly $kind: "None";
3274
- readonly None: true;
3275
- } | {
3276
- readonly $kind: "Epoch";
3277
- readonly Epoch: bigint;
3278
- } | {
3279
- readonly $kind: "ValidDuring";
3280
- readonly ValidDuring: {
3281
- readonly minEpoch: bigint | null;
3282
- readonly maxEpoch: bigint | null;
3283
- readonly minTimestamp: bigint | null;
3284
- readonly maxTimestamp: bigint | null;
3285
- readonly chain: string;
3286
- readonly nonce: number;
3287
- };
3288
- } | {
3289
- readonly $kind: "Validity";
3290
- readonly Validity: {
3291
- readonly allowedProposers: {
3292
- readonly epoch: bigint;
3293
- readonly proposers: readonly number[];
3294
- } | null;
3295
- readonly minEpoch: bigint | null;
3296
- readonly maxEpoch: bigint | null;
3297
- readonly minTimestamp: bigint | null;
3298
- readonly maxTimestamp: bigint | null;
3299
- readonly chain: string;
3300
- readonly nonce: number;
3301
- };
3302
- } | undefined;
3303
- }, SigningError, never>;
3099
+ }) => Effect.Effect<Built, TransportError | SimulationFailed | BuildError, Sui>;
3100
+ readonly sign: (built: Built, signer: Signer) => Effect.Effect<Signed, SigningError, never>;
3101
+ readonly cosign: (signed: Signed, signer: Signer) => Effect.Effect<Signed, SigningError, never>;
3304
3102
  readonly sponsored: (opts: {
3305
3103
  readonly sender: SuiAddress;
3306
3104
  readonly gasOwner: SuiAddress;
3307
3105
  }) => (recipe: Recipe) => Recipe;
3308
3106
  readonly submit: (signed: Signed) => Effect.Effect<Executed, SubmitError, Sui>;
3107
+ readonly submitVia: <E, R>(signed: Signed, send: (bytes: Uint8Array, signatures: ReadonlyArray<Signature>) => Effect.Effect<SubmitViaReply, E, R>) => Effect.Effect<Executed, SubmitViaError | E, Sui | R>;
3309
3108
  readonly reconcile: (input: ReconcileInput) => Effect.Effect<Executed, TransportError | ExecutionFailed | SubmissionUnknown | NotApplied, Sui>;
3109
+ readonly recorded: (digest: Digest) => Effect.Effect<Option.Option<JournalEntry>, JournalError, never>;
3310
3110
  readonly run: (recipe: Transaction | Recipe, opts: {
3311
3111
  readonly signer: Signer;
3312
3112
  readonly gasOwner?: SuiAddress;
@@ -3315,8 +3115,42 @@ declare const Tx: {
3315
3115
  * the bytes name a gas owner that is not the sender.
3316
3116
  */
3317
3117
  readonly sponsor?: Signer;
3118
+ /**
3119
+ * Called with the signed bytes **after every signature is on them and
3120
+ * before the first `executeTransaction`**, which is the one moment a
3121
+ * consumer's own record has to be written: the digest is final from here
3122
+ * on, and anything that happens next may have reached the network.
3123
+ *
3124
+ * `Tx.submit` already writes its `Signed` journal entry at this point; this
3125
+ * is for the record the journal does not hold — a domain row joining the
3126
+ * digest to a batch, an outbox, a log line an operator greps. It runs
3127
+ * inside the sender lock, so it is ordered with the submission it belongs
3128
+ * to.
3129
+ *
3130
+ * Failing it fails the run **before anything is sent**, which is why its
3131
+ * error is a `JournalError`: that is the taxonomy's "the record could not
3132
+ * be written and nothing has gone out yet", it is already in `Tx.run`'s
3133
+ * union, and it is `not_applied`, so the documented retry idiom is correct.
3134
+ * Map your own persistence failure into it
3135
+ * (`Effect.mapError((cause) => new JournalError({ cause }))`).
3136
+ *
3137
+ * @since 0.1.2
3138
+ */
3139
+ readonly onSigned?: (signed: Signed) => Effect.Effect<void, JournalError>;
3318
3140
  }) => Effect.Effect<Executed, TransportError | SimulationFailed | ExecutionFailed | SubmissionUnknown | NotApplied | SigningError | BuildError | PolicyDenied | JournalError, Sui>;
3319
- readonly reconcileAll: () => Effect.Effect<readonly Reconciled[], TransportError | JournalError, Sui>;
3141
+ readonly reconcileAll: () => Effect.Effect<readonly ({
3142
+ readonly _tag: "ExecutionFailed";
3143
+ readonly error: ExecutionFailed;
3144
+ } | {
3145
+ readonly _tag: "SubmissionUnknown";
3146
+ readonly error: SubmissionUnknown;
3147
+ } | {
3148
+ readonly _tag: "NotApplied";
3149
+ readonly error: NotApplied;
3150
+ } | {
3151
+ readonly _tag: "Executed";
3152
+ readonly executed: Executed;
3153
+ })[], TransportError | JournalError, Sui>;
3320
3154
  }
3321
3155
  ```
3322
3156
 
@@ -3338,44 +3172,7 @@ The tags whose entries still need an answer from the network.
3338
3172
  declare const build: (input: Transaction | Recipe, opts: {
3339
3173
  readonly sender: SuiAddress;
3340
3174
  readonly gasOwner?: SuiAddress;
3341
- }) => Effect.Effect<{
3342
- readonly digest: Digest;
3343
- readonly sender: SuiAddress;
3344
- readonly bytes: Uint8Array<ArrayBufferLike>;
3345
- readonly chain?: string | undefined;
3346
- readonly expiration?: {
3347
- readonly $kind: "None";
3348
- readonly None: true;
3349
- } | {
3350
- readonly $kind: "Epoch";
3351
- readonly Epoch: bigint;
3352
- } | {
3353
- readonly $kind: "ValidDuring";
3354
- readonly ValidDuring: {
3355
- readonly minEpoch: bigint | null;
3356
- readonly maxEpoch: bigint | null;
3357
- readonly minTimestamp: bigint | null;
3358
- readonly maxTimestamp: bigint | null;
3359
- readonly chain: string;
3360
- readonly nonce: number;
3361
- };
3362
- } | {
3363
- readonly $kind: "Validity";
3364
- readonly Validity: {
3365
- readonly allowedProposers: {
3366
- readonly epoch: bigint;
3367
- readonly proposers: readonly number[];
3368
- } | null;
3369
- readonly minEpoch: bigint | null;
3370
- readonly maxEpoch: bigint | null;
3371
- readonly minTimestamp: bigint | null;
3372
- readonly maxTimestamp: bigint | null;
3373
- readonly chain: string;
3374
- readonly nonce: number;
3375
- };
3376
- } | undefined;
3377
- readonly gasOwner?: SuiAddress | undefined;
3378
- }, TransportError | SimulationFailed | BuildError, Sui>
3175
+ }) => Effect.Effect<Built, TransportError | SimulationFailed | BuildError, Sui>
3379
3176
  ```
3380
3177
 
3381
3178
  Builds a transaction into signable bytes.
@@ -3416,81 +3213,7 @@ Re-exported from `@unconfirmed/sui-effect`.
3416
3213
  ### `cosign` (const)
3417
3214
 
3418
3215
  ```ts
3419
- declare const cosign: (signed: {
3420
- readonly digest: Digest;
3421
- readonly sender: SuiAddress;
3422
- readonly signatures: readonly Signature[];
3423
- readonly bytes: Uint8Array<ArrayBufferLike>;
3424
- readonly chain?: string | undefined;
3425
- readonly expiration?: {
3426
- readonly $kind: "None";
3427
- readonly None: true;
3428
- } | {
3429
- readonly $kind: "Epoch";
3430
- readonly Epoch: bigint;
3431
- } | {
3432
- readonly $kind: "ValidDuring";
3433
- readonly ValidDuring: {
3434
- readonly minEpoch: bigint | null;
3435
- readonly maxEpoch: bigint | null;
3436
- readonly minTimestamp: bigint | null;
3437
- readonly maxTimestamp: bigint | null;
3438
- readonly chain: string;
3439
- readonly nonce: number;
3440
- };
3441
- } | {
3442
- readonly $kind: "Validity";
3443
- readonly Validity: {
3444
- readonly allowedProposers: {
3445
- readonly epoch: bigint;
3446
- readonly proposers: readonly number[];
3447
- } | null;
3448
- readonly minEpoch: bigint | null;
3449
- readonly maxEpoch: bigint | null;
3450
- readonly minTimestamp: bigint | null;
3451
- readonly maxTimestamp: bigint | null;
3452
- readonly chain: string;
3453
- readonly nonce: number;
3454
- };
3455
- } | undefined;
3456
- }, signer: Signer) => Effect.Effect<{
3457
- readonly digest: Digest;
3458
- readonly sender: SuiAddress;
3459
- readonly signatures: readonly Signature[];
3460
- readonly bytes: Uint8Array<ArrayBufferLike>;
3461
- readonly chain?: string | undefined;
3462
- readonly expiration?: {
3463
- readonly $kind: "None";
3464
- readonly None: true;
3465
- } | {
3466
- readonly $kind: "Epoch";
3467
- readonly Epoch: bigint;
3468
- } | {
3469
- readonly $kind: "ValidDuring";
3470
- readonly ValidDuring: {
3471
- readonly minEpoch: bigint | null;
3472
- readonly maxEpoch: bigint | null;
3473
- readonly minTimestamp: bigint | null;
3474
- readonly maxTimestamp: bigint | null;
3475
- readonly chain: string;
3476
- readonly nonce: number;
3477
- };
3478
- } | {
3479
- readonly $kind: "Validity";
3480
- readonly Validity: {
3481
- readonly allowedProposers: {
3482
- readonly epoch: bigint;
3483
- readonly proposers: readonly number[];
3484
- } | null;
3485
- readonly minEpoch: bigint | null;
3486
- readonly maxEpoch: bigint | null;
3487
- readonly minTimestamp: bigint | null;
3488
- readonly maxTimestamp: bigint | null;
3489
- readonly chain: string;
3490
- readonly nonce: number;
3491
- };
3492
- } | undefined;
3493
- }, SigningError, never>
3216
+ declare const cosign: (signed: Signed, signer: Signer) => Effect.Effect<Signed, SigningError, never>
3494
3217
  ```
3495
3218
 
3496
3219
  Adds one more signature to already signed bytes, for a sponsored or
@@ -3526,15 +3249,23 @@ be a security bug, not a convenience.
3526
3249
  declare const fromConfig: (name?: string) => Effect.Effect<Signer, Config.ConfigError>
3527
3250
  ```
3528
3251
 
3529
- Reads a Bech32 `suiprivkey1…` secret key from configuration and builds the
3530
- signer for whichever of the three schemes its flag names.
3252
+ Reads a secret key from configuration and builds its signer.
3253
+
3254
+ Two spellings, told apart by the text itself:
3255
+
3256
+ - a **Bech32 `suiprivkey1…`**, whose flag names one of the three schemes;
3257
+ - a **32-byte hex seed** (64 hex characters, `0x` optional), which carries no
3258
+ scheme and is read as **Ed25519** — the default every Sui tool uses for a
3259
+ raw seed. This is what a secret manager or another language's SDK hands
3260
+ over, and reading it here is what keeps `fromHex` and the decoded bytes out
3261
+ of application code.
3531
3262
 
3532
3263
  The key is read with `Config.redacted`, and the decoded bytes never leave
3533
3264
  this function. Neither does anything derived from them: the failure carries
3534
3265
  one fixed sentence and no `cause`, because the decoder's own message quotes
3535
3266
  the input it rejected.
3536
3267
 
3537
- **Fails with: `ConfigError` when the variable is missing, is not a Bech32 Sui private key, or names a scheme that has no keypair class (`MultiSig`, `ZkLogin`, `Passkey` — use {@link remote} for those).**
3268
+ **Fails with: `ConfigError` when the variable is missing, is neither spelling, or names a scheme that has no keypair class (`MultiSig`, `ZkLogin`, `Passkey` — use {@link remote} for those).**
3538
3269
 
3539
3270
  ### `fromKeypair` (const)
3540
3271
 
@@ -3563,12 +3294,29 @@ and `signPersonalMessage`. Nothing here needs the secret, so nothing here
3563
3294
  needs a keypair, and the `Signer` this returns exposes no secret material
3564
3295
  either.
3565
3296
 
3297
+ **`toSuiAddress()` and `getKeyScheme()` are read here, synchronously**, and
3298
+ the address and scheme of the returned `Signer` are whatever they answered at
3299
+ this moment: a credential that changes accounts later is a different
3300
+ `Signer`, built again. A test double therefore needs both of those methods,
3301
+ not only `signTransaction` — a double without `getKeyScheme` used to produce
3302
+ `scheme: undefined` and nothing complained until a validator did.
3303
+
3304
+ **Clear-signing inputs are the SDK signer's own concern.** The Ledger signer
3305
+ takes `signTransaction(bytes, bcsObjects?, resolution?)` and resolves those
3306
+ extra arguments through the client it was constructed with; this passes only
3307
+ the bytes, which is the whole of the base `Signer` contract. A device that
3308
+ needs more than the bytes gets it from its own client, or from
3309
+ `remote`.
3310
+
3566
3311
  For a credential that is not an SDK `Signer` at all — a remote service, a
3567
3312
  hardware device behind your own protocol — use `remote`, which takes
3568
3313
  Effects and the address to sign as.
3569
3314
 
3570
- Never fails: a bad address or signature surfaces as a `SigningError` from the
3571
- member that produced it, not from construction.
3315
+ **Throws** a `TypeError` naming the missing member when the argument is not
3316
+ an SDK signer — that is a wiring mistake in the caller, not a runtime
3317
+ failure a program recovers from. Otherwise never fails: a bad address or
3318
+ signature surfaces as a `SigningError` from the member that produced it, not
3319
+ from construction.
3572
3320
 
3573
3321
  ### `isUnresolved` (const)
3574
3322
 
@@ -3909,12 +3657,29 @@ always `SubmissionUnknown`. Pass the `Signed` bytes (or the
3909
3657
  ### `reconcileAll` (const)
3910
3658
 
3911
3659
  ```ts
3912
- declare const reconcileAll: () => Effect.Effect<readonly Reconciled[], TransportError | JournalError, Sui>
3660
+ declare const reconcileAll: () => Effect.Effect<readonly ({
3661
+ readonly _tag: "ExecutionFailed";
3662
+ readonly error: ExecutionFailed;
3663
+ } | {
3664
+ readonly _tag: "SubmissionUnknown";
3665
+ readonly error: SubmissionUnknown;
3666
+ } | {
3667
+ readonly _tag: "NotApplied";
3668
+ readonly error: NotApplied;
3669
+ } | {
3670
+ readonly _tag: "Executed";
3671
+ readonly executed: Executed;
3672
+ })[], TransportError | JournalError, Sui>
3913
3673
  ```
3914
3674
 
3915
3675
  Settles every unresolved entry in the journal: the explicit startup call a
3916
3676
  long-lived application makes after building a durable `Journal`.
3917
3677
 
3678
+ **Only unresolved entries come back.** `Signed` and `Unknown` are the tags
3679
+ that still need an answer; a digest that already settled is not in the
3680
+ journal's unresolved index and is not in this array. Ask about one of those
3681
+ with `recorded`.
3682
+
3918
3683
  Nothing here fails per entry: each one settles to an `Executed`, an
3919
3684
  `ExecutionFailed`, a `NotApplied` or a `SubmissionUnknown`, in the order the
3920
3685
  journal listed them, and the journal is updated to match. Every settled entry
@@ -3930,6 +3695,28 @@ the same rule `Tx.submit` follows.
3930
3695
 
3931
3696
  **Fails with: `JournalError`, `TransportError`.**
3932
3697
 
3698
+ ### `recorded` (const)
3699
+
3700
+ ```ts
3701
+ declare const recorded: (digest: Digest) => Effect.Effect<Option.Option<JournalEntry>, JournalError, never>
3702
+ ```
3703
+
3704
+ What the journal recorded for one digest, if anything.
3705
+
3706
+ `Tx.reconcileAll` returns **only the entries that were still unresolved**,
3707
+ because those are the ones it had work to do about; a transaction that had
3708
+ already settled — executed, failed, or proven never applied — is not in its
3709
+ answer and never will be. This is how to ask about one of those: the entry is
3710
+ `Executed`, `Failed` or `NotApplied` for a settled digest, `Signed` or
3711
+ `Unknown` for one still in flight, and `None` for a digest this journal has
3712
+ never seen (including every digest at all, when the journal is the in-memory
3713
+ default and the process restarted).
3714
+
3715
+ It is exactly `(yield* Journal).get(digest)`, named so that the recovery path
3716
+ does not have to reach for the reference.
3717
+
3718
+ **Fails with: `JournalError`.**
3719
+
3933
3720
  ### `remote` (const)
3934
3721
 
3935
3722
  ```ts
@@ -3957,6 +3744,28 @@ declare const run: (recipe: Transaction | Recipe, opts: {
3957
3744
  * the bytes name a gas owner that is not the sender.
3958
3745
  */
3959
3746
  readonly sponsor?: Signer;
3747
+ /**
3748
+ * Called with the signed bytes **after every signature is on them and
3749
+ * before the first `executeTransaction`**, which is the one moment a
3750
+ * consumer's own record has to be written: the digest is final from here
3751
+ * on, and anything that happens next may have reached the network.
3752
+ *
3753
+ * `Tx.submit` already writes its `Signed` journal entry at this point; this
3754
+ * is for the record the journal does not hold — a domain row joining the
3755
+ * digest to a batch, an outbox, a log line an operator greps. It runs
3756
+ * inside the sender lock, so it is ordered with the submission it belongs
3757
+ * to.
3758
+ *
3759
+ * Failing it fails the run **before anything is sent**, which is why its
3760
+ * error is a `JournalError`: that is the taxonomy's "the record could not
3761
+ * be written and nothing has gone out yet", it is already in `Tx.run`'s
3762
+ * union, and it is `not_applied`, so the documented retry idiom is correct.
3763
+ * Map your own persistence failure into it
3764
+ * (`Effect.mapError((cause) => new JournalError({ cause }))`).
3765
+ *
3766
+ * @since 0.1.2
3767
+ */
3768
+ readonly onSigned?: (signed: Signed) => Effect.Effect<void, JournalError>;
3960
3769
  }) => Effect.Effect<Executed, TransportError | SimulationFailed | ExecutionFailed | SubmissionUnknown | NotApplied | SigningError | BuildError | PolicyDenied | JournalError, Sui>
3961
3770
  ```
3962
3771
 
@@ -3989,86 +3798,20 @@ and immediately after the build when it came out of the recipe. Use the
3989
3798
  explicit lifecycle (`Tx.build`, `Tx.sign`, `Tx.cosign`, `Tx.submit`) when the
3990
3799
  two parties cannot both sign in one process.
3991
3800
 
3801
+ **`onSigned` is the hook between signing and sending.** A program with its
3802
+ own record to keep — a batch row, an outbox, an idempotency key — has to
3803
+ write the digest before the first send, and that used to mean giving up
3804
+ `Tx.run` and reassembling `withSenderLock(build → sign → record → submit)` by
3805
+ hand. Pass `onSigned` instead: it runs inside the sender lock, after the last
3806
+ signature and before `Tx.submit`'s first `executeTransaction`, and failing it
3807
+ fails the run with nothing sent.
3808
+
3992
3809
  **Fails with: `BuildError`, `SimulationFailed`, `PolicyDenied`, `SigningError`, `ExecutionFailed`, `NotApplied`, `SubmissionUnknown`, `JournalError`, `TransportError` (from the build reads; once bytes are sent, transport failures become `SubmissionUnknown`).**
3993
3810
 
3994
3811
  ### `sign` (const)
3995
3812
 
3996
3813
  ```ts
3997
- declare const sign: (built: {
3998
- readonly digest: Digest;
3999
- readonly sender: SuiAddress;
4000
- readonly bytes: Uint8Array<ArrayBufferLike>;
4001
- readonly chain?: string | undefined;
4002
- readonly expiration?: {
4003
- readonly $kind: "None";
4004
- readonly None: true;
4005
- } | {
4006
- readonly $kind: "Epoch";
4007
- readonly Epoch: bigint;
4008
- } | {
4009
- readonly $kind: "ValidDuring";
4010
- readonly ValidDuring: {
4011
- readonly minEpoch: bigint | null;
4012
- readonly maxEpoch: bigint | null;
4013
- readonly minTimestamp: bigint | null;
4014
- readonly maxTimestamp: bigint | null;
4015
- readonly chain: string;
4016
- readonly nonce: number;
4017
- };
4018
- } | {
4019
- readonly $kind: "Validity";
4020
- readonly Validity: {
4021
- readonly allowedProposers: {
4022
- readonly epoch: bigint;
4023
- readonly proposers: readonly number[];
4024
- } | null;
4025
- readonly minEpoch: bigint | null;
4026
- readonly maxEpoch: bigint | null;
4027
- readonly minTimestamp: bigint | null;
4028
- readonly maxTimestamp: bigint | null;
4029
- readonly chain: string;
4030
- readonly nonce: number;
4031
- };
4032
- } | undefined;
4033
- readonly gasOwner?: SuiAddress | undefined;
4034
- }, signer: Signer) => Effect.Effect<{
4035
- readonly digest: Digest;
4036
- readonly sender: SuiAddress;
4037
- readonly signatures: readonly Signature[];
4038
- readonly bytes: Uint8Array<ArrayBufferLike>;
4039
- readonly chain?: string | undefined;
4040
- readonly expiration?: {
4041
- readonly $kind: "None";
4042
- readonly None: true;
4043
- } | {
4044
- readonly $kind: "Epoch";
4045
- readonly Epoch: bigint;
4046
- } | {
4047
- readonly $kind: "ValidDuring";
4048
- readonly ValidDuring: {
4049
- readonly minEpoch: bigint | null;
4050
- readonly maxEpoch: bigint | null;
4051
- readonly minTimestamp: bigint | null;
4052
- readonly maxTimestamp: bigint | null;
4053
- readonly chain: string;
4054
- readonly nonce: number;
4055
- };
4056
- } | {
4057
- readonly $kind: "Validity";
4058
- readonly Validity: {
4059
- readonly allowedProposers: {
4060
- readonly epoch: bigint;
4061
- readonly proposers: readonly number[];
4062
- } | null;
4063
- readonly minEpoch: bigint | null;
4064
- readonly maxEpoch: bigint | null;
4065
- readonly minTimestamp: bigint | null;
4066
- readonly maxTimestamp: bigint | null;
4067
- readonly chain: string;
4068
- readonly nonce: number;
4069
- };
4070
- } | undefined;
4071
- }, SigningError, never>
3814
+ declare const sign: (built: Built, signer: Signer) => Effect.Effect<Signed, SigningError, never>
4072
3815
  ```
4073
3816
 
4074
3817
  Signs built bytes.
@@ -4113,9 +3856,16 @@ the transaction that was already signed. When the retries run out it runs
4113
3856
  `Tx.reconcile`, which either finds the transaction, proves it never applied,
4114
3857
  or says it does not know.
4115
3858
 
4116
- `TransportError` never escapes: once bytes may have been sent, "the network
4117
- was unreachable" is not an answer a caller can act on, so it becomes
4118
- `SubmissionUnknown` carrying the signed bytes.
3859
+ `TransportError` almost never escapes: once bytes may have been sent, "the
3860
+ network was unreachable" is not an answer a caller can act on, so it becomes
3861
+ `SubmissionUnknown` carrying the signed bytes. **The one exception is a node
3862
+ that refused the request outright** — gRPC `INVALID_ARGUMENT`, which is what
3863
+ a validator answers for malformed bytes or for a sponsored transaction
3864
+ carrying one signature. That answer came from the node, it is final, and
3865
+ nothing was executed, so it is reported as the `TransportError` it is rather
3866
+ than reconciled: a reconcile would go on to ask "is this digest on chain?",
3867
+ a question about a transaction that was never sent, and a lagging or scripted
3868
+ node can answer it yes.
4119
3869
 
4120
3870
  `JournalError` can only come from the `Signed` write, before anything has
4121
3871
  been sent. Once the network has answered, a journal write that fails is
@@ -4123,7 +3873,40 @@ logged with `Effect.logError` and the answer stands, because "the journal is
4123
3873
  broken" is not a thing a caller can act on and reporting it in place of a
4124
3874
  charged `ExecutionFailed` would invite a second submission.
4125
3875
 
4126
- **Fails with: `ExecutionFailed` (applied on chain and failed; gas was charged), `NotApplied` (provably never applied), `SubmissionUnknown` (the outcome is not known and the bytes are in the error), `JournalError` (only before the first send).**
3876
+ **Fails with: `ExecutionFailed` (applied on chain and failed; gas was charged), `NotApplied` (provably never applied), `SubmissionUnknown` (the outcome is not known and the bytes are in the error), `JournalError` (only before the first send), `TransportError` (only `INVALID_ARGUMENT`: the node refused the submission and nothing was executed).**
3877
+
3878
+ ### `submitVia` (const)
3879
+
3880
+ ```ts
3881
+ declare const submitVia: <E, R>(signed: Signed, send: (bytes: Uint8Array, signatures: ReadonlyArray<Signature>) => Effect.Effect<SubmitViaReply, E, R>) => Effect.Effect<Executed, SubmitViaError | E, Sui | R>
3882
+ ```
3883
+
3884
+ Submits through **someone else** — a relay, a sponsorship service, a backend
3885
+ that holds the only key allowed to talk to the node — and keeps the journal
3886
+ and the evidence rules that `Tx.submit` would have kept.
3887
+
3888
+ The bytes never reach `executeTransaction` here; `send` does whatever the
3889
+ service needs (an HTTP POST, a queue, another process) and answers with
3890
+ whatever the service returns. What this owns is everything around it:
3891
+
3892
+ - a `Signed` journal entry is written **before** `send` is called, so a crash
3893
+ between here and the service leaves the same record a direct submit leaves;
3894
+ - `send` is called **once**. A third party's submit is not known to be
3895
+ idempotent and re-sending is not this function's decision;
3896
+ - the reply is turned into an `Executed` when it carries one (see
3897
+ `SubmitViaReply`), and otherwise the chain is asked — by the digest
3898
+ of the bytes that were handed over, which a co-signature does not change;
3899
+ - a failure from `send` is **ambiguous** unless it says otherwise, so it ends
3900
+ in `Tx.reconcile` with the full evidence rules: the ordered expiry check,
3901
+ the chain-identity guard, the versioned consumer check. A sender error that
3902
+ **declares** `outcome: "not_applied"` on the instance — a 400 from the
3903
+ service, a refusal before anything went out — is taken at its word and
3904
+ fails straight through without spending a reconcile. A `TransportError` is
3905
+ not that: the taxonomy calls it `not_applied`, but a transport failure
3906
+ *talking to the relay* is exactly the ambiguous case, so it reconciles;
3907
+ - every terminal answer is journalled, exactly as `Tx.submit` journals it.
3908
+
3909
+ **Fails with: `ExecutionFailed`, `NotApplied`, `SubmissionUnknown` (carrying the signed bytes, with the sender's failure as its `cause`), `JournalError` (only from the write before `send`), and `E` — whatever `send` fails with — when that error declares `outcome: "not_applied"`.**
4127
3910
 
4128
3911
  ## `@unconfirmed/sui-effect/journal`
4129
3912
 
@@ -4488,7 +4271,28 @@ added to the value itself.
4488
4271
  ## `@unconfirmed/sui-effect/script`
4489
4272
 
4490
4273
 
4491
- 9 exported symbols.
4274
+ 11 exported symbols.
4275
+
4276
+ ### `ReportOptions` (interface)
4277
+
4278
+ ```ts
4279
+ export interface ReportOptions {
4280
+ /** Where the lines go, one at a time. Defaults to `process.stderr`. */
4281
+ readonly stderr?: (line: string) => void;
4282
+ /**
4283
+ * The journal the program ran with, for the unresolved entries.
4284
+ *
4285
+ * A program with its own `ManagedRuntime` has to hand it over — reading the
4286
+ * bare `Journal` reference here would look in the process-wide in-memory
4287
+ * default and find nothing, which is precisely wrong for the program that
4288
+ * provided a durable one. Left out, the default reference is read, which is
4289
+ * right for a program that never provided a journal at all.
4290
+ */
4291
+ readonly journal?: JournalService;
4292
+ }
4293
+ ```
4294
+
4295
+ What `report` needs beyond the `Exit`.
4492
4296
 
4493
4297
  ### `Script` (class)
4494
4298
 
@@ -4537,6 +4341,8 @@ export declare class Script extends Script_base {
4537
4341
  static readonly exitCode: <A, E>(exit: Exit.Exit<A, E>, options?: ExitCodeOptions) => number;
4538
4342
  /** See {@link run}. */
4539
4343
  static readonly run: <A, E>(effect: Effect.Effect<A, E, Script | Sui | SuiCore>, options?: ScriptRunOptions) => Promise<number>;
4344
+ /** See {@link report}. */
4345
+ static readonly report: <A, E>(exit: Exit.Exit<A, E>, options?: ReportOptions) => Promise<number>;
4540
4346
  }
4541
4347
  ```
4542
4348
 
@@ -4663,6 +4469,26 @@ script that means mainnet has to say so twice, in `SUI_NETWORK` and in
4663
4469
 
4664
4470
  **Fails with: `ConfigError`.**
4665
4471
 
4472
+ ### `report` (const)
4473
+
4474
+ ```ts
4475
+ declare const report: <A, E>(exit: Exit.Exit<A, E>, options?: ReportOptions) => Promise<number>
4476
+ ```
4477
+
4478
+ Prints what `run` prints, and answers the exit code, for a program that
4479
+ owns its own process.
4480
+
4481
+ `Script.run` is a whole entrypoint: it builds the layer, forks the root
4482
+ fiber, installs signal handlers and exits. A CLI with its own argv parser and
4483
+ twenty subcommands has all of that already and still wants the two things
4484
+ `run` does at the end — one diagnostic line per failure (with the bytes of a
4485
+ `SubmissionUnknown`), and every unresolved journal entry on a non-zero exit —
4486
+ plus the code itself. That is this: hand it the `Exit` of whatever you ran,
4487
+ get the lines on stderr and the number back, and call `process.exitCode = …`
4488
+ yourself rather than `process.exit`, so buffered output still flushes.
4489
+
4490
+ **Never fails.**
4491
+
4666
4492
  ### `run` (const)
4667
4493
 
4668
4494
  ```ts
@@ -4699,7 +4525,7 @@ Returns the exit code as well as passing it to `exit`, so a test can inject
4699
4525
  ## `@unconfirmed/sui-effect/testing`
4700
4526
 
4701
4527
 
4702
- 16 exported symbols.
4528
+ 17 exported symbols.
4703
4529
 
4704
4530
  ### `CLOCK_TYPE` (const)
4705
4531
 
@@ -4728,6 +4554,19 @@ declare const DEFAULT_EPOCH = 100n
4728
4554
 
4729
4555
  The epoch the fake reports when a script does not set one.
4730
4556
 
4557
+ ### `FakeBalance` (interface)
4558
+
4559
+ ```ts
4560
+ export interface FakeBalance extends SuiClientTypes.Balance {
4561
+ readonly owner?: string;
4562
+ }
4563
+ ```
4564
+
4565
+ A balance the fake serves, optionally for one owner.
4566
+
4567
+ `owner` is the address this balance belongs to. Left out, the entry answers
4568
+ for **any** owner, which is what every script written before 0.1.2 assumed.
4569
+
4731
4570
  ### `FakeChange` (interface)
4732
4571
 
4733
4572
  ```ts
@@ -4860,7 +4699,29 @@ export interface FakeScript {
4860
4699
  readonly coins?: ReadonlyArray<SuiClientTypes.Coin>;
4861
4700
  /** The gas budget the resolve plugin sets when a transaction has none. */
4862
4701
  readonly gasBudget?: bigint;
4863
- readonly balances?: ReadonlyArray<SuiClientTypes.Balance>;
4702
+ /**
4703
+ * The balances `getBalance` and `listBalances` serve.
4704
+ *
4705
+ * Keyed by **owner and coin type**: an entry with an `owner` answers only for
4706
+ * that address, and one without answers for any, which is what a script
4707
+ * written before 0.1.2 meant. A `getBalance` for an owner and coin type no
4708
+ * entry names answers zero, the way a node does for an address that holds
4709
+ * none of that coin.
4710
+ */
4711
+ readonly balances?: ReadonlyArray<FakeBalance>;
4712
+ /**
4713
+ * Scripted outcomes for `getObject`, oldest first; the last repeats forever.
4714
+ *
4715
+ * This is transport-error injection on a **read**: `FakeOutcome.transportError`
4716
+ * and `FakeOutcome.timeoutThen` are what it is for, and they are how a test
4717
+ * drives `SuiCore`'s read retry policy or an extension's own fallback.
4718
+ * `FakeOutcome.notFound` answers the way a missing object does
4719
+ * (`ObjectNotFound`). A `succeed` entry — and an empty or exhausted script —
4720
+ * means "serve the object map", which is the normal behaviour.
4721
+ *
4722
+ * @since 0.1.2
4723
+ */
4724
+ readonly getObject?: ReadonlyArray<FakeOutcome>;
4864
4725
  /**
4865
4726
  * What `getCoinMetadata` answers, keyed by coin type.
4866
4727
  *
@@ -5021,7 +4882,7 @@ export interface SuiCoreFakeState {
5021
4882
  */
5022
4883
  readonly recordTransaction: (digest: string, outcome: FakeOutcome) => Effect.Effect<void>;
5023
4884
  /** Replaces the remaining scripted outcomes of a method. */
5024
- readonly setOutcomes: (method: "simulate" | "execute" | "getTransaction" | "buildSimulate", outcomes: ReadonlyArray<FakeOutcome>) => Effect.Effect<void>;
4885
+ readonly setOutcomes: (method: "simulate" | "execute" | "getTransaction" | "buildSimulate" | "getObject", outcomes: ReadonlyArray<FakeOutcome>) => Effect.Effect<void>;
5025
4886
  }
5026
4887
  ```
5027
4888
 
@@ -5043,6 +4904,7 @@ declare const SuiTest: {
5043
4904
  readonly scriptExecute: (outcomes: ReadonlyArray<FakeOutcome>) => Effect.Effect<void, never, SuiCoreFake>;
5044
4905
  readonly scriptSimulate: (outcomes: ReadonlyArray<FakeOutcome>) => Effect.Effect<void, never, SuiCoreFake>;
5045
4906
  readonly scriptGetTransaction: (outcomes: ReadonlyArray<FakeOutcome>) => Effect.Effect<void, never, SuiCoreFake>;
4907
+ readonly scriptGetObject: (outcomes: ReadonlyArray<FakeOutcome>) => Effect.Effect<void, never, SuiCoreFake>;
5046
4908
  readonly calls: (method?: string) => Effect.Effect<ReadonlyArray<RecordedCall>, never, SuiCoreFake>;
5047
4909
  }
5048
4910
  ```
@@ -5774,22 +5636,12 @@ const SettlementBcs = bcs.struct("Settlement", {
5774
5636
  * needs a real `BcsType` so it can re-serialize what it parsed and reject
5775
5637
  * trailing bytes, and a domain type is not a BCS layout.
5776
5638
  */
5777
- export class Settlement extends Schema.Class<Settlement>("Settlement")({
5639
+ export class Settlement extends Schema.Class<Settlement>("escrow/Settlement")({
5778
5640
  escrowId: ObjectId,
5779
5641
  settledAt: Schema.DateTimeUtc,
5780
5642
  claimedBy: SuiAddress
5781
5643
  }) {}
5782
5644
 
5783
- /**
5784
- * The halfway shape the transformation produces: the domain field names, before
5785
- * `Settlement`'s own schema brands the ids.
5786
- */
5787
- interface SettlementParts {
5788
- readonly escrowId: string
5789
- readonly settledAt: DateTime.Utc
5790
- readonly claimedBy: string
5791
- }
5792
-
5793
5645
  /**
5794
5646
  * The composed codec: BCS bytes to `Settlement`, and back.
5795
5647
  *
@@ -5803,10 +5655,11 @@ interface SettlementParts {
5803
5655
  *
5804
5656
  * Two details worth copying:
5805
5657
  *
5806
- * - **`decode` produces the target's field shape, not an instance.** `decodeTo`
5807
- * sits between the source type and the target schema, which is what lets the
5808
- * target's own checks the `ObjectId` and `SuiAddress` brands here — run
5809
- * afterwards.
5658
+ * - **`decode` produces the target's `Encoded` side, not an instance.**
5659
+ * `typeof Settlement.Encoded` is exactly that shape, so nothing has to be
5660
+ * written out by hand and nothing can drift. `decodeTo` sits between the
5661
+ * source type and the target schema, which is what lets the target's own
5662
+ * checks — the `ObjectId` and `SuiAddress` brands here — run afterwards.
5810
5663
  * - **`encode` is the inverse mapper and is not optional.** A codec that cannot
5811
5664
  * encode is one `Schema.encodeUnknownEffect` fails on, and the compiler asks
5812
5665
  * for it here rather than at the call site.
@@ -5826,7 +5679,7 @@ export const SettlementContent = (typeOrigin: string) =>
5826
5679
  ).pipe(
5827
5680
  Schema.decodeTo(
5828
5681
  Settlement,
5829
- SchemaTransformation.transformOrFail<SettlementParts, typeof SettlementBcs.$inferType>({
5682
+ SchemaTransformation.transformOrFail<typeof Settlement.Encoded, typeof SettlementBcs.$inferType>({
5830
5683
  decode: (fields, options) =>
5831
5684
  // `transformOrFail`, not `transform`, because one of these mappings can
5832
5685
  // fail: a `u64` of milliseconds is not necessarily a time. A `transform`
@@ -5843,7 +5696,7 @@ export const SettlementContent = (typeOrigin: string) =>
5843
5696
  options
5844
5697
  )
5845
5698
  ),
5846
- (settledAt): SettlementParts => ({
5699
+ (settledAt): typeof Settlement.Encoded => ({
5847
5700
  escrowId: fields.escrow_id,
5848
5701
  settledAt,
5849
5702
  claimedBy: fields.claimed_by
@@ -5875,6 +5728,14 @@ export const SettlementContent = (typeOrigin: string) =>
5875
5728
  *
5876
5729
  * The tags are prefixed with the package name because `EscrowNotFound` is a
5877
5730
  * name two packages could plausibly both want.
5731
+ *
5732
+ * And every one of them has a real `.message`. `Schema.TaggedError` leaves it
5733
+ * empty, so an error that supplies neither an `override get message()` nor a
5734
+ * `message` schema field surfaces an empty string everywhere a consumer
5735
+ * catches it, and `SuiError.toJson` emits no `message` key at all. The getter
5736
+ * is the usual answer — it stays out of the encoding, so it costs nothing at
5737
+ * the constructor — and a `message` schema field is for the case where the
5738
+ * sentence comes from somewhere else, as `EscrowSettlementUnknown`'s does.
5878
5739
  */
5879
5740
  import { Schema } from "effect"
5880
5741
  import { Digest, type Outcome, ObjectId } from "@unconfirmed/sui-effect"
@@ -5890,6 +5751,18 @@ export class EscrowNotFound extends Schema.TaggedError<EscrowNotFound>()(
5890
5751
  { escrowId: ObjectId }
5891
5752
  ) {
5892
5753
  readonly outcome: Outcome = "not_applied"
5754
+
5755
+ /**
5756
+ * `Schema.TaggedError` leaves `.message` empty, so anything surfacing
5757
+ * `error.message` — a log line, a `catch` in a consumer's UI,
5758
+ * `SuiError.toJson` — shows nothing unless the class supplies one. This is
5759
+ * the idiom sui-effect's own errors use, and the reason every error here has
5760
+ * one: define a getter over the fields, never a `message` schema field you
5761
+ * then have to pass to every constructor.
5762
+ */
5763
+ override get message(): string {
5764
+ return `no escrow ${this.escrowId}`
5765
+ }
5893
5766
  }
5894
5767
 
5895
5768
  /**
@@ -5899,6 +5772,10 @@ export class EscrowNotFound extends Schema.TaggedError<EscrowNotFound>()(
5899
5772
  * This is the case the `outcome` field exists for: the transaction applied, the
5900
5773
  * operation as a whole did not finish, and the only safe next step is to
5901
5774
  * reconcile rather than to retry. A script that fails with this exits 3.
5775
+ *
5776
+ * Its `message` is a **schema field** rather than a getter, because the
5777
+ * sentence comes from the settlement service rather than from these fields.
5778
+ * Either way `.message` is a real string and `SuiError.toJson` carries it.
5902
5779
  */
5903
5780
  export class EscrowSettlementUnknown extends Schema.TaggedError<EscrowSettlementUnknown>()(
5904
5781
  "escrow/EscrowSettlementUnknown",
@@ -5923,6 +5800,11 @@ export class EscrowUnsupportedNetwork extends Schema.TaggedError<EscrowUnsupport
5923
5800
  { network: Schema.String }
5924
5801
  ) {
5925
5802
  readonly outcome: Outcome = "not_applied"
5803
+
5804
+ /** See {@link EscrowNotFound.message}: a getter, not a schema field. */
5805
+ override get message(): string {
5806
+ return `this release bundles no escrow deployment for ${this.network}`
5807
+ }
5926
5808
  }
5927
5809
  ```
5928
5810
 
@@ -6144,7 +6026,7 @@ const make = (
6144
6026
  Effect.flatMap((raw) =>
6145
6027
  decodeAddress(raw).pipe(
6146
6028
  Effect.mapError((issue) =>
6147
- new DecodeError({ expectedType: "SuiAddress", issue: issue.message })
6029
+ new DecodeError({ expectedType: "SuiAddress", kind: "shape", issue: issue.message })
6148
6030
  )
6149
6031
  )
6150
6032
  ),
@@ -6788,6 +6670,15 @@ describe("Settlement: a domain class over the BCS bridge", () => {
6788
6670
  claimed_by: bcs.Address
6789
6671
  })
6790
6672
 
6673
+ // The identifier is the class's stable runtime marker and its JSON-Schema
6674
+ // `$ref` key, so it follows the guide's `"<package>/<Name>"` rule: two
6675
+ // extensions with a `Settlement` class must not collide in one document.
6676
+ test("the class identifier is scoped to the package", () => {
6677
+ expect(JSON.stringify(Schema.toJsonSchemaDocument(Settlement))).toContain(
6678
+ "escrow/Settlement"
6679
+ )
6680
+ })
6681
+
6791
6682
  test("snake_case Move fields decode into the camelCase domain class", async () => {
6792
6683
  const bytes = SettlementBcs.serialize({
6793
6684
  escrow_id: ESCROW_ID,
@@ -7165,6 +7056,41 @@ describe("the errors", () => {
7165
7056
  expect(json["outcome"]).toBe("unknown")
7166
7057
  expect(json["outcome"]).toBe(SuiError.outcome(error))
7167
7058
  })
7059
+
7060
+ /**
7061
+ * `Schema.TaggedError` leaves `.message` empty, so an error that defines
7062
+ * neither a getter nor a `message` schema field logs as an empty string and
7063
+ * `SuiError.toJson` emits no `message` key at all. Every error here defines
7064
+ * one, and the guide promises a reader that they will.
7065
+ */
7066
+ test("every error has a real message, and toJson carries it", () => {
7067
+ const errors = [
7068
+ new EscrowNotFound({ escrowId: ESCROW_ID }),
7069
+ new EscrowSettlementUnknown({
7070
+ escrowId: ESCROW_ID,
7071
+ digest: "1".repeat(32) as never,
7072
+ message: "the operator never confirmed"
7073
+ }),
7074
+ new EscrowUnsupportedNetwork({ network: "devnet" })
7075
+ ]
7076
+ for (const error of errors) {
7077
+ expect([error._tag, error.message.length > 0]).toEqual([error._tag, true])
7078
+ expect([error._tag, SuiError.toJson(error)["message"]]).toEqual([
7079
+ error._tag,
7080
+ error.message
7081
+ ])
7082
+ }
7083
+ // The getter reads the error's own fields, so the sentence names the thing
7084
+ // that failed rather than repeating the tag.
7085
+ expect(new EscrowNotFound({ escrowId: ESCROW_ID }).message).toBe(`no escrow ${ESCROW_ID}`)
7086
+ expect(new EscrowUnsupportedNetwork({ network: "devnet" }).message).toBe(
7087
+ "this release bundles no escrow deployment for devnet"
7088
+ )
7089
+ // And a getter stays out of the encoding, so it is not a constructor
7090
+ // argument: only `EscrowSettlementUnknown` takes a `message`.
7091
+ expect(Object.keys(SuiError.toJson(new EscrowUnsupportedNetwork({ network: "devnet" }))))
7092
+ .toEqual(["_tag", "network", "outcome", "message"])
7093
+ })
7168
7094
  })
7169
7095
  ```
7170
7096