Update pages

This commit is contained in:
Yurii Didyk 2022-06-03 17:33:28 +03:00
parent 1e9d5b0243
commit 84214e8999
19 changed files with 644 additions and 485 deletions

View File

@ -0,0 +1,6 @@
namespace NftFaucet.Models.Enums;
public enum NetworkType
{
Ethereum,
Solana
}

View File

@ -4,6 +4,4 @@ public enum TokenType : byte
{
ERC721 = 0,
ERC1155 = 1,
SolanaDevnet = 2,
SolanaTestnet = 3
}

View File

@ -1,4 +1,5 @@
using CSharpFunctionalExtensions;
using System.Text.RegularExpressions;
using CSharpFunctionalExtensions;
using Nethereum.Hex.HexConvertors.Extensions;
using Nethereum.Util;
@ -16,9 +17,16 @@ public class SolanaAddress : ValueObject<SolanaAddress>
public static implicit operator string(SolanaAddress address) => address.Value;
public static explicit operator SolanaAddress(string address) => Create(address).Value;
public static Result<SolanaAddress> Create(string address)
public static Result<SolanaAddress> Create(string value)
{
return new SolanaAddress(address);
var regex = "[1-9A-HJ-NP-Za-km-z]";
if (!Regex.IsMatch(value, regex))
{
return Result.Failure<SolanaAddress>("Invalid base58 string");
}
return new SolanaAddress(value);
}
public override string ToString() => Value;

View File

@ -16,4 +16,9 @@ public class StateStorage
public string TokenMetadata { get; set; }
public string TokenUrl { get; set; }
public string DestinationAddress { get; set; }
public NetworkType NetworkType { get; set; }
public EthereumNetwork Network { get; set; }
public string TokenSymbol { get; set; } = "DFNT";
public bool IsTokenMutable { get; set; } = true;
public double SellerFeeBasisPoints { get; set; } = 88;
}

View File

@ -1,4 +1,4 @@
@page "/step1"
@page "/step1"
@using NftFaucet.Models.Enums
@using Microsoft.AspNetCore.Components
@inherits Step1Component
@ -7,107 +7,28 @@
<SpaceItem Class="drk-full-width">
<Space Direction="DirectionVHType.Horizontal" Class="drk-vertical-space-center">
<SpaceItem>
<FileDropZone class="drop-zone">
<Upload Name="file" Class="@ImageClass" ListType="picture-card"
ShowUploadList="false" BeforeAllUploadAsync="BeforeUpload">
@if (AppState?.Storage?.UploadIsInProgress ?? false)
{
<div>
<Icon Spin="true" Type="loading" Style="font-size: 2rem;"></Icon>
<Title Level="4">Uploading...</Title>
</div>
}
else if (AppState?.Storage?.LocalImageUrl != null && AppState.Storage.CanPreviewTokenFile)
{
<img src="@AppState?.Storage?.LocalImageUrl" alt="avatar" style="width: 100%"/>
}
else if (AppState?.Storage?.LocalImageUrl != null)
{
<div>
<Icon Type="eye-invisible" Style="font-size: 2rem;"></Icon>
<Title Level="4">Unable to preview</Title>
</div>
}
else
{
<div>
<Icon Type="plus" Style="font-size: 2rem;"></Icon>
<Title Level="4">Choose or drag & drop file</Title>
</div>
}
</Upload>
</FileDropZone>
<Button Type="@ButtonType.Primary" Size="large" OnClick="OnEthereumSelected">
<div>Ethereum</div>
</Button>
<Button Type="@ButtonType.Primary" Size="large" OnClick="OnSolanaSelected">
<div>Solana</div>
</Button>
</SpaceItem>
<SpaceItem Class="drk-grow">
<Space Align="start" Direction="DirectionVHType.Vertical" Class="drk-vertical-space">
<SpaceItem Class="drk-full-width">
<Space Align="start" Direction="DirectionVHType.Vertical" Class="drk-full-width">
<SpaceItem>
<Title Level="4" Style="margin-bottom: 0;">Name:</Title>
</SpaceItem>
<SpaceItem Class="drk-full-width">
<div class="@NameClass">
<Input Size="medium" @bind-Value="@AppState.Storage.TokenName" OnInput="@OnNameInputChange" />
</div>
</SpaceItem>
</Space>
</SpaceItem>
<SpaceItem Class="drk-full-width">
<Space Align="start" Direction="DirectionVHType.Vertical" Class="drk-full-width">
<SpaceItem>
<Title Level="4" Style="margin-bottom: 0;">Description:</Title>
</SpaceItem>
<SpaceItem Class="drk-full-width">
<div class="@DescriptionClass">
<TextArea ShowCount MaxLength="255" MinRows="3" MaxRows="5" OnInput="@OnDescriptionInputChange" @bind-Value="@AppState.Storage.TokenDescription" />
</div>
</SpaceItem>
</Space>
</SpaceItem>
<SpaceItem Class="drk-full-width">
<Space Align="start" Direction="DirectionVHType.Vertical" Class="drk-full-width">
<SpaceItem>
<Title Level="4" Style="margin-bottom: 0;">IPFS Gateway:</Title>
</SpaceItem>
<SpaceItem>
<Select DataSource="@IpfsGateways"
DefaultValue="@(nameof(IpfsGatewayType.Infura))"
ValueName="@nameof(EnumWrapper<IpfsGatewayType>.ValueString)"
LabelName="@nameof(EnumWrapper<IpfsGatewayType>.Description)"
OnSelectedItemChanged="OnIpfsGatewayChange">
</Select>
</SpaceItem>
</Space>
</SpaceItem>
<SpaceItem Class="drk-full-width">
<Space Align="start" Direction="DirectionVHType.Vertical" Class="drk-full-width">
<SpaceItem>
<Title Level="4" Style="margin-bottom: 0;">Token type:</Title>
</SpaceItem>
<SpaceItem>
<Select DataSource="@TokenTypes"
DefaultValue="@(nameof(TokenType.ERC721))"
ValueName="@nameof(EnumWrapper<TokenType>.ValueString)"
LabelName="@nameof(EnumWrapper<TokenType>.Description)"
OnSelectedItemChanged="OnTokenTypeChange">
</Select>
</SpaceItem>
</Space>
</SpaceItem>
<SpaceItem Class="drk-full-width">
<Space Align="start" Direction="DirectionVHType.Vertical" Class="drk-full-width">
<SpaceItem>
<Title Level="4" Style="margin-bottom: 0;">Amount:</Title>
</SpaceItem>
<SpaceItem>
<div>
<AntDesign.InputNumber @bind-Value="@AppState.Storage.TokenAmount" Disabled="@(AppState.Storage.TokenType == TokenType.ERC721)" Min="1" Max="1000" DefaultValue="1"></AntDesign.InputNumber>
</div>
</SpaceItem>
</Space>
</SpaceItem>
</Space>
</Space>
<Space Direction="DirectionVHType.Horizontal" Class="drk-vertical-space-center">
<SpaceItem>
@if (AppState.Storage.NetworkType == NetworkType.Solana)
{
<Select DataSource="@ChainTypes"
DefaultValue="@(nameof(EthereumNetwork.SolanaDevnet))"
ValueName="@nameof(EnumWrapper<EthereumNetwork>.ValueString)"
LabelName="@nameof(EnumWrapper<EthereumNetwork>.Description)"
OnSelectedItemChanged="OnNetworkChange">
</Select>
}
</SpaceItem>
</Space>
</SpaceItem>
</Space>

