Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 152 additions & 0 deletions CornAPI/Controllers/WalletController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using CornAPI.Wallet.HLAPI;
using CornAPI.Wallet.LLAPI;
using CornAPI.Wallet.HLAPI.Models;
using Newtonsoft.Json.Linq;

namespace CornAPI.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class WalletController : ControllerBase
{

//API: /api/wallet/createcornaddy
[HttpPost("CreateCornaddy")]
public async Task<object> CreateCornaddy([FromBody] dynamic input)
{
//TODO: select user
//TODO: select wallet server
string endpoint = GetWalletServerEndpoint();
string accessToken = await GetWalletServerAccessToken();

using (var client = new JsonWalletClient(endpoint, accessToken))
{
var response = await client.GetNewAddressAsync("main");
if (!response.IsError)
{
var address = response.GetParsedContent();
//TODO: assign address to the user
}
//we got an error, fetch the internal wallet error code and figure out what to do
else
{
//get wallet error response
var error = response.GetError();

if (error.Code == WalletErrorCodes.HTTP_ERROR)
{
//TODO: figure out what to do when wallet server is not reached
}
}
}

throw new NotImplementedException();
}

//API: /api/wallet/deposit
//called by the wallet servers only
[HttpPost("Deposit")]
public async Task<object> Deposit([FromBody] dynamic input)
{
int? index = input.index?.Value;
if (index == null)
{
throw new ArgumentException("index");
}
string block = input.block?.Value;

if (string.IsNullOrWhiteSpace(block))
{
throw new ArgumentException("block");
}

JArray payments = input?.payments;

if (payments == null)
{
throw new ArgumentException("payments");
}

//TODO: update user balance
throw new NotImplementedException();
}

//API: /api/wallet/withdraw
[HttpPost("Withdraw")]
public async Task<object> Withdraw([FromBody] dynamic input)
{
decimal amount = 0;
if (input.amount.Value != null)
{
amount = Convert.ToDecimal(input.amount.Value);
}
else
{
throw new ArgumentException("amount");
}

string withdrawalAddress = input.cornaddy?.Value;

if (string.IsNullOrWhiteSpace(withdrawalAddress))
{
throw new ArgumentException("cornaddy");
}

//TODO: select user
string endpoint = GetWalletServerEndpoint();
string accessToken = await GetWalletServerAccessToken();

using (var client = new JsonWalletClient(endpoint, accessToken))
{
var response = await client.SendFromAsync("main", withdrawalAddress, amount, 120);
if (!response.IsError)
{
//TODO: subract balance from user
}
//we got an error, fetch the internal wallet error code and figure out what to do
else
{
//get wallet error response
var error = response.GetError();

//invalid withdrawal address
if (error.Code == WalletErrorCodes.RPC_INVALID_ADDRESS_OR_KEY)
{
//TODO: figure out what to do when withdrawal address is not a cornaddy
}
//too much immature corn to complete this transaction at this time
else if(error.Code == WalletErrorCodes.RPC_WALLET_INSUFFICIENT_FUNDS)
{
//TODO: figure out what to do
}
//wallet server was not reached
else if(error.Code == WalletErrorCodes.HTTP_ERROR)
{
//TODO: figure out what to do when wallet server is not reached
}

}
}

throw new NotImplementedException();
}

//TODO: implement access token fetching
private async Task<string> GetWalletServerAccessToken()
{
throw new NotImplementedException();
}
//TODO: implement server fetching
private string GetWalletServerEndpoint()
{
throw new NotImplementedException();
}

}
}
22 changes: 22 additions & 0 deletions CornAPI/Wallet/HLAPI/IWalletClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using CornAPI.Wallet.HLAPI.Models;
namespace CornAPI.Wallet.HLAPI
{
/// <summary>
/// Wallet client interface used to communicate with the wallet implementation
/// </summary>
public interface IWalletClient
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this need an interface?

{
/// <summary>
/// Makes request to the wallet implementation
/// </summary>
/// <param name="method">which wallet method to call</param>
/// <param name="parameters">parameters passed to the wallet call</param>
/// <returns>Deserialized response from the wallet implementation</returns>
Task<RawWalletResponse> MakeRequestAsync(ImplementedWalletMethods method, params object[] parameters);

}
}
55 changes: 55 additions & 0 deletions CornAPI/Wallet/HLAPI/ImplementedWalletMethods.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace CornAPI.Wallet.HLAPI
{
/// <summary>
/// Lists all implemented wallet methods on the high level api as constants for Attribute access
/// </summary>
public enum ImplementedWalletMethods
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we should go ahead and add all methods to this just so we have them for the future, maybe not

{
/// <summary>
/// Creates new address in the wallet
/// </summary>
getnewaddress,
/// <summary>
/// Gets information about transaction
/// </summary>
gettransaction,
/// <summary>
/// Gets informaiton about the wallet
/// </summary>
getwalletinfo,
/// <summary>
/// Lists all accounts in the wallet
/// </summary>
listaccounts,
/// <summary>
/// Lists transactions in the wallet
/// </summary>
listtransactions,
/// <summary>
/// Move funds from account a to account b, inside the wallet
/// </summary>
move,
/// <summary>
/// Send funds from account to address
/// </summary>
sendfrom,
/// <summary>
/// Batched sendfrom, 1 call to send to multiple addresses
/// </summary>
sendmany,
/// <summary>
/// Sends funds from the wallet (any account) to address
/// </summary>
sendtoaddress,
/// <summary>
/// Gets addresses by account
/// </summary>
getaddressesbyaccount,
getbalance
}
}

76 changes: 76 additions & 0 deletions CornAPI/Wallet/HLAPI/JsonWalletClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Net;
using System.Net.Http;
using CornAPI.Wallet.HLAPI.Models;
using CornAPI.Wallet.LLAPI;
using CornAPI.Models;
using Microsoft.EntityFrameworkCore;

namespace CornAPI.Wallet.HLAPI
{
/// <summary>
/// Wallet client that Deserializes responses as json
/// </summary>
public class JsonWalletClient : LLAPIWalletClient, IWalletClient
{

/// <summary>
/// Handles communication with the wallet server
/// </summary>
/// <param name="walletServer">current wallet server</param>
/// <param name="contentType">request content type</param>
/// <param name="accessToken">api call access token</param>
/// <param name="messageHandler">optional message handler to easily extend the httpclient</param>
public JsonWalletClient(string endpoint,
string accessToken,
HttpMessageHandler messageHandler = null)
: base(new Uri(endpoint),"application/json",accessToken,messageHandler)
{

}
/// <summary>
/// implement IWalletClient interface
/// </summary>
/// <param name="method">wallet method</param>
/// <param name="parameters">parameters required by the wallet method</param>
/// <returns>Deserialized wallet response</returns>
public async Task<RawWalletResponse> MakeRequestAsync(ImplementedWalletMethods method, object[] parameters)
{
var response = await MakeInternalRequestAsync(method.ToString(), parameters);

RawWalletResponse data = new RawWalletResponse();

if (response.StatusCode == HttpStatusCode.OK)
{

string json = await response.Content.ReadAsStringAsync();
data.FromJson(json);

}
else
{
var error = CreateHttpErrorResponse(response);
data.Error = error;
}

data.StatusCode = response.StatusCode;
return data;

}
/// <summary>
/// Creates wallet error object with HTTP_ERROR flag indicating that the error was in the request
/// </summary>
/// <returns></returns>
private WalletError CreateHttpErrorResponse(HttpResponseMessage message)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would stack trace help here?

{
var error = new WalletError();
error.Code = WalletErrorCodes.HTTP_ERROR;
error.Message = message.StatusCode.ToString();
return error;
}

}
}
88 changes: 88 additions & 0 deletions CornAPI/Wallet/HLAPI/Models/ParsedWalletResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace CornAPI.Wallet.HLAPI.Models
{
/// <summary>
/// Wrapper response object for all wallet calls;
/// If call was succesfull (IsError is false), m_Content will be of type T, otherwise m_Content will be of type WalletError
/// </summary>
/// <typeparam name="T">Succesfull response datatype</typeparam>
public class ParsedWalletResponse<T>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One of the wallet responses should just be "walletresponse"

{
/// <summary>
/// data container
/// </summary>
object _content = null;
/// <summary>
/// has this object been initialized as error?
/// </summary>
public bool IsError { get; private set; }
/// <summary>
/// has this object been initialized?
/// </summary>
public bool IsInitialized { get; private set; }
/// <summary>
/// get error object from the data container
/// </summary>
/// <returns>Wallet error object</returns>
public WalletError GetError()
{
return GetContentInternal<WalletError>();
}
/// <summary>
/// get succesfully parsed content, will return null if the data container is an error
/// </summary>
/// <returns>data container as T</returns>
public T GetParsedContent()
{
return GetContentInternal<T>();
}
/// <summary>
/// Tries to cast data container to type
/// </summary>
/// <returns>data container as TContent</returns>
TContent GetContentInternal<TContent>()
{

if (_content is TContent)
{
return (TContent)_content;
}
return default(TContent);

}
/// <summary>
/// Set data container object to succesfully parsed value
/// </summary>
/// <returns>Response wrapper with valid data</returns>
public static ParsedWalletResponse<T> CreateContent(T data)
{
var obj = new ParsedWalletResponse<T>();
obj.SetContent(data);
return obj;
}
/// <summary>
/// Set data container object to error
/// </summary>
/// <returns>Response wrapper with error</returns>
public static ParsedWalletResponse<T> CreateError(WalletError error)
{
var obj = new ParsedWalletResponse<T>();
obj.IsError = true;
obj.SetContent(error);
return obj;

}
/// <summary>
/// Sets internal data container object
/// </summary>
public void SetContent(object data)
{
this._content = data;
this.IsInitialized = true;
}

}

}
Loading