@unconfirmed/sui-effect 0.1.0 → 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 (82) hide show
  1. package/AGENTS.md +47 -14
  2. package/CHANGELOG.md +86 -0
  3. package/LLMS.md +2889 -1726
  4. package/README.md +517 -11
  5. package/dist/domain/bcs.d.ts +44 -0
  6. package/dist/domain/bcs.d.ts.map +1 -1
  7. package/dist/domain/bcs.js +64 -0
  8. package/dist/domain/bcs.js.map +1 -1
  9. package/dist/domain/errors.d.ts +164 -26
  10. package/dist/domain/errors.d.ts.map +1 -1
  11. package/dist/domain/errors.js +193 -15
  12. package/dist/domain/errors.js.map +1 -1
  13. package/dist/domain/executed.d.ts +123 -18
  14. package/dist/domain/executed.d.ts.map +1 -1
  15. package/dist/domain/executed.js +196 -5
  16. package/dist/domain/executed.js.map +1 -1
  17. package/dist/domain/journal-entry.d.ts +6 -2
  18. package/dist/domain/journal-entry.d.ts.map +1 -1
  19. package/dist/domain/schemas.d.ts +234 -67
  20. package/dist/domain/schemas.d.ts.map +1 -1
  21. package/dist/domain/schemas.js +82 -4
  22. package/dist/domain/schemas.js.map +1 -1
  23. package/dist/domain/sui-schema.d.ts +3 -2
  24. package/dist/domain/sui-schema.d.ts.map +1 -1
  25. package/dist/domain/sui-schema.js +3 -2
  26. package/dist/domain/sui-schema.js.map +1 -1
  27. package/dist/extension.d.ts +1 -1
  28. package/dist/extension.d.ts.map +1 -1
  29. package/dist/extension.js +1 -1
  30. package/dist/extension.js.map +1 -1
  31. package/dist/index.d.ts +1 -1
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +1 -1
  34. package/dist/index.js.map +1 -1
  35. package/dist/script.d.ts +1 -1
  36. package/dist/script.d.ts.map +1 -1
  37. package/dist/script.js +1 -1
  38. package/dist/script.js.map +1 -1
  39. package/dist/services/Script.d.ts +42 -0
  40. package/dist/services/Script.d.ts.map +1 -1
  41. package/dist/services/Script.js +69 -2
  42. package/dist/services/Script.js.map +1 -1
  43. package/dist/services/Signer.d.ts +32 -7
  44. package/dist/services/Signer.d.ts.map +1 -1
  45. package/dist/services/Signer.js +69 -10
  46. package/dist/services/Signer.js.map +1 -1
  47. package/dist/services/Sui.d.ts +42 -1
  48. package/dist/services/Sui.d.ts.map +1 -1
  49. package/dist/services/Sui.js +18 -4
  50. package/dist/services/Sui.js.map +1 -1
  51. package/dist/services/SuiCore.d.ts +12 -0
  52. package/dist/services/SuiCore.d.ts.map +1 -1
  53. package/dist/services/SuiCore.js +124 -0
  54. package/dist/services/SuiCore.js.map +1 -1
  55. package/dist/services/SuiCoreFake.d.ts +59 -4
  56. package/dist/services/SuiCoreFake.d.ts.map +1 -1
  57. package/dist/services/SuiCoreFake.js +199 -23
  58. package/dist/services/SuiCoreFake.js.map +1 -1
  59. package/dist/services/SuiExtension.d.ts +127 -14
  60. package/dist/services/SuiExtension.d.ts.map +1 -1
  61. package/dist/services/SuiExtension.js +125 -28
  62. package/dist/services/SuiExtension.js.map +1 -1
  63. package/dist/services/SuiGraphQL.d.ts +13 -0
  64. package/dist/services/SuiGraphQL.d.ts.map +1 -1
  65. package/dist/services/SuiGraphQL.js +13 -0
  66. package/dist/services/SuiGraphQL.js.map +1 -1
  67. package/dist/services/Tx.d.ts +630 -12
  68. package/dist/services/Tx.d.ts.map +1 -1
  69. package/dist/services/Tx.js +187 -8
  70. package/dist/services/Tx.js.map +1 -1
  71. package/dist/testing.d.ts +22 -3
  72. package/dist/testing.d.ts.map +1 -1
  73. package/dist/testing.js +39 -3
  74. package/dist/testing.js.map +1 -1
  75. package/dist/tx.d.ts +1 -1
  76. package/dist/tx.d.ts.map +1 -1
  77. package/dist/tx.js +1 -1
  78. package/dist/tx.js.map +1 -1
  79. package/docs/extensions.md +871 -33
  80. package/examples/extension-template/src/Escrow.ts +1 -1
  81. package/examples/extension-template/test/escrow.test.ts +91 -2
  82. package/package.json +3 -2
package/README.md CHANGED
@@ -67,6 +67,341 @@ That is `examples/script-claim.ts`, which a test runs against the in-memory
67
67
  fake. Every failure it can produce is in the generator's inferred error type and
68
68
  has its own exit code, with no handling lines anywhere.
69
69
 