View File

@ -1,125 +1,34 @@
using AntDesign;
using Microsoft.AspNetCore.Components;
using NftFaucet.Components;
using NftFaucet.Constants;
using NftFaucet.Extensions;
using NftFaucet.Components;
using NftFaucet.Models.Enums;
namespace NftFaucet.Pages;
public class Step1Component : BasicComponent
{
protected string NameErrorMessage { get; set; }
protected string DescriptionErrorMessage { get; set; }
protected string ImageErrorMessage { get; set; }
protected string NameClass => string.IsNullOrWhiteSpace(NameErrorMessage) ? null : "invalid-input";
protected string DescriptionClass => string.IsNullOrWhiteSpace(DescriptionErrorMessage) ? null : "invalid-input";
protected string ImageClass => "file-uploader" + (string.IsNullOrWhiteSpace(ImageErrorMessage) ? string.Empty : " invalid-input");
protected EnumWrapper<NetworkType>[] NetworkTypes { get; } = Enum.GetValues<NetworkType>()
.Select(x => new EnumWrapper<NetworkType>(x, x.ToString())).ToArray();
protected EnumWrapper<IpfsGatewayType>[] IpfsGateways { get; } = Enum.GetValues<IpfsGatewayType>()
.Select(x => new EnumWrapper<IpfsGatewayType>(x, x.ToString())).ToArray();
protected EnumWrapper<EthereumNetwork>[] ChainTypes { get; } = new List<EthereumNetwork>() {EthereumNetwork.SolanaTestnet, EthereumNetwork.SolanaDevnet, EthereumNetwork.SolanaMainnet}
.Select(x => new EnumWrapper<EthereumNetwork>(x, x.ToString())).ToArray();
protected EnumWrapper<TokenType>[] TokenTypes { get; } = Enum.GetValues<TokenType>()
.Select(x => new EnumWrapper<TokenType>(x, x.ToString())).ToArray();
protected override async Task OnInitializedAsync()
protected void OnEthereumSelected()
{
if (!await AppState.Metamask.IsReady() || !AppState.IpfsContext.IsInitialized)
UriHelper.NavigateToRelative("/");
AppState.Storage.NetworkType = NetworkType.Ethereum;
AppState.Navigation.SetForwardHandler(ForwardHandler);
AppState.Navigation.GoForward();
}
protected Task<bool> ForwardHandler()
protected void OnSolanaSelected()
{
var isValidName = !string.IsNullOrWhiteSpace(AppState.Storage.TokenName);
var isValidDescription = !string.IsNullOrWhiteSpace(AppState.Storage.TokenDescription);
var isValidFile = AppState.Storage.IpfsImageUrl != null;
var isNotUploading = !AppState.Storage.UploadIsInProgress;
if (!isValidName)
{
NameErrorMessage = "Invalid name";
}
if (!isValidDescription)
{
DescriptionErrorMessage = "Invalid description";
}
if (!isValidFile)
{
ImageErrorMessage = "Invalid file";
}
if (!isNotUploading)
{
ImageErrorMessage = "Upload is still in progress";
}
AppState.Storage.NetworkType = NetworkType.Solana;
}
protected void OnNetworkChange(EnumWrapper<EthereumNetwork> network)
{
AppState.Storage.Network = network.Value;
RefreshMediator.NotifyStateHasChangedSafe();
var canProceed = isValidName && isValidDescription && isValidFile && isNotUploading;
return Task.FromResult(canProceed);
}
protected void OnNameInputChange(ChangeEventArgs args)
{
NameErrorMessage = string.Empty;
}
protected void OnDescriptionInputChange(ChangeEventArgs args)
{
DescriptionErrorMessage = string.Empty;
}
protected void OnIpfsGatewayChange(EnumWrapper<IpfsGatewayType> ipfsGatewayItem)
{
AppState.Storage.IpfsGatewayType = ipfsGatewayItem.Value;
}
protected void OnTokenTypeChange(EnumWrapper<TokenType> tokenTypeItem)
{
AppState.Storage.TokenType = tokenTypeItem.Value;
if (AppState.Storage.TokenType == TokenType.ERC721)
{
AppState.Storage.TokenAmount = 1;
}
RefreshMediator.NotifyStateHasChangedSafe();
}
protected async Task<bool> BeforeUpload(List<UploadFileItem> files)
{
var file = files.FirstOrDefault();
if (file == null)
{
return false;
}
var hasValidSize = file.Size < UploadConstants.MaxFileSizeInBytes;
if (!hasValidSize)
{
MessageService.Error($"File must be smaller than {UploadConstants.MaxFileSizeInMegabytes} MB!");
return false;
}
ImageErrorMessage = string.Empty;
AppState.Storage.UploadIsInProgress = true;
RefreshMediator.NotifyStateHasChangedSafe();
AppState.Storage.IpfsImageUrl = await IpfsService.Upload(file.FileName, file.Type, file.ObjectURL);
AppState.Storage.LocalImageUrl = new Uri(file.ObjectURL);
ImageErrorMessage = string.Empty;
AppState.Storage.UploadIsInProgress = false;
AppState.Storage.CanPreviewTokenFile = file.IsPicture();
if (!AppState.Storage.CanPreviewTokenFile)
{
MessageService.Warning("Can't preview this file. Tho you can still mint a NFT with it.");
}
RefreshMediator.NotifyStateHasChangedSafe();
return false;
AppState.Navigation.GoForward();
}
}

View File

