Skip to content

Commit

Permalink
Viem extension
Browse files Browse the repository at this point in the history
  • Loading branch information
tuler committed Aug 26, 2024
1 parent cd75f6e commit 9fd0e0f
Show file tree
Hide file tree
Showing 53 changed files with 3,696 additions and 64 deletions.
5 changes: 5 additions & 0 deletions .changeset/rude-beans-study.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@deroll/viem": minor
---

viem Cartesi extension
1 change: 1 addition & 0 deletions packages/viem/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
src/rollups.ts
127 changes: 127 additions & 0 deletions packages/viem/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# Cartesi viem extension

Viem provides an [extension mechanism to clients](https://viem.sh/docs/clients/custom) that can provide a convenient and familiar API for application developers based on L2 solutions.

This extension provides extensions for L1 operations and L2 operations related to Cartesi.

## WalletClient L1

Transactions related to Cartesi applications are sent directly to the base layer (L1).
This extension provides convenient methods for [sending inputs](https://docs.cartesi.io/cartesi-rollups/1.5/rollups-apis/json-rpc/input-box/#addinput) and interacting with the [portals](https://docs.cartesi.io/cartesi-rollups/1.5/rollups-apis/json-rpc/portals/ERC20Portal/) provided by Cartesi Rollups.

- addInput
- depositEther
- depositERC20Tokens
- depositERC721Token
- depositSingleERC1155Token
- depositBatchERC1155Token
- executeVoucher
- relayDAppAddress

## PublicClient L1

The following methods are provided to interact with the Cartesi Rollups Smart Contracts.

- estimateAddInputGas
- estimateDepositEtherGas
- estimateDepositERC20TokensGas
- estimateDepositERC721TokenGas
- estimateDepositSingleERC1155TokenGas
- estimateDepositBatchERC1155TokenGas
- estimateExecuteVoucherGas
- estimateRelayDAppAddressGas

## PublicClient L2

The following methods are provided to interact with the Cartesi Rollups Node.

- inputNumber
- getInput
- getNoticeCount
- getReportCount
- getVoucherCount
- getNotice
- getReport
- getVoucher

## Utilities

- getInputsAdded: this provides a utility method to get the input(s) added to the InputBox given a `TransactionReceipt`.

```typescript
const receipt = await publicClient.waitForTransactionReceipt({ hash });
const [inputAdded] = getInputsAdded(receipt);
```

## Example

Find below a complete example of depositing ERC-20 tokens and waiting for its effect on L2.

```typescript
import { createPublicClient, createWalletClient, http, parseUnits } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { foundry } from "viem/chains";
import { getInputsAdded, publicActionsL2, walletActionsL1 } from "../src";

async function main() {
const chain = foundry;
const account = privateKeyToAccount(
"0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
);

// create public client to chain default url
const publicClient = createPublicClient({ chain, transport: http() });

// create extended public client to L2 with RPC url
// https://github.com/tuler/deroll/tree/main/packages/rpc
const publicClientL2 = createPublicClient({
chain,
transport: http("http://localhost:4000/rpc"),
}).extend(publicActionsL2());

// create extended wallet client to chain default url
const walletClient = createWalletClient({
chain,
transport: http(),
}).extend(walletActionsL1());

// application address
const application = "0xab7528bb862fb57e8a2bcd567a2e929a0be56a5e";
const token = "0x92C6bcA388E99d6B304f1Af3c3Cd749Ff0b591e2";

// send input transaction
const hash = await walletClient.depositERC20Tokens({
account,
application,
chain,
token,
amount: parseUnits("1", 18),
execLayerData: "0x",
});

// wait for receipt
const receipt = await publicClient.waitForTransactionReceipt({ hash });

// get input index from receipt
const [inputAdded] = getInputsAdded(receipt);
if (inputAdded) {
const { inputIndex } = inputAdded;

// wait for input to be processed
const input = await publicClientL2.waitForInput({
inputNumber: Number(inputIndex),
});
console.log(input);

// get notice 0 produced by application
const notice = await publicClientL2.getNotice({
inputNumber: Number(inputIndex),
noticeNumber: 0,
});

console.log(notice);
}
}

main();
```
98 changes: 98 additions & 0 deletions packages/viem/__tests__/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import {
createPublicClient,
createWalletClient,
http,
zeroAddress,
zeroHash,
} from "viem";
import { describe, it } from "vitest";
import { privateKeyToAccount } from "viem/accounts";
import { foundry } from "viem/chains";
import { getInputsAdded, publicActionsL2, walletActionsL1 } from "../src";
import { inputBoxAbi, inputBoxAddress } from "../src/rollups";
import { AddInput2Parameters } from "../src/decorators/walletL1";

describe("viem extension", () => {
it("getInputIndex", async () => {
const rpcUrl = "http://localhost:8545";
const cartesiRpcUrl = "http://localhost:4000/rpc";

const account = privateKeyToAccount(
"0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
);
const chain = foundry;

const publicClient = createPublicClient({
transport: http(rpcUrl),
});

const publicClientL2 = createPublicClient({
transport: http(cartesiRpcUrl),
}).extend(publicActionsL2());

const walletClient = createWalletClient({ transport: http() }).extend(
walletActionsL1(),
);

const inputNumber = await publicClientL2.inputNumber([]);

/*
const { request: request_ } = await publicClient.simulateAddInput({
args: [zeroAddress, zeroHash],
});
const hash = await walletClient.addInput(request);
*/

// prepare an addInput
const { request } = await publicClient.simulateContract({
account,
abi: inputBoxAbi,
address: inputBoxAddress,
functionName: "addInput",
args: [zeroAddress, zeroHash],
});

// send an input
const hash = await walletClient.writeContract(request);

// wait for the transaction receipt
const receipt = await publicClient.waitForTransactionReceipt({ hash });

// get the input index from the transaction receipt
const [inputAdded] = getInputsAdded(receipt);

if (inputAdded) {
const { inputIndex } = inputAdded;
const notice = await publicClientL2.getNotice([
Number(inputIndex),
0,
]);
}

const p: AddInput2Parameters = {
account,
chain: foundry,
request: {
application: zeroAddress,
payload: zeroHash,
},
};
const hash2 = await walletClient.addInput({
// account,
args: [zeroAddress, zeroHash],
// abi: inputBoxAbi,
// address: inputBoxAddress,
// functionName: "addInput",
// chain: foundry,
});

// addInput
/*
const { request: request2 } = await publicClient.simulateAddInput({
account,
args: [zeroAddress, zeroHash],
});
await walletClient.addInput(request2);
*/
});
});
74 changes: 74 additions & 0 deletions packages/viem/examples/addInput.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import {
createPublicClient,
createWalletClient,
http,
stringToHex,
} from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { foundry } from "viem/chains";
import { getInputsAdded, publicActionsL2, walletActionsL1 } from "../src";

async function main() {
const chain = foundry;
const account = privateKeyToAccount(
"0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
);

// create public client to chain default url
const publicClient = createPublicClient({ chain, transport: http() });

// create extended public client to L2 with RPC url
// https://github.com/tuler/deroll/tree/main/packages/rpc
const publicClientL2 = createPublicClient({
chain,
transport: http("http://localhost:4000/rpc"),
}).extend(publicActionsL2());

// create extended wallet client to chain default url
const walletClient = createWalletClient({
chain,
transport: http(),
}).extend(walletActionsL1());

// application address
const application = "0xab7528bb862fb57e8a2bcd567a2e929a0be56a5e";

// input payload
const payload = stringToHex("hello");

// send input transaction
const hash = await walletClient.addInput({
account,
application,
chain,
payload,
});

// wait for receipt
const receipt = await publicClient.waitForTransactionReceipt({
hash,
timeout: 6000,
});

// get input index from receipt
const [inputAdded] = getInputsAdded(receipt);
if (inputAdded) {
const { inputIndex } = inputAdded;

// wait for input to be processed
const input = await publicClientL2.waitForInput({
inputNumber: Number(inputIndex),
});
console.log(input);

// get notice 0 produced by application
const notice = await publicClientL2.getNotice({
inputNumber: Number(inputIndex),
noticeNumber: 0,
});

console.log(notice);
}
}

main();
68 changes: 68 additions & 0 deletions packages/viem/examples/depositBatchERC1155Token.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { createPublicClient, createWalletClient, http, parseUnits } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { foundry } from "viem/chains";
import { getInputsAdded, publicActionsL2, walletActionsL1 } from "../src";

async function main() {
const chain = foundry;
const account = privateKeyToAccount(
"0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
);

// create public client to chain default url
const publicClient = createPublicClient({ chain, transport: http() });

// create extended public client to L2 with RPC url
// https://github.com/tuler/deroll/tree/main/packages/rpc
const publicClientL2 = createPublicClient({
chain,
transport: http("http://localhost:4000/rpc"),
}).extend(publicActionsL2());

// create extended wallet client to chain default url
const walletClient = createWalletClient({
chain,
transport: http(),
}).extend(walletActionsL1());

// application address
const application = "0xab7528bb862fb57e8a2bcd567a2e929a0be56a5e";
const token = "0xc6582A9b48F211Fa8c2B5b16CB615eC39bcA653B";

// send input transaction
const hash = await walletClient.depositBatchERC1155Token({
account,
application,
baseLayerData: "0x",
chain,
execLayerData: "0x",
token,
values: [1n],
tokenIds: [1n],
});

// wait for receipt
const receipt = await publicClient.waitForTransactionReceipt({ hash });

// get input index from receipt
const [inputAdded] = getInputsAdded(receipt);
if (inputAdded) {
const { inputIndex } = inputAdded;

// wait for input to be processed
const input = await publicClientL2.waitForInput({
inputNumber: Number(inputIndex),
});
console.log(input);

// get notice 0 produced by application
const notice = await publicClientL2.getNotice({
inputNumber: Number(inputIndex),
noticeNumber: 0,
});

console.log(notice);
}
}

main();
Loading

0 comments on commit 9fd0e0f

Please sign in to comment.