Fluence Network

The Fluence Labs Developer Hub

Welcome to the Fluence Labs developer hub. You'll find comprehensive guides and documentation to help you start working with Fluence Labs as quickly as possible, as well as support if you get stuck. Let's jump right in!

Should you have any questions, feel free to join our Discord or Telegram!

Get Started

Fluence JS SDK is a bridge to Fluence Network. It provides you a local Fluence Peer, powering you to develop your application in a p2p fashion.

SDK gives you the following powers:

  • Means to create and manage the identity of your local peer and applications
  • Ability to execute AIR scripts on local WASM runtime
  • Define the behavior of local function calls (i.e., when script calls function on your local peer)
  • Automatically forward AIR script execution to remote peers, according to script-defined topology

Usage examples

How to install

If you have npm, you can add fluence to your project by executing the following.

npm install fluence

Defining services

First, let's register a local service console and define its behavior by registering function log, as in console.log. We'll show how to call it in the next step.

// TODO somehow delete 'dist' directory from paths
import {ServiceMultiple} from "fluence/dist/service";
import {registerService} from "fluence/dist/globalState";

let service = new ServiceMultiple("console")
service.registerFunction("log", (args: any[]) => {
    console.log(`log: ${args}`);
  
    return {};
})

Calling a function

As AIR scripts describe the topology of execution functions on peers, we can write a script to call a function on our local console service.

A script could be as follows

(call %init_peer_id% ("console" "log") ["hello" "from" "WASM"])

init_peer_id refers to a peer that initiated script execution. In that case, it is us, so the call of console.log will call the previously defined function call on service console.

Here's how this can be expressed in terms of Fluence JS SDK.

import Fluence from "fluence";
import {build} from "fluence/dist/particle";

let client = await Fluence.local();

// call is an instruction that takes the following parameters
// (call <Peer location> <Function location> <[argument list]> <optional output>)
let script = `(call %init_peer_id% ("console" "log") ["hello"])`;

// Wrap script into particle, so it can be executed by local WASM runtime
let particle = await build(client.selfPeerId, script, new Map());

await client.executeParticle(particle)
// "[hello]" should be printed in a console

Using variables

We've seen how to pass literal arguments (i.e., values in quotes). Using literals can be tedious if you need to repeat values or wish to keep the script short and readable. To avoid that, you can use variables that refer to particle data.

let script = `(call %init_peer_id% ("console" "log") [msg])`;
let particle = await build(client.selfPeerId, script, new Map("msg" -> "hello"));
await client.executeParticle(particle)

To learn more about writing AIR scripts, refer to AIR doc.

Identity

Managing identity is crucial, as your identity is used to locate your peer in the network and enable ownership services created by you on remote peers.

In JS, PeerId holds 3 related things:

  • Private key: PeerId.privKey()
  • Public key: PeerId.pubKey()
  • PeerId in base58: PeerId. toB58String()

This makes a peer's identity: the ability to sign things and the peer's location. You can generate and use PeerId like the following.

let peerId = await Fluence.generatePeerId()
console.log(peerId.toB58String())

let client = await Fluence.connect(multiaddr, peerId);

peerIdToSeed and seedToPeerId functions are there to simplify private key extraction and vice versa. You can pair them with some secure browser storage and follow standard key management guidelines.

import {peerIdToSeed, seedToPeerId} from "fluence/dist/seed";

let seed = peerIdToSeed(peerId)
// store it somewhere and then transform back to peerId
let peerId = seedToPeerId(peerId)

Upload wasm modules

Services are created from wasm modules, and these modules are currently distributed by manually uploading them to desired nodes.

To learn more about the service lifecycle, refer to doc.

To upload modules, as .wasm files they are, they first need to be converted to base64 strings. Here I'll assume you already possess a base64 string of desired modules.

One more thing about modules is that they are referred to by their names. When you upload a module, you specify its name. You can later use these names when specifying dependencies in a blueprint.

// connect to some Fluence node
let client = await Fluence.connect(multiaddr);

let moduleBs64 = ...; // load .wasm module into base64 string

// upload module under "module_name" to connected node
await client.addModule("module_name", moduleBs64);

// uploading to different node by specifying its PeerId
let remotePeerId = "123DTargetNode";
await client.addModule("module_name", moduleBs64, remotePeerId);

Create service

In Fluence, services are created from blueprints. Blueprints specify a list of modules required to create the service and some meta-information. To learn more about blueprints, refer to doc.

Modules specified in the blueprint can be interlinked, i.e., they can import functions from each other. The important thing is that the last module in the dependency list is a facade module. i.e., it's the only module that can be called directly from AIR scripts. The facade module is "public," while all other modules are "private" in that sense. To learn more about building services, refer to doc.

You can create a service from modules ["module_a", "module_b", "module_facade"] as follows.

// modules could be linked to each other. If so, dependent modules should be specified after dependencies.
let blueprintId = await client.addBlueprint("great_service", ["module_a", "module_b", "module_facade"], remotePeerId)
let serviceId = await client.createService(blueprintId, remotePeerId)

