Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/whole-showers-warn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@cartesi/cli": patch
---

adapt espresso service to updated espresso-dev-node
128 changes: 128 additions & 0 deletions apps/cli/src/commands/send/eip712.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { Hex, WalletClient } from "viem";
import { Address } from "viem/accounts";

type SendOptions = {
eip712TxUrl: string;
chainId: number;
maxGasPrice: number;
};

export const DEFAULT_SEND_CONFIG: Readonly<SendOptions> = {
eip712TxUrl: "http://localhost:8080/transaction",
chainId: 31337,
maxGasPrice: 10,
} as const;

type TypedData = {
domain: {
name: string;
version: string;
chainId: number;
verifyingContract: Address;
};
types: Record<string, { name: string; type: string }[]>;
primaryType: string;
message: Record<string, unknown>;
};

const fetchJSON = async <T>(url: string, options: RequestInit): Promise<T> => {
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`HTTP Error: ${response.status} - ${await response.text()}`);
}
return response.json();
};

const serializeBigInt = (_key: string, value: unknown): unknown =>
typeof value === "bigint" ? value.toString() : value;


const createTypedDataTemplate = (chainId: number, verifyingContract: Address): TypedData => ({
domain: {
name: "Cartesi",
version: "0.1.0",
chainId,
verifyingContract,
},
types: {
EIP712Domain: [
{ name: "name", type: "string" },
{ name: "version", type: "string" },
{ name: "chainId", type: "uint256" },
{ name: "verifyingContract", type: "address" },
],
CartesiMessage: [
{ name: "app", type: "address" },
{ name: "nonce", type: "uint64" },
{ name: "max_gas_price", type: "uint128" },
{ name: "data", type: "bytes" },
],
},
primaryType: "CartesiMessage",
message: {},
});

/** Fetches the nonce for a given user and application */
export const getNonceForEip712 = async (
user: Address,
application: Address,
sendOptions: SendOptions
): Promise<number> => {
return fetchJSON<{ nonce: number }>(`${sendOptions.eip712TxUrl}/nonce`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ msg_sender: user, app_contract: application }),
}).then((res) => res.nonce);
};

/** Submits a transaction to L2 */
export const postEip712Transaction = async (
data: Record<string, unknown>,
sendOptions: SendOptions
): Promise<{ id: string }> => {
return fetchJSON<{ id: string }>(`${sendOptions.eip712TxUrl}/submit`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data, serializeBigInt),
});
};

/** Creates a Typed Data object for signing */
export const createEip712TypedData = async (
app: Address,
data: Hex,
nonce: number,
sendOptions: SendOptions
): Promise<TypedData> => {
return {
...createTypedDataTemplate(sendOptions.chainId, app),
message: {
app,
nonce,
data,
max_gas_price: BigInt(sendOptions.maxGasPrice),
},
};
};

/** Sends a transaction to L2 */
export const sendEip712 = async (
walletClient: WalletClient,
applicationAddress: Address,
payload: Hex,
sendOptions?: Partial<SendOptions>
): Promise<string> => {
if (!walletClient.account) {
throw new Error("Wallet account is missing.");
}

const options = { ...DEFAULT_SEND_CONFIG, ...sendOptions };
const account = walletClient.account.address;

const nonce = await getNonceForEip712(applicationAddress, account, options);
const typedData = await createEip712TypedData(applicationAddress, payload, nonce, options);
const signature = await walletClient.signTypedData({ account, ...typedData });

const { id } = await postEip712Transaction({ typedData, account, signature }, options);
return id;
};
22 changes: 22 additions & 0 deletions apps/cli/src/commands/send/generic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
getInputApplicationAddress,
SendCommandOpts,
} from "../send.js";
import { DEFAULT_SEND_CONFIG, sendEip712 } from "./eip712.js";

