@unconfirmed/sui-effect 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/AGENTS.md +34 -11
  2. package/CHANGELOG.md +46 -0
  3. package/LLMS.md +953 -301
  4. package/README.md +171 -9
  5. package/dist/domain/bcs.d.ts.map +1 -1
  6. package/dist/domain/bcs.js +7 -0
  7. package/dist/domain/bcs.js.map +1 -1
  8. package/dist/domain/errors.d.ts +101 -5
  9. package/dist/domain/errors.d.ts.map +1 -1
  10. package/dist/domain/errors.js +158 -8
  11. package/dist/domain/errors.js.map +1 -1
  12. package/dist/domain/executed.d.ts +69 -0
  13. package/dist/domain/executed.d.ts.map +1 -1
  14. package/dist/domain/executed.js +196 -5
  15. package/dist/domain/executed.js.map +1 -1
  16. package/dist/domain/schemas.d.ts +27 -1
  17. package/dist/domain/schemas.d.ts.map +1 -1
  18. package/dist/domain/schemas.js +20 -2
  19. package/dist/domain/schemas.js.map +1 -1
  20. package/dist/index.d.ts +1 -1
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +1 -1
  23. package/dist/index.js.map +1 -1
  24. package/dist/script.d.ts +1 -1
  25. package/dist/script.d.ts.map +1 -1
  26. package/dist/script.js +1 -1
  27. package/dist/script.js.map +1 -1
  28. package/dist/services/Script.d.ts +42 -0
  29. package/dist/services/Script.d.ts.map +1 -1
  30. package/dist/services/Script.js +69 -2
  31. package/dist/services/Script.js.map +1 -1
  32. package/dist/services/Signer.d.ts +32 -7
  33. package/dist/services/Signer.d.ts.map +1 -1
  34. package/dist/services/Signer.js +69 -10
  35. package/dist/services/Signer.js.map +1 -1
  36. package/dist/services/Sui.d.ts +42 -1
  37. package/dist/services/Sui.d.ts.map +1 -1
  38. package/dist/services/Sui.js +18 -4
  39. package/dist/services/Sui.js.map +1 -1
  40. package/dist/services/SuiCoreFake.d.ts +47 -4
  41. package/dist/services/SuiCoreFake.d.ts.map +1 -1
  42. package/dist/services/SuiCoreFake.js +193 -22
  43. package/dist/services/SuiCoreFake.js.map +1 -1
  44. package/dist/services/Tx.d.ts +627 -11
  45. package/dist/services/Tx.d.ts.map +1 -1
  46. package/dist/services/Tx.js +187 -8
  47. package/dist/services/Tx.js.map +1 -1
  48. package/dist/testing.d.ts +1 -0
  49. package/dist/testing.d.ts.map +1 -1
  50. package/dist/testing.js +9 -0
  51. package/dist/testing.js.map +1 -1
  52. package/dist/tx.d.ts +1 -1
  53. package/dist/tx.d.ts.map +1 -1
  54. package/dist/tx.js +1 -1
  55. package/dist/tx.js.map +1 -1
  56. package/docs/extensions.md +533 -8
  57. package/examples/extension-template/src/Escrow.ts +1 -1
  58. package/package.json +1 -1
package/AGENTS.md CHANGED
@@ -28,7 +28,9 @@ the work plan. Where this file and the spec disagree, fix this file.
28
28
  await its `Exit` and exit.
29
29
  - No `any`. No `unknown` in an error channel. No `console.log` in `src/`. The
30
30
  one `console.warn` is `SuiCore.mapSdkError`'s duplicate-SDK warning, which
31
- fires at most once per process and is documented where it lives.
31
+ fires at most once per process and is documented where it lives. **Nothing in
32
+ `src/` writes to stderr otherwise** — `test/release-0-1-2.test.ts` captures it
33
+ around a full `Tx.run` on the fake and asserts it is empty.
32
34
  - **A branded id from a shorthand spelling** comes from `SuiAddress.normalize`
33
35
  / `ObjectId.normalize` (decode, then brand); `.make` validates without
