@fatsolutions/cairo-abi-codec 0.0.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/README.md ADDED
@@ -0,0 +1,157 @@
1
+ # @fatsolutions/cairo-abi-codec
2
+
3
+ Type-safe encode/decode for Cairo structs and enums to Starknet calldata.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pnpm add @fatsolutions/cairo-abi-codec
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```typescript
14
+ import { CairoOption, CairoOptionVariant, CairoCustomEnum } from "starknet";
15
+ import { createTypedCodec, type AbiType } from "@fatsolutions/cairo-abi-codec";
16
+
17
+ const abi = [
18
+ {
19
+ type: "struct",
20
+ name: "MyStruct",
21
+ members: [
22
+ { name: "id", type: "core::integer::u64" },
23
+ { name: "value", type: "core::felt252" },
24
+ ],
25
+ },
26
+ ] as const;
27
+
28
+ const codec = createTypedCodec(abi);
29
+
30
+ // Struct names are autocompleted, data is type-checked against the ABI
31
+ type MyStruct = AbiType<typeof abi, "MyStruct">;
32
+
33
+ const data: MyStruct = { id: 1n, value: 42n };
34
+ const encoded = codec.encode("MyStruct", data);
35
+ // => ['1', '42']
36
+
37
+ const decoded = codec.decode("MyStruct", encoded);
38
+ // => { id: 1n, value: 42n }
39
+ ```
40
+
41
+ ## Structs with Options
42
+
43
+ Include the `Option` enum definition in your ABI:
44
+
45
+ ```typescript
46
+ const abi = [
47
+ {
48
+ type: "struct",
49
+ name: "MyStruct",
50
+ members: [
51
+ { name: "id", type: "core::integer::u64" },
52
+ { name: "maybe_value", type: "core::option::Option::<core::integer::u64>" },
53
+ ],
54
+ },
55
+ {
56
+ type: "enum",
57
+ name: "core::option::Option::<core::integer::u64>",
58
+ variants: [
59
+ { name: "Some", type: "core::integer::u64" },
60
+ { name: "None", type: "()" },
61
+ ],
62
+ },
63
+ ] as const;
64
+
65
+ const codec = createTypedCodec(abi);
66
+
67
+ // AbiType infers: { id: bigint; maybe_value: CairoOption<bigint> }
68
+ const data = {
69
+ id: 1n,
70
+ maybe_value: new CairoOption(CairoOptionVariant.Some, 42n),
71
+ };
72
+
73
+ codec.encode("MyStruct", data);
74
+ // => ['1', '0', '42']
75
+ ```
76
+
77
+ ## Enums
78
+
79
+ Custom enums encode/decode as `CairoCustomEnum`:
80
+
81
+ ```typescript
82
+ const abi = [
83
+ {
84
+ type: "enum",
85
+ name: "Action",
86
+ variants: [
87
+ { name: "Move", type: "core::integer::u64" },
88
+ { name: "Stop", type: "()" },
89
+ ],
90
+ },
91
+ ] as const;
92
+
93
+ const codec = createTypedCodec(abi);
94
+
95
+ const encoded = codec.encode("Action", new CairoCustomEnum({ Move: 42n }));
96
+ // => ['0', '42']
97
+
98
+ const decoded = codec.decode("Action", encoded);
99
+ decoded.activeVariant(); // => 'Move'
100
+ decoded.unwrap(); // => 42n
101
+ ```
102
+
103
+ ## ContractAddress
104
+
105
+ Decoded `ContractAddress` fields are automatically formatted as `0x`-prefixed, zero-padded 64-char hex strings. This applies in all positions: top-level struct members, nested structs, Options, Arrays, and custom enum variants.
106
+
107
+ ```typescript
108
+ const decoded = codec.decode("Transfer", calldata);
109
+ decoded.sender; // => '0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7'
110
+ ```
111
+
112
+ ## ABI Narrowing
113
+
114
+ Use `narrowAbi` to filter a full contract ABI down to specific types, preserving type information:
115
+
116
+ ```typescript
117
+ import { narrowAbi, createTypedCodec } from "@fatsolutions/cairo-abi-codec";
118
+
119
+ const subset = narrowAbi(fullContractAbi, ["MyStruct", "MyEnum"] as const);
120
+ const codec = createTypedCodec(subset);
121
+ ```
122
+
123
+ ## API
124
+
125
+ ### `createTypedCodec(abi)`
126
+
127
+ Creates a reusable codec with `encode` and `decode` methods. Best when encoding/decoding multiple types from the same ABI.
128
+
129
+ ### `encodeTyped(abi, typeName, data)`
130
+
131
+ One-off encoding. Creates the codec internally.
132
+
133
+ ### `decodeTyped(abi, typeName, calldata)`
134
+
135
+ One-off decoding. Creates the codec internally.
136
+
137
+ ### `AbiType<TAbi, TName>`
138
+
139
+ Type utility to extract the TypeScript type for a struct or enum from the ABI.
140
+
141
+ ### `narrowAbi(abi, names)`
142
+
143
+ Filters an ABI to only the named struct/enum entries, preserving the const tuple type.
144
+
145
+ ## Important Notes
146
+
147
+ - **Use `as const`** on your ABI for type inference to work
148
+ - Integer types (`u64`, `u128`, `u256`, `felt252`) resolve to `bigint`
149
+ - `Option<T>` resolves to `CairoOption<T>`, custom enums to `CairoCustomEnum`
150
+ - `ContractAddress` decodes to `0x`-prefixed hex strings (64 chars, zero-padded)
151
+
152
+ ## Build & Test
153
+
154
+ ```bash
155
+ pnpm run build # tsc -> dist/
156
+ pnpm test # node:test with --experimental-strip-types
157
+ ```
@@ -0,0 +1,32 @@
1
+ import { type Abi } from "starknet";
2
+ /**
3
+ * Encodes a Cairo struct to calldata without needing a full contract ABI.
4
+ *
5
+ * @param structAbi - Array of ABI type definitions (structs, enums) needed to encode the data
6
+ * @param structType - The name of the root struct type to encode
7
+ * @param data - The data to encode
8
+ * @returns Encoded calldata as string array
9
+ */
10
+ export declare function encodeStruct(structAbi: Abi, structType: string, data: unknown): string[];
11
+ /**
12
+ * Decodes calldata back into a structured object.
13
+ *
14
+ * @param structAbi - Array of ABI type definitions (structs, enums) needed to decode the data
15
+ * @param structType - The name of the root struct type to decode
16
+ * @param calldata - The encoded calldata string array
17
+ * @returns Decoded struct data
18
+ */
19
+ export declare function decodeStruct(structAbi: Abi, structType: string, calldata: string[]): unknown;
20
+ /**
21
+ * Creates a reusable encoder/decoder for a specific struct type.
22
+ * Useful when encoding/decoding multiple instances of the same struct.
23
+ */
24
+ export declare function createStructCodec(structAbi: Abi, structType: string): {
25
+ encode(data: unknown): string[];
26
+ decode(calldata: string[]): unknown;
27
+ };
28
+ /**
29
+ * @deprecated Use `createStructCodec` instead.
30
+ */
31
+ export declare function createStructEncoder(structAbi: Abi, structType: string): (data: unknown) => string[];
32
+ //# sourceMappingURL=encoder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"encoder.d.ts","sourceRoot":"","sources":["../src/encoder.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,GAAG,EAAgB,MAAM,UAAU,CAAC;AAqB5D;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAC1B,SAAS,EAAE,GAAG,EACd,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,OAAO,GACZ,MAAM,EAAE,CAGV;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAC1B,SAAS,EAAE,GAAG,EACd,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAAE,GACjB,OAAO,CAIT;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM;iBAInD,OAAO,GAAG,MAAM,EAAE;qBAGd,MAAM,EAAE,GAAG,OAAO;EAItC;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,UAZrD,OAAO,KAAG,MAAM,EAAE,CAelC"}
@@ -0,0 +1,67 @@
1
+ import { CallData } from "starknet";
2
+ function wrapAbi(structAbi, structType) {
3
+ return [
4
+ {
5
+ type: "interface",
6
+ name: "__Wrapper__",
7
+ items: [
8
+ {
9
+ type: "function",
10
+ name: "__encode__",
11
+ inputs: [{ name: "data", type: structType }],
12
+ outputs: [{ type: structType }],
13
+ state_mutability: "external",
14
+ },
15
+ ],
16
+ },
17
+ ...structAbi,
18
+ ];
19
+ }
20
+ /**
21
+ * Encodes a Cairo struct to calldata without needing a full contract ABI.
22
+ *
23
+ * @param structAbi - Array of ABI type definitions (structs, enums) needed to encode the data
24
+ * @param structType - The name of the root struct type to encode
25
+ * @param data - The data to encode
26
+ * @returns Encoded calldata as string array
27
+ */
28
+ export function encodeStruct(structAbi, structType, data) {
29
+ const callData = new CallData(wrapAbi(structAbi, structType));
30
+ return callData.compile("__encode__", [data]);
31
+ }
32
+ /**
33
+ * Decodes calldata back into a structured object.
34
+ *
35
+ * @param structAbi - Array of ABI type definitions (structs, enums) needed to decode the data
36
+ * @param structType - The name of the root struct type to decode
37
+ * @param calldata - The encoded calldata string array
38
+ * @returns Decoded struct data
39
+ */
40
+ export function decodeStruct(structAbi, structType, calldata) {
41
+ const callData = new CallData(wrapAbi(structAbi, structType));
42
+ const result = callData.parse("__encode__", calldata);
43
+ return result;
44
+ }
45
+ /**
46
+ * Creates a reusable encoder/decoder for a specific struct type.
47
+ * Useful when encoding/decoding multiple instances of the same struct.
48
+ */
49
+ export function createStructCodec(structAbi, structType) {
50
+ const callData = new CallData(wrapAbi(structAbi, structType));
51
+ return {
52
+ encode(data) {
53
+ return callData.compile("__encode__", [data]);
54
+ },
55
+ decode(calldata) {
56
+ return callData.parse("__encode__", calldata);
57
+ },
58
+ };
59
+ }
60
+ /**
61
+ * @deprecated Use `createStructCodec` instead.
62
+ */
63
+ export function createStructEncoder(structAbi, structType) {
64
+ const codec = createStructCodec(structAbi, structType);
65
+ return codec.encode;
66
+ }
67
+ //# sourceMappingURL=encoder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"encoder.js","sourceRoot":"","sources":["../src/encoder.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAA0B,MAAM,UAAU,CAAC;AAE5D,SAAS,OAAO,CAAC,SAAc,EAAE,UAAkB;IACjD,OAAO;QACL;YACE,IAAI,EAAE,WAAW;YACjB,IAAI,EAAE,aAAa;YACnB,KAAK,EAAE;gBACL;oBACE,IAAI,EAAE,UAAU;oBAChB,IAAI,EAAE,YAAY;oBAClB,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;oBAC5C,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;oBAC/B,gBAAgB,EAAE,UAAU;iBAC7B;aACF;SACF;QACD,GAAG,SAAS;KACb,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAC1B,SAAc,EACd,UAAkB,EAClB,IAAa;IAEb,MAAM,QAAQ,GAAG,IAAI,QAAQ,CAAC,OAAO,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC,CAAC;IAC9D,OAAO,QAAQ,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC,IAAI,CAAY,CAAa,CAAC;AACvE,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAC1B,SAAc,EACd,UAAkB,EAClB,QAAkB;IAElB,MAAM,QAAQ,GAAG,IAAI,QAAQ,CAAC,OAAO,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC,CAAC;IAC9D,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC;IACtD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,SAAc,EAAE,UAAkB;IAClE,MAAM,QAAQ,GAAG,IAAI,QAAQ,CAAC,OAAO,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC,CAAC;IAE9D,OAAO;QACL,MAAM,CAAC,IAAa;YAClB,OAAO,QAAQ,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC,IAAI,CAAY,CAAa,CAAC;QACvE,CAAC;QACD,MAAM,CAAC,QAAkB;YACvB,OAAO,QAAQ,CAAC,KAAK,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC;QAChD,CAAC;KACF,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,mBAAmB,CAAC,SAAc,EAAE,UAAkB;IACpE,MAAM,KAAK,GAAG,iBAAiB,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;IACvD,OAAO,KAAK,CAAC,MAAM,CAAC;AACtB,CAAC"}
@@ -0,0 +1,52 @@
1
+ import type { Abi, ExtractAbiStructNames, ExtractAbiEnumNames, StringToPrimitiveType } from "abi-wan-kanabi/kanabi";
2
+ export type { Abi, ExtractAbiStructNames, ExtractAbiEnumNames } from "abi-wan-kanabi/kanabi";
3
+ /** Union of all struct and enum type names in the ABI. */
4
+ export type ExtractAbiTypeNames<TAbi extends Abi> = ExtractAbiStructNames<TAbi> | ExtractAbiEnumNames<TAbi>;
5
+ export type ExtractAbiType<TAbi extends Abi, K extends ExtractAbiTypeNames<TAbi>> = Extract<TAbi[number], {
6
+ type: "struct" | "enum";
7
+ name: K;
8
+ }>;
9
+ export type FilterTuple<T extends readonly any[], Match> = T extends readonly [infer Head, ...infer Tail] ? Head extends Match ? [Head, ...FilterTuple<Tail, Match>] : FilterTuple<Tail, Match> : [];
10
+ export type NarrowedAbi<TAbi extends Abi, TNames extends readonly ExtractAbiTypeNames<TAbi>[]> = FilterTuple<TAbi, {
11
+ name: TNames[number];
12
+ }>;
13
+ export declare function narrowAbi<TAbi extends Abi, const TNames extends readonly ExtractAbiTypeNames<TAbi>[]>(abi: TAbi, names: TNames): FilterTuple<TAbi, {
14
+ name: TNames[number];
15
+ }>;
16
+ /**
17
+ * Gets the TypeScript type for a struct or enum defined in the ABI.
18
+ * starknet.js configures abi-wan-kanabi to use CairoOption<T> for Options
19
+ * and CairoCustomEnum for custom enums via module declaration merging.
20
+ */
21
+ export type AbiType<TAbi extends Abi, TName extends ExtractAbiTypeNames<TAbi>> = StringToPrimitiveType<TAbi, TName>;
22
+ /** @deprecated Use `AbiType` instead. */
23
+ export type StructType<TAbi extends Abi, TStructName extends ExtractAbiStructNames<TAbi>> = StringToPrimitiveType<TAbi, TStructName>;
24
+ /**
25
+ * Creates a type-safe codec for structs and enums defined in an ABI.
26
+ * Uses abi-wan-kanabi for type inference.
27
+ *
28
+ * @example
29
+ * const codec = createTypedCodec(abi);
30
+ * const encoded = codec.encode("MyStruct", myData);
31
+ * const decoded = codec.decode("MyStruct", encoded);
32
+ * const enumEncoded = codec.encode("Direction", myEnum);
33
+ */
34
+ export declare function createTypedCodec<TAbi extends Abi>(abi: TAbi): {
35
+ encode<TName extends ExtractAbiTypeNames<TAbi>>(typeName: TName, data: AbiType<TAbi, TName>): string[];
36
+ decode<TName extends ExtractAbiTypeNames<TAbi>>(typeName: TName, calldata: string[]): AbiType<TAbi, TName>;
37
+ };
38
+ /**
39
+ * One-off type-safe encoding.
40
+ */
41
+ export declare function encodeTyped<TAbi extends Abi, TName extends ExtractAbiTypeNames<TAbi>>(abi: TAbi, typeName: TName, data: AbiType<TAbi, TName>): string[];
42
+ /**
43
+ * One-off type-safe decoding.
44
+ */
45
+ export declare function decodeTyped<TAbi extends Abi, TName extends ExtractAbiTypeNames<TAbi>>(abi: TAbi, typeName: TName, calldata: string[]): AbiType<TAbi, TName>;
46
+ /** @deprecated Use `createTypedCodec` instead. */
47
+ export declare const createTypedEncoder: typeof createTypedCodec;
48
+ /** @deprecated Use `encodeTyped` instead. */
49
+ export declare const encodeStructTyped: typeof encodeTyped;
50
+ /** @deprecated Use `decodeTyped` instead. */
51
+ export declare const decodeStructTyped: typeof decodeTyped;
52
+ //# sourceMappingURL=typed-encoder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"typed-encoder.d.ts","sourceRoot":"","sources":["../src/typed-encoder.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,190 @@
1
+ import { CallData, CairoOption, CairoCustomEnum, CairoResult, } from "starknet";
2
+ export function narrowAbi(abi, names) {
3
+ return abi.filter((item) => (item.type === "struct" || item.type === "enum") &&
4
+ names.includes(item.name));
5
+ }
6
+ // ============================================================================
7
+ // Internal Helpers
8
+ // ============================================================================
9
+ function buildWrappedAbi(abi) {
10
+ return [
11
+ {
12
+ type: "interface",
13
+ name: "__Wrapper__",
14
+ items: [
15
+ {
16
+ type: "function",
17
+ name: "__codec__",
18
+ inputs: [{ name: "data", type: "__PLACEHOLDER__" }],
19
+ outputs: [{ type: "__PLACEHOLDER__" }],
20
+ state_mutability: "external",
21
+ },
22
+ ],
23
+ },
24
+ ...abi,
25
+ ];
26
+ }
27
+ function patchAbi(wrappedAbi, structName) {
28
+ return wrappedAbi.map((item) => {
29
+ if (item.type === "interface" && item.name === "__Wrapper__") {
30
+ return {
31
+ ...item,
32
+ items: item.items.map((fn) => ({
33
+ ...fn,
34
+ inputs: [{ name: "data", type: structName }],
35
+ outputs: [{ type: structName }],
36
+ })),
37
+ };
38
+ }
39
+ return item;
40
+ });
41
+ }
42
+ const ADDRESS_TYPES = new Set([
43
+ "core::starknet::contract_address::ContractAddress",
44
+ "core::starknet::eth_address::EthAddress",
45
+ ]);
46
+ function toChecksumAddress(value) {
47
+ return "0x" + BigInt(value).toString(16).padStart(64, "0");
48
+ }
49
+ /**
50
+ * Build a lookup of struct and enum member/variant types from the ABI.
51
+ */
52
+ function buildTypeMap(abi) {
53
+ const typeMap = new Map();
54
+ for (const entry of abi) {
55
+ if (entry.type === "struct" && "members" in entry) {
56
+ const members = new Map();
57
+ for (const m of entry.members) {
58
+ members.set(m.name, m.type);
59
+ }
60
+ typeMap.set(entry.name, { kind: "struct", members });
61
+ }
62
+ else if (entry.type === "enum" && "variants" in entry) {
63
+ const variants = new Map();
64
+ for (const v of entry.variants) {
65
+ variants.set(v.name, v.type);
66
+ }
67
+ typeMap.set(entry.name, { kind: "enum", variants });
68
+ }
69
+ }
70
+ return typeMap;
71
+ }
72
+ const OPTION_RE = /^core::option::Option::<(.+)>$/;
73
+ const RESULT_RE = /^core::result::Result::<(.+),\s*(.+)>$/;
74
+ const ARRAY_RE = /^core::array::(?:Array|Span)::<(.+)>$/;
75
+ function extractInnerType(cairoType) {
76
+ let m = OPTION_RE.exec(cairoType);
77
+ if (m)
78
+ return { wrapper: "option", inner: [m[1]] };
79
+ m = RESULT_RE.exec(cairoType);
80
+ if (m)
81
+ return { wrapper: "result", inner: [m[1], m[2]] };
82
+ m = ARRAY_RE.exec(cairoType);
83
+ if (m)
84
+ return { wrapper: "array", inner: [m[1]] };
85
+ return null;
86
+ }
87
+ /**
88
+ * Recursively walk a decoded value and transform address fields
89
+ * from bigint/number to 0x-prefixed, zero-padded hex strings.
90
+ * Handles structs, Options, Results, CustomEnums, and arrays.
91
+ */
92
+ function transformAddresses(value, cairoType, typeMap) {
93
+ // Direct address type
94
+ if (ADDRESS_TYPES.has(cairoType)) {
95
+ return toChecksumAddress(value);
96
+ }
97
+ // Generic wrappers: Option<T>, Result<T,E>, Array<T>
98
+ const generic = extractInnerType(cairoType);
99
+ if (generic) {
100
+ if (generic.wrapper === "option" && value instanceof CairoOption) {
101
+ if (value.isNone())
102
+ return value;
103
+ const inner = transformAddresses(value.unwrap(), generic.inner[0], typeMap);
104
+ return new CairoOption(0, inner); // 0 = Some
105
+ }
106
+ if (generic.wrapper === "result" && value instanceof CairoResult) {
107
+ if (value.isOk()) {
108
+ const inner = transformAddresses(value.unwrap(), generic.inner[0], typeMap);
109
+ return new CairoResult(0, inner); // 0 = Ok
110
+ }
111
+ const inner = transformAddresses(value.unwrap(), generic.inner[1], typeMap);
112
+ return new CairoResult(1, inner); // 1 = Err
113
+ }
114
+ if (generic.wrapper === "array" && Array.isArray(value)) {
115
+ return value.map((el) => transformAddresses(el, generic.inner[0], typeMap));
116
+ }
117
+ }
118
+ const info = typeMap.get(cairoType);
119
+ if (!info || typeof value !== "object" || value === null)
120
+ return value;
121
+ // Struct: transform each member
122
+ if (info.kind === "struct") {
123
+ const result = {};
124
+ for (const [key, val] of Object.entries(value)) {
125
+ const memberType = info.members.get(key);
126
+ result[key] = memberType ? transformAddresses(val, memberType, typeMap) : val;
127
+ }
128
+ return result;
129
+ }
130
+ // Custom enum (CairoCustomEnum): transform the active variant's data
131
+ if (info.kind === "enum" && value instanceof CairoCustomEnum) {
132
+ const active = value.activeVariant();
133
+ const variantType = info.variants.get(active);
134
+ if (variantType && variantType !== "()") {
135
+ const transformed = transformAddresses(value.unwrap(), variantType, typeMap);
136
+ return new CairoCustomEnum({ [active]: transformed });
137
+ }
138
+ return value;
139
+ }
140
+ return value;
141
+ }
142
+ // ============================================================================
143
+ // Type-Safe Codec
144
+ // ============================================================================
145
+ /**
146
+ * Creates a type-safe codec for structs and enums defined in an ABI.
147
+ * Uses abi-wan-kanabi for type inference.
148
+ *
149
+ * @example
150
+ * const codec = createTypedCodec(abi);
151
+ * const encoded = codec.encode("MyStruct", myData);
152
+ * const decoded = codec.decode("MyStruct", encoded);
153
+ * const enumEncoded = codec.encode("Direction", myEnum);
154
+ */
155
+ export function createTypedCodec(abi) {
156
+ const wrappedAbi = buildWrappedAbi(abi);
157
+ const typeMap = buildTypeMap(abi);
158
+ return {
159
+ encode(typeName, data) {
160
+ const patchedAbi = patchAbi(wrappedAbi, typeName);
161
+ const callData = new CallData(patchedAbi);
162
+ return callData.compile("__codec__", [data]);
163
+ },
164
+ decode(typeName, calldata) {
165
+ const patchedAbi = patchAbi(wrappedAbi, typeName);
166
+ const callData = new CallData(patchedAbi);
167
+ const raw = callData.parse("__codec__", calldata);
168
+ return transformAddresses(raw, typeName, typeMap);
169
+ },
170
+ };
171
+ }
172
+ /**
173
+ * One-off type-safe encoding.
174
+ */
175
+ export function encodeTyped(abi, typeName, data) {
176
+ return createTypedCodec(abi).encode(typeName, data);
177
+ }
178
+ /**
179
+ * One-off type-safe decoding.
180
+ */
181
+ export function decodeTyped(abi, typeName, calldata) {
182
+ return createTypedCodec(abi).decode(typeName, calldata);
183
+ }
184
+ /** @deprecated Use `createTypedCodec` instead. */
185
+ export const createTypedEncoder = createTypedCodec;
186
+ /** @deprecated Use `encodeTyped` instead. */
187
+ export const encodeStructTyped = encodeTyped;
188
+ /** @deprecated Use `decodeTyped` instead. */
189
+ export const decodeStructTyped = decodeTyped;
190
+ //# sourceMappingURL=typed-encoder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"typed-encoder.js","sourceRoot":"","sources":["../src/typed-encoder.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,QAAQ,EACR,WAAW,EACX,eAAe,EACf,WAAW,GAGZ,MAAM,UAAU,CAAC;AAmClB,MAAM,UAAU,SAAS,CAGvB,GAAS,EAAE,KAAa;IAExB,OAAO,GAAG,CAAC,MAAM,CACf,CAAC,IAAI,EAAgD,EAAE,CACrD,CAAC,IAAI,CAAC,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,CAAC;QAC/C,KAA2B,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CACH,CAAC;AACpD,CAAC;AAmBD,+EAA+E;AAC/E,mBAAmB;AACnB,+EAA+E;AAE/E,SAAS,eAAe,CAAmB,GAAS;IAClD,OAAO;QACL;YACE,IAAI,EAAE,WAAoB;YAC1B,IAAI,EAAE,aAAa;YACnB,KAAK,EAAE;gBACL;oBACE,IAAI,EAAE,UAAmB;oBACzB,IAAI,EAAE,WAAW;oBACjB,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,EAAE,CAAC;oBACnD,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,iBAAiB,EAAE,CAAC;oBACtC,gBAAgB,EAAE,UAAmB;iBACtC;aACF;SACF;QACD,GAAG,GAAG;KACP,CAAC;AACJ,CAAC;AAED,SAAS,QAAQ,CACf,UAA8C,EAC9C,UAAkB;IAElB,OAAO,UAAU,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QAC7B,IAAI,IAAI,CAAC,IAAI,KAAK,WAAW,IAAI,IAAI,CAAC,IAAI,KAAK,aAAa,EAAE,CAAC;YAC7D,OAAO;gBACL,GAAG,IAAI;gBACP,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;oBAC7B,GAAG,EAAE;oBACL,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;oBAC5C,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;iBAChC,CAAC,CAAC;aACJ,CAAC;QACJ,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC;IAC5B,mDAAmD;IACnD,yCAAyC;CAC1C,CAAC,CAAC;AAEH,SAAS,iBAAiB,CAAC,KAA+B;IACxD,OAAO,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;AAC7D,CAAC;AAMD;;GAEG;AACH,SAAS,YAAY,CAAC,GAAgB;IACpC,MAAM,OAAO,GAAG,IAAI,GAAG,EAAoB,CAAC;IAC5C,KAAK,MAAM,KAAK,IAAI,GAAG,EAAE,CAAC;QACxB,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,SAAS,IAAI,KAAK,EAAE,CAAC;YAClD,MAAM,OAAO,GAAG,IAAI,GAAG,EAAkB,CAAC;YAC1C,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;gBAC9B,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;YAC9B,CAAC;YACD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC;QACvD,CAAC;aAAM,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,IAAI,UAAU,IAAI,KAAK,EAAE,CAAC;YACxD,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAkB,CAAC;YAC3C,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;gBAC/B,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;YAC/B,CAAC;YACD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;QACtD,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,SAAS,GAAG,gCAAgC,CAAC;AACnD,MAAM,SAAS,GAAG,wCAAwC,CAAC;AAC3D,MAAM,QAAQ,GAAG,uCAAuC,CAAC;AAEzD,SAAS,gBAAgB,CAAC,SAAiB;IACzC,IAAI,CAAC,GAAG,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAClC,IAAI,CAAC;QAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACnD,CAAC,GAAG,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAC9B,IAAI,CAAC;QAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACzD,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAC7B,IAAI,CAAC;QAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAClD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;GAIG;AACH,SAAS,kBAAkB,CACzB,KAAc,EACd,SAAiB,EACjB,OAA8B;IAE9B,sBAAsB;IACtB,IAAI,aAAa,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;QACjC,OAAO,iBAAiB,CAAC,KAAiC,CAAC,CAAC;IAC9D,CAAC;IAED,qDAAqD;IACrD,MAAM,OAAO,GAAG,gBAAgB,CAAC,SAAS,CAAC,CAAC;IAC5C,IAAI,OAAO,EAAE,CAAC;QACZ,IAAI,OAAO,CAAC,OAAO,KAAK,QAAQ,IAAI,KAAK,YAAY,WAAW,EAAE,CAAC;YACjE,IAAI,KAAK,CAAC,MAAM,EAAE;gBAAE,OAAO,KAAK,CAAC;YACjC,MAAM,KAAK,GAAG,kBAAkB,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;YAC5E,OAAO,IAAI,WAAW,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,WAAW;QAC/C,CAAC;QACD,IAAI,OAAO,CAAC,OAAO,KAAK,QAAQ,IAAI,KAAK,YAAY,WAAW,EAAE,CAAC;YACjE,IAAI,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;gBACjB,MAAM,KAAK,GAAG,kBAAkB,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;gBAC5E,OAAO,IAAI,WAAW,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,SAAS;YAC7C,CAAC;YACD,MAAM,KAAK,GAAG,kBAAkB,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;YAC5E,OAAO,IAAI,WAAW,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,UAAU;QAC9C,CAAC;QACD,IAAI,OAAO,CAAC,OAAO,KAAK,OAAO,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YACxD,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,kBAAkB,CAAC,EAAE,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;QAC9E,CAAC;IACH,CAAC;IAED,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IACpC,IAAI,CAAC,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAEvE,gCAAgC;IAChC,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC3B,MAAM,MAAM,GAA4B,EAAE,CAAC;QAC3C,KAAK,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAgC,CAAC,EAAE,CAAC;YAC1E,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACzC,MAAM,CAAC,GAAG,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,kBAAkB,CAAC,GAAG,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;QAChF,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,qEAAqE;IACrE,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,IAAI,KAAK,YAAY,eAAe,EAAE,CAAC;QAC7D,MAAM,MAAM,GAAG,KAAK,CAAC,aAAa,EAAE,CAAC;QACrC,MAAM,WAAW,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAC9C,IAAI,WAAW,IAAI,WAAW,KAAK,IAAI,EAAE,CAAC;YACxC,MAAM,WAAW,GAAG,kBAAkB,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC;YAC7E,OAAO,IAAI,eAAe,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC;QACxD,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED,+EAA+E;AAC/E,kBAAkB;AAClB,+EAA+E;AAG/E;;;;;;;;;GASG;AACH,MAAM,UAAU,gBAAgB,CAAmB,GAAS;IAC1D,MAAM,UAAU,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC;IACxC,MAAM,OAAO,GAAG,YAAY,CAAC,GAA6B,CAAC,CAAC;IAE5D,OAAO;QACL,MAAM,CACJ,QAAe,EACf,IAA0B;YAE1B,MAAM,UAAU,GAAG,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;YAClD,MAAM,QAAQ,GAAG,IAAI,QAAQ,CAAC,UAAU,CAAC,CAAC;YAC1C,OAAO,QAAQ,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,IAAI,CAAY,CAAa,CAAC;QACtE,CAAC;QAED,MAAM,CACJ,QAAe,EACf,QAAkB;YAElB,MAAM,UAAU,GAAG,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;YAClD,MAAM,QAAQ,GAAG,IAAI,QAAQ,CAAC,UAAU,CAAC,CAAC;YAC1C,MAAM,GAAG,GAAG,QAAQ,CAAC,KAAK,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;YAClD,OAAO,kBAAkB,CAAC,GAAG,EAAE,QAAQ,EAAE,OAAO,CAAyB,CAAC;QAC5E,CAAC;KACF,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,WAAW,CAGzB,GAAS,EAAE,QAAe,EAAE,IAA0B;IACtD,OAAO,gBAAgB,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;AACtD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,WAAW,CAGzB,GAAS,EAAE,QAAe,EAAE,QAAkB;IAC9C,OAAO,gBAAgB,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;AAC1D,CAAC;AAED,kDAAkD;AAClD,MAAM,CAAC,MAAM,kBAAkB,GAAG,gBAAgB,CAAC;AACnD,6CAA6C;AAC7C,MAAM,CAAC,MAAM,iBAAiB,GAAG,WAAW,CAAC;AAC7C,6CAA6C;AAC7C,MAAM,CAAC,MAAM,iBAAiB,GAAG,WAAW,CAAC"}
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@fatsolutions/cairo-abi-codec",
3
+ "version": "0.0.1",
4
+ "description": "Encode/decode arbitrary Cairo structs to calldata",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/fatlabsxyz/cairo-abi-codec.git"
8
+ },
9
+ "publishConfig": {
10
+ "access": "public",
11
+ "registry": "https://registry.npmjs.org"
12
+ },
13
+ "type": "module",
14
+ "main": "dist/typed-encoder.js",
15
+ "types": "dist/typed-encoder.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./dist/typed-encoder.d.ts",
19
+ "default": "./dist/typed-encoder.js"
20
+ }
21
+ },
22
+ "files": [
23
+ "dist"
24
+ ],
25
+ "keywords": [
26
+ "starknet",
27
+ "cairo",
28
+ "encoding",
29
+ "calldata",
30
+ "abi"
31
+ ],
32
+ "author": "",
33
+ "license": "ISC",
34
+ "devDependencies": {
35
+ "@changesets/cli": "^2.30.0",
36
+ "@tsconfig/node22": "^22.0.5",
37
+ "@types/node": "^25.5.0",
38
+ "typescript": "^6.0.2"
39
+ },
40
+ "dependencies": {
41
+ "abi-wan-kanabi": "^2.2.4",
42
+ "starknet": "^9.4.2"
43
+ },
44
+ "scripts": {
45
+ "build": "tsc",
46
+ "test": "node --test --experimental-strip-types 'src/**/*.test.ts'",
47
+ "changeset": "changeset",
48
+ "changeset:publish": "changeset publish",
49
+ "changeset:version": "changeset version && pnpm install --ignore-scripts"
50
+ }
51
+ }