70
+ ## Side by side
71
+
72
+ The same two tasks, once against the canonical `@mysten/sui` 2.30 SDK (a
73
+ gRPC client, the Core API, `Transaction`) and once against sui-effect. Both
74
+ sides are exercised in `test/examples.test.ts`, and this section is asserted
75
+ to be the literal text of the four files it quotes.
76
+
77
+ ### Read
78
+
79
+ `examples/compare-read.sdk.ts`:
80
+
81
+ ```ts
82
+ /**
83
+ * Reads one Escrow object and an optional dynamic field on it, written
84
+ * against the canonical 2.30 SDK: a gRPC client, the Core API, manual BCS.
85
+ *
86
+ * Compare with `examples/compare-read.effect.ts`. Both print identical
87
+ * output for the same id.
88
+ *
89
+ * Run with `SUI_NETWORK=testnet ESCROW_ID=0x… bun examples/compare-read.sdk.ts`.
90
+ */
91
+ import { bcs } from "@mysten/sui/bcs"
92
+ import type { ClientWithCoreApi } from "@mysten/sui/client"
93
+ import { ObjectError } from "@mysten/sui/client"
94
+ import { SuiGrpcClient } from "@mysten/sui/grpc"
95
+
96
+ const PKG = "0x0000000000000000000000000000000000000000000000000000000000000002"
97
+ const ESCROW_TYPE = `${PKG}::escrow::Escrow`
98
+ const EscrowBcs = bcs.struct("Escrow", { id: bcs.Address, amount: bcs.u64() })
99
+ // A note some escrows carry, keyed by the literal index 0. The key is a
100
+ // primitive `u64`, not a struct tag, so nothing here parses it as one.
101
+ const NOTE_NAME = { type: "u64", bcs: bcs.u64().serialize(0).toBytes() }
102
+
103
+ /** Reads the escrow at `id` plus its optional note. */
104
+ export const readEscrow = async (
105
+ client: ClientWithCoreApi,
106
+ id: string
107
+ ): Promise<{ id: string; amount: string; note: string | undefined }> => {
108
+ let object
109
+ try {
110
+ ;({ object } = await client.core.getObject({ objectId: id, include: { content: true } }))
111
+ } catch (error) {
112
+ // `getObject` throws rather than returning a value that says which of
113
+ // "missing", "deleted" or "unreachable" happened; that three-way split
114
+ // is `ObjectError.reason`, read by hand.
115
+ if (error instanceof ObjectError) {
116
+ if (error.reason === "notFound") throw new Error(`escrow ${id} does not exist`)
117
+ if (error.reason === "deleted") throw new Error(`escrow ${id} was deleted`)
118
+ throw new Error(`escrow ${id}: node could not say what happened to it`, { cause: error })
119
+ }
120
+ throw error
121
+ }
122
+ // No type parameters on this tag, so a plain string comparison is honest;
123
+ // a generic type would need to be parsed and compared piece by piece,
124
+ // which is what sui-effect's bridge does for every caller.
125
+ if (object.type !== ESCROW_TYPE) {
126
+ throw new Error(`${id} is a ${object.type}, not ${ESCROW_TYPE}`)
127
+ }
128
+ const { id: escrowId, amount } = EscrowBcs.parse(object.content)
129
+
130
+ let note: string | undefined
131
+ try {
132
+ const { dynamicField } = await client.core.getDynamicField({ parentId: id, name: NOTE_NAME })
133
+ note = bcs.u64().parse(dynamicField.value.bcs)
134
+ } catch (error) {
135
+ // Absence is normal here; only a reason other than "notFound"/"deleted"
136
+ // is a real problem.
137
+ if (!(error instanceof ObjectError) || error.reason === "unknown") throw error
138
+ }
139
+
140
+ return { id: escrowId, amount, note }
141
+ }
142
+
143
+ if (import.meta.main) {
144
+ const id = process.env.ESCROW_ID
145
+ if (!id) throw new Error("ESCROW_ID is required")
146
+ const client = new SuiGrpcClient({
147
+ network: (process.env.SUI_NETWORK ?? "testnet") as "testnet",
148
+ baseUrl: process.env.SUI_RPC_URL ?? "https://fullnode.testnet.sui.io:443"
149
+ })
150
+ const escrow = await readEscrow(client, id)
151
+ console.log(`${escrow.id} holds ${escrow.amount}, note: ${escrow.note ?? "none"}`)
152
+ }
153
+ ```
154
+
155
+ `examples/compare-read.effect.ts`:
156
+
157
+ ```ts
158
+ /**
159
+ * Reads one Escrow object and its optional note through sui-effect.
160
+ *
161
+ * Compare with `examples/compare-read.sdk.ts`, which reads the same two
162
+ * things with the canonical SDK client. Both print identical output.
163
+ *
164
+ * Run with `SUI_NETWORK=testnet ESCROW_ID=0x… bun examples/compare-read.effect.ts`.
165
+ * Every failure this program can produce is in the generator's inferred error
166
+ * type: `ObjectNotFound`, `ObjectDeleted`, `ObjectUnavailable` and
167
+ * `DecodeError` (the last one is what a wrong Move type becomes, because
168
+ * `getObject`'s tag check runs before a byte is parsed), plus `TransportError`
169
+ * from both reads and, from the layer, `ConfigError` and `NetworkMismatch`.
170
+ */
171
+ import { bcs } from "@mysten/sui/bcs"
172
+ import { Config, Console, Effect, Option } from "effect"
173
+ import { ObjectId, Sui, SuiSchema } from "../src/index.ts"
174
+
175
+ const PKG = "0x0000000000000000000000000000000000000000000000000000000000000002"
176
+
177
+ const Escrow = SuiSchema.bcs(
178
+ bcs.struct("Escrow", { id: bcs.Address, amount: bcs.u64() }),
179
+ `${PKG}::escrow::Escrow`
180
+ )
181
+ // The note's value has no struct tag of its own, so its codec carries none;
182
+ // the key is a primitive `u64`, which is why nothing here reaches for
183
+ // `normalizeStructTag` on it.
184
+ const Note = SuiSchema.bcs(bcs.u64())
185
+ const NOTE_NAME = { type: "u64", bcs: bcs.u64().serialize(0).toBytes() }
186
+
187
+ /** The program itself, exported so a test can run it against the fake. */
188
+ export const program = Effect.gen(function*() {
189
+ const sui = yield* Sui
190
+ const id = yield* Config.schema(ObjectId, "ESCROW_ID")
191
+ // No `ObjectError.reason` switch and no manual type comparison: a missing
192
+ // or deleted escrow and a wrong Move type are already three different tags
193
+ // in the type this line returns.
194
+ const escrow = yield* sui.getObject(id, { schema: Escrow })
195
+ const field = yield* sui.getDynamicFieldOption(id, NOTE_NAME)
196
+ const note = yield* Option.match(field, {
197
+ onNone: () => Effect.void,
198
+ onSome: (entry) => SuiSchema.decode(Note, entry.value.bcs)
199
+ })
200
+ yield* Console.log(`${escrow.id} holds ${escrow.content.amount}, note: ${note ?? "none"}`)
201
+ })
202
+
203
+ if (import.meta.main) {
204
+ // Imported here rather than at the top so that a test can import `program`
205
+ // without pulling a platform package into the test process.
206
+ const { BunRuntime } = await import("@effect/platform-bun")
207
+ BunRuntime.runMain(program.pipe(Effect.provide(Sui.layerConfig)))
208
+ }
209
+ ```
210
+
211
+ ### Write
212
+
213
+ `examples/compare-write.sdk.ts`:
214
+
215
+ ```ts
216
+ /**
217
+ * Claims an escrow through a PTB, written against the canonical 2.30 SDK.
218
+ *
219
+ * Compare with `examples/compare-write.effect.ts`, which does the same thing
220
+ * through `Tx.run` and notes what that adds over this file. Both sign with
221
+ * `SUI_PRIVATE_KEY` and print the created receipt's object id.
222
+ *
223
+ * Run with:
224
+ * SUI_NETWORK=testnet SUI_PRIVATE_KEY=suiprivkey1… ESCROW_ID=0x… bun examples/compare-write.sdk.ts
225
+ */
226
+ import { bcs } from "@mysten/sui/bcs"
227
+ import type { ClientWithCoreApi } from "@mysten/sui/client"
228
+ import type { Keypair } from "@mysten/sui/cryptography"
229
+ import { decodeSuiPrivateKey } from "@mysten/sui/cryptography"
230
+ import { SuiGrpcClient } from "@mysten/sui/grpc"
231
+ import { Ed25519Keypair } from "@mysten/sui/keypairs/ed25519"
232
+ import { Secp256k1Keypair } from "@mysten/sui/keypairs/secp256k1"
233
+ import { Secp256r1Keypair } from "@mysten/sui/keypairs/secp256r1"
234
+ import { Transaction } from "@mysten/sui/transactions"
235
+
236
+ const PKG = "0x0000000000000000000000000000000000000000000000000000000000000002"
237
+ const RECEIPT_TYPE = `${PKG}::escrow::Receipt`
238
+ const EscrowBcs = bcs.struct("Escrow", { id: bcs.Address, amount: bcs.u64() })
239
+
240
+ // The scheme flag on a Bech32 key names one of three keypair classes;
241
+ // nothing else decodes it, and a fourth scheme has no keypair class at all.
242
+ const KEYPAIR_FOR: Record<string, undefined | ((secretKey: Uint8Array) => Keypair)> = {
243
+ ED25519: Ed25519Keypair.fromSecretKey,
244
+ Secp256k1: Secp256k1Keypair.fromSecretKey,
245
+ Secp256r1: Secp256r1Keypair.fromSecretKey
246
+ }
247
+
248
+ /**
249
+ * Reads the escrow, claims it, and returns the created receipt's object id.
250
+ *
251
+ * Builds, signs and executes as three explicit steps rather than the
252
+ * one-call `signAndExecuteTransaction`, so this can run against an in-memory
253
+ * client with no live network or signer behind it.
254
+ */
255
+ export const claimEscrow = async (
256
+ client: ClientWithCoreApi,
257
+ escrowId: string,
258
+ keypair: Keypair
259
+ ): Promise<string> => {
260
+ const { object } = await client.core.getObject({ objectId: escrowId, include: { content: true } })
261
+ const { amount } = EscrowBcs.parse(object.content)
262
+
263
+ const tx = new Transaction()
264
+ tx.setSenderIfNotSet(keypair.toSuiAddress())
265
+ tx.moveCall({ target: `${PKG}::escrow::claim`, arguments: [tx.object(escrowId), tx.pure.u64(amount)] })
266
+ const bytes = await tx.build({ client })
267
+ const { signature } = await keypair.signTransaction(bytes)
268
+
269
+ const result = await client.core.executeTransaction({
270
+ transaction: bytes,
271
+ signatures: [signature],
272
+ include: { effects: true, objectTypes: true }
273
+ })
274
+ const digest = (result.Transaction ?? result.FailedTransaction).digest
275
+ await client.core.waitForTransaction({ digest })
276
+
277
+ if (result.$kind === "FailedTransaction") {
278
+ const { error } = result.FailedTransaction.status
279
+ if (error?.$kind === "MoveAbort") {
280
+ const { abortCode, cleverError } = error.MoveAbort
281
+ throw new Error(`claim aborted: code ${abortCode}${cleverError?.constantName ? ` (${cleverError.constantName})` : ""}`)
282
+ }
283
+ throw new Error(`claim failed: ${error?.message}`)
284
+ }
285
+
286
+ // Effects list every changed object by id; only the `objectTypes` join
287
+ // says which one is the receipt. sui-effect's `expectCreated` is this join,
288
+ // plus a check that exactly one match exists.
289
+ const { changedObjects } = result.Transaction.effects
290
+ const created = changedObjects.find(
291
+ (change) => change.idOperation === "Created" && result.Transaction.objectTypes[change.objectId] === RECEIPT_TYPE
292
+ )
293
+ if (created === undefined) throw new Error(`claim applied (${digest}) but created no ${RECEIPT_TYPE}`)
294
+ return created.objectId
295
+ }
296
+
297
+ if (import.meta.main) {
298
+ const escrowId = process.env.ESCROW_ID
299
+ const key = process.env.SUI_PRIVATE_KEY
300
+ if (!escrowId || !key) throw new Error("ESCROW_ID and SUI_PRIVATE_KEY are required")
301
+ const parsed = decodeSuiPrivateKey(key)
302
+ const fromSecretKey = KEYPAIR_FOR[parsed.scheme]
303
+ if (!fromSecretKey) throw new Error(`${parsed.scheme} has no keypair class here (use a remote signer)`)
304
+ const client = new SuiGrpcClient({
305
+ network: (process.env.SUI_NETWORK ?? "testnet") as "testnet",
306
+ baseUrl: process.env.SUI_RPC_URL ?? "https://fullnode.testnet.sui.io:443"
307
+ })
308
+ console.log(await claimEscrow(client, escrowId, fromSecretKey(parsed.secretKey)))
309
+ }
310
+ ```
311
+
312
+ `examples/compare-write.effect.ts`:
313
+
314
+ ```ts
315
+ /**
316
+ * Claims an escrow through `Tx.run`, sui-effect's write path.
317
+ *
318
+ * Compare with `examples/compare-write.sdk.ts`, which does the same thing
319
+ * against the canonical SDK client. Both sign with `SUI_PRIVATE_KEY` and
320
+ * print the created receipt's object id.
321
+ *
322
+ * What `Tx.run` adds over the SDK file: a default expiration bounded to the
323
+ * current epoch, a journal entry written before the first execute so a crash
324
+ * mid-flight leaves a record, resending the identical signed bytes rather
325
+ * than rebuilding on a transient failure, `reconcile` when a submission's
326
+ * outcome is unknown, and a sender lock so two concurrent claims from one
327
+ * address cannot pick the same gas coin. What it costs is the Effect
328
+ * vocabulary below.
329
+ *
330
+ * Run with:
331
+ * SUI_NETWORK=testnet SUI_PRIVATE_KEY=suiprivkey1… ESCROW_ID=0x… bun examples/compare-write.effect.ts
332
+ */
333
+ import { bcs } from "@mysten/sui/bcs"
334
+ import { Config, Console, Effect } from "effect"
335
+ import { ObjectId, SuiSchema } from "../src/index.ts"
336
+ import { Script } from "../src/script.ts"
337
+ import { Tx } from "../src/tx.ts"
338
+
339
+ const PKG = "0x0000000000000000000000000000000000000000000000000000000000000002"
340
+
341
+ const Escrow = SuiSchema.bcs(
342
+ bcs.struct("Escrow", { id: bcs.Address, amount: bcs.u64() }),
343
+ `${PKG}::escrow::Escrow`
344
+ )
345
+
346
+ /**
347
+ * The program itself, exported so a test can run it against the fake. Every
348
+ * failure it can produce is in the generator's inferred error type, with no
349
+ * handling lines anywhere in this file: `ObjectNotFound`, `ObjectDeleted`,
350
+ * `ObjectUnavailable`, `DecodeError`, `TransportError`, `BuildError`,
351
+ * `SimulationFailed`, `SigningError`, `PolicyDenied`, `NotApplied`,
352
+ * `JournalError`, `UnexpectedEffects`, `ExecutionFailed`, `SubmissionUnknown`,
353
+ * plus `ConfigError` and `NetworkMismatch` from the layer.
354
+ */
355
+ export const program = Effect.gen(function*() {
356
+ const { signer, sui } = yield* Script
357
+ const id = yield* Config.schema(ObjectId, "ESCROW_ID")
358
+ const escrow = yield* sui.getObject(id, { schema: Escrow })
359
+ const executed = yield* Tx.run((tx) => {
360
+ tx.moveCall({
361
+ target: `${PKG}::escrow::claim`,
362
+ arguments: [tx.object(id), tx.pure.u64(escrow.content.amount)]
363
+ })
364
+ }, { signer })
365
+ const receipt = yield* executed.expectCreated(`${PKG}::escrow::Receipt`)
366
+ yield* Console.log(receipt.id)
367
+ })
368
+
369
+ if (import.meta.main) {
370
+ await Script.run(program)
371
+ }
372
+ ```
373
+
374
+ ### What differs
375
+
376
+ - **Absence.** The SDK throws `ObjectError` and the reason (`notFound`,
377
+ `deleted`, `unknown`) is read off the caught value by hand; sui-effect's
378
+ `getObject` fails with `ObjectNotFound` / `ObjectDeleted` / `ObjectUnavailable`,
379
+ three separate tags a caller can `catchTag` without an `if` chain.
380
+ - **Wrong type.** The SDK compares `object.type` itself, and only gets away
381
+ with a plain string equality because this particular type has no type
382
+ parameters; a generic Move type would need the same piecewise comparison
383
+ sui-effect's bridge (`typeMatches`) already runs inside every `getObject`.
384
+ - **BCS.** Both sides hand a `BcsType` to a struct; the SDK calls `.parse`
385
+ itself, sui-effect calls it once, inside `getObject`, after the type check
386
+ above has already passed.
387
+ - **Signing.** Both decode `SUI_PRIVATE_KEY` and dispatch on the scheme flag
388
+ to the matching keypair class; sui-effect's version is `Signer.fromConfig`,
389
+ one call, done once in `src/services/Signer.ts` rather than in every script.
390
+ - **Effects.** The SDK inspects `$kind`, `status.error` and joins
391
+ `changedObjects` against `objectTypes` by hand to find the created object;
392
+ sui-effect's `expectCreated(type)` is that join, plus a check that exactly
393
+ one match exists, as a single call on `Executed`.
394
+ - **What sui-effect adds that neither SDK file has:** a default expiration
395
+ bounded to the current epoch, a journal entry written before the first
396
+ execute, resending identical signed bytes instead of rebuilding on a
397
+ transient failure, `reconcile` when a submission's outcome is unknown, and
398
+ a sender lock so two concurrent writes from one address cannot pick the
399
+ same gas coin.
400
+ - **What it costs.** The sui-effect files import `effect` and run inside
401
+ `Effect.gen`; a reader who does not already know Effect's vocabulary
402
+ (`Effect.fn`, `catchTag`, `Option.match`, `yield*`) has that to learn before
403
+ either file is legible.
404
+
70
405
  ## The two tiers