@ -1,13 +1,162 @@
@page "/step2"
@using NftFaucet.Models.Enums
@using Microsoft.AspNetCore.Components
@inherits Step2Component
<Space Align="start" Direction="DirectionVHType.Vertical" Class="drk-full-width">
<SpaceItem>
<Title Level="4" Style="margin-bottom: 0;">Token metadata:</Title>
</SpaceItem>
<Space Align="center" Direction="DirectionVHType.Vertical" Class="drk-vertical-space-center">
<SpaceItem Class="drk-full-width">
<div class="@EditorClass">
<MonacoEditor @ref="Editor" Id="metadata-monaco-editor" ConstructionOptions="EditorConstructionOptions"/>
</div>
<Space Direction="DirectionVHType.Horizontal" Class="drk-vertical-space-center">
<SpaceItem>
<FileDropZone class="drop-zone">
<Upload Name="file" Class="@ImageClass" ListType="picture-card"
ShowUploadList="false" BeforeAllUploadAsync="BeforeUpload">
@if (AppState?.Storage?.UploadIsInProgress ?? false)
{
<div>
<Icon Spin="true" Type="loading" Style="font-size: 2rem;"></Icon>
<Title Level="4">Uploading...</Title>
</div>
}
else if (AppState?.Storage?.LocalImageUrl != null && AppState.Storage.CanPreviewTokenFile)
{
<img src="@AppState?.Storage?.LocalImageUrl" alt="avatar" style="width: 100%"/>
}
else if (AppState?.Storage?.LocalImageUrl != null)
{
<div>
<Icon Type="eye-invisible" Style="font-size: 2rem;"></Icon>
<Title Level="4">Unable to preview</Title>
</div>
}
else
{
<div>
<Icon Type="plus" Style="font-size: 2rem;"></Icon>
<Title Level="4">Choose or drag & drop file</Title>
</div>
}
</Upload>
</FileDropZone>
</SpaceItem>
<SpaceItem Class="drk-grow">
<Space Align="start" Direction="DirectionVHType.Vertical" Class="drk-vertical-space">
<SpaceItem Class="drk-full-width">
<Space Align="start" Direction="DirectionVHType.Vertical" Class="drk-full-width">
<SpaceItem>
<Title Level="4" Style="margin-bottom: 0;">Name:</Title>
</SpaceItem>
<SpaceItem Class="drk-full-width">
<div class="@NameClass">
<Input Size="medium" @bind-Value="@AppState.Storage.TokenName" OnInput="@OnNameInputChange" />
</div>
</SpaceItem>
</Space>
</SpaceItem>
@if (AppState.Storage.NetworkType == NetworkType.Ethereum)
{
<SpaceItem Class="drk-full-width">
<Space Align="start" Direction="DirectionVHType.Vertical" Class="drk-full-width">
<SpaceItem>
<Title Level="4" Style="margin-bottom: 0;">Description:</Title>
</SpaceItem>
<SpaceItem Class="drk-full-width">
<div class="@DescriptionClass">
<TextArea ShowCount MaxLength="255" MinRows="3" MaxRows="5" OnInput="@OnDescriptionInputChange" @bind-Value="@AppState.Storage.TokenDescription" />
</div>
</SpaceItem>
</Space>
</SpaceItem>
}
@if (AppState.Storage.NetworkType == NetworkType.Solana)
{
<SpaceItem Class="drk-full-width">
<Space Align="start" Direction="DirectionVHType.Vertical" Class="drk-full-width">
<SpaceItem>
<Title Level="4" Style="margin-bottom: 0;">Description:</Title>
</SpaceItem>
<SpaceItem Class="drk-full-width">
<div class="@SymbolClass">
<Input Size="medium" @bind-Value="@AppState.Storage.TokenSymbol" OnInput="@OnSymbolInputChange" />
</div>
</SpaceItem>
</Space>
</SpaceItem>
}
<SpaceItem Class="drk-full-width">
<Space Align="start" Direction="DirectionVHType.Vertical" Class="drk-full-width">
<SpaceItem>
<Title Level="4" Style="margin-bottom: 0;">IPFS Gateway:</Title>
</SpaceItem>
<SpaceItem>
<Select DataSource="@IpfsGateways"
DefaultValue="@(nameof(IpfsGatewayType.Infura))"
ValueName="@nameof(EnumWrapper<IpfsGatewayType>.ValueString)"
LabelName="@nameof(EnumWrapper<IpfsGatewayType>.Description)"
OnSelectedItemChanged="OnIpfsGatewayChange">
</Select>
</SpaceItem>
</Space>
</SpaceItem>
@if (AppState.Storage.NetworkType == NetworkType.Ethereum)
{
<SpaceItem Class="drk-full-width">
<Space Align="start" Direction="DirectionVHType.Vertical" Class="drk-full-width">
<SpaceItem>
<Title Level="4" Style="margin-bottom: 0;">Token type:</Title>
</SpaceItem>
<SpaceItem>
<Select DataSource="@TokenTypes"
DefaultValue="@(nameof(TokenType.ERC721))"
ValueName="@nameof(EnumWrapper<TokenType>.ValueString)"
LabelName="@nameof(EnumWrapper<TokenType>.Description)"
OnSelectedItemChanged="OnTokenTypeChange">
</Select>
</SpaceItem>
</Space>
</SpaceItem>
}
@if (AppState.Storage.NetworkType == NetworkType.Solana)
{
<SpaceItem Class="drk-full-width">
<Space Align="start" Direction="DirectionVHType.Vertical" Class="drk-full-width">
<SpaceItem>
<Title Level="4" Style="margin-bottom: 0;">Is token mutable:</Title>
</SpaceItem>
<SpaceItem>
<Checkbox @bind-Value="AppState.Storage.IsTokenMutable"/>
</SpaceItem>
</Space>
</SpaceItem>
}
@if (AppState.Storage.NetworkType == NetworkType.Solana)
{
<SpaceItem Class="drk-full-width">
<Space Align="start" Direction="DirectionVHType.Vertical" Class="drk-full-width">
<SpaceItem>
<Title Level="4" Style="margin-bottom: 0;">Seller fee basis points:</Title>
</SpaceItem>
<SpaceItem>
<div>
<AntDesign.InputNumber @bind-Value="@AppState.Storage.SellerFeeBasisPoints" Min="1" Max="10000" DefaultValue="88"></AntDesign.InputNumber>
</div>
</SpaceItem>
</Space>
</SpaceItem>
}
<SpaceItem Class="drk-full-width">
<Space Align="start" Direction="DirectionVHType.Vertical" Class="drk-full-width">
<SpaceItem>
<Title Level="4" Style="margin-bottom: 0;">Amount:</Title>
</SpaceItem>
<SpaceItem>
<div>
<AntDesign.InputNumber @bind-Value="@AppState.Storage.TokenAmount" Disabled="@(AppState.Storage.TokenType == TokenType.ERC721)" Min="1" Max="1000" DefaultValue="1"></AntDesign.InputNumber>
</div>
</SpaceItem>
</Space>
</SpaceItem>
</Space>
</SpaceItem>
</Space>
</SpaceItem>
</Space>

View File

