Skip to content

Commit

Permalink
feat: implement main methods
Browse files Browse the repository at this point in the history
test: implement unit and e2e tests for main methods
docs: add documentation
build: update ci pipeline
docs: update README.md
  • Loading branch information
ffmcgee725 committed Nov 20, 2023
1 parent 7e2e6e4 commit 55471f7
Show file tree
Hide file tree
Showing 21 changed files with 2,667 additions and 159 deletions.
1 change: 1 addition & 0 deletions .env.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
P_KEY=
61 changes: 0 additions & 61 deletions .github/workflows/ci.yaml

This file was deleted.

25 changes: 25 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Web3.js Ipfs Plugin Pipeline

on:
push:
branches:
- main

jobs:
build-and-test:
runs-on: ubuntu-latest
env:
P_KEY: ${{ secrets.P_KEY }}

steps:
- name: Checkout repository
uses: actions/checkout@v2

- name: Install dependencies
run: yarn install

- name: Run unit tests
run: yarn test

- name: Run e2e tests
run: yarn test:e2e
23 changes: 0 additions & 23 deletions .github/workflows/pr_title.yml

This file was deleted.

3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
node_modules
lib
lib
.env
73 changes: 52 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,34 +1,65 @@
web3-plugin-template
===========
# Web3.js IPFS Plugin

This is a template for creating a repository for web3.js plugin.
This is a plugin for Web3.js library, designed for uploading files to IPFS, storing CIDs in a smart contract, and listing all stored CIDs of given ethereum address.

How to use
------------
## Using the library

1. Create your project out of this template.
1. Instantiate a Web3 instance and register the plugin

You can do so by pressing on `Use this template` on the above right corner and then select `Create new Repositor`. Please, use the convention `web3-plugin-<name>` for your repo name.
2. Update the `name` and `description` fileds at your `package.json`.
```
import { Web3 } from "web3";
import { IpfsPlugin } from "web3-ipfs-plugin";
Chose a name like: `@<organization>/web3-plugin-<name>` (or the less better `web3-plugin-<name>`).
3. Update the code inside `src` folder.
web3 = new Web3("RPC_URL");
web3.registerPlugin(new IpfsPlugin("RPC_URL"));
```

4. Modify and add tests inside `test` folder.
2. Use uploadFile() to upload a file to IPFS network and store the generated CID in a Registry Smart Contract `0xA683BF985BC560c5dc99e8F33f3340d1e53736EB`

5. Publish to the npm registry.
```
// Calling this method will upload the file to IPFS, store the CID to a smart contract, and return the transaction receipt (or throws an error if something goes wrong)
try {
const receipt = await web3.ipfs.uploadFile("path/to/file/");
} catch (e) {
// handle error
}
```