71
406
 
72
407
  `SuiCore` is the mechanical tier: a one-to-one Effect wrap of the SDK's
@@ -81,7 +416,8 @@ need a field or a method the tier above does not expose, and through
81
416
  code reads through: fixed include sets, BCS content decoded through `Schema`
82
417
  with the object's Move type checked first, `Option` where absence is normal,
83
418
  batch reads chunked to 50 and checked for missing or duplicated ids — per-item
84
- `Result` from `getObjects`, first-error-wins from `getObjectsOrFail` pagination
419
+ `Result` from `getObjects`, first-error-wins from `getObjectsStrict` (the same
420
+ function as the deprecated `getObjectsOrFail`) — pagination
85
421
  as `Stream`, one lock per sender so two transactions from one address cannot
86
422
  pick the same gas coin, and the chain's own clock. A Move type with no type
87
423
  arguments matches every instantiation of it, wherever a type is compared, so one
@@ -92,7 +428,8 @@ was built over as `sui.core`, which is why every `Tx.*` function needs only
92
428
  ## The transaction lifecycle
93
429
 
94
430
  `Tx` is the lifecycle as functions — `build`, `sign`, `cosign`, `sponsored`,
95
- `submit`, `reconcile`, `run`, `reconcileAll` — each with a closed error union
431
+ `submit`, `submitVia`, `reconcile`, `recorded`, `run`, `reconcileAll` — each
432
+ with a closed error union
96
433
  and `R = Sui`. `Tx.run` holds the sender lock from build through submit, builds