34
36
  decoding and is for the padded form only. Neither is for a value that came
@@ -73,7 +75,7 @@ the work plan. Where this file and the spec disagree, fix this file.
73
75
  | Tier | What it is | When to use it |
74
76
  |---|---|---|
75
77
  | `SuiCore` | A 1:1 Effect wrap of `ClientWithCoreApi`. One member per `SuiClientTypes.TransportMethods` key plus `getObject`, `getDynamicObjectField`, `waitForTransaction`, `signAndExecuteTransaction` and `use`. `Include` generics preserved. | Reaching a field or method `Sui` does not expose, and inside extension implementations. |
76
- | `Sui` | The opinionated tier over `SuiCore`: fixed include sets, decoded BCS content, `Option` where absence is normal, chunked and integrity-checked batch reads (`getObjects` per-item `Result`, `getObjectsOrFail` first-error-wins), `Stream` pagination, one sender lock. | Almost all application and extension code. |
78
+ | `Sui` | The opinionated tier over `SuiCore`: fixed include sets, decoded BCS content, `Option` where absence is normal, chunked and integrity-checked batch reads (`getObjects` per-item `Result`, `getObjectsStrict` first-error-wins — `getObjectsOrFail` is the deprecated alias), `Stream` pagination, one sender lock. | Almost all application and extension code. |
77
79
 
78
80
  Reads go through `Sui`; writes go through `Tx`. An extension never calls
79
81
  `SuiCore.executeTransaction` directly. `Sui` exposes the `SuiCore` it was built
@@ -85,7 +87,7 @@ and nothing else.
85
87
  | Name | What it is |
86
88
  |---|---|
87
89
  | `Signer` | A credential as a **value**, never a service: `{ address, scheme, signTransaction, signPersonalMessage }`. One process may hold two. Secret material never reaches the value. |
88
- | `Tx.build/sign/cosign/sponsored/submit/reconcile/run/reconcileAll` | The lifecycle as functions, each with a closed error union, all `R = Sui`. |
90
+ | `Tx.build/sign/cosign/sponsored/submit/submitVia/reconcile/recorded/run/reconcileAll` | The lifecycle as functions, each with a closed error union, all `R = Sui`. `submitVia` is `submit` for a submission somebody else makes; `recorded(digest)` reads one journal entry, which is what `reconcileAll` — unresolved entries only — cannot answer. `Tx.run` takes `onSigned`, the hook between the last signature and the first send. |
89
91
  | `SubmitConfig` | A `Context.Reference` holding expiration policy, the optional `validFor` wall-clock bound, the gas-budget ceiling, `preflight`, the sender lock, the resubmit schedule, attempts, timeout and expiry margin, plus `expiryEvidence`, `reconcileRecheck`, `awaitVisibility`, `visibilityTimeout` and `nonce`. |
90
92
  | `Journal` | A `Context.Reference` with an in-memory default. `@unconfirmed/sui-effect/journal` swaps in a durable one over `KeyValueStore`; `Tx.reconcileAll()` is the explicit startup call. |
91
93
  | `Script` | `{ sui, core, signer, network }` plus `Script.run` and `Script.exitCode`. `ScriptReadOnly` is the signer-less variant, a separate key on purpose. |
@@ -105,7 +107,12 @@ copyable package every block of that guide is quoted from, and
105
107
  `Tx.submit` journals `Signed` before the first execute, re-sends the identical
106
108
  bytes (never a rebuild) on a retryable `TransportError` or a timeout, and
107
109
  reconciles when the retries run out. A `TransportError` never escapes once bytes
108
- may have been sent: it becomes `SubmissionUnknown`, which carries them.
110
+ may have been sent: it becomes `SubmissionUnknown`, which carries them. **The
111
+ one exception is a gRPC `INVALID_ARGUMENT`** (`REFUSED_OUTRIGHT` in `Tx.ts`):
112
+ the node refused the request, nothing executed, and reconciling would ask
113
+ whether a never-sent transaction is on chain — a question a lagging or scripted
114
+ node can answer yes. That error escapes as itself, which is why `SubmitError`
115
+ includes `TransportError`.
109
116
  `JournalError` escapes only from the `Signed` write, before anything is sent;
