@unconfirmed/sui-effect 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +16 -6
- package/CHANGELOG.md +40 -0
- package/LLMS.md +1609 -1098
- package/README.md +347 -3
- package/dist/domain/bcs.d.ts +44 -0
- package/dist/domain/bcs.d.ts.map +1 -1
- package/dist/domain/bcs.js +57 -0
- package/dist/domain/bcs.js.map +1 -1
- package/dist/domain/errors.d.ts +63 -21
- package/dist/domain/errors.d.ts.map +1 -1
- package/dist/domain/errors.js +35 -7
- package/dist/domain/errors.js.map +1 -1
- package/dist/domain/executed.d.ts +54 -18
- package/dist/domain/executed.d.ts.map +1 -1
- package/dist/domain/journal-entry.d.ts +6 -2
- package/dist/domain/journal-entry.d.ts.map +1 -1
- package/dist/domain/schemas.d.ts +207 -66
- package/dist/domain/schemas.d.ts.map +1 -1
- package/dist/domain/schemas.js +62 -2
- package/dist/domain/schemas.js.map +1 -1
- package/dist/domain/sui-schema.d.ts +3 -2
- package/dist/domain/sui-schema.d.ts.map +1 -1
- package/dist/domain/sui-schema.js +3 -2
- package/dist/domain/sui-schema.js.map +1 -1
- package/dist/extension.d.ts +1 -1
- package/dist/extension.d.ts.map +1 -1
- package/dist/extension.js +1 -1
- package/dist/extension.js.map +1 -1
- package/dist/services/SuiCore.d.ts +12 -0
- package/dist/services/SuiCore.d.ts.map +1 -1
- package/dist/services/SuiCore.js +124 -0
- package/dist/services/SuiCore.js.map +1 -1
- package/dist/services/SuiCoreFake.d.ts +12 -0
- package/dist/services/SuiCoreFake.d.ts.map +1 -1
- package/dist/services/SuiCoreFake.js +6 -1
- package/dist/services/SuiCoreFake.js.map +1 -1
- package/dist/services/SuiExtension.d.ts +127 -14
- package/dist/services/SuiExtension.d.ts.map +1 -1
- package/dist/services/SuiExtension.js +125 -28
- package/dist/services/SuiExtension.js.map +1 -1
- package/dist/services/SuiGraphQL.d.ts +13 -0
- package/dist/services/SuiGraphQL.d.ts.map +1 -1
- package/dist/services/SuiGraphQL.js +13 -0
- package/dist/services/SuiGraphQL.js.map +1 -1
- package/dist/services/Tx.d.ts +3 -1
- package/dist/services/Tx.d.ts.map +1 -1
- package/dist/testing.d.ts +21 -3
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +30 -3
- package/dist/testing.js.map +1 -1
- package/docs/extensions.md +338 -25
- package/examples/extension-template/test/escrow.test.ts +91 -2
- package/package.json +3 -2
package/LLMS.md
CHANGED
|
@@ -105,23 +105,6 @@ A transaction built into bytes and ready to sign, with the expiration the
|
|
|
105
105
|
builder settled on recorded so `Tx.reconcile` can decide, later and without
|
|
106
106
|
the builder, whether the transaction can still land.
|
|
107
107
|
|
|
108
|
-
### `chainOf` (const)
|
|
109
|
-
|
|
110
|
-
```ts
|
|
111
|
-
declare const chainOf: (expiration: TransactionExpiration | undefined) => string | undefined
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
The chain identifier an expiration names, or `undefined` for the variants
|
|
115
|
-
that name none (`None`, `Epoch`).
|
|
116
|
-
|
|
117
|
-
This is the first half of the chain-identity guard `Tx.reconcile` applies
|
|
118
|
-
before it asks a node anything: bytes built for one chain must not be
|
|
119
|
-
declared expired by another chain's epoch. The second half is
|
|
120
|
-
`SignedTransaction.chain`, which `Tx.build` records for the variants that
|
|
121
|
-
carry no chain of their own.
|
|
122
|
-
|
|
123
|
-
**Never fails.**
|
|
124
|
-
|
|
125
108
|
### `ChangedObject` (const)
|
|
126
109
|
|
|
127
110
|
```ts
|
|
@@ -254,18 +237,6 @@ export declare class DecodeError extends DecodeError_base {
|
|
|
254
237
|
|
|
255
238
|
BCS content or a schema boundary did not decode.
|
|
256
239
|
|
|
257
|
-
### `defaultGrpcUrl` (const)
|
|
258
|
-
|
|
259
|
-
```ts
|
|
260
|
-
declare const defaultGrpcUrl: (network: string) => string | undefined
|
|
261
|
-
```
|
|
262
|
-
|
|
263
|
-
The gRPC endpoint sui-effect uses when `SUI_RPC_URL` is not set. The SDK
|
|
264
|
-
ships no such table; these are the URLs its own documentation uses.
|
|
265
|
-
Returns `undefined` for any other network.
|
|
266
|
-
|
|
267
|
-
**Never fails.**
|
|
268
|
-
|
|
269
240
|
### `Digest` (const)
|
|
270
241
|
|
|
271
242
|
```ts
|
|
@@ -842,42 +813,6 @@ declare const KnownNetwork: Schema
|
|
|
842
813
|
|
|
843
814
|
The four networks with a built-in default gRPC endpoint.
|
|
844
815
|
|
|
845
|
-
### `maxEpochOf` (const)
|
|
846
|
-
|
|
847
|
-
```ts
|
|
848
|
-
declare const maxEpochOf: (expiration: TransactionExpiration | undefined) => bigint | undefined
|
|
849
|
-
```
|
|
850
|
-
|
|
851
|
-
The last epoch in which a transaction can still be applied, or `undefined`
|
|
852
|
-
when its expiration sets no such bound (`None`, or a `ValidDuring` with no
|
|
853
|
-
`maxEpoch`).
|
|
854
|
-
|
|
855
|
-
An `Epoch` expiration is that epoch: the SDK's `Epoch` variant means "valid
|
|
856
|
-
until the end of this epoch", so it is its own upper bound.
|
|
857
|
-
|
|
858
|
-
This is the bound that matters in practice. Epochs are what the default
|
|
859
|
-
expiration carries and what the validator rule is written in, and unlike a
|
|
860
|
-
wall clock an epoch is a consensus fact, so `Tx.reconcile` needs no skew
|
|
861
|
-
margin to reason about it.
|
|
862
|
-
|
|
863
|
-
**Never fails.**
|
|
864
|
-
|
|
865
|
-
### `maxTimestampMsOf` (const)
|
|
866
|
-
|
|
867
|
-
```ts
|
|
868
|
-
declare const maxTimestampMsOf: (expiration: TransactionExpiration | undefined) => bigint | undefined
|
|
869
|
-
```
|
|
870
|
-
|
|
871
|
-
The wall-clock bound after which a transaction can no longer be applied, in
|
|
872
|
-
milliseconds, or `undefined` when its expiration sets no such bound — which
|
|
873
|
-
is `None`, `Epoch`, and the default `ValidDuring`, whose `maxTimestamp` is
|
|
874
|
-
`null` unless `SubmitConfig.validFor` asks for one.
|
|
875
|
-
|
|
876
|
-
`Tx.reconcile` compares it against `chainTime` with
|
|
877
|
-
`SubmitConfig.expiryMargin` for skew, which the epoch bound needs none of.
|
|
878
|
-
|
|
879
|
-
**Never fails.**
|
|
880
|
-
|
|
881
816
|
### `Mist` (const)
|
|
882
817
|
|
|
883
818
|
```ts
|
|
@@ -1018,8 +953,7 @@ declare const ObjectId: Schema
|
|
|
1018
953
|
// decodes to: ObjectId
|
|
1019
954
|
```
|
|
1020
955
|
|
|
1021
|
-
|
|
1022
|
-
separate so an address cannot be passed where an object id is expected.
|
|
956
|
+
The schema, plus `normalizeObjectIdValue` as `ObjectId.normalize`.
|
|
1023
957
|
|
|
1024
958
|
### `ObjectLookupError` (type)
|
|
1025
959
|
|
|
@@ -1079,17 +1013,6 @@ declare const ObjectRef: Schema
|
|
|
1079
1013
|
Everything the transaction builder needs to consume an object again, plus the
|
|
1080
1014
|
type and owner so a caller can tell a shared object from an owned one.
|
|
1081
1015
|
|
|
1082
|
-
### `objectRefOf` (const)
|
|
1083
|
-
|
|
1084
|
-
```ts
|
|
1085
|
-
declare const objectRefOf: (ref: ChangedRef) => ObjectRef | undefined
|
|
1086
|
-
```
|
|
1087
|
-
|
|
1088
|
-
The full builder reference of a changed object, when the effects carried
|
|
1089
|
-
every field the builder needs.
|
|
1090
|
-
|
|
1091
|
-
**Never fails.**
|
|
1092
|
-
|
|
1093
1016
|
### `ObjectType` (const)
|
|
1094
1017
|
|
|
1095
1018
|
```ts
|
|
@@ -1186,34 +1109,6 @@ export interface SdkObjectRef {
|
|
|
1186
1109
|
|
|
1187
1110
|
The SDK's own object reference shape, as `Transaction#objectRef` takes it.
|
|
1188
1111
|
|
|
1189
|
-
### `sdkRefOf` (const)
|
|
1190
|
-
|
|
1191
|
-
```ts
|
|
1192
|
-
declare const sdkRefOf: (ref: ChangedRef | ObjectRef) => SdkObjectRef | undefined
|
|
1193
|
-
```
|
|
1194
|
-
|
|
1195
|
-
The shape `Transaction#objectRef` and `Inputs.ObjectRef` want: `objectId`
|
|
1196
|
-
rather than `id`, and a decimal **string** version rather than a `bigint`.
|
|
1197
|
-
|
|
1198
|
-
sui-effect's own `ObjectRef` carries the branded `id` and a `bigint`
|
|
1199
|
-
`version`, because that is what a domain model wants and what a `Version`
|
|
1200
|
-
check can be run on; the SDK builder wants neither. This is the one
|
|
1201
|
-
conversion, so nobody writes `{ objectId: ref.id, version: String(ref.version) }`
|
|
1202
|
-
by hand and gets the field name wrong.
|
|
1203
|
-
|
|
1204
|
-
**Only for an address-owned (or immutable) object.** `tx.objectRef` pins a
|
|
1205
|
-
version, which is exactly right for an owned input and exactly wrong for a
|
|
1206
|
-
**shared** object: a shared object is passed with `tx.sharedObjectRef({
|
|
1207
|
-
objectId, initialSharedVersion, mutable })` (the initial shared version lives
|
|
1208
|
-
on `ref.owner.Shared.initialSharedVersion`), and a **receiving** object with
|
|
1209
|
-
`tx.receivingRef(...)`, which takes the same three fields this returns.
|
|
1210
|
-
Passing a shared object by `objectRef` produces bytes a validator rejects.
|
|
1211
|
-
|
|
1212
|
-
`undefined` when the effects did not carry a version or a digest — a deleted
|
|
1213
|
-
object has no output version, and nothing can be consumed without both.
|
|
1214
|
-
|
|
1215
|
-
**Never fails.**
|
|
1216
|
-
|
|
1217
1112
|
### `Signature` (const)
|
|
1218
1113
|
|
|
1219
1114
|
```ts
|
|
@@ -1675,8 +1570,7 @@ declare const SuiAddress: Schema
|
|
|
1675
1570
|
// decodes to: SuiAddress
|
|
1676
1571
|
```
|
|
1677
1572
|
|
|
1678
|
-
|
|
1679
|
-
decode. Rejects anything `isValidSuiAddress` rejects with a `SchemaError`.
|
|
1573
|
+
The schema, plus `normalizeSuiAddressValue` as `SuiAddress.normalize`.
|
|
1680
1574
|
|
|
1681
1575
|
### `SuiCore` (class)
|
|
1682
1576
|
|
|
@@ -1892,6 +1786,19 @@ export declare class SuiGraphQL extends SuiGraphQL_base {
|
|
|
1892
1786
|
The SDK's GraphQL client as a service, so an extension can require it
|
|
1893
1787
|
without constructing one.
|
|
1894
1788
|
|
|
1789
|
+
**The service value *is* the client.** `yield* SuiGraphQL` hands back the
|
|
1790
|
+
`SuiGraphQLClient` that was passed to `SuiGraphQL.layer(client)` — not a
|
|
1791
|
+
wrapper with `client` on it, not an interface of methods this package
|
|
1792
|
+
defines. `SuiGraphQL["Service"]` is therefore `SuiGraphQLClient`, which is
|
|
1793
|
+
the type to write when a function takes the service as a parameter:
|
|
1794
|
+
|
|
1795
|
+
```ts
|
|
1796
|
+
const names = (graphql: SuiGraphQL["Service"]) => graphql.query({ query, variables })
|
|
1797
|
+
```
|
|
1798
|
+
|
|
1799
|
+
The requirement is spelled `SuiGraphQL` in an `Effect`'s `R`, as with every
|
|
1800
|
+
other tag here.
|
|
1801
|
+
|
|
1895
1802
|
### `SuiGrpcLayerOptions` (interface)
|
|
1896
1803
|
|
|
1897
1804
|
```ts
|
|
@@ -1997,6 +1904,36 @@ the error so an operator knows which object did not decode.
|
|
|
1997
1904
|
|
|
1998
1905
|
**Fails with: `DecodeError`.**
|
|
1999
1906
|
|
|
1907
|
+
#### `decodeWith` (const)
|
|
1908
|
+
|
|
1909
|
+
```ts
|
|
1910
|
+
declare const decodeWith: <T extends Input, Input, A>(bcsType: BcsType<T, Input>, expectedType: string | undefined, map: (parsed: T) => A) => Schema.Codec<A, Uint8Array>
|
|
1911
|
+
```
|
|
1912
|
+
|
|
1913
|
+
A BCS layout plus the mapping into a domain value, as one codec.
|
|
1914
|
+
|
|
1915
|
+
This is the shape every extension writes by hand and writes slightly
|
|
1916
|
+
differently: parse the bytes with a layout, then hand the raw struct to a
|
|
1917
|
+
constructor or a mapping function that may throw (an id that has to be
|
|
1918
|
+
branded, a `bigint` that has to be range-checked, a discriminant that has to
|
|
1919
|
+
become a union). Written out it is `SuiSchema.bcs(...)` piped into a
|
|
1920
|
+
`Schema.decodeTo` with a `transformOrFail` and an `Effect.try`, and the part
|
|
1921
|
+
that gets forgotten is turning the thrown value into a schema issue, so the
|
|
1922
|
+
failure arrives as a defect instead of a `DecodeError`.
|
|
1923
|
+
|
|
1924
|
+
`map` is called with whatever the layout parsed. Returning a value decodes
|
|
1925
|
+
it; **throwing** fails the decode, and the thrown value's message becomes the
|
|
1926
|
+
`DecodeError.issue` the caller sees, with the expected type already on it.
|
|
1927
|
+
The result is a `Schema.Codec<A, Uint8Array>` like any other: pass it as
|
|
1928
|
+
`sui.getObject(id, { schema })`, and the Move type check still runs first
|
|
1929
|
+
because the annotation `bcs` leaves behind survives the composition.
|
|
1930
|
+
|
|
1931
|
+
Encoding is not supported: a mapping function has no inverse, and inventing
|
|
1932
|
+
one silently is worse than saying so. Encode with the layout itself when you
|
|
1933
|
+
need bytes back.
|
|
1934
|
+
|
|
1935
|
+
**Fails with: `DecodeError` (through the schema), when the bytes do not parse or `map` throws.**
|
|
1936
|
+
|
|
2000
1937
|
#### `matchesType` (const)
|
|
2001
1938
|
|
|
2002
1939
|
```ts
|
|
@@ -2533,203 +2470,119 @@ What a versioned object read found: the object as it was at that exact
|
|
|
2533
2470
|
version, with the digest of the transaction that produced it, or nothing at
|
|
2534
2471
|
all with a reason a log can print.
|
|
2535
2472
|
|
|
2536
|
-
|
|
2473
|
+
### `chainOf` (const)
|
|
2537
2474
|
|
|
2475
|
+
```ts
|
|
2476
|
+
declare const chainOf: (expiration: TransactionExpiration | undefined) => string | undefined
|
|
2477
|
+
```
|
|
2538
2478
|
|
|
2539
|
-
|
|
2479
|
+
The chain identifier an expiration names, or `undefined` for the variants
|
|
2480
|
+
that name none (`None`, `Epoch`).
|
|
2540
2481
|
|
|
2541
|
-
|
|
2482
|
+
This is the first half of the chain-identity guard `Tx.reconcile` applies
|
|
2483
|
+
before it asks a node anything: bytes built for one chain must not be
|
|
2484
|
+
declared expired by another chain's epoch. The second half is
|
|
2485
|
+
`SignedTransaction.chain`, which `Tx.build` records for the variants that
|
|
2486
|
+
carry no chain of their own.
|
|
2487
|
+
|
|
2488
|
+
**Never fails.**
|
|
2489
|
+
|
|
2490
|
+
### `defaultGrpcUrl` (const)
|
|
2542
2491
|
|
|
2543
2492
|
```ts
|
|
2544
|
-
declare const
|
|
2545
|
-
readonly sender: SuiAddress;
|
|
2546
|
-
readonly gasOwner?: SuiAddress;
|
|
2547
|
-
}) => Effect.Effect<{
|
|
2548
|
-
readonly digest: Digest;
|
|
2549
|
-
readonly sender: SuiAddress;
|
|
2550
|
-
readonly bytes: Uint8Array<ArrayBufferLike>;
|
|
2551
|
-
readonly chain?: string | undefined;
|
|
2552
|
-
readonly expiration?: {
|
|
2553
|
-
readonly $kind: "None";
|
|
2554
|
-
readonly None: true;
|
|
2555
|
-
} | {
|
|
2556
|
-
readonly $kind: "Epoch";
|
|
2557
|
-
readonly Epoch: bigint;
|
|
2558
|
-
} | {
|
|
2559
|
-
readonly $kind: "ValidDuring";
|
|
2560
|
-
readonly ValidDuring: {
|
|
2561
|
-
readonly minEpoch: bigint | null;
|
|
2562
|
-
readonly maxEpoch: bigint | null;
|
|
2563
|
-
readonly minTimestamp: bigint | null;
|
|
2564
|
-
readonly maxTimestamp: bigint | null;
|
|
2565
|
-
readonly chain: string;
|
|
2566
|
-
readonly nonce: number;
|
|
2567
|
-
};
|
|
2568
|
-
} | {
|
|
2569
|
-
readonly $kind: "Validity";
|
|
2570
|
-
readonly Validity: {
|
|
2571
|
-
readonly allowedProposers: {
|
|
2572
|
-
readonly epoch: bigint;
|
|
2573
|
-
readonly proposers: readonly number[];
|
|
2574
|
-
} | null;
|
|
2575
|
-
readonly minEpoch: bigint | null;
|
|
2576
|
-
readonly maxEpoch: bigint | null;
|
|
2577
|
-
readonly minTimestamp: bigint | null;
|
|
2578
|
-
readonly maxTimestamp: bigint | null;
|
|
2579
|
-
readonly chain: string;
|
|
2580
|
-
readonly nonce: number;
|
|
2581
|
-
};
|
|
2582
|
-
} | undefined;
|
|
2583
|
-
readonly gasOwner?: SuiAddress | undefined;
|
|
2584
|
-
}, TransportError | SimulationFailed | BuildError, Sui>
|
|
2493
|
+
declare const defaultGrpcUrl: (network: string) => string | undefined
|
|
2585
2494
|
```
|
|
2586
2495
|
|
|
2587
|
-
|
|
2496
|
+
The gRPC endpoint sui-effect uses when `SUI_RPC_URL` is not set. The SDK
|
|
2497
|
+
ships no such table; these are the URLs its own documentation uses.
|
|
2498
|
+
Returns `undefined` for any other network.
|
|
2588
2499
|
|
|
2589
|
-
**
|
|
2590
|
-
with checks enabled to choose the gas budget, and an execution failure there
|
|
2591
|
-
arrives as `SimulationFailed`. That costs nothing extra — but the resolver
|
|
2592
|
-
returns early for a transaction that was **already fully resolved** (every
|
|
2593
|
-
input resolved, gas price, budget and payment set), and then nothing
|
|
2594
|
-
simulates at all. `Tx.build` detects that case and runs one explicit
|
|
2595
|
-
`simulateTransaction` with checks enabled, so simulate-before-submit holds
|
|
2596
|
-
for every transaction: it costs nothing extra when the SDK had to resolve,
|
|
2597
|
-
and one call otherwise.
|
|
2500
|
+
**Never fails.**
|
|
2598
2501
|
|
|
2599
|
-
|
|
2600
|
-
client whose Core calls carry the Effect's `AbortSignal`.
|
|
2502
|
+
### `maxEpochOf` (const)
|
|
2601
2503
|
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
and carries a `u32` nonce from `SubmitConfig.nonce`. There is **no
|
|
2606
|
-
wall-clock bound** unless `SubmitConfig.validFor` asks for one: no Sui
|
|
2607
|
-
network accepts a timestamp expiration yet, and a node refuses any
|
|
2608
|
-
transaction carrying one. The expiration that ends up in the bytes is
|
|
2609
|
-
recorded on the result, because it is what `Tx.reconcile` needs later to
|
|
2610
|
-
prove a transaction can no longer land.
|
|
2504
|
+
```ts
|
|
2505
|
+
declare const maxEpochOf: (expiration: TransactionExpiration | undefined) => bigint | undefined
|
|
2506
|
+
```
|
|
2611
2507
|
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2508
|
+
The last epoch in which a transaction can still be applied, or `undefined`
|
|
2509
|
+
when its expiration sets no such bound (`None`, or a `ValidDuring` with no
|
|
2510
|
+
`maxEpoch`).
|
|
2615
2511
|
|
|
2616
|
-
|
|
2512
|
+
An `Epoch` expiration is that epoch: the SDK's `Epoch` variant means "valid
|
|
2513
|
+
until the end of this epoch", so it is its own upper bound.
|
|
2617
2514
|
|
|
2618
|
-
|
|
2515
|
+
This is the bound that matters in practice. Epochs are what the default
|
|
2516
|
+
expiration carries and what the validator rule is written in, and unlike a
|
|
2517
|
+
wall clock an epoch is a consensus fact, so `Tx.reconcile` needs no skew
|
|
2518
|
+
margin to reason about it.
|
|
2619
2519
|
|
|
2620
|
-
|
|
2520
|
+
**Never fails.**
|
|
2621
2521
|
|
|
2622
|
-
### `
|
|
2522
|
+
### `maxTimestampMsOf` (const)
|
|
2623
2523
|
|
|
2624
|
-
|
|
2524
|
+
```ts
|
|
2525
|
+
declare const maxTimestampMsOf: (expiration: TransactionExpiration | undefined) => bigint | undefined
|
|
2526
|
+
```
|
|
2625
2527
|
|
|
2626
|
-
|
|
2528
|
+
The wall-clock bound after which a transaction can no longer be applied, in
|
|
2529
|
+
milliseconds, or `undefined` when its expiration sets no such bound — which
|
|
2530
|
+
is `None`, `Epoch`, and the default `ValidDuring`, whose `maxTimestamp` is
|
|
2531
|
+
`null` unless `SubmitConfig.validFor` asks for one.
|
|
2532
|
+
|
|
2533
|
+
`Tx.reconcile` compares it against `chainTime` with
|
|
2534
|
+
`SubmitConfig.expiryMargin` for skew, which the epoch bound needs none of.
|
|
2535
|
+
|
|
2536
|
+
**Never fails.**
|
|
2537
|
+
|
|
2538
|
+
### `objectRefOf` (const)
|
|
2627
2539
|
|
|
2628
2540
|
```ts
|
|
2629
|
-
declare const
|
|
2630
|
-
readonly digest: Digest;
|
|
2631
|
-
readonly sender: SuiAddress;
|
|
2632
|
-
readonly signatures: readonly Signature[];
|
|
2633
|
-
readonly bytes: Uint8Array<ArrayBufferLike>;
|
|
2634
|
-
readonly chain?: string | undefined;
|
|
2635
|
-
readonly expiration?: {
|
|
2636
|
-
readonly $kind: "None";
|
|
2637
|
-
readonly None: true;
|
|
2638
|
-
} | {
|
|
2639
|
-
readonly $kind: "Epoch";
|
|
2640
|
-
readonly Epoch: bigint;
|
|
2641
|
-
} | {
|
|
2642
|
-
readonly $kind: "ValidDuring";
|
|
2643
|
-
readonly ValidDuring: {
|
|
2644
|
-
readonly minEpoch: bigint | null;
|
|
2645
|
-
readonly maxEpoch: bigint | null;
|
|
2646
|
-
readonly minTimestamp: bigint | null;
|
|
2647
|
-
readonly maxTimestamp: bigint | null;
|
|
2648
|
-
readonly chain: string;
|
|
2649
|
-
readonly nonce: number;
|
|
2650
|
-
};
|
|
2651
|
-
} | {
|
|
2652
|
-
readonly $kind: "Validity";
|
|
2653
|
-
readonly Validity: {
|
|
2654
|
-
readonly allowedProposers: {
|
|
2655
|
-
readonly epoch: bigint;
|
|
2656
|
-
readonly proposers: readonly number[];
|
|
2657
|
-
} | null;
|
|
2658
|
-
readonly minEpoch: bigint | null;
|
|
2659
|
-
readonly maxEpoch: bigint | null;
|
|
2660
|
-
readonly minTimestamp: bigint | null;
|
|
2661
|
-
readonly maxTimestamp: bigint | null;
|
|
2662
|
-
readonly chain: string;
|
|
2663
|
-
readonly nonce: number;
|
|
2664
|
-
};
|
|
2665
|
-
} | undefined;
|
|
2666
|
-
}, signer: Signer) => Effect.Effect<{
|
|
2667
|
-
readonly digest: Digest;
|
|
2668
|
-
readonly sender: SuiAddress;
|
|
2669
|
-
readonly signatures: readonly Signature[];
|
|
2670
|
-
readonly bytes: Uint8Array<ArrayBufferLike>;
|
|
2671
|
-
readonly chain?: string | undefined;
|
|
2672
|
-
readonly expiration?: {
|
|
2673
|
-
readonly $kind: "None";
|
|
2674
|
-
readonly None: true;
|
|
2675
|
-
} | {
|
|
2676
|
-
readonly $kind: "Epoch";
|
|
2677
|
-
readonly Epoch: bigint;
|
|
2678
|
-
} | {
|
|
2679
|
-
readonly $kind: "ValidDuring";
|
|
2680
|
-
readonly ValidDuring: {
|
|
2681
|
-
readonly minEpoch: bigint | null;
|
|
2682
|
-
readonly maxEpoch: bigint | null;
|
|
2683
|
-
readonly minTimestamp: bigint | null;
|
|
2684
|
-
readonly maxTimestamp: bigint | null;
|
|
2685
|
-
readonly chain: string;
|
|
2686
|
-
readonly nonce: number;
|
|
2687
|
-
};
|
|
2688
|
-
} | {
|
|
2689
|
-
readonly $kind: "Validity";
|
|
2690
|
-
readonly Validity: {
|
|
2691
|
-
readonly allowedProposers: {
|
|
2692
|
-
readonly epoch: bigint;
|
|
2693
|
-
readonly proposers: readonly number[];
|
|
2694
|
-
} | null;
|
|
2695
|
-
readonly minEpoch: bigint | null;
|
|
2696
|
-
readonly maxEpoch: bigint | null;
|
|
2697
|
-
readonly minTimestamp: bigint | null;
|
|
2698
|
-
readonly maxTimestamp: bigint | null;
|
|
2699
|
-
readonly chain: string;
|
|
2700
|
-
readonly nonce: number;
|
|
2701
|
-
};
|
|
2702
|
-
} | undefined;
|
|
2703
|
-
}, SigningError, never>
|
|
2541
|
+
declare const objectRefOf: (ref: ChangedRef) => ObjectRef | undefined
|
|
2704
2542
|
```
|
|
2705
2543
|
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
exactly the same transaction.
|
|
2709
|
-
|
|
2710
|
-
As in `sign`, the co-signer's address must be the sender or the gas
|
|
2711
|
-
owner named in the bytes.
|
|
2544
|
+
The full builder reference of a changed object, when the effects carried
|
|
2545
|
+
every field the builder needs.
|
|
2712
2546
|
|
|
2713
|
-
**
|
|
2547
|
+
**Never fails.**
|
|
2714
2548
|
|
|
2715
|
-
### `
|
|
2549
|
+
### `sdkRefOf` (const)
|
|
2716
2550
|
|
|
2717
2551
|
```ts
|
|
2718
|
-
declare const
|
|
2552
|
+
declare const sdkRefOf: (ref: ChangedRef | ObjectRef) => SdkObjectRef | undefined
|
|
2719
2553
|
```
|
|
2720
2554
|
|
|
2721
|
-
|
|
2722
|
-
|
|
2555
|
+
The shape `Transaction#objectRef` and `Inputs.ObjectRef` want: `objectId`
|
|
2556
|
+
rather than `id`, and a decimal **string** version rather than a `bigint`.
|
|
2723
2557
|
|
|
2724
|
-
|
|
2725
|
-
`
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2558
|
+
sui-effect's own `ObjectRef` carries the branded `id` and a `bigint`
|
|
2559
|
+
`version`, because that is what a domain model wants and what a `Version`
|
|
2560
|
+
check can be run on; the SDK builder wants neither. This is the one
|
|
2561
|
+
conversion, so nobody writes `{ objectId: ref.id, version: String(ref.version) }`
|
|
2562
|
+
by hand and gets the field name wrong.
|
|
2563
|
+
|
|
2564
|
+
**Only for an address-owned (or immutable) object.** `tx.objectRef` pins a
|
|
2565
|
+
version, which is exactly right for an owned input and exactly wrong for a
|
|
2566
|
+
**shared** object: a shared object is passed with `tx.sharedObjectRef({
|
|
2567
|
+
objectId, initialSharedVersion, mutable })` (the initial shared version lives
|
|
2568
|
+
on `ref.owner.Shared.initialSharedVersion`), and a **receiving** object with
|
|
2569
|
+
`tx.receivingRef(...)`, which takes the same three fields this returns.
|
|
2570
|
+
Passing a shared object by `objectRef` produces bytes a validator rejects.
|
|
2571
|
+
|
|
2572
|
+
`undefined` when the effects did not carry a version or a digest — a deleted
|
|
2573
|
+
object has no output version, and nothing can be consumed without both.
|
|
2730
2574
|
|
|
2731
2575
|
**Never fails.**
|
|
2732
2576
|
|
|
2577
|
+
## `@unconfirmed/sui-effect/tx`
|
|
2578
|
+
|
|
2579
|
+
|
|
2580
|
+
37 exported symbols.
|
|
2581
|
+
|
|
2582
|
+
### `Built` (const)
|
|
2583
|
+
|
|
2584
|
+
Re-exported from `@unconfirmed/sui-effect`.
|
|
2585
|
+
|
|
2733
2586
|
### `ExpirationPolicy` (type)
|
|
2734
2587
|
|
|
2735
2588
|
```ts
|
|
@@ -2766,351 +2619,31 @@ fact executed, twice in a row. The recheck delay makes that unlikely, not
|
|
|
2766
2619
|
impossible; a deployment that cannot tolerate it sets `"never"` and settles
|
|
2767
2620
|
unknown submissions by hand.
|
|
2768
2621
|
|
|
2769
|
-
### `
|
|
2770
|
-
|
|
2771
|
-
```ts
|
|
2772
|
-
declare const fromConfig: (name?: string) => Effect.Effect<Signer, Config.ConfigError>
|
|
2773
|
-
```
|
|
2774
|
-
|
|
2775
|
-
Reads a Bech32 `suiprivkey1…` secret key from configuration and builds the
|
|
2776
|
-
signer for whichever of the three schemes its flag names.
|
|
2777
|
-
|
|
2778
|
-
The key is read with `Config.redacted`, and the decoded bytes never leave
|
|
2779
|
-
this function. Neither does anything derived from them: the failure carries
|
|
2780
|
-
one fixed sentence and no `cause`, because the decoder's own message quotes
|
|
2781
|
-
the input it rejected.
|
|
2782
|
-
|
|
2783
|
-
**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).**
|
|
2784
|
-
|
|
2785
|
-
### `fromKeypair` (const)
|
|
2786
|
-
|
|
2787
|
-
```ts
|
|
2788
|
-
declare const fromKeypair: (keypair: Keypair) => Signer
|
|
2789
|
-
```
|
|
2790
|
-
|
|
2791
|
-
`fromSdkSigner` under the name the spec gave it when a keypair was the
|
|
2792
|
-
only thing it took. A `Keypair` **is** an SDK `Signer`, so this is a thin
|
|
2793
|
-
alias kept for callers who hold one.
|
|
2794
|
-
|
|
2795
|
-
**Never fails.**
|
|
2796
|
-
|
|
2797
|
-
### `fromSdkSigner` (const)
|
|
2622
|
+
### `Journal` (const)
|
|
2798
2623
|
|
|
2799
2624
|
```ts
|
|
2800
|
-
declare const
|
|
2625
|
+
declare const Journal: Context.Reference<JournalService> & {
|
|
2626
|
+
/** A fresh in-memory journal, for a test or a process that wants its own. */
|
|
2627
|
+
layerMemory: Layer.Layer<never, never, never>;
|
|
2628
|
+
/** The in-memory implementation, for building one directly. */
|
|
2629
|
+
makeMemoryUnsafe: () => JournalService;
|
|
2630
|
+
}
|
|
2801
2631
|
```
|
|
2802
2632
|
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
The SDK's `Signer` is the base class every credential extends: `Keypair` and
|
|
2806
|
-
its three schemes, but also a Ledger signer, a wallet adapter's signer, a KMS
|
|
2807
|
-
signer — anything that can `toSuiAddress`, `getKeyScheme`, `signTransaction`
|
|
2808
|
-
and `signPersonalMessage`. Nothing here needs the secret, so nothing here
|
|
2809
|
-
needs a keypair, and the `Signer` this returns exposes no secret material
|
|
2810
|
-
either.
|
|
2633
|
+
The submission journal.
|
|
2811
2634
|
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
Effects and the address to sign as.
|
|
2635
|
+
Being a `Context.Reference`, it is never in an `R`: `Tx.submit` reads it from
|
|
2636
|
+
context and finds the in-memory default unless something provided another.
|
|
2815
2637
|
|
|
2816
|
-
|
|
2817
|
-
|
|
2638
|
+
**The default is process-wide.** A `Context.Reference`'s default value is
|
|
2639
|
+
computed once and cached on the reference itself, so every fiber that does
|
|
2640
|
+
not provide one shares a single `Map` for the life of the process. That is
|
|
2641
|
+
what makes a one-shot script work with zero wiring, and it is also why a
|
|
2642
|
+
test that runs `Tx.submit` or `Tx.run` should provide
|
|
2643
|
+
`Journal.layerMemory`: without it, entries from one test are visible to
|
|
2644
|
+
the next, and `listUnresolved` returns other tests' submissions.
|
|
2818
2645
|
|
|
2819
|
-
### `
|
|
2820
|
-
|
|
2821
|
-
```ts
|
|
2822
|
-
declare const isUnresolved: (value: {
|
|
2823
|
-
readonly _tag: "Unknown";
|
|
2824
|
-
readonly digest: Digest;
|
|
2825
|
-
readonly signed: {
|
|
2826
|
-
readonly digest: Digest;
|
|
2827
|
-
readonly sender: SuiAddress;
|
|
2828
|
-
readonly signatures: readonly Signature[];
|
|
2829
|
-
readonly bytes: Uint8Array<ArrayBufferLike>;
|
|
2830
|
-
readonly chain?: string | undefined;
|
|
2831
|
-
readonly expiration?: {
|
|
2832
|
-
readonly $kind: "None";
|
|
2833
|
-
readonly None: true;
|
|
2834
|
-
} | {
|
|
2835
|
-
readonly $kind: "Epoch";
|
|
2836
|
-
readonly Epoch: bigint;
|
|
2837
|
-
} | {
|
|
2838
|
-
readonly $kind: "ValidDuring";
|
|
2839
|
-
readonly ValidDuring: {
|
|
2840
|
-
readonly minEpoch: bigint | null;
|
|
2841
|
-
readonly maxEpoch: bigint | null;
|
|
2842
|
-
readonly minTimestamp: bigint | null;
|
|
2843
|
-
readonly maxTimestamp: bigint | null;
|
|
2844
|
-
readonly chain: string;
|
|
2845
|
-
readonly nonce: number;
|
|
2846
|
-
};
|
|
2847
|
-
} | {
|
|
2848
|
-
readonly $kind: "Validity";
|
|
2849
|
-
readonly Validity: {
|
|
2850
|
-
readonly allowedProposers: {
|
|
2851
|
-
readonly epoch: bigint;
|
|
2852
|
-
readonly proposers: readonly number[];
|
|
2853
|
-
} | null;
|
|
2854
|
-
readonly minEpoch: bigint | null;
|
|
2855
|
-
readonly maxEpoch: bigint | null;
|
|
2856
|
-
readonly minTimestamp: bigint | null;
|
|
2857
|
-
readonly maxTimestamp: bigint | null;
|
|
2858
|
-
readonly chain: string;
|
|
2859
|
-
readonly nonce: number;
|
|
2860
|
-
};
|
|
2861
|
-
} | undefined;
|
|
2862
|
-
};
|
|
2863
|
-
readonly lastError: string;
|
|
2864
|
-
readonly attempts: number;
|
|
2865
|
-
readonly at: import("effect/DateTime").Utc;
|
|
2866
|
-
} | {
|
|
2867
|
-
readonly _tag: "NotApplied";
|
|
2868
|
-
readonly digest: Digest;
|
|
2869
|
-
readonly evidence: "expired" | "inputConsumed";
|
|
2870
|
-
readonly at: import("effect/DateTime").Utc;
|
|
2871
|
-
} | {
|
|
2872
|
-
readonly _tag: "Signed";
|
|
2873
|
-
readonly digest: Digest;
|
|
2874
|
-
readonly signed: {
|
|
2875
|
-
readonly digest: Digest;
|
|
2876
|
-
readonly sender: SuiAddress;
|
|
2877
|
-
readonly signatures: readonly Signature[];
|
|
2878
|
-
readonly bytes: Uint8Array<ArrayBufferLike>;
|
|
2879
|
-
readonly chain?: string | undefined;
|
|
2880
|
-
readonly expiration?: {
|
|
2881
|
-
readonly $kind: "None";
|
|
2882
|
-
readonly None: true;
|
|
2883
|
-
} | {
|
|
2884
|
-
readonly $kind: "Epoch";
|
|
2885
|
-
readonly Epoch: bigint;
|
|
2886
|
-
} | {
|
|
2887
|
-
readonly $kind: "ValidDuring";
|
|
2888
|
-
readonly ValidDuring: {
|
|
2889
|
-
readonly minEpoch: bigint | null;
|
|
2890
|
-
readonly maxEpoch: bigint | null;
|
|
2891
|
-
readonly minTimestamp: bigint | null;
|
|
2892
|
-
readonly maxTimestamp: bigint | null;
|
|
2893
|
-
readonly chain: string;
|
|
2894
|
-
readonly nonce: number;
|
|
2895
|
-
};
|
|
2896
|
-
} | {
|
|
2897
|
-
readonly $kind: "Validity";
|
|
2898
|
-
readonly Validity: {
|
|
2899
|
-
readonly allowedProposers: {
|
|
2900
|
-
readonly epoch: bigint;
|
|
2901
|
-
readonly proposers: readonly number[];
|
|
2902
|
-
} | null;
|
|
2903
|
-
readonly minEpoch: bigint | null;
|
|
2904
|
-
readonly maxEpoch: bigint | null;
|
|
2905
|
-
readonly minTimestamp: bigint | null;
|
|
2906
|
-
readonly maxTimestamp: bigint | null;
|
|
2907
|
-
readonly chain: string;
|
|
2908
|
-
readonly nonce: number;
|
|
2909
|
-
};
|
|
2910
|
-
} | undefined;
|
|
2911
|
-
};
|
|
2912
|
-
readonly signedAt: import("effect/DateTime").Utc;
|
|
2913
|
-
} | {
|
|
2914
|
-
readonly at: import("effect/DateTime").Utc;
|
|
2915
|
-
readonly digest: Digest;
|
|
2916
|
-
readonly _tag: "Executed";
|
|
2917
|
-
readonly checkpoint?: bigint | undefined;
|
|
2918
|
-
} | {
|
|
2919
|
-
readonly _tag: "Failed";
|
|
2920
|
-
readonly digest: Digest;
|
|
2921
|
-
readonly reason: {
|
|
2922
|
-
readonly $kind: "MoveAbort";
|
|
2923
|
-
readonly MoveAbort: {
|
|
2924
|
-
readonly abortCode: bigint;
|
|
2925
|
-
readonly location?: {
|
|
2926
|
-
readonly function?: number | undefined;
|
|
2927
|
-
readonly package?: string | undefined;
|
|
2928
|
-
readonly module?: string | undefined;
|
|
2929
|
-
readonly functionName?: string | undefined;
|
|
2930
|
-
readonly instruction?: number | undefined;
|
|
2931
|
-
} | undefined;
|
|
2932
|
-
readonly cleverError?: {
|
|
2933
|
-
readonly value?: string | undefined;
|
|
2934
|
-
readonly errorCode?: number | undefined;
|
|
2935
|
-
readonly lineNumber?: number | undefined;
|
|
2936
|
-
readonly constantName?: string | undefined;
|
|
2937
|
-
readonly constantType?: string | undefined;
|
|
2938
|
-
} | undefined;
|
|
2939
|
-
};
|
|
2940
|
-
} | {
|
|
2941
|
-
readonly $kind: "SizeError";
|
|
2942
|
-
readonly SizeError: {
|
|
2943
|
-
readonly name: string;
|
|
2944
|
-
readonly size: number;
|
|
2945
|
-
readonly maxSize: number;
|
|
2946
|
-
};
|
|
2947
|
-
} | {
|
|
2948
|
-
readonly $kind: "CommandArgumentError";
|
|
2949
|
-
readonly CommandArgumentError: {
|
|
2950
|
-
readonly argument: number;
|
|
2951
|
-
readonly name: string;
|
|
2952
|
-
};
|
|
2953
|
-
} | {
|
|
2954
|
-
readonly $kind: "TypeArgumentError";
|
|
2955
|
-
readonly TypeArgumentError: {
|
|
2956
|
-
readonly typeArgument: number;
|
|
2957
|
-
readonly name: string;
|
|
2958
|
-
};
|
|
2959
|
-
} | {
|
|
2960
|
-
readonly $kind: "PackageUpgradeError";
|
|
2961
|
-
readonly PackageUpgradeError: {
|
|
2962
|
-
readonly name: string;
|
|
2963
|
-
readonly digest?: string | undefined;
|
|
2964
|
-
readonly packageId?: string | undefined;
|
|
2965
|
-
};
|
|
2966
|
-
} | {
|
|
2967
|
-
readonly $kind: "IndexError";
|
|
2968
|
-
readonly IndexError: {
|
|
2969
|
-
readonly index?: number | undefined;
|
|
2970
|
-
readonly subresult?: number | undefined;
|
|
2971
|
-
};
|
|
2972
|
-
} | {
|
|
2973
|
-
readonly $kind: "CoinDenyListError";
|
|
2974
|
-
readonly CoinDenyListError: {
|
|
2975
|
-
readonly coinType: string;
|
|
2976
|
-
readonly name: string;
|
|
2977
|
-
readonly address?: string | undefined;
|
|
2978
|
-
};
|
|
2979
|
-
} | {
|
|
2980
|
-
readonly $kind: "CongestedObjects";
|
|
2981
|
-
readonly CongestedObjects: {
|
|
2982
|
-
readonly name: string;
|
|
2983
|
-
readonly objects: readonly string[];
|
|
2984
|
-
};
|
|
2985
|
-
} | {
|
|
2986
|
-
readonly $kind: "ObjectIdError";
|
|
2987
|
-
readonly ObjectIdError: {
|
|
2988
|
-
readonly objectId: string;
|
|
2989
|
-
readonly name?: string | undefined;
|
|
2990
|
-
};
|
|
2991
|
-
} | {
|
|
2992
|
-
readonly $kind: "Unknown";
|
|
2993
|
-
};
|
|
2994
|
-
readonly at: import("effect/DateTime").Utc;
|
|
2995
|
-
}) => value is {
|
|
2996
|
-
readonly _tag: "Unknown";
|
|
2997
|
-
readonly digest: Digest;
|
|
2998
|
-
readonly signed: {
|
|
2999
|
-
readonly digest: Digest;
|
|
3000
|
-
readonly sender: SuiAddress;
|
|
3001
|
-
readonly signatures: readonly Signature[];
|
|
3002
|
-
readonly bytes: Uint8Array<ArrayBufferLike>;
|
|
3003
|
-
readonly chain?: string | undefined;
|
|
3004
|
-
readonly expiration?: {
|
|
3005
|
-
readonly $kind: "None";
|
|
3006
|
-
readonly None: true;
|
|
3007
|
-
} | {
|
|
3008
|
-
readonly $kind: "Epoch";
|
|
3009
|
-
readonly Epoch: bigint;
|
|
3010
|
-
} | {
|
|
3011
|
-
readonly $kind: "ValidDuring";
|
|
3012
|
-
readonly ValidDuring: {
|
|
3013
|
-
readonly minEpoch: bigint | null;
|
|
3014
|
-
readonly maxEpoch: bigint | null;
|
|
3015
|
-
readonly minTimestamp: bigint | null;
|
|
3016
|
-
readonly maxTimestamp: bigint | null;
|
|
3017
|
-
readonly chain: string;
|
|
3018
|
-
readonly nonce: number;
|
|
3019
|
-
};
|
|
3020
|
-
} | {
|
|
3021
|
-
readonly $kind: "Validity";
|
|
3022
|
-
readonly Validity: {
|
|
3023
|
-
readonly allowedProposers: {
|
|
3024
|
-
readonly epoch: bigint;
|
|
3025
|
-
readonly proposers: readonly number[];
|
|
3026
|
-
} | null;
|
|
3027
|
-
readonly minEpoch: bigint | null;
|
|
3028
|
-
readonly maxEpoch: bigint | null;
|
|
3029
|
-
readonly minTimestamp: bigint | null;
|
|
3030
|
-
readonly maxTimestamp: bigint | null;
|
|
3031
|
-
readonly chain: string;
|
|
3032
|
-
readonly nonce: number;
|
|
3033
|
-
};
|
|
3034
|
-
} | undefined;
|
|
3035
|
-
};
|
|
3036
|
-
readonly lastError: string;
|
|
3037
|
-
readonly attempts: number;
|
|
3038
|
-
readonly at: import("effect/DateTime").Utc;
|
|
3039
|
-
} | {
|
|
3040
|
-
readonly _tag: "Signed";
|
|
3041
|
-
readonly digest: Digest;
|
|
3042
|
-
readonly signed: {
|
|
3043
|
-
readonly digest: Digest;
|
|
3044
|
-
readonly sender: SuiAddress;
|
|
3045
|
-
readonly signatures: readonly Signature[];
|
|
3046
|
-
readonly bytes: Uint8Array<ArrayBufferLike>;
|
|
3047
|
-
readonly chain?: string | undefined;
|
|
3048
|
-
readonly expiration?: {
|
|
3049
|
-
readonly $kind: "None";
|
|
3050
|
-
readonly None: true;
|
|
3051
|
-
} | {
|
|
3052
|
-
readonly $kind: "Epoch";
|
|
3053
|
-
readonly Epoch: bigint;
|
|
3054
|
-
} | {
|
|
3055
|
-
readonly $kind: "ValidDuring";
|
|
3056
|
-
readonly ValidDuring: {
|
|
3057
|
-
readonly minEpoch: bigint | null;
|
|
3058
|
-
readonly maxEpoch: bigint | null;
|
|
3059
|
-
readonly minTimestamp: bigint | null;
|
|
3060
|
-
readonly maxTimestamp: bigint | null;
|
|
3061
|
-
readonly chain: string;
|
|
3062
|
-
readonly nonce: number;
|
|
3063
|
-
};
|
|
3064
|
-
} | {
|
|
3065
|
-
readonly $kind: "Validity";
|
|
3066
|
-
readonly Validity: {
|
|
3067
|
-
readonly allowedProposers: {
|
|
3068
|
-
readonly epoch: bigint;
|
|
3069
|
-
readonly proposers: readonly number[];
|
|
3070
|
-
} | null;
|
|
3071
|
-
readonly minEpoch: bigint | null;
|
|
3072
|
-
readonly maxEpoch: bigint | null;
|
|
3073
|
-
readonly minTimestamp: bigint | null;
|
|
3074
|
-
readonly maxTimestamp: bigint | null;
|
|
3075
|
-
readonly chain: string;
|
|
3076
|
-
readonly nonce: number;
|
|
3077
|
-
};
|
|
3078
|
-
} | undefined;
|
|
3079
|
-
};
|
|
3080
|
-
readonly signedAt: import("effect/DateTime").Utc;
|
|
3081
|
-
}
|
|
3082
|
-
```
|
|
3083
|
-
|
|
3084
|
-
Whether this entry is still waiting for an answer, and therefore something
|
|
3085
|
-
`Tx.reconcileAll` has work to do about.
|
|
3086
|
-
|
|
3087
|
-
**Never fails.**
|
|
3088
|
-
|
|
3089
|
-
### `Journal` (const)
|
|
3090
|
-
|
|
3091
|
-
```ts
|
|
3092
|
-
declare const Journal: Context.Reference<JournalService> & {
|
|
3093
|
-
/** A fresh in-memory journal, for a test or a process that wants its own. */
|
|
3094
|
-
layerMemory: Layer.Layer<never, never, never>;
|
|
3095
|
-
/** The in-memory implementation, for building one directly. */
|
|
3096
|
-
makeMemoryUnsafe: () => JournalService;
|
|
3097
|
-
}
|
|
3098
|
-
```
|
|
3099
|
-
|
|
3100
|
-
The submission journal.
|
|
3101
|
-
|
|
3102
|
-
Being a `Context.Reference`, it is never in an `R`: `Tx.submit` reads it from
|
|
3103
|
-
context and finds the in-memory default unless something provided another.
|
|
3104
|
-
|
|
3105
|
-
**The default is process-wide.** A `Context.Reference`'s default value is
|
|
3106
|
-
computed once and cached on the reference itself, so every fiber that does
|
|
3107
|
-
not provide one shares a single `Map` for the life of the process. That is
|
|
3108
|
-
what makes a one-shot script work with zero wiring, and it is also why a
|
|
3109
|
-
test that runs `Tx.submit` or `Tx.run` should provide
|
|
3110
|
-
`Journal.layerMemory`: without it, entries from one test are visible to
|
|
3111
|
-
the next, and `listUnresolved` returns other tests' submissions.
|
|
3112
|
-
|
|
3113
|
-
### `JournalEntry` (const)
|
|
2646
|
+
### `JournalEntry` (const)
|
|
3114
2647
|
|
|
3115
2648
|
```ts
|
|
3116
2649
|
declare const JournalEntry: Schema
|
|
@@ -3332,138 +2865,32 @@ export interface JournalService {
|
|
|
3332
2865
|
|
|
3333
2866
|
Reading and writing the submission journal.
|
|
3334
2867
|
|
|
3335
|
-
### `
|
|
3336
|
-
|
|
3337
|
-
Re-exported from `@unconfirmed/sui-effect`.
|
|
2868
|
+
### `ReconcileInput` (type)
|
|
3338
2869
|
|
|
3339
|
-
|
|
2870
|
+
```ts
|
|
2871
|
+
export type ReconcileInput = Digest | Signed | SubmissionUnknown;
|
|
2872
|
+
```
|
|
3340
2873
|
|
|
3341
|
-
|
|
2874
|
+
What `Tx.reconcile` can be asked about.
|
|
3342
2875
|
|
|
3343
|
-
### `
|
|
2876
|
+
### `Reconciled` (type)
|
|
3344
2877
|
|
|
3345
2878
|
```ts
|
|
3346
|
-
|
|
2879
|
+
export type Reconciled = Executed | ExecutionFailed | NotApplied | SubmissionUnknown;
|
|
3347
2880
|
```
|
|
3348
2881
|
|
|
3349
|
-
|
|
3350
|
-
for.
|
|
2882
|
+
What one entry of `Tx.reconcileAll` settled to.
|
|
3351
2883
|
|
|
3352
|
-
|
|
3353
|
-
applied and aborted. A transaction the node does not know is only ever
|
|
3354
|
-
`NotApplied` on evidence, and there are exactly two kinds:
|
|
2884
|
+
### `RemoteSigner` (interface)
|
|
3355
2885
|
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
is what a deployment behind a mixed-node load balancer wants. The residual
|
|
3365
|
-
risk is a node whose transaction index lags its epoch view;
|
|
3366
|
-
- `"inputConsumed"` when the transaction that last mutated a pinned object is
|
|
3367
|
-
a **different** one **and its own effects report `inputVersion` equal to
|
|
3368
|
-
the version these bytes pinned**, so those exact bytes can never execute
|
|
3369
|
-
again.
|
|
3370
|
-
|
|
3371
|
-
An input that merely advanced is not evidence: the transaction being
|
|
3372
|
-
reconciled is itself the likeliest thing to have advanced it, and calling
|
|
3373
|
-
that `NotApplied` would tell the documented retry idiom to execute the
|
|
3374
|
-
caller's intent a second time. When the live object names *our* digest the
|
|
3375
|
-
transaction applied and `getTransaction` is asked again; when the consuming
|
|
3376
|
-
transaction took a **later** version than ours — which is the common case,
|
|
3377
|
-
because Sui stamps every output with the transaction's Lamport version and
|
|
3378
|
-
the object "one version on" from ours usually never existed — nothing is
|
|
3379
|
-
proven and the answer is `SubmissionUnknown`, which carries the bytes so a
|
|
3380
|
-
later process, or a person, can settle it. Every pinned reference is tried
|
|
3381
|
-
before that answer is given.
|
|
3382
|
-
|
|
3383
|
-
**Chain identity is checked before anything is asked.** Bytes built for one
|
|
3384
|
-
chain must never be declared expired by another chain's epoch, which a
|
|
3385
|
-
process-wide journal holding two networks' submissions makes easy to do. A
|
|
3386
|
-
mismatch is `SubmissionUnknown` naming both chains.
|
|
3387
|
-
|
|
3388
|
-
**No `TransportError` escapes.** A recovery read that fails says nothing
|
|
3389
|
-
about whether the transaction applied, and `SuiError.outcome` puts
|
|
3390
|
-
`TransportError` on `"not_applied"` — which would tell a wrapper to retry a
|
|
3391
|
-
submission whose outcome is genuinely unknown. Every read failure here
|
|
3392
|
-
becomes `SubmissionUnknown` carrying the digest, the bytes and the cause. The
|
|
3393
|
-
tag stays in the signature so the union does not shrink under callers.
|
|
3394
|
-
|
|
3395
|
-
Given only a `Digest` there can be no evidence, so an unknown transaction is
|
|
3396
|
-
always `SubmissionUnknown`. Pass the `Signed` bytes (or the
|
|
3397
|
-
`SubmissionUnknown` that carries them) to get the evidence rules.
|
|
3398
|
-
|
|
3399
|
-
**Fails with: `ExecutionFailed`, `NotApplied`, `SubmissionUnknown`, `TransportError`.**
|
|
3400
|
-
|
|
3401
|
-
### `reconcileAll` (const)
|
|
3402
|
-
|
|
3403
|
-
```ts
|
|
3404
|
-
declare const reconcileAll: () => Effect.Effect<readonly Reconciled[], TransportError | JournalError, Sui>
|
|
3405
|
-
```
|
|
3406
|
-
|
|
3407
|
-
Settles every unresolved entry in the journal: the explicit startup call a
|
|
3408
|
-
long-lived application makes after building a durable `Journal`.
|
|
3409
|
-
|
|
3410
|
-
Nothing here fails per entry: each one settles to an `Executed`, an
|
|
3411
|
-
`ExecutionFailed`, a `NotApplied` or a `SubmissionUnknown`, in the order the
|
|
3412
|
-
journal listed them, and the journal is updated to match. Every settled entry
|
|
3413
|
-
gets the same evidence rules `Tx.reconcile` applies — the ordered, repeated
|
|
3414
|
-
expiry rule, the chain-identity guard and the versioned consumer check — so a
|
|
3415
|
-
startup never reports a transaction that applied as `NotApplied`, and a
|
|
3416
|
-
recovery read that fails becomes that entry's `SubmissionUnknown` rather than
|
|
3417
|
-
escaping as a `TransportError` the taxonomy would call "not applied".
|
|
3418
|
-
|
|
3419
|
-
The whole call fails only if the journal itself cannot be **read**: a write
|
|
3420
|
-
that fails after an entry has been settled is logged and the answer stands,
|
|
3421
|
-
the same rule `Tx.submit` follows.
|
|
3422
|
-
|
|
3423
|
-
**Fails with: `JournalError`, `TransportError`.**
|
|
3424
|
-
|
|
3425
|
-
### `Reconciled` (type)
|
|
3426
|
-
|
|
3427
|
-
```ts
|
|
3428
|
-
export type Reconciled = Executed | ExecutionFailed | NotApplied | SubmissionUnknown;
|
|
3429
|
-
```
|
|
3430
|
-
|
|
3431
|
-
What one entry of `Tx.reconcileAll` settled to.
|
|
3432
|
-
|
|
3433
|
-
### `ReconcileInput` (type)
|
|
3434
|
-
|
|
3435
|
-
```ts
|
|
3436
|
-
export type ReconcileInput = Digest | Signed | SubmissionUnknown;
|
|
3437
|
-
```
|
|
3438
|
-
|
|
3439
|
-
What `Tx.reconcile` can be asked about.
|
|
3440
|
-
|
|
3441
|
-
### `remote` (const)
|
|
3442
|
-
|
|
3443
|
-
```ts
|
|
3444
|
-
declare const remote: (signer: RemoteSigner) => Signer
|
|
3445
|
-
```
|
|
3446
|
-
|
|
3447
|
-
Builds a signer around something that signs elsewhere: a KMS, a wallet, a
|
|
3448
|
-
hardware device, another process.
|
|
3449
|
-
|
|
3450
|
-
The returned signer decodes whatever the remote produced, so a malformed
|
|
3451
|
-
signature is a `SigningError` rather than a surprise at execution. When
|
|
3452
|
-
`signPersonalMessage` is not given, asking for one fails with `SigningError`
|
|
3453
|
-
instead of pretending.
|
|
3454
|
-
|
|
3455
|
-
**Never fails.**
|
|
3456
|
-
|
|
3457
|
-
### `RemoteSigner` (interface)
|
|
3458
|
-
|
|
3459
|
-
```ts
|
|
3460
|
-
export interface RemoteSigner {
|
|
3461
|
-
readonly address: SuiAddress;
|
|
3462
|
-
readonly scheme: SignatureScheme;
|
|
3463
|
-
readonly signTransaction: (bytes: Uint8Array) => Effect.Effect<string, SigningError>;
|
|
3464
|
-
readonly signPersonalMessage?: (bytes: Uint8Array) => Effect.Effect<string, SigningError>;
|
|
3465
|
-
}
|
|
3466
|
-
```
|
|
2886
|
+
```ts
|
|
2887
|
+
export interface RemoteSigner {
|
|
2888
|
+
readonly address: SuiAddress;
|
|
2889
|
+
readonly scheme: SignatureScheme;
|
|
2890
|
+
readonly signTransaction: (bytes: Uint8Array) => Effect.Effect<string, SigningError>;
|
|
2891
|
+
readonly signPersonalMessage?: (bytes: Uint8Array) => Effect.Effect<string, SigningError>;
|
|
2892
|
+
}
|
|
2893
|
+
```
|
|
3467
2894
|
|
|
3468
2895
|
What `remote` needs to know about a credential it does not hold.
|
|
3469
2896
|
|
|
@@ -3473,51 +2900,6 @@ transaction's sender and gas owner and refuse a mismatch, which is the only
|
|
|
3473
2900
|
thing standing between a misconfigured KMS key and an `INVALID_ARGUMENT`
|
|
3474
2901
|
rejection that `Tx.submit` can only report as `SubmissionUnknown`.
|
|
3475
2902
|
|
|
3476
|
-
### `run` (const)
|
|
3477
|
-
|
|
3478
|
-
```ts
|
|
3479
|
-
declare const run: (recipe: Transaction | Recipe, opts: {
|
|
3480
|
-
readonly signer: Signer;
|
|
3481
|
-
readonly gasOwner?: SuiAddress;
|
|
3482
|
-
/**
|
|
3483
|
-
* The gas owner's signer, for a sponsored transaction. Required whenever
|
|
3484
|
-
* the bytes name a gas owner that is not the sender.
|
|
3485
|
-
*/
|
|
3486
|
-
readonly sponsor?: Signer;
|
|
3487
|
-
}) => Effect.Effect<Executed, TransportError | SimulationFailed | ExecutionFailed | SubmissionUnknown | NotApplied | SigningError | BuildError | PolicyDenied | JournalError, Sui>
|
|
3488
|
-
```
|
|
3489
|
-
|
|
3490
|
-
Build, preflight, sign and submit, with the sender lock held throughout.
|
|
3491
|
-
|
|
3492
|
-
Gas coins are chosen during build, so two transactions from one address that
|
|
3493
|
-
overlap can pick the same coin and one of them will fail on chain. `Tx.run`
|
|
3494
|
-
holds the sender lock from build through submit, which is the whole reason to
|
|
3495
|
-
prefer it over calling the steps separately.
|
|
3496
|
-
|
|
3497
|
-
The address that matters is the one whose coins are being spent, which is the
|
|
3498
|
-
**gas owner** when there is one: two sponsored runs for different senders
|
|
3499
|
-
paid by one sponsor are exactly the case that picks the same coin twice. When
|
|
3500
|
-
sender and gas owner differ, both locks are held, in ascending address order
|
|
3501
|
-
— a fixed order, so two runs that each need the same pair cannot deadlock by
|
|
3502
|
-
taking them the other way round.
|
|
3503
|
-
|
|
3504
|
-
When `SubmitConfig.preflight` is set it costs one extra simulate, and is
|
|
3505
|
-
where spend limits and target policies refuse a transaction before anything
|
|
3506
|
-
is signed.
|
|
3507
|
-
|
|
3508
|
-
**A sponsored run needs both signatures.** A transaction whose gas owner is
|
|
3509
|
-
not its sender is signed by *both* parties; one signature is bytes a
|
|
3510
|
-
validator rejects. So when `opts.gasOwner` differs from the signer's address
|
|
3511
|
-
— or when the recipe itself set a different gas owner, which `Tx.sponsored`
|
|
3512
|
-
does — `opts.sponsor` is required and co-signs the same bytes. Without it
|
|
3513
|
-
`Tx.run` fails with `SigningError` naming the address whose signature is
|
|
3514
|
-
missing, before anything is built when the gas owner was given as an option
|
|
3515
|
-
and immediately after the build when it came out of the recipe. Use the
|
|
3516
|
-
explicit lifecycle (`Tx.build`, `Tx.sign`, `Tx.cosign`, `Tx.submit`) when the
|
|
3517
|
-
two parties cannot both sign in one process.
|
|
3518
|
-
|
|
3519
|
-
**Fails with: `BuildError`, `SimulationFailed`, `PolicyDenied`, `SigningError`, `ExecutionFailed`, `NotApplied`, `SubmissionUnknown`, `JournalError`, `TransportError` (from the build reads; once bytes are sent, transport failures become `SubmissionUnknown`).**
|
|
3520
|
-
|
|
3521
2903
|
### `RunError` (type)
|
|
3522
2904
|
|
|
3523
2905
|
```ts
|
|
@@ -3527,95 +2909,6 @@ export type RunError = BuildError | SimulationFailed | PolicyDenied | SigningErr
|
|
|
3527
2909
|
Everything `run` can fail with, as one name: `SubmitError` plus
|
|
3528
2910
|
what building, preflighting and signing can produce.
|
|
3529
2911
|
|
|
3530
|
-
### `sign` (const)
|
|
3531
|
-
|
|
3532
|
-
```ts
|
|
3533
|
-
declare const sign: (built: {
|
|
3534
|
-
readonly digest: Digest;
|
|
3535
|
-
readonly sender: SuiAddress;
|
|
3536
|
-
readonly bytes: Uint8Array<ArrayBufferLike>;
|
|
3537
|
-
readonly chain?: string | undefined;
|
|
3538
|
-
readonly expiration?: {
|
|
3539
|
-
readonly $kind: "None";
|
|
3540
|
-
readonly None: true;
|
|
3541
|
-
} | {
|
|
3542
|
-
readonly $kind: "Epoch";
|
|
3543
|
-
readonly Epoch: bigint;
|
|
3544
|
-
} | {
|
|
3545
|
-
readonly $kind: "ValidDuring";
|
|
3546
|
-
readonly ValidDuring: {
|
|
3547
|
-
readonly minEpoch: bigint | null;
|
|
3548
|
-
readonly maxEpoch: bigint | null;
|
|
3549
|
-
readonly minTimestamp: bigint | null;
|
|
3550
|
-
readonly maxTimestamp: bigint | null;
|
|
3551
|
-
readonly chain: string;
|
|
3552
|
-
readonly nonce: number;
|
|
3553
|
-
};
|
|
3554
|
-
} | {
|
|
3555
|
-
readonly $kind: "Validity";
|
|
3556
|
-
readonly Validity: {
|
|
3557
|
-
readonly allowedProposers: {
|
|
3558
|
-
readonly epoch: bigint;
|
|
3559
|
-
readonly proposers: readonly number[];
|
|
3560
|
-
} | null;
|
|
3561
|
-
readonly minEpoch: bigint | null;
|
|
3562
|
-
readonly maxEpoch: bigint | null;
|
|
3563
|
-
readonly minTimestamp: bigint | null;
|
|
3564
|
-
readonly maxTimestamp: bigint | null;
|
|
3565
|
-
readonly chain: string;
|
|
3566
|
-
readonly nonce: number;
|
|
3567
|
-
};
|
|
3568
|
-
} | undefined;
|
|
3569
|
-
readonly gasOwner?: SuiAddress | undefined;
|
|
3570
|
-
}, signer: Signer) => Effect.Effect<{
|
|
3571
|
-
readonly digest: Digest;
|
|
3572
|
-
readonly sender: SuiAddress;
|
|
3573
|
-
readonly signatures: readonly Signature[];
|
|
3574
|
-
readonly bytes: Uint8Array<ArrayBufferLike>;
|
|
3575
|
-
readonly chain?: string | undefined;
|
|
3576
|
-
readonly expiration?: {
|
|
3577
|
-
readonly $kind: "None";
|
|
3578
|
-
readonly None: true;
|
|
3579
|
-
} | {
|
|
3580
|
-
readonly $kind: "Epoch";
|
|
3581
|
-
readonly Epoch: bigint;
|
|
3582
|
-
} | {
|
|
3583
|
-
readonly $kind: "ValidDuring";
|
|
3584
|
-
readonly ValidDuring: {
|
|
3585
|
-
readonly minEpoch: bigint | null;
|
|
3586
|
-
readonly maxEpoch: bigint | null;
|
|
3587
|
-
readonly minTimestamp: bigint | null;
|
|
3588
|
-
readonly maxTimestamp: bigint | null;
|
|
3589
|
-
readonly chain: string;
|
|
3590
|
-
readonly nonce: number;
|
|
3591
|
-
};
|
|
3592
|
-
} | {
|
|
3593
|
-
readonly $kind: "Validity";
|
|
3594
|
-
readonly Validity: {
|
|
3595
|
-
readonly allowedProposers: {
|
|
3596
|
-
readonly epoch: bigint;
|
|
3597
|
-
readonly proposers: readonly number[];
|
|
3598
|
-
} | null;
|
|
3599
|
-
readonly minEpoch: bigint | null;
|
|
3600
|
-
readonly maxEpoch: bigint | null;
|
|
3601
|
-
readonly minTimestamp: bigint | null;
|
|
3602
|
-
readonly maxTimestamp: bigint | null;
|
|
3603
|
-
readonly chain: string;
|
|
3604
|
-
readonly nonce: number;
|
|
3605
|
-
};
|
|
3606
|
-
} | undefined;
|
|
3607
|
-
}, SigningError, never>
|
|
3608
|
-
```
|
|
3609
|
-
|
|
3610
|
-
Signs built bytes.
|
|
3611
|
-
|
|
3612
|
-
The signer's address must be the transaction's sender or, for a sponsored
|
|
3613
|
-
transaction, its gas owner; anything else is a `SigningError` rather than a
|
|
3614
|
-
rejection at execution time. A `Signer.remote` therefore has to report the
|
|
3615
|
-
address it signs as truthfully.
|
|
3616
|
-
|
|
3617
|
-
**Fails with: `SigningError`.**
|
|
3618
|
-
|
|
3619
2912
|
### `Signature` (const)
|
|
3620
2913
|
|
|
3621
2914
|
Re-exported from `@unconfirmed/sui-effect`.
|
|
@@ -3665,67 +2958,22 @@ Both members fail with `SigningError` and nothing else: a signer that has to
|
|
|
3665
2958
|
reach a KMS or a wallet wraps its own transport failure in the `cause`, so a
|
|
3666
2959
|
caller's error union does not grow a branch per credential kind.
|
|
3667
2960
|
|
|
3668
|
-
### `
|
|
2961
|
+
### `SubmitConfig` (const)
|
|
3669
2962
|
|
|
3670
2963
|
```ts
|
|
3671
|
-
declare const
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
}) => (recipe: Recipe) => Recipe
|
|
2964
|
+
declare const SubmitConfig: Context.Reference<SubmitConfigService> & {
|
|
2965
|
+
defaults: SubmitConfigService;
|
|
2966
|
+
}
|
|
3675
2967
|
```
|
|
3676
2968
|
|
|
3677
|
-
|
|
3678
|
-
gas owner pays, and gas comes from the sponsor's address balance rather than
|
|
3679
|
-
from coin objects, so the two parties never have to agree on a gas coin and
|
|
3680
|
-
can sign in either order.
|
|
2969
|
+
The lifecycle settings.
|
|
3681
2970
|
|
|
3682
|
-
|
|
3683
|
-
|
|
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" })`.
|
|
3684
2975
|
|
|
3685
|
-
### `
|
|
3686
|
-
|
|
3687
|
-
```ts
|
|
3688
|
-
declare const submit: (signed: Signed) => Effect.Effect<Executed, SubmitError, Sui>
|
|
3689
|
-
```
|
|
3690
|
-
|
|
3691
|
-
Sends signed bytes, and does not stop caring until it knows what happened.
|
|
3692
|
-
|
|
3693
|
-
Before the first `executeTransaction` it writes a `Signed` journal entry, so
|
|
3694
|
-
a process that dies mid-flight leaves a record of bytes that may be on the
|
|
3695
|
-
wire. A retryable transport failure or a timeout re-sends the identical bytes
|
|
3696
|
-
on `SubmitConfig.resubmit`; nothing is ever rebuilt, so a retry can only land
|
|
3697
|
-
the transaction that was already signed. When the retries run out it runs
|
|
3698
|
-
`Tx.reconcile`, which either finds the transaction, proves it never applied,
|
|
3699
|
-
or says it does not know.
|
|
3700
|
-
|
|
3701
|
-
`TransportError` never escapes: once bytes may have been sent, "the network
|
|
3702
|
-
was unreachable" is not an answer a caller can act on, so it becomes
|
|
3703
|
-
`SubmissionUnknown` carrying the signed bytes.
|
|
3704
|
-
|
|
3705
|
-
`JournalError` can only come from the `Signed` write, before anything has
|
|
3706
|
-
been sent. Once the network has answered, a journal write that fails is
|
|
3707
|
-
logged with `Effect.logError` and the answer stands, because "the journal is
|
|
3708
|
-
broken" is not a thing a caller can act on and reporting it in place of a
|
|
3709
|
-
charged `ExecutionFailed` would invite a second submission.
|
|
3710
|
-
|
|
3711
|
-
**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).**
|
|
3712
|
-
|
|
3713
|
-
### `SubmitConfig` (const)
|
|
3714
|
-
|
|
3715
|
-
```ts
|
|
3716
|
-
declare const SubmitConfig: Context.Reference<SubmitConfigService> & {
|
|
3717
|
-
defaults: SubmitConfigService;
|
|
3718
|
-
}
|
|
3719
|
-
```
|
|
3720
|
-
|
|
3721
|
-
The lifecycle settings.
|
|
3722
|
-
|
|
3723
|
-
Because this is a `Context.Reference` and not a service, it never appears in
|
|
3724
|
-
an `R`: a one-shot script gets the defaults with no wiring, and an
|
|
3725
|
-
application overrides what it cares about with
|
|
3726
|
-
`Effect.provideService(effect, SubmitConfig, { ...SubmitConfig.defaults, validFor: "30 seconds" })`.
|
|
3727
|
-
|
|
3728
|
-
### `SubmitConfigService` (interface)
|
|
2976
|
+
### `SubmitConfigService` (interface)
|
|
3729
2977
|
|
|
3730
2978
|
```ts
|
|
3731
2979
|
export interface SubmitConfigService {
|
|
@@ -4076,13 +3324,806 @@ The lifecycle, namespaced the way the spec spells it: `Tx.build`, `Tx.sign`,
|
|
|
4076
3324
|
`Tx.cosign`, `Tx.sponsored`, `Tx.submit`, `Tx.reconcile`, `Tx.run`,
|
|
4077
3325
|
`Tx.reconcileAll`.
|
|
4078
3326
|
|
|
4079
|
-
### `UNRESOLVED_TAGS` (const)
|
|
3327
|
+
### `UNRESOLVED_TAGS` (const)
|
|
3328
|
+
|
|
3329
|
+
```ts
|
|
3330
|
+
declare const UNRESOLVED_TAGS: readonly ["Signed", "Unknown"]
|
|
3331
|
+
```
|
|
3332
|
+
|
|
3333
|
+
The tags whose entries still need an answer from the network.
|
|
3334
|
+
|
|
3335
|
+
### `build` (const)
|
|
3336
|
+
|
|
3337
|
+
```ts
|
|
3338
|
+
declare const build: (input: Transaction | Recipe, opts: {
|
|
3339
|
+
readonly sender: SuiAddress;
|
|
3340
|
+
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>
|
|
3379
|
+
```
|
|
3380
|
+
|
|
3381
|
+
Builds a transaction into signable bytes.
|
|
3382
|
+
|
|
3383
|
+
**Building always simulates.** On gRPC the SDK's resolve plugin simulates
|
|
3384
|
+
with checks enabled to choose the gas budget, and an execution failure there
|
|
3385
|
+
arrives as `SimulationFailed`. That costs nothing extra — but the resolver
|
|
3386
|
+
returns early for a transaction that was **already fully resolved** (every
|
|
3387
|
+
input resolved, gas price, budget and payment set), and then nothing
|
|
3388
|
+
simulates at all. `Tx.build` detects that case and runs one explicit
|
|
3389
|
+
`simulateTransaction` with checks enabled, so simulate-before-submit holds
|
|
3390
|
+
for every transaction: it costs nothing extra when the SDK had to resolve,
|
|
3391
|
+
and one call otherwise.
|
|
3392
|
+
|
|
3393
|
+
An interrupted build cancels the request it started: the SDK is handed a
|
|
3394
|
+
client whose Core calls carry the Effect's `AbortSignal`.
|
|
3395
|
+
|
|
3396
|
+
When the recipe set no expiration, `SubmitConfig.expiration` decides one.
|
|
3397
|
+
The default, `ValidDuring`, bounds the transaction to the **current epoch and
|
|
3398
|
+
the next**, names the chain (bytes signed for testnet cannot land on mainnet)
|
|
3399
|
+
and carries a `u32` nonce from `SubmitConfig.nonce`. There is **no
|
|
3400
|
+
wall-clock bound** unless `SubmitConfig.validFor` asks for one: no Sui
|
|
3401
|
+
network accepts a timestamp expiration yet, and a node refuses any
|
|
3402
|
+
transaction carrying one. The expiration that ends up in the bytes is
|
|
3403
|
+
recorded on the result, because it is what `Tx.reconcile` needs later to
|
|
3404
|
+
prove a transaction can no longer land.
|
|
3405
|
+
|
|
3406
|
+
`Tx.build` takes no sender lock; `Tx.run` is what holds one from build
|
|
3407
|
+
through submit. Called on its own, two concurrent builds for one address can
|
|
3408
|
+
pick the same gas coin.
|
|
3409
|
+
|
|
3410
|
+
**Fails with: `BuildError` (the recipe threw, an input could not be resolved, or the budget the node chose is over `SubmitConfig.maxGasBudget`), `SimulationFailed` (the transaction would abort on chain), `TransportError`.**
|
|
3411
|
+
|
|
3412
|
+
### `chainOf` (const)
|
|
3413
|
+
|
|
3414
|
+
Re-exported from `@unconfirmed/sui-effect`.
|
|
3415
|
+
|
|
3416
|
+
### `cosign` (const)
|
|
3417
|
+
|
|
3418
|
+
```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>
|
|
3494
|
+
```
|
|
3495
|
+
|
|
3496
|
+
Adds one more signature to already signed bytes, for a sponsored or
|
|
3497
|
+
multi-party transaction. The bytes are untouched, so both parties sign
|
|
3498
|
+
exactly the same transaction.
|
|
3499
|
+
|
|
3500
|
+
As in `sign`, the co-signer's address must be the sender or the gas
|
|
3501
|
+
owner named in the bytes.
|
|
3502
|
+
|
|
3503
|
+
**Fails with: `SigningError`.**
|
|
3504
|
+
|
|
3505
|
+
### `ephemeral` (const)
|
|
3506
|
+
|
|
3507
|
+
```ts
|
|
3508
|
+
declare const ephemeral: Effect.Effect<Signer>
|
|
3509
|
+
```
|
|
3510
|
+
|
|
3511
|
+
A fresh Ed25519 credential that exists only for this process. For tests,
|
|
3512
|
+
localnet and throwaway addresses.
|
|
3513
|
+
|
|
3514
|
+
This is the one place sui-effect does not take randomness from Effect's
|
|
3515
|
+
`Random`: key generation must come from a cryptographically secure source,
|
|
3516
|
+
and `Random` is a seeded, test-controllable PRNG whose whole purpose is to be
|
|
3517
|
+
reproducible. `new Ed25519Keypair()` uses the SDK's CSPRNG (`@noble/curves`
|
|
3518
|
+
over `crypto.getRandomValues`). A `TestClock`-style deterministic key would
|
|
3519
|
+
be a security bug, not a convenience.
|
|
3520
|
+
|
|
3521
|
+
**Never fails.**
|
|
3522
|
+
|
|
3523
|
+
### `fromConfig` (const)
|
|
3524
|
+
|
|
3525
|
+
```ts
|
|
3526
|
+
declare const fromConfig: (name?: string) => Effect.Effect<Signer, Config.ConfigError>
|
|
3527
|
+
```
|
|
3528
|
+
|
|
3529
|
+
Reads a Bech32 `suiprivkey1…` secret key from configuration and builds the
|
|
3530
|
+
signer for whichever of the three schemes its flag names.
|
|
3531
|
+
|
|
3532
|
+
The key is read with `Config.redacted`, and the decoded bytes never leave
|
|
3533
|
+
this function. Neither does anything derived from them: the failure carries
|
|
3534
|
+
one fixed sentence and no `cause`, because the decoder's own message quotes
|
|
3535
|
+
the input it rejected.
|
|
3536
|
+
|
|
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).**
|
|
3538
|
+
|
|
3539
|
+
### `fromKeypair` (const)
|
|
3540
|
+
|
|
3541
|
+
```ts
|
|
3542
|
+
declare const fromKeypair: (keypair: Keypair) => Signer
|
|
3543
|
+
```
|
|
3544
|
+
|
|
3545
|
+
`fromSdkSigner` under the name the spec gave it when a keypair was the
|
|
3546
|
+
only thing it took. A `Keypair` **is** an SDK `Signer`, so this is a thin
|
|
3547
|
+
alias kept for callers who hold one.
|
|
3548
|
+
|
|
3549
|
+
**Never fails.**
|
|
3550
|
+
|
|
3551
|
+
### `fromSdkSigner` (const)
|
|
3552
|
+
|
|
3553
|
+
```ts
|
|
3554
|
+
declare const fromSdkSigner: (keypair: SdkSigner) => Signer
|
|
3555
|
+
```
|
|
3556
|
+
|
|
3557
|
+
Wraps any `@mysten/sui/cryptography` `Signer`.
|
|
3558
|
+
|
|
3559
|
+
The SDK's `Signer` is the base class every credential extends: `Keypair` and
|
|
3560
|
+
its three schemes, but also a Ledger signer, a wallet adapter's signer, a KMS
|
|
3561
|
+
signer — anything that can `toSuiAddress`, `getKeyScheme`, `signTransaction`
|
|
3562
|
+
and `signPersonalMessage`. Nothing here needs the secret, so nothing here
|
|
3563
|
+
needs a keypair, and the `Signer` this returns exposes no secret material
|
|
3564
|
+
either.
|
|
3565
|
+
|
|
3566
|
+
For a credential that is not an SDK `Signer` at all — a remote service, a
|
|
3567
|
+
hardware device behind your own protocol — use `remote`, which takes
|
|
3568
|
+
Effects and the address to sign as.
|
|
3569
|
+
|
|
3570
|
+
Never fails: a bad address or signature surfaces as a `SigningError` from the
|
|
3571
|
+
member that produced it, not from construction.
|
|
3572
|
+
|
|
3573
|
+
### `isUnresolved` (const)
|
|
3574
|
+
|
|
3575
|
+
```ts
|
|
3576
|
+
declare const isUnresolved: (value: {
|
|
3577
|
+
readonly _tag: "Unknown";
|
|
3578
|
+
readonly digest: Digest;
|
|
3579
|
+
readonly signed: {
|
|
3580
|
+
readonly digest: Digest;
|
|
3581
|
+
readonly sender: SuiAddress;
|
|
3582
|
+
readonly signatures: readonly Signature[];
|
|
3583
|
+
readonly bytes: Uint8Array<ArrayBufferLike>;
|
|
3584
|
+
readonly chain?: string | undefined;
|
|
3585
|
+
readonly expiration?: {
|
|
3586
|
+
readonly $kind: "None";
|
|
3587
|
+
readonly None: true;
|
|
3588
|
+
} | {
|
|
3589
|
+
readonly $kind: "Epoch";
|
|
3590
|
+
readonly Epoch: bigint;
|
|
3591
|
+
} | {
|
|
3592
|
+
readonly $kind: "ValidDuring";
|
|
3593
|
+
readonly ValidDuring: {
|
|
3594
|
+
readonly minEpoch: bigint | null;
|
|
3595
|
+
readonly maxEpoch: bigint | null;
|
|
3596
|
+
readonly minTimestamp: bigint | null;
|
|
3597
|
+
readonly maxTimestamp: bigint | null;
|
|
3598
|
+
readonly chain: string;
|
|
3599
|
+
readonly nonce: number;
|
|
3600
|
+
};
|
|
3601
|
+
} | {
|
|
3602
|
+
readonly $kind: "Validity";
|
|
3603
|
+
readonly Validity: {
|
|
3604
|
+
readonly allowedProposers: {
|
|
3605
|
+
readonly epoch: bigint;
|
|
3606
|
+
readonly proposers: readonly number[];
|
|
3607
|
+
} | null;
|
|
3608
|
+
readonly minEpoch: bigint | null;
|
|
3609
|
+
readonly maxEpoch: bigint | null;
|
|
3610
|
+
readonly minTimestamp: bigint | null;
|
|
3611
|
+
readonly maxTimestamp: bigint | null;
|
|
3612
|
+
readonly chain: string;
|
|
3613
|
+
readonly nonce: number;
|
|
3614
|
+
};
|
|
3615
|
+
} | undefined;
|
|
3616
|
+
};
|
|
3617
|
+
readonly lastError: string;
|
|
3618
|
+
readonly attempts: number;
|
|
3619
|
+
readonly at: import("effect/DateTime").Utc;
|
|
3620
|
+
} | {
|
|
3621
|
+
readonly _tag: "NotApplied";
|
|
3622
|
+
readonly digest: Digest;
|
|
3623
|
+
readonly evidence: "expired" | "inputConsumed";
|
|
3624
|
+
readonly at: import("effect/DateTime").Utc;
|
|
3625
|
+
} | {
|
|
3626
|
+
readonly _tag: "Signed";
|
|
3627
|
+
readonly digest: Digest;
|
|
3628
|
+
readonly signed: {
|
|
3629
|
+
readonly digest: Digest;
|
|
3630
|
+
readonly sender: SuiAddress;
|
|
3631
|
+
readonly signatures: readonly Signature[];
|
|
3632
|
+
readonly bytes: Uint8Array<ArrayBufferLike>;
|
|
3633
|
+
readonly chain?: string | undefined;
|
|
3634
|
+
readonly expiration?: {
|
|
3635
|
+
readonly $kind: "None";
|
|
3636
|
+
readonly None: true;
|
|
3637
|
+
} | {
|
|
3638
|
+
readonly $kind: "Epoch";
|
|
3639
|
+
readonly Epoch: bigint;
|
|
3640
|
+
} | {
|
|
3641
|
+
readonly $kind: "ValidDuring";
|
|
3642
|
+
readonly ValidDuring: {
|
|
3643
|
+
readonly minEpoch: bigint | null;
|
|
3644
|
+
readonly maxEpoch: bigint | null;
|
|
3645
|
+
readonly minTimestamp: bigint | null;
|
|
3646
|
+
readonly maxTimestamp: bigint | null;
|
|
3647
|
+
readonly chain: string;
|
|
3648
|
+
readonly nonce: number;
|
|
3649
|
+
};
|
|
3650
|
+
} | {
|
|
3651
|
+
readonly $kind: "Validity";
|
|
3652
|
+
readonly Validity: {
|
|
3653
|
+
readonly allowedProposers: {
|
|
3654
|
+
readonly epoch: bigint;
|
|
3655
|
+
readonly proposers: readonly number[];
|
|
3656
|
+
} | null;
|
|
3657
|
+
readonly minEpoch: bigint | null;
|
|
3658
|
+
readonly maxEpoch: bigint | null;
|
|
3659
|
+
readonly minTimestamp: bigint | null;
|
|
3660
|
+
readonly maxTimestamp: bigint | null;
|
|
3661
|
+
readonly chain: string;
|
|
3662
|
+
readonly nonce: number;
|
|
3663
|
+
};
|
|
3664
|
+
} | undefined;
|
|
3665
|
+
};
|
|
3666
|
+
readonly signedAt: import("effect/DateTime").Utc;
|
|
3667
|
+
} | {
|
|
3668
|
+
readonly at: import("effect/DateTime").Utc;
|
|
3669
|
+
readonly digest: Digest;
|
|
3670
|
+
readonly _tag: "Executed";
|
|
3671
|
+
readonly checkpoint?: bigint | undefined;
|
|
3672
|
+
} | {
|
|
3673
|
+
readonly _tag: "Failed";
|
|
3674
|
+
readonly digest: Digest;
|
|
3675
|
+
readonly reason: {
|
|
3676
|
+
readonly $kind: "MoveAbort";
|
|
3677
|
+
readonly MoveAbort: {
|
|
3678
|
+
readonly abortCode: bigint;
|
|
3679
|
+
readonly location?: {
|
|
3680
|
+
readonly function?: number | undefined;
|
|
3681
|
+
readonly package?: string | undefined;
|
|
3682
|
+
readonly module?: string | undefined;
|
|
3683
|
+
readonly functionName?: string | undefined;
|
|
3684
|
+
readonly instruction?: number | undefined;
|
|
3685
|
+
} | undefined;
|
|
3686
|
+
readonly cleverError?: {
|
|
3687
|
+
readonly value?: string | undefined;
|
|
3688
|
+
readonly errorCode?: number | undefined;
|
|
3689
|
+
readonly lineNumber?: number | undefined;
|
|
3690
|
+
readonly constantName?: string | undefined;
|
|
3691
|
+
readonly constantType?: string | undefined;
|
|
3692
|
+
} | undefined;
|
|
3693
|
+
};
|
|
3694
|
+
} | {
|
|
3695
|
+
readonly $kind: "SizeError";
|
|
3696
|
+
readonly SizeError: {
|
|
3697
|
+
readonly name: string;
|
|
3698
|
+
readonly size: number;
|
|
3699
|
+
readonly maxSize: number;
|
|
3700
|
+
};
|
|
3701
|
+
} | {
|
|
3702
|
+
readonly $kind: "CommandArgumentError";
|
|
3703
|
+
readonly CommandArgumentError: {
|
|
3704
|
+
readonly argument: number;
|
|
3705
|
+
readonly name: string;
|
|
3706
|
+
};
|
|
3707
|
+
} | {
|
|
3708
|
+
readonly $kind: "TypeArgumentError";
|
|
3709
|
+
readonly TypeArgumentError: {
|
|
3710
|
+
readonly typeArgument: number;
|
|
3711
|
+
readonly name: string;
|
|
3712
|
+
};
|
|
3713
|
+
} | {
|
|
3714
|
+
readonly $kind: "PackageUpgradeError";
|
|
3715
|
+
readonly PackageUpgradeError: {
|
|
3716
|
+
readonly name: string;
|
|
3717
|
+
readonly digest?: string | undefined;
|
|
3718
|
+
readonly packageId?: string | undefined;
|
|
3719
|
+
};
|
|
3720
|
+
} | {
|
|
3721
|
+
readonly $kind: "IndexError";
|
|
3722
|
+
readonly IndexError: {
|
|
3723
|
+
readonly index?: number | undefined;
|
|
3724
|
+
readonly subresult?: number | undefined;
|
|
3725
|
+
};
|
|
3726
|
+
} | {
|
|
3727
|
+
readonly $kind: "CoinDenyListError";
|
|
3728
|
+
readonly CoinDenyListError: {
|
|
3729
|
+
readonly coinType: string;
|
|
3730
|
+
readonly name: string;
|
|
3731
|
+
readonly address?: string | undefined;
|
|
3732
|
+
};
|
|
3733
|
+
} | {
|
|
3734
|
+
readonly $kind: "CongestedObjects";
|
|
3735
|
+
readonly CongestedObjects: {
|
|
3736
|
+
readonly name: string;
|
|
3737
|
+
readonly objects: readonly string[];
|
|
3738
|
+
};
|
|
3739
|
+
} | {
|
|
3740
|
+
readonly $kind: "ObjectIdError";
|
|
3741
|
+
readonly ObjectIdError: {
|
|
3742
|
+
readonly objectId: string;
|
|
3743
|
+
readonly name?: string | undefined;
|
|
3744
|
+
};
|
|
3745
|
+
} | {
|
|
3746
|
+
readonly $kind: "Unknown";
|
|
3747
|
+
};
|
|
3748
|
+
readonly at: import("effect/DateTime").Utc;
|
|
3749
|
+
}) => value is {
|
|
3750
|
+
readonly _tag: "Unknown";
|
|
3751
|
+
readonly digest: Digest;
|
|
3752
|
+
readonly signed: {
|
|
3753
|
+
readonly digest: Digest;
|
|
3754
|
+
readonly sender: SuiAddress;
|
|
3755
|
+
readonly signatures: readonly Signature[];
|
|
3756
|
+
readonly bytes: Uint8Array<ArrayBufferLike>;
|
|
3757
|
+
readonly chain?: string | undefined;
|
|
3758
|
+
readonly expiration?: {
|
|
3759
|
+
readonly $kind: "None";
|
|
3760
|
+
readonly None: true;
|
|
3761
|
+
} | {
|
|
3762
|
+
readonly $kind: "Epoch";
|
|
3763
|
+
readonly Epoch: bigint;
|
|
3764
|
+
} | {
|
|
3765
|
+
readonly $kind: "ValidDuring";
|
|
3766
|
+
readonly ValidDuring: {
|
|
3767
|
+
readonly minEpoch: bigint | null;
|
|
3768
|
+
readonly maxEpoch: bigint | null;
|
|
3769
|
+
readonly minTimestamp: bigint | null;
|
|
3770
|
+
readonly maxTimestamp: bigint | null;
|
|
3771
|
+
readonly chain: string;
|
|
3772
|
+
readonly nonce: number;
|
|
3773
|
+
};
|
|
3774
|
+
} | {
|
|
3775
|
+
readonly $kind: "Validity";
|
|
3776
|
+
readonly Validity: {
|
|
3777
|
+
readonly allowedProposers: {
|
|
3778
|
+
readonly epoch: bigint;
|
|
3779
|
+
readonly proposers: readonly number[];
|
|
3780
|
+
} | null;
|
|
3781
|
+
readonly minEpoch: bigint | null;
|
|
3782
|
+
readonly maxEpoch: bigint | null;
|
|
3783
|
+
readonly minTimestamp: bigint | null;
|
|
3784
|
+
readonly maxTimestamp: bigint | null;
|
|
3785
|
+
readonly chain: string;
|
|
3786
|
+
readonly nonce: number;
|
|
3787
|
+
};
|
|
3788
|
+
} | undefined;
|
|
3789
|
+
};
|
|
3790
|
+
readonly lastError: string;
|
|
3791
|
+
readonly attempts: number;
|
|
3792
|
+
readonly at: import("effect/DateTime").Utc;
|
|
3793
|
+
} | {
|
|
3794
|
+
readonly _tag: "Signed";
|
|
3795
|
+
readonly digest: Digest;
|
|
3796
|
+
readonly signed: {
|
|
3797
|
+
readonly digest: Digest;
|
|
3798
|
+
readonly sender: SuiAddress;
|
|
3799
|
+
readonly signatures: readonly Signature[];
|
|
3800
|
+
readonly bytes: Uint8Array<ArrayBufferLike>;
|
|
3801
|
+
readonly chain?: string | undefined;
|
|
3802
|
+
readonly expiration?: {
|
|
3803
|
+
readonly $kind: "None";
|
|
3804
|
+
readonly None: true;
|
|
3805
|
+
} | {
|
|
3806
|
+
readonly $kind: "Epoch";
|
|
3807
|
+
readonly Epoch: bigint;
|
|
3808
|
+
} | {
|
|
3809
|
+
readonly $kind: "ValidDuring";
|
|
3810
|
+
readonly ValidDuring: {
|
|
3811
|
+
readonly minEpoch: bigint | null;
|
|
3812
|
+
readonly maxEpoch: bigint | null;
|
|
3813
|
+
readonly minTimestamp: bigint | null;
|
|
3814
|
+
readonly maxTimestamp: bigint | null;
|
|
3815
|
+
readonly chain: string;
|
|
3816
|
+
readonly nonce: number;
|
|
3817
|
+
};
|
|
3818
|
+
} | {
|
|
3819
|
+
readonly $kind: "Validity";
|
|
3820
|
+
readonly Validity: {
|
|
3821
|
+
readonly allowedProposers: {
|
|
3822
|
+
readonly epoch: bigint;
|
|
3823
|
+
readonly proposers: readonly number[];
|
|
3824
|
+
} | null;
|
|
3825
|
+
readonly minEpoch: bigint | null;
|
|
3826
|
+
readonly maxEpoch: bigint | null;
|
|
3827
|
+
readonly minTimestamp: bigint | null;
|
|
3828
|
+
readonly maxTimestamp: bigint | null;
|
|
3829
|
+
readonly chain: string;
|
|
3830
|
+
readonly nonce: number;
|
|
3831
|
+
};
|
|
3832
|
+
} | undefined;
|
|
3833
|
+
};
|
|
3834
|
+
readonly signedAt: import("effect/DateTime").Utc;
|
|
3835
|
+
}
|
|
3836
|
+
```
|
|
3837
|
+
|
|
3838
|
+
Whether this entry is still waiting for an answer, and therefore something
|
|
3839
|
+
`Tx.reconcileAll` has work to do about.
|
|
3840
|
+
|
|
3841
|
+
**Never fails.**
|
|
3842
|
+
|
|
3843
|
+
### `maxEpochOf` (const)
|
|
3844
|
+
|
|
3845
|
+
Re-exported from `@unconfirmed/sui-effect`.
|
|
3846
|
+
|
|
3847
|
+
### `maxTimestampMsOf` (const)
|
|
3848
|
+
|
|
3849
|
+
Re-exported from `@unconfirmed/sui-effect`.
|
|
3850
|
+
|
|
3851
|
+
### `reconcile` (const)
|
|
3852
|
+
|
|
3853
|
+
```ts
|
|
3854
|
+
declare const reconcile: (input: ReconcileInput) => Effect.Effect<Executed, TransportError | ExecutionFailed | SubmissionUnknown | NotApplied, Sui>
|
|
3855
|
+
```
|
|
3856
|
+
|
|
3857
|
+
Finds out what happened to a transaction that was sent but never answered
|
|
3858
|
+
for.
|
|
3859
|
+
|
|
3860
|
+
A transaction the node knows is `Executed`, or `ExecutionFailed` when it
|
|
3861
|
+
applied and aborted. A transaction the node does not know is only ever
|
|
3862
|
+
`NotApplied` on evidence, and there are exactly two kinds:
|
|
3863
|
+
|
|
3864
|
+
- `"expired"`, under an **ordered and repeated** rule, because a closed
|
|
3865
|
+
expiration window proves only that the bytes cannot execute *later*, not
|
|
3866
|
+
that they did not execute *earlier*, and a transaction can execute between
|
|
3867
|
+
a lookup and an expiry check. So: the window must be observed closed, then
|
|
3868
|
+
`getTransaction` must miss, then — after `SubmitConfig.reconcileRecheck`
|
|
3869
|
+
(two seconds by default, through the `Clock`) — both must hold again. Any
|
|
3870
|
+
other order, or a single observation, is `SubmissionUnknown`. Set
|
|
3871
|
+
`SubmitConfig.expiryEvidence: "never"` to disable the rule entirely, which
|
|
3872
|
+
is what a deployment behind a mixed-node load balancer wants. The residual
|
|
3873
|
+
risk is a node whose transaction index lags its epoch view;
|
|
3874
|
+
- `"inputConsumed"` when the transaction that last mutated a pinned object is
|
|
3875
|
+
a **different** one **and its own effects report `inputVersion` equal to
|
|
3876
|
+
the version these bytes pinned**, so those exact bytes can never execute
|
|
3877
|
+
again.
|
|
3878
|
+
|
|
3879
|
+
An input that merely advanced is not evidence: the transaction being
|
|
3880
|
+
reconciled is itself the likeliest thing to have advanced it, and calling
|
|
3881
|
+
that `NotApplied` would tell the documented retry idiom to execute the
|
|
3882
|
+
caller's intent a second time. When the live object names *our* digest the
|
|
3883
|
+
transaction applied and `getTransaction` is asked again; when the consuming
|
|
3884
|
+
transaction took a **later** version than ours — which is the common case,
|
|
3885
|
+
because Sui stamps every output with the transaction's Lamport version and
|
|
3886
|
+
the object "one version on" from ours usually never existed — nothing is
|
|
3887
|
+
proven and the answer is `SubmissionUnknown`, which carries the bytes so a
|
|
3888
|
+
later process, or a person, can settle it. Every pinned reference is tried
|
|
3889
|
+
before that answer is given.
|
|
3890
|
+
|
|
3891
|
+
**Chain identity is checked before anything is asked.** Bytes built for one
|
|
3892
|
+
chain must never be declared expired by another chain's epoch, which a
|
|
3893
|
+
process-wide journal holding two networks' submissions makes easy to do. A
|
|
3894
|
+
mismatch is `SubmissionUnknown` naming both chains.
|
|
3895
|
+
|
|
3896
|
+
**No `TransportError` escapes.** A recovery read that fails says nothing
|
|
3897
|
+
about whether the transaction applied, and `SuiError.outcome` puts
|
|
3898
|
+
`TransportError` on `"not_applied"` — which would tell a wrapper to retry a
|
|
3899
|
+
submission whose outcome is genuinely unknown. Every read failure here
|
|
3900
|
+
becomes `SubmissionUnknown` carrying the digest, the bytes and the cause. The
|
|
3901
|
+
tag stays in the signature so the union does not shrink under callers.
|
|
3902
|
+
|
|
3903
|
+
Given only a `Digest` there can be no evidence, so an unknown transaction is
|
|
3904
|
+
always `SubmissionUnknown`. Pass the `Signed` bytes (or the
|
|
3905
|
+
`SubmissionUnknown` that carries them) to get the evidence rules.
|
|
3906
|
+
|
|
3907
|
+
**Fails with: `ExecutionFailed`, `NotApplied`, `SubmissionUnknown`, `TransportError`.**
|
|
3908
|
+
|
|
3909
|
+
### `reconcileAll` (const)
|
|
3910
|
+
|
|
3911
|
+
```ts
|
|
3912
|
+
declare const reconcileAll: () => Effect.Effect<readonly Reconciled[], TransportError | JournalError, Sui>
|
|
3913
|
+
```
|
|
3914
|
+
|
|
3915
|
+
Settles every unresolved entry in the journal: the explicit startup call a
|
|
3916
|
+
long-lived application makes after building a durable `Journal`.
|
|
3917
|
+
|
|
3918
|
+
Nothing here fails per entry: each one settles to an `Executed`, an
|
|
3919
|
+
`ExecutionFailed`, a `NotApplied` or a `SubmissionUnknown`, in the order the
|
|
3920
|
+
journal listed them, and the journal is updated to match. Every settled entry
|
|
3921
|
+
gets the same evidence rules `Tx.reconcile` applies — the ordered, repeated
|
|
3922
|
+
expiry rule, the chain-identity guard and the versioned consumer check — so a
|
|
3923
|
+
startup never reports a transaction that applied as `NotApplied`, and a
|
|
3924
|
+
recovery read that fails becomes that entry's `SubmissionUnknown` rather than
|
|
3925
|
+
escaping as a `TransportError` the taxonomy would call "not applied".
|
|
3926
|
+
|
|
3927
|
+
The whole call fails only if the journal itself cannot be **read**: a write
|
|
3928
|
+
that fails after an entry has been settled is logged and the answer stands,
|
|
3929
|
+
the same rule `Tx.submit` follows.
|
|
3930
|
+
|
|
3931
|
+
**Fails with: `JournalError`, `TransportError`.**
|
|
3932
|
+
|
|
3933
|
+
### `remote` (const)
|
|
3934
|
+
|
|
3935
|
+
```ts
|
|
3936
|
+
declare const remote: (signer: RemoteSigner) => Signer
|
|
3937
|
+
```
|
|
3938
|
+
|
|
3939
|
+
Builds a signer around something that signs elsewhere: a KMS, a wallet, a
|
|
3940
|
+
hardware device, another process.
|
|
3941
|
+
|
|
3942
|
+
The returned signer decodes whatever the remote produced, so a malformed
|
|
3943
|
+
signature is a `SigningError` rather than a surprise at execution. When
|
|
3944
|
+
`signPersonalMessage` is not given, asking for one fails with `SigningError`
|
|
3945
|
+
instead of pretending.
|
|
3946
|
+
|
|
3947
|
+
**Never fails.**
|
|
3948
|
+
|
|
3949
|
+
### `run` (const)
|
|
3950
|
+
|
|
3951
|
+
```ts
|
|
3952
|
+
declare const run: (recipe: Transaction | Recipe, opts: {
|
|
3953
|
+
readonly signer: Signer;
|
|
3954
|
+
readonly gasOwner?: SuiAddress;
|
|
3955
|
+
/**
|
|
3956
|
+
* The gas owner's signer, for a sponsored transaction. Required whenever
|
|
3957
|
+
* the bytes name a gas owner that is not the sender.
|
|
3958
|
+
*/
|
|
3959
|
+
readonly sponsor?: Signer;
|
|
3960
|
+
}) => Effect.Effect<Executed, TransportError | SimulationFailed | ExecutionFailed | SubmissionUnknown | NotApplied | SigningError | BuildError | PolicyDenied | JournalError, Sui>
|
|
3961
|
+
```
|
|
3962
|
+
|
|
3963
|
+
Build, preflight, sign and submit, with the sender lock held throughout.
|
|
3964
|
+
|
|
3965
|
+
Gas coins are chosen during build, so two transactions from one address that
|
|
3966
|
+
overlap can pick the same coin and one of them will fail on chain. `Tx.run`
|
|
3967
|
+
holds the sender lock from build through submit, which is the whole reason to
|
|
3968
|
+
prefer it over calling the steps separately.
|
|
3969
|
+
|
|
3970
|
+
The address that matters is the one whose coins are being spent, which is the
|
|
3971
|
+
**gas owner** when there is one: two sponsored runs for different senders
|
|
3972
|
+
paid by one sponsor are exactly the case that picks the same coin twice. When
|
|
3973
|
+
sender and gas owner differ, both locks are held, in ascending address order
|
|
3974
|
+
— a fixed order, so two runs that each need the same pair cannot deadlock by
|
|
3975
|
+
taking them the other way round.
|
|
3976
|
+
|
|
3977
|
+
When `SubmitConfig.preflight` is set it costs one extra simulate, and is
|
|
3978
|
+
where spend limits and target policies refuse a transaction before anything
|
|
3979
|
+
is signed.
|
|
3980
|
+
|
|
3981
|
+
**A sponsored run needs both signatures.** A transaction whose gas owner is
|
|
3982
|
+
not its sender is signed by *both* parties; one signature is bytes a
|
|
3983
|
+
validator rejects. So when `opts.gasOwner` differs from the signer's address
|
|
3984
|
+
— or when the recipe itself set a different gas owner, which `Tx.sponsored`
|
|
3985
|
+
does — `opts.sponsor` is required and co-signs the same bytes. Without it
|
|
3986
|
+
`Tx.run` fails with `SigningError` naming the address whose signature is
|
|
3987
|
+
missing, before anything is built when the gas owner was given as an option
|
|
3988
|
+
and immediately after the build when it came out of the recipe. Use the
|
|
3989
|
+
explicit lifecycle (`Tx.build`, `Tx.sign`, `Tx.cosign`, `Tx.submit`) when the
|
|
3990
|
+
two parties cannot both sign in one process.
|
|
3991
|
+
|
|
3992
|
+
**Fails with: `BuildError`, `SimulationFailed`, `PolicyDenied`, `SigningError`, `ExecutionFailed`, `NotApplied`, `SubmissionUnknown`, `JournalError`, `TransportError` (from the build reads; once bytes are sent, transport failures become `SubmissionUnknown`).**
|
|
3993
|
+
|
|
3994
|
+
### `sign` (const)
|
|
3995
|
+
|
|
3996
|
+
```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>
|
|
4072
|
+
```
|
|
4073
|
+
|
|
4074
|
+
Signs built bytes.
|
|
4075
|
+
|
|
4076
|
+
The signer's address must be the transaction's sender or, for a sponsored
|
|
4077
|
+
transaction, its gas owner; anything else is a `SigningError` rather than a
|
|
4078
|
+
rejection at execution time. A `Signer.remote` therefore has to report the
|
|
4079
|
+
address it signs as truthfully.
|
|
4080
|
+
|
|
4081
|
+
**Fails with: `SigningError`.**
|
|
4082
|
+
|
|
4083
|
+
### `sponsored` (const)
|
|
4084
|
+
|
|
4085
|
+
```ts
|
|
4086
|
+
declare const sponsored: (opts: {
|
|
4087
|
+
readonly sender: SuiAddress;
|
|
4088
|
+
readonly gasOwner: SuiAddress;
|
|
4089
|
+
}) => (recipe: Recipe) => Recipe
|
|
4090
|
+
```
|
|
4091
|
+
|
|
4092
|
+
Turns a recipe into a sponsored one: the sender owns the transaction, the
|
|
4093
|
+
gas owner pays, and gas comes from the sponsor's address balance rather than
|
|
4094
|
+
from coin objects, so the two parties never have to agree on a gas coin and
|
|
4095
|
+
can sign in either order.
|
|
4096
|
+
|
|
4097
|
+
Never fails; the recipe it returns throws only if the recipe it wraps does,
|
|
4098
|
+
which `Tx.build` reports as `BuildError`.
|
|
4099
|
+
|
|
4100
|
+
### `submit` (const)
|
|
4080
4101
|
|
|
4081
4102
|
```ts
|
|
4082
|
-
declare const
|
|
4103
|
+
declare const submit: (signed: Signed) => Effect.Effect<Executed, SubmitError, Sui>
|
|
4083
4104
|
```
|
|
4084
4105
|
|
|
4085
|
-
|
|
4106
|
+
Sends signed bytes, and does not stop caring until it knows what happened.
|
|
4107
|
+
|
|
4108
|
+
Before the first `executeTransaction` it writes a `Signed` journal entry, so
|
|
4109
|
+
a process that dies mid-flight leaves a record of bytes that may be on the
|
|
4110
|
+
wire. A retryable transport failure or a timeout re-sends the identical bytes
|
|
4111
|
+
on `SubmitConfig.resubmit`; nothing is ever rebuilt, so a retry can only land
|
|
4112
|
+
the transaction that was already signed. When the retries run out it runs
|
|
4113
|
+
`Tx.reconcile`, which either finds the transaction, proves it never applied,
|
|
4114
|
+
or says it does not know.
|
|
4115
|
+
|
|
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.
|
|
4119
|
+
|
|
4120
|
+
`JournalError` can only come from the `Signed` write, before anything has
|
|
4121
|
+
been sent. Once the network has answered, a journal write that fails is
|
|
4122
|
+
logged with `Effect.logError` and the answer stands, because "the journal is
|
|
4123
|
+
broken" is not a thing a caller can act on and reporting it in place of a
|
|
4124
|
+
charged `ExecutionFailed` would invite a second submission.
|
|
4125
|
+
|
|
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).**
|
|
4086
4127
|
|
|
4087
4128
|
## `@unconfirmed/sui-effect/journal`
|
|
4088
4129
|
|
|
@@ -4167,7 +4208,7 @@ Every member fails with `JournalError` and nothing else.
|
|
|
4167
4208
|
## `@unconfirmed/sui-effect/extension`
|
|
4168
4209
|
|
|
4169
4210
|
|
|
4170
|
-
|
|
4211
|
+
7 exported symbols.
|
|
4171
4212
|
|
|
4172
4213
|
### `ExtensionFace` (interface)
|
|
4173
4214
|
|
|
@@ -4196,68 +4237,23 @@ What every registration carries besides the service's own members, under
|
|
|
4196
4237
|
`$`-prefixed names so an extension is free to call a member `ready` or
|
|
4197
4238
|
`dispose` itself.
|
|
4198
4239
|
|
|
4199
|
-
### `
|
|
4240
|
+
### `Leaf` (type)
|
|
4200
4241
|
|
|
4201
4242
|
```ts
|
|
4202
|
-
|
|
4243
|
+
export type Leaf<T> = T & LeafBrand;
|
|
4203
4244
|
```
|
|
4204
4245
|
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
`register(client)` does no work by default: the `ManagedRuntime` over
|
|
4209
|
-
`SuiCore.layerFromClient(client)`, a `Sui` for the client's effective chain
|
|
4210
|
-
id and the extension's own layer is built on the first call and shared by
|
|
4211
|
-
every call after it. The `Sui` and `SuiCore` underneath are shared with every
|
|
4212
|
-
other registration on that client that names the same chain id — one
|
|
4213
|
-
transport, one chain identity and one sender-lock map. A rejection carries
|
|
4214
|
-
the original tagged error instance, so a Promise consumer can still switch on
|
|
4215
|
-
`_tag`.
|
|
4216
|
-
|
|
4217
|
-
`name` is generic in a string literal, so `client.escrow` is a property of
|
|
4218
|
-
the extended client's type and not an index lookup: no cast, and no
|
|
4219
|
-
`| undefined` under `noUncheckedIndexedAccess`.
|
|
4220
|
-
|
|
4221
|
-
**The window before the runtime exists.** Until then nothing knows what a
|
|
4222
|
-
member *is*, so a member read off the face is a placeholder. An `Effect` or
|
|
4223
|
-
`Stream` member behaves exactly as its type says — the call returns a
|
|
4224
|
-
Promise, the iteration works — because that is what the face promises for
|
|
4225
|
-
them anyway. A **synchronous** member does not: `PromiseFace` types a recipe
|
|
4226
|
-
builder as returning a `Recipe` and a plain value as that value, and a
|
|
4227
|
-
placeholder has neither. So a synchronous member used in that window fails
|
|
4228
|
-
with `ExtensionNotReady` naming itself, rather than quietly handing back a
|
|
4229
|
-
Promise where the type says `Recipe` — which is a bug that only shows up on
|
|
4230
|
-
the *second* call, when the member has become real. Two cures:
|
|
4231
|
-
|
|
4232
|
-
- `await client.<name>.$ready()` once after `$extend`, which builds the
|
|
4233
|
-
runtime and resolves the service; every member is real from then on;
|
|
4234
|
-
- register with `warm`, which does the same synchronously inside `register`,
|
|
4235
|
-
for a layer that needs no network.
|
|
4236
|
-
|
|
4237
|
-
Two lifetimes worth knowing:
|
|
4238
|
-
|
|
4239
|
-
- **`$dispose()` is not final.** It releases everything the layer acquired
|
|
4240
|
-
and forgets the runtime; the next call builds a fresh one. That is what a
|
|
4241
|
-
long-lived page wants (a disposed extension is usable again after a
|
|
4242
|
-
reconnect) and it does mean a `$dispose()` that races an in-flight call can
|
|
4243
|
-
leave the caller's Promise rejected while a new runtime starts behind it.
|
|
4244
|
-
Dispose when the consumer is done, not between calls. `dispose()` is the
|
|
4245
|
-
same function under the name it had first.
|
|
4246
|
-
- **Each `register` is independent.** Registering the same extension on two
|
|
4247
|
-
clients — or twice on one — gives two runtimes, two layer builds and two
|
|
4248
|
-
copies of whatever the layer holds (a cache, a connection). Register once
|
|
4249
|
-
per client and keep the extended client.
|
|
4246
|
+
`T`, marked as a leaf of the Promise face: it is handed to a Promise consumer
|
|
4247
|
+
exactly as the Effect service holds it.
|
|
4250
4248
|
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
chain identifier and none was given. Otherwise the layer's own failures
|
|
4254
|
-
surface as rejections of the first call that needs it.
|
|
4249
|
+
Declare the member as `Leaf<MyClass>` and build it with `leaf`. A
|
|
4250
|
+
`Leaf<T>` is still a `T` — every consumer of the Effect face is unaffected.
|
|
4255
4251
|
|
|
4256
4252
|
### `PromiseFace` (type)
|
|
4257
4253
|
|
|
4258
4254
|
```ts
|
|
4259
4255
|
export type PromiseFace<S> = {
|
|
4260
|
-
readonly [K in keyof S]: S[K] extends Stream.Stream<infer A, infer _E, infer _R> ? AsyncIterable<A> : S[K] extends Effect.Effect<infer A, infer _E2, infer _R2> ? () => Promise<A> : S[K] extends (...args: infer Args) => Stream.Stream<infer A, infer _E3, infer _R3> ? (...args: Args) => AsyncIterable<A> : S[K] extends (...args: infer Args) => Effect.Effect<infer A, infer _E4, infer _R4> ? (...args: Args) => Promise<A> : S[K] extends
|
|
4256
|
+
readonly [K in keyof S]: S[K] extends Stream.Stream<infer A, infer _E, infer _R> ? AsyncIterable<A> : S[K] extends Effect.Effect<infer A, infer _E2, infer _R2> ? () => Promise<A> : S[K] extends (...args: infer Args) => Stream.Stream<infer A, infer _E3, infer _R3> ? (...args: Args) => AsyncIterable<A> : S[K] extends (...args: infer Args) => Effect.Effect<infer A, infer _E4, infer _R4> ? (...args: Args) => Promise<A> : S[K] extends FaceLeaf ? S[K] : S[K] extends (...args: ReadonlyArray<never>) => unknown ? S[K] : S[K] extends object ? PromiseFace<S[K]> : S[K];
|
|
4261
4257
|
};
|
|
4262
4258
|
```
|
|
4263
4259
|
|
|
@@ -4265,8 +4261,8 @@ The Promise face of a service interface.
|
|
|
4265
4261
|
|
|
4266
4262
|
An `Effect` member becomes a zero-argument method returning a Promise, a
|
|
4267
4263
|
function returning an `Effect` keeps its arguments and returns a Promise, a
|
|
4268
|
-
`Stream` member becomes an `AsyncIterable`, a nested
|
|
4269
|
-
|
|
4264
|
+
`Stream` member becomes an `AsyncIterable`, a nested object of members is
|
|
4265
|
+
mapped the same way (platform SDKs namespace their surface as
|
|
4270
4266
|
`client.miso.protocol.*`), and anything else passes through untouched.
|
|
4271
4267
|
|
|
4272
4268
|
**A synchronous member stays synchronous**: a recipe builder
|
|
@@ -4275,11 +4271,22 @@ value is still that value. The type says so and, once the runtime exists, the
|
|
|
4275
4271
|
runtime agrees — see `warm` and `$ready` on `fromService` for the
|
|
4276
4272
|
window before it does.
|
|
4277
4273
|
|
|
4278
|
-
**
|
|
4279
|
-
|
|
4280
|
-
a `Date`,
|
|
4281
|
-
|
|
4282
|
-
|
|
4274
|
+
**The recursion is by type, not by declaration style.** Every object-typed
|
|
4275
|
+
member that is not a function, an array, an `Effect`, a `Stream`, a
|
|
4276
|
+
`Uint8Array`, a `Date`, a `Promise`, a BCS codec (both `parse` and
|
|
4277
|
+
`serialize`) or a `Leaf` is mapped as a namespace — an `interface`
|
|
4278
|
+
exactly like a type alias. Before 0.1.1 the bound was
|
|
4279
|
+
`Record<string, unknown>`, which an interface is not assignable to, so an
|
|
4280
|
+
interface-typed namespace (`readonly protocol: ProtocolService`) kept its
|
|
4281
|
+
`Effect` members **in the type** while the runtime mapped them to Promises.
|
|
4282
|
+
The type lied; it no longer does.
|
|
4283
|
+
|
|
4284
|
+
**A class instance that is a value, not a namespace, is marked.** The runtime
|
|
4285
|
+
maps plain-prototype objects and passes class instances through, so a class
|
|
4286
|
+
whose methods return `Effect`s would be typed as mapped and arrive unmapped.
|
|
4287
|
+
Declare such a member `Leaf<T>` and build it with `leaf`: the type and
|
|
4288
|
+
the runtime then agree that it is a value. `Uint8Array`, `Date`, `Promise`,
|
|
4289
|
+
arrays and BCS codecs need no marker.
|
|
4283
4290
|
|
|
4284
4291
|
**A plain-object *value* member is the one place the two faces cannot agree.**
|
|
4285
4292
|
`{ packageId: "0x…" }` is indistinguishable from a namespace of members, so
|
|
@@ -4296,10 +4303,13 @@ through an `Effect` member instead.
|
|
|
4296
4303
|
```ts
|
|
4297
4304
|
declare const SuiExtension: {
|
|
4298
4305
|
readonly fromService: <Self, Shape, E, const Name extends string>(service: Context.Key<Self, Shape>, options: SuiExtensionOptions<Self, E, Name>) => SuiClientRegistration<ClientWithCoreApi, Name, PromiseFace<Shape> & ExtensionFace>;
|
|
4306
|
+
readonly leaf: <T>(value: T) => Leaf<T>;
|
|
4299
4307
|
}
|
|
4300
4308
|
```
|
|
4301
4309
|
|
|
4302
|
-
The namespace the spec spells: `SuiExtension.fromService(...)
|
|
4310
|
+
The namespace the spec spells: `SuiExtension.fromService(...)`, plus the
|
|
4311
|
+
leaf marker a service uses to say "this member is a value, not a namespace"
|
|
4312
|
+
(`SuiExtension.leaf(value)`, typed `SuiExtension.Leaf<T>`).
|
|
4303
4313
|
|
|
4304
4314
|
### `SuiExtensionOptions` (interface)
|
|
4305
4315
|
|
|
@@ -4344,6 +4354,16 @@ export interface SuiExtensionOptions<Self, E, Name extends string = string> {
|
|
|
4344
4354
|
* registers. Every member is then the real thing immediately, and
|
|
4345
4355
|
* `$ready()` has nothing left to do.
|
|
4346
4356
|
*
|
|
4357
|
+
* **Every failure of the layer becomes a synchronous throw.** The whole
|
|
4358
|
+
* layer is built inside `register`, so a missing deployment, a bad
|
|
4359
|
+
* configuration, a `NetworkMismatch` — anything the layer can fail with —
|
|
4360
|
+
* comes out of `client.$extend(...)` as a thrown value rather than as the
|
|
4361
|
+
* rejection of a first call. That is the trade `warm` makes, and a consumer
|
|
4362
|
+
* has to catch it where it registers.
|
|
4363
|
+
*
|
|
4364
|
+
* After `$dispose()`, the next use re-runs this same build rather than
|
|
4365
|
+
* degrading to a cold registration.
|
|
4366
|
+
*
|
|
4347
4367
|
* Two conditions, both enforced:
|
|
4348
4368
|
*
|
|
4349
4369
|
* - **The layer must not perform an asynchronous step.** A layer that reads
|
|
@@ -4379,90 +4399,96 @@ export interface SuiExtensionOptions<Self, E, Name extends string = string> {
|
|
|
4379
4399
|
|
|
4380
4400
|
What `fromService` needs to know beyond the service key itself.
|
|
4381
4401
|
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
9 exported symbols.
|
|
4386
|
-
|
|
4387
|
-
### `exitCode` (const)
|
|
4402
|
+
### `fromService` (const)
|
|
4388
4403
|
|
|
4389
4404
|
```ts
|
|
4390
|
-
declare const
|
|
4405
|
+
declare const fromService: <Self, Shape, E, const Name extends string>(service: Context.Key<Self, Shape>, options: SuiExtensionOptions<Self, E, Name>) => SuiClientRegistration<ClientWithCoreApi, Name, PromiseFace<Shape> & ExtensionFace>
|
|
4391
4406
|
```
|
|
4392
4407
|
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
The axis is what a wrapper can act on: did the transaction apply (5, gas was
|
|
4396
|
-
charged, do not retry), is the outcome unknown (3, reconcile before doing
|
|
4397
|
-
anything else), or did nothing apply (4, safe to retry)? Configuration
|
|
4398
|
-
problems are 2 because no amount of retrying fixes them, a defect is 1, and
|
|
4399
|
-
an interrupt is 130 the way a shell expects — unless the journal says there
|
|
4400
|
-
is a submission outstanding, in which case it is 3, because a wrapper that
|
|
4401
|
-
sees 130 has no reason to go looking for one.
|
|
4408
|
+
Turns an Effect service into a `SuiClientRegistration` a Promise consumer
|
|
4409
|
+
passes to `client.$extend(...)`.
|
|
4402
4410
|
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4411
|
+
`register(client)` does no work by default: the `ManagedRuntime` over
|
|
4412
|
+
`SuiCore.layerFromClient(client)`, a `Sui` for the client's effective chain
|
|
4413
|
+
id and the extension's own layer is built on the first call and shared by
|
|
4414
|
+
every call after it. The `Sui` and `SuiCore` underneath are shared with every
|
|
4415
|
+
other registration on that client that names the same chain id — one
|
|
4416
|
+
transport, one chain identity and one sender-lock map. A rejection carries
|
|
4417
|
+
the original tagged error instance, so a Promise consumer can still switch on
|
|
4418
|
+
`_tag`.
|
|
4408
4419
|
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
value: 3 would tell a wrapper there is a transaction to reconcile, and an
|
|
4413
|
-
unrecognised error is not evidence that anything was ever sent. Extensions
|
|
4414
|
-
are told to declare `outcome` on every error precisely so their failures
|
|
4415
|
-
never land here.
|
|
4420
|
+
`name` is generic in a string literal, so `client.escrow` is a property of
|
|
4421
|
+
the extended client's type and not an index lookup: no cast, and no
|
|
4422
|
+
`| undefined` under `noUncheckedIndexedAccess`.
|
|
4416
4423
|
|
|
4417
|
-
**
|
|
4424
|
+
**The window before the runtime exists.** Until then nothing knows what a
|
|
4425
|
+
member *is*, so a member read off the face is a placeholder. An `Effect` or
|
|
4426
|
+
`Stream` member behaves exactly as its type says — the call returns a
|
|
4427
|
+
Promise, the iteration works — because that is what the face promises for
|
|
4428
|
+
them anyway. A **synchronous** member does not: `PromiseFace` types a recipe
|
|
4429
|
+
builder as returning a `Recipe` and a plain value as that value, and a
|
|
4430
|
+
placeholder has neither. So a synchronous member used in that window fails
|
|
4431
|
+
with `ExtensionNotReady` naming itself, rather than quietly handing back a
|
|
4432
|
+
Promise where the type says `Recipe` — which is a bug that only shows up on
|
|
4433
|
+
the *second* call, when the member has become real. Two cures:
|
|
4418
4434
|
|
|
4419
|
-
|
|
4435
|
+
- `await client.<name>.$ready()` once after `$extend`, which builds the
|
|
4436
|
+
runtime and resolves the service; every member is real from then on;
|
|
4437
|
+
- register with `warm`, which does the same synchronously inside `register`,
|
|
4438
|
+
for a layer that needs no network.
|
|
4420
4439
|
|
|
4421
|
-
|
|
4422
|
-
declare const readNetwork: Effect.Effect<string, Config.ConfigError>
|
|
4423
|
-
```
|
|
4440
|
+
Two lifetimes worth knowing:
|
|
4424
4441
|
|
|
4425
|
-
`
|
|
4442
|
+
- **`$dispose()` is not final.** It releases everything the layer acquired
|
|
4443
|
+
and forgets the runtime; the next call builds a fresh one — and a `warm`
|
|
4444
|
+
registration re-runs its **warm** build, with the same options, on the next
|
|
4445
|
+
use, so it does not silently degrade to cold with every synchronous member
|
|
4446
|
+
throwing `ExtensionNotReady`. That is what a
|
|
4447
|
+
long-lived page wants (a disposed extension is usable again after a
|
|
4448
|
+
reconnect) and it does mean a `$dispose()` that races an in-flight call can
|
|
4449
|
+
leave the caller's Promise rejected while a new runtime starts behind it.
|
|
4450
|
+
Dispose when the consumer is done, not between calls. `dispose()` is the
|
|
4451
|
+
same function under the name it had first.
|
|
4452
|
+
- **Each `register` is independent.** Registering the same extension on two
|
|
4453
|
+
clients — or twice on one — gives two runtimes, two layer builds and two
|
|
4454
|
+
copies of whatever the layer holds (a cache, a connection). Register once
|
|
4455
|
+
per client and keep the extended client.
|
|
4426
4456
|
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
`
|
|
4457
|
+
**A cold call is a real `Promise`.** The placeholder a member call returns
|
|
4458
|
+
before the runtime exists is a `Promise` subclass that also implements
|
|
4459
|
+
`Symbol.asyncIterator`, so `instanceof Promise` holds and `bun:test`'s
|
|
4460
|
+
`expect(...).rejects` recognises it. Its rejection is **pre-handled** (a
|
|
4461
|
+
no-op `catch` is attached at creation), so a cold call nobody awaits —
|
|
4462
|
+
`client.ext.doThing()` as a statement — cannot abort the process with an
|
|
4463
|
+
unhandled rejection; an `await` of it still throws `ExtensionNotReady`.
|
|
4431
4464
|
|
|
4432
|
-
|
|
4465
|
+
Never fails, except a `warm` registration. A warm registration builds the
|
|
4466
|
+
**whole layer** synchronously inside `register`, so **any** failure of that
|
|
4467
|
+
layer — a missing deployment for the network, a configuration error, a
|
|
4468
|
+
`NetworkMismatch`, not only an asynchronous step or an unknown chain
|
|
4469
|
+
identifier — is thrown synchronously out of `register`, which means out of
|
|
4470
|
+
`client.$extend(...)`. Catch it where you register. A lazy registration has
|
|
4471
|
+
nowhere to put such a failure at registration time, so it surfaces as the
|
|
4472
|
+
rejection of the first call that needs the layer.
|
|
4433
4473
|
|
|
4434
|
-
### `
|
|
4474
|
+
### `leaf` (const)
|
|
4435
4475
|
|
|
4436
4476
|
```ts
|
|
4437
|
-
declare const
|
|
4477
|
+
declare const leaf: <T>(value: T) => Leaf<T>
|
|
4438
4478
|
```
|
|
4439
4479
|
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
to stderr, and exits with `exitCode`.
|
|
4480
|
+
Marks a value as a `Leaf`: the Promise face passes it through untouched
|
|
4481
|
+
instead of walking into it.
|
|
4443
4482
|
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
calls cannot land in the script's output. A `SubmissionUnknown` additionally
|
|
4447
|
-
prints the base64 of the signed bytes and a line saying to reconcile, because
|
|
4448
|
-
those bytes are the durable record a script has.
|
|
4483
|
+
The registry is a `WeakSet`, so a frozen value can be marked and nothing is
|
|
4484
|
+
added to the value itself.
|
|
4449
4485
|
|
|
4450
|
-
|
|
4451
|
-
with still holds unresolved, which is the only record of bytes that may be on
|
|
4452
|
-
the wire when a script is killed — or fails — between signing and the answer.
|
|
4453
|
-
That count is also what decides a timeout (3 rather than 4) and an interrupt
|
|
4454
|
-
(3 rather than 130): an `Effect.timeout` around a submission interrupts it
|
|
4455
|
-
from the outside and never reaches `Tx.submit`'s own mapping.
|
|
4486
|
+
**Never fails.**
|
|
4456
4487
|
|
|
4457
|
-
|
|
4458
|
-
pressing Ctrl-C again while finalizers run is ignored, because the whole
|
|
4459
|
-
point of the first interrupt is to let those finalizers — the journal write
|
|
4460
|
-
that records what was sent, above all — complete. A script whose finalizers
|
|
4461
|
-
hang has to be killed with SIGKILL, which by construction no process can
|
|
4462
|
-
handle.
|
|
4488
|
+
## `@unconfirmed/sui-effect/script`
|
|
4463
4489
|
|
|
4464
|
-
|
|
4465
|
-
|
|
4490
|
+
|
|
4491
|
+
9 exported symbols.
|
|
4466
4492
|
|
|
4467
4493
|
### `Script` (class)
|
|
4468
4494
|
|
|
@@ -4588,7 +4614,87 @@ export interface SignalSource {
|
|
|
4588
4614
|
}
|
|
4589
4615
|
```
|
|
4590
4616
|
|
|
4591
|
-
The part of `process` `run` uses, so a test can stand in for it.
|
|
4617
|
+
The part of `process` `run` uses, so a test can stand in for it.
|
|
4618
|
+
|
|
4619
|
+
### `exitCode` (const)
|
|
4620
|
+
|
|
4621
|
+
```ts
|
|
4622
|
+
declare const exitCode: <A, E>(exit: Exit.Exit<A, E>, options?: ExitCodeOptions) => number
|
|
4623
|
+
```
|
|
4624
|
+
|
|
4625
|
+
The exit code one failure deserves.
|
|
4626
|
+
|
|
4627
|
+
The axis is what a wrapper can act on: did the transaction apply (5, gas was
|
|
4628
|
+
charged, do not retry), is the outcome unknown (3, reconcile before doing
|
|
4629
|
+
anything else), or did nothing apply (4, safe to retry)? Configuration
|
|
4630
|
+
problems are 2 because no amount of retrying fixes them, a defect is 1, and
|
|
4631
|
+
an interrupt is 130 the way a shell expects — unless the journal says there
|
|
4632
|
+
is a submission outstanding, in which case it is 3, because a wrapper that
|
|
4633
|
+
sees 130 has no reason to go looking for one.
|
|
4634
|
+
|
|
4635
|
+
An extension error that declares an `outcome` is honoured, so a downstream
|
|
4636
|
+
SDK's own failures land on the same axis. `SchemaError` — what Effect's own
|
|
4637
|
+
`Config.schema` and `Schema.decodeUnknownEffect` fail with — is exit 2 with
|
|
4638
|
+
`ConfigError`, because in a script it can only mean the input a person gave
|
|
4639
|
+
did not fit the schema, and no retry fixes that.
|
|
4640
|
+
|
|
4641
|
+
An error with a tag this library has never heard of and no `outcome` is
|
|
4642
|
+
*unclassified* and exits 1, the code that also means defect. It deliberately
|
|
4643
|
+
does not follow `SuiError.outcome`, which answers `"unknown"` for the same
|
|
4644
|
+
value: 3 would tell a wrapper there is a transaction to reconcile, and an
|
|
4645
|
+
unrecognised error is not evidence that anything was ever sent. Extensions
|
|
4646
|
+
are told to declare `outcome` on every error precisely so their failures
|
|
4647
|
+
never land here.
|
|
4648
|
+
|
|
4649
|
+
**Never fails.**
|
|
4650
|
+
|
|
4651
|
+
### `readNetwork` (const)
|
|
4652
|
+
|
|
4653
|
+
```ts
|
|
4654
|
+
declare const readNetwork: Effect.Effect<string, Config.ConfigError>
|
|
4655
|
+
```
|
|
4656
|
+
|
|
4657
|
+
`SUI_NETWORK`, with no default and with the mainnet gate.
|
|
4658
|
+
|
|
4659
|
+
There is no default network on purpose: a script that runs against whatever
|
|
4660
|
+
happened to be configured is how a test transaction reaches mainnet. And a
|
|
4661
|
+
script that means mainnet has to say so twice, in `SUI_NETWORK` and in
|
|
4662
|
+
`SUI_ALLOW_MAINNET=1`.
|
|
4663
|
+
|
|
4664
|
+
**Fails with: `ConfigError`.**
|
|
4665
|
+
|
|
4666
|
+
### `run` (const)
|
|
4667
|
+
|
|
4668
|
+
```ts
|
|
4669
|
+
declare const run: <A, E>(effect: Effect.Effect<A, E, Script | Sui | SuiCore>, options?: ScriptRunOptions) => Promise<number>
|
|
4670
|
+
```
|
|
4671
|
+
|
|
4672
|
+
Runs a script: builds `Script.layer`, forks the program, interrupts it on
|
|
4673
|
+
SIGINT or SIGTERM so finalizers run, writes one diagnostic line per failure
|
|
4674
|
+
to stderr, and exits with `exitCode`.
|
|
4675
|
+
|
|
4676
|
+
stdout carries only what the script itself printed: the logger is bound to
|
|
4677
|
+
stderr for the whole run, so `Effect.log` from the script or from anything it
|
|
4678
|
+
calls cannot land in the script's output. A `SubmissionUnknown` additionally
|
|
4679
|
+
prints the base64 of the signed bytes and a line saying to reconcile, because
|
|
4680
|
+
those bytes are the durable record a script has.
|
|
4681
|
+
|
|
4682
|
+
On **every** non-zero exit it also prints whatever the journal the script ran
|
|
4683
|
+
with still holds unresolved, which is the only record of bytes that may be on
|
|
4684
|
+
the wire when a script is killed — or fails — between signing and the answer.
|
|
4685
|
+
That count is also what decides a timeout (3 rather than 4) and an interrupt
|
|
4686
|
+
(3 rather than 130): an `Effect.timeout` around a submission interrupts it
|
|
4687
|
+
from the outside and never reaches `Tx.submit`'s own mapping.
|
|
4688
|
+
|
|
4689
|
+
**A second SIGINT does nothing.** The handler interrupts the root fiber once;
|
|
4690
|
+
pressing Ctrl-C again while finalizers run is ignored, because the whole
|
|
4691
|
+
point of the first interrupt is to let those finalizers — the journal write
|
|
4692
|
+
that records what was sent, above all — complete. A script whose finalizers
|
|
4693
|
+
hang has to be killed with SIGKILL, which by construction no process can
|
|
4694
|
+
handle.
|
|
4695
|
+
|
|
4696
|
+
Returns the exit code as well as passing it to `exit`, so a test can inject
|
|
4697
|
+
`exit` and assert on the number without ending the test process.
|
|
4592
4698
|
|
|
4593
4699
|
## `@unconfirmed/sui-effect/testing`
|
|
4594
4700
|
|
|
@@ -4660,14 +4766,6 @@ export interface FakeChange {
|
|
|
4660
4766
|
|
|
4661
4767
|
A created, mutated or deleted object in a scripted execution.
|
|
4662
4768
|
|
|
4663
|
-
### `fakeDigest` (const)
|
|
4664
|
-
|
|
4665
|
-
```ts
|
|
4666
|
-
declare const fakeDigest: (seed: number) => string
|
|
4667
|
-
```
|
|
4668
|
-
|
|
4669
|
-
A deterministic base58 32-byte digest, for fixtures.
|
|
4670
|
-
|
|
4671
4769
|
### `FakeExecution` (interface)
|
|
4672
4770
|
|
|
4673
4771
|
```ts
|
|
@@ -4763,6 +4861,18 @@ export interface FakeScript {
|
|
|
4763
4861
|
/** The gas budget the resolve plugin sets when a transaction has none. */
|
|
4764
4862
|
readonly gasBudget?: bigint;
|
|
4765
4863
|
readonly balances?: ReadonlyArray<SuiClientTypes.Balance>;
|
|
4864
|
+
/**
|
|
4865
|
+
* What `getCoinMetadata` answers, keyed by coin type.
|
|
4866
|
+
*
|
|
4867
|
+
* Unscripted, the method dies naming itself the way every uncovered method
|
|
4868
|
+
* does. Scripted, a coin type the record does not name answers
|
|
4869
|
+
* `{ coinMetadata: null }` — which is what a node says about a type that has
|
|
4870
|
+
* no metadata object, and the case an extension that formats balances has to
|
|
4871
|
+
* handle.
|
|
4872
|
+
*
|
|
4873
|
+
* @since 0.1.1
|
|
4874
|
+
*/
|
|
4875
|
+
readonly coinMetadata?: Readonly<Record<string, SuiClientTypes.CoinMetadata>>;
|
|
4766
4876
|
readonly dynamicFields?: Readonly<Record<string, ReadonlyArray<SuiClientTypes.DynamicFieldEntry>>>;
|
|
4767
4877
|
readonly dynamicFieldValues?: Readonly<Record<string, SuiClientTypes.DynamicFieldValue>>;
|
|
4768
4878
|
/** How many items a list method returns per page. Defaults to 50. */
|
|
@@ -4822,42 +4932,6 @@ export declare class FakeUnimplemented extends Error {
|
|
|
4822
4932
|
|
|
4823
4933
|
Thrown, and re-thrown as a defect, when a test reaches an unscripted method.
|
|
4824
4934
|
|
|
4825
|
-
### `layerExtensionTest` (const)
|
|
4826
|
-
|
|
4827
|
-
```ts
|
|
4828
|
-
declare const layerExtensionTest: <Self, E>(layer: Layer.Layer<Self, E, Sui | SuiCore>, script?: FakeScript) => Layer.Layer<Self | Sui | SuiCore | SuiCoreFake, E | NetworkMismatch | TransportError>
|
|
4829
|
-
```
|
|
4830
|
-
|
|
4831
|
-
An extension's own layer over `layerTest`, which is the whole wiring an
|
|
4832
|
-
extension test needs.
|
|
4833
|
-
|
|
4834
|
-
`layerTest` provides `Sui`, `SuiCore` and `SuiCoreFake`, and an extension's
|
|
4835
|
-
`layerTest` (or `layer`, for an extension with no fake of its own) may
|
|
4836
|
-
require `Sui | SuiCore` and nothing else — the same requirement
|
|
4837
|
-
`SuiExtension.fromService` satisfies in production. So composing the two is
|
|
4838
|
-
the recipe, and everything the extension's layer provides plus the fake's
|
|
4839
|
-
handle comes out the other side.
|
|
4840
|
-
|
|
4841
|
-
**Fails with: whatever the extension's layer fails with, plus `NetworkMismatch` and `TransportError` when the script asks for them.**
|
|
4842
|
-
|
|
4843
|
-
### `layerTest` (const)
|
|
4844
|
-
|
|
4845
|
-
```ts
|
|
4846
|
-
declare const layerTest: (script?: FakeScript) => Layer.Layer<Sui | SuiCore | SuiCoreFake, NetworkMismatch | TransportError>
|
|
4847
|
-
```
|
|
4848
|
-
|
|
4849
|
-
The real `Sui` over the fake `SuiCore`, plus the fake's own handle so a test
|
|
4850
|
-
can script outcomes and inspect what the fake received.
|
|
4851
|
-
|
|
4852
|
-
This is `Sui.layerNoDeps`, the production layer, so the chain-id rules are
|
|
4853
|
-
the production rules: a script whose `network` is `mainnet` or `testnet` must
|
|
4854
|
-
report that network's identifier, and any other network asserts nothing. A
|
|
4855
|
-
test that wants the assertion on a custom network builds
|
|
4856
|
-
`Sui.layerNoDepsWith({ chainId })` over `SuiCoreFake.layer(script)` itself,
|
|
4857
|
-
rather than having the fake compared against its own script.
|
|
4858
|
-
|
|
4859
|
-
**Fails with: `NetworkMismatch`, `TransportError` — both only when the script asks for them.**
|
|
4860
|
-
|
|
4861
4935
|
### `RecordedCall` (interface)
|
|
4862
4936
|
|
|
4863
4937
|
```ts
|
|
@@ -4979,11 +5053,359 @@ inside an Effect, without reaching for the `SuiCoreFake` handle by hand.
|
|
|
4979
5053
|
Every member requires `SuiCoreFake`, which `layerTest` and
|
|
4980
5054
|
`layerExtensionTest` provide.
|
|
4981
5055
|
|
|
5056
|
+
### `fakeDigest` (const)
|
|
5057
|
+
|
|
5058
|
+
```ts
|
|
5059
|
+
declare const fakeDigest: (seed: number) => string
|
|
5060
|
+
```
|
|
5061
|
+
|
|
5062
|
+
A deterministic base58 32-byte digest, for fixtures.
|
|
5063
|
+
|
|
5064
|
+
### `layerExtensionTest` (const)
|
|
5065
|
+
|
|
5066
|
+
```ts
|
|
5067
|
+
declare const layerExtensionTest: <Self, E, RExtra = never, EExtra = never>(layer: Layer.Layer<Self, E, Sui | SuiCore | SuiGraphQL | RExtra>, script?: FakeScript, options?: {
|
|
5068
|
+
readonly extra?: Layer.Layer<RExtra, EExtra>;
|
|
5069
|
+
}) => Layer.Layer<Self | Sui | SuiCore | SuiCoreFake, E | EExtra | NetworkMismatch | TransportError>
|
|
5070
|
+
```
|
|
5071
|
+
|
|
5072
|
+
An extension's own layer over `layerTest`, which is the whole wiring an
|
|
5073
|
+
extension test needs.
|
|
5074
|
+
|
|
5075
|
+
`layerTest` provides `Sui`, `SuiCore` and `SuiCoreFake`, and an extension's
|
|
5076
|
+
`layerTest` (or `layer`, for an extension with no fake of its own) may
|
|
5077
|
+
require `Sui | SuiCore` and nothing else — the same requirement
|
|
5078
|
+
`SuiExtension.fromService` satisfies in production. So composing the two is
|
|
5079
|
+
the recipe, and everything the extension's layer provides plus the fake's
|
|
5080
|
+
handle comes out the other side.
|
|
5081
|
+
|
|
5082
|
+
**`SuiGraphQL` is provided too**, as `SuiGraphQL.layerUnavailable`: an
|
|
5083
|
+
extension that reads GraphQL requires the tag, its layer would otherwise not
|
|
5084
|
+
build in a test, and "there is no endpoint" is the answer a test wants by
|
|
5085
|
+
default — every GraphQL call fails with `GraphQLUnavailable`, which is a
|
|
5086
|
+
failure the extension already handles. Pass `extra` to override it with a
|
|
5087
|
+
real or scripted client, or to provide anything else the extension's layer
|
|
5088
|
+
requires that the client could not have given it (an `HttpClient`, an
|
|
5089
|
+
operator service, a sibling extension's test layer).
|
|
5090
|
+
|
|
5091
|
+
**Fails with: whatever the extension's layer fails with, whatever `extra` fails with, plus `NetworkMismatch` and `TransportError` when the script asks for them.**
|
|
5092
|
+
|
|
5093
|
+
### `layerTest` (const)
|
|
5094
|
+
|
|
5095
|
+
```ts
|
|
5096
|
+
declare const layerTest: (script?: FakeScript) => Layer.Layer<Sui | SuiCore | SuiCoreFake, NetworkMismatch | TransportError>
|
|
5097
|
+
```
|
|
5098
|
+
|
|
5099
|
+
The real `Sui` over the fake `SuiCore`, plus the fake's own handle so a test
|
|
5100
|
+
can script outcomes and inspect what the fake received.
|
|
5101
|
+
|
|
5102
|
+
This is `Sui.layerNoDeps`, the production layer, so the chain-id rules are
|
|
5103
|
+
the production rules: a script whose `network` is `mainnet` or `testnet` must
|
|
5104
|
+
report that network's identifier, and any other network asserts nothing. A
|
|
5105
|
+
test that wants the assertion on a custom network builds
|
|
5106
|
+
`Sui.layerNoDepsWith({ chainId })` over `SuiCoreFake.layer(script)` itself,
|
|
5107
|
+
rather than having the fake compared against its own script.
|
|
5108
|
+
|
|
5109
|
+
**Fails with: `NetworkMismatch`, `TransportError` — both only when the script asks for them.**
|
|
5110
|
+
|
|
4982
5111
|
# Examples
|
|
4983
5112
|
|
|
4984
5113
|
Every file below is typechecked, and each one is exercised by a test against
|
|
4985
5114
|
the in-memory fake.
|
|
4986
5115
|
|
|
5116
|
+
### `examples/compare-read.effect.ts`
|
|
5117
|
+
|
|
5118
|
+
```ts
|
|
5119
|
+
/**
|
|
5120
|
+
* Reads one Escrow object and its optional note through sui-effect.
|
|
5121
|
+
*
|
|
5122
|
+
* Compare with `examples/compare-read.sdk.ts`, which reads the same two
|
|
5123
|
+
* things with the canonical SDK client. Both print identical output.
|
|
5124
|
+
*
|
|
5125
|
+
* Run with `SUI_NETWORK=testnet ESCROW_ID=0x… bun examples/compare-read.effect.ts`.
|
|
5126
|
+
* Every failure this program can produce is in the generator's inferred error
|
|
5127
|
+
* type: `ObjectNotFound`, `ObjectDeleted`, `ObjectUnavailable` and
|
|
5128
|
+
* `DecodeError` (the last one is what a wrong Move type becomes, because
|
|
5129
|
+
* `getObject`'s tag check runs before a byte is parsed), plus `TransportError`
|
|
5130
|
+
* from both reads and, from the layer, `ConfigError` and `NetworkMismatch`.
|
|
5131
|
+
*/
|
|
5132
|
+
import { bcs } from "@mysten/sui/bcs"
|
|
5133
|
+
import { Config, Console, Effect, Option } from "effect"
|
|
5134
|
+
import { ObjectId, Sui, SuiSchema } from "../src/index.ts"
|
|
5135
|
+
|
|
5136
|
+
const PKG = "0x0000000000000000000000000000000000000000000000000000000000000002"
|
|
5137
|
+
|
|
5138
|
+
const Escrow = SuiSchema.bcs(
|
|
5139
|
+
bcs.struct("Escrow", { id: bcs.Address, amount: bcs.u64() }),
|
|
5140
|
+
`${PKG}::escrow::Escrow`
|
|
5141
|
+
)
|
|
5142
|
+
// The note's value has no struct tag of its own, so its codec carries none;
|
|
5143
|
+
// the key is a primitive `u64`, which is why nothing here reaches for
|
|
5144
|
+
// `normalizeStructTag` on it.
|
|
5145
|
+
const Note = SuiSchema.bcs(bcs.u64())
|
|
5146
|
+
const NOTE_NAME = { type: "u64", bcs: bcs.u64().serialize(0).toBytes() }
|
|
5147
|
+
|
|
5148
|
+
/** The program itself, exported so a test can run it against the fake. */
|
|
5149
|
+
export const program = Effect.gen(function*() {
|
|
5150
|
+
const sui = yield* Sui
|
|
5151
|
+
const id = yield* Config.schema(ObjectId, "ESCROW_ID")
|
|
5152
|
+
// No `ObjectError.reason` switch and no manual type comparison: a missing
|
|
5153
|
+
// or deleted escrow and a wrong Move type are already three different tags
|
|
5154
|
+
// in the type this line returns.
|
|
5155
|
+
const escrow = yield* sui.getObject(id, { schema: Escrow })
|
|
5156
|
+
const field = yield* sui.getDynamicFieldOption(id, NOTE_NAME)
|
|
5157
|
+
const note = yield* Option.match(field, {
|
|
5158
|
+
onNone: () => Effect.void,
|
|
5159
|
+
onSome: (entry) => SuiSchema.decode(Note, entry.value.bcs)
|
|
5160
|
+
})
|
|
5161
|
+
yield* Console.log(`${escrow.id} holds ${escrow.content.amount}, note: ${note ?? "none"}`)
|
|
5162
|
+
})
|
|
5163
|
+
|
|
5164
|
+
if (import.meta.main) {
|
|
5165
|
+
// Imported here rather than at the top so that a test can import `program`
|
|
5166
|
+
// without pulling a platform package into the test process.
|
|
5167
|
+
const { BunRuntime } = await import("@effect/platform-bun")
|
|
5168
|
+
BunRuntime.runMain(program.pipe(Effect.provide(Sui.layerConfig)))
|
|
5169
|
+
}
|
|
5170
|
+
```
|
|
5171
|
+
|
|
5172
|
+
### `examples/compare-read.sdk.ts`
|
|
5173
|
+
|
|
5174
|
+
```ts
|
|
5175
|
+
/**
|
|
5176
|
+
* Reads one Escrow object and an optional dynamic field on it, written
|
|
5177
|
+
* against the canonical 2.30 SDK: a gRPC client, the Core API, manual BCS.
|
|
5178
|
+
*
|
|
5179
|
+
* Compare with `examples/compare-read.effect.ts`. Both print identical
|
|
5180
|
+
* output for the same id.
|
|
5181
|
+
*
|
|
5182
|
+
* Run with `SUI_NETWORK=testnet ESCROW_ID=0x… bun examples/compare-read.sdk.ts`.
|
|
5183
|
+
*/
|
|
5184
|
+
import { bcs } from "@mysten/sui/bcs"
|
|
5185
|
+
import type { ClientWithCoreApi } from "@mysten/sui/client"
|
|
5186
|
+
import { ObjectError } from "@mysten/sui/client"
|
|
5187
|
+
import { SuiGrpcClient } from "@mysten/sui/grpc"
|
|
5188
|
+
|
|
5189
|
+
const PKG = "0x0000000000000000000000000000000000000000000000000000000000000002"
|
|
5190
|
+
const ESCROW_TYPE = `${PKG}::escrow::Escrow`
|
|
5191
|
+
const EscrowBcs = bcs.struct("Escrow", { id: bcs.Address, amount: bcs.u64() })
|
|
5192
|
+
// A note some escrows carry, keyed by the literal index 0. The key is a
|
|
5193
|
+
// primitive `u64`, not a struct tag, so nothing here parses it as one.
|
|
5194
|
+
const NOTE_NAME = { type: "u64", bcs: bcs.u64().serialize(0).toBytes() }
|
|
5195
|
+
|
|
5196
|
+
/** Reads the escrow at `id` plus its optional note. */
|
|
5197
|
+
export const readEscrow = async (
|
|
5198
|
+
client: ClientWithCoreApi,
|
|
5199
|
+
id: string
|
|
5200
|
+
): Promise<{ id: string; amount: string; note: string | undefined }> => {
|
|
5201
|
+
let object
|
|
5202
|
+
try {
|
|
5203
|
+
;({ object } = await client.core.getObject({ objectId: id, include: { content: true } }))
|
|
5204
|
+
} catch (error) {
|
|
5205
|
+
// `getObject` throws rather than returning a value that says which of
|
|
5206
|
+
// "missing", "deleted" or "unreachable" happened; that three-way split
|
|
5207
|
+
// is `ObjectError.reason`, read by hand.
|
|
5208
|
+
if (error instanceof ObjectError) {
|
|
5209
|
+
if (error.reason === "notFound") throw new Error(`escrow ${id} does not exist`)
|
|
5210
|
+
if (error.reason === "deleted") throw new Error(`escrow ${id} was deleted`)
|
|
5211
|
+
throw new Error(`escrow ${id}: node could not say what happened to it`, { cause: error })
|
|
5212
|
+
}
|
|
5213
|
+
throw error
|
|
5214
|
+
}
|
|
5215
|
+
// No type parameters on this tag, so a plain string comparison is honest;
|
|
5216
|
+
// a generic type would need to be parsed and compared piece by piece,
|
|
5217
|
+
// which is what sui-effect's bridge does for every caller.
|
|
5218
|
+
if (object.type !== ESCROW_TYPE) {
|
|
5219
|
+
throw new Error(`${id} is a ${object.type}, not ${ESCROW_TYPE}`)
|
|
5220
|
+
}
|
|
5221
|
+
const { id: escrowId, amount } = EscrowBcs.parse(object.content)
|
|
5222
|
+
|
|
5223
|
+
let note: string | undefined
|
|
5224
|
+
try {
|
|
5225
|
+
const { dynamicField } = await client.core.getDynamicField({ parentId: id, name: NOTE_NAME })
|
|
5226
|
+
note = bcs.u64().parse(dynamicField.value.bcs)
|
|
5227
|
+
} catch (error) {
|
|
5228
|
+
// Absence is normal here; only a reason other than "notFound"/"deleted"
|
|
5229
|
+
// is a real problem.
|
|
5230
|
+
if (!(error instanceof ObjectError) || error.reason === "unknown") throw error
|
|
5231
|
+
}
|
|
5232
|
+
|
|
5233
|
+
return { id: escrowId, amount, note }
|
|
5234
|
+
}
|
|
5235
|
+
|
|
5236
|
+
if (import.meta.main) {
|
|
5237
|
+
const id = process.env.ESCROW_ID
|
|
5238
|
+
if (!id) throw new Error("ESCROW_ID is required")
|
|
5239
|
+
const client = new SuiGrpcClient({
|
|
5240
|
+
network: (process.env.SUI_NETWORK ?? "testnet") as "testnet",
|
|
5241
|
+
baseUrl: process.env.SUI_RPC_URL ?? "https://fullnode.testnet.sui.io:443"
|
|
5242
|
+
})
|
|
5243
|
+
const escrow = await readEscrow(client, id)
|
|
5244
|
+
console.log(`${escrow.id} holds ${escrow.amount}, note: ${escrow.note ?? "none"}`)
|
|
5245
|
+
}
|
|
5246
|
+
```
|
|
5247
|
+
|
|
5248
|
+
### `examples/compare-write.effect.ts`
|
|
5249
|
+
|
|
5250
|
+
```ts
|
|
5251
|
+
/**
|
|
5252
|
+
* Claims an escrow through `Tx.run`, sui-effect's write path.
|
|
5253
|
+
*
|
|
5254
|
+
* Compare with `examples/compare-write.sdk.ts`, which does the same thing
|
|
5255
|
+
* against the canonical SDK client. Both sign with `SUI_PRIVATE_KEY` and
|
|
5256
|
+
* print the created receipt's object id.
|
|
5257
|
+
*
|
|
5258
|
+
* What `Tx.run` adds over the SDK file: a default expiration bounded to the
|
|
5259
|
+
* current epoch, a journal entry written before the first execute so a crash
|
|
5260
|
+
* mid-flight leaves a record, resending the identical signed bytes rather
|
|
5261
|
+
* than rebuilding on a transient failure, `reconcile` when a submission's
|
|
5262
|
+
* outcome is unknown, and a sender lock so two concurrent claims from one
|
|
5263
|
+
* address cannot pick the same gas coin. What it costs is the Effect
|
|
5264
|
+
* vocabulary below.
|
|
5265
|
+
*
|
|
5266
|
+
* Run with:
|
|
5267
|
+
* SUI_NETWORK=testnet SUI_PRIVATE_KEY=suiprivkey1… ESCROW_ID=0x… bun examples/compare-write.effect.ts
|
|
5268
|
+
*/
|
|
5269
|
+
import { bcs } from "@mysten/sui/bcs"
|
|
5270
|
+
import { Config, Console, Effect } from "effect"
|
|
5271
|
+
import { ObjectId, SuiSchema } from "../src/index.ts"
|
|
5272
|
+
import { Script } from "../src/script.ts"
|
|
5273
|
+
import { Tx } from "../src/tx.ts"
|
|
5274
|
+
|
|
5275
|
+
const PKG = "0x0000000000000000000000000000000000000000000000000000000000000002"
|
|
5276
|
+
|
|
5277
|
+
const Escrow = SuiSchema.bcs(
|
|
5278
|
+
bcs.struct("Escrow", { id: bcs.Address, amount: bcs.u64() }),
|
|
5279
|
+
`${PKG}::escrow::Escrow`
|
|
5280
|
+
)
|
|
5281
|
+
|
|
5282
|
+
/**
|
|
5283
|
+
* The program itself, exported so a test can run it against the fake. Every
|
|
5284
|
+
* failure it can produce is in the generator's inferred error type, with no
|
|
5285
|
+
* handling lines anywhere in this file: `ObjectNotFound`, `ObjectDeleted`,
|
|
5286
|
+
* `ObjectUnavailable`, `DecodeError`, `TransportError`, `BuildError`,
|
|
5287
|
+
* `SimulationFailed`, `SigningError`, `PolicyDenied`, `NotApplied`,
|
|
5288
|
+
* `JournalError`, `UnexpectedEffects`, `ExecutionFailed`, `SubmissionUnknown`,
|
|
5289
|
+
* plus `ConfigError` and `NetworkMismatch` from the layer.
|
|
5290
|
+
*/
|
|
5291
|
+
export const program = Effect.gen(function*() {
|
|
5292
|
+
const { signer, sui } = yield* Script
|
|
5293
|
+
const id = yield* Config.schema(ObjectId, "ESCROW_ID")
|
|
5294
|
+
const escrow = yield* sui.getObject(id, { schema: Escrow })
|
|
5295
|
+
const executed = yield* Tx.run((tx) => {
|
|
5296
|
+
tx.moveCall({
|
|
5297
|
+
target: `${PKG}::escrow::claim`,
|
|
5298
|
+
arguments: [tx.object(id), tx.pure.u64(escrow.content.amount)]
|
|
5299
|
+
})
|
|
5300
|
+
}, { signer })
|
|
5301
|
+
const receipt = yield* executed.expectCreated(`${PKG}::escrow::Receipt`)
|
|
5302
|
+
yield* Console.log(receipt.id)
|
|
5303
|
+
})
|
|
5304
|
+
|
|
5305
|
+
if (import.meta.main) {
|
|
5306
|
+
await Script.run(program)
|
|
5307
|
+
}
|
|
5308
|
+
```
|
|
5309
|
+
|
|
5310
|
+
### `examples/compare-write.sdk.ts`
|
|
5311
|
+
|
|
5312
|
+
```ts
|
|
5313
|
+
/**
|
|
5314
|
+
* Claims an escrow through a PTB, written against the canonical 2.30 SDK.
|
|
5315
|
+
*
|
|
5316
|
+
* Compare with `examples/compare-write.effect.ts`, which does the same thing
|
|
5317
|
+
* through `Tx.run` and notes what that adds over this file. Both sign with
|
|
5318
|
+
* `SUI_PRIVATE_KEY` and print the created receipt's object id.
|
|
5319
|
+
*
|
|
5320
|
+
* Run with:
|
|
5321
|
+
* SUI_NETWORK=testnet SUI_PRIVATE_KEY=suiprivkey1… ESCROW_ID=0x… bun examples/compare-write.sdk.ts
|
|
5322
|
+
*/
|
|
5323
|
+
import { bcs } from "@mysten/sui/bcs"
|
|
5324
|
+
import type { ClientWithCoreApi } from "@mysten/sui/client"
|
|
5325
|
+
import type { Keypair } from "@mysten/sui/cryptography"
|
|
5326
|
+
import { decodeSuiPrivateKey } from "@mysten/sui/cryptography"
|
|
5327
|
+
import { SuiGrpcClient } from "@mysten/sui/grpc"
|
|
5328
|
+
import { Ed25519Keypair } from "@mysten/sui/keypairs/ed25519"
|
|
5329
|
+
import { Secp256k1Keypair } from "@mysten/sui/keypairs/secp256k1"
|
|
5330
|
+
import { Secp256r1Keypair } from "@mysten/sui/keypairs/secp256r1"
|
|
5331
|
+
import { Transaction } from "@mysten/sui/transactions"
|
|
5332
|
+
|
|
5333
|
+
const PKG = "0x0000000000000000000000000000000000000000000000000000000000000002"
|
|
5334
|
+
const RECEIPT_TYPE = `${PKG}::escrow::Receipt`
|
|
5335
|
+
const EscrowBcs = bcs.struct("Escrow", { id: bcs.Address, amount: bcs.u64() })
|
|
5336
|
+
|
|
5337
|
+
// The scheme flag on a Bech32 key names one of three keypair classes;
|
|
5338
|
+
// nothing else decodes it, and a fourth scheme has no keypair class at all.
|
|
5339
|
+
const KEYPAIR_FOR: Record<string, undefined | ((secretKey: Uint8Array) => Keypair)> = {
|
|
5340
|
+
ED25519: Ed25519Keypair.fromSecretKey,
|
|
5341
|
+
Secp256k1: Secp256k1Keypair.fromSecretKey,
|
|
5342
|
+
Secp256r1: Secp256r1Keypair.fromSecretKey
|
|
5343
|
+
}
|
|
5344
|
+
|
|
5345
|
+
/**
|
|
5346
|
+
* Reads the escrow, claims it, and returns the created receipt's object id.
|
|
5347
|
+
*
|
|
5348
|
+
* Builds, signs and executes as three explicit steps rather than the
|
|
5349
|
+
* one-call `signAndExecuteTransaction`, so this can run against an in-memory
|
|
5350
|
+
* client with no live network or signer behind it.
|
|
5351
|
+
*/
|
|
5352
|
+
export const claimEscrow = async (
|
|
5353
|
+
client: ClientWithCoreApi,
|
|
5354
|
+
escrowId: string,
|
|
5355
|
+
keypair: Keypair
|
|
5356
|
+
): Promise<string> => {
|
|
5357
|
+
const { object } = await client.core.getObject({ objectId: escrowId, include: { content: true } })
|
|
5358
|
+
const { amount } = EscrowBcs.parse(object.content)
|
|
5359
|
+
|
|
5360
|
+
const tx = new Transaction()
|
|
5361
|
+
tx.setSenderIfNotSet(keypair.toSuiAddress())
|
|
5362
|
+
tx.moveCall({ target: `${PKG}::escrow::claim`, arguments: [tx.object(escrowId), tx.pure.u64(amount)] })
|
|
5363
|
+
const bytes = await tx.build({ client })
|
|
5364
|
+
const { signature } = await keypair.signTransaction(bytes)
|
|
5365
|
+
|
|
5366
|
+
const result = await client.core.executeTransaction({
|
|
5367
|
+
transaction: bytes,
|
|
5368
|
+
signatures: [signature],
|
|
5369
|
+
include: { effects: true, objectTypes: true }
|
|
5370
|
+
})
|
|
5371
|
+
const digest = (result.Transaction ?? result.FailedTransaction).digest
|
|
5372
|
+
await client.core.waitForTransaction({ digest })
|
|
5373
|
+
|
|
5374
|
+
if (result.$kind === "FailedTransaction") {
|
|
5375
|
+
const { error } = result.FailedTransaction.status
|
|
5376
|
+
if (error?.$kind === "MoveAbort") {
|
|
5377
|
+
const { abortCode, cleverError } = error.MoveAbort
|
|
5378
|
+
throw new Error(`claim aborted: code ${abortCode}${cleverError?.constantName ? ` (${cleverError.constantName})` : ""}`)
|
|
5379
|
+
}
|
|
5380
|
+
throw new Error(`claim failed: ${error?.message}`)
|
|
5381
|
+
}
|
|
5382
|
+
|
|
5383
|
+
// Effects list every changed object by id; only the `objectTypes` join
|
|
5384
|
+
// says which one is the receipt. sui-effect's `expectCreated` is this join,
|
|
5385
|
+
// plus a check that exactly one match exists.
|
|
5386
|
+
const { changedObjects } = result.Transaction.effects
|
|
5387
|
+
const created = changedObjects.find(
|
|
5388
|
+
(change) => change.idOperation === "Created" && result.Transaction.objectTypes[change.objectId] === RECEIPT_TYPE
|
|
5389
|
+
)
|
|
5390
|
+
if (created === undefined) throw new Error(`claim applied (${digest}) but created no ${RECEIPT_TYPE}`)
|
|
5391
|
+
return created.objectId
|
|
5392
|
+
}
|
|
5393
|
+
|
|
5394
|
+
if (import.meta.main) {
|
|
5395
|
+
const escrowId = process.env.ESCROW_ID
|
|
5396
|
+
const key = process.env.SUI_PRIVATE_KEY
|
|
5397
|
+
if (!escrowId || !key) throw new Error("ESCROW_ID and SUI_PRIVATE_KEY are required")
|
|
5398
|
+
const parsed = decodeSuiPrivateKey(key)
|
|
5399
|
+
const fromSecretKey = KEYPAIR_FOR[parsed.scheme]
|
|
5400
|
+
if (!fromSecretKey) throw new Error(`${parsed.scheme} has no keypair class here (use a remote signer)`)
|
|
5401
|
+
const client = new SuiGrpcClient({
|
|
5402
|
+
network: (process.env.SUI_NETWORK ?? "testnet") as "testnet",
|
|
5403
|
+
baseUrl: process.env.SUI_RPC_URL ?? "https://fullnode.testnet.sui.io:443"
|
|
5404
|
+
})
|
|
5405
|
+
console.log(await claimEscrow(client, escrowId, fromSecretKey(parsed.secretKey)))
|
|
5406
|
+
}
|
|
5407
|
+
```
|
|
5408
|
+
|
|
4987
5409
|
### `examples/extension-consumer.ts`
|
|
4988
5410
|
|
|
4989
5411
|
```ts
|
|
@@ -6120,13 +6542,16 @@ import {
|
|
|
6120
6542
|
Stream
|
|
6121
6543
|
} from "effect"
|
|
6122
6544
|
import { TestClock } from "effect/testing"
|
|
6123
|
-
import type { Sui, SuiCore } from "@unconfirmed/sui-effect"
|
|
6124
|
-
import { KNOWN_CHAIN_IDS, ObjectId, SuiAddress, SuiSchema } from "@unconfirmed/sui-effect"
|
|
6545
|
+
import type { ChangedRef, Recipe, Sui, SuiCore } from "@unconfirmed/sui-effect"
|
|
6546
|
+
import { KNOWN_CHAIN_IDS, ObjectId, SuiAddress, SuiError, SuiSchema } from "@unconfirmed/sui-effect"
|
|
6547
|
+
import type { PromiseFace } from "@unconfirmed/sui-effect/extension"
|
|
6125
6548
|
import { FakeOutcome, layerExtensionTest, layerTest, SuiCoreFake, SuiTest } from "@unconfirmed/sui-effect/testing"
|
|
6126
6549
|
import { Journal, Signer } from "@unconfirmed/sui-effect/tx"
|
|
6550
|
+
import type { EscrowObject, EscrowService } from "../src/Escrow.ts"
|
|
6127
6551
|
import { DEPLOYMENTS, Escrow } from "../src/Escrow.ts"
|
|
6128
6552
|
import { escrow as escrowRegistration } from "../src/extension.ts"
|
|
6129
6553
|
import { EscrowNotFound, EscrowSettlementUnknown, EscrowUnsupportedNetwork } from "../src/errors.ts"
|
|
6554
|
+
import type { PlatformService } from "../src/Platform.ts"
|
|
6130
6555
|
import { Platform, platform as platformRegistration } from "../src/Platform.ts"
|
|
6131
6556
|
import { ESCROW_PACKAGE, receiptType, Settlement, SettlementContent } from "../src/schema.ts"
|
|
6132
6557
|
|
|
@@ -6655,6 +7080,92 @@ describe("layerConfig validates through the typed deployment path", () => {
|
|
|
6655
7080
|
expect(String(exit)).toContain("32-byte Sui object id")
|
|
6656
7081
|
})
|
|
6657
7082
|
})
|
|
7083
|
+
|
|
7084
|
+
/**
|
|
7085
|
+
* The face's **type**, asserted rather than described.
|
|
7086
|
+
*
|
|
7087
|
+
* This is the test every extension copies. `PromiseFace<Service>` is what a
|
|
7088
|
+
* Promise consumer actually holds, and it is derived, so nothing in the service
|
|
7089
|
+
* says out loud what it produced: an `Effect` member has to become a
|
|
7090
|
+
* Promise-returning method, a `Stream` member an `AsyncIterable`, a synchronous
|
|
7091
|
+
* member has to stay synchronous, and a **namespace has to be mapped all the
|
|
7092
|
+
* way down** — `PlatformService.escrow` is an interface, and until 0.1.1 an
|
|
7093
|
+
* interface-typed namespace kept its `Effect` members in the type while the
|
|
7094
|
+
* runtime handed back Promises.
|
|
7095
|
+
*
|
|
7096
|
+
* Write one of these per namespace. It costs four lines and it is the only
|
|
7097
|
+
* thing that catches a face type that has quietly stopped matching the runtime.
|
|
7098
|
+
*/
|
|
7099
|
+
describe("the Promise face type", () => {
|
|
7100
|
+
/** Compile-time assignability, as a value a test can assert on. */
|
|
7101
|
+
const assignableTo = <_A extends _B, _B>(): true => true
|
|
7102
|
+
|
|
7103
|
+
type EscrowFace = PromiseFace<EscrowService>
|
|
7104
|
+
type PlatformFace = PromiseFace<PlatformService>
|
|
7105
|
+
|
|
7106
|
+
test("an Effect member becomes a Promise-returning method", () => {
|
|
7107
|
+
expect(assignableTo<EscrowFace["get"], (id: ObjectId) => Promise<EscrowObject>>()).toBe(true)
|
|
7108
|
+
expect(assignableTo<EscrowFace["feeCollector"], () => Promise<SuiAddress>>()).toBe(true)
|
|
7109
|
+
})
|
|
7110
|
+
|
|
7111
|
+
test("a Stream member becomes an AsyncIterable", () => {
|
|
7112
|
+
expect(
|
|
7113
|
+
assignableTo<EscrowFace["owned"]["stream"], (owner: SuiAddress) => AsyncIterable<EscrowObject>>()
|
|
7114
|
+
).toBe(true)
|
|
7115
|
+
})
|
|
7116
|
+
|
|
7117
|
+
test("a synchronous member stays synchronous", () => {
|
|
7118
|
+
expect(assignableTo<EscrowFace["packageId"], string>()).toBe(true)
|
|
7119
|
+
expect(assignableTo<EscrowFace["claim"], (escrow: EscrowObject) => Recipe>()).toBe(true)
|
|
7120
|
+
})
|
|
7121
|
+
|
|
7122
|
+
test("an interface-typed namespace is mapped all the way down", () => {
|
|
7123
|
+
// `PlatformService.escrow` is `EscrowService`, an interface. The members
|
|
7124
|
+
// reached through it must be the mapped ones, not the Effect ones.
|
|
7125
|
+
expect(assignableTo<PlatformFace["escrow"]["get"], (id: ObjectId) => Promise<EscrowObject>>())
|
|
7126
|
+
.toBe(true)
|
|
7127
|
+
expect(
|
|
7128
|
+
assignableTo<
|
|
7129
|
+
PlatformFace["escrow"]["owned"]["count"],
|
|
7130
|
+
(owner: SuiAddress) => Promise<number>
|
|
7131
|
+
>()
|
|
7132
|
+
).toBe(true)
|
|
7133
|
+
expect(assignableTo<PlatformFace["escrow"]["packageId"], string>()).toBe(true)
|
|
7134
|
+
})
|
|
7135
|
+
|
|
7136
|
+
test("the composition's own member is mapped too", () => {
|
|
7137
|
+
expect(
|
|
7138
|
+
assignableTo<
|
|
7139
|
+
PlatformFace["claimEverything"],
|
|
7140
|
+
(ids: ReadonlyArray<ObjectId>, opts: { readonly signer: Signer }) => Promise<
|
|
7141
|
+
ReadonlyArray<ChangedRef>
|
|
7142
|
+
>
|
|
7143
|
+
>()
|
|
7144
|
+
).toBe(true)
|
|
7145
|
+
})
|
|
7146
|
+
})
|
|
7147
|
+
|
|
7148
|
+
/**
|
|
7149
|
+
* The errors serialize with their `outcome`, which is what a wrapper script
|
|
7150
|
+
* acts on and what an operator reads out of a log line.
|
|
7151
|
+
*/
|
|
7152
|
+
describe("the errors", () => {
|
|
7153
|
+
test("SuiError.toJson keeps the outcome, though it is a class field", () => {
|
|
7154
|
+
const error = new EscrowSettlementUnknown({
|
|
7155
|
+
escrowId: ESCROW_ID,
|
|
7156
|
+
digest: "1".repeat(32) as never,
|
|
7157
|
+
message: "the operator never confirmed"
|
|
7158
|
+
})
|
|
7159
|
+
const json = SuiError.toJson(error)
|
|
7160
|
+
expect(json["_tag"]).toBe("escrow/EscrowSettlementUnknown")
|
|
7161
|
+
expect(json["escrowId"]).toBe(ESCROW_ID)
|
|
7162
|
+
// `outcome` is declared as a class field — not a schema field — because
|
|
7163
|
+
// that is the shape that reads well at the call site. `toJson` reads it off
|
|
7164
|
+
// the instance, so it is in the JSON anyway.
|
|
7165
|
+
expect(json["outcome"]).toBe("unknown")
|
|
7166
|
+
expect(json["outcome"]).toBe(SuiError.outcome(error))
|
|
7167
|
+
})
|
|
7168
|
+
})
|
|
6658
7169
|
```
|
|
6659
7170
|
|
|
6660
7171
|
### `examples/extension-template/package.json`
|