Skip to content

Commit a540baf

Browse files
committed
feat(cli): add erc1155 to deposit commands
1 parent 20e1630 commit a540baf

3 files changed

Lines changed: 354 additions & 2 deletions

File tree

.changeset/calm-ends-follow.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@cartesi/cli": minor
3+
---
4+
5+
Added support for single and batch erc1155 deposit to applications via the cli

apps/cli/src/commands/deposit.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import { Command } from "@commander-js/extra-typings";
22
import select from "@inquirer/select";
3+
import {
4+
createErc1155BatchCommand,
5+
createErc1155SingleCommand,
6+
} from "./deposit/erc1155.js";
37
import { createErc20Command } from "./deposit/erc20.js";
48
import { createErc721Command } from "./deposit/erc721.js";
59
import { createEtherCommand } from "./deposit/ether.js";
@@ -38,8 +42,8 @@ export const createDepositCommand = () => {
3842
command.addCommand(createEtherCommand());
3943
command.addCommand(createErc20Command());
4044
command.addCommand(createErc721Command());
41-
// command.addCommand(createErc1155BatchCommand());
42-
// command.addCommand(createErc1155SingleCommand());
45+
command.addCommand(createErc1155SingleCommand());
46+
command.addCommand(createErc1155BatchCommand());
4347

4448
return command;
4549
};
Lines changed: 343 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,343 @@
1+
import { Command } from "@commander-js/extra-typings";
2+
import input from "@inquirer/input";
3+
import chalk from "chalk";
4+
import ora from "ora";
5+
import { type Address, getAddress, isAddress, isHex } from "viem";
6+
import { getProjectName } from "../../base.js";
7+
import {
8+
erc1155BatchPortalAbi,
9+
erc1155BatchPortalAddress,
10+
erc1155SinglePortalAbi,
11+
erc1155SinglePortalAddress,
12+
testMultiTokenAbi,
13+
testMultiTokenAddress,
14+
} from "../../contracts.js";
15+
import {
16+
addressInput,
17+
bigintInput,
18+
getInputApplicationAddress,
19+
} from "../../prompts.js";
20+
import { connect } from "../../wallet.js";
21+
import type { DepositCommandOpts } from "../deposit.js";
22+
23+
type ERC1155Token = {
24+
address: Address;
25+
name: string;
26+
};
27+
28+
const parseToken = async (options: {
29+
token?: string;
30+
}): Promise<ERC1155Token> => {
31+
const address =
32+
options.token && isAddress(options.token)
33+
? getAddress(options.token)
34+
: await addressInput({
35+
message: "Token address",
36+
default: testMultiTokenAddress,
37+
});
38+
39+
return { address, name: "TestMultiToken" };
40+
};
41+
42+
export const createErc1155SingleCommand = () => {
43+
return new Command<[], Record<string, never>, DepositCommandOpts>(
44+
"erc1155-single",
45+
)
46+
.description("Deposits an ERC1155 token to the application")
47+
.configureHelp({ showGlobalOptions: true })
48+
.argument("[token-id]", "token ID")
49+
.argument("[amount]", "amount to send")
50+
.option("--token <address>", "ERC1155 token address")
51+
.option("--base-layer-data <hex>", "base layer data", "0x")
52+
.option("--exec-layer-data <hex>", "exec layer data", "0x")
53+
.action(async (tokenIdString, amountString, options, command) => {
54+
const { from } = command.optsWithGlobals();
55+
56+
// connect to anvil
57+
const testClient = await connect(command.optsWithGlobals());
58+
59+
const token = await parseToken({
60+
token: options.token,
61+
});
62+
63+
const tokenId = tokenIdString
64+
? BigInt(tokenIdString)
65+
: await bigintInput({ decimals: 0, message: "Token ID" });
66+
const projectName = getProjectName(command.optsWithGlobals());
67+
68+
// get dapp address from local node, or ask
69+
const application = await getInputApplicationAddress({
70+
...command.optsWithGlobals(),
71+
projectName,
72+
});
73+
74+
// the input sender, impersonated
75+
const account =
76+
from && isAddress(from)
77+
? getAddress(from)
78+
: (await testClient.getAddresses())[0];
79+
80+
const amount = amountString
81+
? BigInt(amountString)
82+
: await bigintInput({
83+
message: `Amount of token ID ${tokenId}`,
84+
decimals: 0,
85+
});
86+
87+
// ensure amount is positive
88+
if (amount <= BigInt(0)) {
89+
console.error(
90+
chalk.red(
91+
"Amount of tokens to be deposited, must be greater than zero.",
92+
),
93+
);
94+
return;
95+
}
96+
97+
const tokenAbi = testMultiTokenAbi;
98+
99+
const baseLayerData = isHex(options.baseLayerData)
100+
? options.baseLayerData
101+
: "0x";
102+
const execLayerData = isHex(options.execLayerData)
103+
? options.execLayerData
104+
: "0x";
105+
106+
// progress spinner
107+
const progress = ora();
108+
109+
// check balance
110+
const balance = await testClient.readContract({
111+
abi: tokenAbi,
112+
address: token.address,
113+
functionName: "balanceOf",
114+
args: [account, tokenId],
115+
});
116+
if (balance < amount) {
117+
progress.fail("Insufficient balance");
118+
return;
119+
}
120+
121+
// check if sufficiently approved
122+
const isApproved = await testClient.readContract({
123+
abi: tokenAbi,
124+
address: token.address,
125+
functionName: "isApprovedForAll",
126+
args: [account, erc1155SinglePortalAddress],
127+
});
128+
129+
// approve if needed
130+
if (isApproved === false) {
131+
progress.start(`Approving ERC1155Portal...`);
132+
const { request } = await testClient.simulateContract({
133+
abi: tokenAbi,
134+
account,
135+
address: token.address,
136+
functionName: "setApprovalForAll",
137+
args: [erc1155SinglePortalAddress, true],
138+
});
139+
const hash = await testClient.writeContract(request);
140+
await testClient.waitForTransactionReceipt({ hash });
141+
progress.succeed(`Approved ERC1155Portal`);
142+
}
143+
144+
// simulate deposit call
145+
const { request } = await testClient.simulateContract({
146+
abi: erc1155SinglePortalAbi,
147+
account,
148+
address: erc1155SinglePortalAddress,
149+
functionName: "depositSingleERC1155Token",
150+
args: [
151+
token.address,
152+
application,
153+
tokenId,
154+
amount,
155+
baseLayerData,
156+
execLayerData,
157+
],
158+
});
159+
160+
// for messages
161+
const amountLabel = `${chalk.cyan(amount)} units of token id ${tokenId}`;
162+
163+
// send deposit
164+
progress.start(
165+
`Depositing ${amountLabel} to ${chalk.cyan(application)}...`,
166+
);
167+
const hash = await testClient.writeContract(request);
168+
await testClient.waitForTransactionReceipt({ hash });
169+
progress.succeed(
170+
`Deposited ${amountLabel} to ${chalk.cyan(application)}`,
171+
);
172+
});
173+
};
174+
175+
export const createErc1155BatchCommand = () => {
176+
return new Command<[], Record<string, never>, DepositCommandOpts>(
177+
"erc1155-batch",
178+
)
179+
.description("Deposits multiple ERC1155 tokens to the application")
180+
.configureHelp({ showGlobalOptions: true })
181+
.argument("[token-ids]", "token IDs separated by comma")
182+
.argument("[amounts]", "amounts separated by comma")
183+
.option("--token <address>", "ERC1155 token address")
184+
.option("--base-layer-data <hex>", "base layer data", "0x")
185+
.option("--exec-layer-data <hex>", "exec layer data", "0x")
186+
.action(async (tokenIdsString, amountsString, options, command) => {
187+
const { from } = command.optsWithGlobals();
188+
189+
// connect to anvil
190+
const testClient = await connect(command.optsWithGlobals());
191+
192+
const token = await parseToken({
193+
token: options.token,
194+
});
195+
196+
// Parse token IDs
197+
let tokenIds: bigint[];
198+
199+
if (tokenIdsString) {
200+
tokenIds = tokenIdsString
201+
.split(",")
202+
.map((id) => BigInt(id.trim()));
203+
} else {
204+
// Prompt for token IDs if not provided
205+
const user_input = await input({
206+
message: "Token IDs (comma separated)",
207+
});
208+
tokenIds = user_input.split(",").map((id) => BigInt(id.trim()));
209+
}
210+
211+
// Parse amounts
212+
let amounts: bigint[];
213+
214+
if (amountsString) {
215+
amounts = amountsString.split(",").map((a) => BigInt(a.trim()));
216+
} else {
217+
// Prompt for amounts if not provided
218+
const user_input = await input({
219+
message: "Amounts (comma separated)",
220+
});
221+
amounts = user_input.split(",").map((a) => BigInt(a.trim()));
222+
}
223+
224+
if (tokenIds.length !== amounts.length) {
225+
console.error(
226+
chalk.red(
227+
"Token IDs and amounts must have the same length.",
228+
),
229+
);
230+
return;
231+
}
232+
233+
for (let i = 0; i < amounts.length; i++) {
234+
if (amounts[i] <= BigInt(0)) {
235+
console.error(
236+
chalk.red(
237+
`Amount of token Id: ${tokenIds[i]} to be deposited, must be greater than zero.`,
238+
),
239+
);
240+
return;
241+
}
242+
}
243+
244+
const projectName = getProjectName(command.optsWithGlobals());
245+
246+
// get dapp address from local node, or ask
247+
const application = await getInputApplicationAddress({
248+
...command.optsWithGlobals(),
249+
projectName,
250+
});
251+
252+
// the input sender, impersonated
253+
const account =
254+
from && isAddress(from)
255+
? getAddress(from)
256+
: (await testClient.getAddresses())[0];
257+
258+
const tokenAbi = testMultiTokenAbi;
259+
260+
const baseLayerData = isHex(options.baseLayerData)
261+
? options.baseLayerData
262+
: "0x";
263+
const execLayerData = isHex(options.execLayerData)
264+
? options.execLayerData
265+
: "0x";
266+
267+
// progress spinner
268+
const progress = ora();
269+
270+
// check balances
271+
for (let i = 0; i < tokenIds.length; i++) {
272+
const balance = await testClient.readContract({
273+
abi: tokenAbi,
274+
address: token.address,
275+
functionName: "balanceOf",
276+
args: [account, tokenIds[i]],
277+
});
278+
if (balance < amounts[i]) {
279+
progress.fail(
280+
`Insufficient balance for token ID ${tokenIds[i]}`,
281+
);
282+
return;
283+
}
284+
}
285+
286+
// check if sufficiently approved
287+
const isApproved = await testClient.readContract({
288+
abi: tokenAbi,
289+
address: token.address,
290+
functionName: "isApprovedForAll",
291+
args: [account, erc1155BatchPortalAddress],
292+
});
293+
294+
// approve if needed
295+
if (isApproved === false) {
296+
progress.start(`Approving ERC1155Portal...`);
297+
const { request } = await testClient.simulateContract({
298+
abi: tokenAbi,
299+
account,
300+
address: token.address,
301+
functionName: "setApprovalForAll",
302+
args: [erc1155BatchPortalAddress, true],
303+
});
304+
const hash = await testClient.writeContract(request);
305+
await testClient.waitForTransactionReceipt({ hash });
306+
progress.succeed(`Approved ERC1155Portal`);
307+
}
308+
309+
// simulate batch deposit call
310+
const { request } = await testClient.simulateContract({
311+
abi: erc1155BatchPortalAbi,
312+
account,
313+
address: erc1155BatchPortalAddress,
314+
functionName: "depositBatchERC1155Token",
315+
args: [
316+
token.address,
317+
application,
318+
tokenIds,
319+
amounts,
320+
baseLayerData,
321+
execLayerData,
322+
],
323+
});
324+
325+
// for messages
326+
const amountLabel = tokenIds
327+
.map(
328+
(id, i) =>
329+
`${chalk.cyan(amounts[i])} units of token id ${id}`,
330+
)
331+
.join(", ");
332+
333+
// send deposit
334+
progress.start(
335+
`Depositing tokens to ${chalk.cyan(application)}...`,
336+
);
337+
const hash = await testClient.writeContract(request);
338+
await testClient.waitForTransactionReceipt({ hash });
339+
progress.succeed(
340+
`Deposited ${amountLabel} to ${chalk.cyan(application)}`,
341+
);
342+
});
343+
};

0 commit comments

Comments
 (0)