Skip to content

Commit

Permalink
feat: add the option to mint public notes with the faucet (#339)
Browse files Browse the repository at this point in the history
  • Loading branch information
tomyrd authored May 3, 2024
1 parent 318e41d commit 33ad0ef
Show file tree
Hide file tree
Showing 9 changed files with 123 additions and 29 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## 0.3.0 (TBD)

* Added option to mint pulic notes in the faucet (#339).
* Renamed `note_hash` into `note_id` in the database (#336)
* Changed `version` and `timestamp` fields in `Block` message to `u32` (#337).
* [BREAKING] Implemented `NoteMetadata` protobuf message (#338).
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ authors = ["Miden contributors"]
homepage = "https://polygon.technology/polygon-miden"
repository = "https://github.com/0xPolygonMiden/miden-node"
exclude = [".github/"]
readme = "README.md"

[workspace.dependencies]
miden-air = { version = "0.9", default-features = false }
Expand Down
2 changes: 1 addition & 1 deletion bin/faucet/miden-faucet.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ database_filepath = "store.sqlite3"
node_url = "http://localhost:57291"

# Data used to construct the faucet account of the faucet
asset_amount = 333
asset_amount_options = [100, 500, 1000]
token_symbol = "POL"
decimals = 8
max_supply = 1000000
8 changes: 4 additions & 4 deletions bin/faucet/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ pub struct FaucetConfig {
pub node_url: String,
/// Location to store database files
pub database_filepath: PathBuf,
/// Amount of asset that should be dispered on each faucet request
pub asset_amount: u64,
/// Possible options on the amount of asset that should be dispered on each faucet request
pub asset_amount_options: Vec<u64>,
/// Token symbol of the generated fungible asset
pub token_symbol: String,
/// Number of decimals of the generated fungible asset
Expand All @@ -36,8 +36,8 @@ impl FaucetConfig {
impl Display for FaucetConfig {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!(
"{{ endpoint: \"{}\", database_filepath: {:?}, asset_amount: {}, token_symbol: {}, decimals: {}, max_supply: {} }}",
self.endpoint, self.database_filepath, self.asset_amount, self.token_symbol, self.decimals, self.max_supply
"{{ endpoint: \"{}\", database_filepath: {:?}, asset_amount_options: {:?}, token_symbol: {}, decimals: {}, max_supply: {} }}",
self.endpoint, self.database_filepath, self.asset_amount_options, self.token_symbol, self.decimals, self.max_supply
))
}
}
25 changes: 20 additions & 5 deletions bin/faucet/src/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,21 @@ use crate::{errors::FaucetError, utils::FaucetState};
#[derive(Deserialize)]
struct FaucetRequest {
account_id: String,
is_private_note: bool,
asset_amount: u64,
}

#[derive(Serialize)]
struct FaucetMetadataReponse {
id: String,
asset_amount: u64,
asset_amount_options: Vec<u64>,
}

#[get("/get_metadata")]
pub async fn get_metadata(state: web::Data<FaucetState>) -> HttpResponse {
let response = FaucetMetadataReponse {
id: state.id.to_string(),
asset_amount: state.asset_amount,
asset_amount_options: state.asset_amount_options.clone(),
};

HttpResponse::Ok().json(response)
Expand All @@ -39,7 +41,15 @@ pub async fn get_tokens(
req: web::Json<FaucetRequest>,
state: web::Data<FaucetState>,
) -> Result<HttpResponse> {
info!("Received a request with account_id: {}", req.account_id);
info!(
"Received a request with account_id: {}, is_private_note: {}, asset_amount: {}",
req.account_id, req.is_private_note, req.asset_amount
);

// Check that the amount is in the asset amount options
if !state.asset_amount_options.contains(&req.asset_amount) {
return Err(FaucetError::BadRequest("Invalid asset amount.".to_string()).into());
}

let client = state.client.clone();

Expand All @@ -48,11 +58,15 @@ pub async fn get_tokens(
.map_err(|err| FaucetError::BadRequest(err.to_string()))?;

// Instantiate asset
let asset = FungibleAsset::new(state.id, state.asset_amount)
let asset = FungibleAsset::new(state.id, req.asset_amount)
.map_err(|err| FaucetError::InternalServerError(err.to_string()))?;

// Instantiate note type
let note_type = NoteType::OffChain;
let note_type = if req.is_private_note {
NoteType::OffChain
} else {
NoteType::Public
};

// Instantiate transaction template
let tx_template = TransactionTemplate::MintFungibleAsset(asset, target_account_id, note_type);
Expand Down Expand Up @@ -108,5 +122,6 @@ pub async fn get_tokens(
"note.mno".to_string(),
)],
})
.append_header(("Note-Id", note_id.to_string()))
.body(bytes))
}
50 changes: 46 additions & 4 deletions bin/faucet/src/static/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ body {
-webkit-font-smoothing: antialiased;
height: 100vh;
display: flex;
flex-direction: column;
padding-top: 100px;
justify-content: center;
align-items: center;
background-image: url(./background.png);
Expand Down Expand Up @@ -67,24 +69,64 @@ body {
margin-bottom: 20px;
}

#form-container {
display: flex;
flex-direction: row;
align-items: center;
}

#account-id {
padding: 10px;
border-radius: 10px;
border: 1px solid #ccc;
margin-bottom: 30px;
width: 300px;
margin-bottom: 10px;
width: 200px;
}

#asset-amount {
padding: 10px;
border-radius: 10px;
border: 1px solid #ccc;
margin-bottom: 10px;
width: 95px;
background-color: white;
margin-left: 5px;
text-align: center;
}

#faucetId {
color: white;
margin-top: 4px;
}