const getInput = async (options: {
input?: string;
Expand Down Expand Up @@ -111,6 +112,19 @@ export const createGenericCommand = () => {
"input encoding",
).choices(["hex", "string", "abi"]),
)
.addOption(
new Option(
"--type <type>",
"Transaction type",
).choices(["evm", "eip712"])
.default("evm"),
)
.addOption(
new Option(
"--eip712-tx-url <url>",
"EIP-712 base url",
).default(DEFAULT_SEND_CONFIG.eip712TxUrl),
)
.option("--input-abi-params <input-abi-params>", "input abi params")
.action(async (options, command) => {
const sendOptions = command.optsWithGlobals();
Expand All @@ -126,6 +140,14 @@ export const createGenericCommand = () => {
const payload =
(await getInput(options)) ||
(await bytesInput({ message: "Input" }));
if (options.type === 'eip712') {
const progress = ora("Sending input...").start();
const hash = await sendEip712(walletClient, applicationAddress, payload, {
eip712TxUrl: options.eip712TxUrl,
})
progress.succeed(`Input sent: ${hash}`);
return
}
const { request } = await publicClient.simulateContract({
address: inputBoxAddress,
abi: inputBoxAbi,
Expand Down
25 changes: 10 additions & 15 deletions apps/cli/src/compose/rollups/docker-compose-espresso.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ services:
espresso:
image: ${CARTESI_SDK_IMAGE}
command: ["espresso-dev-node"]
init: true
deploy:
resources:
limits:
Expand All @@ -14,25 +15,21 @@ services:
condition: service_healthy
environment:
ESPRESSO_SEQUENCER_L1_PROVIDER: ${CARTESI_BLOCKCHAIN_HTTP_ENDPOINT:-http://anvil:8545}
ESPRESSO_SEQUENCER_API_PORT: 8770
ESPRESSO_BUILDER_PORT: 8771
ESPRESSO_PROVER_PORT: 8772
ESPRESSO_SEQUENCER_API_PORT: 24000
ESPRESSO_BUILDER_PORT: 23000
ESPRESSO_DEV_NODE_PORT: 20000
ESPRESSO_SEQUENCER_POSTGRES_HOST: database
ESPRESSO_SEQUENCER_POSTGRES_PORT: 5432
ESPRESSO_SEQUENCER_POSTGRES_USER: postgres
ESPRESSO_SEQUENCER_POSTGRES_PASSWORD: password
ESPRESSO_SEQUENCER_POSTGRES_DATABASE: espresso
ESPRESSO_SEQUENCER_ETH_MNEMONIC: ${CARTESI_AUTH_MNEMONIC:-test test test test test test test test test test test junk}
ESPRESSO_SEQUENCER_L1_POLLING_INTERVAL: "1s"
ESPRESSO_STATE_PROVER_UPDATE_INTERVAL: "1s"
ESPRESSO_SEQUENCER_L1_POLLING_INTERVAL: "5s"
ESPRESSO_STATE_PROVER_UPDATE_INTERVAL: "10s"
ESPRESSO_SEQUENCER_DATABASE_MAX_CONNECTIONS: 25
ESPRESSO_SEQUENCER_STORAGE_PATH: /data/espresso
healthcheck:
test:
[
"CMD",
"curl",
"-fsS",
"http://127.0.0.1:8770/status/block-height",
"http://127.0.0.1:24000/status/block-height",
]
start_period: 10s
start_interval: 200ms
Expand All @@ -53,11 +50,9 @@ services:
espresso:
condition: service_healthy
environment:
CARTESI_POSTGRES_ENDPOINT: postgres://postgres:password@database:5432/rollupsdb?sslmode=disable
CARTESI_DATABASE_CONNECTION: postgres://postgres:password@database:5432/rollupsdb?sslmode=disable
ESPRESSO_SERVICE_ENDPOINT: ":8081"
ESPRESSO_BASE_URL: http://espresso:8770
ESPRESSO_NAMESPACE: 51025
ESPRESSO_STARTING_BLOCK: 101
ESPRESSO_BASE_URL: http://espresso:24000

proxy:
volumes:
Expand Down
Loading