97
434
  (which always simulates before anything is signed — the SDK's resolver does it
98
435
  when there is anything to resolve, and `Tx.build` runs one explicitly when
@@ -103,7 +440,12 @@ timeout, waits for the execution to be visible to reads before it releases the
103
440
  lock, and if it still does not know what happened, reconciles: `Executed`,
104
441
  `ExecutionFailed`, `NotApplied { evidence }`, or `SubmissionUnknown` carrying
105
442
  the bytes. A `TransportError` never escapes once bytes may have been sent —
106
- from `submit`, and from `reconcile` and `reconcileAll` too. `Signer` is a
443
+ from `submit`, and from `reconcile` and `reconcileAll` too with one exception:
444
+ a gRPC **`INVALID_ARGUMENT`**, which is the node refusing the request outright
445
+ (malformed bytes, or a sponsored transaction carrying one signature). Nothing
446
+ was executed, so `Tx.submit` reports that error as it is rather than
447
+ reconciling, because a reconcile would go on to ask whether that digest is on
448
+ chain — a question about a transaction that was never sent. `Signer` is a
107
449
  value, not a service, so one process can hold two credentials; `SubmitConfig`
108
450
  and `Journal` are `Context.Reference`s with working defaults, so none of this
109
451
  needs wiring, and `@unconfirmed/sui-effect/journal` swaps the memory journal for a durable
@@ -134,6 +476,30 @@ do not build a retry loop that expects `NotApplied { inputConsumed }`. Before
134
476
  any of this, reconcile compares the chain the bytes were built for with
135
477
  `sui.chainId` and refuses to reason across chains.
136
478
 
479
+ **`Tx.run` has a hook between signing and sending.** `onSigned(signed)` runs
480
+ inside the sender lock, after the last signature and before the first
481
+ `executeTransaction`, for the record the journal does not hold — a domain row
482
+ joining the digest to a batch, an outbox, an idempotency key. Failing it fails
483
+ the run with nothing sent; its error is `JournalError`, which is already in
484
+ `Tx.run`'s union and already means "the record could not be written and nothing
485
+ has gone out".
486
+
487
+ **`Tx.submitVia(signed, send)` is the lifecycle when someone else submits.** A
488
+ relay or a sponsorship service that holds the only key allowed to talk to the
489
+ node gets the bytes from you; this keeps everything around that. It journals
490
+ `Signed` before calling `send`, calls it exactly once, turns the reply into an
491
+ `Executed` when it carries one (an SDK `TransactionResult`, a reduced envelope,
492
+ or a bare digest, in which case it asks the chain), reconciles an ambiguous
493
+ `send` failure with the full evidence rules, and journals the terminal answer.
494
+ A `send` error whose instance declares `outcome: "not_applied"` is taken at its
495
+ word and fails straight through.
496
+
497
+ **`Tx.reconcileAll()` returns only what was unresolved**, as a tagged union —
498
+ `{ _tag: "Executed", executed }`, `{ _tag: "ExecutionFailed", error }`,
499
+ `{ _tag: "NotApplied", error }`, `{ _tag: "SubmissionUnknown", error }`. A
500
+ digest that had already settled is not in it; `Tx.recorded(digest)` answers with
501
+ that entry (`Option<JournalEntry>`).
502
+
137
503
  **A sponsored `Tx.run` needs both signatures.** When the gas owner is not the
138
504
  sender, pass `sponsor`: `Tx.run(recipe, { signer, gasOwner, sponsor })`. Without
139
505
  it the run fails with `SigningError` before anything is built, because one
@@ -172,7 +538,12 @@ pattern. A Promise face's **synchronous** members (recipe builders, a package
172
538
  id) are real only once the runtime exists, so either `await client.<name>.$ready()`
