Skip to content

Commit 2b35a6d

Browse files
committed
Write contract action
1 parent 3b4695d commit 2b35a6d

File tree

6 files changed

+370
-9
lines changed

6 files changed

+370
-9
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ Visit [http://localhost:3000](http://localhost:3000) to get started.
8282
- **Resend**: Send Email
8383
- **Slack**: Send Slack Message
8484
- **v0**: Create Chat, Send Message
85-
- **Web3**: Transfer Funds, Read Contract
85+
- **Web3**: Transfer Funds, Read Contract, Write Contract
8686
<!-- PLUGINS:END -->
8787

8888
## Code Generation

components/workflow/config/action-config-renderer.tsx

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ function SchemaBuilderField(props: FieldProps) {
116116

117117
type AbiFunctionSelectProps = FieldProps & {
118118
abiValue: string;
119+
functionFilter?: "read" | "write";
119120
};
120121

121122
function AbiFunctionSelectField({
@@ -124,6 +125,7 @@ function AbiFunctionSelectField({
124125
onChange,
125126
disabled,
126127
abiValue,
128+
functionFilter = "read",
127129
}: AbiFunctionSelectProps) {
128130
// Parse ABI and extract functions
129131
const functions = React.useMemo(() => {
@@ -137,12 +139,18 @@ function AbiFunctionSelectField({
137139
return [];
138140
}
139141

140-
// Extract only read-only functions (view/pure) from the ABI
142+
// Filter functions based on functionFilter prop
143+
const filterFn = functionFilter === "write"
144+
? (item: { type: string; stateMutability?: string }) =>
145+
item.type === "function" &&
146+
item.stateMutability !== "view" &&
147+
item.stateMutability !== "pure"
148+
: (item: { type: string; stateMutability?: string }) =>
149+
item.type === "function" &&
150+
(item.stateMutability === "view" || item.stateMutability === "pure");
151+
141152
return abi
142-
.filter((item) =>
143-
item.type === "function" &&
144-
(item.stateMutability === "view" || item.stateMutability === "pure")
145-
)
153+
.filter(filterFn)
146154
.map((func) => {
147155
const inputs = func.inputs || [];
148156
const params = inputs
@@ -160,7 +168,7 @@ function AbiFunctionSelectField({
160168
} catch {
161169
return [];
162170
}
163-
}, [abiValue]);
171+
}, [abiValue, functionFilter]);
164172

165173
if (functions.length === 0) {
166174
return (
@@ -341,6 +349,7 @@ function renderField(
341349
abiValue={abiValue}
342350
disabled={disabled}
343351
field={field}
352+
functionFilter={field.functionFilter}
344353
onChange={(val) => onUpdateConfig(field.key, val)}
345354
value={value}
346355
/>

components/workflow/node-config-panel.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -697,8 +697,9 @@ export const PanelInner = () => {
697697
{selectedNode.data.type !== "action" ||
698698
selectedNode.data.config?.actionType ? (
699699
<>
700-
{/* Hide Label and Description for read-contract action */}
701-
{selectedNode.data.config?.actionType !== "web3/read-contract" && (
700+
{/* Hide Label and Description for read-contract and write-contract actions */}
701+
{selectedNode.data.config?.actionType !== "web3/read-contract" &&
702+
selectedNode.data.config?.actionType !== "web3/write-contract" && (
702703
<>
703704
<div className="space-y-2">
704705
<Label className="ml-1" htmlFor="label">

plugins/registry.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ export type ActionConfigFieldBase = {
6262
// For abi-function-select: which field contains the ABI JSON
6363
abiField?: string;
6464

65+
// For abi-function-select: filter functions by type ("read" or "write")
66+
functionFilter?: "read" | "write";
67+
6568
// For abi-function-args: which field contains the ABI JSON and selected function
6669
abiFunctionField?: string;
6770
};

plugins/web3/index.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,77 @@ const web3Plugin: IntegrationPlugin = {
118118
},
119119
],
120120
},
121+
{
122+
slug: "write-contract",
123+
label: "Write Contract",
124+
description: "Write data to a smart contract (state-changing functions)",
125+
category: "Web3",
126+
stepFunction: "writeContractStep",
127+
stepImportPath: "write-contract",
128+
outputFields: [
129+
{
130+
field: "success",
131+
description: "Whether the contract call succeeded",
132+
},
133+
{
134+
field: "transactionHash",
135+
description: "The transaction hash of the successful write",
136+
},
137+
{
138+
field: "result",
139+
description: "The contract function return value (if any)",
140+
},
141+
{
142+
field: "error",
143+
description: "Error message if the call failed",
144+
},
145+
],
146+
configFields: [
147+
{
148+
key: "contractAddress",
149+
label: "Contract Address",
150+
type: "template-input",
151+
placeholder: "0x... or {{NodeName.contractAddress}}",
152+
example: "0x6B175474E89094C44Da98b954EedeAC495271d0F",
153+
required: true,
154+
},
155+
{
156+
key: "network",
157+
label: "Network",
158+
type: "select",
159+
placeholder: "Select network",
160+
required: true,
161+
options: [
162+
{ label: "Ethereum Mainnet", value: "mainnet" },
163+
{ label: "Sepolia Testnet", value: "sepolia" },
164+
],
165+
},
166+
{
167+
key: "abi",
168+
label: "Contract ABI",
169+
type: "template-textarea",
170+
placeholder: "Paste contract ABI JSON here",
171+
rows: 6,
172+
required: true,
173+
},
174+
{
175+
key: "abiFunction",
176+
label: "Function",
177+
type: "abi-function-select",
178+
abiField: "abi",
179+
functionFilter: "write",
180+
placeholder: "Select a function",
181+
required: true,
182+
},
183+
{
184+
key: "functionArgs",
185+
label: "Function Arguments",
186+
type: "abi-function-args",
187+
abiField: "abi",
188+
abiFunctionField: "abiFunction",
189+
},
190+
],
191+
},
121192
],
122193
};
123194

0 commit comments

Comments
 (0)