-
Notifications
You must be signed in to change notification settings - Fork 1
Basic wallet integration mockup #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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(); | ||
| } | ||
|
|
||
| } | ||
| } |
| 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 | ||
| { | ||
| /// <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); | ||
|
|
||
| } | ||
| } | ||
| 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 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
| } | ||
| } | ||
|
|
||
| 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) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
| } | ||
|
|
||
| } | ||
| } | ||
| 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> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
| } | ||
|
|
||
| } | ||
|
|
||
| } | ||
There was a problem hiding this comment.
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?