blueprintId now can be used to create instances of great_service. Remember that blueprints must be added to a node before you can use them.

serviceId can be used to call function on the created service, the same way as console was used in Calling function section above. Here's an example of calling function greet on the created service.

let script = `
  (seq
      (call relay ("op" "identity") [])
      (call remotePeerId (serviceId "greet") [name])
  )
`;
let data = new Map(
  "name" -> "folex", 
  "serviceId" -> serviceId, 
  "remotePeerId" -> ...
);
let particle = await build(client.selfPeerId, script, data);
await client.executeParticle(particle)

Service aliases

To learn about service aliasing, refer to doc on Aliases.

JS SDK wraps these scripts into JS API, so you can call them like follows.

+----------------+      +---------+            +-----------+
+----------------+      |         |            ||          |
        | |             |         ++           ||          ++
        | |             |         |-+          ||          |-+
        | |             |         |-|          ||          |--+
        | |             |         |-+          ||          |--|
        | |             |         ||           ||          |--|
        | |             +-----------+          ||          |--|
        | |             |           |          ||          |--|
        | |             |           ++         ||          |--+
        | |             |           ||         ||          |-+
        | |             |           ||         ||          ||
        | |             |           ++         ||          ++
        +-+             +-----------+          +-----------+

Authentication

Read doc on authentication & authorization patterns to learn of possibilities.

JS SDK provides a nice API for some of these patterns.

+----------------+      +---------+            +-----------+
+----------------+      |         |            ||          |
        | |             |         ++           ||          ++
        | |             |         |-+          ||          |-+
        | |             |         |-|          ||          |--+
        | |             |         |-+          ||          |--|
        | |             |         ||           ||          |--|
        | |             +-----------+          ||          |--|
        | |             |           |          ||          |--|
        | |             |           ++         ||          |--+
        | |             |           ||         ||          |-+
        | |             |           ||         ||          ||
        | |             |           ++         ||          ++
        +-+             +-----------+          +-----------+

Showcase: relaying & remote execution

Fluence network is made of peers of various execution power, availability guarantees, and most importantly – various connectivity. To allow peers from non-public networks to communicate, Fluence employs relay mechanics. Currently, any Fluence Node can be used as a relay.

To learn more about relaying, refer to the doc.

For now, we'll use a relay to connect two browser peers. You can emulate two peers by opening two browser tabs, for example. I'll assume that you have done so, and their peer ids are 123DPeerIdA and 123DPeerIdB.

We'll use the following relays:

  • /dns4/stage.fluence.dev/tcp/19001/wss/p2p/12D3KooWEXNUbCXooUwHrHBbrmjsrpHXoEphPwbjQXEGyzbqKnE9
  • /dns4/stage.fluence.dev/tcp/19002/wss/p2p/12D3KooWHk9BjDQBUqnavciRPhAYFvqKBe4ZiPPvde7vDaqgn5er

On a first browser, connect to the first relay, and register service with a single function as follows.

// TODO somehow delete 'dist' directory from paths
import {Service} from "fluence/dist/service";
import {registerService} from "fluence/dist/globalState";

let service = new Service("console")
service.registerFunction("log", (args: any[]) => {
    console.log(`log: ${args}`)
})

let multiaddr = "/dns4/stage.fluence.dev/tcp/19001/wss/p2p/12D3KooWEXNUbCXooUwHrHBbrmjsrpHXoEphPwbjQXEGyzbqKnE9"
let client = await Fluence.connect(multiaddr);
console.log(`First PeerId: ${client.selfPeerId}`);

On a second browser, connect to the second relay, and call remote console.log as follows.

let multiaddr = "/dns4/stage.fluence.dev/tcp/19002/wss/p2p/12D3KooWHk9BjDQBUqnavciRPhAYFvqKBe4ZiPPvde7vDaqgn5er"
let client = await Fluence.connect(multiaddr);
console.log(`Second PeerId: ${client.selfPeerId}`);

let script = `
    (seq
        (call second-relay ("op" "identity") [])
        (seq
            (call first-relay ("op" "identity") [])
            (call first-peer ("console" "log") [msg])
        )
    )
`;
let particle = await build(
    client.selfPeerId, script, 
    new Map(
        "first-peer" -> "123DPeerIdA" // <== Do not forget to change 123DPeerIdA to actual peer id
        "second-relay" -> "12D3KooWHk9BjDQBUqnavciRPhAYFvqKBe4ZiPPvde7vDaqgn5er",
        "first-relay" -> "12D3KooWEXNUbCXooUwHrHBbrmjsrpHXoEphPwbjQXEGyzbqKnE9",
        "msg" -> "hello"
    )
);
await client.executeParticle(particle)

After that, you should see the message log: [hello] in the console of the first browser.

To learn more about AIR scripts, refer to doc.

Updated about a month ago

JS SDK


Suggested Edits are limited on API Reference Pages

You can only suggest edits to Markdown body content, but not to the API spec.