Skip to content

Commit

Permalink
Merge pull request #8 from marigold-dev/quyen@staging
Browse files Browse the repository at this point in the history
Minting and stashing UX: add some visual feedback to the corresponding buttons.
  • Loading branch information
aguillon authored Jan 22, 2024
2 parents 9150161 + 36a91b2 commit e960a99
Show file tree
Hide file tree
Showing 7 changed files with 422 additions and 133 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
/examples/nft/node_modules/
/examples/nft/.svelte-kit/
node_modules
dist
dist
**/*d.ts
**/*.js
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# Tezos Gas Station library

This library helps you
- use [Marigold's Gas Station API](https://github.com/marigold-dev/gas-station) in TypeScript
- create [TZIP-17 permit contracts](https://tzip.tezosagora.org/proposal/tzip-17/), which are FA2
contracts that can be manipulated by a 3rd party (such as the gas station API). Permits are signed
This library facilitates the following:
- Utilizing [Marigold's Gas Station API](https://github.com/marigold-dev/gas-station) in TypeScript.
- Creating [TZIP-17 permit contracts](https://tzip.tezosagora.org/proposal/tzip-17/), which are FA2
contracts capable of being manipulated by a 3rd party, such as the gas station API. Permits are signed
off-chain and can be posted and executed by anyone.

A toy webapp example is available in the `examples/` directory.
An example of a toy web app is provided in the `examples/nft` directory.

Contributions welcome at https://github.com/marigold-dev/gas-station-lib.
Feel free to contribute and provide feedback on https://github.com/marigold-dev/gas-station-lib.
85 changes: 81 additions & 4 deletions examples/nft/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,82 @@
Toy example of a webapp using a gas station API and a permit contract. The webapp lets you mint a
NFT and transfer it to a “staking” contract even if you have no tez in your wallet.
# Gas Station Demo

The contracts are already deployed on Ghostnet. Their addresses, together with the API URL, can be
changed in the `.env` file.
This project is a toy web application demonstrating the use of a Gas Station API and a Permit Contract on the Tezos blockchain.

The web app allows you to mint a non-fungible token (NFT) and transfer it to a "staking" contract, even if you have no Tez in your wallet.


## Prerequisites

Before you start, ensure you have the following installed on your machine:

- Node.js [Download and Install Node.js](https://nodejs.org/)
- npm (Node Package Manager): Comes with Node.js installation
- NVM (Node Version Manager): This is the key component for managing Node.js versions.
To install NVM for managing Node.js versions, refer to the official installation guide
at [install](https://github.com/nvm-sh/nvm#installing-and-updating)

## Gas Station API and Permit Contract

This project interacts with a Gas Station API and a Permit Contract on the Tezos blockchain. The contracts are already deloyed on Ghostnet. Configure the follwing in the `.env` file:

- **Gas Station API URL**: the URL of the Gas Station API that handles the reply of operations to the blockchain.
- **Permit Contract Address**: the address of the Permit Contract on the Tezos blockchain. This contract is used for authorizing and signing operations.
- **Staking Contract Address (Optional)**: If applicable, the address of the Staking Contract where NFTs can be transferred.

**Note:** The contract addresses and API URL can be changed in the `.env` file.


## Configuration

Copy the `.env.example` file to `.env` and update the following variables based on your deployment:

```dotenv
PUBLIC_TZKT_API=https://api.ghostnet.tzkt.io
PUBLIC_TEZOS_RPC=https://ghostnet.smartpy.io
# Gas Station API URL
PUBLIC_GAS_STATION_API=http://localhost:8000
# Permit Contract Address
PUBLIC_PERMIT=KT1HUdxmgZUw21ED9gqELVvCty5d1ff41p7J
# Staking Contract Address (if applicable)
PUBLIC_STAKING_CONTRACT=KT1VVotciVbvz1SopVfoXsxXcpyBBSryQgEn
PUBLIC_APP_BASE_URL=http://localhost:5173
```

## Running locally

Navigate to the project folder:

```
cd examples/nft
```

To install dependencies, run:

```
npm install
```
or

```
npm i
```

To launch the project on your localhost using a development server, run:

```
npm run dev
```

Your project will be accessible at http://localhost:5173/

To clean up generated files, run:

```
rm -rf node_modules
```

Make sure to configure the `.env` file with the appropriate values before running the project locally.
121 changes: 86 additions & 35 deletions examples/nft/src/lib/MintingComponent.svelte
Original file line number Diff line number Diff line change
@@ -1,71 +1,117 @@
<script lang="ts">
import { Tezos, subTezos } from "$lib/tezos";
import { GasStation, GAS_STATION_PUBLIC_API_GHOSTNET } from "@marigold-dev/gas-station-lib";
import { PUBLIC_GAS_STATION_API, PUBLIC_PERMIT, PUBLIC_TZKT_API } from '$env/static/public';
import {
GasStation,
GAS_STATION_PUBLIC_API_GHOSTNET,
} from "@marigold-dev/gas-station-lib";
import {
PUBLIC_GAS_STATION_API,
PUBLIC_PERMIT,
PUBLIC_TZKT_API,
} from "$env/static/public";
export let user_address = '';
export let user_address = "";
export let available_token_ids = new Set<string>(); // to be shared with StakingComponent
let user_tokens: any[] = [];
/*** Generate a random integer up to a given maximum value.
* It uses to generate a random token ID when minting. */
function randomInt(max) {
return Math.floor(Math.random() * max);
}
// Converts an IPFS link to its HTTPs counterpart
function IPFSLinkToHTTPS(url: string) {
return url.replace("ipfs://", "https://ipfs.io/ipfs/");
}
function get_tokens(user_address: string) {
return fetch(`${PUBLIC_TZKT_API}/v1/tokens/balances?account=${user_address}&token.contract=${PUBLIC_PERMIT}&balance.gt=0`)
return fetch(
`${PUBLIC_TZKT_API}/v1/tokens/balances?account=${user_address}&token.contract=${PUBLIC_PERMIT}&balance.gt=0`,
)
.then((response) => {
return response.json();
})
.then((fa2_tokens) => {
user_tokens = fa2_tokens;
});
};
}
// Add minting progress
let mintingProgress = 0;
let isMinting = false;
/** This function is triggered when the "Mint" button is clicked.
* It initiates the minting process, interacting with the permit contract
* and gas station API. */
function mint(user_address: string) {
const token_id = randomInt(6);
(async () => {
const gas_api = new GasStation({
apiURL: PUBLIC_GAS_STATION_API
});
const contract = await Tezos.wallet.at(PUBLIC_PERMIT);
const mint_op = await contract.methodsObject.mint_token([{
(async () => {
isMinting = true;
try {
const gas_api = new GasStation({
apiURL: PUBLIC_GAS_STATION_API,
});
const contract = await Tezos.wallet.at(PUBLIC_PERMIT);
const mint_op = await contract.methodsObject
.mint_token([
{
owner: user_address,
token_id: token_id,
amount_: 1
}]).toTransferParams()
console.log(mint_op);
const response = await gas_api.postOperation(user_address, {
destination: mint_op.to,
parameters: mint_op.parameter
});
console.log(response);
// √ construct mint operation from Taquito
// √ jsonize
// √ const reponse = await fetch("http://127.0.0.1/operation")
// √ display balance
// next: randomize (on the server side) the NFT we get
// display an error message if we didn't wait for long enough
})();
amount_: 1,
},
])
.toTransferParams();
mintingProgress = 50; // Set progress to 50% after Taquito operation
const response = await gas_api.postOperation(user_address, {
destination: mint_op.to,
parameters: mint_op.parameter,
});
mintingProgress = 100; // Set progress to 100% after gas station operation
} catch (error) {
console.error("Minting failed:", error);
mintingProgress = 0; // Reset progress on failure
} finally {
isMinting = false;
}
// √ construct mint operation from Taquito
// √ jsonize
// √ const reponse = await fetch("http://127.0.0.1/operation")
// √ display balance
// next: randomize (on the server side) the NFT we get
// display an error message if we didn't wait for long enough
})();
}
subTezos(() => {
get_tokens(user_address)
get_tokens(user_address);
});
// Maintain the set of available token IDs to pick one in the stash operation
$: available_token_ids = new Set(user_tokens.map(token => token.token.tokenId));
$: available_token_ids = new Set(
user_tokens.map((token) => token.token.tokenId),
);
</script>

<div style="display: flex">
<div>
<button on:click={() => mint(user_address)}>
mint
<button on:click={() => mint(user_address)} disabled={isMinting}
>{isMinting ? "Minting..." : "Mint"}
</button>
{#if isMinting}
<div class="progress-bar">
<div
class="progress-bar-fill"
style={`width: ${mintingProgress}%`}
></div>
</div>
{/if}
</div>

<div>
Expand All @@ -74,16 +120,21 @@
{:else}
<div style="display:flex;align-items:center;justify-content:center;">
{#each user_tokens as token, i}
<div style="display:flex;flex-direction:column;justify-content:center;align-items:center;">
<div
style="display:flex;flex-direction:column;justify-content:center;align-items:center;"
>
{#if Object.hasOwn(token.token, "metadata")}
<img src="{IPFSLinkToHTTPS(token.token.metadata.thumbnailUri)}" alt="Token thumnail"/>
<div style="text-align: center; font-size:14px">{token.balance}</div>
<img
src={IPFSLinkToHTTPS(token.token.metadata.thumbnailUri)}
alt="Token thumnail"
/>
<div style="text-align: center; font-size:14px">
{token.balance}
</div>
{/if}
</div>
{/each}
</div>
{/if}
</div>
</div>


Loading

0 comments on commit e960a99

Please sign in to comment.