173
539
  once or register with `warm`; calling one before that fails with
174
540
  `ExtensionNotReady` rather than returning a Promise the type does not mention
175
- (`Effect` and `Stream` members work cold, as Promises and as async iterables).
541
+ (`Effect` and `Stream` members work cold: a cold call is a real `Promise` that
542
+ is also an `AsyncIterable`, and its rejection is pre-handled so an un-awaited
543
+ one cannot abort the process). A namespace member may be an `interface` — the
544
+ face maps it by type, not by declaration style — while a class instance with
545
+ `Effect`-returning methods is declared `SuiExtension.Leaf<T>` and built with
546
+ `SuiExtension.leaf(value)`.
176
547
  Every registration on one client **shares one `Sui`**, and therefore one
177
548
  sender-lock map, so two extensions never select gas for the same address at
178
549
  once; `$dispose()` releases that shared base only when the last registration on
@@ -192,7 +563,7 @@ no error inheritance to match on.
192
563
  | `ObjectNotFound` / `ObjectDeleted` / `ObjectUnavailable` | `objectId`, `version?` | The three `ObjectError.reason` values |
193
564
  | `TransactionNotFound` | `digest` | No transaction with that digest is known |
194
565
  | `NetworkMismatch` | `expected`, `actual` | The node is on another chain than the layer was built for |
195
- | `DecodeError` | `objectId?`, `expectedType?`, `issue` | BCS content or a schema boundary did not decode |
566
+ | `DecodeError` | `objectId?`, `expectedType?`, `kind`, `issue` | BCS content or a schema boundary did not decode. `kind` is `"type"` (the Move type was not the one expected — nothing was parsed, and the one a caller may answer with a 404), `"bytes"` (the BCS parse failed: a layout mismatch, never safe to swallow) or `"shape"` (a domain schema refused a parsed or JSON value). Branch on `kind`, never on `issue`, whose wording changes between releases |
196
567
  | `SimulationFailed` | `reason`, `message` | Simulation reported an execution failure. No gas charged |
197
568
  | `ExecutionFailed` | `digest`, `reason`, `command?`, `effects` | Applied on chain and failed. Gas charged |
