Skip to content

Commit

Permalink
Various improvements (#6)
Browse files Browse the repository at this point in the history
* nit(web3js): adds `setFeeCurrency()` todo

* refactor(viem): adds ERC20 transfer and refactors code

* docs(CONTRIBUTING): adds a basic development guide

* nit(README): linting

* nit(viem): transaction message

* chore(constants): refactor constants into separate file

* docs(README): fix header

* docs(README): adds URLs
  • Loading branch information
arthurgousset authored Feb 29, 2024
1 parent 88baa3d commit ed2ac46
Show file tree
Hide file tree
Showing 7 changed files with 151 additions and 90 deletions.
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
18.17.1
45 changes: 45 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Contributing

## Basic guide

This guide is intended to help you get started with contributing. By following these steps,
you will understand the development process and workflow.

### Cloning the repository

To start contributing to the project, clone it to your local machine using git:

```sh
$ git clone https://github.com/celo-org/feecurrency.git
```

Navigate to the project's root directory:

```sh
$ cd developer-tooling
```

### Installing Node.js

We use [Node.js](https://nodejs.org/en/) to run the project locally.
You need to install the **Node.js version** specified in [.nvmrc](./.nvmrc). To do so, run:

```sh
$ nvm install
$ nvm use
```

### Installing dependencies

Once in the project's root directory, run the following command to install the project's
dependencies:

```sh
$ yarn install
```

After installing the dependencies, the project is ready to be run.

```sh
$ yarn ts-node <scrip-name>
```
41 changes: 21 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,51 +1,52 @@
# Fee Currencies on Celo

> [!TIP]
> Code examples for [docs.celo.org > Paying for Gas in Tokens](https://docs.celo.org/protocol/transaction/erc20-transaction-fees#alfajores-testnet). Visit that page for more information.
> [!TIP] Code examples for
> [docs.celo.org > Paying for Gas in Tokens](https://docs.celo.org/protocol/transaction/erc20-transaction-fees#alfajores-testnet).
> Visit that page for more information.
Fee currency transactions are unique to Celo. Fee currency transactions are made using a
custom transaction type described here: `celo-org/txtypes` (TODO(Arthur): link repo). Visit that repository for an explainer
and more context on the lower-level implementation of fee currency transactions.
Fee currency transactions are unique to Celo. Fee currency transactions are made using a custom
transaction type described here: [`celo-org/txtypes`](https://github.com/celo-org/txtypes). Visit
that repository for an explainer and more context on the lower-level implementation of fee currency
transactions.

This repo provides as a higher-level explainer on making fee currency transactions with popular
This repo provides as a higher-level explainer on making fee currency transactions with popular
JS/TS client libraries.

## Usage

### `viem` (Recommended ✅)

We recommend you build with `viem`, if you are starting from scratch.
See demo in [`viem.ts`](/viem.ts).
We recommend you build with `viem`, if you are starting from scratch. See demo in
[`viem.ts`](/viem.ts).

## RPC client (Experts-only 🟠)
### Raw transaction (Experts-only 🟠)

We don't recommend constructing fee currency transactions without client libraries, if you are
not an expert.
We don't recommend constructing fee currency transactions without client libraries, if you are not
an expert.

That's because constructing fee currency transactions without client libraries requires a thorough
understanding of cryptographic signatures, transaction serialization, and typed transaction
understanding of cryptographic signatures, transaction serialization, and typed transaction
envelopes.

But, if you would really like to construct fee currency transactions this way, you
can learn more about the transaction arguments and format here: `celo-org/txtypes`.
But, if you would really like to construct fee currency transactions this way, you can learn more
about the transaction arguments and format here: `celo-org/txtypes`.

### `web3js` + `@celo/connect` (Discouraged ❌)

We don't recommend you build with `web3js` and `@celo/connect`, if you are starting from scratch.

That's because `@celo/connect` only supports `[email protected]`, and not the latest version
That's because `@celo/connect` only supports `[email protected]`, and not the latest version
`[email protected]`.

Instead, we recommend you build with `viem`. But, for completeness, we included a demo in
Instead, we recommend you build with `viem`. But, for completeness, we included a demo in
[`web3.ts`](/web3.ts) so you can see usage patterns.

### `@celo/contractkit` (Discouraged ❌)

We don't recommend you build with `@celo/contractkit`, if you are starting from scratch.

That's because `@celo/contractkit` is a library for developers who want to
interact with core contracts (TODO(Arthur): link docs.celo.org) like `Governance.sol` or
`Election.sol`.
That's because `@celo/contractkit` is a library for developers who want to interact with
[core contracts](https://docs.celo.org/contract-addresses) like `Governance.sol` or `Election.sol`.

Instead, we recommend you build with `viem`. But, for completeness, we included a demo in
Instead, we recommend you build with `viem`. But, for completeness, we included a demo in
[`contractkit.ts`](/contractkit.ts) so you can see usage patterns.
12 changes: 12 additions & 0 deletions constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import "dotenv/config";

export const RECIPIENT = "0x22579CA45eE22E2E16dDF72D955D6cf4c767B0eF"; // arbitrary address
export const cUSD_CONTRACT_ADDRESS = "0x874069Fa1Eb16D44d622F2e0Ca25eeA172369bC1"; // ERC20
export const USDC_CONTRACT_ADDRESS = "0x780c1551C2Be3ea3B1f8b1E4CeDc9C3CE40da24E"; // ERC20
export const USDC_ADAPTER_ADDRESS = "0xDB93874fE111F5a87Acc11Ff09Ee9450Ac6509AE"; // Adapter
export const PRIVATE_KEY = process.env.PRIVATE_KEY;
if (!PRIVATE_KEY) {
throw new Error(
"PRIVATE_KEY is not set in .env file. Please set PRIVATE_KEY in .env file."
);
}
24 changes: 8 additions & 16 deletions contractkit.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,11 @@
import { newKit } from "@celo/contractkit";
import "dotenv/config";

/**
* Constants
*/
const RECIPIENT = "0x22579CA45eE22E2E16dDF72D955D6cf4c767B0eF"; // arbitrary address
const cUSD_CONTRACT_ADDRESS = "0x874069Fa1Eb16D44d622F2e0Ca25eeA172369bC1"; // ERC20
const USDC_CONTRACT_ADDRESS = "0x780c1551C2Be3ea3B1f8b1E4CeDc9C3CE40da24E"; // ERC20
const USDC_ADAPTER_ADDRESS = "0xDB93874fE111F5a87Acc11Ff09Ee9450Ac6509AE"; // Adapter
const PRIVATE_KEY = process.env.PRIVATE_KEY;
if (!PRIVATE_KEY) {
throw new Error(
"PRIVATE_KEY is not set in .env file. Please set PRIVATE_KEY=<your_private_key> in .env file."
);
}
import {
PRIVATE_KEY,
RECIPIENT,
cUSD_CONTRACT_ADDRESS,
USDC_CONTRACT_ADDRESS,
USDC_ADAPTER_ADDRESS,
} from "./constants";

/**
* Boilerplate to create a ContractKit instance and ContractKit-compatible wallet
Expand Down Expand Up @@ -48,7 +40,7 @@ async function erc20Transfer() {
feeCurrency: cUSD_CONTRACT_ADDRESS,
});

console.log(transactionReceipt);
console.log(`Done! Transaction hash: ${transactionReceipt.transactionHash}`);
}

erc20Transfer().catch((err) => {
Expand Down
77 changes: 46 additions & 31 deletions viem.ts
Original file line number Diff line number Diff line change
@@ -1,49 +1,64 @@
import { createPublicClient, createWalletClient, http, parseEther, parseGwei } from "viem";
import {
createPublicClient,
createWalletClient,
http,
getContract,
erc20Abi,
parseUnits,
formatUnits,
} from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { celoAlfajores } from "viem/chains";
import "dotenv/config"; // use to read private key from environment variable

const PRIVATE_KEY = process.env.PRIVATE_KEY;
if (!PRIVATE_KEY) {
throw new Error(
"PRIVATE_KEY is not set in .env file. Please set PRIVATE_KEY=<your_private_key> in .env file."
);
}
const RECIPIENT = "0x22579CA45eE22E2E16dDF72D955D6cf4c767B0eF"; // arbitrary address
import {
PRIVATE_KEY,
RECIPIENT,
cUSD_CONTRACT_ADDRESS,
USDC_CONTRACT_ADDRESS,
USDC_ADAPTER_ADDRESS,
} from "./constants";

/**
* Boilerplate to create a viem client
* Boilerplate to create a viem client and viem-compatible wallet
*/
const account = privateKeyToAccount(`0x${PRIVATE_KEY}`);
const publicClient = createPublicClient({
const read = createPublicClient({
chain: celoAlfajores,
transport: http(),
});
const walletClient = createWalletClient({
const write = createWalletClient({
chain: celoAlfajores, // Celo testnet
transport: http(),
});
const sender = privateKeyToAccount(`0x${PRIVATE_KEY}`);

async function nativeTransfer() {
console.log(`Initiating fee currency transaction...`);
const transactionHash = await walletClient.sendTransaction({
account, // Sender
to: RECIPIENT, // recipient
// TODO: Adjust the amount to send based on the token's decimals (USDC has 6 decimals)
value: parseEther("0.01"), // 0.01 CELO
feeCurrency: "0x874069Fa1Eb16D44d622F2e0Ca25eeA172369bC1", // cUSD fee currency
// gas: // TODO: implement gas estimation
maxFeePerGas: parseGwei("10"), // Special field for dynamic fee transaction type (EIP-1559)
maxPriorityFeePerGas: parseGwei("10"), // Special field for dynamic fee transaction type (EIP-1559)
});
/**
* Set up ERC20 contract
*/
const contract = getContract({
address: cUSD_CONTRACT_ADDRESS,
abi: erc20Abi,
client: { public: read, wallet: write },
});

const transactionReceipt = await publicClient.waitForTransactionReceipt({
hash: await transactionHash,
});
/**
* Makes a transaction to transfer ERC20 tokens using a fee currency
*/
async function erc20Transfer() {
console.log(`Initiating fee currency transaction...`);

const [symbol, decimals, tokenBalance] = await Promise.all([
contract.read.symbol(),
contract.read.decimals(),
contract.read.balanceOf([sender.address]),
]);
console.log(`${symbol} balance of ${sender.address}: ${formatUnits(tokenBalance, decimals)}`);

console.log(transactionReceipt);
const transactionHash = await contract.write.transfer(
[RECIPIENT, parseUnits("0.01", decimals)],
{ account: sender, feeCurrency: cUSD_CONTRACT_ADDRESS }
);
console.log(`Done! Transaction hash: ${transactionHash}`);
}

nativeTransfer().catch((err) => {
erc20Transfer().catch((err) => {
console.error("An error occurred:", err);
});
41 changes: 18 additions & 23 deletions web3.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,16 @@
import Web3 from "web3";
import { Connection } from "@celo/connect";
import { LocalWallet } from "@celo/wallet-local"
import { CeloTxReceipt, Connection } from "@celo/connect";
import { LocalWallet } from "@celo/wallet-local";
import "dotenv/config";
import { AbiItem } from 'web3-utils';
import { AbiItem } from "web3-utils";
import { ERC20ABI } from "./erc20Abi";

/**
* Constants
*/
const RECIPIENT = "0x22579CA45eE22E2E16dDF72D955D6cf4c767B0eF"; // arbitrary address
const cUSD_CONTRACT_ADDRESS = "0x874069Fa1Eb16D44d622F2e0Ca25eeA172369bC1"; // ERC20
const USDC_CONTRACT_ADDRESS = "0x780c1551C2Be3ea3B1f8b1E4CeDc9C3CE40da24E"; // ERC20
const USDC_ADAPTER_ADDRESS = "0xDB93874fE111F5a87Acc11Ff09Ee9450Ac6509AE"; // Adapter
const PRIVATE_KEY = process.env.PRIVATE_KEY;
if (!PRIVATE_KEY) {
throw new Error(
"PRIVATE_KEY is not set in .env file. Please set PRIVATE_KEY=<your_private_key> in .env file."
);
}
import {
PRIVATE_KEY,
RECIPIENT,
cUSD_CONTRACT_ADDRESS,
USDC_CONTRACT_ADDRESS,
USDC_ADAPTER_ADDRESS,
} from "./constants";

/**
* Boilerplate to create a web3js client and web3js-compatible wallet
Expand All @@ -32,9 +25,9 @@ const connection = new Connection(web3, celoWallet);
*/
const contract = new web3.eth.Contract(ERC20ABI as AbiItem[], cUSD_CONTRACT_ADDRESS);

/**
/**
* Makes a transaction to transfer ERC20 tokens using a fee currency
*/
*/
async function erc20Transfer() {
console.log(`Initiating fee currency transaction...`);

Expand All @@ -47,19 +40,21 @@ async function erc20Transfer() {
// Get the sender's address
const sender = celoWallet.getAccounts()[0];

const transactionReceipt = await connection
const transactionReceipt = (await connection
.sendTransaction({
from: sender,
to: cUSD_CONTRACT_ADDRESS,
feeCurrency: cUSD_CONTRACT_ADDRESS,
data: transactionObject.encodeABI(),
})
.then((tx) => tx.waitReceipt())
.catch((err) => console.error(err));
console.log(transactionReceipt);
.catch((err) => console.error(err))) as CeloTxReceipt;

console.log(`Done! Transaction hash: ${transactionReceipt.transactionHash}`);
}

// TODO(Arthur): Add example using `setFeeCurrency()`

// Initiate the transfer
erc20Transfer().catch((err) => {
console.error("An error occurred:", err);
Expand Down

0 comments on commit ed2ac46

Please sign in to comment.