#button {
#visibility-buttons button{
color: white;
border-radius: 10px;
font-weight: bold;
padding: 10px 20px;
padding: 10px;
background-color: rgb(124, 58, 237);
width: 300px;
margin-top: 10px;
}

#info-message {
color: white;
font-weight: bold;
text-align: center;
}

#info-container {
display: none;
background-color: rgb(17, 24, 39);
border-radius: 10px;
padding: 20px;
margin-top: 25px;
}

#client-commands {
background-color: black;
color: lightgray;
font-family: Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New, monospace;
padding: 10px;
border: 1px solid #ccc;
}
19 changes: 17 additions & 2 deletions bin/faucet/src/static/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,23 @@ <h1 id="title">Miden Faucet</h1>
<div id="center-container">
<h2 id="subtitle">Request test tokens</h2>
<span id="error-message"></span>
<input type="text" id="account-id" placeholder="Hex encoded Account id" required>
<button id="button"></button>
<div id="form-container">
<input type="text" id="account-id" placeholder="Hex encoded Account id" required>
<select id="asset-amount"></select>
</div>
<div id="visibility-buttons">
<button id="button-private">Send Private Note</button>
<br>
<button id="button-public">Send Public Note</button>
</div>
</div>
<div id="info-container">
<p id="info-message">You can consume the note by running the following commands in your shell</p>
<p id="client-commands">
<span id="import-command" style="display: none;">miden-client input-notes import ./path/to/downloaded/note<br></span>
miden-client sync <br>
miden-client tx new consume-notes --account <span id="command-account-id"></span> <span id="note-id"></span>
</p>
</div>
<script src="./index.js"></script>
</body>
Expand Down
42 changes: 31 additions & 11 deletions bin/faucet/src/static/index.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,41 @@
document.addEventListener('DOMContentLoaded', function () {
const faucetIdElem = document.getElementById('faucetId');
const button = document.getElementById('button');
const privateButton = document.getElementById('button-private');
const publicButton = document.getElementById('button-public');
const accountIdInput = document.getElementById('account-id');
const errorMessage = document.getElementById('error-message');
const infoContainer = document.getElementById('info-container');
const importCommand = document.getElementById('import-command');
const noteIdElem = document.getElementById('note-id');
const accountIdElem = document.getElementById('command-account-id');
const assetSelect = document.getElementById('asset-amount');

fetchMetadata();

button.addEventListener('click', handleButtonClick);
privateButton.addEventListener('click', () => {handleButtonClick(true)});
publicButton.addEventListener('click', () => {handleButtonClick(false)});

function fetchMetadata() {
fetch('http://localhost:8080/get_metadata')
.then(response => response.json())
.then(data => {
faucetIdElem.textContent = data.id;
button.textContent = `Send me ${data.asset_amount} tokens!`;
button.dataset.originalText = button.textContent;
for (const amount of data.asset_amount_options){
const option = document.createElement('option');
option.value = amount;
option.textContent = amount;
assetSelect.appendChild(option);
}
})
.catch(error => {
console.error('Error fetching metadata:', error);
faucetIdElem.textContent = 'Error loading Faucet ID.';
button.textContent = 'Error retrieving Faucet asset amount.';
errorMessage.textContent = 'Failed to load metadata. Please try again.';
errorMessage.style.display = 'block';
});
}

async function handleButtonClick() {
async function handleButtonClick(isPrivateNote) {
let accountId = accountIdInput.value.trim();
errorMessage.style.display = 'none';

Expand All @@ -33,26 +45,34 @@ document.addEventListener('DOMContentLoaded', function () {
return;
}

button.textContent = 'Loading...';
infoContainer.style.display = 'none';
importCommand.style.display = 'none';

try {
const response = await fetch('http://localhost:8080/get_tokens', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ account_id: accountId })
body: JSON.stringify({ account_id: accountId, is_private_note: isPrivateNote, asset_amount: parseInt(assetSelect.value)})
});

if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}

const blob = await response.blob();
downloadBlob(blob, 'note.mno');
if(isPrivateNote) {
importCommand.style.display = 'block';
downloadBlob(blob, 'note.mno');
}

const noteId = response.headers.get('Note-Id');
noteIdElem.textContent = noteId;
accountIdElem.textContent = accountId;
infoContainer.style.display = 'block';
} catch (error) {
console.error('Error:', error);
errorMessage.textContent = 'Failed to receive tokens. Please try again.';
errorMessage.style.display = 'block';
} finally {
button.textContent = button.dataset.originalText;
}
}

Expand Down
4 changes: 2 additions & 2 deletions bin/faucet/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ pub type FaucetClient = Client<TonicRpcClient, RpoRandomCoin, SqliteStore>;
#[derive(Clone)]
pub struct FaucetState {
pub id: AccountId,
pub asset_amount: u64,
pub asset_amount_options: Vec<u64>,
pub client: Arc<Mutex<FaucetClient>>,
}

Expand All @@ -45,7 +45,7 @@ pub async fn build_faucet_state(config: FaucetConfig) -> Result<FaucetState, Fau

Ok(FaucetState {
id: faucet_account.id(),
asset_amount: config.asset_amount,
asset_amount_options: config.asset_amount_options,
client: Arc::new(Mutex::new(client)),
})
}
Expand Down

0 comments on commit 33ad0ef

Please sign in to comment.