@misofm/effect 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,39 @@
1
+ import { Effect, Option, Schema, Stream } from "effect";
2
+ import type { SuiClientTypes } from "@mysten/sui/client";
3
+ import { BcsDecodeError, ObjectNotFoundError, ObjectTypeMismatchError, SuiRpcError } from "./errors.ts";
4
+ import { SuiClient } from "./sui-client.ts";
5
+ /** One object's BCS content, on-chain type, and version. */
6
+ export interface ObjectContent {
7
+ readonly content: Uint8Array;
8
+ readonly type: string;
9
+ readonly version: string;
10
+ }
11
+ /** A dynamic field entry as returned by `listDynamicFields` (name + value type, not the decoded value). */
12
+ export type DynamicField = SuiClientTypes.DynamicFieldEntry;
13
+ /** Any generated BCS codec with a `parse` method — the shape every `src/contracts/**` struct exports. */
14
+ export interface BcsParser<T> {
15
+ parse(bytes: Uint8Array): T;
16
+ }
17
+ /** Identifies the object/type a `decodeBcs` failure is reported against. */
18
+ export interface DecodeBcsContext {
19
+ /** Fully-qualified Move type (or domain type name) being decoded. */
20
+ readonly type: string;
21
+ /** Object id being decoded, when decoding a specific object's content. */
22
+ readonly objectId?: string;
23
+ }
24
+ /** Fetches one object's BCS content by id; fails with `ObjectNotFoundError` if it does not exist. */
25
+ export declare const getObjectContent: (objectId: string) => Effect.Effect<ObjectContent, ObjectNotFoundError | SuiRpcError, SuiClient>;
26
+ /** Like {@link getObjectContent}, but a missing object resolves to `Option.none()` instead of failing. */
27
+ export declare const getOptionalObjectContent: (objectId: string) => Effect.Effect<Option.Option<ObjectContent>, SuiRpcError, SuiClient>;
28
+ /** Fetches many objects' BCS content in one Core request; ids that are missing or errored are omitted from the map. */
29
+ export declare const getObjectsContent: (objectIds: readonly string[]) => Effect.Effect<ReadonlyMap<string, {
30
+ content: Uint8Array;
31
+ type: string;
32
+ }>, SuiRpcError, SuiClient>;
33
+ /** Pages every dynamic field under `parentId` through `client.core.listDynamicFields`, following the cursor. */
34
+ export declare function listDynamicFields(parentId: string): Stream.Stream<DynamicField, SuiRpcError, SuiClient>;
35
+ /** Runs a generated BCS codec's `.parse`, then decodes the result into a domain `Schema` type; both failure paths become `BcsDecodeError`. */
36
+ export declare const decodeBcs: <A>(codec: BcsParser<unknown>, schema: Schema.Codec<A, any, never, never>, bytes: Uint8Array<ArrayBufferLike>, context: DecodeBcsContext) => Effect.Effect<A, BcsDecodeError, never>;
37
+ /** Fails with `ObjectTypeMismatchError` unless `actual` is exactly `expected`. */
38
+ export declare const assertObjectType: (objectId: string, actual: string, expected: string) => Effect.Effect<void, ObjectTypeMismatchError, never>;
39
+ //# sourceMappingURL=reads.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reads.d.ts","sourceRoot":"","sources":["../src/reads.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACxD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEzD,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACxG,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,4DAA4D;AAC5D,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,2GAA2G;AAC3G,MAAM,MAAM,YAAY,GAAG,cAAc,CAAC,iBAAiB,CAAC;AAE5D,yGAAyG;AACzG,MAAM,WAAW,SAAS,CAAC,CAAC;IAC1B,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,CAAC,CAAC;CAC7B;AAED,4EAA4E;AAC5E,MAAM,WAAW,gBAAgB;IAC/B,qEAAqE;IACrE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,0EAA0E;IAC1E,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,qGAAqG;AACrG,eAAO,MAAM,gBAAgB,kGAY3B,CAAC;AAEH,0GAA0G;AAC1G,eAAO,MAAM,wBAAwB,2FAOnC,CAAC;AAEH,uHAAuH;AACvH,eAAO,MAAM,iBAAiB;aAEqB,UAAU;UAAQ,MAAM;2BAczE,CAAC;AAEH,gHAAgH;AAChH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,YAAY,EAAE,WAAW,EAAE,SAAS,CAAC,CAYvG;AAED,8IAA8I;AAC9I,eAAO,MAAM,SAAS,GAAqC,CAAC,kLAa1D,CAAC;AAEH,kFAAkF;AAClF,eAAO,MAAM,gBAAgB,6GAQ3B,CAAC"}
package/dist/reads.js ADDED
@@ -0,0 +1,107 @@
1
+ // Copyright (c) Miso Labs, Inc.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ // The shared read primitives every `@misofm/*` package's queries are built
4
+ // from. Every primitive requires the `SuiClient` service instead of taking a
5
+ // client parameter, and every not-found path is classified into a typed
6
+ // `ObjectNotFoundError` rather than a bare thrown error — see `isNotFound`
7
+ // below, ported from the transport-sniffing heuristic each package used to
8
+ // hand-roll.
9
+ import { Effect, Option, Schema, Stream } from "effect";
10
+ import { BcsDecodeError, ObjectNotFoundError, ObjectTypeMismatchError, SuiRpcError } from "./errors.js";
11
+ import { SuiClient } from "./sui-client.js";
12
+ /** Fetches one object's BCS content by id; fails with `ObjectNotFoundError` if it does not exist. */
13
+ export const getObjectContent = Effect.fn("getObjectContent")(function* (objectId) {
14
+ const client = yield* SuiClient;
15
+ const { object } = yield* Effect.tryPromise({
16
+ try: (signal) => client.core.getObject({ objectId, include: { content: true }, signal }),
17
+ catch: (cause) => classifyObjectError(objectId, "getObject", cause),
18
+ });
19
+ if (!object.content) {
20
+ return yield* new ObjectNotFoundError({ objectId });
21
+ }
22
+ return { content: object.content, type: object.type, version: object.version };
23
+ });
24
+ /** Like {@link getObjectContent}, but a missing object resolves to `Option.none()` instead of failing. */
25
+ export const getOptionalObjectContent = Effect.fn("getOptionalObjectContent")(function* (objectId) {
26
+ return yield* getObjectContent(objectId).pipe(Effect.map(Option.some), Effect.catchTag("ObjectNotFoundError", () => Effect.succeed(Option.none())));
27
+ });
28
+ /** Fetches many objects' BCS content in one Core request; ids that are missing or errored are omitted from the map. */
29
+ export const getObjectsContent = Effect.fn("getObjectsContent")(function* (objectIds) {
30
+ const out = new Map();
31
+ if (objectIds.length === 0)
32
+ return out;
33
+ const client = yield* SuiClient;
34
+ const { objects } = yield* Effect.tryPromise({
35
+ try: (signal) => client.core.getObjects({ objectIds: [...objectIds], include: { content: true }, signal }),
36
+ catch: (cause) => new SuiRpcError({ operation: "getObjects", cause }),
37
+ });
38
+ for (const obj of objects) {
39
+ if (obj instanceof Error || !obj.content)
40
+ continue;
41
+ out.set(obj.objectId, { content: obj.content, type: obj.type });
42
+ }
43
+ return out;
44
+ });
45
+ /** Pages every dynamic field under `parentId` through `client.core.listDynamicFields`, following the cursor. */
46
+ export function listDynamicFields(parentId) {
47
+ return Stream.paginate(null, (cursor) => Effect.gen(function* () {
48
+ const client = yield* SuiClient;
49
+ const page = yield* Effect.tryPromise({
50
+ try: (signal) => client.core.listDynamicFields({ parentId, cursor, signal }),
51
+ catch: (cause) => new SuiRpcError({ operation: "listDynamicFields", cause }),
52
+ });
53
+ const next = page.hasNextPage ? Option.some(page.cursor) : Option.none();
54
+ return [page.dynamicFields, next];
55
+ }));
56
+ }
57
+ /** Runs a generated BCS codec's `.parse`, then decodes the result into a domain `Schema` type; both failure paths become `BcsDecodeError`. */
58
+ export const decodeBcs = Effect.fn("decodeBcs")(function* (codec, schema, bytes, context) {
59
+ const parsed = yield* Effect.try({
60
+ try: () => codec.parse(bytes),
61
+ catch: (cause) => new BcsDecodeError({ type: context.type, objectId: context.objectId, cause }),
62
+ });
63
+ return yield* Schema.decodeUnknownEffect(schema)(parsed).pipe(Effect.mapError((cause) => new BcsDecodeError({ type: context.type, objectId: context.objectId, cause })));
64
+ });
65
+ /** Fails with `ObjectTypeMismatchError` unless `actual` is exactly `expected`. */
66
+ export const assertObjectType = Effect.fn("assertObjectType")(function* (objectId, actual, expected) {
67
+ if (actual !== expected) {
68
+ return yield* new ObjectTypeMismatchError({ objectId, expected, actual });
69
+ }
70
+ });
71
+ // ── Private ──────────────────────────────────────────────────────────────────
72
+ /** Classifies a Core API rejection: a missing object becomes `ObjectNotFoundError`, anything else `SuiRpcError`. */
73
+ function classifyObjectError(objectId, operation, cause) {
74
+ return isNotFound(cause) ? new ObjectNotFoundError({ objectId }) : new SuiRpcError({ operation, cause });
75
+ }
76
+ /**
77
+ * True when `e` is a "this object does not exist" error from any of the Sui
78
+ * client transports. Matches, in order of preference:
79
+ *
80
+ * 1. Structured `ObjectError.code` values thrown by the JSON-RPC core client
81
+ * (`notExists`, `deleted`, `dynamicFieldNotFound`) and the GraphQL core
82
+ * client (`notFound`). The class itself is not exported by `@mysten/sui`,
83
+ * so we duck-type on `code`.
84
+ * 2. The message shapes those clients (and the gRPC core client, which wraps
85
+ * the server's per-object status message in a plain `Error`) produce:
86
+ * "Object 0x… does not exist" / "Object 0x… not found" / "Object 0x… has
87
+ * been deleted" / "Dynamic field not found for object 0x…" / "No object
88
+ * found for id 0x…".
89
+ *
90
+ * Transport/protocol errors must NOT match: every message pattern requires
91
+ * object-ish context ("object" / "dynamic field"), so e.g. a JSON-RPC
92
+ * "Method not found" or a gRPC "peer not found" is never treated as a missing
93
+ * object and is reported as a `SuiRpcError` instead.
94
+ */
95
+ function isNotFound(e) {
96
+ if (typeof e === "object" && e !== null && "code" in e) {
97
+ const code = e.code;
98
+ if (code === "notExists" || code === "deleted" || code === "dynamicFieldNotFound" || code === "notFound") {
99
+ return true;
100
+ }
101
+ }
102
+ const msg = e instanceof Error ? e.message : String(e);
103
+ return (/\bobject\b[\s\S]*\b(?:not\s?found|does not exist|has been deleted)\b/i.test(msg) ||
104
+ /\bdynamic field\b[\s\S]*\bnot\s?found\b/i.test(msg) ||
105
+ /\bno object\b/i.test(msg));
106
+ }
107
+ //# sourceMappingURL=reads.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reads.js","sourceRoot":"","sources":["../src/reads.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import { Context, Layer } from "effect";
2
+ import type { ClientWithCoreApi } from "@mysten/sui/client";
3
+ import type { SuiGraphQLClient } from "@mysten/sui/graphql";
4
+ declare const SuiClient_base: Context.ServiceClass<SuiClient, "@misofm/effect/SuiClient", ClientWithCoreApi>;
5
+ /** The unified Core API client (gRPC / JSON-RPC / GraphQL transports all satisfy it). */
6
+ export declare class SuiClient extends SuiClient_base {
7
+ /** Provides a concrete `ClientWithCoreApi` as the `SuiClient` service. */
8
+ static layer(client: ClientWithCoreApi): Layer.Layer<SuiClient>;
9
+ }
10
+ declare const SuiGraphQL_base: Context.ServiceClass<SuiGraphQL, "@misofm/effect/SuiGraphQL", SuiGraphQLClient<{}>>;
11
+ /** The optional GraphQL client, required only by type-discovery reads. */
12
+ export declare class SuiGraphQL extends SuiGraphQL_base {
13
+ /** Provides a concrete `SuiGraphQLClient` as the `SuiGraphQL` service. */
14
+ static layer(client: SuiGraphQLClient): Layer.Layer<SuiGraphQL>;
15
+ }
16
+ export {};
17
+ //# sourceMappingURL=sui-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sui-client.d.ts","sourceRoot":"","sources":["../src/sui-client.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;;AAE5D,yFAAyF;AACzF,qBAAa,SAAU,SAAQ,cAA2E;IACxG,0EAA0E;IAC1E,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,iBAAiB,GAAG,KAAK,CAAC,KAAK,CAAC,SAAS,CAAC,CAE9D;CACF;;AAED,0EAA0E;AAC1E,qBAAa,UAAW,SAAQ,eAA4E;IAC1G,0EAA0E;IAC1E,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,gBAAgB,GAAG,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC,CAE9D;CACF"}
@@ -0,0 +1,22 @@
1
+ // Copyright (c) Miso Labs, Inc.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ // The Sui client services every `@misofm/*` SDK reaches through the Requirements
4
+ // channel instead of a `client` parameter. There is exactly one `SuiClient`
5
+ // service tag (and one `SuiGraphQL` tag) across the SDKs, so a program built
6
+ // from primitives in different packages still needs only one `Layer.provide`.
7
+ import { Context, Layer } from "effect";
8
+ /** The unified Core API client (gRPC / JSON-RPC / GraphQL transports all satisfy it). */
9
+ export class SuiClient extends Context.Service()("@misofm/effect/SuiClient") {
10
+ /** Provides a concrete `ClientWithCoreApi` as the `SuiClient` service. */
11
+ static layer(client) {
12
+ return Layer.succeed(SuiClient, client);
13
+ }
14
+ }
15
+ /** The optional GraphQL client, required only by type-discovery reads. */
16
+ export class SuiGraphQL extends Context.Service()("@misofm/effect/SuiGraphQL") {
17
+ /** Provides a concrete `SuiGraphQLClient` as the `SuiGraphQL` service. */
18
+ static layer(client) {
19
+ return Layer.succeed(SuiGraphQL, client);
20
+ }
21
+ }
22
+ //# sourceMappingURL=sui-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sui-client.js","sourceRoot":"","sources":["../src/sui-client.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,sCAAsC;AAEtC,iFAAiF;AACjF,4EAA4E;AAC5E,6EAA6E;AAC7E,8EAA8E;AAE9E,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAIxC,yFAAyF;AACzF,MAAM,OAAO,SAAU,SAAQ,OAAO,CAAC,OAAO,EAAgC,CAAC,0BAA0B,CAAC;IACxG,0EAA0E;IAC1E,MAAM,CAAC,KAAK,CAAC,MAAyB;QACpC,OAAO,KAAK,CAAC,OAAO,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IAC1C,CAAC;CACF;AAED,0EAA0E;AAC1E,MAAM,OAAO,UAAW,SAAQ,OAAO,CAAC,OAAO,EAAgC,CAAC,2BAA2B,CAAC;IAC1G,0EAA0E;IAC1E,MAAM,CAAC,KAAK,CAAC,MAAwB;QACnC,OAAO,KAAK,CAAC,OAAO,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IAC3C,CAAC;CACF"}
package/package.json ADDED
@@ -0,0 +1,82 @@
1
+ {
2
+ "name": "@misofm/effect",
3
+ "version": "0.1.0",
4
+ "packageManager": "bun@1.4.0",
5
+ "description": "Shared Effect foundation for the Miso SDKs: one SuiClient service, one error vocabulary, and the read/execute primitives every package builds on.",
6
+ "license": "Apache-2.0",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/misofm/sdks.git",
10
+ "directory": "packages/effect"
11
+ },
12
+ "homepage": "https://github.com/misofm/sdks#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/misofm/sdks/issues"
15
+ },
16
+ "keywords": [
17
+ "sui",
18
+ "effect",
19
+ "miso",
20
+ "blockchain",
21
+ "sdk"
22
+ ],
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
26
+ "module": "./dist/index.js",
27
+ "types": "./dist/index.d.ts",
28
+ "type": "module",
29
+ "sideEffects": false,
30
+ "scripts": {
31
+ "typecheck": "tsc --noEmit",
32
+ "test": "bun test",
33
+ "docs": "typedoc",
34
+ "build": "rm -rf dist && tsc -p tsconfig.build.json",
35
+ "prepack": "rm -rf dist && tsc -p tsconfig.build.json",
36
+ "prepare": "rm -rf dist && tsc -p tsconfig.build.json --noCheck"
37
+ },
38
+ "exports": {
39
+ ".": {
40
+ "types": "./dist/index.d.ts",
41
+ "default": "./dist/index.js"
42
+ },
43
+ "./errors": {
44
+ "types": "./dist/errors.d.ts",
45
+ "default": "./dist/errors.js"
46
+ },
47
+ "./sui-client": {
48
+ "types": "./dist/sui-client.d.ts",
49
+ "default": "./dist/sui-client.js"
50
+ },
51
+ "./reads": {
52
+ "types": "./dist/reads.d.ts",
53
+ "default": "./dist/reads.js"
54
+ },
55
+ "./execute": {
56
+ "types": "./dist/execute.d.ts",
57
+ "default": "./dist/execute.js"
58
+ },
59
+ "./package.json": "./package.json"
60
+ },
61
+ "files": [
62
+ "dist",
63
+ "src",
64
+ "README.md",
65
+ "LICENSE",
66
+ "package.json"
67
+ ],
68
+ "devDependencies": {
69
+ "@mysten/sui": "2.29.0",
70
+ "@types/bun": "^1.4.0",
71
+ "effect": "4.0.0-rc.112",
72
+ "typedoc": "^0.28.20",
73
+ "typedoc-plugin-markdown": "^4.13.0",
74
+ "typescript": "^7.0.2"
75
+ },
76
+ "peerDependencies": {
77
+ "@mysten/sui": "2.29.0",
78
+ "effect": "4.0.0-rc.112",
79
+ "typescript": "^5.7 || ^6 || ^7"
80
+ },
81
+ "main": "./dist/index.js"
82
+ }
package/src/errors.ts ADDED
@@ -0,0 +1,59 @@
1
+ // Copyright (c) Miso Labs, Inc.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ // The shared error vocabulary for every `@misofm/*` SDK. Every failure a caller
5
+ // might need to act on is a `Schema.TaggedError`, carrying the fields needed to
6
+ // act (object id, type, package, cause) rather than a bare message. Foreign
7
+ // failures (RPC rejections, thrown parsers) are wrapped with `cause: Schema.Defect()`
8
+ // so the original error is preserved but not part of the typed contract.
9
+
10
+ import { Schema } from "effect";
11
+ import type { SuiClientTypes } from "@mysten/sui/client";
12
+
13
+ /** The requested object does not exist on-chain (never existed, was deleted, or has no such dynamic field). */
14
+ export class ObjectNotFoundError extends Schema.TaggedError<ObjectNotFoundError>()("ObjectNotFoundError", {
15
+ objectId: Schema.String,
16
+ }) {}
17
+
18
+ /** An object's on-chain type does not match the type the caller expected. */
19
+ export class ObjectTypeMismatchError extends Schema.TaggedError<ObjectTypeMismatchError>()(
20
+ "ObjectTypeMismatchError",
21
+ {
22
+ objectId: Schema.String,
23
+ expected: Schema.String,
24
+ actual: Schema.String,
25
+ },
26
+ ) {}
27
+
28
+ /** A Core API call threw or rejected for a reason other than a missing object. */
29
+ export class SuiRpcError extends Schema.TaggedError<SuiRpcError>()("SuiRpcError", {
30
+ operation: Schema.String,
31
+ cause: Schema.Defect(),
32
+ }) {}
33
+
34
+ /** A generated BCS codec's `.parse` threw, or the parsed value failed `Schema.decodeUnknown` into the domain type. */
35
+ export class BcsDecodeError extends Schema.TaggedError<BcsDecodeError>()("BcsDecodeError", {
36
+ type: Schema.String,
37
+ objectId: Schema.optional(Schema.String),
38
+ cause: Schema.Defect(),
39
+ }) {}
40
+
41
+ /** A submitted transaction executed but its on-chain effects reported failure. */
42
+ export class TransactionFailedError extends Schema.TaggedError<TransactionFailedError>()("TransactionFailedError", {
43
+ digest: Schema.String,
44
+ status: Schema.declare((u): u is SuiClientTypes.ExecutionStatus => true),
45
+ }) {}
46
+
47
+ /** A `SuiGraphQL` read was requested but no GraphQL client is configured for this context. */
48
+ export class GraphQLUnavailableError extends Schema.TaggedError<GraphQLUnavailableError>()(
49
+ "GraphQLUnavailableError",
50
+ {},
51
+ ) {}
52
+
53
+ /** A deployment manifest failed validation (wrong shape, missing package id, unverified network, ...). */
54
+ export class DeploymentError extends Schema.TaggedError<DeploymentError>()("DeploymentError", {
55
+ message: Schema.String,
56
+ }) {}
57
+
58
+ /** The error union every `reads.ts` primitive can fail with. */
59
+ export type SuiReadError = ObjectNotFoundError | ObjectTypeMismatchError | SuiRpcError | BcsDecodeError;
package/src/execute.ts ADDED
@@ -0,0 +1,177 @@
1
+ // Copyright (c) Miso Labs, Inc.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ // Transaction building + execution + effect extraction (the Signer-parameter
5
+ // pattern). Builders elsewhere only *append* to a caller-owned Transaction;
6
+ // this module is where a transaction is actually built and submitted.
7
+ // `signAndExecute` submits over the unified Core API, unwraps the
8
+ // `{ $kind: "Transaction", Transaction }` envelope, waits for finality, and
9
+ // returns changed objects, the objectId→type map, balance changes, and net gas
10
+ // for downstream extraction. Everything here is transport-agnostic — it
11
+ // requires only the `SuiClient` service, so gRPC / JSON-RPC / GraphQL clients
12
+ // all work.
13
+
14
+ import { Effect } from "effect";
15
+ import { Transaction } from "@mysten/sui/transactions";
16
+ import type { SuiClientTypes } from "@mysten/sui/client";
17
+ import type { Signer } from "@mysten/sui/cryptography";
18
+
19
+ import { SuiRpcError, TransactionFailedError } from "./errors.ts";
20
+ import { SuiClient } from "./sui-client.ts";
21
+
22
+ /** A function that appends commands to a caller-owned `Transaction`; may be async. */
23
+ export type TxThunk = (tx: Transaction) => void | Promise<void>;
24
+
25
+ /** The effect fields every submit path requests, so extraction is uniform. */
26
+ export const FULL_INCLUDE = { effects: true, objectTypes: true, balanceChanges: true } as const;
27
+ type FullInclude = typeof FULL_INCLUDE;
28
+
29
+ /** The normalized outcome of a successfully-executed transaction. */
30
+ export interface ExecResult {
31
+ digest: string;
32
+ /** Objects created/mutated/deleted by the transaction. */
33
+ changedObjects: SuiClientTypes.ChangedObject[];
34
+ /** Map of changed objectId → fully-qualified type. */
35
+ objectTypes: Record<string, string>;
36
+ /** Net coin balance deltas by address. */
37
+ balanceChanges: SuiClientTypes.BalanceChange[];
38
+ /** Net gas cost in MIST (computation + storage − rebate). */
39
+ gasUsed: number;
40
+ }
41
+
42
+ /** Builds a fresh `Transaction` from one or more thunks (awaiting async ones); never fails. */
43
+ export const buildTx = Effect.fn("buildTx")(function* (...thunks: readonly TxThunk[]): Effect.fn.Return<Transaction> {
44
+ const tx = new Transaction();
45
+ for (const thunk of thunks) {
46
+ yield* Effect.promise(() => Promise.resolve(thunk(tx)));
47
+ }
48
+ return tx;
49
+ });
50
+
51
+ /** Normalizes the `{ $kind }` transaction-result envelope into an {@link ExecResult}; throws on a failed status. */
52
+ export function toExecResult(res: SuiClientTypes.TransactionResult<FullInclude>): ExecResult {
53
+ if (res.$kind !== "Transaction") {
54
+ const failed = res.FailedTransaction;
55
+ const status = failed.effects?.status;
56
+ const err = status && !status.success ? JSON.stringify(status.error) : "unknown error";
57
+ throw new Error(`Transaction failed: ${err} (digest ${failed.digest})`);
58
+ }
59
+
60
+ const t = res.Transaction;
61
+ const effects = t.effects as SuiClientTypes.TransactionEffects;
62
+ if (!effects.status.success) {
63
+ throw new Error(`Transaction reverted: ${JSON.stringify(effects.status.error)} (digest ${t.digest})`);
64
+ }
65
+
66
+ return {
67
+ digest: t.digest,
68
+ changedObjects: effects.changedObjects,
69
+ objectTypes: (t.objectTypes as Record<string, string>) ?? {},
70
+ balanceChanges: (t.balanceChanges as SuiClientTypes.BalanceChange[]) ?? [],
71
+ gasUsed: netGas(effects.gasUsed),
72
+ };
73
+ }
74
+
75
+ /** Signs, executes, and waits for a transaction; fails with `TransactionFailedError` if its effects report failure. */
76
+ export const signAndExecute = Effect.fn("signAndExecute")(function* (
77
+ signer: Signer,
78
+ tx: Transaction,
79
+ ): Effect.fn.Return<ExecResult, TransactionFailedError | SuiRpcError, SuiClient> {
80
+ const client = yield* SuiClient;
81
+ const res = yield* Effect.tryPromise({
82
+ try: (signal) => client.core.signAndExecuteTransaction({ transaction: tx, signer, include: FULL_INCLUDE, signal }),
83
+ catch: (cause) => new SuiRpcError({ operation: "signAndExecuteTransaction", cause }),
84
+ });
85
+
86
+ const { digest, status } = statusOf(res);
87
+ if (!status.success) {
88
+ return yield* new TransactionFailedError({ digest, status });
89
+ }
90
+
91
+ const result = toExecResult(res);
92
+
93
+ yield* Effect.tryPromise({
94
+ try: (signal) => client.core.waitForTransaction({ digest: result.digest, signal }),
95
+ catch: (cause) => new SuiRpcError({ operation: "waitForTransaction", cause }),
96
+ });
97
+
98
+ return result;
99
+ });
100
+
101
+ /** Convenience: build from thunks, then sign+execute in one call. */
102
+ export const execThunks = Effect.fn("execThunks")(function* (
103
+ signer: Signer,
104
+ ...thunks: readonly TxThunk[]
105
+ ): Effect.fn.Return<ExecResult, TransactionFailedError | SuiRpcError, SuiClient> {
106
+ const tx = yield* buildTx(...thunks);
107
+ return yield* signAndExecute(signer, tx);
108
+ });
109
+
110
+ // ── Object-change extractors (pure) ─────────────────────────────────────────
111
+
112
+ /** The package id from the (single) newly-published package. */
113
+ export function publishedPackageId(r: ExecResult): string {
114
+ const pkg = r.changedObjects.find((c) => c.idOperation === "Created" && c.outputState === "PackageWrite");
115
+ if (!pkg) throw new Error("No published package found in object changes.");
116
+ return pkg.objectId;
117
+ }
118
+
119
+ /** All package ids newly published by the transaction (up to 5 per PTB). */
120
+ export function allPublishedPackageIds(r: ExecResult): string[] {
121
+ return r.changedObjects
122
+ .filter((c) => c.idOperation === "Created" && c.outputState === "PackageWrite")
123
+ .map((c) => c.objectId);
124
+ }
125
+
126
+ /** The first newly-created object whose type contains `substr`. */
127
+ export function createdByType(r: ExecResult, substr: string): string {
128
+ const id = maybeCreatedByType(r, substr);
129
+ if (!id) throw new Error(`No created object with type containing "${substr}" found in object changes.`);
130
+ return id;
131
+ }
132
+
133
+ /** Like {@link createdByType} but returns undefined instead of throwing. */
134
+ export function maybeCreatedByType(r: ExecResult, substr: string): string | undefined {
135
+ for (const c of r.changedObjects) {
136
+ if (c.idOperation === "Created" && (r.objectTypes[c.objectId] ?? "").includes(substr)) return c.objectId;
137
+ }
138
+ return undefined;
139
+ }
140
+
141
+ /** The first newly-created object whose type is EXACTLY `type` (for non-generic types). */
142
+ export function createdByExactType(r: ExecResult, type: string): string {
143
+ for (const c of r.changedObjects) {
144
+ if (c.idOperation === "Created" && r.objectTypes[c.objectId] === type) return c.objectId;
145
+ }
146
+ throw new Error(`No created object of exact type "${type}" found in object changes.`);
147
+ }
148
+
149
+ /** All newly-created objects whose type contains `substr`. */
150
+ export function allCreatedByType(r: ExecResult, substr: string): { objectId: string; objectType: string }[] {
151
+ const out: { objectId: string; objectType: string }[] = [];
152
+ for (const c of r.changedObjects) {
153
+ const type = r.objectTypes[c.objectId] ?? "";
154
+ if (c.idOperation === "Created" && type.includes(substr)) out.push({ objectId: c.objectId, objectType: type });
155
+ }
156
+ return out;
157
+ }
158
+
159
+ /** The signed balance delta for `address` in `coinType`, or "0" if absent. */
160
+ export function balanceDelta(r: ExecResult, address: string, coinType: string): string {
161
+ const change = r.balanceChanges.find((b) => b.address === address && b.coinType === coinType);
162
+ return change?.amount ?? "0";
163
+ }
164
+
165
+ // ── Private ──────────────────────────────────────────────────────────────────
166
+
167
+ /** The digest and top-level execution status of a `TransactionResult`, whichever `$kind` it is. */
168
+ function statusOf(
169
+ res: SuiClientTypes.TransactionResult<FullInclude>,
170
+ ): { digest: string; status: SuiClientTypes.ExecutionStatus } {
171
+ const t = res.$kind === "Transaction" ? res.Transaction : res.FailedTransaction;
172
+ return { digest: t.digest, status: t.status };
173
+ }
174
+
175
+ function netGas(gasUsed: SuiClientTypes.GasCostSummary): number {
176
+ return Number(gasUsed.computationCost) + Number(gasUsed.storageCost) - Number(gasUsed.storageRebate);
177
+ }
package/src/index.ts ADDED
@@ -0,0 +1,10 @@
1
+ // Copyright (c) Miso Labs, Inc.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ // `@misofm/effect` — the shared Effect foundation for the Miso SDKs: one
5
+ // `SuiClient` service, one error vocabulary, and the read/execute primitives
6
+ // every package's queries and transaction builders are composed from.
7
+ export * from "./errors.ts";
8
+ export * from "./sui-client.ts";
9
+ export * from "./reads.ts";
10
+ export * from "./execute.ts";