@ -1,84 +1,135 @@
using System.Text;
using BlazorMonaco;
using Newtonsoft.Json;
using AntDesign;
using Microsoft.AspNetCore.Components;
using NftFaucet.Components;
using NftFaucet.Constants;
using NftFaucet.Extensions;
using NftFaucet.Models.Token;
using NftFaucet.Models.Enums;
namespace NftFaucet.Pages;
public class Step2Component : BasicComponent
{
protected MonacoEditor Editor { get; set; }
protected string EditorErrorMessage { get; set; }
protected string EditorClass => string.IsNullOrWhiteSpace(EditorErrorMessage) ? null : "invalid-input";
protected string NameErrorMessage { get; set; }
protected string DescriptionErrorMessage { get; set; }
protected string SymbolErrorMessage { get; set; }
protected string ImageErrorMessage { get; set; }
protected string NameClass => string.IsNullOrWhiteSpace(NameErrorMessage) ? null : "invalid-input";
protected string DescriptionClass => string.IsNullOrWhiteSpace(DescriptionErrorMessage) ? null : "invalid-input";
protected string SymbolClass => string.IsNullOrWhiteSpace(SymbolErrorMessage) ? null : "invalid-input";
protected string ImageClass => "file-uploader" + (string.IsNullOrWhiteSpace(ImageErrorMessage) ? string.Empty : " invalid-input");
protected StandaloneEditorConstructionOptions EditorConstructionOptions(MonacoEditor editor)
{
return new StandaloneEditorConstructionOptions
{
Language = "json",
GlyphMargin = true,
Value = GetCurrentMetadataJson(),
};
}
protected EnumWrapper<IpfsGatewayType>[] IpfsGateways { get; } = Enum.GetValues<IpfsGatewayType>()
.Select(x => new EnumWrapper<IpfsGatewayType>(x, x.ToString())).ToArray();
protected EnumWrapper<TokenType>[] TokenTypes { get; } = Enum.GetValues<TokenType>()
.Select(x => new EnumWrapper<TokenType>(x, x.ToString())).ToArray();
protected override async Task OnInitializedAsync()
{
if (!await AppState.Metamask.IsReady() || !AppState.IpfsContext.IsInitialized || AppState.Storage.IpfsImageUrl == null)
if (!await AppState.Metamask.IsReady() || !AppState.IpfsContext.IsInitialized)
UriHelper.NavigateToRelative("/");
AppState.Navigation.SetForwardHandler(ForwardHandler);
await Task.Yield();
await Editor.SetValue(GetCurrentMetadataJson());
}
protected async Task<bool> ForwardHandler()
protected Task<bool> ForwardHandler()
{
var metadataJson = await Editor.GetValue();
if (!metadataJson.IsValidJson())
var isValidName = !string.IsNullOrWhiteSpace(AppState.Storage.TokenName);
var isValidDescription = AppState.Storage.NetworkType == NetworkType.Ethereum
? !string.IsNullOrWhiteSpace(AppState.Storage.TokenDescription)
: !string.IsNullOrEmpty(AppState.Storage.TokenSymbol);
var isValidFile = AppState.Storage.IpfsImageUrl != null;
var isNotUploading = !AppState.Storage.UploadIsInProgress;
if (!isValidName)
{
NameErrorMessage = "Invalid name";
}
if (!isValidDescription)
{
DescriptionErrorMessage = "Invalid description";
}
if (!isValidFile)
{
ImageErrorMessage = "Invalid file";
}
if (!isNotUploading)
{
ImageErrorMessage = "Upload is still in progress";
}
RefreshMediator.NotifyStateHasChangedSafe();
var canProceed = isValidName && isValidDescription && isValidFile && isNotUploading;
return Task.FromResult(canProceed);
}
protected void OnNameInputChange(ChangeEventArgs args)
{
NameErrorMessage = string.Empty;
}
protected void OnDescriptionInputChange(ChangeEventArgs args)
{
DescriptionErrorMessage = string.Empty;
}
protected void OnSymbolInputChange(ChangeEventArgs args)
{
DescriptionErrorMessage = string.Empty;
}
protected void OnIpfsGatewayChange(EnumWrapper<IpfsGatewayType> ipfsGatewayItem)
{
AppState.Storage.IpfsGatewayType = ipfsGatewayItem.Value;
}
protected void OnTokenTypeChange(EnumWrapper<TokenType> tokenTypeItem)
{
AppState.Storage.TokenType = tokenTypeItem.Value;
if (AppState.Storage.TokenType == TokenType.ERC721)
{
AppState.Storage.TokenAmount = 1;
}
RefreshMediator.NotifyStateHasChangedSafe();
}
protected async Task<bool> BeforeUpload(List<UploadFileItem> files)
{
var file = files.FirstOrDefault();
if (file == null)
{
EditorErrorMessage = "Invalid JSON";
RefreshMediator.NotifyStateHasChangedSafe();
return false;
}
if (AppState.Storage.TokenMetadata == metadataJson)
var hasValidSize = file.Size < UploadConstants.MaxFileSizeInBytes;
if (!hasValidSize)
{
return true;
MessageService.Error($"File must be smaller than {UploadConstants.MaxFileSizeInMegabytes} MB!");
return false;
}
ImageErrorMessage = string.Empty;
AppState.Storage.UploadIsInProgress = true;
RefreshMediator.NotifyStateHasChangedSafe();
var metadataBytes = Encoding.UTF8.GetBytes(metadataJson);
var tokenUri = await IpfsService.Upload("token.json", "application/json", metadataBytes);
tokenUri = IpfsService.GetUrlToGateway(tokenUri, AppState.Storage.IpfsGatewayType);
AppState.Storage.TokenMetadata = metadataJson;
AppState.Storage.TokenUrl = tokenUri.OriginalString;
AppState.Storage.IpfsImageUrl = await IpfsService.Upload(file.FileName, file.Type, file.ObjectURL);
AppState.Storage.LocalImageUrl = new Uri(file.ObjectURL);
ImageErrorMessage = string.Empty;
AppState.Storage.UploadIsInProgress = false;
AppState.Storage.CanPreviewTokenFile = file.IsPicture();
if (!AppState.Storage.CanPreviewTokenFile)
{
MessageService.Warning("Can't preview this file. Tho you can still mint a NFT with it.");
}
RefreshMediator.NotifyStateHasChangedSafe();
return true;
}
private string GetCurrentMetadataJson()
{
var imageUrl = AppState?.Storage?.IpfsImageUrl != null
? IpfsService.GetUrlToGateway(AppState.Storage.IpfsImageUrl, AppState.Storage.IpfsGatewayType)
: null;
var metadata = new TokenMetadata
{
Name = AppState?.Storage?.TokenName,
Description = AppState?.Storage?.TokenDescription,
Image = imageUrl?.OriginalString,
ExternalUrl = "https://darkcodi.github.io/nft-faucet/",
};
var metadataJson = JsonConvert.SerializeObject(metadata, Formatting.Indented);
return metadataJson;
return false;
}
}

View File

