examples/gen-nft/README.md
This example application implements generative non-fungible tokens (NFTs). It is based on the Non-Fungible Token Example Application, with the difference being that instead of representing the token payload as an arbitrary sequence of bytes, it is represented as a string generated by a large-language model.
The main purpose of this application is to show how to run an artificial intelligence model in the application service, allowing it to generate strings used as the contents of non-fungible tokens. The application service runs on the client (usually inside the wallet), which means it executes off-chain. In this example, it is used to prepare the mint operation by running the AI model to generate the token contents from a user-provided prompt. The resulting operation can then be placed by the wallet in a block proposal, minting the token on-chain.
This application's contract is mostly identical to the one from the NFT Example application. The only difference is the change of the token payload to be a text string instead of an arbitrary sequence of bytes. Like the NFT example application, this also shows cross-chain messages, demonstrating how NFTs can be minted, transferred, and claimed across different chains, emphasizing the instantiation and auto-deployment of application instances within the Linera blockchain ecosystem.
For more details on the application's contract and how it manages multiple different NFTs, see the NFT example.
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 XXX
Enable logs for user applications:
export LINERA_APPLICATION_LOGS=true
Create 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))
INFO_2=($(linera 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]}"
Next, compile the non-fungible application WebAssembly binaries, and publish them as an application module:
(cd examples/gen-nft && cargo build --release --target wasm32-unknown-unknown)
MODULE_ID=$(linera publish-module \
examples/target/wasm32-unknown-unknown/release/gen_nft_{contract,service}.wasm)
Here, we stored the new module ID in a variable MODULE_ID to be reused later.
Unlike fungible tokens, each NFT is unique and identified by a unique token ID. Also unlike fungible tokens, when creating the NFT application you don't need to specify an initial state or parameters. NFTs will be minted later.
Refer to the fungible app README to figure out how to list the chains created for the test in the default wallet, as well as defining some variables corresponding to these values.
To create the NFT application, run the command below:
APP_ID=$(linera create-application $MODULE_ID)
This will store the application ID in a new variable APP_ID.
Operations such as minting NFTs, transferring NFTs, and claiming NFTs from other chains follow a similar approach to fungible tokens, with adjustments for the unique nature of NFTs.
First, a node service for the current wallet has to be started:
PORT=8080
linera service --port $PORT &
Type each of these in the GraphiQL interface and substitute the env variables with their actual values that we've defined above.
echo "http://localhost:8080/chains/$CHAIN_1/applications/$APP_ID".mutation {
mint(
minter: "$OWNER_1",
prompt: "Hello!"
)
}
query {
ownedNfts(owner: "$OWNER_1")
}
Set the QUERY_RESULT variable to have the result returned by the previous query, and TOKEN_ID will be properly set for you.
Alternatively you can set the TOKEN_ID variable to the tokenId value returned by the previous query yourself.
TOKEN_ID=$(echo "$QUERY_RESULT" | jq -r '.ownedNfts[].tokenId')
query {
nft(tokenId: "$TOKEN_ID") {
tokenId,
owner,
prompt,
minter,
}
}
query {
nfts
}
$OWNER_2, still on chain $CHAIN_1, run the query:mutation {
transfer(
sourceOwner: "$OWNER_1",
tokenId: "$TOKEN_ID",
targetAccount: {
chainId: "$CHAIN_1",
owner: "$OWNER_2"
}
)
}
Installing and starting the web server:
cd examples/gen-nft/web-frontend
npm install --no-save
# Start the server but do not open the web page right away.
BROWSER=none npm start &
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"
For the final part, refer to Fungible Token Example Application - Using web frontend.