198
569
  | `SubmissionUnknown` | `digest`, `signed?`, `cause` | Bytes may have been sent; the outcome is unknown. Carries them, unless it came from reconciling a bare digest |
@@ -207,14 +578,39 @@ no error inheritance to match on.
207
578
 
208
579
  `ExecutionReason` mirrors the SDK's `ExecutionError` variant for variant, with
209
580
  `MoveAbort.abortCode` as a `bigint` and clever-error constant names decoded.
210
- `SuiError.isRetryable`, `SuiError.outcome`, `SuiError.describe` and
211
- `SuiError.toJson` are the four helpers every repo otherwise hand-rolls;
581
+ **Every one of them has a readable `.message`**: for the classes that carry no
582
+ `message` field of their own it is a getter returning `SuiError.describe(this)`,
583
+ so anything that surfaces `error.message` — a log line, a UI, another library's
584
+ formatter — shows the actionable line instead of an empty string. It is not a
585
+ schema field and does not appear in `SuiError.toJson`'s output.
586
+
587
+ `SuiError.isRetryable`, `SuiError.outcome`, `SuiError.isTaxonomy`,
588
+ `SuiError.describe` and `SuiError.toJson` are the helpers every repo otherwise
589
+ hand-rolls;
212
590
  `outcome` puts every failure on the `"applied" | "not_applied" | "unknown"`
213
591
  axis, and an extension error may declare its own. An error that is neither a tag
214
592
  above nor declares an `outcome` is *unclassified*: `outcome` answers `"unknown"`,
215
593
  because an unrecognised tag is no evidence that nothing happened, and
216
594
  `Script.exitCode` exits 1 rather than 3, because it is no evidence that anything
217
- was sent either. Declare `outcome` on every error your extension defines.
595
+ was sent either. Declare `outcome` on every error your extension defines — as a
596
+ class field is fine, `toJson` reads it off the instance and serializes it either
597
+ way, and the check duck-types the field, so an existing `Error` subclass with an
598
+ `outcome` property lands on the axis without becoming a `Schema.TaggedError`
599
+ first.
600
+
601
+ `SuiError.outcome(error, { phase: "pre-submit" })` changes exactly one answer:
602
+ an unrecognised tag caught **before** anything could have been sent — in a
603
+ `catchAll` around a build, a simulate or a signature — is `"not_applied"`
604
+ rather than `"unknown"`, which is true there by construction. The default stays
605
+ `"post-submit"`. `SuiError.isTaxonomy(error)` answers the question underneath
606
+ it, and `SuiError.describe` accepts a foreign error too, falling back to its tag
607
+ and message rather than returning nothing.
608
+
609
+ **A wrapper error must carry what it wrapped.** `Script.exitCode` honours a
610
+ declared `outcome` before the tag, so a `catchAll` that re-raises its own error
611
+ type around a `Tx.run` must copy `outcome: SuiError.outcome(cause)` and the
612
+ digest, or a charged `ExecutionFailed` becomes exit 1 or, worse, exit 4 — and
613
+ the wrapper retries a transaction that already ran.
218
614
 
219
615
  ## Scripts
220
616
 
@@ -225,7 +621,7 @@ was sent either. Declare `outcome` on every error your extension defines.
225
621
  | `SUI_NETWORK` | yes, no default | `mainnet`, `testnet`, `devnet`, `localnet` or your own |
226
622
  | `SUI_ALLOW_MAINNET` | only for mainnet | `1` or `true`; a script that means mainnet has to say so twice |
227
623
  | `SUI_RPC_URL` | no | The gRPC endpoint; defaulted per known network |
228
- | `SUI_PRIVATE_KEY` | `Script` only | A Bech32 `suiprivkey1…` key, read through `Config.redacted`. A key that does not decode fails with one fixed sentence and no cause: the Bech32 decoder quotes the whole input it rejected, so nothing derived from it is ever printed |
624
+ | `SUI_PRIVATE_KEY` | `Script` only | A Bech32 `suiprivkey1…` key **or a 32-byte hex seed** (64 hex characters, `0x` optional, read as Ed25519), through `Config.redacted`. A key that does not decode fails with one fixed sentence and no cause: the Bech32 decoder quotes the whole input it rejected, so nothing derived from it is ever printed |
229
625
 
230
626
  `Script.layerReadOnly` provides `ScriptReadOnly`, which has no signer at all —
231
627
  a separate service key, so a script written to sign cannot silently build over a
@@ -249,18 +645,128 @@ bytes finish.
249
645
  | 5 | applied on chain: `ExecutionFailed` (gas charged) or `UnexpectedEffects` (it ran; the receipt is missing) |
250
646
  | 130 | interrupted, with nothing outstanding in the journal |
251
647
 
648
+ **`Script.report(exit, { stderr?, journal? })`** is those last two steps on
649
+ their own — the diagnostic lines and the unresolved entries, with the exit code
650
+ as the answer — for a CLI that owns its own argv parsing and process. Assign it
651
+ to `process.exitCode` rather than calling `process.exit`.
652
+
252
653
  A timeout or an interrupt asks the journal: with an unresolved submission in it
253
654
  the exit is 3, not 4 or 130, because an `Effect.timeout` wrapped around a