@ -2,26 +2,12 @@
@inherits Step3Component
<Space Align="start" Direction="DirectionVHType.Vertical" Class="drk-full-width">
@if (!IsSupportedNetwork)
{
<SpaceItem>
<Title Level="2" Strong="true" Style="color: red;">Selected network is not supported. Please change network in Metamask!</Title>
</SpaceItem>
}
<SpaceItem>
<Title Level="4" Style="margin-bottom: 0;">Token URI:</Title>
<Title Level="4" Style="margin-bottom: 0;">Token metadata:</Title>
</SpaceItem>
<SpaceItem Class="drk-full-width">
<div class="@TokenUrlClass">
<Input Size="medium" @bind-Value="@AppState.Storage.TokenUrl" OnInput="@OnTokenUrlInputChange"/>
</div>
</SpaceItem>
<SpaceItem>
<Title Level="4" Style="margin-bottom: 0;">Destination address:</Title>
</SpaceItem>
<SpaceItem Class="drk-full-width">
<div class="@DestinationAddressClass">
<Input Size="medium" @bind-Value="@AppState.Storage.DestinationAddress" OnInput="@OnDestinationAddressInputChange"/>
<div class="@EditorClass">
<MonacoEditor @ref="Editor" Id="metadata-monaco-editor" ConstructionOptions="EditorConstructionOptions"/>
</div>
</SpaceItem>
</Space>

View File

@ -1,53 +1,84 @@
using System.Text;
using BlazorMonaco;
using Newtonsoft.Json;
using NftFaucet.Components;
using NftFaucet.Extensions;
using NftFaucet.Models;
using NftFaucet.Models.Token;
namespace NftFaucet.Pages;
public class Step3Component : BasicComponent
{
protected string TokenUrlErrorMessage { get; set; }
protected string DestinationAddressErrorMessage { get; set; }
protected string TokenUrlClass => string.IsNullOrWhiteSpace(TokenUrlErrorMessage) ? null : "invalid-input";
protected string DestinationAddressClass => string.IsNullOrWhiteSpace(DestinationAddressErrorMessage) ? null : "invalid-input";
protected bool IsSupportedNetwork => AppState?.Metamask?.Network != null && Settings?.GetEthereumNetworkOptions(AppState.Metamask.Network!.Value) != null;
protected MonacoEditor Editor { get; set; }
protected string EditorErrorMessage { get; set; }
protected string EditorClass => string.IsNullOrWhiteSpace(EditorErrorMessage) ? null : "invalid-input";
protected StandaloneEditorConstructionOptions EditorConstructionOptions(MonacoEditor editor)
{
return new StandaloneEditorConstructionOptions
{
Language = "json",
GlyphMargin = true,
Value = GetCurrentMetadataJson(),
};
}
protected override async Task OnInitializedAsync()
{
if (!await AppState.Metamask.IsReady() || !AppState.IpfsContext.IsInitialized || string.IsNullOrEmpty(AppState.Storage.TokenUrl))
if (!await AppState.Metamask.IsReady() || !AppState.IpfsContext.IsInitialized || AppState.Storage.IpfsImageUrl == null)
UriHelper.NavigateToRelative("/");
AppState.Navigation.SetForwardHandler(ForwardHandler);
AppState.Storage.DestinationAddress = AppState.Metamask.Address;
await Task.Yield();
await Editor.SetValue(GetCurrentMetadataJson());
}
protected void OnTokenUrlInputChange()
protected async Task<bool> ForwardHandler()
{
TokenUrlErrorMessage = string.Empty;
}
protected void OnDestinationAddressInputChange()
{
DestinationAddressErrorMessage = string.Empty;
}
protected Task<bool> ForwardHandler()
{
var isValidTokenUri = !string.IsNullOrWhiteSpace(AppState.Storage.TokenUrl);
var isValidDestinationAddress = Address.Create(AppState.Storage.DestinationAddress).IsSuccess || SolanaAddress.Create(AppState.Storage.DestinationAddress).IsSuccess;
if (!isValidTokenUri)
var metadataJson = await Editor.GetValue();
if (!metadataJson.IsValidJson())
{
TokenUrlErrorMessage = "Invalid token URI";
EditorErrorMessage = "Invalid JSON";
RefreshMediator.NotifyStateHasChangedSafe();
return false;
}
if (!isValidDestinationAddress)
if (AppState.Storage.TokenMetadata == metadataJson)
{
DestinationAddressErrorMessage = "Invalid destination address";
return true;
}
AppState.Storage.UploadIsInProgress = true;
RefreshMediator.NotifyStateHasChangedSafe();
return Task.FromResult(isValidTokenUri && isValidDestinationAddress && IsSupportedNetwork);
var metadataBytes = Encoding.UTF8.GetBytes(metadataJson);
var tokenUri = await IpfsService.Upload("token.json", "application/json", metadataBytes);
tokenUri = IpfsService.GetUrlToGateway(tokenUri, AppState.Storage.IpfsGatewayType);
AppState.Storage.TokenMetadata = metadataJson;
AppState.Storage.TokenUrl = tokenUri.OriginalString;
AppState.Storage.UploadIsInProgress = false;
RefreshMediator.NotifyStateHasChangedSafe();
return true;
}
private string GetCurrentMetadataJson()
{
var imageUrl = AppState?.Storage?.IpfsImageUrl != null
? IpfsService.GetUrlToGateway(AppState.Storage.IpfsImageUrl, AppState.Storage.IpfsGatewayType)
: null;
var metadata = new TokenMetadata
{
Name = AppState?.Storage?.TokenName,
Description = AppState?.Storage?.TokenDescription,
Image = imageUrl?.OriginalString,
ExternalUrl = "https://darkcodi.github.io/nft-faucet/",
};
var metadataJson = JsonConvert.SerializeObject(metadata, Formatting.Indented);
return metadataJson;
}
}

View File

@ -1,39 +1,27 @@
@page "/step4"
@inherits Step4Component
@if (TransactionHash == null)
{
<Space Align="center" Direction="DirectionVHType.Horizontal" Class="drk-vertical-space-center">
<SpaceItem Class="drk-full-height">
<Space Align="center" Direction="DirectionVHType.Vertical" Class="drk-vertical-space-center" Style="align-items: center !important;">
<SpaceItem>
<Spin size="large" />
</SpaceItem>
<SpaceItem>
<Title Level="3">MetaMask will show a transaction signing pop-up. Please approve.</Title>
</SpaceItem>
</Space>
<Space Align="start" Direction="DirectionVHType.Vertical" Class="drk-full-width">
@if (!IsSupportedNetwork)
{
<SpaceItem>
<Title Level="2" Strong="true" Style="color: red;">Selected network is not supported. Please change network in Metamask!</Title>
</SpaceItem>
</Space>
}
else if (TransactionHash.Value.IsSuccess)
{
<Result Status="success"
Title="Transaction was successfully created!"
SubTitle="@($"Transaction: {TransactionHash.Value.Value}. Please wait for 1-5 minutes till transaction is completed.")">
<Extra>
<Button OnClick="@ResetState">Start a new mint</Button>
<Button Type="@ButtonType.Primary" OnClick="@ViewOnExplorer">View on explorer</Button>
</Extra>
</Result>
}
else
{
<Result Status="error"
Title="Failed to create transaction"
SubTitle="@("Please ensure that you approve created transactions in Metamask")">
<Extra>
<Button Type="primary" OnClick="@RetryTransaction">Try again</Button>
</Extra>
</Result>
}
}
<SpaceItem>
<Title Level="4" Style="margin-bottom: 0;">Token URI:</Title>
</SpaceItem>
<SpaceItem Class="drk-full-width">
<div class="@TokenUrlClass">
<Input Size="medium" @bind-Value="@AppState.Storage.TokenUrl" OnInput="@OnTokenUrlInputChange"/>
</div>
</SpaceItem>
<SpaceItem>
<Title Level="4" Style="margin-bottom: 0;">Destination address:</Title>
</SpaceItem>
<SpaceItem Class="drk-full-width">
<div class="@DestinationAddressClass">
<Input Size="medium" @bind-Value="@AppState.Storage.DestinationAddress" OnInput="@OnDestinationAddressInputChange"/>
</div>
</SpaceItem>
</Space>

