File: README.md
Linera is a decentralized blockchain infrastructure designed for highly scalable,
secure, low-latency Web3 applications.
Documentation
Visit our developer page and read our
whitepaper to learn more about the Linera protocol.
Repository Structure
The main crates and directories of this repository can be summarized as follows: (listed
from low to high levels in the dependency graph)
linera-baseBase
definitions, including cryptography.linera-version
A library to manage version info in binaries and services.linera-viewsA
library mapping complex data structures onto a key-value store. The corresponding
procedural macros are implemented inlinera-views-derive.linera-execution
Persistent data and the corresponding logic for runtime and execution of Linera
applications.linera-chain
Persistent data and the corresponding logic for chains of blocks, certificates, and
cross-chain messaging.linera-storage
Defines the storage abstractions for the protocol on top oflinera-chain.linera-coreThe
core Linera protocol, including client and server logic, node synchronization, etc.linera-rpc
Defines the data-type for RPC messages (currently all client ↔ proxy ↔
chain ↔ chain interactions), and track the corresponding data schemas.linera-client
Library for writing Linera clients. Used for the command-line
client and the node service inlinera-service, as well as the Web
client inlinera-web.linera-service
Executable for clients (aka CLI wallets), proxy (aka validator frontend) and servers.linera-sdkThe
library to develop Linera applications written in Rust for the Wasm virtual machine. The
corresponding procedural macros are implemented inlinera-sdk-derive.examplesExamples of Linera applications written in Rust.
Prerequisites
See INSTALL.md for software requirements to develop in this repo.
Quickstart with the Linera CLI tool
The following commands set up a local test network and run some transfers between the
microchains owned by a single wallet.
# Make sure to compile the Linera binaries and add them in the $PATH.
# cargo build -p linera-storage-service -p linera-service --bins
export PATH="$PWD/target/debug:$PATH"
# Import the optional helper function `linera_spawn`.
source /dev/stdin <<<"$(linera net helper 2>/dev/null)"
# Run a local test network with the default parameters and a number of microchains
# owned by the default wallet. This also defines `LINERA_TMP_DIR`.
linera_spawn \
linera net up --with-faucet --faucet-port 8080
# Remember the URL of the faucet.
FAUCET_URL=http://localhost:8080
# If you're using a testnet, start here and run this instead:
# LINERA_TMP_DIR=$(mktemp -d)
# FAUCET_URL=https://faucet.testnet-XXX.linera.net # for some value XXXEnable logs for user applications:
export LINERA_APPLICATION_LOGS=trueSet the path of the future wallet:
export LINERA_WALLET="$LINERA_TMP_DIR/wallet.json"
export LINERA_KEYSTORE="$LINERA_TMP_DIR/keystore.json"
export LINERA_STORAGE="rocksdb:$LINERA_TMP_DIR/client.db"
# Initialize a new user wallet.
linera wallet init --faucet $FAUCET_URL
# Request chains.
INFO1=($(linera wallet request-chain --faucet $FAUCET_URL))
INFO2=($(linera wallet request-chain --faucet $FAUCET_URL))
CHAIN1="${INFO1[0]}"
ACCOUNT1="${INFO1[1]}"
CHAIN2="${INFO2[0]}"
ACCOUNT2="${INFO2[1]}"
# Show the different chains tracked by the wallet.
linera wallet show
# Query the chain balance of some of the chains.
linera query-balance "$CHAIN1"
linera query-balance "$CHAIN2"
# Transfer 10 units then 5 back.
linera transfer 10 --from "$CHAIN1" --to "$CHAIN2"
linera transfer 5 --from "$CHAIN2" --to "$CHAIN1"
# Query balances again.
linera query-balance "$CHAIN1"
linera query-balance "$CHAIN2"
# Now let's fund the user balances.
linera transfer 5 --from "$CHAIN1" --to "$ACCOUNT1@$CHAIN1"
linera transfer 2 --from "$ACCOUNT1@$CHAIN1" --to "$ACCOUNT2@$CHAIN2"
# Query user balances again.
linera query-balance "$ACCOUNT1@$CHAIN1"
linera query-balance "$ACCOUNT2@$CHAIN2"More complex examples may be found in our developer manual as well
as the example applications in this repository.
Contributing
We welcome contributions from the community! If you'd like to contribute to the Linera protocol:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
For detailed guidelines, see our contribution guide.
File: examples/agent/README.md
Linera Agent
This example application runs a Large Language Model (LLM) Agent which uses an LLM
to forge queries by introspecting the Linera Node Service GraphQL API.
How it works
The agent wraps an LLM of choice and is provided with context on what Linera is and
the core concepts. Further context is provided by performing an introspection query
which injects the Linera Node Service GraphQL schema. The schema is quite large -
the resulting context in ~15,000 tokens being used just for context.
After the context is provided, a chat is initiated between user and agent, where the
agent writes GraphQL queries on behalf of the user and uses the Linera Node Service
tool to submit them.
Usage
Assume a Linera Node Service is running on localhost:8080 and is connected to a running
network (local or otherwise).
export OPENAI_API_KEY="..." # Get your OpenAI API key here: https://openai.com/index/openai-api/
cargo run
I'm the Linera agent. How can I help! (Type 'exit' to quit.)
> Hey! What's my default chain?
Req: query { chains { default } }
========================== Response ============================
{"data":{"chains":{"default":"01a7d54c59b43084ae49ebc490bb3c7c1f0924c18e93e62ecddfaf721c98e493"}},"errors":null}
================================================================File: examples/amm/README.md
Automated Market Maker (AMM) Example Application
This example implements an Automated Market Maker (AMM) which demonstrates the DeFi capabilities of the
Linera protocol. The prerequisite for the AMM application is the fungible application, as we will
be adding/removing liquidity and also performing a swap.
How it works
It supports the following operations. All operations need to be executed remotely.
Swap: For a given input token and an input amount, it swaps that token amount for an
amount of the other token calculated based on the current AMM ratio.Add Liquidity: This operation allows adding liquidity to the AMM. Given a maximum
token0andtoken1amount that you're willing to add, it adds liquidity such that you'll be
adding at mostmax_token0_amountoftoken0andmax_token1_amountoftoken1. The amounts
will be calculated based on the current AMM ratio. The owner, in this case, refers to the user
adding liquidity, which currently can only be a chain owner.Remove Liquidity: This withdraws tokens from the AMM. Given the index of the token you'd
like to remove (can be 0 or 1), and the amount of that token that you'd like to remove, it calculates
how much of the other token will also be removed based on the current AMM ratio. Then it removes
the amounts from both tokens as a removal of liquidity. The owner, in this context, is the user
removing liquidity, which currently can only be a chain owner.
Usage
Setting Up
Before getting started, make sure that the binary tools linera* corresponding to
your version of linera-sdk are in your PATH. For scripting purposes, we also assume
that the BASH function linera_spawn is defined.
From the root of Linera repository, this can be achieved as follows:
export PATH="$PWD/target/debug:$PATH"
source /dev/stdin <<<"$(linera net helper 2>/dev/null)"Next, start the local Linera network and run a faucet:
FAUCET_PORT=8079
FAUCET_URL=http://localhost:$FAUCET_PORT
linera_spawn linera net up --with-faucet --faucet-port $FAUCET_PORT
# If you're using a testnet, run this instead:
# LINERA_TMP_DIR=$(mktemp -d)
# FAUCET_URL=https://faucet.testnet-XXX.linera.net # for some value XXXEnable logs for user applications:
export LINERA_APPLICATION_LOGS=trueCreate the user wallet and add chains to it:
export LINERA_WALLET="$LINERA_TMP_DIR/wallet.json"
export LINERA_KEYSTORE="$LINERA_TMP_DIR/keystore.json"
export LINERA_STORAGE="rocksdb:$LINERA_TMP_DIR/client.db"
linera wallet init --faucet $FAUCET_URL
INFO_AMM=($(linera wallet request-chain --faucet $FAUCET_URL))
INFO_1=($(linera wallet request-chain --faucet $FAUCET_URL))
INFO_2=($(linera wallet request-chain --faucet $FAUCET_URL))
CHAIN_AMM="${INFO_AMM[0]}"
CHAIN_1="${INFO_1[0]}"
CHAIN_2="${INFO_2[0]}"
OWNER_AMM="${INFO_AMM[1]}"
OWNER_1="${INFO_1[1]}"
OWNER_2="${INFO_2[1]}"Now we have to publish and create the fungible applications. The flag --wait-for-outgoing-messages waits until a quorum of validators has confirmed that all sent cross-chain messages have been delivered.
(cd examples/fungible && cargo build --release --target wasm32-unknown-unknown)
FUN1_APP_ID=$(linera --wait-for-outgoing-messages \
publish-and-create examples/target/wasm32-unknown-unknown/release/fungible_{contract,service}.wasm \
--json-argument "{ \"accounts\": {
\"$OWNER_AMM\": \"100.\"
} }" \
--json-parameters "{ \"ticker_symbol\": \"FUN1\" }" \
)
FUN2_APP_ID=$(linera --wait-for-outgoing-messages \
publish-and-create examples/target/wasm32-unknown-unknown/release/fungible_{contract,service}.wasm \
--json-argument "{ \"accounts\": {
\"$OWNER_AMM\": \"100.\"
} }" \
--json-parameters "{ \"ticker_symbol\": \"FUN2\" }" \
)
(cd examples/amm && cargo build --release --target wasm32-unknown-unknown)
AMM_APPLICATION_ID=$(linera --wait-for-outgoing-messages \
publish-and-create examples/target/wasm32-unknown-unknown/release/amm_{contract,service}.wasm \
--json-parameters "{\"tokens\":["\"$FUN1_APP_ID\"","\"$FUN2_APP_ID\""]}" \
--required-application-ids $FUN1_APP_ID $FUN2_APP_ID)Using the AMM Application
First, a node service for the current wallet has to be started:
PORT=8080
linera service --port $PORT &Using GraphiQL
Type each of these in the GraphiQL interface and substitute the env variables with their actual
values that we've defined above.
To properly setup the tokens in the proper chains, we need to do some transfer operations:
- Transfer 50 FUN1 from
$OWNER_AMMin$CHAIN_AMMto$OWNER_1in$CHAIN_1, so they're in the proper chain.
Runecho "http://localhost:8080/chains/$CHAIN_AMM/applications/$FUN1_APP_ID"to print the URL
of the GraphiQL interface for the FUN1 app. Navigate to that URL and enter:
mutation {
transfer(
owner: "$OWNER_AMM",
amount: "50.",
targetAccount: {
chainId: "$CHAIN_1",
owner: "$OWNER_1",
}
)
}- Transfer 50 FUN1 from
$OWNER_AMMin$CHAIN_AMMto$OWNER_2in$CHAIN_2, so they're in the proper chain:
mutation {
transfer(
owner: "$OWNER_AMM",
amount: "50.",
targetAccount: {
chainId: "$CHAIN_2",
owner: "$OWNER_2",
}
)
}- Transfer 50 FUN2 from
$OWNER_AMMin$CHAIN_AMMto$OWNER_1in$CHAIN_1, so they're in the proper chain.
Since this is the other token, FUN2, we need to go to its own GraphiQL interface:echo "http://localhost:8080/chains/$CHAIN_AMM/applications/$FUN2_APP_ID".
mutation {
transfer(
owner: "$OWNER_AMM",
amount: "50.",
targetAccount: {
chainId: "$CHAIN_1",
owner: "$OWNER_1",
}
)
}- Transfer 50 FUN2 from
$OWNER_AMMin$CHAIN_AMMto$OWNER_2in$CHAIN_2, so they're in the proper chain:
mutation {
transfer(
owner: "$OWNER_AMM",
amount: "50.",
targetAccount: {
chainId: "$CHAIN_2",
owner: "$OWNER_2",
}
)
}All operations can only be from a remote chain i.e. other than the chain on which AMM is deployed to.
Before performing any operation we need to provide liquidity to it, so we will use the AddLiquidity operation,
navigate to the URL you get by running echo "http://localhost:8080/chains/$CHAIN_1/applications/$AMM_APPLICATION_ID".
To perform the AddLiquidity operation:
mutation {
addLiquidity(
owner: "$OWNER_1",
maxToken0Amount: "50",
maxToken1Amount: "50",
)
}To perform the Swap operation, navigate to the URL you get by running echo "http://localhost:8080/chains/$CHAIN_2/applications/$AMM_APPLICATION_ID" and
perform the following mutation:
mutation {
swap(
owner: "$OWNER_2",
inputTokenIdx: 1,
inputAmount: "1",
)
}To perform the RemoveLiquidity operation, navigate to the URL you get by running echo "http://localhost:8080/chains/$CHAIN_1/applications/$AMM_APPLICATION_ID" and
perform the following mutation:
mutation {
removeLiquidity(
owner: "$OWNER_1",
tokenToRemoveIdx: 1,
tokenToRemoveAmount: "1",
)
}To perform the RemoveAllAddedLiquidity operation, navigate to the URL you get by running echo "http://localhost:8080/chains/$CHAIN_1/applications/$AMM_APPLICATION_ID" and
perform the following mutation:
mutation {
removeAllAddedLiquidity(
owner: "$OWNER_1",
)
}Atomic Swaps
In general, if you send tokens to a chain owned by someone else, you rely on them
for asset availability: If they don't handle your messages, you don't have access to
your tokens.
Fortunately, Linera provides a solution based on temporary chains:
If the number of parties who want to swap tokens is limited, we can make them all chain
owners, allow only AMM operations on the chain, and allow only the AMM to close the chain.
In addition, we make an AMM operation per block mandatory, so owners cannot spam the chain
with empty blocks.
kill %% && sleep 1 # Kill the service so we can use CLI commands for chain 1.
linera --wait-for-outgoing-messages change-ownership \
--owners "{\"$OWNER_AMM\":100,\"$OWNER_2\":100}"
linera --wait-for-outgoing-messages change-application-permissions \
--execute-operations "[\"$AMM_APPLICATION_ID\"]" \
--mandatory-applications "[\"$AMM_APPLICATION_ID\"]" \
--manage-chain "[\"$AMM_APPLICATION_ID\"]"
linera service --port $PORT &First, let's add some liquidity again to the AMM. Navigate to the URL you get by runningecho "http://localhost:8080/chains/$CHAIN_1/applications/$AMM_APPLICATION_ID" and perform the following mutation:
mutation {
addLiquidity(
owner: "$OWNER_1",
maxToken0Amount: "40",
maxToken1Amount: "40",
)
}The only way to close the chain is via the application. Navigate to the URL you get by runningecho "http://localhost:8080/chains/$CHAIN_AMM/applications/$AMM_APPLICATION_ID" and perform the following mutation:
mutation { closeChain }Owner 1 should now get back their tokens, and have around 49 FUN1 left and 51 FUN2 left. To check that, navigate
to the URL you get by running echo "http://localhost:8080/chains/$CHAIN_1/applications/$FUN1_APP_ID", and perform the following mutation:
query {
accounts {
entry(
key: "$OWNER_1"
) {
value
}
}
}Then navigate to the URL you get by running echo "http://localhost:8080/chains/$CHAIN_1/applications/$FUN2_APP_ID", and perform the following mutation:
query {
accounts {
entry(
key: "$OWNER_1"
) {
value
}
}
}File: examples/counter/README.md
Counter Example Application
This example application implements a simple counter contract, it is initialized with an
unsigned integer that can be increased by the increment operation.
How It Works
It is a simple Linera application, which is initialized by a u64 which can be incremented
by a u64.
For example, if the contract was initialized with 1, querying the contract would give us 1. Now if we want toincrement it by 3, we will have to perform an operation with the parameter being 3. Now querying the
application would give us 4 (1+3 = 4).
Setup and Deployment
Before getting started, make sure that the binary tools linera* corresponding to
your version of linera-sdk are in your PATH. For scripting purposes, we also assume
that the BASH function linera_spawn is defined.
From the root of Linera repository, this can be achieved as follows:
export PATH="$PWD/target/debug:$PATH"
eval "$(linera net helper 2>/dev/null)"Next, start the local Linera network and run a faucet:
LINERA_FAUCET_PORT=8079
LINERA_FAUCET_URL=http://localhost:$LINERA_FAUCET_PORT
linera_spawn linera net up --with-faucet --faucet-port $LINERA_FAUCET_PORT
# If you're using a testnet, run this instead:
# LINERA_TMP_DIR=$(mktemp -d)
# export LINERA_FAUCET_URL=https://faucet.testnet-XXX.linera.net # for some value XXXEnable logs for user applications:
export LINERA_APPLICATION_LOGS=trueCreate the user wallet and add chains to it:
export LINERA_WALLET="$LINERA_TMP_DIR/wallet.json"
export LINERA_KEYSTORE="$LINERA_TMP_DIR/keystore.json"
export LINERA_STORAGE="rocksdb:$LINERA_TMP_DIR/client.db"
linera wallet init --faucet $LINERA_FAUCET_URL
INFO=($(linera wallet request-chain --faucet $LINERA_FAUCET_URL))
CHAIN="${INFO[0]}"
OWNER="${INFO[1]}"Now, compile the counter application WebAssembly binaries, publish and create an application instance.
cd examples/counter
cargo build --release --target wasm32-unknown-unknown
LINERA_APPLICATION_ID=$(linera publish-and-create \
../target/wasm32-unknown-unknown/release/counter_{contract,service}.wasm \
--json-argument "1")We have saved the LINERA_APPLICATION_ID as it will be useful later.
Connecting with the Web Frontend
If you install the dependencies for the Web frontend and export the
necessary variables you can run a local development server using Vite:
export LINERA_APPLICATION_ID LINERA_FAUCET_URL
pnpm install
pnpm devThis will start a server and print its address; access that URL to use
the application frontend.
Alternatively, there is a frontend in metamask that signs
transactions using the user's MetaMask wallet; it can be accessed the
same way.
Connecting with the GraphQL Client
Alternatively, you can connect to the application using the GraphQL
client. This is useful for inspecting and debugging the backend APIs.
First, a node service for the current wallet has to be started:
PORT=8080
linera service --port $PORT &
echo "http://localhost:8080/chains/$CHAIN/applications/$LINERA_APPLICATION_ID"Clicking the printed URL will take you to a
GraphiQL
environment connected to your app.
To get the current value of counter, you can run the query:
query {
value
}To increase the value of the counter, you can run the increment
mutation. Note that the result of a mutation is the hash of the
resulting block, if any.
mutation Increment {
increment(value: 3)
}If you run the query again, you'll now see a value of 4.
File: examples/counter-no-graphql/README.md
Counter No GraphQL Example Application
This example application implements a simple counter contract, it is initialized with an
unsigned integer that can be increased by the increment operation. In contrast with the
counter application, it works without GraphQL.
How It Works
It is a simple Linera application, which is initialized by a u64 which can be incremented
by a u64.
For example, if the contract was initialized with 1, querying the contract would give us 1. Now if we want toincrement it by 3, we will have to perform an operation with the parameter being 3. Now querying the
application would give us 4 (1+3 = 4).
Usage
File: examples/crowd-funding/README.md
Crowd-funding Example Application
This example application implements crowd-funding campaigns using fungible tokens in
the fungible application. This demonstrates how to compose applications together and
how to instantiate applications where one chain has a special role.
Once an application is built and its module published on a Linera chain, the
published module can be used to create different instances. Each instance or crowd-funding
represents a different campaign.
How It Works
The chain that created the campaign is called the "campaign chain". It is owned by the
creator (and beneficiary) of the campaign.
The goal of a crowd-funding campaign is to let people pledge any number of tokens from
their own chain(s). If enough tokens are pledged before the campaign expires, the campaign is
successful and the creator can receive all the funds, including ones exceeding the funding
target. Otherwise, the campaign is unsuccessful and contributors should be refunded.
Caveat
Currently, only the owner of the campaign can create blocks that contain the Cancel
operation. In the future, campaign chains will not be single-owner chains and should
instead allow contributors (users with a pledge) to cancel the campaign if
appropriate (even without the owner's cooperation).
Optionally, contributors may also be able to create a block to accept a new epoch
(i.e. a change of validators).
Usage
Setting Up
The WebAssembly binaries for the module can be built and published using steps from the
book,
summarized below.
Set up the path and the helper function. From the root of Linera repository, this can be
achieved as follows:
export PATH="$PWD/target/debug:$PATH"
source /dev/stdin <<<"$(linera net helper 2>/dev/null)"Start the local Linera network and run a faucet:
FAUCET_PORT=8079
FAUCET_URL=http://localhost:$FAUCET_PORT
linera_spawn linera net up --with-faucet --faucet-port $FAUCET_PORT
# If you're using a testnet, run this instead:
# LINERA_TMP_DIR=$(mktemp -d)
# FAUCET_URL=https://faucet.testnet-XXX.linera.net # for some value XXXEnable logs for user applications:
export LINERA_APPLICATION_LOGS=trueCreate the user wallets and add chains to them:
export LINERA_WALLET_1="$LINERA_TMP_DIR/wallet_1.json"
export LINERA_KEYSTORE_1="$LINERA_TMP_DIR/keystore_1.json"
export LINERA_STORAGE_1="rocksdb:$LINERA_TMP_DIR/client_1.db"
export LINERA_WALLET_2="$LINERA_TMP_DIR/wallet_2.json"
export LINERA_KEYSTORE_2="$LINERA_TMP_DIR/keystore_2.json"
export LINERA_STORAGE_2="rocksdb:$LINERA_TMP_DIR/client_2.db"
linera --with-wallet 1 wallet init --faucet $FAUCET_URL
linera --with-wallet 2 wallet init --faucet $FAUCET_URL
INFO_1=($(linera --with-wallet 1 wallet request-chain --faucet $FAUCET_URL))
INFO_2=($(linera --with-wallet 2 wallet request-chain --faucet $FAUCET_URL))
CHAIN_1="${INFO_1[0]}"
CHAIN_2="${INFO_2[0]}"
OWNER_1="${INFO_1[1]}"
OWNER_2="${INFO_2[1]}"Note that linera --with-wallet 1 is equivalent to linera --wallet "$LINERA_WALLET_1" --keystore "$LINERA_KEYSTORE_1" --storage "$LINERA_STORAGE_1".
The command below can be used to list the chains created for the test as known by each
wallet:
linera --with-wallet 1 wallet show
linera --with-wallet 2 wallet showA table will be shown with the chains registered in the wallet and their meta-data:
โญโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ
โ Chain Id โ Latest Block โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโชโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโก
โ 582843bc9322ed1928239ce3f6a855f6cd9ea94c8690907f113d6d7a8296a119 โ Public Key: 84eddaaafce7fb923c3b2494b3d25e54e910490a726ad9b3a2228d3fb18f9874 โ
โ โ Owner: 849baa540589d95e167d2622018fa341553bf2aff9f328d760443282c6654deb โ
โ โ Block Hash: - โ
โ โ Timestamp: 2023-06-28 09:53:51.167301 โ
โ โ Next Block Height: 0 โ
โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏThe default chain of each wallet should be highlighted in green. Each chain has anOwner field, and that is what is used for the account. Let's pick the owners of the
default chain of each wallet and call them $OWNER_1 and $OWNER_2. Remember the corresponding
chain IDs as $CHAIN_1 (the chain where we just published the application) and $CHAIN_2
(some user chain in wallet 2).
Creating tokens
Compile the Wasm binaries for the two applications fungible and crowd-funding, publish
them as applications, and give them initial states. This initial state is where the tokens
are minted. After the token is created, no additional tokens can be minted and added to
the application. The initial state is a JSON string that specifies the accounts that start
with tokens.
Create a fungible token application where two accounts start with the minted tokens, one
with 100 of them and another with 200 of them:
APP_ID_0=$(linera --with-wallet 1 project publish-and-create \
examples/fungible \
--json-argument '{ "accounts": { "'$OWNER_1'": "100", "'$OWNER_2'": "200" } }' \
--json-parameters "{ \"ticker_symbol\": \"FUN\" }")
# Wait for it to fully complete
sleep 8We will remember the application ID for the newly created token as $APP_ID_0.
Creating a crowd-funding campaign
Similarly, we're going to create a crowd-funding campaign on the default chain. We have
to specify our fungible application as a dependency and a parameter:
APP_ID_1=$(linera --with-wallet 1 \
project publish-and-create \
examples/crowd-funding \
crowd_funding \
--required-application-ids $APP_ID_0 \
--json-argument '{ "owner": "'$OWNER_1'", "deadline": 4102473600000000, "target": "100." }' \
--json-parameters '"'"$APP_ID_0"'"')
# Wait for it to fully complete
sleep 5Interacting with the campaign
First, a node service has to be started for each wallet, using two different ports:
linera --with-wallet 1 service --port 8080 &
# Wait for it to fully complete
sleep 2
linera --with-wallet 2 service --port 8081 &
# Wait for it to fully complete
sleep 2Type each of these in the GraphiQL interface and substitute the env variables with their actual values that we've defined above.
Point your browser to http://localhost:8080, and enter the query:
query { applications(
chainId: "$CHAIN_1"
) { id link } }The response will have two entries, one for each application.
On both http://localhost:8080 and http://localhost:8081, you recognize the crowd-funding
application by its ID. The entry also has a field link. If you open that in a new tab, you
see the GraphQL API for that application on that chain.
Let's pledge 30 tokens by the campaign creator themselves.
For $OWNER_1 on 8080, run echo "http://localhost:8080/chains/$CHAIN_1/applications/$APP_ID_1" to get the URL, open it
and run the following query:
mutation { pledge(
owner:"$OWNER_1",
amount:"30."
) }This will make the owner show up if we list everyone who has made a pledge so far:
query { pledges { keys } }To also have $OWNER_2 make a pledge, they first need to claim their tokens. Those are still
on the other chain, where the application was created. To get the link on 8081
for the fungible application, run echo "http://localhost:8081/chains/$CHAIN_2/applications/$APP_ID_0",
open it and run the following query:
mutation { claim(
sourceAccount: {
owner: "$OWNER_2",
chainId: "$CHAIN_1"
},
amount: "200.",
targetAccount: {
owner: "$OWNER_2",
chainId: "$CHAIN_2"
}
) }You can check that the 200 tokens have arrived:
query {
accounts { entry(key: "$OWNER_2") { value } }
}Now, also on 8081, you can open the link for the crowd-funding
application you get when you run echo "http://localhost:8081/chains/$CHAIN_2/applications/$APP_ID_1"
and run:
mutation { pledge(
owner:"$OWNER_2",
amount:"80."
) }This pledges another 80 tokens. With 110 pledged in total, we have now reached the campaign
goal. Now the campaign owner (on 8080) can collect the funds:
mutation { collect }Get the fungible application on
8080 URL by running echo "http://localhost:8080/chains/$CHAIN_1/applications/$APP_ID_0",
then check that we have received 110 tokens, in addition to the
70 that we had left after pledging 30 by running the following query:
query {
accounts { entry(key: "$OWNER_1") { value } }
}File: examples/fungible/web-frontend/README.md
Getting Started with the Fungible App.
This project was bootstrapped with Create React App.
The application (contract/service) must be deployed on the network, and a Node Service must be running
on port 8080.
Web UIs for specific accounts can be opened by navigating URLs of the formhttp://localhost:3000/$CHAIN?app=$APP_ID&owner=$OWNER&port=$PORT where
- the path is the ID of the chain where the account is located.
- the argument
appis the token application ID obtained when creating the token. owneris the address of the chosen user account (owner must have permissions to create blocks in the given chain).portis the port of the wallet service (the wallet must know the secret key ofowner).
Available Scripts
In the project directory, you can run:
`npm start`
Runs the app in the development mode.
Open http://localhost:3000 to view it in your browser.
The page will reload when you make changes.
You may also see any lint errors in the console.
`npx prettier --write .`
Formats the project using prettier.
`npm run compile`
Generates necessary graphql types codegen.ts
`npm run build`
Builds the app for production to the build folder.
It bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.
Your app is ready to be deployed!
See the section about deployment for more information.
`npm run eject`
Note: this is a one-way operation. Once you eject, you can't go back!
If you aren't satisfied with the build tool and configuration choices, you can eject at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except eject will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.
You don't have to ever use eject. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.
Learn More
To learn React, check out the React documentation.
File: examples/fungible/README.md
Fungible Token Example Application
This example application implements fungible tokens. This demonstrates in particular
cross-chain messages and how applications are instantiated and auto-deployed.
Once this application is built and its module published on a Linera chain, the
published module can be used to create multiple application instances, where each
instance represents a different fungible token.
How It Works
Individual chains have a set of accounts, where each account has an owner and a balance. The
same owner can have accounts on multiple chains, with a different balance on each chain. This
means that an account's balance is sharded across one or more chains.
There are two operations: Transfer and Claim. Transfer sends tokens from an account on the
chain where the operation is executed, while Claim sends a message from the current chain to
another chain in order to transfer tokens from that remote chain.
Tokens can be transferred from an account to different destinations, such as:
- other accounts on the same chain,
- the same account on another chain,
- other accounts on other chains.
Usage
Setting Up
The WebAssembly binaries for the module can be built and published using steps from the
book,
summarized below.
Before getting started, make sure that the binary tools linera* corresponding to
your version of linera-sdk are in your PATH. For scripting purposes, we also assume
that the BASH function linera_spawn is defined.
From the root of Linera repository, this can be achieved as follows:
export PATH="$PWD/target/debug:$PATH"
source /dev/stdin <<<"$(linera net helper 2>/dev/null)"Next, start the local Linera network and run a faucet:
FAUCET_PORT=8079
FAUCET_URL=http://localhost:$FAUCET_PORT
linera_spawn linera net up --with-faucet --faucet-port $FAUCET_PORT
# If you're using a testnet, run this instead:
# LINERA_TMP_DIR=$(mktemp -d)
# FAUCET_URL=https://faucet.testnet-XXX.linera.net # for some value XXXEnable logs for user applications:
export LINERA_APPLICATION_LOGS=trueCreate the user wallet and add chains to it:
export LINERA_WALLET="$LINERA_TMP_DIR/wallet.json"
export LINERA_KEYSTORE="$LINERA_TMP_DIR/keystore.json"
export LINERA_STORAGE="rocksdb:$LINERA_TMP_DIR/client.db"
linera wallet init --faucet $FAUCET_URL
INFO_1=($(linera wallet request-chain --faucet $FAUCET_URL))
CHAIN_1="${INFO_1[0]}"
OWNER_1="${INFO_1[1]}"
INFO_2=($(linera wallet request-chain --faucet $FAUCET_URL))
CHAIN_2="${INFO_2[0]}"
OWNER_2="${INFO_2[1]}"Now, compile the fungible application WebAssembly binaries, and publish them as an application
bytecode:
(cd examples/fungible && cargo build --release --target wasm32-unknown-unknown)
MODULE_ID=$(linera publish-module \
examples/target/wasm32-unknown-unknown/release/fungible_{contract,service}.wasm)Here, we stored the new module ID in a variable MODULE_ID to be reused it later.
Creating a Token
In order to use the published module to create a token application, the initial state must be
specified. This initial state is where the tokens are minted. After the token is created, no
additional tokens can be minted and added to the application. The initial state is a JSON string
that specifies the accounts that start with tokens.
In order to select the accounts to have initial tokens, the command below can be used to list
the chains created for the test in the default wallet:
linera wallet showA table will be shown with the chains registered in the wallet and their meta-data. The default
chain should be highlighted in green. Each chain has an Owner field, and that is what is used
for the account.
The example below creates a token application on the default chain CHAIN_1 and gives the owner 100 tokens:
APP_ID=$(linera create-application $MODULE_ID \
--json-argument "{ \"accounts\": {
\"$OWNER_1\": \"100.\"
} }" \
--json-parameters "{ \"ticker_symbol\": \"FUN\" }" \
)This will store the application ID in a new variable APP_ID.
Using the Token Application
Before using the token, a source and target address should be selected. The source address
should ideally be on the default chain (used to create the token) and one of the accounts chosen
for the initial state, because it will already have some initial tokens to send.
First, a node service for the current wallet has to be started:
PORT=8080
linera service --port $PORT &Using GraphiQL
Type each of these in the GraphiQL interface and substitute the env variables with their actual values that we've defined above.
- Navigate to the URL you get by running
echo "http://localhost:8080/chains/$CHAIN_1/applications/$APP_ID". - To get the current balance of user $OWNER_1, run the query:
query {
accounts {
entry(
key: "$OWNER_1"
) {
value
}
}
}- To get the current balance of user $OWNER_2, run the query:
query {
accounts {
entry(
key: "$OWNER_2"
) {
value
}
}
}- To transfer 50 tokens from $OWNER_1 to $OWNER_2
mutation {
transfer(
owner: "$OWNER_1",
amount: "50.",
targetAccount: {
chainId: "$CHAIN_1",
owner: "$OWNER_2"
}
)
}- To get the new balance of user $OWNER_1, run the query:
query {
accounts {
entry(
key: "$OWNER_1"
) {
value
}
}
}- To get the new balance of user $OWNER_2, run the query:
query {
accounts {
entry(
key: "$OWNER_2"
) {
value
}
}
}Using web frontend
Installing and starting the web server:
cd examples/fungible/web-frontend
npm install --no-save
# Start the server but not open the web page right away.
BROWSER=none npm start &Web UIs for specific accounts can be opened by navigating URLs of the formhttp://localhost:3000/$CHAIN?app=$APP_ID&owner=$OWNER&port=$PORT where
- the path is the ID of the chain where the account is located.
- the argument
appis the token application ID obtained when creating the token. owneris the address of the chosen user account (owner must have permissions to create blocks in the given chain).portis the port of the wallet service (the wallet must know the secret key ofowner).
In this example, two web pages for OWNER_1 and OWNER_2 can be opened by navigating these URLs:
echo "http://localhost:3000/$CHAIN_1?app=$APP_ID&owner=$OWNER_1&port=$PORT"
echo "http://localhost:3000/$CHAIN_1?app=$APP_ID&owner=$OWNER_2&port=$PORT"OWNER_2 doesn't have the applications loaded initially. Using the first page to
transfer tokens from OWNER_1 to OWNER_2 at CHAIN_2 will instantly update the UI of the
second page.
File: examples/gen-nft/web-frontend/README.md
Getting Started with the Gen-NFT App
This project was bootstrapped with Create React App.
The application (contract/service) must be deployed on the network, and a Node Service must be running
on port 8080.
Web UIs for specific accounts can be opened by navigating URLs of the formhttp://localhost:3000/$CHAIN?app=$APP_ID&owner=$OWNER&port=$PORT where
- the path is the ID of the chain where the account is located.
- the argument
appis the token application ID obtained when creating the token. owneris the address of the chosen user account (owner must have permissions to create blocks in the given chain).portis the port of the wallet service (the wallet must know the secret key ofowner).
Available Scripts
In the project directory, you can run:
`npm start`
Runs the app in the development mode.
Open http://localhost:3000 to view it in your browser.
The page will reload when you make changes.
You may also see any lint errors in the console.
`npm test`
Launches the test runner in the interactive watch mode.
See the section about running tests for more information.
`npm run build`
Builds the app for production to the build folder.
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.
Your app is ready to be deployed!
See the section about deployment for more information.
`npm run eject`
Note: this is a one-way operation. Once you eject, you can't go back!
If you aren't satisfied with the build tool and configuration choices, you can eject at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except eject will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.
You don't have to ever use eject. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.
Learn More
You can learn more in the Create React App documentation.
To learn React, check out the React documentation.
Code Splitting
This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting
Analyzing the Bundle Size
This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size
Making a Progressive Web App
This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app
Advanced Configuration
This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration
Deployment
This section has moved here: https://facebook.github.io/create-react-app/docs/deployment
`npm run build` fails to minify
This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify