mirror of
https://github.com/status-im/nft-faucet.git
synced 2025-02-23 12:08:32 +00:00
Update pages
This commit is contained in:
parent
1e9d5b0243
commit
84214e8999
6
NftFaucet/Models/Enums/NetworkType.cs
Normal file
6
NftFaucet/Models/Enums/NetworkType.cs
Normal file
@ -0,0 +1,6 @@
|
||||
namespace NftFaucet.Models.Enums;
|
||||
public enum NetworkType
|
||||
{
|
||||
Ethereum,
|
||||
Solana
|
||||
}
|
@ -4,6 +4,4 @@ public enum TokenType : byte
|
||||
{
|
||||
ERC721 = 0,
|
||||
ERC1155 = 1,
|
||||
SolanaDevnet = 2,
|
||||
SolanaTestnet = 3
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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>
|
||||
</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>
|
||||
<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>
|
||||
</Space>
|
||||
</SpaceItem>
|
||||
<SpaceItem Class="drk-full-width">
|
||||
<Space Align="start" Direction="DirectionVHType.Vertical" Class="drk-full-width">
|
||||
<Space Direction="DirectionVHType.Horizontal" Class="drk-vertical-space-center">
|
||||
<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">
|
||||
@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>
|
||||
<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>
|
||||
}
|
||||
</SpaceItem>
|
||||
</Space>
|
||||
</SpaceItem>
|
||||
</Space>
|
||||
|
||||
|
||||
|
@ -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";
|
||||
AppState.Storage.NetworkType = NetworkType.Solana;
|
||||
}
|
||||
|
||||
if (!isValidDescription)
|
||||
protected void OnNetworkChange(EnumWrapper<EthereumNetwork> network)
|
||||
{
|
||||
DescriptionErrorMessage = "Invalid description";
|
||||
}
|
||||
|
||||
if (!isValidFile)
|
||||
{
|
||||
ImageErrorMessage = "Invalid file";
|
||||
}
|
||||
|
||||
if (!isNotUploading)
|
||||
{
|
||||
ImageErrorMessage = "Upload is still in progress";
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
@ -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">
|
||||
<Space Align="center" Direction="DirectionVHType.Vertical" Class="drk-vertical-space-center">
|
||||
<SpaceItem Class="drk-full-width">
|
||||
<Space Direction="DirectionVHType.Horizontal" Class="drk-vertical-space-center">
|
||||
<SpaceItem>
|
||||
<Title Level="4" Style="margin-bottom: 0;">Token metadata:</Title>
|
||||
<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="@EditorClass">
|
||||
<MonacoEditor @ref="Editor" Id="metadata-monaco-editor" ConstructionOptions="EditorConstructionOptions"/>
|
||||
<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>
|
||||
|
@ -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)
|
||||
{
|
||||
EditorErrorMessage = "Invalid JSON";
|
||||
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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
var metadataJson = await Editor.GetValue();
|
||||
if (!metadataJson.IsValidJson())
|
||||
{
|
||||
EditorErrorMessage = "Invalid JSON";
|
||||
RefreshMediator.NotifyStateHasChangedSafe();
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void OnDestinationAddressInputChange()
|
||||
if (AppState.Storage.TokenMetadata == metadataJson)
|
||||
{
|
||||
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)
|
||||
{
|
||||
TokenUrlErrorMessage = "Invalid token URI";
|
||||
}
|
||||
|
||||
if (!isValidDestinationAddress)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -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;">
|
||||
<Space Align="start" Direction="DirectionVHType.Vertical" Class="drk-full-width">
|
||||
@if (!IsSupportedNetwork)
|
||||
{
|
||||
<SpaceItem>
|
||||
<Spin size="large" />
|
||||
<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>
|
||||
</SpaceItem>
|
||||
<SpaceItem Class="drk-full-width">
|
||||
<div class="@TokenUrlClass">
|
||||
<Input Size="medium" @bind-Value="@AppState.Storage.TokenUrl" OnInput="@OnTokenUrlInputChange"/>
|
||||
</div>
|
||||
</SpaceItem>
|
||||
<SpaceItem>
|
||||
<Title Level="3">MetaMask will show a transaction signing pop-up. Please approve.</Title>
|
||||
<Title Level="4" Style="margin-bottom: 0;">Destination address:</Title>
|
||||
</SpaceItem>
|
||||
</Space>
|
||||
<SpaceItem Class="drk-full-width">
|
||||
<div class="@DestinationAddressClass">
|
||||
<Input Size="medium" @bind-Value="@AppState.Storage.DestinationAddress" OnInput="@OnDestinationAddressInputChange"/>
|
||||
</div>
|
||||
</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>
|
||||
}
|
||||
</Space>
|
||||
|
@ -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()
|
||||
{
|
||||
TransactionHash = await ResultWrapper.Wrap(TransactionService.MintErc721Token(network, address, uri));
|
||||
DestinationAddressErrorMessage = string.Empty;
|
||||
}
|
||||
else if (AppState.Storage.TokenType == TokenType.ERC1155)
|
||||
|
||||
protected Task<bool> ForwardHandler()
|
||||
{
|
||||
var amount = (int) AppState.Storage.TokenAmount;
|
||||
TransactionHash = await ResultWrapper.Wrap(TransactionService.MintErc1155Token(network, address, amount, uri));
|
||||
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)
|
||||
{
|
||||
TokenUrlErrorMessage = "Invalid token URI";
|
||||
}
|
||||
else if (AppState.Storage.TokenType == TokenType.SolanaDevnet)
|
||||
|
||||
if (!isValidDestinationAddress)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
49
NftFaucet/Pages/Step5Page.razor
Normal file
49
NftFaucet/Pages/Step5Page.razor
Normal 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>
|
||||
}
|
136
NftFaucet/Pages/Step5Page.razor.cs
Normal file
136
NftFaucet/Pages/Step5Page.razor.cs
Normal 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,
|
||||
};
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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"/>
|
||||
|
@ -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",
|
||||
};
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user