View File

@ -1,146 +1,56 @@
using CSharpFunctionalExtensions;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
using NftFaucet.Components;
using NftFaucet.Extensions;
using NftFaucet.Models;
using NftFaucet.Models.Enums;
using NftFaucet.Services;
using NftFaucet.Utils;
namespace NftFaucet.Pages;
public class Step4Component : BasicComponent
{
[Inject]
public IIpfsService IpfsService { get; set; }
[Inject]
public IEthereumTransactionService TransactionService { get; set; }
[Inject]
public ISolanaTransactionService SolanaTransactionService { get; set; }
[Inject]
protected IJSRuntime JsRuntime { get; set; }
protected Result<string>? TransactionHash { get; set; }
protected string TokenUrlErrorMessage { get; set; }
protected string DestinationAddressErrorMessage { get; set; }
protected string TokenUrlClass => string.IsNullOrWhiteSpace(TokenUrlErrorMessage) ? null : "invalid-input";
protected string DestinationAddressClass => string.IsNullOrWhiteSpace(DestinationAddressErrorMessage) ? null : "invalid-input";
protected bool IsSupportedNetwork => AppState?.Metamask?.Network != null && Settings?.GetEthereumNetworkOptions(AppState.Metamask.Network!.Value) != null;
protected override async Task OnInitializedAsync()
{
if (!await AppState.Metamask.IsReady() || !AppState.IpfsContext.IsInitialized || string.IsNullOrEmpty(AppState.Storage.DestinationAddress))
{
if (!await AppState.Metamask.IsReady() || !AppState.IpfsContext.IsInitialized || string.IsNullOrEmpty(AppState.Storage.TokenUrl))
UriHelper.NavigateToRelative("/");
}
else
{
Task.Run(Mint);
}
AppState.Navigation.SetForwardHandler(ForwardHandler);
AppState.Storage.DestinationAddress = AppState.Metamask.Address;
}
public async Task Mint()
protected void OnTokenUrlInputChange()
{
var network = AppState.Metamask.Network!.Value;
var address = AppState.Storage.DestinationAddress;
var uri = AppState.Storage.TokenUrl;
TokenUrlErrorMessage = string.Empty;
}
if (AppState.Storage.TokenType == TokenType.ERC721)
protected void OnDestinationAddressInputChange()
{
DestinationAddressErrorMessage = string.Empty;
}
protected Task<bool> ForwardHandler()
{
var isValidTokenUri = !string.IsNullOrWhiteSpace(AppState.Storage.TokenUrl);
var isValidDestinationAddress = AppState.Storage.NetworkType == NetworkType.Ethereum
? Address.Create(AppState.Storage.DestinationAddress).IsSuccess
: SolanaAddress.Create(AppState.Storage.DestinationAddress).IsSuccess;
if (!isValidTokenUri)
{
TransactionHash = await ResultWrapper.Wrap(TransactionService.MintErc721Token(network, address, uri));
TokenUrlErrorMessage = "Invalid token URI";
}
else if (AppState.Storage.TokenType == TokenType.ERC1155)
if (!isValidDestinationAddress)
{
var amount = (int) AppState.Storage.TokenAmount;
TransactionHash = await ResultWrapper.Wrap(TransactionService.MintErc1155Token(network, address, amount, uri));
}
else if (AppState.Storage.TokenType == TokenType.SolanaDevnet)
{
TransactionHash =
await ResultWrapper.Wrap(SolanaTransactionService.MintNft(EthereumNetwork.SolanaDevnet,
address,
uri,
AppState.Storage.TokenName,
AppState.Storage.TokenAmount));
}
else if (AppState.Storage.TokenType == TokenType.SolanaTestnet)
{
TransactionHash =
await ResultWrapper.Wrap(SolanaTransactionService.MintNft(EthereumNetwork.SolanaTestnet,
address,
uri,
AppState.Storage.TokenName,
AppState.Storage.TokenAmount));
DestinationAddressErrorMessage = "Invalid destination address";
}
RefreshMediator.NotifyStateHasChangedSafe();
}
protected void ResetState()
{
AppState.Reset();
UriHelper.NavigateToRelative("/");
}
protected async Task ViewOnExplorer()
{
var network = AppState.Metamask.Network;
if (AppState.Storage.TokenType == TokenType.SolanaDevnet)
{
network = EthereumNetwork.SolanaDevnet;
}
if (AppState.Storage.TokenType == TokenType.SolanaTestnet)
{
network = EthereumNetwork.SolanaTestnet;
}
var baseUrl = network switch
{
EthereumNetwork.EthereumMainnet => "https://etherscan.io/tx/",
EthereumNetwork.Ropsten => "https://ropsten.etherscan.io/tx/",
EthereumNetwork.Rinkeby => "https://rinkeby.etherscan.io/tx/",
EthereumNetwork.Goerli => "https://goerli.etherscan.io/tx/",
EthereumNetwork.Kovan => "https://kovan.etherscan.io/tx/",
EthereumNetwork.OptimismMainnet => "https://optimistic.etherscan.io/tx/",
EthereumNetwork.OptimismKovan => "https://kovan-optimistic.etherscan.io/tx/",
EthereumNetwork.PolygonMainnet => "https://polygonscan.com/tx/",
EthereumNetwork.PolygonMumbai => "https://mumbai.polygonscan.com/tx/",
EthereumNetwork.MoonbeamMainnet => "https://blockscout.moonbeam.network/tx/",
EthereumNetwork.MoonbaseAlpha => "https://moonbase.moonscan.io/tx/",
EthereumNetwork.ArbitrumMainnetBeta => "https://explorer.arbitrum.io/tx/",
EthereumNetwork.ArbitrumRinkeby => "https://testnet.arbiscan.io/tx/",
EthereumNetwork.ArbitrumGoerli => "https://nitro-devnet-explorer.arbitrum.io/tx/",
EthereumNetwork.AvalancheMainnet => "https://snowtrace.io/tx/",
EthereumNetwork.AvalancheFuji => "https://testnet.snowtrace.io/tx/",
EthereumNetwork.SolanaDevnet => "https://explorer.solana.com/tx/",
EthereumNetwork.SolanaTestnet => "https://explorer.solana.com/tx/",
EthereumNetwork.SolanaMainnet => "https://explorer.solana.com/tx/",
_ => null,
};
if (baseUrl == null && !network.HasValue)
return;
var txHash = TransactionHash!.Value!.Value;
var txUrl = BuildTxUrl(network.Value, baseUrl, txHash);
await JsRuntime.InvokeAsync<object>("open", txUrl, "_blank");
}
protected async Task RetryTransaction()
{
TransactionHash = null;
RefreshMediator.NotifyStateHasChangedSafe();
Mint();
}
private string BuildTxUrl(EthereumNetwork chain, string baseUrl, string txHash)
{
return chain switch
{
EthereumNetwork.SolanaDevnet => baseUrl + txHash + "?cluster=devnet",
EthereumNetwork.SolanaTestnet => baseUrl + txHash + "?cluster=testnet",
EthereumNetwork.SolanaMainnet => baseUrl + txHash,
_ => baseUrl + txHash,
};
return Task.FromResult(isValidTokenUri && isValidDestinationAddress && IsSupportedNetwork);
}
}

View File

@ -0,0 +1,49 @@
@page "/step5"
@using NftFaucet.Models.Enums
@inherits Step5Component
@if (TransactionHash == null)
{
<Space Align="center" Direction="DirectionVHType.Horizontal" Class="drk-vertical-space-center">
<SpaceItem Class="drk-full-height">
<Space Align="center" Direction="DirectionVHType.Vertical" Class="drk-vertical-space-center" Style="align-items: center !important;">
<SpaceItem>
<Spin size="large" />
</SpaceItem>
@if (AppState.Storage.NetworkType == NetworkType.Ethereum)
{
<SpaceItem>
<Title Level="3">MetaMask will show a transaction signing pop-up. Please approve.</Title>
</SpaceItem>
}
@if (AppState.Storage.NetworkType == NetworkType.Solana)
{
<SpaceItem>
<Title Level="3">Solana mint in progress. Please wait...</Title>
</SpaceItem>
}
</Space>
</SpaceItem>
</Space>
}
else if (TransactionHash.Value.IsSuccess)
{
<Result Status="success"
Title="Transaction was successfully created!"
SubTitle="@($"Transaction: {TransactionHash.Value.Value}. Please wait for 1-5 minutes till transaction is completed.")">
<Extra>
<Button OnClick="@ResetState">Start a new mint</Button>
<Button Type="@ButtonType.Primary" OnClick="@ViewOnExplorer">View on explorer</Button>
</Extra>
</Result>
}
else
{
<Result Status="error"
Title="Failed to create transaction"
SubTitle="@("Please ensure that you approve created transactions in Metamask")">
<Extra>
<Button Type="primary" OnClick="@RetryTransaction">Try again</Button>
</Extra>
</Result>
}

View File

@ -0,0 +1,136 @@
using CSharpFunctionalExtensions;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
using NftFaucet.Components;
using NftFaucet.Extensions;
using NftFaucet.Models.Enums;
using NftFaucet.Services;
using NftFaucet.Utils;
namespace NftFaucet.Pages;
public class Step5Component : BasicComponent
{
[Inject]
public IIpfsService IpfsService { get; set; }
[Inject]
public IEthereumTransactionService TransactionService { get; set; }
[Inject]
public ISolanaTransactionService SolanaTransactionService { get; set; }
[Inject]
protected IJSRuntime JsRuntime { get; set; }
protected Result<string>? TransactionHash { get; set; }
protected override async Task OnInitializedAsync()
{
if (!await AppState.Metamask.IsReady() || !AppState.IpfsContext.IsInitialized || string.IsNullOrEmpty(AppState.Storage.DestinationAddress))
{
UriHelper.NavigateToRelative("/");
}
else
{
Task.Run(Mint);
}
}
public async Task Mint()
{
var network = AppState.Metamask.Network!.Value;
var address = AppState.Storage.DestinationAddress;
var uri = AppState.Storage.TokenUrl;
if (AppState.Storage.NetworkType == NetworkType.Ethereum && AppState.Storage.TokenType == TokenType.ERC721)
{
TransactionHash = await ResultWrapper.Wrap(TransactionService.MintErc721Token(network, address, uri));
}
else if (AppState.Storage.NetworkType == NetworkType.Ethereum && AppState.Storage.TokenType == TokenType.ERC1155)
{
var amount = (int) AppState.Storage.TokenAmount;
TransactionHash = await ResultWrapper.Wrap(TransactionService.MintErc1155Token(network, address, amount, uri));
}
else if (AppState.Storage.NetworkType == NetworkType.Solana)
{
TransactionHash =
await ResultWrapper.Wrap(SolanaTransactionService.MintNft(AppState.Storage.Network,
address,
uri,
AppState.Storage.TokenName,
AppState.Storage.TokenSymbol,
AppState.Storage.IsTokenMutable,
(uint)AppState.Storage.SellerFeeBasisPoints,
(ulong)AppState.Storage.TokenAmount));
}
RefreshMediator.NotifyStateHasChangedSafe();
}
protected void ResetState()
{
AppState.Reset();
UriHelper.NavigateToRelative("/");
}
protected async Task ViewOnExplorer()
{
var network = AppState.Metamask.Network;
if (AppState.Storage.NetworkType == NetworkType.Solana)
{
network = AppState.Storage.Network;
}
var baseUrl = network switch
{
EthereumNetwork.EthereumMainnet => "https://etherscan.io/tx/",
EthereumNetwork.Ropsten => "https://ropsten.etherscan.io/tx/",
EthereumNetwork.Rinkeby => "https://rinkeby.etherscan.io/tx/",
EthereumNetwork.Goerli => "https://goerli.etherscan.io/tx/",
EthereumNetwork.Kovan => "https://kovan.etherscan.io/tx/",
EthereumNetwork.OptimismMainnet => "https://optimistic.etherscan.io/tx/",
EthereumNetwork.OptimismKovan => "https://kovan-optimistic.etherscan.io/tx/",
EthereumNetwork.PolygonMainnet => "https://polygonscan.com/tx/",
EthereumNetwork.PolygonMumbai => "https://mumbai.polygonscan.com/tx/",
EthereumNetwork.MoonbeamMainnet => "https://blockscout.moonbeam.network/tx/",
EthereumNetwork.MoonbaseAlpha => "https://moonbase.moonscan.io/tx/",
EthereumNetwork.ArbitrumMainnetBeta => "https://explorer.arbitrum.io/tx/",
EthereumNetwork.ArbitrumRinkeby => "https://testnet.arbiscan.io/tx/",
EthereumNetwork.ArbitrumGoerli => "https://nitro-devnet-explorer.arbitrum.io/tx/",
EthereumNetwork.AvalancheMainnet => "https://snowtrace.io/tx/",
EthereumNetwork.AvalancheFuji => "https://testnet.snowtrace.io/tx/",
EthereumNetwork.SolanaDevnet => "https://explorer.solana.com/tx/",
EthereumNetwork.SolanaTestnet => "https://explorer.solana.com/tx/",
EthereumNetwork.SolanaMainnet => "https://explorer.solana.com/tx/",
_ => null,
};
if (baseUrl == null && !network.HasValue)
return;
var txHash = TransactionHash!.Value!.Value;
var txUrl = BuildTxUrl(network.Value, baseUrl, txHash);
await JsRuntime.InvokeAsync<object>("open", txUrl, "_blank");
}
protected async Task RetryTransaction()
{
TransactionHash = null;
RefreshMediator.NotifyStateHasChangedSafe();
Mint();
}
private string BuildTxUrl(EthereumNetwork chain, string baseUrl, string txHash)
{
return chain switch
{
EthereumNetwork.SolanaDevnet => baseUrl + txHash + "?cluster=devnet",
EthereumNetwork.SolanaTestnet => baseUrl + txHash + "?cluster=testnet",
EthereumNetwork.SolanaMainnet => baseUrl + txHash,
_ => baseUrl + txHash,
};
}
}