254
- submission interrupts it from the outside and the bytes may be on the wire.
655
+ submission interrupts it from the outside and the bytes may be on the wire. Each
656
+ unresolved entry is printed three ways: the digest and tag, the raw base64
657
+ bytes, and the whole entry encoded through the `JournalEntry` schema — the same
658
+ JSON a durable journal stores — so a wrapper can hand stderr to a recovery
659
+ process verbatim.
660
+
661
+ **Every `SubmitConfig` field is overridable, and one of them usually should be.**
662
+ `SubmitConfig` is a `Context.Reference` with the spec's defaults, including a
663
+ `maxGasBudget` of **50 SUI** — a ceiling, not a budget, but far above what a
664
+ sponsor policy typically allows. Narrow it once, where the runtime is built:
665
+
666
+ ```ts
667
+ Effect.provideService(program, SubmitConfig, {
668
+ ...SubmitConfig.defaults,
669
+ maxGasBudget: 1_000_000_000n, // 1 SUI
670
+ lockSender: false // see below
671
+ })
672
+ ```
673
+
674
+ `lockSender` is the other one worth a decision. The lock exists because two
675
+ concurrent builds from one address can pick the same gas coin; with
676
+ `tx.setGasPayment([])` there is no coin to pick — the node pays from the address
677
+ balance — and the lock buys nothing. That is the case for every transaction
678
+ built with `Tx.sponsored`, whose empty gas payment `Tx.build` preserves through
679
+ the resolver, and it is the only setting that makes sense across isolates
680
+ (Workers, Durable Objects), where a per-runtime semaphore is not a lock at all.
681
+
682
+ ## Relay and sponsor envelopes
683
+
684
+ `Executed` describes the SDK's own execute include set. A relay, a sponsor or
685
+ any service that submitted on your behalf returns whatever *it* asked the node
686
+ for, which is usually less: `changedObjects` with an `objectId` and an
687
+ `idOperation` and nothing else, no `objectTypes`, no `balanceChanges`, no
688
+ checkpoint, events as JSON with no BCS.
689
+
690
+ `Executed.fromPartial(envelope)` decodes exactly that, and what it was not told
691
+ stays "not told": input and output states are `Unknown` rather than a guessed
692
+ `ObjectWrite`, versions and digests are `null`, and the accessors read `Unknown`
693
+ as "the envelope did not say", so `created()` and `deleted()` still classify
694
+ from the id operation alone. JSON spellings are accepted where the SDK's types
695
+ are not JSON — `bcs` as base64 or a byte array, every `u64` as a number or a
696
+ `bigint` as well as the decimal string the wire uses. Two things it cannot
697
+ invent: the **types** (`created(type)` and `expectCreated(type)` match against
698
+ the `objectTypes` join, so without one they match nothing — use `created()` or
699
+ `createdWhere(predicate)`), and the **gas** (`gasUsedTotal` is `0n` for an
700
+ envelope that reported none, which means "not reported", not "free").
701
+
702
+ `Executed.fromTransactionResult(result)` is the strict constructor, for an SDK
703
+ `TransactionResult` read with the full include set.
704
+
705
+ The whole sequence, for a service that co-signs and submits: `Tx.build` with
706
+ `Tx.sponsored`, `Tx.sign`, hand `toBase64(signed.bytes)` and `signed.signatures[0]`
707
+ to the service, and then either `Executed.fromPartial(reply)` or — through
708
+ `Tx.submitVia`, which keeps the journal — `Tx.reconcile(signed)`. The digest
709
+ does not change when the sponsor adds its signature, so `signed.digest` is what
710
+ to record. `docs/extensions.md` has the section.
711
+
712
+ **`Executed.events` is `ReadonlyArray<Event>`, not `SuiClientTypes.Event[]`**:
713
+ the same fields with branded ids, and `json` present only when whatever produced
714
+ the `Executed` carried one (a relay envelope, never a gRPC execute). Decode an
715
+ event with `SuiSchema.decode(codec, event.bcs)`. Code typed against the SDK's
716
+ `Event[]` does not accept these.
717
+
718
+ **`bigint` throws in `JSON.stringify`.** Gas, balances, versions and
719
+ `checkpoint` are all `bigint`; anything that logs, persists or returns one over
720
+ HTTP needs `.toString()` or a replacer. Decimal strings are what the wire uses
721
+ and what every schema here decodes from.
722
+
723
+ ## Applications, Workers and Durable Objects
724
+
725
+ An application that consumes this library — an SPA, a Worker, a Durable Object —
726
+ wants one `ManagedRuntime` at module scope over
727
+ `Sui.layerNoDepsWith({ chainId })` and `SuiCore.layerFromClient(client)`, and
728
+ should know four things:
729
+
730
+ - **a `ManagedRuntime` memoizes its layer build, failure included.** One flaky
731
+ `getChainIdentifier` at boot and every later use of that runtime fails with the
732
+ same stale `TransportError`. Pass `SuiLayerOptions.retry` (a `Schedule` for
733
+ that one read), dispose and rebuild on a build failure, or use
734
+ `Sui.layerNoDepsPinned(chainId)`, which makes no call at all;
735
+ - **the default `Journal` is process-wide memory.** In a browser tab a refresh
736
+ is a new process and two tabs are two journals and two sender locks; what an
737
+ app actually holds is the `SubmissionUnknown` and its bytes, so persist that
738
+ and reconcile it on the next boot, or provide the durable journal;
739
+ - **the sender lock does not cross isolates**, so correctness there rests on
740
+ address-balance gas (`tx.setGasPayment([])`, `lockSender: false`) or on
741
+ serializing an address through one Durable Object;
742
+ - **there is no `process`** in a Worker: `Script` is a Node entrypoint, and
743
+ configuration comes from `ConfigProvider.fromEnvRecord(env)`.
744
+
745
+ `docs/extensions.md` sections 17 and 18 have the runtime module, the HMR
746
+ dispose, the vitest double and the `KeyValueStore.makeStringOnly` adapter over
747
+ Durable Object storage.
255
748
 
256
749
  ## Testing
257
750
 
258
751
  `@unconfirmed/sui-effect/testing` ships the in-memory `SuiCoreFake`, `layerTest(script)` (the
259
752
  real `Sui` over the fake, so tests exercise the production high tier),