You can publish with something like: `yarn build && npm publish --access public`.
NOTE: If calling from a web browser, the file data should be passed as an Iterable<Uint8Array>, as the 2nd argument (since file path implementation uses `fs` underneath the hood which is only supported on a node environment)
We suggest using [File System API](https://developer.mozilla.org/en-US/docs/Web/API/File_System_API) to implement client-side code capable of fetching and manipulating this data accordingly

Contributing
------------
```
try {
const receipt = await web3.ipfs.uploadFile(
"this/path/becomes/irrelevant",
data
);
} catch (e) {
// handle error
}
```

Pull requests are welcome. For major changes, please open an issue first
to discuss what you would like to change.
3. Use listAllByAddress() to list all encoded CIDs for given Ethereum address (or throws an error if something goes wrong)

Please make sure to update tests as appropriate.
```
try {
const cidStoredEvents = await web3.ipfs.listAllByAddress("0xb9089c00f17B7c9Cf77f3Fb87164CbD0eC1F7d08");
} catch(e) {
// handle error...
}
```

License
-------
NOTE: A limitation on Web3.js lib makes it so only data from the most recent 50000 blocks can be retrieved

[MIT](https://choosealicense.com/licenses/mit/)
## Running Tests

1. To run unit tests, run the following command
`yarn test`

2. To run end to end tests, run the following command
`yarn test:e2e`

NOTE: `/!\` Make sure to add a private key (without the `0x` prefix) to the `P_KEY` environment variable
as specified from the `.env.sample` file, before running e2e tests.
If e2e test fails due to insufficient funds in the address you chose to use, just mint some SepoliaETH
You can use this [faucet](https://sepolia-faucet.pk910.de/#/) `/!\`
19 changes: 19 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
const IS_E2E = process.env.IS_E2E === "true";

let testRegex = ".*\\.spec\\.ts$";
if (IS_E2E) {
testRegex = ".e2e-spec.ts$";
}
const testPathIgnorePatterns = ["/lib/"];
module.exports = {
preset: "ts-jest",
moduleFileExtensions: ["js", "json", "ts"],
moduleDirectories: ["node_modules", "src"],
moduleNameMapper: {
"^@helia/unixfs$": "<rootDir>/node_modules/@helia/unixfs/dist/src/index.js",
},
testEnvironment: "node",
testPathIgnorePatterns,
testRegex,
};
18 changes: 14 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "web3-plugin-template",
"name": "web3-ipfs-plugin",
"version": "1.0.0",
"description": "Template plugin to extend web3.js with additional methods",
"description": "Plugin for Web3.js library, designed for uploading files to IPFS, storing CIDs in a smart contract, and listing all stored CIDs of given ethereum address",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"homepage": "https://github.com/web3/web3.js-plugin-template#readme",
Expand All @@ -11,17 +11,27 @@
"scripts": {
"lint": "eslint '{src,test}/**/*.ts'",
"build": "tsc --project tsconfig.build.json",
"test": "jest --config=./test/jest.config.js"
"test": "jest --verbose",
"test:e2e": "IS_E2E=true jest --verbose"
},
"contributors": [
"ChainSafe <info@chainsafe.io>"
"ffmcgee <joao@chainsafe.io>"
],
"license": "MIT",
"repository": {
"type": "git",
"url": "git@github.com:web3/web3.js-plugin-template.git"
},
"dependencies": {
"@chainsafe/libp2p-noise": "^13.0.3",
"@chainsafe/libp2p-yamux": "^5.0.3",
"@helia/unixfs": "^1.4.2",
"@libp2p/tcp": "^8.0.13",
"blockstore-core": "^4.3.4",
"datastore-core": "^9.2.3",
"dotenv": "^16.3.1",
"helia": "^2.1.0",
"libp2p": "^0.46.21"
},
"devDependencies": {
"@chainsafe/eslint-config": "^2.0.0",
Expand Down
88 changes: 88 additions & 0 deletions src/clients/ipfs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { createReadStream } from "fs";
import { UnixFS } from "@helia/unixfs";
/**
* An abstraction for encapsulating helia IPFS operations.
*/
export class IpfsClient {
private unixFsInstance?: UnixFS;

/**
* Uploads local files to IPFS.
* `NOTE: /!\ if calling from web browser, use File System Access API to fetch data, and pass it in 2nd optional param, as implementation via filePath uses "fs" which is only supported in a Node environment. /!\`
* @param {string} filePath local path of the file to upload.
* @param {Iterable<Uint8Array>} data file as byte array. `/!\ use this optional param when calling this method from a web browser /!\`
* @returns {string} generated CID from uploaded file. If an error is thrown, returns an empty string.
*/
public async uploadFile(
filePath: string,
data?: Iterable<Uint8Array>
): Promise<string> {
try {
if (!this.unixFsInstance) {
await this.initUnixFSInstance();
}

const cid = await this.unixFsInstance?.addByteStream(
data ?? createReadStream(filePath)
);
return cid?.toString() ?? "";
} catch (e) {
const err = e as Error;
const msg = `error while uploading file: ${err.message}`;
console.error(msg, err.stack);
throw new Error(msg);
}
}

/**
* Spawns a UnixFS instance to be used for uploading files to IPFS. Since this is asynchronous behavior, we cannot do it in the class constructor.
*/
private async initUnixFSInstance(): Promise<void> {
try {
/**
* we do dynamic import in order for e2e tests to run properly, because of an issue with how the library is exposed.
* issue: https://github.com/ipfs/helia/issues/149
*/
const [
{ createHelia },
{ createLibp2p },
{ tcp },
{ noise },
{ yamux },
{ MemoryBlockstore },
{ MemoryDatastore },
{ unixfs },
] = await Promise.all([
import("helia"),
import("libp2p"),
import("@libp2p/tcp"),
import("@chainsafe/libp2p-noise"),
import("@chainsafe/libp2p-yamux"),
import("blockstore-core"),
import("datastore-core"),
import("@helia/unixfs"),
]);

const datastore = new MemoryDatastore();
const libp2p = await createLibp2p({
addresses: {
listen: ["/ip4/0.0.0.0/tcp/0"],
},
transports: [tcp()],
connectionEncryption: [noise()],
streamMuxers: [yamux()],
datastore,
});

const helia = await createHelia({
libp2p,
blockstore: new MemoryBlockstore(),
datastore,
});

this.unixFsInstance = unixfs(helia);
} catch (e) {
console.error("error while initiating ipfs client: ", e);
}
}
}
Loading

0 comments on commit 55471f7

Please sign in to comment.