Skip to content

Commit 27f5b4f

Browse files
feat: typed execute based off abi:
1 parent a8dc7c1 commit 27f5b4f

File tree

2 files changed

+451
-206
lines changed

2 files changed

+451
-206
lines changed

packages/sdk/src/execute.ts

+178
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
import { Contract, AccountInterface } from "starknet";
2+
3+
type AbiType =
4+
| "felt"
5+
| "felt*"
6+
| "core::integer::u8"
7+
| "core::integer::u16"
8+
| "core::integer::u32"
9+
| "core::integer::u64"
10+
| "core::integer::u128"
11+
| "core::integer::u256"
12+
| "core::bool"
13+
| "core::array::Array<felt>"
14+
| "core::array::Array<u256>"
15+
| string; // For custom types
16+
17+
type InputsType = ReadonlyArray<{ name: string; type: AbiType }>;
18+
type OutputsType = ReadonlyArray<{ type: AbiType }>;
19+
20+
interface FunctionAbi {
21+
type: "function";
22+
name: string;
23+
inputs: InputsType;
24+
outputs: OutputsType;
25+
state_mutability?: string;
26+
}
27+
28+
interface InterfaceAbi {
29+
type: "interface";
30+
name: string;
31+
items: FunctionAbi[];
32+
}
33+
34+
type AbiItem = FunctionAbi | InterfaceAbi | { type: string };
35+
36+
export interface ContractDefinition {
37+
kind: string;
38+
address: string;
39+
abi: readonly AbiItem[];
40+
systems?: readonly string[];
41+
tag: string;
42+
}
43+
44+
type MapAbiType<T extends AbiType> = T extends "felt"
45+
? string
46+
: T extends "felt*"
47+
? string[]
48+
: T extends
49+
| "core::integer::u8"
50+
| "core::integer::u16"
51+
| "core::integer::u32"
52+
| "core::integer::u64"
53+
? number
54+
: T extends "core::integer::u128" | "core::integer::u256"
55+
? bigint
56+
: T extends "core::bool"
57+
? boolean
58+
: T extends "core::array::Array<felt>"
59+
? string[]
60+
: T extends "core::array::Array<u256>"
61+
? bigint[]
62+
: string; // Default case for custom types
63+
64+
type MapInputType<T extends InputsType> = {
65+
[K in T[number] as K["name"]]: MapAbiType<K["type"]>;
66+
};
67+
68+
type MapOutputType<T extends OutputsType> = T["length"] extends 0
69+
? void
70+
: T["length"] extends 1
71+
? MapAbiType<T[0]["type"]>
72+
: { [K in keyof T]: MapAbiType<T[K]["type"]> };
73+
74+
type ExtractFunctions<T extends readonly AbiItem[]> = Extract<
75+
T[number],
76+
FunctionAbi
77+
>;
78+
79+
type ContractFunctions<T extends readonly AbiItem[]> = {
80+
[K in ExtractFunctions<T>["name"]]: (
81+
args: MapInputType<Extract<ExtractFunctions<T>, { name: K }>["inputs"]>
82+
) => Promise<
83+
MapOutputType<Extract<ExtractFunctions<T>, { name: K }>["outputs"]>
84+
>;
85+
};
86+
87+
export type WorldContracts<T extends readonly ContractDefinition[]> = {
88+
[K in T[number]["tag"]]: ContractFunctions<T[number]["abi"]>;
89+
};
90+
91+
export function createWorldProxy<T extends readonly ContractDefinition[]>(
92+
contractDefinitions: T,
93+
providerOrAccount: AccountInterface
94+
): WorldContracts<T> {
95+
const proxy = {} as WorldContracts<T>;
96+
97+
for (const contractDef of contractDefinitions) {
98+
const contract = new Contract(
99+
contractDef.abi as AbiItem[],
100+
contractDef.address,
101+
providerOrAccount
102+
);
103+
104+
(proxy as any)[contractDef.tag] = new Proxy(
105+
{} as ContractFunctions<typeof contractDef.abi>,
106+
{
107+
get: (target, prop: string) => {
108+
if (prop in contract.functions) {
109+
return async (args: any) => {
110+
const functionAbi = contractDef.abi.find(
111+
(item) =>
112+
item.type === "function" &&
113+
"name" in item &&
114+
item.name === prop
115+
) as FunctionAbi;
116+
const inputs = functionAbi.inputs.map(
117+
(input) => args[input.name]
118+
);
119+
return await contract.functions[prop](...inputs);
120+
};
121+
}
122+
return undefined;
123+
},
124+
}
125+
);
126+
}
127+
128+
return proxy;
129+
}
130+
131+
// Example usage
132+
const contractDefinitions = [
133+
{
134+
kind: "DojoContract",
135+
address:
136+
"0x25d128c5fe89696e7e15390ea58927bbed4290ae46b538b28cfc7c2190e378b",
137+
abi: [
138+
{
139+
type: "function",
140+
name: "spawn",
141+
inputs: [],
142+
outputs: [],
143+
state_mutability: "external",
144+
},
145+
{
146+
type: "function",
147+
name: "move",
148+
inputs: [
149+
{
150+
name: "direction",
151+
type: "dojo_starter::models::Direction",
152+
},
153+
],
154+
outputs: [],
155+
state_mutability: "external",
156+
},
157+
],
158+
systems: ["spawn", "move"],
159+
tag: "actions",
160+
},
161+
] as const;
162+
163+
// Assuming you have a provider or account set up
164+
const providerOrAccount: AccountInterface = {} as any; // replace with actual provider or account
165+
166+
const world = createWorldProxy(contractDefinitions, providerOrAccount);
167+
168+
// Usage example
169+
async function useWorld() {
170+
await world.actions.spawn({});
171+
await world.actions.move({ direction: "Left" });
172+
173+
// TypeScript will catch these errors:
174+
// @ts-expect-error
175+
await world.actions.nonexistentMethod();
176+
// @ts-expect-error
177+
await world.nonexistentContract.someMethod();
178+
}

0 commit comments

Comments
 (0)