View File

@ -4,5 +4,12 @@ namespace NftFaucet.Services;
public interface ISolanaTransactionService
{
Task<string> MintNft(EthereumNetwork chain, string destinationAddress, string tokenUri, string name, double amount);
Task<string> MintNft(EthereumNetwork chain,
string destinationAddress,
string tokenUri,
string name,
string symbol,
bool isTokenMutable,
uint sellerFeeBasisPoints,
ulong amount);
}

View File

@ -22,7 +22,7 @@ public class SolanaTransactionInstructionsPipeline
Add(SystemProgram.Transfer(from, from, tokenPrice));
}
public void AddMetadata(PublicKey from, PublicKey mint, PublicKey metadataAddress, MetadataParameters data)
public void AddMetadata(PublicKey from, PublicKey mint, PublicKey metadataAddress, MetadataParameters data, bool isMutable = true)
{
Add(MetadataProgram.CreateMetadataAccount(
metadataAddress,
@ -32,7 +32,7 @@ public class SolanaTransactionInstructionsPipeline
from,
data,
true,
true
isMutable
));
}

View File

@ -19,7 +19,9 @@ public class SolanaTransactionService : ISolanaTransactionService
string tokenUri,
string name,
string symbol,
double amount)
bool isTokenMutable,
uint sellerFeeBasisPoints,
ulong amount)
{
var cluster = chain switch
{
@ -61,10 +63,10 @@ public class SolanaTransactionService : ISolanaTransactionService
var data = new MetadataParameters()
{
name = name,
symbol = "DNFT",
symbol = symbol,
uri = tokenUri,
creators = new List<Creator> { new Creator(walletAddress, 100, true) },
sellerFeeBasisPoints = 88,
sellerFeeBasisPoints = sellerFeeBasisPoints,
};
var destinationPublicKey = new PublicKey(destinationAddress);
@ -72,7 +74,7 @@ public class SolanaTransactionService : ISolanaTransactionService
var pipeline = new SolanaTransactionInstructionsPipeline();
pipeline.InitializeForMint(walletAddress, destinationPublicKey, mintAddress, rentExemption.Result, (ulong)amount, tokenPrice);
pipeline.AddMetadata(walletAddress, mintAddress, metadataAddress, data);
pipeline.AddMetadata(walletAddress, mintAddress, metadataAddress, data, isTokenMutable);
pipeline.AddMasterEdition(walletAddress, mintAddress, masterEditionAddress, metadataAddress, data);
var blockHash = (await client.GetRecentBlockHashAsync()).Result.Value.Blockhash;

View File

@ -1,15 +1,17 @@
@inherits MainLayoutComponent
@using NftFaucet.Models.Enums
@inherits MainLayoutComponent
<Layout Style="background-color: #FFFFFF; height: 100%;">
<Content Style="padding: 0 50px; display: flex; flex-direction: column;">
<PageHeader Class="create-nft-header">
<PageHeaderTitle>NFT Faucet</PageHeaderTitle>
<PageHeaderTags>
<Tag PresetColor="@PresetColor.Blue">Address: @(AppState?.Metamask?.Address ?? "<null>")</Tag>
<Tag PresetColor="@ChainColor">@("Chain: " + (AppState?.Metamask?.Network?.ToString() ?? "<unknown>") + " (" + (AppState?.Metamask?.ChainId.ToString() ?? "<null>") + ")")</Tag>
<Tag PresetColor="@PresetColor.Blue">Address: @(AppState.Storage.NetworkType == NetworkType.Ethereum ? AppState?.Metamask?.Address ?? "<null>" : "<null>")</Tag>
<Tag PresetColor="@ChainColor">@("Chain: " + (AppState.Storage.NetworkType == NetworkType.Ethereum ? AppState?.Metamask?.Network?.ToString() ?? "<unknown>" : AppState.Storage.Network.ToString()) + " (" + (AppState.Storage.NetworkType == NetworkType.Ethereum ? AppState?.Metamask?.ChainId.ToString() ?? "<null>" : "<null>") + ")")</Tag>
</PageHeaderTags>
</PageHeader>
<Steps Current="@(AppState.Navigation.CurrentStep - 1)" Type="navigation" Class="fix-alignment">
<Step Title="Select network type"/>
<Step Title="Specify token metadata"/>
<Step Title="Review metadata details"/>
<Step Title="Review mint details"/>

View File

@ -6,7 +6,7 @@ namespace NftFaucet.Shared;
public class MainLayoutComponent : LayoutBasicComponent
{
private const int StepsCount = 4;
private const int StepsCount = 5;
protected bool IsFirstStep => AppState.Navigation.CurrentStep == 1;
protected bool IsLastStep => AppState.Navigation.CurrentStep == StepsCount;
@ -15,15 +15,16 @@ public class MainLayoutComponent : LayoutBasicComponent
protected string ForwardButtonText => AppState.Navigation.CurrentStep switch
{
1 => "Review NFT",
2 => "Review mint",
3 => "Send me this NFT!",
1 => "Confirm network selection",
2 => "Review NFT",
3 => "Review mint",
4 => "Send me this NFT!",
_ => "Next"
};
protected string ForwardButtonIcon => AppState.Navigation.CurrentStep switch
{
3 => "send",
4 => "send",
_ => "arrow-right",
};