260
- `layerExtensionTest(layer, script)` for an extension's own tests, and `SuiTest`
753
+ `layerExtensionTest(layer, script, { extra })` for an extension's own tests
754
+ (`SuiGraphQL.layerUnavailable` is provided by default; `extra` is for anything
755
+ else the extension's layer requires), and `SuiTest`
261
756
  for driving the fake's state and reading back what it was sent. No test in this
262
757
  repository touches the network, and neither should yours.
263
758
 
759
+ Four things a test on the fake has to know. `layerTest` is the production layer,
760
+ so a script naming `mainnet` or `testnet` must report that network's real chain
761
+ identifier — use `localnet` (the default) in fixtures. `Tx.submit` reconciles
762
+ through `getTransaction`, so a submit test that does not mean "this landed"
763
+ scripts `getTransaction: [FakeOutcome.notFound()]`. A **sponsored** submit needs
764
+ `Tx.cosign` first: the fake refuses an under-signed or wrongly-signed submission
765
+ with a gRPC `INVALID_ARGUMENT`, the way a validator does. And the build's
766
+ simulate is the resolver's — recorded, so `SuiTest.calls("simulateTransaction")`
767
+ sees it, and answered by `FakeScript.buildSimulate` if there is one and by the
768
+ ordered `simulate` script otherwise.
769
+
264
770
  ## Versions
265
771
 
266
772
  | Package | Range | Tested against |
@@ -32,6 +32,50 @@ import type { ObjectId } from "./schemas.ts";
32
32
  * from an object, which is every `getObject(id, { schema })` read.
33
33
  */
34
34
  export declare const bcs: <T extends Input, Input>(bcsType: BcsType<T, Input>, expectedType?: string) => Schema.Codec<T, Uint8Array>;
35
+ /**
36
+ * A BCS layout plus the mapping into a domain value, as one codec.
37
+ *
38
+ * This is the shape every extension writes by hand and writes slightly
39
+ * differently: parse the bytes with a layout, then hand the raw struct to a
40
+ * constructor or a mapping function that may throw (an id that has to be
41
+ * branded, a `bigint` that has to be range-checked, a discriminant that has to
42
+ * become a union). Written out it is `SuiSchema.bcs(...)` piped into a
43
+ * `Schema.decodeTo` with a `transformOrFail` and an `Effect.try`, and the part
44
+ * that gets forgotten is turning the thrown value into a schema issue, so the
45
+ * failure arrives as a defect instead of a `DecodeError`.
46
+ *
47
+ * `map` is called with whatever the layout parsed. Returning a value decodes
48
+ * it; **throwing** fails the decode, and the thrown value's message becomes the
49
+ * `DecodeError.issue` the caller sees, with the expected type already on it.
50
+ * The result is a `Schema.Codec<A, Uint8Array>` like any other: pass it as
51
+ * `sui.getObject(id, { schema })`, and the Move type check still runs first
52
+ * because the annotation {@link bcs} leaves behind survives the composition.
53
+ *
54
+ * Encoding is not supported: a mapping function has no inverse, and inventing
55
+ * one silently is worse than saying so. Encode with the layout itself when you
56
+ * need bytes back.
57
+ *
58
+ * Fails with: `DecodeError` (through the schema), when the bytes do not parse
59
+ * or `map` throws.
60
+ *
61
+ * @since 0.1.1
62
+ *
63
+ * @example
64
+ * ```ts
65
+ * import { ObjectId, SuiSchema } from "@unconfirmed/sui-effect"
66
+ *
67
+ * class Escrow {
68
+ * constructor(readonly id: ObjectId, readonly amount: bigint) {}
69
+ * }
70
+ *
71
+ * const EscrowContent = SuiSchema.decodeWith(
72
+ * EscrowLayout,
73
+ * `${packageId}::escrow::Escrow`,
74
+ * (raw) => new Escrow(ObjectId.normalize(raw.id), BigInt(raw.amount))
75
+ * )
76
+ * ```
77
+ */
78
+ export declare const decodeWith: <T extends Input, Input, A>(bcsType: BcsType<T, Input>, expectedType: string | undefined, map: (parsed: T) => A) => Schema.Codec<A, Uint8Array>;
35
79
  /**
36
80
  * The normalized Move type a codec built by {@link bcs} expects, or `undefined`
37
81
  * for any other codec. `Sui.getObject` compares it with the object's own type
@@ -1 +1 @@
1
- {"version":3,"file":"bcs.d.ts","sourceRoot":"","sources":["../../src/domain/bcs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAE1C,OAAO,EAAE,MAAM,EAAE,MAAM,EAAgD,MAAM,QAAQ,CAAA;AACrF,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAY5C;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,GAAG,GAAI,CAAC,SAAS,KAAK,EAAE,KAAK,EACxC,SAAS,OAAO,CAAC,CAAC,EAAE,KAAK,CAAC,EAC1B,eAAe,MAAM,KACpB,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAgD5B,CAAA;AAuBD;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,GAAI,CAAC,EAAE,CAAC,EAAE,QAAQ,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,KAAG,MAAM,GAAG,SAC/C,CAAA;AAU5B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,eAAO,MAAM,WAAW,GAAI,UAAU,MAAM,EAAE,QAAQ,MAAM,KAAG,OAU9D,CAAA;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,aAAa,GAAI,CAAC,EAC7B,QAAQ,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,EACnC,SAAS,UAAU,EACnB,UAAU;IACR,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAA;IAC5B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;IAC9B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAC7B,KACA,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,WAAW,CA0B9B,CAAA"}
1
+ {"version":3,"file":"bcs.d.ts","sourceRoot":"","sources":["../../src/domain/bcs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAE1C,OAAO,EAAE,MAAM,EAAE,MAAM,EAAgD,MAAM,QAAQ,CAAA;AACrF,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAY5C;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,GAAG,GAAI,CAAC,SAAS,KAAK,EAAE,KAAK,EACxC,SAAS,OAAO,CAAC,CAAC,EAAE,KAAK,CAAC,EAC1B,eAAe,MAAM,KACpB,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAgD5B,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,eAAO,MAAM,UAAU,GAAI,CAAC,SAAS,KAAK,EAAE,KAAK,EAAE,CAAC,EAClD,SAAS,OAAO,CAAC,CAAC,EAAE,KAAK,CAAC,EAC1B,cAAc,MAAM,GAAG,SAAS,EAChC,KAAK,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,KACpB,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAmC5B,CAAA;AAuBD;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,GAAI,CAAC,EAAE,CAAC,EAAE,QAAQ,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,KAAG,MAAM,GAAG,SAC/C,CAAA;AAU5B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,eAAO,MAAM,WAAW,GAAI,UAAU,MAAM,EAAE,QAAQ,MAAM,KAAG,OAU9D,CAAA;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,aAAa,GAAI,CAAC,EAC7B,QAAQ,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,EACnC,SAAS,UAAU,EACnB,UAAU;IACR,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAA;IAC5B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;IAC9B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAC7B,KACA,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,WAAW,CAiC9B,CAAA"}