mirror of
https://github.com/fluencelabs/examples
synced 2024-12-04 19:20:17 +00:00
add web3 examples and tutorial
This commit is contained in:
parent
da0b83d3dd
commit
45012cd093
24
web3-examples/web-frontend/.gitignore
vendored
Normal file
24
web3-examples/web-frontend/.gitignore
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.eslintcache
|
8
web3-examples/web-frontend/.prettierrc.js
Normal file
8
web3-examples/web-frontend/.prettierrc.js
Normal file
@ -0,0 +1,8 @@
|
||||
module.exports = {
|
||||
semi: true,
|
||||
trailingComma: "all",
|
||||
singleQuote: true,
|
||||
printWidth: 120,
|
||||
tabWidth: 4,
|
||||
useTabs: false
|
||||
};
|
9
web3-examples/web-frontend/.vscode/settings.json
vendored
Normal file
9
web3-examples/web-frontend/.vscode/settings.json
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"files.exclude": {
|
||||
"**/.git": true, // this is a default value
|
||||
"**/.DS_Store": true, // this is a default value
|
||||
"**/node_modules": true // this excludes all folders
|
||||
},
|
||||
"workbench.tree.indent": 23,
|
||||
"workbench.list.smoothScrolling": true,
|
||||
}
|
15
web3-examples/web-frontend/README.md
Normal file
15
web3-examples/web-frontend/README.md
Normal file
@ -0,0 +1,15 @@
|
||||
|
||||
|
||||
## How to run
|
||||
|
||||
To run app in the development mode use:
|
||||
|
||||
npm start
|
||||
|
||||
To run tests in the interactive watch mode use:
|
||||
|
||||
npm test
|
||||
|
||||
To make a production build use:
|
||||
|
||||
npm run build
|
18497
web3-examples/web-frontend/package-lock.json
generated
Normal file
18497
web3-examples/web-frontend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
51
web3-examples/web-frontend/package.json
Normal file
51
web3-examples/web-frontend/package.json
Normal file
@ -0,0 +1,51 @@
|
||||
{
|
||||
"name": "fluent-pad",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@fluencelabs/fluence": "^0.9.17",
|
||||
"@fluencelabs/fluence-network-environment": "^1.0.8",
|
||||
"@testing-library/jest-dom": "^5.11.6",
|
||||
"@testing-library/react": "^11.2.2",
|
||||
"@testing-library/user-event": "^12.5.0",
|
||||
"@types/jest": "^26.0.19",
|
||||
"@types/node": "^12.19.9",
|
||||
"@types/react": "^16.14.2",
|
||||
"@types/react-dom": "^16.9.10",
|
||||
"node-sass": "^4.14.1",
|
||||
"react": "^17.0.1",
|
||||
"react-dom": "^17.0.1",
|
||||
"react-scripts": "4.0.1",
|
||||
"typescript": "^4.1.3",
|
||||
"web-vitals": "^0.2.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 12
|
||||
},
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {}
|
||||
}
|
BIN
web3-examples/web-frontend/public/favicon.ico
Normal file
BIN
web3-examples/web-frontend/public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.8 KiB |
45
web3-examples/web-frontend/public/index.html
Normal file
45
web3-examples/web-frontend/public/index.html
Normal file
@ -0,0 +1,45 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Montserrat&display=swap" rel="stylesheet" />
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/tonsky/FiraCode@4/distr/fira_code.css">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using create-react-app"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>React App</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
BIN
web3-examples/web-frontend/public/logo192.png
Normal file
BIN
web3-examples/web-frontend/public/logo192.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.2 KiB |
BIN
web3-examples/web-frontend/public/logo512.png
Normal file
BIN
web3-examples/web-frontend/public/logo512.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.4 KiB |
25
web3-examples/web-frontend/public/manifest.json
Normal file
25
web3-examples/web-frontend/public/manifest.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"short_name": "React App",
|
||||
"name": "Create React App Sample",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "logo192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "logo512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
3
web3-examples/web-frontend/public/robots.txt
Normal file
3
web3-examples/web-frontend/public/robots.txt
Normal file
@ -0,0 +1,3 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
199
web3-examples/web-frontend/src/components/App.scss
Normal file
199
web3-examples/web-frontend/src/components/App.scss
Normal file
@ -0,0 +1,199 @@
|
||||
$bg-color1: white;
|
||||
$bg-color2: #f4f4f4;
|
||||
$font-color1: black;
|
||||
$color-disabled: lightgray;
|
||||
|
||||
$color1: black;
|
||||
$color2: rgb(214, 214, 214);
|
||||
|
||||
$accent-color: rgb(225, 30, 90);
|
||||
|
||||
$header-height: 60px;
|
||||
$border-radius: 10px;
|
||||
|
||||
@mixin font($size) {
|
||||
font-family: 'Montserrat', sans-serif;
|
||||
font-size: $size;
|
||||
}
|
||||
|
||||
$shadow: 0px 5px 20px rgba(0, 0, 0, 0.1);
|
||||
|
||||
body {
|
||||
background-color: $bg-color2;
|
||||
|
||||
font-family: 'Montserrat', sans-serif;
|
||||
}
|
||||
|
||||
.header-wrapper {
|
||||
padding: 0 20px;
|
||||
height: $header-height;
|
||||
|
||||
background-color: $bg-color1;
|
||||
color: $font-color1;
|
||||
|
||||
box-shadow: $shadow;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
width: 1200px;
|
||||
height: 100%;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
|
||||
@media (max-width: 1280px) {
|
||||
width: calc(100vw - 80px);
|
||||
margin-left: 20px;
|
||||
margin-right: 45px;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
width: 1700px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
padding-top: 50px;
|
||||
|
||||
@media (max-width: 1280px) {
|
||||
width: calc(100vw - 80px);
|
||||
margin-left: 35px;
|
||||
margin-right: 45px;
|
||||
}
|
||||
}
|
||||
|
||||
.text-input {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
padding: 0 15px;
|
||||
box-sizing: border-box;
|
||||
|
||||
margin-top: 5px;
|
||||
border: none;
|
||||
|
||||
font-style: normal;
|
||||
@include font(15px);
|
||||
color: $color1;
|
||||
|
||||
box-shadow: $shadow;
|
||||
|
||||
&::placeholder {
|
||||
color: $color2;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
outline: 1px solid white;
|
||||
border: 1px solid $accent-color;
|
||||
}
|
||||
}
|
||||
|
||||
.button {
|
||||
display: inline;
|
||||
width: 100px;
|
||||
height: 40px;
|
||||
|
||||
box-sizing: border-box;
|
||||
background-color: white;
|
||||
|
||||
margin-top: 10px;
|
||||
margin-right: 10px;
|
||||
border: none;
|
||||
|
||||
font-style: normal;
|
||||
@include font(15px);
|
||||
color: $color1;
|
||||
|
||||
box-shadow: $shadow;
|
||||
|
||||
&::placeholder {
|
||||
color: $color2;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
outline: 1px solid white;
|
||||
border: 1px solid $accent-color;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
color: gray;
|
||||
}
|
||||
}
|
||||
|
||||
.form-caption {
|
||||
color: $accent-color;
|
||||
text-align: center;
|
||||
|
||||
font-style: normal;
|
||||
font-weight: bold;
|
||||
@include font(20px);
|
||||
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.header-item {
|
||||
@include font(14px);
|
||||
text-transform: uppercase;
|
||||
|
||||
& button {
|
||||
@include font(14px);
|
||||
text-transform: uppercase;
|
||||
|
||||
background: none;
|
||||
border-top: none;
|
||||
border-right: none;
|
||||
border-left: none;
|
||||
border-bottom: 1px solid $accent-color;
|
||||
text-decoration: none;
|
||||
|
||||
outline: none;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: $accent-color;
|
||||
border-bottom: 2px solid $accent-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.red {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.green {
|
||||
color: green;
|
||||
}
|
||||
|
||||
.accent {
|
||||
color: $accent-color;
|
||||
}
|
||||
|
||||
.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.table-wrapper {
|
||||
width: 100%;
|
||||
min-height: 800px;
|
||||
margin-top: 50px;
|
||||
padding: 10px;
|
||||
box-sizing: border-box;
|
||||
background-color: white;
|
||||
|
||||
box-shadow: $shadow;
|
||||
}
|
||||
|
||||
th {
|
||||
@include font(18px);
|
||||
min-width: 100px;
|
||||
border-bottom: 20px solid transparent;
|
||||
}
|
||||
|
||||
td {
|
||||
@include font(14px);
|
||||
border-left: 15px solid transparent;
|
||||
border-right: 15px solid transparent;
|
||||
}
|
141
web3-examples/web-frontend/src/components/App.tsx
Normal file
141
web3-examples/web-frontend/src/components/App.tsx
Normal file
@ -0,0 +1,141 @@
|
||||
import { createClient, FluenceClient, subscribeToEvent } from '@fluencelabs/fluence';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import {
|
||||
createFilter,
|
||||
getFilterChanges,
|
||||
getFilterChangesWithoutNulls,
|
||||
getTxInfo,
|
||||
relayNode,
|
||||
removeFilter,
|
||||
TxInfo,
|
||||
} from 'src/fluence';
|
||||
|
||||
import './App.scss';
|
||||
|
||||
const intervalMs = 4000;
|
||||
|
||||
const App = () => {
|
||||
const [client, setClient] = useState<FluenceClient | null>(null);
|
||||
const [ethNodeUrl, setEthNodeUrl] = useState('');
|
||||
const [filterId, setFilterId] = useState<string | null>(null);
|
||||
const [timer, setTimer] = useState<any>();
|
||||
const [data, setData] = useState<TxInfo[]>([]);
|
||||
|
||||
const updateData = async (filterId) => {
|
||||
if (!filterId || !client) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const data = await getFilterChangesWithoutNulls(client, ethNodeUrl, filterId, '50');
|
||||
console.log(data);
|
||||
setData((prev) => {
|
||||
return [...data, ...prev];
|
||||
});
|
||||
} catch (err) {
|
||||
console.log('updateData failed', err);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const fn = async () => {
|
||||
try {
|
||||
const client = await createClient(relayNode);
|
||||
setClient(client);
|
||||
} catch (err) {
|
||||
console.log('Client initialization failed', err);
|
||||
}
|
||||
};
|
||||
fn();
|
||||
}, []);
|
||||
|
||||
const start = async () => {
|
||||
if (!client) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const filterId = await createFilter(client, ethNodeUrl);
|
||||
setFilterId(filterId);
|
||||
const timer = setInterval(updateData, intervalMs, filterId);
|
||||
setTimer(timer);
|
||||
} catch (err) {
|
||||
console.log('createFilter failed', err);
|
||||
}
|
||||
};
|
||||
|
||||
const stop = async () => {
|
||||
if (!filterId || !client) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
clearInterval(timer);
|
||||
setTimer(null);
|
||||
|
||||
const res = await removeFilter(client, ethNodeUrl, filterId);
|
||||
console.log(res);
|
||||
setFilterId(null);
|
||||
setData([]);
|
||||
} catch (err) {
|
||||
console.log('stop failed', err);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="header-wrapper">
|
||||
<div className="header">
|
||||
<div className="header-item"></div>
|
||||
|
||||
<div className="header-item">
|
||||
Connection status: {client ? <span className="accent">connected</span> : 'disconnected'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="content">
|
||||
<div>Node address. e.g: https://eth-mainnet.alchemyapi.io/v2/xxxxxxx-xxxxxxxx-xxxxxxxxxx_xxxx</div>
|
||||
<div>
|
||||
<input
|
||||
className="text-input"
|
||||
onChange={(e) => setEthNodeUrl(e.target.value)}
|
||||
type="text"
|
||||
value={ethNodeUrl}
|
||||
/>
|
||||
</div>
|
||||
<div className="buttons">
|
||||
<button disabled={!ethNodeUrl} className="button" onClick={start}>
|
||||
start
|
||||
</button>
|
||||
|
||||
<button className="button" onClick={stop}>
|
||||
stop
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="table-wrapper">
|
||||
<table className="table">
|
||||
<tr>
|
||||
<th>from</th>
|
||||
<th>to</th>
|
||||
<th>gas</th>
|
||||
<th>gas price</th>
|
||||
<th>hash</th>
|
||||
</tr>
|
||||
{data.map((x) => (
|
||||
<tr key={x.hash}>
|
||||
<td className="td1">{x.from}</td>
|
||||
<td className="td2">{x.to}</td>
|
||||
<td className="td3">{parseInt(x.gas, 16)}</td>
|
||||
<td className="td4">{parseInt(x.gasPrice, 16)}</td>
|
||||
<td className="td5">{x.hash}</td>
|
||||
</tr>
|
||||
))}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
179
web3-examples/web-frontend/src/fluence.ts
Normal file
179
web3-examples/web-frontend/src/fluence.ts
Normal file
@ -0,0 +1,179 @@
|
||||
import { FluenceClient, Particle, sendParticleAsFetch } from '@fluencelabs/fluence';
|
||||
import { testNet, dev } from '@fluencelabs/fluence-network-environment';
|
||||
|
||||
const timeout = 20000;
|
||||
|
||||
export const relayNode = dev[2];
|
||||
|
||||
const node = dev[2].peerId;
|
||||
|
||||
const serviceId = '8067ab18-1a95-4cd9-b477-a33e3549012f';
|
||||
const bluePrintId = 'uuid-dc0b258-65f0-11eb-bf24-acde48001122';
|
||||
|
||||
export const getInterface = async (client: FluenceClient) => {
|
||||
const script = `
|
||||
(seq
|
||||
(call myRelay ("op" "identity") [])
|
||||
(seq
|
||||
(call node ("srv" "get_interfaces") [arg] result)
|
||||
(seq
|
||||
(call myRelay ("op" "identity") [])
|
||||
(call myPeerId ("_callback" "getInterface") [result])
|
||||
)
|
||||
)
|
||||
)
|
||||
`;
|
||||
|
||||
const data = {
|
||||
node: node,
|
||||
// arg: { blueprint_id: bluePrintId, service_id: serviceId },
|
||||
arg: serviceId,
|
||||
myRelay: client.relayPeerId,
|
||||
myPeerId: client.selfPeerId,
|
||||
};
|
||||
return sendParticleAsFetch(client, new Particle(script, data, 999999), 'getInterface');
|
||||
};
|
||||
|
||||
type Method =
|
||||
| 'eth_get_balance'
|
||||
| 'test_eth_get_balance_bad'
|
||||
| 'test_filters'
|
||||
| 'test_drop_outliers_and_average'
|
||||
| 'test_eth_get_balance_good'
|
||||
| 'test_simple_average'
|
||||
| 'test_pending_with_null_filter'
|
||||
| 'new_pending_tx_filter'
|
||||
| 'eth_hash_method_id'
|
||||
| 'test_eth_hash_method_id'
|
||||
| 'sum_data'
|
||||
| 'drop_outliers_and_average'
|
||||
| 'uninstall_filter'
|
||||
| 'eth_get_tx_by_hash'
|
||||
| 'simple_average'
|
||||
| 'test_eth_get_tx_by_hash'
|
||||
| 'eth_get_block_height'
|
||||
| 'get_filter_changes'
|
||||
| 'get_filter_changes_without_null';
|
||||
|
||||
const callEthService = async <T>(client: FluenceClient, method: Method, args: any[], ttl: number) => {
|
||||
const argsNames = args.map((val, index) => `arg${index}`).join(' ');
|
||||
|
||||
const script = `
|
||||
(seq
|
||||
(call myRelay ("op" "identity") [])
|
||||
(seq
|
||||
(call node (serviceId fnName) [${argsNames}] result)
|
||||
(seq
|
||||
(call myRelay ("op" "identity") [])
|
||||
(call myPeerId ("_callback" "${method}") [result])
|
||||
)
|
||||
)
|
||||
)
|
||||
`;
|
||||
|
||||
const data = new Map();
|
||||
data.set('node', node);
|
||||
data.set('serviceId', serviceId);
|
||||
data.set('fnName', method);
|
||||
data.set('myRelay', client.relayPeerId);
|
||||
data.set('myPeerId', client.selfPeerId);
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
data.set('arg' + i, args[i]);
|
||||
}
|
||||
|
||||
return await sendParticleAsFetch<T>(client, new Particle(script, data, ttl), method);
|
||||
};
|
||||
|
||||
export const createFilter = async (client: FluenceClient, url: string): Promise<string> => {
|
||||
const [res] = await callEthService<[string]>(client, 'new_pending_tx_filter', [url], timeout);
|
||||
return res;
|
||||
};
|
||||
|
||||
export const getFilterChanges = async (
|
||||
client: FluenceClient,
|
||||
url: string,
|
||||
filterId: string,
|
||||
): Promise<Array<string>> => {
|
||||
const [res] = await callEthService<[string]>(client, 'get_filter_changes', [url, filterId], timeout);
|
||||
return JSON.parse(res).result;
|
||||
};
|
||||
|
||||
export const getFilterChangesWithoutNulls = async (
|
||||
client: FluenceClient,
|
||||
url: string,
|
||||
filterId: string,
|
||||
n: string,
|
||||
): Promise<Array<TxInfo>> => {
|
||||
const [res] = await callEthService<[any]>(client, 'get_filter_changes_without_null', [url, filterId, n], timeout);
|
||||
return res;
|
||||
};
|
||||
|
||||
export const removeFilter = async (client: FluenceClient, url: string, filterId: string): Promise<boolean> => {
|
||||
const [res] = await callEthService<[number]>(client, 'uninstall_filter', [url, filterId], timeout);
|
||||
return res === 1 ? true : false;
|
||||
};
|
||||
|
||||
export interface TxInfo {
|
||||
/**
|
||||
* DATA, 32 Bytes - hash of the block where this transaction was in. null when its pending.
|
||||
*/
|
||||
blockHash: any;
|
||||
/**
|
||||
* QUANTITY - block number where this transaction was in. null when its pending.
|
||||
*/
|
||||
blockNumber: any;
|
||||
/**
|
||||
* DATA, 20 Bytes - address of the sender.
|
||||
*/
|
||||
from: any;
|
||||
/**
|
||||
* QUANTITY - gas provided by the sender.
|
||||
*/
|
||||
gas: any;
|
||||
/**
|
||||
* QUANTITY - gas price provided by the sender in Wei.
|
||||
*/
|
||||
gasPrice: any;
|
||||
/**
|
||||
* DATA, 32 Bytes - hash of the transaction.
|
||||
*/
|
||||
hash: any;
|
||||
/**
|
||||
* DATA - the data send along with the transaction.
|
||||
*/
|
||||
input: any;
|
||||
/**
|
||||
* QUANTITY - the number of transactions made by the sender prior to this one.
|
||||
*/
|
||||
nonce: any;
|
||||
/**
|
||||
* DATA, 20 Bytes - address of the receiver. null when its a contract creation transaction.
|
||||
*/
|
||||
to: any;
|
||||
/**
|
||||
* QUANTITY - integer of the transactions index position in the block. null when its pending.
|
||||
*/
|
||||
transactionIndex: any;
|
||||
/**
|
||||
* QUANTITY - value transferred in Wei.
|
||||
*/
|
||||
value: any;
|
||||
/**
|
||||
* QUANTITY - ECDSA recovery id
|
||||
*/
|
||||
v: any;
|
||||
/**
|
||||
* DATA, 32 Bytes - ECDSA signature r
|
||||
*/
|
||||
r: any;
|
||||
/**
|
||||
* DATA, 32 Bytes - ECDSA signature s
|
||||
*/
|
||||
s: any;
|
||||
}
|
||||
|
||||
export const getTxInfo = async (client: FluenceClient, url: string, tx: string): Promise<TxInfo> => {
|
||||
const [raw] = await callEthService<[string]>(client, 'eth_get_tx_by_hash', [url, tx], timeout);
|
||||
const res = JSON.parse(raw);
|
||||
return res.result;
|
||||
};
|
144
web3-examples/web-frontend/src/index.scss
Normal file
144
web3-examples/web-frontend/src/index.scss
Normal file
@ -0,0 +1,144 @@
|
||||
/* http://meyerweb.com/eric/tools/css/reset/
|
||||
v2.0 | 20110126
|
||||
License: none (public domain)
|
||||
*/
|
||||
|
||||
html,
|
||||
body,
|
||||
div,
|
||||
span,
|
||||
applet,
|
||||
object,
|
||||
iframe,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
p,
|
||||
blockquote,
|
||||
pre,
|
||||
a,
|
||||
abbr,
|
||||
acronym,
|
||||
address,
|
||||
big,
|
||||
cite,
|
||||
code,
|
||||
del,
|
||||
dfn,
|
||||
em,
|
||||
img,
|
||||
ins,
|
||||
kbd,
|
||||
q,
|
||||
s,
|
||||
samp,
|
||||
small,
|
||||
strike,
|
||||
strong,
|
||||
sub,
|
||||
sup,
|
||||
tt,
|
||||
var,
|
||||
b,
|
||||
u,
|
||||
i,
|
||||
center,
|
||||
dl,
|
||||
dt,
|
||||
dd,
|
||||
ol,
|
||||
ul,
|
||||
li,
|
||||
fieldset,
|
||||
form,
|
||||
label,
|
||||
legend,
|
||||
table,
|
||||
caption,
|
||||
tbody,
|
||||
tfoot,
|
||||
thead,
|
||||
tr,
|
||||
th,
|
||||
td,
|
||||
article,
|
||||
aside,
|
||||
canvas,
|
||||
details,
|
||||
embed,
|
||||
figure,
|
||||
figcaption,
|
||||
footer,
|
||||
header,
|
||||
hgroup,
|
||||
menu,
|
||||
nav,
|
||||
output,
|
||||
ruby,
|
||||
section,
|
||||
summary,
|
||||
time,
|
||||
mark,
|
||||
audio,
|
||||
video {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
font-size: 100%;
|
||||
font: inherit;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
/* HTML5 display-role reset for older browsers */
|
||||
|
||||
article,
|
||||
aside,
|
||||
details,
|
||||
figcaption,
|
||||
figure,
|
||||
footer,
|
||||
header,
|
||||
hgroup,
|
||||
menu,
|
||||
nav,
|
||||
section {
|
||||
display: block;
|
||||
}
|
||||
|
||||
body {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
blockquote,
|
||||
q {
|
||||
quotes: none;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
&:before,
|
||||
&:after {
|
||||
content: '';
|
||||
content: none;
|
||||
}
|
||||
}
|
||||
|
||||
q {
|
||||
&:before,
|
||||
&:after {
|
||||
content: '';
|
||||
content: none;
|
||||
}
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
14
web3-examples/web-frontend/src/index.tsx
Normal file
14
web3-examples/web-frontend/src/index.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import './index.scss';
|
||||
import App from './components/App';
|
||||
import log from 'loglevel';
|
||||
|
||||
log.setLevel('error');
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root'),
|
||||
);
|
1
web3-examples/web-frontend/src/react-app-env.d.ts
vendored
Normal file
1
web3-examples/web-frontend/src/react-app-env.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
/// <reference types="react-scripts" />
|
5
web3-examples/web-frontend/src/setupTests.ts
Normal file
5
web3-examples/web-frontend/src/setupTests.ts
Normal file
@ -0,0 +1,5 @@
|
||||
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
||||
// allows you to do things like:
|
||||
// expect(element).toHaveTextContent(/react/i)
|
||||
// learn more: https://github.com/testing-library/jest-dom
|
||||
import '@testing-library/jest-dom';
|
30
web3-examples/web-frontend/tsconfig.json
Normal file
30
web3-examples/web-frontend/tsconfig.json
Normal file
@ -0,0 +1,30 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"baseUrl": ".",
|
||||
"noImplicitAny": false,
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
]
|
||||
}
|
6
web3-examples/web3-examples/.gitignore
vendored
Normal file
6
web3-examples/web3-examples/.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
.DS_Store
|
||||
.repl_history
|
||||
/target
|
||||
**/**.bak
|
||||
**/**.bk
|
||||
|
12
web3-examples/web3-examples/Config.toml
Normal file
12
web3-examples/web3-examples/Config.toml
Normal file
@ -0,0 +1,12 @@
|
||||
modules_dir = "artifacts/"
|
||||
|
||||
[[module]]
|
||||
name = "curl_adapter"
|
||||
# logger_enabled = false
|
||||
|
||||
[module.mounted_binaries]
|
||||
curl = "/usr/bin/curl"
|
||||
|
||||
[[module]]
|
||||
name = "facade"
|
||||
# logger_enabled = true
|
417
web3-examples/web3-examples/Readme.md
Normal file
417
web3-examples/web3-examples/Readme.md
Normal file
@ -0,0 +1,417 @@
|
||||
# EthDenver Virtual '21 Fluence Hackathon
|
||||
|
||||
## Introduction
|
||||
This quickstart aims to get teams up and running with the Fluence stack and Ethereum. If you're new to Fluence, give the ol' [documentation](https://fluence.dev/docs) a gander before diving in. Please note that the Fluence stack is under heavy development. If you find errors, incompatibilities or the dreaded dead link, post an issue or even better, push a PR.
|
||||
|
||||
## Quickstart
|
||||
The point of this tutorial is to get you up-to-speed and productive with the Fluence stack as quickly as possible in the context of Web3 development. To this end, we bootstrap from a few [Ethereum JSON-RPC](https://eth.wiki/json-rpc/API) calls to a stylized frontend and cover all the good stuff along the way. If you haven't had a chance to work through the [greeting example](https://fluence.dev/docs/how-to-develop-a-module), this might be a good time. For additional examples, check out the [fce](https://github.com/fluencelabs/fce/tree/master/examples) repo and the [fluent pad](https://github.com/fluencelabs/fluent-pad) and [aqua demo](https://github.com/fluencelabs/aqua-demo) demos.
|
||||
|
||||
Before we dive in, setup your [Rust](https://www.rust-lang.org/tools/install) and [Fluence environment](https://fluence.dev/docs/how-to-develop-a-module) if you haven't done so already, clone this repo to your machine or instance:
|
||||
|
||||
```bash
|
||||
git clone git@github.com:fluencelabs/ethdenver-hackathon.git
|
||||
cd ethdenver-hackathon
|
||||
```
|
||||
|
||||
and build the examples:
|
||||
|
||||
```bash
|
||||
cd web3-examples
|
||||
./build.sh
|
||||
```
|
||||
|
||||
if you get a permission error, `chmod +x build.sh` and while we're at it, add:
|
||||
|
||||
```bash
|
||||
mkdir artifacts
|
||||
```
|
||||
|
||||
where the artifacts directory serves as a convenient destination for the wasm files we create with the build process.
|
||||
|
||||
Recall from the [create a service](https://fluence.dev/docs/services-development) docs that a service is comprised of one or more modules. For for the purposes of a our tutorial, we are working with a "fat" service, i.e., one service with multiple modules. For all intents and purposes, this is not advisable but helpful for keeping things tight for this overview.
|
||||
|
||||
Before we proceed, make sure you have an ethereum node running and ready to connect. If you prefer to use a node-as-a-service, we recommend [Alchemy](https://www.alchemyapi.io/), which offers a generous free account.
|
||||
### Getting Started With Fluence and Web3 Services
|
||||
[WASM](https://developer.mozilla.org/en-US/docs/WebAssembly) is a relatively new concept and WASM for backend is even newer, e.g., [wasmer](https://github.com/wasmerio/wasmer), [WASI](https://github.com/CraneStation/wasi), but maturing at a rapid clip. Yet, there are still limitations we need to be aware of. For example, sock support and async capabilities are currently not available. Not to worry, we can work around those constraints without too much heavy lifting and still build effective solutions.
|
||||
|
||||
For the time being, our go-to transport comes courtesy of [curl](https://curl.se/docs/) as a service. Please note that since curl generally does not provide web socket (ws, wss) capabilities, https is our transport tool of choice. This has a few implications especially when it comes blockchain client access as a service. For example, a subset of the Ethereum JSON RPC calls in [Infura](https://infura.io/docs/ethereum/wss/introduction), are only accessible via wss. Luckily, [Alchemy](https://www.alchemyapi.io/) offers a viable alternative for those not running their own node. Using curl generally has no performance penalties and in most cases actually speeds things up but it should be noted that leaving the WASM sandbox comes at a cost: a node provider can easily monitor and exploit curl call data, such as api-keys. If that is a concern, we recommend you run your own node; if it is more of a testnet concern, we recommend using project-specific api-keys, and rotate them periodically.
|
||||
|
||||
As mentioned earlier, async is currently not quite there but the Fluence team has implemented a [cron-like](https://fluence.dev/docs/built-in-services#script-add) script to allow polling as part of the native node services.
|
||||
|
||||
From a development perspective, a little extra care needs to be taken with respect to error management. Specifically, Result<_,_> does not work out of the box in WASI. If you want to return a Result, you need to implement your own. See the [example](facade/src/fce_results.rs) code.
|
||||
|
||||
In the web3-examples folder, we illustrate the core concepts of Web3 service development with a few Ethereum JSON-RPC calls. In a nutshell, [FCE](https://github.com/fluencelabs/fce) compliant services are written and compiled with `fce build`. Let's install the tool:
|
||||
|
||||
```bash
|
||||
cargo install fcli
|
||||
```
|
||||
|
||||
Or see the Fluence [documentation](https://fluence.dev/docs/setting-up-the-development-environment) for a step-by-step dev setup.
|
||||
|
||||
The resulting WASM modules can then be locally inspected and executed with the Fluence repl, `fce-repl`, e.g.:
|
||||
|
||||
```bash
|
||||
mbp16~/localdev/ethdenver-hackathon/web3-examples(main|✚3…) % fce-repl Config.toml
|
||||
Welcome to the FCE REPL (version 0.1.33)
|
||||
app service was created with service id = 78f2f68f-cec6-4134-b69e-e4826dc2a846
|
||||
elapsed time 180.489951ms
|
||||
|
||||
1> help
|
||||
Commands:
|
||||
|
||||
n/new [config_path] create a new service (current will be removed)
|
||||
l/load <module_name> <module_path> load a new Wasm module
|
||||
u/unload <module_name> unload a Wasm module
|
||||
c/call <module_name> <func_name> [args] call function with given name from given module
|
||||
i/interface print public interface of all loaded modules
|
||||
e/envs <module_name> print environment variables of a module
|
||||
f/fs <module_name> print filesystem state of a module
|
||||
h/help print this message
|
||||
q/quit/Ctrl-C exit
|
||||
```
|
||||
|
||||
### A Simple Example
|
||||
Let's have a look at one of the examples, eth_get_balance, from `eth_calls_test.rs`:
|
||||
|
||||
```rust
|
||||
#[fce]
|
||||
pub fn eth_get_balance(url: String, account: String, block_number: String) -> JsonRpcResult {
|
||||
let method = String::from("eth_getBalance");
|
||||
let id = get_nonce();
|
||||
|
||||
let block_identifier: String;
|
||||
let number_test = block_number.parse::<u64>();
|
||||
if number_test.is_ok() {
|
||||
block_identifier = format!("0x{:x}", number_test.unwrap());
|
||||
} else if BLOCK_NUMBER_TAGS.contains(&block_number.as_str()) {
|
||||
block_identifier = String::from(block_number);
|
||||
} else {
|
||||
block_identifier = String::from("latest");
|
||||
}
|
||||
|
||||
let params: Vec<String> = vec![account, block_identifier];
|
||||
let curl_args: String = Request::new(method, params, id).as_sys_string(&url);
|
||||
let response: String = unsafe { curl_request(curl_args) };
|
||||
check_response_string(response, &id)
|
||||
}
|
||||
```
|
||||
|
||||
This code snippet is based on the Ethereum JSON-RPC API [eth_getBalance](https://eth.wiki/json-rpc/API#eth_getbalance) and returns the balance of the named account for the destination chain specified. We implement that method by combining our custom code with the [curl module](https://fluence.dev/docs/creating-a-service#curl-module). This call should look familiar to most dAPP developers, although Web3 libraries abstract over the raw calls.
|
||||
|
||||
So what's going on?
|
||||
1. We apply the fce macro to the function, which returns our custom [JsonRpcResult](facade/src/results.rs)
|
||||
2. We specify the actual method name, which by the way, may deviate from the Ethereum spec's depending on the eth-client provider. See [eth_filters.rs](facade/src/eth_filters.rs) for an example.
|
||||
3. We generate our nonce, aka id, which is based on the thread-safe nonce counter, NONCE_COUNTER, implemented in [eth_utils.rs](facade/src/eth_utis.rs):
|
||||
|
||||
```rust
|
||||
pub static NONCE_COUNTER: AtomicUsize = AtomicUsize::new(1);
|
||||
```
|
||||
|
||||
4. We handle our block_number parameters to makes sure it's either a valid (positive) number or one of the ["latest", "pending", "earliest"] parameter options. Note that some of the node-as-a-service providers do not provide historical data without users signing up for archive services.
|
||||
5. Now we format our params and args into a json-rpc dict suitable for curl consumption.
|
||||
6. We finally check our response and return the result
|
||||
|
||||
We can now run that function in Fluence Repl with `fce-repl Config.toml` from the web3-examples directory.:
|
||||
```bash
|
||||
1> call facade eth_get_balance ["https://eth-mainnet.alchemyapi.io/v2/<your key>", "0x0000000000000000000000000000000000000000", "latest"]
|
||||
curl args: -X POST --data '{"jsonrpc":"2.0", "method": "eth_getBalance", "params":["0x0000000000000000000000000000000000000000", "latest"], "id":2}' https://eth-mainnet.alchemyapi.io/v2/<your key>
|
||||
INFO: Running "/usr/bin/curl -X POST --data {"jsonrpc":"2.0", "method": "eth_getBalance", "params":["0x0000000000000000000000000000000000000000", "latest"], "id":2} https://eth-mainnet.alchemyapi.io/v2/<your key>" ...
|
||||
% Total % Received % Xferd Average Speed Time Time Time Current
|
||||
Dload Upload Total Spent Left Speed
|
||||
100 182 100 62 100 120 83 161 --:--:-- --:--:-- --:--:-- 243
|
||||
result: Object({"error": String(""), "id": Number(2), "jsonrpc": String("2.0"), "result": String("0x1c804d8c47f4e326821")})
|
||||
elapsed time: 756.728025ms
|
||||
2>
|
||||
```
|
||||
|
||||
Before we dive into what's been happening, make sure you are familiar with the [Fluence REPL](https://fluence.dev/docs/fluence-repl) and the construction of [Config.toml](Config.toml).
|
||||
|
||||
We specify that we want to `call` the `eth_get_balance` function from the `facade` module with our Ethereum node `url` and `latest` block parameters.
|
||||
Note that for the purpose of the examples, we return the raw result(s), which are usually hex strings; due to the Result limitations discussed earlier, you need to explicitly check the error string before processing the result. In a general manner, this entails:
|
||||
|
||||
```rust
|
||||
// <snip>
|
||||
let result = JsonRpcResult {error: "".to_string(),
|
||||
id: 2u64,
|
||||
jsonrpc: "2.0".to_string(),
|
||||
result: "0x1c804d8c47f4e326821".to_string()};
|
||||
match result.error.len() {
|
||||
0 => println!("do something with ok such as {}", u128::from_str_radix(result[2..], 16)),
|
||||
_ => println!("do something with err")
|
||||
}
|
||||
```
|
||||
|
||||
#### A Note On Testing
|
||||
Due to current limitations in WASI, Rust unit tests proper are not working for fce modules when an external binary, such as curl, is imported. A workaround is to implement fce mearked-up test functions and run them in fce-repl. The examples below are based on `eth_getBalance` call discussed above.
|
||||
|
||||
```rust
|
||||
#[fce]
|
||||
fn test_eth_get_balance_good(url: String) -> TestResult {
|
||||
let burn_address = String::from("0x0000000000000000000000000000000000000000");
|
||||
let block_height = String::from("latest");
|
||||
// burn account balances, min, per 1/27/21:
|
||||
// https://etherscan.io/address/0x0000000000000000000000000000000000000000; 8412.0
|
||||
// https://kovan.etherscan.io/address/0x0000000000000000000000000000000000000000; 213.0
|
||||
// https://rinkeby.etherscan.io/address/0x0000000000000000000000000000000000000000; 1566.0
|
||||
// https://goerli.etherscan.io/address/0x0000000000000000000000000000000000000000; 1195.0
|
||||
|
||||
let result = eth_get_balance(url, burn_address, block_height);
|
||||
let hex_balance: String = result.result;
|
||||
let wei_balance: u128 = u128::from_str_radix(&hex_balance[2..], 16).unwrap();
|
||||
let eth_balance: f64 = wei_to_eth(&wei_balance);
|
||||
if eth_balance > 213.0 {
|
||||
return TestResult::from(Result::from(Ok(String::from(""))));
|
||||
}
|
||||
let err_msg = format!("expected: gt {}, actual {:.2}", 213.0, eth_balance);
|
||||
TestResult::from(Result::from(Err(err_msg)))
|
||||
}
|
||||
|
||||
#[fce]
|
||||
fn test_eth_get_balance_bad(url: String) -> TestResult {
|
||||
let burn_address = String::from("0x0000000000000000000000000000000000000000");
|
||||
let block_height = String::from("latest");
|
||||
// burn account balances, min, per 1/27/21:
|
||||
// https://etherscan.io/address/0x0000000000000000000000000000000000000000; 8412.0
|
||||
// https://kovan.etherscan.io/address/0x0000000000000000000000000000000000000000; 213.0
|
||||
// https://rinkeby.etherscan.io/address/0x0000000000000000000000000000000000000000; 1566.0
|
||||
// https://goerli.etherscan.io/address/0x0000000000000000000000000000000000000000; 1195.0
|
||||
|
||||
let result = eth_get_balance(url, burn_address, block_height);
|
||||
let hex_balance: String = result.result;
|
||||
let wei_balance: u128 = u128::from_str_radix(&hex_balance[2..], 16).unwrap();
|
||||
let eth_balance: f64 = wei_to_eth(&wei_balance);
|
||||
if eth_balance > 1_000_000.0 {
|
||||
return TestResult::from(Result::from(Ok(String::from(""))));
|
||||
}
|
||||
let err_msg = format!("expected: gt {}, actual {:.2}", 1_000_000, eth_balance);
|
||||
TestResult::from(Result::from(Err(err_msg)))
|
||||
}
|
||||
```
|
||||
|
||||
Here we test `eth_get_balance` with the burn address "0x0000000000000000000000000000000000000000" for the the latest block and return the result as [TestResult](facade/src/eth_utils.rs). Running the functions in fce-repl:
|
||||
|
||||
```bash
|
||||
2> call facade test_eth_get_balance_bad ["https://eth-mainnet.alchemyapi.io/v2/<your key>"]
|
||||
curl args: -X POST --data '{"jsonrpc":"2.0", "method": "eth_getBalance", "params":["0x0000000000000000000000000000000000000000", "latest"], "id":1}' https://eth-mainnet.alchemyapi.io/v2/<your key>
|
||||
INFO: Running "/usr/bin/curl -X POST --data {"jsonrpc":"2.0", "method": "eth_getBalance", "params":["0x0000000000000000000000000000000000000000", "latest"], "id":1} https://eth-mainnet.alchemyapi.io/v2/<your key>" ...
|
||||
% Total % Received % Xferd Average Speed Time Time Time Current
|
||||
Dload Upload Total Spent Left Speed
|
||||
100 182 100 62 100 120 123 238 --:--:-- --:--:-- --:--:-- 360
|
||||
result: Object({"error": String("expected: gt 1000000, actual 8412.06"), "test_passed": Number(0)})
|
||||
elapsed time: 516.627078ms
|
||||
|
||||
3> call facade test_eth_get_balance_good ["https://eth-mainnet.alchemyapi.io/v2/<your key>"]
|
||||
curl args: -X POST --data '{"jsonrpc":"2.0", "method": "eth_getBalance", "params":["0x0000000000000000000000000000000000000000", "latest"], "id":2}' https://eth-mainnet.alchemyapi.io/v2/<your key>
|
||||
INFO: Running "/usr/bin/curl -X POST --data {"jsonrpc":"2.0", "method": "eth_getBalance", "params":["0x0000000000000000000000000000000000000000", "latest"], "id":2} https://eth-mainnet.alchemyapi.io/v2/<your key>" ...
|
||||
% Total % Received % Xferd Average Speed Time Time Time Current
|
||||
Dload Upload Total Spent Left Speed
|
||||
100 182 100 62 100 120 164 319 --:--:-- --:--:-- --:--:-- 482
|
||||
result: Object({"error": String(""), "test_passed": Number(1)})
|
||||
elapsed time: 387.537328ms
|
||||
|
||||
4>
|
||||
```
|
||||
|
||||
That's it !!
|
||||
|
||||
#### A Note on Service Granularity
|
||||
While there are no hard and fast rules to determine optional service granularity, [theory](https://onlinelibrary.wiley.com/doi/full/10.1002/spe.2869) and common sense do help. Let's look at what could be a fine-grained, self-contained service: A service that could generate the [method id](https://docs.soliditylang.org/en/latest/abi-spec.html) for Ethereum smart contract functions. A simple method id [generator](facade/src/eth_hashers.rs) may look like this:
|
||||
|
||||
```rust
|
||||
use fluence::fce;
|
||||
use tiny_keccak::Sha3;
|
||||
|
||||
#[fce]
|
||||
pub fn eth_hash_method_id(input: Vec<u8>) -> Vec<u8> {
|
||||
let mut output = [0u8; 32];
|
||||
let mut keccak = Keccak::v256();
|
||||
|
||||
keccak.update(&input);
|
||||
keccak.finalize(&mut output);
|
||||
output.to_vec()
|
||||
}
|
||||
```
|
||||
|
||||
with the corresponding test:
|
||||
|
||||
```rust
|
||||
#[fce]
|
||||
pub fn test_eth_hash_method_id() -> String {
|
||||
use hex::encode;
|
||||
|
||||
// see https://docs.soliditylang.org/en/latest/abi-spec.html#examples
|
||||
let input = b"baz(uint32,bool)".to_vec();
|
||||
let expected = String::from("cdcd77c0");
|
||||
let res = eth_hash_method(input);
|
||||
let res = format!("{}", hex::encode(&res[..4]));
|
||||
|
||||
if res == expected {
|
||||
return "test passed".to_string();
|
||||
}
|
||||
"test failed".to_string()
|
||||
}
|
||||
```
|
||||
|
||||
and fce-repl execution:
|
||||
|
||||
```bash
|
||||
fce-repl Config.toml
|
||||
<snip>
|
||||
4> call facade test_eth_hash_method []
|
||||
result: String("test passed")
|
||||
elapsed time: 98.266µs
|
||||
|
||||
5>
|
||||
```
|
||||
|
||||
### Deploying our Services
|
||||
The next step is to upload our work to the network, in this case the Fluence test network.
|
||||
Recall that you can inspect all interfaces with the fce-repl tool, e.g.:
|
||||
|
||||
```
|
||||
mbp16~/localdev/lw3d/web3-examples(main|✚4…) % fce-repl Config.toml
|
||||
Welcome to the Fluence FaaS REPL
|
||||
app service's created with service id = 06acbe9e-f598-4e34-98d0-1d71117450ce
|
||||
elapsed time 138.917556ms
|
||||
|
||||
1> interface
|
||||
Application service interface:
|
||||
TestResult {
|
||||
test_passed: I32
|
||||
error: String
|
||||
}
|
||||
JsonRpcResult {
|
||||
jsonrpc: String
|
||||
result: String
|
||||
error: String
|
||||
id: U64
|
||||
}
|
||||
|
||||
facade:
|
||||
fn test_drop_outliers_and_average()
|
||||
fn get_filter_changes(url: String, filter_id: String) -> String
|
||||
fn uninstall_filter(url: String, filter_id: String) -> I32
|
||||
fn eth_hash_method_id(input: Array<U8>) -> Array<U8>
|
||||
fn test_eth_get_tx_by_hash(url: String, tx_hash: String)
|
||||
fn test_simple_average()
|
||||
fn eth_get_tx_by_hash(url: String, tx_hash: String) -> String
|
||||
fn test_pending_with_null_filter(url: String) -> String
|
||||
fn simple_average(data: Array<String>) -> String
|
||||
fn test_eth_hash_method_id() -> String
|
||||
fn new_pending_tx_filter(url: String) -> String
|
||||
fn test_eth_get_balance_good(url: String) -> TestResult
|
||||
fn sum_data(data: Array<String>) -> String
|
||||
fn eth_get_balance(url: String, account: String, block_number: String) -> JsonRpcResult
|
||||
fn test_filters(url: String) -> TestResult
|
||||
fn eth_get_block_height(url: String) -> JsonRpcResult
|
||||
fn test_eth_get_balance_bad(url: String) -> TestResult
|
||||
fn drop_outliers_and_average(data: Array<String>) -> String
|
||||
|
||||
curl_adapter:
|
||||
fn curl_request(url: String) -> String
|
||||
|
||||
```
|
||||
|
||||
First, we need some [tooling](https://fluence.dev/docs/upload-example-to-the-fluence-network):
|
||||
|
||||
```bash
|
||||
npm i @fluencelabls/fldist -g
|
||||
```
|
||||
|
||||
This installs the Fluence [proto distributor](https://github.com/fluencelabs/proto-distributor), which makes deploying our service(s) quite easy. It also includes some magic to get your services to the right test network node(s). You may recall the steps to deploy our service from the [documentation](https://fluence.dev/docs/service-lifecycle):
|
||||
|
||||
1. Upload the module(s)
|
||||
2. Create the blueprint(s)
|
||||
3. Create the service(s)
|
||||
|
||||
Since our project is structured as a "fat" service, we have two modules, see your artifacts directory, and one service. Let's get busy and upload our modules to the network:
|
||||
|
||||
```bash
|
||||
mbp16~/localdev/lw3d/web3-examples(main↑4|✚2…) % fldist upload -c curl_adapter/Config.json -p artifacts/curl_adapter.wasm -n curl_adapter
|
||||
seed: 8Nr1bfAkLzFKknwJzq5RGSkNMo9Hqk1ukF2bf7QkcBB5
|
||||
uploading module curl_adapter to node 12D3KooWBUJifCTgaxAUrcM9JysqCcS4CS8tiYH5hExbdWCAoNwb via client 12D3KooWRNGvgejbeY3aceVprwsPGgaVzvXeWGVeM8YY478JfRfE
|
||||
module uploaded successfully
|
||||
mbp16~/localdev/lw3d/web3-examples(main↑4|✚3…) % fldist upload -c facade/Config.json -p artifacts/facade.wasm -n web3_facade
|
||||
seed: AGjAP2TgthBuVJ3mESPJMRncKkamU1aMkyL4FLb4t529
|
||||
uploading module web3_facade to node 12D3KooWBUJifCTgaxAUrcM9JysqCcS4CS8tiYH5hExbdWCAoNwb via client 12D3KooWJeoiuxZuRhK91CcwcrHfGvZAekhVHrMkRDvEBnYDbkMQ
|
||||
fldist upload
|
||||
```
|
||||
|
||||
Here we uploaded both modules to the test network with `fldist upload`. Make sure your module names are unique. That is, don't use
|
||||
<i>web3_test_curl_1</i> and <i>web3_test_functions</i> but come up with your own names. You can use `fldist get_modules` to get a list of all modules and their respective names on a node. Make sure you retain the response data!
|
||||
|
||||
Let's use the `fldist` cli to verify our uploads:
|
||||
|
||||
```bash
|
||||
mbp16~/localdev/lw3d/web3-examples(main↑4|✚3…) % fldist get_modules -s 8Nr1bfAkLzFKknwJzq5RGSkNMo9Hqk1ukF2bf7QkcBB5
|
||||
[[{"interface":{"function_signatures":[{"arguments":[["url","String"],["file_name","String"]],"name":"get_n_save",<snip>
|
||||
|
||||
mbp16~/localdev/lw3d/web3-examples(main↑4|✚3…) % fldist get_modules -s AGjAP2TgthBuVJ3mESPJMRncKkamU1aMkyL4FLb4t529
|
||||
[[{"interface":{"function_signatures":[{"arguments":[["url","String"],["file_name","String"]],"name":"get_n_save" <snip>
|
||||
```
|
||||
|
||||
Looks lie we are good to go to the next step: Deploy our [blueprint](https://fluence.dev/docs/service-lifecycle#blueprints), which essentially is a configuration object. Let's design one:
|
||||
|
||||
```
|
||||
blueprint:
|
||||
```json
|
||||
{
|
||||
"id": dc0b258-65f0-11eb-bf24-acde48001132",
|
||||
"name": "eth_test_1",
|
||||
"dependencies": [ "curl_adapter", "facade"]
|
||||
}
|
||||
```
|
||||
|
||||
The blueprint id is a UUID that you need to generate . Don't reuse the one in the examples. We give our service-to-be a unique name and finally, we associate the necessary modules in dependencies. That's it. Of course, we need a blueprint for each service we want to deploy. To deploy a blueprint, we:
|
||||
|
||||
```bash
|
||||
mbp16~/localdev/lw3d/web3-examples(main↑4|✚3…) % fldist add_blueprint -i dc0b258-65f0-11eb-bf24-acde48001132 -d curl_adapter web3_test_functions -n eth_test_fat_service_01 -s 7sHe8vxCo4BkdPNPdb8f2T8CJMgTmSvBTmeqtH9QQrar
|
||||
uploading blueprint eth_test_fat_service_01 to node 12D3KooWBUJifCTgaxAUrcM9JysqCcS4CS8tiYH5hExbdWCAoNwb via client 12D3KooW9r3GAnRBa2RthsmTJq9tuvHvSFFJfN71hhcoErSBkFsZ
|
||||
blueprint 'dc0b258-65f0-11eb-bf24-acde48001132' added successfully
|
||||
```
|
||||
|
||||
We use the `fldist add_blueprint` command and add our blueprint id with the -i flag, the name with -n flag, and the dependencies with the -d flag. So what's the -s flag? It's our client seed which is our gateway to [security](https://fluence.dev/docs/security-model). Fundamentally, the client seed is created as a base58 encoding of your ED25119 secret key. If you don't have a keypair, you can use <i>fldist</i> to create one:
|
||||
|
||||
```bash
|
||||
mbp16~(:|✔) % fldist create_keypair
|
||||
{
|
||||
id: '12D3KooWKW51pN9M5xx9aBiLXm9VnZryoj6poj1e8AycVYiiPzBh',
|
||||
privKey: 'CAESYHwBglTBz5A4SaNXYVt8CrpYos8y3vEqU6gm6MympmUMj+UEygty3m6HJE/fM1hP1qe1l82s9k3w9uKTXLqyY9CP5QTKC3LebockT98zWE/Wp7WXzaz2TfD24pNcurJj0A==',
|
||||
pubKey: 'CAESII/lBMoLct5uhyRP3zNYT9antZfNrPZN8Pbik1y6smPQ',
|
||||
seed: '9M4taDKCDsJnjcmjHV8RuuW4Zj3fBU1MmKK1cKbwVUhq'
|
||||
}
|
||||
```
|
||||
|
||||
where `seed` parameterizes the -s flag. Make sure you safely retain this info.
|
||||
|
||||
Before we proceed, make sure you grab the client reference.e.g., 12D3KooW9r3GAnRBa2RthsmTJq9tuvHvSFFJfN71hhcoErSBkFsZ, and node reference, 12D3KooWBUJifCTgaxAUrcM9JysqCcS4CS8tiYH5hExbdWCAoNwb, for future use. Now we have our modules and blueprints on the network and can instantiate our service:
|
||||
|
||||
```bash
|
||||
mbp16~/localdev/lw3d/web3-examples(main↑4|✚3…) % fldist create_service -i dc0b258-65f0-11eb-bf24-acde48001132 -s 7sHe8vxCo4BkdPNPdb8f2T8CJMgTmSvBTmeqtH9QQrar
|
||||
client seed: CovY7pi37Hksxk6KvLoiYT6udHXSF8C86YrtFPnswenj
|
||||
client peerId: 12D3KooWEUd1RYhbDESfgjw1XiZe7wrFXK1DQ97r7TZdXPXHpSTM
|
||||
node peerId: 12D3KooWBUJifCTgaxAUrcM9JysqCcS4CS8tiYH5hExbdWCAoNwb
|
||||
creating service dc0b258-65f0-11eb-bf24-acde48001132
|
||||
fldist create_service
|
||||
```
|
||||
|
||||
This gives you the service id, dc0b258-65f0-11eb-bf24-acde48001132, and node id, 12D3KooWBUJifCTgaxAUrcM9JysqCcS4CS8tiYH5hExbdWCAoNwb. Now we can check on our final result:
|
||||
|
||||
```bash
|
||||
fldist get_interfaces -p 12D3KooWBUJifCTgaxAUrcM9JysqCcS4CS8tiYH5hExbdWCAoNwb|grep dc0b258-65f0-11eb-bf24-acde48001132
|
||||
```
|
||||
|
||||
So far so good. Now we are all dressed up and need somewhere to go. In the next section we put it all together in a frontend application. If you haven't had time to look over the various example [filter functions](facade/src/eth_filters.rs), this is a good time to do so.
|
||||
### Frontend
|
||||
Our frontend is quite simple but more than suffices to illustrate and work through the key concepts of using our deployed modules and services. The task at hand is to install the [eth_newPendingTransactionFilter](https://eth.wiki/json-rpc/API#eth_newpendingtransactionfilter) and to periodically poll with [eth_getFilterChanges](https://eth.wiki/json-rpc/API#eth_getfilterchanges) from our deployed services. The result is a table of pending transaction data including tx hash and gas. Why checkout pending transactions? Well, it's good for just about anything from looking for front-running opportunities to arriving at pretty accurate gas estimates and transaction backlogs, aka mainnet congestion.
|
||||
|
||||
Before we dive into the meaty details, let's take the frontend for a spin. From the repo root:
|
||||
|
||||
```bash
|
||||
cd /web-frontend
|
||||
npm install
|
||||
npm start
|
||||
```
|
||||
|
||||
Now open a tab in browser and navigate to `localhost:3000`, enter your Ethereum mainnet client url, and press the proverbial start button and pretty soon you should see your Fluence services go to work -- fetching, filtering, and transforming pending transaction data on the Fluence test network. Right on and a long time coming.
|
||||
|
BIN
web3-examples/web3-examples/artifacts/curl_adapter.wasm
Executable file
BIN
web3-examples/web3-examples/artifacts/curl_adapter.wasm
Executable file
Binary file not shown.
BIN
web3-examples/web3-examples/artifacts/facade.wasm
Executable file
BIN
web3-examples/web3-examples/artifacts/facade.wasm
Executable file
Binary file not shown.
26
web3-examples/web3-examples/build.sh
Executable file
26
web3-examples/web3-examples/build.sh
Executable file
@ -0,0 +1,26 @@
|
||||
#!/bin/sh
|
||||
|
||||
# This script builds all subprojects and puts all created Wasm modules in one dir
|
||||
# cd sqlite
|
||||
# cargo update
|
||||
# fce build --release
|
||||
# cd ..
|
||||
|
||||
mkdir -p artifacts
|
||||
|
||||
cd curl_adapter
|
||||
cargo update
|
||||
fce build --release
|
||||
cd ..
|
||||
|
||||
cd facade
|
||||
cargo update
|
||||
fce build --release
|
||||
cd ..
|
||||
|
||||
rm -f artifacts/*
|
||||
cp curl_adapter/target/wasm32-wasi/release/curl_adapter.wasm artifacts/
|
||||
cp facade/target/wasm32-wasi/release/facade.wasm artifacts/
|
||||
# cp sqlite/target/wasm32-wasi/release/sqlite_test.wasm artifacts/
|
||||
# wget https://github.com/fluencelabs/sqlite/releases/download/v0.9.0_w/sqlite3.wasm
|
||||
# mv sqlite3.wasm artifacts/
|
5
web3-examples/web3-examples/curl_adapter/.gitignore
vendored
Normal file
5
web3-examples/web3-examples/curl_adapter/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
DS_Store
|
||||
.repl_history
|
||||
/target
|
||||
**/**.bak
|
||||
**/**.bk
|
178
web3-examples/web3-examples/curl_adapter/Cargo.lock
generated
Normal file
178
web3-examples/web3-examples/curl_adapter/Cargo.lock
generated
Normal file
@ -0,0 +1,178 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "curl_adapter"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"fluence",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fluence"
|
||||
version = "0.2.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "27d9a5e4292d7bbd809a0e968e3c3aacac91cbc5acab3e26ee1e1d726f0aab24"
|
||||
dependencies = [
|
||||
"fluence-sdk-macro",
|
||||
"fluence-sdk-main",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fluence-sdk-macro"
|
||||
version = "0.2.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea1a7c75a617f827d1ba9a17b4d84e1565ab239915c63f5a85c41f89a9f1d4ba"
|
||||
dependencies = [
|
||||
"fluence-sdk-wit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fluence-sdk-main"
|
||||
version = "0.2.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6edcc983f9517c1b6bf9f851ef27f2894a3159aaa4a2fb6c9deb2ae8ecb603fa"
|
||||
dependencies = [
|
||||
"fluence-sdk-macro",
|
||||
"log",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fluence-sdk-wit"
|
||||
version = "0.2.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b75dbdd0275160f3818db3218563d791e6c612b616cd3c5d6e66283f207f648d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"syn",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.84"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1cca32fa0182e8c0989459524dc356b8f2b5c10f1b9eb521b7d182c03cf8c5ff"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
|
||||
dependencies = [
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.118"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06c64263859d87aa2eb554587e2d23183398d617427327cf2b3d0ed8c69e4800"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.118"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c84d3526699cd55261af4b941e4e725444df67aa4f9e6a3564f18030d12672df"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.61"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fceb2595057b6891a4ee808f70054bd2d12f0e97f1cbb78689b59f676df325a"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.10.2+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
|
14
web3-examples/web3-examples/curl_adapter/Cargo.toml
Normal file
14
web3-examples/web3-examples/curl_adapter/Cargo.toml
Normal file
@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "curl_adapter"
|
||||
version = "0.1.0"
|
||||
authors = ["Fluence Labs"]
|
||||
edition = "2018"
|
||||
publish = false
|
||||
|
||||
[[bin]]
|
||||
path = "src/main.rs"
|
||||
name = "curl_adapter"
|
||||
|
||||
[dependencies]
|
||||
fluence = { version = "=0.2.18", features = ["logger"]}
|
||||
log = "0.4.8"
|
7
web3-examples/web3-examples/curl_adapter/Config.json
Normal file
7
web3-examples/web3-examples/curl_adapter/Config.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "curl_adapter",
|
||||
"mountedBinaries":
|
||||
{
|
||||
"curl": "/usr/bin/curl"
|
||||
}
|
||||
}
|
55
web3-examples/web3-examples/curl_adapter/src/main.rs
Normal file
55
web3-examples/web3-examples/curl_adapter/src/main.rs
Normal file
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2021 Fluence Labs Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use fluence::fce;
|
||||
use fluence::WasmLoggerBuilder;
|
||||
|
||||
/// Log level can be changed by `RUST_LOG` env as well.
|
||||
pub fn main() {
|
||||
WasmLoggerBuilder::new().build().unwrap();
|
||||
}
|
||||
|
||||
#[fce]
|
||||
pub fn curl_request(url: String) -> String {
|
||||
// log::info!("get called with url {}", url);
|
||||
|
||||
unsafe { curl(url) }
|
||||
}
|
||||
|
||||
/// Permissions in `Config.toml` should exist to use host functions.
|
||||
#[fce]
|
||||
#[link(wasm_import_module = "host")]
|
||||
extern "C" {
|
||||
fn curl(cmd: String) -> String;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn curl_test() {
|
||||
let args = r#"-X POST --data '{"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["0x1b4", true],"id":1}'"#;
|
||||
let url = "https://kovan.infura.io/v3//0cc023286cae4ab886598ecd14e256fd";
|
||||
|
||||
let cmd = format!("{} {}", args, url);
|
||||
println!("cmd: {}", cmd);
|
||||
|
||||
let res = curl_request(cmd);
|
||||
println!("res: {}", res);
|
||||
assert!(true);
|
||||
}
|
||||
}
|
11
web3-examples/web3-examples/docs/Frontend.md
Normal file
11
web3-examples/web3-examples/docs/Frontend.md
Normal file
@ -0,0 +1,11 @@
|
||||
## Idea for frontend
|
||||
|
||||
* Use the eth_get_balance function and do a periodic pull into a sqlite db
|
||||
* requires : blockchain url, account
|
||||
|
||||
* Graph the account balance from the sqlite data
|
||||
|
||||
For an example implementation, we could use
|
||||
* Mainnet
|
||||
* DAI account
|
||||
*
|
5
web3-examples/web3-examples/facade/.gitignore
vendored
Normal file
5
web3-examples/web3-examples/facade/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
DS_Store
|
||||
.repl_history
|
||||
/target
|
||||
**/**.bak
|
||||
**/**.bk
|
497
web3-examples/web3-examples/facade/Cargo.lock
generated
Normal file
497
web3-examples/web3-examples/facade/Cargo.lock
generated
Normal file
@ -0,0 +1,497 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
||||
|
||||
[[package]]
|
||||
name = "bitvec"
|
||||
version = "0.20.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f5011ffc90248764d7005b0e10c7294f5aa1bd87d9dd7248f4ad475b347c294d"
|
||||
dependencies = [
|
||||
"funty",
|
||||
"radium",
|
||||
"tap",
|
||||
"wyz",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "byte-slice-cast"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "65c1bf4a04a88c54f589125563643d773f3254b5c38571395e2b591c693bbc81"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"time",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
|
||||
|
||||
[[package]]
|
||||
name = "ethbloom"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "779864b9c7f7ead1f092972c3257496c6a84b46dba2ce131dd8a282cb2cc5972"
|
||||
dependencies = [
|
||||
"crunchy",
|
||||
"fixed-hash",
|
||||
"impl-rlp",
|
||||
"impl-serde",
|
||||
"tiny-keccak",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ethereum-types"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f64b5df66a228d85e4b17e5d6c6aa43b0310898ffe8a85988c4c032357aaabfd"
|
||||
dependencies = [
|
||||
"ethbloom",
|
||||
"fixed-hash",
|
||||
"impl-rlp",
|
||||
"impl-serde",
|
||||
"primitive-types",
|
||||
"uint",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "facade"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"ethereum-types",
|
||||
"fluence",
|
||||
"hex",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tiny-keccak",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fixed-hash"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfcf0ed7fe52a17a03854ec54a9f76d6d84508d1c0e66bc1793301c73fc8493c"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"rand",
|
||||
"rustc-hex",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fluence"
|
||||
version = "0.2.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "27d9a5e4292d7bbd809a0e968e3c3aacac91cbc5acab3e26ee1e1d726f0aab24"
|
||||
dependencies = [
|
||||
"fluence-sdk-macro",
|
||||
"fluence-sdk-main",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fluence-sdk-macro"
|
||||
version = "0.2.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea1a7c75a617f827d1ba9a17b4d84e1565ab239915c63f5a85c41f89a9f1d4ba"
|
||||
dependencies = [
|
||||
"fluence-sdk-wit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fluence-sdk-main"
|
||||
version = "0.2.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6edcc983f9517c1b6bf9f851ef27f2894a3159aaa4a2fb6c9deb2ae8ecb603fa"
|
||||
dependencies = [
|
||||
"fluence-sdk-macro",
|
||||
"log",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fluence-sdk-wit"
|
||||
version = "0.2.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b75dbdd0275160f3818db3218563d791e6c612b616cd3c5d6e66283f207f648d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"syn",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "funty"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hex"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35"
|
||||
|
||||
[[package]]
|
||||
name = "impl-codec"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df170efa359aebdd5cb7fe78edcc67107748e4737bdca8a8fb40d15ea7a877ed"
|
||||
dependencies = [
|
||||
"parity-scale-codec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "impl-rlp"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808"
|
||||
dependencies = [
|
||||
"rlp",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "impl-serde"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b47ca4d2b6931707a55fce5cf66aff80e2178c8b63bbb4ecb5695cbc870ddf6f"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.84"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1cca32fa0182e8c0989459524dc356b8f2b5c10f1b9eb521b7d182c03cf8c5ff"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parity-scale-codec"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75c823fdae1bb5ff5708ee61a62697e6296175dc671710876871c853f48592b3"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"bitvec",
|
||||
"byte-slice-cast",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
|
||||
|
||||
[[package]]
|
||||
name = "primitive-types"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2415937401cb030a2a0a4d922483f945fa068f52a7dbb22ce0fe5f2b6f6adace"
|
||||
dependencies = [
|
||||
"fixed-hash",
|
||||
"impl-codec",
|
||||
"impl-rlp",
|
||||
"impl-serde",
|
||||
"uint",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
|
||||
dependencies = [
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "radium"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c026d7df8b298d90ccbbc5190bd04d85e159eaf5576caeacf8741da93ccbd2e5"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rlp"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e54369147e3e7796c9b885c7304db87ca3d09a0a98f72843d532868675bbfba8"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"rustc-hex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hex"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.118"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06c64263859d87aa2eb554587e2d23183398d617427327cf2b3d0ed8c69e4800"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.118"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c84d3526699cd55261af4b941e4e725444df67aa4f9e6a3564f18030d12672df"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.61"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fceb2595057b6891a4ee808f70054bd2d12f0e97f1cbb78689b59f676df325a"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tap"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36474e732d1affd3a6ed582781b3683df3d0563714c59c39591e8ff707cf078e"
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.1.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"wasi",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiny-keccak"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
|
||||
dependencies = [
|
||||
"crunchy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uint"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e11fe9a9348741cf134085ad57c249508345fe16411b3d7fb4ff2da2f1d6382e"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"crunchy",
|
||||
"hex",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.10.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "wyz"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214"
|
22
web3-examples/web3-examples/facade/Cargo.toml
Normal file
22
web3-examples/web3-examples/facade/Cargo.toml
Normal file
@ -0,0 +1,22 @@
|
||||
[package]
|
||||
name = "facade"
|
||||
version = "0.1.0"
|
||||
authors = ["Fluence Labs"]
|
||||
edition = "2018"
|
||||
publish = false
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[[bin]]
|
||||
name = "facade"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
serde = {version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
fluence = { version = "=0.2.18", features = ["logger"]}
|
||||
log = "0.4.8"
|
||||
chrono = "0.4.19"
|
||||
ethereum-types = "0.11.0"
|
||||
hex = "0.4.2"
|
||||
tiny-keccak = {version = "2.0.2", features = ["keccak", "sha3"]}
|
188
web3-examples/web3-examples/facade/src/data_processing.rs
Normal file
188
web3-examples/web3-examples/facade/src/data_processing.rs
Normal file
@ -0,0 +1,188 @@
|
||||
/*
|
||||
* Copyright 2021 Fluence Labs Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
use serde_json;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use fluence::fce;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct DataProcResponse {
|
||||
result: f64,
|
||||
outliers: Vec<f64>,
|
||||
error: String
|
||||
}
|
||||
|
||||
#[fce]
|
||||
pub fn sum_data(data:Vec<String>) -> String {
|
||||
if data.len() < 1 {
|
||||
let result = DataProcResponse {result: -1.0, outliers: [].to_vec(), error: "No data provided".to_string()};
|
||||
return serde_json::to_string(&result).unwrap();
|
||||
}
|
||||
|
||||
let objs: Vec<serde_json::Value> = data
|
||||
.iter()
|
||||
.map(|o| serde_json::from_str(o).unwrap())
|
||||
.collect();
|
||||
|
||||
let mut data:Vec<u128> = objs
|
||||
.iter()
|
||||
.map(|o| {
|
||||
u128::from_str_radix(
|
||||
o["result"].as_str().unwrap().strip_prefix("0x").unwrap(),
|
||||
16,
|
||||
)
|
||||
.unwrap()
|
||||
})
|
||||
.collect();
|
||||
|
||||
let sum:f64 = data.iter().map(|x| *x as f64).sum();
|
||||
let result = DataProcResponse {result: sum, outliers: [].to_vec(), error: "".to_string()};
|
||||
println!("{:?}", result);
|
||||
serde_json::to_string(&result).unwrap()
|
||||
}
|
||||
|
||||
#[fce]
|
||||
pub fn drop_outliers_and_average(data:Vec<String>) -> String {
|
||||
|
||||
if data.len() < 1 {
|
||||
let result = DataProcResponse {result: -1.0, outliers: [].to_vec(), error: "No data provided".to_string()};
|
||||
return serde_json::to_string(&result).unwrap();
|
||||
}
|
||||
|
||||
let objs: Vec<serde_json::Value> = data
|
||||
.iter()
|
||||
.map(|o| serde_json::from_str(o).unwrap())
|
||||
.collect();
|
||||
|
||||
let mut data: Vec<u128> = objs
|
||||
.iter()
|
||||
.map(|o| {
|
||||
u128::from_str_radix(
|
||||
o["result"].as_str().unwrap().strip_prefix("0x").unwrap(),
|
||||
16,
|
||||
)
|
||||
.unwrap()
|
||||
})
|
||||
.collect();
|
||||
data.sort();
|
||||
|
||||
let mut low_high: Vec<f64> = Vec::new();
|
||||
if data.len() > 3 {
|
||||
// low_high.push([data.remove(0) as f64);
|
||||
// low_high.push(data.remove(data.last()) as f64);
|
||||
low_high.extend(&[data.remove(0) as f64, data.remove(data.len() - 1) as f64]);
|
||||
}
|
||||
|
||||
let avg: f64 = (data.iter().sum::<u128>() as f64 / data.len() as f64);
|
||||
|
||||
let result = DataProcResponse {result: avg, outliers: low_high, error: "".to_string()};
|
||||
|
||||
serde_json::to_string(&result).unwrap()
|
||||
|
||||
}
|
||||
|
||||
#[fce]
|
||||
pub fn simple_average(data:Vec<String>) -> String {
|
||||
|
||||
if data.len() < 1 {
|
||||
let result = DataProcResponse {result: -1.0, outliers: [].to_vec(), error: "No data provided".to_string()};
|
||||
return serde_json::to_string(&result).unwrap();
|
||||
}
|
||||
|
||||
let objs: Vec<serde_json::Value> = data
|
||||
.iter()
|
||||
.map(|o| serde_json::from_str(o).unwrap())
|
||||
.collect();
|
||||
|
||||
let mut data: Vec<u128> = objs
|
||||
.iter()
|
||||
.map(|o| {
|
||||
u128::from_str_radix(
|
||||
o["result"].as_str().unwrap().strip_prefix("0x").unwrap(),
|
||||
16,
|
||||
)
|
||||
.unwrap()
|
||||
})
|
||||
.collect();
|
||||
|
||||
let avg: f64 = (data.iter().sum::<u128>() as f64 / data.len() as f64);
|
||||
|
||||
let result = DataProcResponse {result: avg, outliers: [].to_vec(), error: "".to_string()};
|
||||
|
||||
serde_json::to_string(&result).unwrap()
|
||||
|
||||
}
|
||||
|
||||
#[fce]
|
||||
pub fn test_drop_outliers_and_average() {
|
||||
|
||||
let data: Vec<String> = vec![
|
||||
r#"{"result": "0x64"}"#.to_string(),
|
||||
r#"{"result": "0x6E"}"#.to_string(),
|
||||
r#"{"result": "0x5A"}"#.to_string(),
|
||||
r#"{"result": "0xA"}"#.to_string(),
|
||||
r#"{"result": "0x3E8"}"#.to_string(),
|
||||
];
|
||||
println!("{:?}", data);
|
||||
|
||||
let result = drop_outliers_and_average(data);
|
||||
println!("{:?}", result);
|
||||
|
||||
let data: Vec<String> = Vec::new();
|
||||
|
||||
let result = drop_outliers_and_average(data);
|
||||
println!("{:?}", result);
|
||||
|
||||
|
||||
let data: Vec<String> = vec![
|
||||
r#"{"result": "0x64"}"#.to_string(),
|
||||
r#"{"result": "0x6E"}"#.to_string(),
|
||||
];
|
||||
|
||||
let result = drop_outliers_and_average(data);
|
||||
println!("{:?}", result);
|
||||
}
|
||||
|
||||
#[fce]
|
||||
pub fn test_simple_average() {
|
||||
|
||||
let data: Vec<String> = vec![
|
||||
r#"{"result": "0x64"}"#.to_string(),
|
||||
r#"{"result": "0x6E"}"#.to_string(),
|
||||
r#"{"result": "0x5A"}"#.to_string(),
|
||||
r#"{"result": "0xA"}"#.to_string(),
|
||||
r#"{"result": "0x3E8"}"#.to_string(),
|
||||
];
|
||||
println!("{:?}", data);
|
||||
|
||||
let result = simple_average(data);
|
||||
println!("{:?}", result);
|
||||
|
||||
let data: Vec<String> = Vec::new();
|
||||
|
||||
let result = simple_average(data);
|
||||
println!("{:?}", result);
|
||||
|
||||
|
||||
let data: Vec<String> = vec![
|
||||
r#"{"result": "0x64"}"#.to_string(),
|
||||
r#"{"result": "0x6E"}"#.to_string(),
|
||||
];
|
||||
|
||||
let result = simple_average(data);
|
||||
println!("{:?}", result);
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2021 Fluence Labs Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
use serde_json;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use fluence::fce;
|
||||
|
||||
|
||||
#[fce]
|
||||
pub fn test_drop_outliers_and_average() {
|
||||
|
||||
let data: Vec<String> = vec![
|
||||
r#"{"result": "0x64"}"#.to_string(),
|
||||
r#"{"result": "0x6E"}"#.to_string(),
|
||||
r#"{"result": "0x5A"}"#.to_string(),
|
||||
r#"{"result": "0xA"}"#.to_string(),
|
||||
r#"{"result": "0x3E8"}"#.to_string(),
|
||||
];
|
||||
println!("{:?}", data);
|
||||
|
||||
let result = drop_outliers_and_average(data);
|
||||
println!("{:?}", result);
|
||||
|
||||
let data: Vec<String> = Vec::new();
|
||||
|
||||
let result = drop_outliers_and_average(data);
|
||||
println!("{:?}", result);
|
||||
|
||||
|
||||
let data: Vec<String> = vec![
|
||||
r#"{"result": "0x64"}"#.to_string(),
|
||||
r#"{"result": "0x6E"}"#.to_string(),
|
||||
];
|
||||
|
||||
let result = drop_outliers_and_average(data);
|
||||
println!("{:?}", result);
|
||||
}
|
||||
|
||||
#[fce]
|
||||
pub fn test_simple_average() {
|
||||
|
||||
let data: Vec<String> = vec![
|
||||
r#"{"result": "0x64"}"#.to_string(),
|
||||
r#"{"result": "0x6E"}"#.to_string(),
|
||||
r#"{"result": "0x5A"}"#.to_string(),
|
||||
r#"{"result": "0xA"}"#.to_string(),
|
||||
r#"{"result": "0x3E8"}"#.to_string(),
|
||||
];
|
||||
println!("{:?}", data);
|
||||
|
||||
let result = simple_average(data);
|
||||
println!("{:?}", result);
|
||||
|
||||
let data: Vec<String> = Vec::new();
|
||||
|
||||
let result = simple_average(data);
|
||||
println!("{:?}", result);
|
||||
|
||||
|
||||
let data: Vec<String> = vec![
|
||||
r#"{"result": "0x64"}"#.to_string(),
|
||||
r#"{"result": "0x6E"}"#.to_string(),
|
||||
];
|
||||
|
||||
let result = simple_average(data);
|
||||
println!("{:?}", result);
|
||||
|
||||
|
||||
}
|
167
web3-examples/web3-examples/facade/src/eth_calls.rs
Normal file
167
web3-examples/web3-examples/facade/src/eth_calls.rs
Normal file
@ -0,0 +1,167 @@
|
||||
/*
|
||||
* Copyright 2021 Fluence Labs Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use crate::curl_request;
|
||||
use crate::eth_utils::{check_response_string, get_nonce, BLOCK_NUMBER_TAGS};
|
||||
use crate::fce_results::JsonRpcResult;
|
||||
use crate::jsonrpc_helpers::{Request, batch};
|
||||
use chrono::Utc;
|
||||
use fluence::fce;
|
||||
use serde::{Deserialize, Serialize, Deserializer};
|
||||
use serde_json;
|
||||
use serde_json::Value;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
#[fce]
|
||||
pub fn eth_get_balance(url: String, account: String, block_number: String) -> JsonRpcResult {
|
||||
let method = String::from("eth_getBalance");
|
||||
let id = get_nonce();
|
||||
|
||||
let block_identifier: String;
|
||||
let number_test = block_number.parse::<u64>();
|
||||
if number_test.is_ok() {
|
||||
block_identifier = format!("0x{:x}", number_test.unwrap());
|
||||
} else if BLOCK_NUMBER_TAGS.contains(&block_number.as_str()) {
|
||||
block_identifier = String::from(block_number);
|
||||
} else {
|
||||
block_identifier = String::from("latest");
|
||||
}
|
||||
|
||||
let params: Vec<String> = vec![account, block_identifier];
|
||||
let curl_args: String = Request::new(method, params, id).as_sys_string(&url);
|
||||
let response: String = unsafe { curl_request(curl_args) };
|
||||
check_response_string(response, &id)
|
||||
}
|
||||
|
||||
#[fce]
|
||||
pub fn eth_get_block_height(url: String) -> JsonRpcResult {
|
||||
let method = "eth_blockNumber".to_string();
|
||||
let params: Vec<String> = Vec::new();
|
||||
let id = get_nonce();
|
||||
|
||||
let curl_args: String = Request::new(method, params, id).as_sys_string(&url);
|
||||
let response: String = unsafe { curl_request(curl_args) };
|
||||
check_response_string(response, &id)
|
||||
}
|
||||
|
||||
#[fce]
|
||||
pub fn eth_get_tx_by_hash(url: String, tx_hash: String) -> String {
|
||||
let method: String = String::from("eth_getTransactionByHash");
|
||||
let params: Vec<String> = vec![tx_hash];
|
||||
let id = get_nonce();
|
||||
|
||||
let curl_args: String = Request::new(method, params, id).as_sys_string(&url);
|
||||
let response: String = unsafe { curl_request(curl_args) };
|
||||
response
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
pub struct TxSerde {
|
||||
// blockHash: DATA, 32 Bytes - hash of the block where this transaction was in. null when its pending.
|
||||
pub blockHash: Option<String>,
|
||||
// blockNumber: QUANTITY - block number where this transaction was in. null when its pending.
|
||||
pub blockNumber: Option<String>,
|
||||
// from: DATA, 20 Bytes - address of the sender.
|
||||
pub from: Option<String>,
|
||||
// gas: QUANTITY - gas provided by the sender.
|
||||
pub gas: Option<String>,
|
||||
// gasPrice: QUANTITY - gas price provided by the sender in Wei.
|
||||
pub gasPrice: Option<String>,
|
||||
// hash: DATA, 32 Bytes - hash of the transaction.
|
||||
pub hash: Option<String>,
|
||||
// input: DATA - the data send along with the transaction.
|
||||
pub input: Option<String>,
|
||||
// nonce: QUANTITY - the number of transactions made by the sender prior to this one.
|
||||
pub nonce: Option<String>,
|
||||
// to: DATA, 20 Bytes - address of the receiver. null when its a contract creation transaction.
|
||||
pub to: Option<String>,
|
||||
// transactionIndex: QUANTITY - integer of the transactions index position in the block. null when its pending.
|
||||
pub transactionIndex: Option<String>,
|
||||
// value: QUANTITY - value transferred in Wei.
|
||||
pub value: Option<String>,
|
||||
}
|
||||
|
||||
fn null_to_default<'de, D, T>(d: D) -> Result<T, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
T: Default + Deserialize<'de>,
|
||||
{
|
||||
let opt = Option::deserialize(d)?;
|
||||
let val = opt.unwrap_or_else(T::default);
|
||||
Ok(val)
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
struct GetTxResponse {
|
||||
#[serde(deserialize_with = "null_to_default")]
|
||||
result: Option<TxSerde>
|
||||
}
|
||||
|
||||
#[fce]
|
||||
pub struct Tx {
|
||||
pub blockHash: String,
|
||||
pub blockNumber: String,
|
||||
pub from: String,
|
||||
pub gas: String,
|
||||
pub gasPrice: String,
|
||||
pub hash: String,
|
||||
pub input: String,
|
||||
pub nonce: String,
|
||||
pub to: String,
|
||||
pub transactionIndex: String,
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
impl From<TxSerde> for Tx {
|
||||
fn from(ser: TxSerde) -> Self {
|
||||
Self {
|
||||
blockHash: ser.blockHash.unwrap_or_default(),
|
||||
blockNumber: ser.blockNumber.unwrap_or_default(),
|
||||
from: ser.from.unwrap_or_default(),
|
||||
gas: ser.gas.unwrap_or_default(),
|
||||
gasPrice: ser.gasPrice.unwrap_or_default(),
|
||||
hash: ser.hash.unwrap_or_default(),
|
||||
input: ser.input.unwrap_or_default(),
|
||||
nonce: ser.nonce.unwrap_or_default(),
|
||||
to: ser.to.unwrap_or_default(),
|
||||
transactionIndex: ser.transactionIndex.unwrap_or_default(),
|
||||
value: ser.value.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[fce]
|
||||
pub fn eth_get_txs_by_hashes(url: String, tx_hashes: Vec<String>) -> Vec<Tx> {
|
||||
let method: String = String::from("eth_getTransactionByHash");
|
||||
let params: Vec<_> = tx_hashes.into_iter().map(|h| vec![h]).collect();
|
||||
let requests = batch(url, method, params, get_nonce());
|
||||
match requests {
|
||||
Ok(requests) => {
|
||||
requests.into_iter().flat_map(|req| {
|
||||
let response: String = unsafe { curl_request(req) };
|
||||
let responses: Vec<GetTxResponse> = serde_json::from_str(response.as_str()).unwrap_or_else(|err| {
|
||||
log::error!("failed to deserialize batch response: {}", err);
|
||||
panic!("failed to deserialize batch response: {}", err);
|
||||
});
|
||||
responses.into_iter().flat_map(|r| Some(r.result?.into()))
|
||||
}).collect()
|
||||
},
|
||||
Err(err) => {
|
||||
log::error!("failed to create batch request: {}", err);
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
}
|
75
web3-examples/web3-examples/facade/src/eth_calls_tests.rs
Normal file
75
web3-examples/web3-examples/facade/src/eth_calls_tests.rs
Normal file
@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2021 Fluence Labs Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use crate::eth_calls::{eth_get_balance, eth_get_tx_by_hash};
|
||||
use crate::eth_filters::{get_filter_changes, new_pending_tx_filter, uninstall_filter};
|
||||
use crate::eth_utils::wei_to_eth;
|
||||
use crate::fce_results::TestResult;
|
||||
use fluence::fce;
|
||||
|
||||
#[fce]
|
||||
fn test_eth_get_balance_good(url: String) -> TestResult {
|
||||
let burn_address = String::from("0x0000000000000000000000000000000000000000");
|
||||
let block_height = String::from("latest");
|
||||
// burn account balances, min, per 1/27/21:
|
||||
// https://etherscan.io/address/0x0000000000000000000000000000000000000000; 8412.0
|
||||
// https://kovan.etherscan.io/address/0x0000000000000000000000000000000000000000; 213.0
|
||||
// https://rinkeby.etherscan.io/address/0x0000000000000000000000000000000000000000; 1566.0
|
||||
// https://goerli.etherscan.io/address/0x0000000000000000000000000000000000000000; 1195.0
|
||||
|
||||
let result = eth_get_balance(url, burn_address, block_height);
|
||||
let hex_balance: String = result.result;
|
||||
let wei_balance: u128 = u128::from_str_radix(&hex_balance[2..], 16).unwrap();
|
||||
let eth_balance: f64 = wei_to_eth(&wei_balance);
|
||||
if eth_balance > 213.0 {
|
||||
return TestResult::from(Result::from(Ok(String::from(""))));
|
||||
}
|
||||
let err_msg = format!("expected: gt {}, actual {:.2}", 213.0, eth_balance);
|
||||
TestResult::from(Result::from(Err(err_msg)))
|
||||
}
|
||||
|
||||
#[fce]
|
||||
fn test_eth_get_balance_bad(url: String) -> TestResult {
|
||||
let burn_address = String::from("0x0000000000000000000000000000000000000000");
|
||||
let block_height = String::from("latest");
|
||||
// burn account balances, min, per 1/27/21:
|
||||
// https://etherscan.io/address/0x0000000000000000000000000000000000000000; 8412.0
|
||||
// https://kovan.etherscan.io/address/0x0000000000000000000000000000000000000000; 213.0
|
||||
// https://rinkeby.etherscan.io/address/0x0000000000000000000000000000000000000000; 1566.0
|
||||
// https://goerli.etherscan.io/address/0x0000000000000000000000000000000000000000; 1195.0
|
||||
|
||||
let result = eth_get_balance(url, burn_address, block_height);
|
||||
let hex_balance: String = result.result;
|
||||
let wei_balance: u128 = u128::from_str_radix(&hex_balance[2..], 16).unwrap();
|
||||
let eth_balance: f64 = wei_to_eth(&wei_balance);
|
||||
if eth_balance > 1_000_000.0 {
|
||||
return TestResult::from(Result::from(Ok(String::from(""))));
|
||||
}
|
||||
let err_msg = format!("expected: gt {}, actual {:.2}", 1_000_000, eth_balance);
|
||||
TestResult::from(Result::from(Err(err_msg)))
|
||||
}
|
||||
|
||||
#[fce]
|
||||
fn test_eth_get_tx_by_hash(url: String, tx_hash: String) {
|
||||
let res: String = eth_get_tx_by_hash(url, tx_hash.clone());
|
||||
let obj: serde_json::Value = serde_json::from_str(&res).unwrap();
|
||||
println!(
|
||||
"expected: {} == {} actual : {}",
|
||||
tx_hash,
|
||||
obj["result"]["hash"],
|
||||
tx_hash == obj["result"]["hash"]
|
||||
);
|
||||
}
|
75
web3-examples/web3-examples/facade/src/eth_filter_test.rs
Normal file
75
web3-examples/web3-examples/facade/src/eth_filter_test.rs
Normal file
@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2021 Fluence Labs Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use crate::eth_calls::{eth_get_balance, eth_get_tx_by_hash};
|
||||
use crate::eth_filters::{get_filter_changes, new_pending_tx_filter, uninstall_filter};
|
||||
use crate::eth_utils::wei_to_eth;
|
||||
use crate::fce_results::TestResult;
|
||||
use fluence::fce;
|
||||
use serde_json;
|
||||
use serde_json::Value;
|
||||
|
||||
#[fce]
|
||||
fn test_filters(url: String) -> TestResult {
|
||||
let pending_filter_id = new_pending_tx_filter(url.clone());
|
||||
let result = get_filter_changes(url.clone(), pending_filter_id.clone());
|
||||
let result = uninstall_filter(url.clone(), pending_filter_id);
|
||||
|
||||
if result {
|
||||
return TestResult::from(Result::from(Ok(String::from(String::from("")))));
|
||||
}
|
||||
let err_msg = format!("expected filter uninstall to be true but ot false");
|
||||
TestResult::from(Result::from(Err(String::from(err_msg))))
|
||||
}
|
||||
|
||||
#[fce]
|
||||
pub fn test_pending_with_null_filter(url: String) -> String {
|
||||
let mut matches: Vec<(String, String, String, String)> = Vec::new();
|
||||
let pending_filter_id = new_pending_tx_filter(url.clone());
|
||||
let result: String = get_filter_changes(url.clone(), pending_filter_id.clone());
|
||||
let results: serde_json::Value = serde_json::from_str(&result).unwrap();
|
||||
let results: Vec<String> = serde_json::from_value(results["result"].clone()).unwrap();
|
||||
for tx_hash in results.iter() {
|
||||
let tx: String = eth_get_tx_by_hash(url.clone(), tx_hash.clone());
|
||||
let tx: serde_json::Value = serde_json::from_str(&tx).unwrap();
|
||||
if tx["result"] != serde_json::Value::Null {
|
||||
println!("tx: {:?}", tx);
|
||||
let from_acct = serde_json::from_value(tx["result"]["from"].clone());
|
||||
let from_acct: String = if from_acct.is_ok() {
|
||||
from_acct.unwrap()
|
||||
} else {
|
||||
String::from("")
|
||||
};
|
||||
|
||||
let to_acct = serde_json::from_value(tx["result"]["to"].clone());
|
||||
let to_acct: String = if to_acct.is_ok() {
|
||||
to_acct.unwrap()
|
||||
} else {
|
||||
String::from("")
|
||||
};
|
||||
|
||||
let value = serde_json::from_value(tx["result"]["value"].clone());
|
||||
let value: String = if value.is_ok() {
|
||||
value.unwrap()
|
||||
} else {
|
||||
String::from("")
|
||||
};
|
||||
matches.push((tx_hash.clone(), from_acct, to_acct, value));
|
||||
}
|
||||
}
|
||||
uninstall_filter(url.clone(), pending_filter_id);
|
||||
serde_json::to_string(&matches).unwrap()
|
||||
}
|
134
web3-examples/web3-examples/facade/src/eth_filters.rs
Normal file
134
web3-examples/web3-examples/facade/src/eth_filters.rs
Normal file
@ -0,0 +1,134 @@
|
||||
/*
|
||||
* Copyright 2021 Fluence Labs Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
use crate::curl_request;
|
||||
use crate::eth_utils::{check_response_string, get_nonce};
|
||||
use crate::eth_calls::{eth_get_tx_by_hash, Tx, eth_get_txs_by_hashes};
|
||||
use crate::fce_results::JsonRpcResult;
|
||||
use crate::jsonrpc_helpers::Request;
|
||||
use crate::jsonrpc_helpers::JSON_RPC;
|
||||
use fluence::fce;
|
||||
use serde_json::Value;
|
||||
|
||||
/// see:
|
||||
/// https://eth.wiki/json-rpc/API#eth_uninstallfilter
|
||||
/// https://docs.alchemyapi.io/alchemy/documentation/alchemy-api-reference/json-rpc#eth_uninstallfilter
|
||||
/// https://infura.io/docs/ethereum/json-rpc/eth-uninstallFilter
|
||||
#[fce]
|
||||
pub fn uninstall_filter(url: String, filter_id: String) -> bool {
|
||||
let method = String::from("eth_uninstallFilter");
|
||||
let params: Vec<String> = vec![filter_id];
|
||||
let id = get_nonce();
|
||||
|
||||
// let request = Request::new(method, params, id);
|
||||
let curl_args = Request::new(method, params, id).as_sys_string(&url);
|
||||
|
||||
let response: String = unsafe { curl_request(curl_args) };
|
||||
|
||||
/*
|
||||
if response.len() == 0 || response.contains("error") {
|
||||
return false;
|
||||
}
|
||||
*/
|
||||
|
||||
let result_obj: Value = serde_json::from_str(&response).unwrap();
|
||||
let result: bool = serde_json::from_value(result_obj["result"].clone()).unwrap();
|
||||
result
|
||||
}
|
||||
|
||||
// see
|
||||
// https://eth.wiki/json-rpc/API#eth_newpendingtransactionfilter
|
||||
// https://infura.io/docs/ethereum/wss/eth_newPendingTransactionFilter
|
||||
// https://docs.alchemyapi.io/alchemy/documentation/alchemy-api-reference/json-rpc#eth_getfilterchanges
|
||||
#[fce]
|
||||
pub fn new_pending_tx_filter(url: String) -> String {
|
||||
let method: String;
|
||||
let mut params: Vec<String> = Vec::new();
|
||||
|
||||
// Note: Service provider implementations may provide json-rpc wrappers we need to handle
|
||||
if url.contains("infura") {
|
||||
// please note that this is a wss call for infura which mostlikely will not work
|
||||
method = String::from("eth_subscribe");
|
||||
params.push(String::from("newPendingTransactions"));
|
||||
} else if url.contains("alchemyapi") {
|
||||
method = String::from("eth_newPendingTransactionFilter");
|
||||
} else {
|
||||
method = String::from("eth_newPendingTransactionFilter");
|
||||
}
|
||||
|
||||
let id = get_nonce();
|
||||
|
||||
let curl_args = Request::new(method, params, id).as_sys_string(&url);
|
||||
let response: String = unsafe { curl_request(curl_args) };
|
||||
|
||||
let result_obj: Value = serde_json::from_str(&response).unwrap();
|
||||
let result: String = serde_json::from_value(result_obj["result"].clone()).unwrap();
|
||||
result
|
||||
}
|
||||
|
||||
// https://eth.wiki/json-rpc/API#eth_getfilterchanges
|
||||
// https://infura.io/docs/ethereum/json-rpc/eth-getFilterChanges
|
||||
// https://docs.alchemyapi.io/alchemy/documentation/alchemy-api-reference/json-rpc#eth_getfilterchanges
|
||||
#[fce]
|
||||
pub fn get_filter_changes(url: String, filter_id: String) -> String {
|
||||
let method = String::from("eth_getFilterChanges");
|
||||
let params: Vec<String> = vec![filter_id];
|
||||
let id = get_nonce();
|
||||
|
||||
let curl_args = Request::new(method, params, id).as_sys_string(&url);
|
||||
|
||||
let response: String = unsafe { curl_request(curl_args) };
|
||||
response
|
||||
}
|
||||
|
||||
|
||||
#[fce]
|
||||
pub fn get_filter_changes_list(url: String, filter_id: String) -> Vec<String> {
|
||||
let method = String::from("eth_getFilterChanges");
|
||||
let params: Vec<String> = vec![filter_id];
|
||||
let id = get_nonce();
|
||||
|
||||
let curl_args = Request::new(method, params, id).as_sys_string(&url);
|
||||
|
||||
let response = unsafe { curl_request(curl_args) };
|
||||
log::info!("response: {}", response);
|
||||
let mut response: Value = serde_json::from_str(&response).unwrap_or_else(|_| {
|
||||
log::error!("failed to parse ETH RPC response as json");
|
||||
panic!("failed to parse ETH RPC response as json");
|
||||
});
|
||||
let result = response.get_mut("result").unwrap_or_else(|| {
|
||||
log::error!("no 'result' field found in ETH RPC response");
|
||||
panic!("no 'result' field found in ETH RPC response");
|
||||
});
|
||||
if let Value::Array(results) = result.take() {
|
||||
let tx_hashes: Vec<_> = results.into_iter().flat_map(|r| {
|
||||
let hash = r.as_str()?;
|
||||
Some(hash.to_string())
|
||||
}).collect();
|
||||
log::info!("got {} tx hashes", tx_hashes.len());
|
||||
tx_hashes
|
||||
} else {
|
||||
log::error!("expected result to be an array. it wasn't.");
|
||||
panic!("expected result to be an array. it wasn't.")
|
||||
}
|
||||
}
|
||||
|
||||
#[fce]
|
||||
pub fn get_filter_changes_without_null(url: String, filter_id: String, limit: String) -> Vec<Tx> {
|
||||
let tx_hashes = get_filter_changes_list(url.clone(), filter_id.clone());
|
||||
let limit: usize = limit.parse().unwrap_or(5000);
|
||||
let tx_hashes = tx_hashes.into_iter().take(limit as usize).collect();
|
||||
eth_get_txs_by_hashes(url, tx_hashes)
|
||||
}
|
43
web3-examples/web3-examples/facade/src/eth_hashers.rs
Normal file
43
web3-examples/web3-examples/facade/src/eth_hashers.rs
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2021 Fluence Labs Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
use fluence::fce;
|
||||
use tiny_keccak::{Hasher, Keccak};
|
||||
|
||||
#[fce]
|
||||
pub fn eth_hash_method_id(input: Vec<u8>) -> Vec<u8> {
|
||||
let mut output = [0u8; 32];
|
||||
let mut keccak = Keccak::v256();
|
||||
|
||||
keccak.update(&input);
|
||||
keccak.finalize(&mut output);
|
||||
output.to_vec()
|
||||
}
|
||||
|
||||
#[fce]
|
||||
pub fn test_eth_hash_method_id() -> String {
|
||||
use hex::encode;
|
||||
|
||||
// see https://docs.soliditylang.org/en/latest/abi-spec.html#examples
|
||||
let input = b"baz(uint32,bool)".to_vec();
|
||||
let expected = String::from("cdcd77c0");
|
||||
let res = eth_hash_method_id(input);
|
||||
let res = format!("{}", hex::encode(&res[..4]));
|
||||
|
||||
if res == expected {
|
||||
return "test passed".to_string();
|
||||
}
|
||||
"test failed".to_string()
|
||||
}
|
124
web3-examples/web3-examples/facade/src/eth_type_test.rs
Normal file
124
web3-examples/web3-examples/facade/src/eth_type_test.rs
Normal file
@ -0,0 +1,124 @@
|
||||
use fluence::fce;
|
||||
|
||||
#[fce]
|
||||
pub fn eth_type_test() {
|
||||
use ethereum_types::{H160, H256, U256, U512};
|
||||
use serde_json as ser;
|
||||
|
||||
macro_rules! test {
|
||||
($name: ident, $test_name: ident) => {
|
||||
fn $test_name() {
|
||||
let tests = vec![
|
||||
($name::from(0), "0x0"),
|
||||
($name::from(1), "0x1"),
|
||||
($name::from(2), "0x2"),
|
||||
($name::from(10), "0xa"),
|
||||
($name::from(15), "0xf"),
|
||||
($name::from(15), "0xf"),
|
||||
($name::from(16), "0x10"),
|
||||
($name::from(1_000), "0x3e8"),
|
||||
($name::from(100_000), "0x186a0"),
|
||||
($name::from(u64::max_value()), "0xffffffffffffffff"),
|
||||
(
|
||||
$name::from(u64::max_value()) + $name::from(1u64),
|
||||
"0x10000000000000000",
|
||||
),
|
||||
];
|
||||
|
||||
for (number, expected) in tests {
|
||||
assert_eq!(
|
||||
format!("{:?}", expected),
|
||||
ser::to_string_pretty(&number).unwrap()
|
||||
);
|
||||
assert_eq!(number, ser::from_str(&format!("{:?}", expected)).unwrap());
|
||||
}
|
||||
|
||||
// Invalid examples
|
||||
assert!(ser::from_str::<$name>("\"0x\"").unwrap_err().is_data());
|
||||
assert!(ser::from_str::<$name>("\"0xg\"").unwrap_err().is_data());
|
||||
assert!(ser::from_str::<$name>("\"\"").unwrap_err().is_data());
|
||||
assert!(ser::from_str::<$name>("\"10\"").unwrap_err().is_data());
|
||||
assert!(ser::from_str::<$name>("\"0\"").unwrap_err().is_data());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
test!(U256, test_u256);
|
||||
test!(U512, test_u512);
|
||||
|
||||
fn test_h160() {
|
||||
let tests = vec![
|
||||
(
|
||||
H160::from_low_u64_be(0),
|
||||
"0x0000000000000000000000000000000000000000",
|
||||
),
|
||||
(
|
||||
H160::from_low_u64_be(2),
|
||||
"0x0000000000000000000000000000000000000002",
|
||||
),
|
||||
(
|
||||
H160::from_low_u64_be(15),
|
||||
"0x000000000000000000000000000000000000000f",
|
||||
),
|
||||
(
|
||||
H160::from_low_u64_be(16),
|
||||
"0x0000000000000000000000000000000000000010",
|
||||
),
|
||||
(
|
||||
H160::from_low_u64_be(1_000),
|
||||
"0x00000000000000000000000000000000000003e8",
|
||||
),
|
||||
(
|
||||
H160::from_low_u64_be(100_000),
|
||||
"0x00000000000000000000000000000000000186a0",
|
||||
),
|
||||
(
|
||||
H160::from_low_u64_be(u64::max_value()),
|
||||
"0x000000000000000000000000ffffffffffffffff",
|
||||
),
|
||||
];
|
||||
|
||||
for (number, expected) in tests {
|
||||
assert_eq!(
|
||||
format!("{:?}", expected),
|
||||
ser::to_string_pretty(&number).unwrap()
|
||||
);
|
||||
assert_eq!(number, ser::from_str(&format!("{:?}", expected)).unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
fn test_invalid() {
|
||||
assert!(ser::from_str::<H256>(
|
||||
"\"0x000000000000000000000000000000000000000000000000000000000000000\""
|
||||
)
|
||||
.unwrap_err()
|
||||
.is_data());
|
||||
assert!(ser::from_str::<H256>(
|
||||
"\"0x000000000000000000000000000000000000000000000000000000000000000g\""
|
||||
)
|
||||
.unwrap_err()
|
||||
.is_data());
|
||||
assert!(ser::from_str::<H256>(
|
||||
"\"0x00000000000000000000000000000000000000000000000000000000000000000\""
|
||||
)
|
||||
.unwrap_err()
|
||||
.is_data());
|
||||
assert!(ser::from_str::<H256>("\"\"").unwrap_err().is_data());
|
||||
assert!(ser::from_str::<H256>("\"0\"").unwrap_err().is_data());
|
||||
assert!(ser::from_str::<H256>("\"10\"").unwrap_err().is_data());
|
||||
}
|
||||
|
||||
fn test_invalid_char() {
|
||||
const INVALID_STR: &str =
|
||||
"\"0x000000000000000000000000000000000000000000000000000000000000000g\"";
|
||||
const EXPECTED_MSG: &str = "invalid hex character: g, at 65 at line 1 column 68";
|
||||
assert_eq!(
|
||||
ser::from_str::<H256>(INVALID_STR).unwrap_err().to_string(),
|
||||
EXPECTED_MSG
|
||||
);
|
||||
}
|
||||
|
||||
test_h160();
|
||||
test_invalid();
|
||||
test_invalid_char();
|
||||
}
|
26
web3-examples/web3-examples/facade/src/eth_utils.rs
Normal file
26
web3-examples/web3-examples/facade/src/eth_utils.rs
Normal file
@ -0,0 +1,26 @@
|
||||
use crate::fce_results::JsonRpcResult;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
pub const BLOCK_NUMBER_TAGS: [&'static str; 3] = ["latest", "earliest", "pending"];
|
||||
pub static NONCE_COUNTER: AtomicUsize = AtomicUsize::new(1);
|
||||
|
||||
pub fn get_nonce() -> u64 {
|
||||
NONCE_COUNTER.fetch_add(1, Ordering::SeqCst) as u64
|
||||
}
|
||||
|
||||
pub fn check_response_string(response: String, id: &u64) -> JsonRpcResult {
|
||||
if response.len() == 0 {
|
||||
let err_msg = "{\"jsonrpc\":\"$V\",\"id\":$ID,\"error\":{\"code\":-32700,\"message\":Curl connection failed}}";
|
||||
let err_msg = err_msg.replace("$ID", &id.to_string());
|
||||
return JsonRpcResult::from(Result::from(Err(err_msg)));
|
||||
}
|
||||
|
||||
match response.contains("error") {
|
||||
true => JsonRpcResult::from(Result::from(Err(response))),
|
||||
false => JsonRpcResult::from(Result::from(Ok(response))),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn wei_to_eth(amount: &u128) -> f64 {
|
||||
*amount as f64 / (1_000_000_000.0 * 1_000_000_000.0)
|
||||
}
|
82
web3-examples/web3-examples/facade/src/fce_results.rs
Normal file
82
web3-examples/web3-examples/facade/src/fce_results.rs
Normal file
@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright 2021 Fluence Labs Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use crate::jsonrpc_helpers::JSON_RPC;
|
||||
use crate::Result;
|
||||
use fluence::fce;
|
||||
use serde_json::Value;
|
||||
|
||||
#[fce]
|
||||
#[derive(Debug)]
|
||||
pub struct JsonRpcResult {
|
||||
pub jsonrpc: String,
|
||||
pub result: String,
|
||||
pub error: String,
|
||||
pub id: u64,
|
||||
}
|
||||
|
||||
impl From<Result<String>> for JsonRpcResult {
|
||||
fn from(result: Result<String>) -> Self {
|
||||
let jsonrpc = JSON_RPC.into();
|
||||
match result {
|
||||
Ok(res) => {
|
||||
let result_obj: Value = serde_json::from_str(&res).unwrap();
|
||||
let id: u64 = serde_json::from_value(result_obj["id"].clone()).unwrap();
|
||||
let result: String = serde_json::from_value(result_obj["result"].clone()).unwrap();
|
||||
|
||||
Self {
|
||||
jsonrpc,
|
||||
id,
|
||||
result,
|
||||
error: "".to_string(),
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
let result_obj: Value = serde_json::from_str(&err).unwrap();
|
||||
let id: u64 = serde_json::from_value(result_obj["id"].clone()).unwrap();
|
||||
|
||||
Self {
|
||||
jsonrpc,
|
||||
id,
|
||||
result: "".to_string(),
|
||||
error: err,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[fce]
|
||||
#[derive(Debug)]
|
||||
pub struct TestResult {
|
||||
pub test_passed: bool,
|
||||
pub error: String,
|
||||
}
|
||||
|
||||
impl From<Result<String>> for TestResult {
|
||||
fn from(result: Result<String>) -> Self {
|
||||
match result {
|
||||
Ok(res) => Self {
|
||||
test_passed: true,
|
||||
error: res,
|
||||
},
|
||||
Err(err) => Self {
|
||||
test_passed: false,
|
||||
error: err,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
84
web3-examples/web3-examples/facade/src/jsonrpc_helpers.rs
Normal file
84
web3-examples/web3-examples/facade/src/jsonrpc_helpers.rs
Normal file
@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright 2021 Fluence Labs Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
pub const JSON_RPC: &'static str = "2.0";
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Request {
|
||||
pub jsonrpc: String,
|
||||
pub method: String,
|
||||
pub params: Vec<String>,
|
||||
pub id: u64,
|
||||
}
|
||||
|
||||
impl Request {
|
||||
pub fn new(method: String, params: Vec<String>, id: u64) -> Self {
|
||||
Request {
|
||||
jsonrpc: String::from(JSON_RPC),
|
||||
method,
|
||||
params,
|
||||
id,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_sys_string(&self, url: &String) -> String {
|
||||
let result = format!("-s -X POST --data '{{\"jsonrpc\":\"{}\", \"method\": \"{}\", \"params\":{:?}, \"id\":{}}}' {}", self.jsonrpc, self.method, self.params, self.id, url);
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
[
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "eth_getTransactionByHash",
|
||||
"params": [
|
||||
"0xdb3fbed87cc7834981610df7e5827c09b9d2496bb5a6ae604bf7c603a6f79d80"
|
||||
],
|
||||
"id": 766
|
||||
},
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "eth_getTransactionByHash",
|
||||
"params": [
|
||||
"0x8bc16f653235d2130e5fd05659c290dcba2095681e9e3d04805c92f38914b6ce"
|
||||
],
|
||||
"id": 767
|
||||
}
|
||||
]
|
||||
*/
|
||||
pub fn batch(url: String, method: String, params: Vec<Vec<String>>, id_start: u64) -> serde_json::Result<Vec<String>> {
|
||||
use serde_json::json;
|
||||
|
||||
params.chunks(50).map(|params| {
|
||||
let mut id = id_start;
|
||||
let requests: Vec<_> = params.into_iter().map(|params| {
|
||||
let value = json!({
|
||||
"jsonrpc": "2.0",
|
||||
"method": method,
|
||||
"params": params,
|
||||
"id": id
|
||||
});
|
||||
id += 1;
|
||||
value
|
||||
}).collect();
|
||||
|
||||
let requests = serde_json::to_string(&requests)?;
|
||||
let curl_args = format!(r#"-s -X POST --data '{}' {}"#, requests, url);
|
||||
|
||||
Ok(curl_args)
|
||||
}).collect()
|
||||
}
|
42
web3-examples/web3-examples/facade/src/main.rs
Normal file
42
web3-examples/web3-examples/facade/src/main.rs
Normal file
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2021 Fluence Labs Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#![allow(non_snake_case, unused_variables, unused_imports, unused_parens, unused_mut)]
|
||||
|
||||
use fluence::fce;
|
||||
use fluence::WasmLoggerBuilder;
|
||||
|
||||
mod data_processing;
|
||||
mod eth_calls;
|
||||
mod eth_calls_tests;
|
||||
mod eth_filter_test;
|
||||
mod eth_filters;
|
||||
mod eth_hashers;
|
||||
mod eth_utils;
|
||||
mod fce_results;
|
||||
mod jsonrpc_helpers;
|
||||
|
||||
pub(crate) type Result<T> = std::result::Result<T, T>;
|
||||
|
||||
pub fn main() {
|
||||
WasmLoggerBuilder::new().build().ok();
|
||||
}
|
||||
|
||||
#[fce]
|
||||
#[link(wasm_import_module = "curl_adapter")]
|
||||
extern "C" {
|
||||
pub fn curl_request(url: String) -> String;
|
||||
}
|
Loading…
Reference in New Issue
Block a user