mirror of
https://github.com/fluencelabs/examples
synced 2024-12-04 19:20:17 +00:00
add weighted price oracle
This commit is contained in:
parent
4766077e03
commit
455b7c6736
155
aqua-examples/weighted-price-oracle/README.md
Normal file
155
aqua-examples/weighted-price-oracle/README.md
Normal file
@ -0,0 +1,155 @@
|
||||
# Weighted Price Oracle With Fluence And Aqua
|
||||
|
||||
|
||||
## About Fluence
|
||||
|
||||
Fluence provides an open Web3 protocol, framework and associated tooling to develop and host applications, interfaces and backends on permissionless peer-to-peer networks. An integral part of the Fluence solution is the Aquamarine stack comprised of Aqua and Marine. Aqua is a new programming language and paradigm purpose-built to program distributed networks and compose applications from distributed services. Marine is a general purpose Wasm runtime and toolkit, allows developers to build distributed services that can be composed into distributed applications by Aqua.
|
||||
|
||||
Fluence Developer Resources:
|
||||
|
||||
* [Developer Documentation](https://doc.fluence.dev/docs/)
|
||||
|
||||
Aqua Developer Resources:
|
||||
|
||||
* [Aqua Book](https://app.gitbook.com/@fluence/s/aqua-book/)
|
||||
* [Aqua Playground](https://github.com/fluencelabs/aqua-playground)
|
||||
* [Aqua repo](https://github.com/fluencelabs/aqua)
|
||||
|
||||
Marine Developer Resources:
|
||||
|
||||
* [Marine repo](https://github.com/fluencelabs/marine)
|
||||
* [Marine SDK](https://github.com/fluencelabs/marine-rs-sdk)
|
||||
|
||||
|
||||
## Overview
|
||||
|
||||
Price (feed) oracles are probably the most used and in-demand oracle type and tend to have a significant impact on the success and profitability of DeFi and related on- and off-chain operations. In this example, we demonstrate how to create a decentralized, off-chain price (feed) oracle on the Fluence peer-to-peer network from a set of distributed, re-usable Marine services composed by Aqua and use TrustGraph for peer prioritization.
|
||||
|
||||
Figure 1: Stylized Price Oracle Network And Service Process
|
||||
// TODO: insert diagram
|
||||
|
||||
As outlined in Figure 1, we use one or more services distributed across the Fluence peer-to-peer network to obtain price quotes from sources. For example, we could have one service capable of querying one or more sources such as DEX contracts deployed on multiple network peers allowing us to poll price sources in parallel. We then join these results and submit them to a processing service also deployed on the peer-to-peer network to establish, for example, an oracle point-estimate. Once we've obtained our oracle, we return it to the peer-client, e.g., a browser.
|
||||
|
||||
## Quick Start
|
||||
|
||||
Look at [client-peer](./client-peer) directory for a peer-client based on the Fluence JS-SDK. To run the headless client:
|
||||
|
||||
```bash
|
||||
% cd client-peer
|
||||
% npm instal
|
||||
% npm start run
|
||||
```
|
||||
|
||||
which gives us:
|
||||
|
||||
```text
|
||||
# <snip>
|
||||
hello crypto investors
|
||||
created a fluence client 12D3KooWEKui98wfChxaPou6qcNcH5zY3LzfXRwJbTViKUdY7aWi with relay 12D3KooWFEwNWcHqi9rtsmDhsYcDbRUCDXH84RC4FW6UfsFWaoHi
|
||||
// TODO: add smth about trusts chain and weights
|
||||
get_weighted_price result: { error_msg: '', result: 3989.19, success: true }
|
||||
```
|
||||
|
||||
where the Aqua script can be found in the `aqua-scripts` directory and the compiled Aqua code is found in the `get_crypto_prices.ts` file. For more on the Aqua script, see below.
|
||||
|
||||
## Service Development And Deployment
|
||||
|
||||
**Prerequisites:** If you want to follow along, compile Wasm modules, create services and deploy service, you need Rust, Node and a few Fluence tools installed. Please see follow the [Setup](https://doc.fluence.dev/docs/tutorials_tutorials/recipes_setting_up) instructions.
|
||||
|
||||
Applications are composed from one or more services available on one or more Fluence peer-to-peer nodes. Services are comprised of one or more Wasm modules providing a wide range of compute functionality and access to persistance, e.g. IPFS and SQLite. For the purpose of our objective, we need a service that can call on some API to source price quotes. In an ideal, production world, this would be calling on a large set of DEX contacts to obtain price pairs and, say, lquidity, over a particular window of time. For our purposes, we simplify the process and call on the [Coingecko API](https://www.coingecko.com/api/documentations/v3) and add a random jitter to each quote retrieved to give us some variance.
|
||||
|
||||
For implementation details, see [price_getter_service]("./../price_getter_service/src/main.rs), which compiles to our desired wasm32-wasi target. Since Wasm modules don't have sockets but we need to use cUrl, which is provided by the host node. In order to do that, we fist need to write an adapter that allows us to access the cUrl service from a Wasm module and then link that service to our price_getter service. See [cUrl adapter example](./marine-scripts/curl_adapter) for more details on the implementation of our [curl adapter service](./curl_adapter/src/main.rs).
|
||||
|
||||
Figure 3: Stylized Service Creation By Marine Module Linking
|
||||
|
||||
<img src="images/figure_2.png " width="400" />
|
||||
|
||||
As seen in Figure 3, we link the price_getter module and curl adapter module into a price_getter service ready for deployment to the Fluence peer-to-peer network. Before we proceed, we have one more service to consider: the price quote processing service which yields the oracle. Again, we simplified what could be an extensive processing algorithm into a simple weighted mean calculation, see [weighted_mean_service]("./../weighted_mean_service/src/main.rs") for implementation details. Unlike the price getter service, weighted mean service is a simple, FaaS compute module that deploys on any number of network peers.
|
||||
|
||||
For peer prioritization we will use built-in TrustGraph service. TODO: smth about weights
|
||||
|
||||
We now have our code in place and area ready to compile and our compilation instructions are contain in the `scripts/build.sh` script, which basically instructs the the code is compiled with `marine` and that the resulting Wasm modules are copied to the `artifacts` directory. In the project directory:
|
||||
|
||||
```bash
|
||||
./scripts/build.sh
|
||||
```
|
||||
|
||||
which gives you the updated Wasm modules in the `artifacts` directory.
|
||||
|
||||
The next step is to deploy the two services to one or more peers and we use the `fldist` tool to get this done. First, we need to now what peers are available and we can get an enumeration from:
|
||||
|
||||
```bash
|
||||
fldist env
|
||||
```
|
||||
|
||||
*Please note that multiple service instances have been already deployed and the (peer id, service id) tuples can be found in [data]("./data/deployed_services.json) json file. While your more than welcome to deploy your services, you don't have to in order to use them.*
|
||||
|
||||
Pick any of the peer ids from the listed peers to deploy your services. Let's say we use peer id `12D3KooWFEwNWcHqi9rtsmDhsYcDbRUCDXH84RC4FW6UfsFWaoHi`:
|
||||
|
||||
```bash
|
||||
$ fldist --node-id 12D3KooWFEwNWcHqi9rtsmDhsYcDbRUCDXH84RC4FW6UfsFWaoHi new_service --ms artifacts/curl_adapter.wasm:configs/curl_adapter_cfg.json artifacts/price_getter_service.wasm:configs/price_getter_service_cfg.json --name price-getter-service-0
|
||||
service id: f5b456fa-ee18-4df1-b18b-84fe7ebc7ad0 # <--- REMEMBER service id !!
|
||||
service created successfully
|
||||
```
|
||||
|
||||
to deploy a price-getter service and
|
||||
|
||||
```bash
|
||||
$ fldist --node-id 12D3KooWFEwNWcHqi9rtsmDhsYcDbRUCDXH84RC4FW6UfsFWaoHi new_service --ms artifacts/weighted_mean_service.wasm:configs/weighted_mean_service_cfg.json --name weighted_mean-service-0
|
||||
service id: debecd02-ba7d-40a2-92ab-08a9321da2cf # <--- REMEMBER service id !!
|
||||
service created successfully
|
||||
```
|
||||
|
||||
to deploy a weighted mean service. Please take note of the service-id you get back for each fo the deployments, which are needed to locate the service in the future.
|
||||
|
||||
That's it for service development and deployment!
|
||||
|
||||
## Application Composition with Aqua
|
||||
|
||||
Aqua allows us to compose distributed services into decentralized applications such as our price oracle app. However, Aqua permits a great degree of freedom of *how* to compose services. As Aqua combines [Layer 3 and Layer 7](https://en.wikipedia.org/wiki/OSI_model) programming, i.e., network and application programming, respectively, Aqua allows us to specify parallel or sequential workflows in response to service availability and deployment.
|
||||
|
||||
With just a few lines of code, we can program the network and application layers to compose hose peer-to-peer services into powerful decentralized applications, look at the `get_weighted_price` function:
|
||||
|
||||
```aqua
|
||||
-- aqua-scripts/get_crypto_prices.aqua
|
||||
|
||||
data NodeServicePair:
|
||||
node: string
|
||||
service_id: string
|
||||
|
||||
func get_weighted_price(coin: string, currency: string, getter_topo: []NodeServicePair, mean_topo: NodeServicePair) -> Result:
|
||||
prices: *f64
|
||||
weights: *u32
|
||||
for topo <- getter_topo: --< For each instance of the getter topology
|
||||
on topo.node: --< On each specified node
|
||||
PriceGetterService topo.service_id --< And service id
|
||||
ts_ms <- Peer.timestamp_ms()
|
||||
res <- PriceGetterService.price_getter(coin, currency, ts_ms) --< Run the price getter service to obtain a quote
|
||||
prices <- F64Op.identity(res.result)
|
||||
|
||||
on HOST_PEER_ID: --< On our relay
|
||||
for topo <- getter_topo: --< For each instance of the getter topology
|
||||
res <- get_weight(topo.node) --< Obtain subjective weight of each node in trust graph
|
||||
weights <- U32Op.identity(res.weight)
|
||||
|
||||
on mean_topo.node: --< After loops, create the Mean service binding on the specified node
|
||||
WeightedMeanService mean_topo.service_id
|
||||
result <- WeightedMeanService.weighted_mean(prices, weights) --< Process the price quote array
|
||||
<- result --< Return the result to the client peer
|
||||
```
|
||||
|
||||
To compile our Aqua script, we use the `aqua` tool and either compile our code to raw Air:
|
||||
|
||||
```text
|
||||
% aqua -i aqua-scripts -o air-scripts -a
|
||||
```
|
||||
|
||||
or to a ready-made typescript stub:
|
||||
|
||||
```text
|
||||
% aqua -i aqua-scripts -o air-scripts
|
||||
```
|
||||
|
||||
## Summary
|
||||
|
||||
We illustrated how to create decentralized weighted price oracle application with Aqua and the Fluence stack with different peers and use TrustGraph for prioritization.
|
16
aqua-examples/weighted-price-oracle/aqua-scripts/export.aqua
Normal file
16
aqua-examples/weighted-price-oracle/aqua-scripts/export.aqua
Normal file
@ -0,0 +1,16 @@
|
||||
import Peer from "@fluencelabs/aqua-lib/builtin.aqua"
|
||||
use add_trust from "@fluencelabs/trust-graph/trust-graph-api.aqua" as TrustGraph
|
||||
|
||||
export add_trust
|
||||
|
||||
func add_trust(issued_for_peer_id: string, expires_at_sec: u64) -> ?string:
|
||||
on HOST_PEER_ID:
|
||||
error <- TrustGraph.add_trust(issued_for_peer_id, expires_at_sec)
|
||||
<- error
|
||||
|
||||
|
||||
func timestamp_sec() -> u64:
|
||||
on HOST_PEER_ID:
|
||||
result <- Peer.timestamp_sec()
|
||||
<- result
|
||||
|
@ -0,0 +1,43 @@
|
||||
import "@fluencelabs/aqua-lib/builtin.aqua"
|
||||
import "@fluencelabs/trust-graph/trust-graph-api.aqua"
|
||||
|
||||
data Result:
|
||||
result: f64
|
||||
success: bool
|
||||
error_msg: string
|
||||
|
||||
service PriceGetterService:
|
||||
price_getter(coin: string, currency: string, timestamp_ms: u64) -> Result
|
||||
|
||||
service WeightedMeanService:
|
||||
weighted_mean(data: []f64, weights: []u32) -> Result
|
||||
|
||||
service F64Op("op"):
|
||||
identity(x:f64) -> f64
|
||||
|
||||
service U32Op("op"):
|
||||
identity(x:u32) -> u32
|
||||
|
||||
data NodeServicePair:
|
||||
node: string
|
||||
service_id: string
|
||||
|
||||
func get_weighted_price(coin: string, currency: string, getter_topo: []NodeServicePair, mean_topo: NodeServicePair) -> Result:
|
||||
prices: *f64
|
||||
weights: *u32
|
||||
for topo <- getter_topo:
|
||||
on topo.node:
|
||||
PriceGetterService topo.service_id
|
||||
ts_ms <- Peer.timestamp_ms()
|
||||
res <- PriceGetterService.price_getter(coin, currency, ts_ms)
|
||||
prices <- F64Op.identity(res.result)
|
||||
|
||||
on HOST_PEER_ID:
|
||||
for topo <- getter_topo:
|
||||
res <- get_weight(topo.node)
|
||||
weights <- U32Op.identity(res.weight)
|
||||
|
||||
on mean_topo.node:
|
||||
WeightedMeanService mean_topo.service_id
|
||||
result <- WeightedMeanService.weighted_mean(prices, weights)
|
||||
<- result
|
5
aqua-examples/weighted-price-oracle/client-peer/.gitignore
vendored
Normal file
5
aqua-examples/weighted-price-oracle/client-peer/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
dist/
|
||||
|
||||
# fluence
|
||||
|
||||
src/_aqua/*
|
7196
aqua-examples/weighted-price-oracle/client-peer/package-lock.json
generated
Normal file
7196
aqua-examples/weighted-price-oracle/client-peer/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
51
aqua-examples/weighted-price-oracle/client-peer/package.json
Normal file
51
aqua-examples/weighted-price-oracle/client-peer/package.json
Normal file
@ -0,0 +1,51 @@
|
||||
{
|
||||
"name": "price-oracle",
|
||||
"version": "0.1.0",
|
||||
"description": "Fluence Price Oracle Demo",
|
||||
"main": "./dist/src/index.js",
|
||||
"typings": "./dist/src/index.d.ts",
|
||||
"files": [
|
||||
"dist/*"
|
||||
],
|
||||
"bic": [
|
||||
"client-peer/*",
|
||||
"*.aqua",
|
||||
"package-lock.json"
|
||||
],
|
||||
"dependencies": {
|
||||
"@fluencelabs/fluence": "0.17.0",
|
||||
"@fluencelabs/fluence-network-environment": "1.0.10",
|
||||
"it-all": "^1.0.5"
|
||||
},
|
||||
"scripts": {
|
||||
"compile-aqua": "aqua -i ../aqua-scripts -o src/_aqua",
|
||||
"prebuild": "npm run compile-aqua",
|
||||
"build": "tsc",
|
||||
"prestart:local": "npm run build",
|
||||
"start:local": "node dist/src/index.js local",
|
||||
"prestart:remote": "npm run build",
|
||||
"start:remote": "node dist/src/index.js krasnodar",
|
||||
"start": "npm run start:remote"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/fluencelabs/examples/aqua-examples/price-oracle"
|
||||
},
|
||||
"keywords": [
|
||||
"aqua",
|
||||
"fluence"
|
||||
],
|
||||
"author": "Fluence Labs",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "git+https://github.com/fluencelabs/examples/issues"
|
||||
},
|
||||
"homepage": "git+https://github.com/fluencelabs/examples/aqua-examples/price-oracle#readme",
|
||||
"devDependencies": {
|
||||
"@fluencelabs/aqua": "^0.5.2-256",
|
||||
"@fluencelabs/aqua-lib": "^0.3.2",
|
||||
"@fluencelabs/trust-graph": "0.1.2-hl-api.0",
|
||||
"typescript": "^4.5.2",
|
||||
"bs58": "^4.0.1"
|
||||
}
|
||||
}
|
96
aqua-examples/weighted-price-oracle/client-peer/src/index.ts
Normal file
96
aqua-examples/weighted-price-oracle/client-peer/src/index.ts
Normal file
@ -0,0 +1,96 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import { setLogLevel, Fluence, KeyPair } from "@fluencelabs/fluence";
|
||||
import { krasnodar } from "@fluencelabs/fluence-network-environment";
|
||||
import { get_weighted_price } from "./_aqua/get_crypto_prices";
|
||||
import * as tg from "./_aqua/export";
|
||||
|
||||
const bs58 = require('bs58');
|
||||
|
||||
interface NodeServicePair {
|
||||
node: string;
|
||||
service_id: string;
|
||||
}
|
||||
|
||||
let getter_topo: Array<NodeServicePair>;
|
||||
let mean_topo: Array<NodeServicePair>;
|
||||
|
||||
// description of the services' locations, copypaste from data/deployed_services.json
|
||||
getter_topo = [
|
||||
{
|
||||
node: "12D3KooWCMr9mU894i8JXAFqpgoFtx6qnV1LFPSfVc3Y34N4h4LS",
|
||||
service_id: "7d495484-f0f0-4153-b4f0-fbb0fd162965"
|
||||
},
|
||||
{
|
||||
node: "12D3KooWFEwNWcHqi9rtsmDhsYcDbRUCDXH84RC4FW6UfsFWaoHi",
|
||||
service_id: "a79386c1-d6fc-4a41-ab5a-60599b151345"
|
||||
}
|
||||
];
|
||||
mean_topo = [
|
||||
{
|
||||
node: "12D3KooWCMr9mU894i8JXAFqpgoFtx6qnV1LFPSfVc3Y34N4h4LS",
|
||||
service_id: "02fba79b-c72c-4ae6-b54b-8c7d871caf36"
|
||||
},
|
||||
{
|
||||
node: "12D3KooWFEwNWcHqi9rtsmDhsYcDbRUCDXH84RC4FW6UfsFWaoHi",
|
||||
service_id: "d4aef7ec-4d2b-4643-9d7b-93a63387f1f0"
|
||||
}
|
||||
];
|
||||
|
||||
async function add_new_trust_checked(issued_for_peer_id: string, expires_at_sec: number) {
|
||||
let error = await tg.add_trust(issued_for_peer_id, expires_at_sec);
|
||||
if (error !== null) {
|
||||
console.error("%s", error);
|
||||
} else {
|
||||
console.log("Trust issued for %s successfully added", issued_for_peer_id)
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log("hello crypto investors");
|
||||
|
||||
// Uncomment to enable debug logs:
|
||||
// setLogLevel('DEBUG');
|
||||
|
||||
// this key is bundled in all tg builtins
|
||||
// defined in https://github.com/fluencelabs/trust-graph/blob/master/example_secret_key.ed25519
|
||||
let sk = bs58.decode("E5ay3731i4HN8XjJozouV92RDMGAn3qSnb9dKSnujiWv").slice(0, 32); // first 32 bytes - secret key, second - public key
|
||||
let example_kp = await KeyPair.fromEd25519SK(sk);
|
||||
|
||||
// create the Fluence client for the Krasnodar testnet
|
||||
await Fluence.start({ connectTo: krasnodar[0], KeyPair: example_kp });
|
||||
console.log(
|
||||
"created a fluence client %s with relay %s",
|
||||
Fluence.getStatus().peerId,
|
||||
Fluence.getStatus().relayPeerId
|
||||
);
|
||||
|
||||
await add_new_trust_checked(getter_topo[0].node, 9999999999);
|
||||
await add_new_trust_checked(getter_topo[1].node, 9999999999);
|
||||
|
||||
const weighted_price_result = await get_weighted_price("bitcoin", "usd", getter_topo, mean_topo[0]);
|
||||
console.log("get_weighted_price result: ", weighted_price_result);
|
||||
|
||||
await Fluence.stop();
|
||||
}
|
||||
|
||||
main()
|
||||
.then(() => process.exit(0))
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
@ -0,0 +1,62 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig.json to read more about this file */
|
||||
/* Basic Options */
|
||||
// "incremental": true, /* Enable incremental compilation */
|
||||
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
|
||||
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
|
||||
// "lib": [], /* Specify library files to be included in the compilation. */
|
||||
// "allowJs": true, /* Allow javascript files to be compiled. */
|
||||
// "checkJs": true, /* Report errors in .js files. */
|
||||
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
|
||||
// "declaration": true, /* Generates corresponding '.d.ts' file. */
|
||||
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
|
||||
// "sourceMap": true, /* Generates corresponding '.map' file. */
|
||||
// "outFile": "./", /* Concatenate and emit output to single file. */
|
||||
"outDir": "./dist", /* Redirect output structure to the directory. */
|
||||
"rootDir": ".", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
||||
// "composite": true, /* Enable project compilation */
|
||||
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
|
||||
// "removeComments": true, /* Do not emit comments to output. */
|
||||
// "noEmit": true, /* Do not emit outputs. */
|
||||
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
|
||||
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
||||
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
|
||||
/* Strict Type-Checking Options */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* Enable strict null checks. */
|
||||
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
|
||||
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
|
||||
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
|
||||
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
|
||||
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
|
||||
/* Additional Checks */
|
||||
// "noUnusedLocals": true, /* Report errors on unused locals. */
|
||||
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||
/* Module Resolution Options */
|
||||
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
||||
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
|
||||
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
||||
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||
// "typeRoots": [], /* List of folders to include type definitions from. */
|
||||
// "types": [], /* Type declaration files to be included in compilation. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
|
||||
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
/* Source Map Options */
|
||||
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
|
||||
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
||||
/* Experimental Options */
|
||||
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
||||
/* Advanced Options */
|
||||
"skipLibCheck": true, /* Skip type checking of declaration files. */
|
||||
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
|
||||
}
|
||||
}
|
19
aqua-examples/weighted-price-oracle/configs/Config.toml
Normal file
19
aqua-examples/weighted-price-oracle/configs/Config.toml
Normal file
@ -0,0 +1,19 @@
|
||||
modules_dir = "artifacts/"
|
||||
|
||||
[[module]]
|
||||
name = "curl_adapter"
|
||||
logger_enabled = false
|
||||
mem_pages_count = 1
|
||||
|
||||
[module.mounted_binaries]
|
||||
curl = "/usr/bin/curl"
|
||||
|
||||
[[module]]
|
||||
name = "price_getter_service"
|
||||
logger_enabled = false
|
||||
mem_pages_count = 1
|
||||
|
||||
[[module]]
|
||||
name = "weighted_mean_service"
|
||||
logger_enabled = false
|
||||
mem_pages_count = 1
|
@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "curl_adapter",
|
||||
"mountedBinaries": {
|
||||
"curl": "/usr/bin/curl"
|
||||
},
|
||||
"mem_page_count": 1,
|
||||
"logger_enabled": false
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "price_getter_service",
|
||||
"mem_page_count": 1,
|
||||
"logger_enabled": false
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "weighted_mean_service",
|
||||
"mem_page_count": 1,
|
||||
"logger_enabled": false
|
||||
}
|
14
aqua-examples/weighted-price-oracle/curl_adapter/Cargo.toml
Normal file
14
aqua-examples/weighted-price-oracle/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]
|
||||
marine-rs-sdk = "0.6.14"
|
||||
log = "0.4.8"
|
37
aqua-examples/weighted-price-oracle/curl_adapter/src/main.rs
Normal file
37
aqua-examples/weighted-price-oracle/curl_adapter/src/main.rs
Normal file
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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(improper_ctypes)]
|
||||
|
||||
use marine_rs_sdk::marine;
|
||||
use marine_rs_sdk::module_manifest;
|
||||
|
||||
use marine_rs_sdk::MountedBinaryResult;
|
||||
|
||||
module_manifest!();
|
||||
|
||||
pub fn main() {}
|
||||
|
||||
#[marine]
|
||||
pub fn curl_request(cmd: Vec<String>) -> MountedBinaryResult {
|
||||
curl(cmd)
|
||||
}
|
||||
|
||||
#[marine]
|
||||
#[link(wasm_import_module = "host")]
|
||||
extern "C" {
|
||||
fn curl(cmd: Vec<String>) -> MountedBinaryResult;
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
{
|
||||
"price_getter": [
|
||||
{
|
||||
"node": "12D3KooWCMr9mU894i8JXAFqpgoFtx6qnV1LFPSfVc3Y34N4h4LS",
|
||||
"service_id": "7d495484-f0f0-4153-b4f0-fbb0fd162965"
|
||||
},
|
||||
{
|
||||
"node": "12D3KooWFEwNWcHqi9rtsmDhsYcDbRUCDXH84RC4FW6UfsFWaoHi",
|
||||
"service_id": "a79386c1-d6fc-4a41-ab5a-60599b151345"
|
||||
}
|
||||
],
|
||||
"mean": [
|
||||
{
|
||||
"node": "12D3KooWCMr9mU894i8JXAFqpgoFtx6qnV1LFPSfVc3Y34N4h4LS",
|
||||
"service_id": "02fba79b-c72c-4ae6-b54b-8c7d871caf36"
|
||||
},
|
||||
{
|
||||
"node": "12D3KooWFEwNWcHqi9rtsmDhsYcDbRUCDXH84RC4FW6UfsFWaoHi",
|
||||
"service_id": "d4aef7ec-4d2b-4643-9d7b-93a63387f1f0"
|
||||
}
|
||||
]
|
||||
}
|
BIN
aqua-examples/weighted-price-oracle/images/figure_2.png
Normal file
BIN
aqua-examples/weighted-price-oracle/images/figure_2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 58 KiB |
@ -0,0 +1,22 @@
|
||||
[package]
|
||||
name = "price_getter_service"
|
||||
version = "0.1.0"
|
||||
authors = ["boneyard93501 <4523011+boneyard93501@users.noreply.github.com>"]
|
||||
edition = "2018"
|
||||
description = "price-getter-service, a Marine wasi module"
|
||||
license = "Apache-2.0"
|
||||
|
||||
[[bin]]
|
||||
name = "price_getter_service"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
marine-rs-sdk = "0.6.14"
|
||||
log = "0.4.14"
|
||||
picorand = "0.1.1"
|
||||
fstrings = "0.2.3"
|
||||
serde_json = "1.0.57"
|
||||
|
||||
[dev]
|
||||
[profile.release]
|
||||
opt-level = "s"
|
@ -0,0 +1,94 @@
|
||||
/*
|
||||
* 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 marine_rs_sdk::{marine, module_manifest, MountedBinaryResult};
|
||||
use picorand::{PicoRandGenerate, WyRand, RNG};
|
||||
use serde_json;
|
||||
|
||||
#[macro_use]
|
||||
extern crate fstrings;
|
||||
|
||||
module_manifest!();
|
||||
|
||||
pub fn main() {}
|
||||
|
||||
#[marine]
|
||||
pub struct Result {
|
||||
pub result: f64,
|
||||
pub success: bool,
|
||||
pub error_msg: String,
|
||||
}
|
||||
|
||||
#[marine]
|
||||
pub fn price_getter(coin: String, currency: String, timestamp_ms: u64) -> Result {
|
||||
let url =
|
||||
f!("https://api.coingecko.com/api/v3/simple/price?ids={coin}&vs_currencies={currency}");
|
||||
let curl_cmd = vec![
|
||||
"-X".to_string(),
|
||||
"GET".to_string(),
|
||||
"-H".to_string(),
|
||||
"Accept: application/json".to_string(),
|
||||
url,
|
||||
];
|
||||
let response = curl_request(curl_cmd);
|
||||
let result = String::from_utf8(response.stdout);
|
||||
|
||||
match result {
|
||||
Ok(res) => {
|
||||
let json_res = serde_json::from_str(&res.clone());
|
||||
if json_res.is_err() {
|
||||
return Result {
|
||||
result: -1f64,
|
||||
success: false,
|
||||
error_msg: "Failure to complete call".to_string(),
|
||||
};
|
||||
}
|
||||
let json_res: serde_json::Value = json_res.unwrap();
|
||||
let value = json_res[coin.to_lowercase()][currency.to_lowercase()].as_f64();
|
||||
if value.is_none() {
|
||||
return Result {
|
||||
result: -1f64,
|
||||
success: false,
|
||||
error_msg:
|
||||
"No price value from source available. Check your coin and currency values."
|
||||
.to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
let value: f64 = value.unwrap();
|
||||
|
||||
let mut rng = RNG::<WyRand, u16>::new(timestamp_ms);
|
||||
let multiplier = rng.generate_range(2, 5) as f64;
|
||||
let rnd_value = value * (1f64 + multiplier / 100f64);
|
||||
Result {
|
||||
result: format!("{:.2}", rnd_value).parse::<f64>().unwrap(),
|
||||
success: true,
|
||||
error_msg: "".to_string(),
|
||||
}
|
||||
}
|
||||
Err(_) => Result {
|
||||
result: -1f64,
|
||||
success: false,
|
||||
error_msg: String::from_utf8(response.stderr).unwrap(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[marine]
|
||||
#[link(wasm_import_module = "curl_adapter")]
|
||||
extern "C" {
|
||||
pub fn curl_request(cmd: Vec<String>) -> MountedBinaryResult;
|
||||
}
|
24
aqua-examples/weighted-price-oracle/scripts/build.sh
Executable file
24
aqua-examples/weighted-price-oracle/scripts/build.sh
Executable file
@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env bash -o errexit -o nounset -o pipefail
|
||||
|
||||
mkdir -p artifacts
|
||||
|
||||
cd curl_adapter
|
||||
cargo update --aggressive
|
||||
marine build --release
|
||||
cd ..
|
||||
|
||||
cd price_getter_service
|
||||
cargo update --aggressive
|
||||
marine build --release
|
||||
cd ..
|
||||
|
||||
cd weighted_mean_service
|
||||
cargo update --aggressive
|
||||
marine build --release
|
||||
cd ..
|
||||
|
||||
rm -f artifacts/*.wasm
|
||||
|
||||
cp curl_adapter/target/wasm32-wasi/release/curl_adapter.wasm artifacts/
|
||||
cp price_getter_service/target/wasm32-wasi/release/price_getter_service.wasm artifacts/
|
||||
cp weighted_mean_service/target/wasm32-wasi/release/weighted_mean_service.wasm artifacts/
|
6
aqua-examples/weighted-price-oracle/scripts/deploy.sh
Executable file
6
aqua-examples/weighted-price-oracle/scripts/deploy.sh
Executable file
@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env bash -o errexit -o nounset -o pipefail
|
||||
|
||||
fldist --node-id 12D3KooWCMr9mU894i8JXAFqpgoFtx6qnV1LFPSfVc3Y34N4h4LS new_service --ms artifacts/curl_adapter.wasm:configs/curl_adapter_cfg.json artifacts/price_getter_service.wasm:configs/price_getter_service_cfg.json --name price-getter-service-0
|
||||
fldist --node-id 12D3KooWFEwNWcHqi9rtsmDhsYcDbRUCDXH84RC4FW6UfsFWaoHi new_service --ms artifacts/curl_adapter.wasm:configs/curl_adapter_cfg.json artifacts/price_getter_service.wasm:configs/price_getter_service_cfg.json --name price-getter-service-0
|
||||
fldist --node-id 12D3KooWCMr9mU894i8JXAFqpgoFtx6qnV1LFPSfVc3Y34N4h4LS new_service --ms artifacts/weighted_mean_service.wasm:configs/weighted_mean_service_cfg.json --name weighted-mean-service-0
|
||||
fldist --node-id 12D3KooWFEwNWcHqi9rtsmDhsYcDbRUCDXH84RC4FW6UfsFWaoHi new_service --ms artifacts/weighted_mean_service.wasm:configs/weighted_mean_service_cfg.json --name weighted-mean-service-0
|
@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "price_getter_service"
|
||||
version = "0.1.0"
|
||||
authors = ["boneyard93501 <4523011+boneyard93501@users.noreply.github.com>"]
|
||||
edition = "2018"
|
||||
description = "price-getter-service, a Marine wasi module"
|
||||
license = "Apache-2.0"
|
||||
|
||||
[[bin]]
|
||||
name = "weighted_mean_service"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
marine-rs-sdk = "0.6.14"
|
||||
log = "0.4.14"
|
||||
|
||||
[dev]
|
||||
[profile.release]
|
||||
opt-level = "s"
|
@ -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 marine_rs_sdk::{marine, module_manifest};
|
||||
|
||||
module_manifest!();
|
||||
|
||||
fn main() {}
|
||||
|
||||
#[marine]
|
||||
pub struct Result {
|
||||
pub result: f64,
|
||||
pub success: bool,
|
||||
pub error_msg: String,
|
||||
}
|
||||
|
||||
#[marine]
|
||||
pub fn weighted_mean(data: Vec<f64>, weights: Vec<u32>) -> Result {
|
||||
match calc_weighted_mean(data.iter(), weights.iter()) {
|
||||
Some(res) => Result {
|
||||
result: res,
|
||||
success: true,
|
||||
error_msg: "".to_string(),
|
||||
},
|
||||
None => Result {
|
||||
result: -1f64,
|
||||
success: false,
|
||||
error_msg: "Failure to calculate mean. Check your inputs.".to_string(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn calc_weighted_mean<'a>(data: impl ExactSizeIterator<Item = &'a f64>, weights: impl ExactSizeIterator<Item = &'a u32>) -> Option<f64> {
|
||||
let n = data.len();
|
||||
if n < 1 || n != weights.len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut weights_sum = 0u32;
|
||||
let res = data.zip(weights).map(|(&a, &b)| { weights_sum += b; a * b as f64}).sum::<f64>() / weights_sum as f64;
|
||||
let res = format!("{:.2}", res).parse::<f64>().unwrap();
|
||||
Some(res)
|
||||
}
|
Loading…
Reference in New Issue
Block a user