110
117
  after the network has answered, a failed journal write is logged and the answer
111
118
  stands.
@@ -157,7 +164,7 @@ Every failure is one flat tag; there is no error inheritance.
157
164
  | `ObjectNotFound` / `ObjectDeleted` / `ObjectUnavailable` | The three `ObjectError.reason` values. |
158
165
  | `TransactionNotFound` | No transaction with that digest is known. |
159
166
  | `NetworkMismatch` | The node is on another chain than the layer was built for. |
160
- | `DecodeError` | BCS content or a schema boundary did not decode. |
167
+ | `DecodeError` | BCS content or a schema boundary did not decode. `kind` is `"type"` (tag mismatch, nothing parsed), `"bytes"` (BCS parse or trailing bytes) or `"shape"` (a domain schema). Every producer sets it; consumers branch on it, never on `issue`. |
161
168
  | `SimulationFailed` | Simulation reported an execution failure. No gas charged. |
162
169
  | `ExecutionFailed` | Applied on chain and failed. Gas charged. |
163
170
  | `SubmissionUnknown` | Bytes may have been sent; the outcome is unknown. Carries the signed bytes, unless it came from reconciling a bare digest. |
@@ -170,8 +177,13 @@ Every failure is one flat tag; there is no error inheritance.
170
177
  `"not_applied"` for every other tag in the taxonomy, and `"unknown"` for
171
178
  anything that is neither one of those tags nor declares an `outcome`.
172
179
  `Script.exitCode` exits 1 for that last case rather than 3. An extension error
173
- may declare its own `outcome`, and should. `SuiError.isRetryable`, `SuiError.describe` (one actionable line) and
174
- `SuiError.toJson` round it out — and `toJson` adds `outcome` from the instance
180
+ may declare its own `outcome`, and should. `SuiError.outcome` also takes `{ phase: "pre-submit" }`, which changes only the
181
+ unclassified answer (`"not_applied"` instead of `"unknown"`, true by
182
+ construction before a send). `SuiError.isRetryable`, `SuiError.isTaxonomy`,
183
+ `SuiError.describe` (one actionable line, and total over foreign errors) and
184
+ `SuiError.toJson` round it out. **Every error class without a `message` schema
185
+ field carries `override get message()` returning `describe(this)`**, so
186
+ `.message` is never empty; it is a getter, so it stays out of the encoding — and `toJson` adds `outcome` from the instance
175
187
  when the error declares one, which is almost always a class field rather than a
176
188
  schema field.
177
189
 
@@ -186,8 +198,11 @@ by default and `extra` for any other dependency the layer requires) and `SuiTest
186
198
  an extension's tests need. Call recording is reached through `SuiTest.calls`,
187
199
  not off the fake handle. The fake serves in-memory objects with
188
200
  BCS content, the Clock object `0x6`, and scripted outcomes
189
- (`FakeOutcome.succeed`, `failWith`, `transportError`, `notFound`, `timeoutThen`)
190
- for simulate, execute, `getTransaction` which is also what drives every
201
+ (`FakeOutcome.succeed`, `failWith` which takes the SDK's wire `ExecutionError`
202
+ **or** sui-effect's decoded `ExecutionReason`, encoding the second and throwing
203
+ on anything else — `transportError`, `notFound`, `timeoutThen`)
204
+ for simulate, execute, `getObject` (read-failure injection), `getTransaction` —
205
+ which is also what drives every
191
206
  `waitForTransaction` outcome — `coinMetadata`, and the resolver's budget simulation
192
207
  (`buildSimulate`, which is how a test makes `Tx.build` fail with
193
208
  `SimulationFailed`). It records every call so a test can
@@ -214,6 +229,14 @@ rather than failing a `Simulation` decode.
214
229
  The fake enforces the invariants the lifecycle depends on: a known digest
215
230
  executes idempotently, gas selection excludes object inputs, the coin set
216
231
  evolves (deleted, mutated with `FakeChange.balance`, gas-bumped, created), a
217
- submission with fewer signatures than the bytes name signers is refused, and a
232
+ submission whose signatures do not cover the addresses the bytes name by count
233
+ **and** by the addresses recovered from the signatures — is refused the way a
234
+ validator refuses it (an `RpcError` carrying `INVALID_ARGUMENT`, so `Tx.submit`
235
+ fails fast instead of reconciling into a scripted success), and a
218
236
  version history is served through `tryGetPastObject`, which is what
219
- `SuiCore.getObjectAtVersion` reads.
237
+ `SuiCore.getObjectAtVersion` reads. The resolver's budget simulate is recorded
238
+ as a `simulateTransaction` call (`resolver: true`) and answered by
239
+ `FakeScript.buildSimulate` when there is one and by the ordered `simulate`
240
+ script otherwise, so "building always simulates" is observable on the harness.
241
+ `getBalance` and `listBalances` are keyed by owner and coin type; a `FakeBalance`
242
+ with no `owner` answers for everyone.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,52 @@
3
3
  All notable changes to `@unconfirmed/sui-effect`. The format is one line per
4
4
  change, newest release first.
5
5
 
6
+ ## 0.1.2
7
+
8
+ Two defects from the first three downstream conversions, and the surface they
9
+ exposed. Nothing was removed. Two shapes changed: `Tx.reconcileAll` now returns
10
+ a tagged union, and `Tx.submit`'s error union gained `TransportError` for the
11
+ one case where a node refuses a submission outright.
12
+
13
+ ### Fixed
14
+
15
+ - **`Tx.submit` no longer reconciles a submission the node refused.** A gRPC `INVALID_ARGUMENT` — malformed bytes, or a sponsored transaction carrying only the sender's signature — is the node answering that it did not execute anything, so it escapes as the `TransportError` it is. Reconciling it asked "is this digest on chain?" about a transaction that was never sent, and a lagging node (or a scripted `getTransaction`) could answer yes: an app's sponsored, under-signed submission was reported as a **successful `Executed`**. `SubmitError` therefore includes `TransportError`; every other transport failure still becomes `SubmissionUnknown`.
16
+ - **`SuiCoreFake` refuses an under-signed or wrongly-signed submission the way a validator does**, with an `RpcError` carrying `INVALID_ARGUMENT` rather than a plain `Error` that `mapSdkError` classified as a *retryable* transport failure. It checks the signature count and, when the signatures parse, the addresses they were made by. A sponsored submit against the fake needs `Tx.cosign` (or `Tx.run`'s `sponsor`) first, exactly as it does against a node.
17
+ - **The fake's resolver simulate is recorded**, so `SuiTest.calls("simulateTransaction")` sees the simulate that `Tx.build` promises, and it is answered by `FakeScript.buildSimulate` when there is one and by the ordered `simulate` script otherwise — `simulate: [FakeOutcome.failWith(...)]` now surfaces through `Tx.build` and `Tx.run` as `SimulationFailed` instead of vanishing.
18
+ - **Nothing in `src/` writes to stderr** (the one documented `console.warn` aside). A test captures stderr around a full `Tx.run` on the fake, with `bigint`s in the script, and asserts it is empty: the reported `console.error("DEBUG …", JSON.stringify(outcomes))` lines are in no published 0.1.1 artefact, but a `JSON.stringify` over a scripted `bigint` throws, and the throw surfaced later as an unrelated `FakeUnimplemented`.
19
+ - **Every error class has a real `.message`.** `Schema.TaggedError` leaves it empty, so anything surfacing `error.message` showed nothing; the classes without a `message` schema field now carry `override get message()` returning `SuiError.describe(this)`. It is a getter, so `SuiError.toJson`'s encoding is unchanged.
20
+ - **`SuiError.describe` accepts a foreign error** instead of returning `undefined` from an exhaustive `switch` while its signature promised a `string`. An extension's error, or any `{ _tag }`, gets its tag and message.
21
+ - **`SuiCoreFake.getBalance` is keyed by owner**, not by coin type alone. A `FakeBalance` may carry an `owner`; one without answers for every owner, which is what a pre-0.1.2 script meant.
22
+ - **`FakeOutcome.failWith` accepts sui-effect's decoded `ExecutionReason`** as well as the SDK's wire `ExecutionError`, encoding the first into the second. A fixture in the wrong shape used to decode-fail several calls later, on a different method, after the outcome cursor had already moved; it now throws where the fixture is written, naming both shapes.
23
+
24
+ ### Added
25
+
26
+ - **`DecodeError.kind`**: `"type"` (the Move type was not the expected one — nothing was parsed, and the one a consumer may answer with a 404), `"bytes"` (the BCS parse failed or left trailing bytes — never safe to swallow), `"shape"` (a domain schema refused a parsed or JSON value). Every producer sets it, `SuiError.describe` prints it, and it defaults to `"shape"`, so an extension constructing a `DecodeError` without one still compiles. Branch on `kind`, never on `issue`.
27
+ - **`Tx.reconcileAll` returns a `Schema.TaggedUnion`**: `{ _tag: "Executed", executed }`, `{ _tag: "ExecutionFailed", error }`, `{ _tag: "NotApplied", error }`, `{ _tag: "SubmissionUnknown", error }`. The old bare union — an `Executed` with no discriminator beside three errors that had one — is exported as the deprecated type `ReconciledOutcome`.
28
+ - **`Tx.recorded(digest)`**: the journal entry for one digest, as an `Option`. `reconcileAll` returns **only** the entries that were unresolved, and this is how to ask about one that already settled.
29
+ - **`Tx.submitVia(signed, send)`**: the submission lifecycle when a relay or a sponsorship service does the sending. Journals `Signed` before calling `send`, calls it exactly once, decodes whatever it answers (an SDK `TransactionResult`, a reduced envelope, a bare digest, or nothing — in which case it asks the chain), reconciles an ambiguous failure with the full evidence rules, and journals the terminal answer. A `send` error whose instance declares `outcome: "not_applied"` fails straight through without a reconcile.
30
+ - **`Tx.run`'s `onSigned` hook**: called with the signed bytes after the last signature and before the first `executeTransaction`, inside the sender lock, for the record the journal does not hold. Its failure is a `JournalError` and fails the run with nothing sent.
31
+ - **`Executed.fromPartial(envelope)`**: an `Executed` from a reduced relay or sponsor envelope. `changedObjects` entries need only `objectId` and `idOperation`; what the envelope did not say stays `Unknown` rather than being guessed, and the accessors read `Unknown` as "not said" so `created()` and `deleted()` still classify. JSON spellings are accepted (`bcs` as base64 or a byte array, every `u64` as a number or a `bigint`). It cannot invent the `objectTypes` join, so the type-filtered accessors match nothing without it, and `gasUsedTotal` is `0n` for an envelope that reported no gas.
32
+ - **`Executed.fromTransactionResult(result)`** is public: the strict constructor, for an SDK `TransactionResult` read with the full include set.
33
+ - **`Event.json`**: kept when the source carried one, which in practice means a relay envelope whose events have no BCS. Never populated from a gRPC execute.
34
+ - **`SuiError.outcome(error, { phase: "pre-submit" })`** answers `"not_applied"` rather than `"unknown"` for an unclassified error, which is true by construction before anything is sent. The default is `"post-submit"`, the 0.1.1 behaviour. **`SuiError.isTaxonomy(error)`** answers whether an error is one of the tags this package owns.
35
+ - **`Sui.getObjectsStrict`**: `getObjectsOrFail` under a name that reads correctly in isolation. The old name stays as a deprecated alias of the same function.
36
+ - **`Signer.fromConfig` accepts a 32-byte hex seed** (64 hex characters, `0x` optional, read as Ed25519) as well as a Bech32 `suiprivkey`, so a raw seed from a secret manager no longer needs `fromHex` in application code.
37
+ - **`Signer.fromSdkSigner` validates its argument** and throws a `TypeError` naming the missing member. A double without `getKeyScheme` produced `scheme: undefined` and nothing complained until a validator did. Its JSDoc now states that `toSuiAddress()` and `getKeyScheme()` are read at construction, and that clear-signing inputs are the SDK signer's own concern.
38
+ - **`SuiLayerOptions.retry`**: a `Schedule` for the one `getChainIdentifier` a `Sui` layer makes. A `ManagedRuntime` memoizes its layer build, failure included, so one unlucky read at boot otherwise poisons a browser tab or a Worker isolate for its whole life.
39
+ - **`FakeScript.getObject`** (and `SuiTest.scriptGetObject`): scripted outcomes for a **read**, so a test can inject a transport failure, a miss or a timeout into `getObject`. An absent or exhausted script serves the object map as before.
40
+ - **`Script.report(exit, { stderr?, journal? })`**: the diagnostic lines and the unresolved-entry printing `Script.run` does at the end, returning the exit code, for a CLI that owns its own argv parser and process and cannot hand the entrypoint over.
41
+ - **`Script.run` prints each unresolved journal entry encoded through the `JournalEntry` schema** — the same JSON a durable journal stores, bytes base64 — beside the two human lines it already printed.
42
+
43
+ ### Documentation
44
+
45
+ - The extension guide gains three sections: **"Application consumers"** (a module-level `ManagedRuntime` over `Sui.layerNoDepsWith({ chainId })` and `SuiCore.layerFromClient`, HMR disposal, the memoized-failed-build trap, the in-memory journal in a browser, mapping `outcome` onto UI states with the pre-submit caveat, signing with an external cosigner, and a `layerTest`-backed double for an app's own `runSui`), **"Workers and Durable Objects"** (no `process`, `ConfigProvider.fromEnvRecord(env)`, one runtime per isolate, the sender lock not crossing isolates and address-balance gas, DO alarms versus `Schedule` sleeps, and the whole `KeyValueStore.makeStringOnly` adapter over DO storage), and **"Relay and sponsor envelopes"** (`Executed.fromPartial`, what it cannot invent, and `Tx.submitVia`).
46
+ - A **"Sponsored by an external service"** recipe in the signers section: build with `Tx.sponsored`, sign, hand over the base64 bytes and the serialized signature, and reconcile by the digest — which a co-signature does not change.
47
+ - The testing section gains: `getTransaction` is what a submit test's reconcile reads (script `notFound` unless the test means "this landed"), a sponsored submit needs `Tx.cosign` and should assert the signature count on `SuiTest.calls("executeTransaction")`, which script slot answers the build's simulate, that `layerTest` asserts the built-in chain id for `mainnet` and `testnet`, how to inject a read failure, and why an isolated-consumer fixture in the package `tsconfig`'s `include` typechecks your code against the tarball it last installed.
48
+ - Error guidance: tag strings are namespaced by whoever defined them and must be copied from the installed package; `DecodeError.kind` replaces branching on `issue`; a wrapper error must copy the `outcome` and digest of what it wrapped, because `Script.exitCode` honours `outcome` first.
49
+ - Migration-table rows for a hand-rolled idempotent submitter, `sui.core.getTransaction` for a transaction that may have failed, a predecessor `ready()` genesis check mapping to `Sui.layerNoDepsWith({ chainId })`, and a removed standalone read export; plus the `.find(... type?.includes(...))`-then-throw grep pattern that `Executed.expectCreated` replaces, and the rule that a consumer-edit table is regenerated from the published `exports` map rather than from facade call sites.
50
+ - README: the `INVALID_ARGUMENT` rule, `onSigned`, `submitVia`, the `Reconciled` shape and `Tx.recorded`, `DecodeError.kind`, the error `.message` getter, `outcome`'s phase, relay envelopes and `Executed.events`' exact type, `bigint` at the JSON boundary, the `SubmitConfig` override idiom with `maxGasBudget`'s 50 SUI default and when `lockSender: false` is correct, and a section on applications, Workers and Durable Objects.
51
+
6
52
  ## 0.1.1
7
53
 
8
54
  Seventeen fixes from the first downstream conversion. Nothing in the public API