Skip to content

Commit

Permalink
feat add func to handle token and cached
Browse files Browse the repository at this point in the history
  • Loading branch information
wirapratamaz committed May 28, 2024
1 parent d7189c8 commit b32e617
Show file tree
Hide file tree
Showing 2 changed files with 155 additions and 97 deletions.
20 changes: 5 additions & 15 deletions src/helper/ChainFactory.mo
Original file line number Diff line number Diff line change
Expand Up @@ -51,23 +51,13 @@ module ChainFactory {
let tf = TokenF.TokenFactory(true);
switch (chain) {
case (#eth_mainnet) {
var tx_receipt = await web3.getTransactionReceipt(tx, chain, token);
switch (tx_receipt) {
case null return null;
case (?tx_receipt) {
return ?tx_receipt.status;
};
};
// Add your logic here for eth_mainnet without using getTransactionReceipt
Debug.trap("not implemented for eth_mainnet without getTransactionReceipt");
};
case (#eth_testnet) {
web3 := Web3Helper.Web3("https://sepolia.publicgoods.network", true);
var tx_receipt = await web3.getTransactionReceipt(tx, chain, token);
switch (tx_receipt) {
case null return null;
case (?tx_receipt) {
return ?tx_receipt.status;
};
};
// Add your logic here for eth_testnet without using getTransactionReceipt
Debug.trap("not implemented for eth_testnet without getTransactionReceipt");
};
case (#icp_mainnet) {
let ok = await checkIcpForBlockConfirmed(tx, chain, token);
Expand Down Expand Up @@ -115,4 +105,4 @@ module ChainFactory {
};
};
};
};
};
232 changes: 150 additions & 82 deletions src/xrc_canister/main.mo
Original file line number Diff line number Diff line change
Expand Up @@ -40,16 +40,20 @@ import TokenFactory "../helper/TokenFactory";

import Canistergeek "mo:canistergeek/canistergeek";

actor {
shared ({ caller = owner }) actor class Main() = this {

private let FX_CACHE_TIME_SECONDS : Nat = 600; //10 min cache

let IS_TEST_MODE : Bool = true; //enables all test tokens and chains

private stable var quoteStore = Map.new<Text, SupdTypes.TokenQuote>();
private stable var currencyStore = Map.new<Text, SupdTypes.CurrencyQuote>();

stable var _canistergeekMonitorUD : ?Canistergeek.UpgradeData = null;
private let canistergeekMonitor = Canistergeek.Monitor();
stable var _canistergeekLoggerUD : ?Canistergeek.LoggerUpgradeData = null;
private let canistergeekLogger = Canistergeek.Logger();
private let adminPrincipal : Text = "5axoh-2g6uz-nghr4-d44yu-rotpl-vqms2-j7zw3-ibegk-ngfwi-wxjl2-7qe";

system func preupgrade() {
_canistergeekMonitorUD := ?canistergeekMonitor.preupgrade();
Expand Down Expand Up @@ -100,40 +104,40 @@ actor {
};

// extract the current change rate from the XRC canister and return it as a float value (e.g. 1.23)
public func get_exchange_rate(symbol : Text) : async Float {
// public func get_exchange_rate(symbol : Text) : async Float {

let request : XRC.GetExchangeRateRequest = {
base_asset = {
symbol = symbol;
class_ = #Cryptocurrency;
};
quote_asset = {
symbol = "USDT";
class_ = #Cryptocurrency;
};
// Get the current rate.
timestamp = null;
};
// let request : XRC.GetExchangeRateRequest = {
// base_asset = {
// symbol = symbol;
// class_ = #Cryptocurrency;
// };
// quote_asset = {
// symbol = "USDT";
// class_ = #Cryptocurrency;
// };
// // Get the current rate.
// timestamp = null;
// };

// Every XRC call needs 1B cycles.
Cycles.add<system>(1_000_000_000); // Specify the capability explicitly
let response = await XRC.get_exchange_rate(request);
// Print out the response to get a detailed view.
Dbg.print(debug_show (response));
// Return 0.0 if there is an error for the sake of simplicity.
switch (response) {
case (#Ok(rate_response)) {
let float_rate = Float.fromInt(Nat64.toNat(rate_response.rate));
let float_divisor = Float.fromInt(Nat32.toNat(10 ** rate_response.metadata.decimals));
return float_rate / float_divisor;
};
case _ {
return 0.0;
};
};
};
// // Every XRC call needs 1B cycles.
// Cycles.add<system>(1_000_000_000); // Specify the capability explicitly
// let response = await XRC.get_exchange_rate(request);
// // Print out the response to get a detailed view.
// Dbg.print(debug_show (response));
// // Return 0.0 if there is an error for the sake of simplicity.
// switch (response) {
// case (#Ok(rate_response)) {
// let float_rate = Float.fromInt(Nat64.toNat(rate_response.rate));
// let float_divisor = Float.fromInt(Nat32.toNat(10 ** rate_response.metadata.decimals));
// return float_rate / float_divisor;
// };
// case _ {
// return 0.0;
// };
// };
// };

/* -------------------CHAINS----------------------- */
/* -------------------CHAINS----------------------- */
//get chains supported
public query func getChains() : async [SupdTypes.TokenChain] {
let f = TokenF.TokenFactory(true);
Expand All @@ -149,18 +153,18 @@ actor {
return t;
};

//get tokens - serves from cache
public query func getTokenWithQuotes() : async [SupdTypes.Token] {
//get tokens - serves from cache
public query func getTokensWithQuotes() : async [SupdTypes.Token] {
let f = TokenF.TokenFactory(true);
let bootstrap = f.getTokens();
var result = List.nil<SupdTypes.Token>();
for (token in bootstrap.vals()){
let cached : ?SupdTypes.TokenQuote = getTokenQuoteCahced(token.name);
switch(cached) {
case null{
for (token in bootstrap.vals()) {
let cached : ?SupdTypes.TokenQuote = getTokenQuoteCached(token.name);
switch (cached) {
case null {
result := List.push(token, result);
};
case(?cached){
case (?cached) {
var clone : SupdTypes.Token = {
name = token.name;
token_type = token.token_type;
Expand All @@ -179,36 +183,40 @@ actor {
};
//Debug.print("getTokensWithQuotes result: " # debug_show(result));
return List.toArray(result);
};
};

//get top n token quotes ordered by date desc
public query func getTokenQuoteHistory(token : Text, page_size : Nat) : async ?[SupdTypes.TokenQuote] {
assert(Text.size(token) > 2);
assert(page_size > 0 and page_size <= 100);
let filter = Text.toUppercase(token);
let ok = Map.filterDesc(quoteStore, thash, func(k : Text, yo : SupdTypes.TokenQuote) : Bool {
yo.name == filter;
});
let filteredQuoteHistory = Map.vals(ok);
assert (Text.size(token) > 2);
assert (page_size > 0 and page_size <= 100);
let filter = Text.toUppercase(token);
let ok = Map.filterDesc(
quoteStore,
thash,
func(k : Text, yo : SupdTypes.TokenQuote) : Bool {
yo.name == filter;
},
);
let filteredQuoteHistory = Map.vals(ok);
let result : [SupdTypes.TokenQuote] = Iter.toArray(filteredQuoteHistory);
let top10_result = List.take(List.fromArray(result), page_size);
let a = List.toArray(top10_result);
return ?a;
};

/* -------------------FX----------------------- */
//get currencies supported - serves from cache
public shared query func getCurrencies() : async ?[SupdTypes.CurrencyQuote] {
//get currencies supported - serves from cache
public shared query func getCurrencies() : async ?[SupdTypes.CurrencyQuote] {
let f = CurrencyF.CurrencyFactory("", true);
let bootstrap = f.getFxBootstrap();
var result = List.nil<SupdTypes.CurrencyQuote>();
for(currency in bootstrap.vals()){
var cached : ?SupdTypes.CurrencyQuote = getQuoteCached(currency.name);
switch(cached){
case null{
for (currency in bootstrap.vals()) {
var cached : ?SupdTypes.CurrencyQuote = getQuoteCached(currency.name);
switch (cached) {
case null {
result := List.push(currency, result);
};
case(?cached){
case (?cached) {
result := List.push(cached, result);
};
};
Expand All @@ -218,60 +226,120 @@ actor {

//get fx quote with details - serves from cache
public func getQuote(fx_symbol : Text) : async ?SupdTypes.CurrencyQuote {
let cached = getQuoteCached(fx_symbol);
if(cached != null){
let cached = getQuoteCached(fx_symbol);
if (cached != null) {
return cached;
};
let this_canister_id = await canisterId(); //TODO: optimize
let factory = CurrencyF.CurrencyFactory(this_canister_id, true);
let match = await factory.getCurrency(fx_symbol);
let quote = await factory.getQuote(match);
switch(quote){
switch (quote) {
case null return null;
case(?quote){
case (?quote) {
let anon = Principal.fromText("2vxsx-fae");
logPriceQuote(anon, quote, "getQuote testing");
return ?quote;
};
};
};

private func logPriceQuote(caller : Principal, quote : SupdTypes.CurrencyQuote, log_msg : Text) {
let time_now = Utils.now_seconds();
let p = Principal.toText(caller);
var l : SupdTypes.CurrencyQuote = {
name = quote.name;
symbol = quote.symbol;
value = quote.value;
value_str = quote.value_str;
created_at = time_now;
source = quote.source;
currency_type = quote.currency_type;
description = quote.description;
};
let f = p # Nat64.toText(quote.created_at) # log_msg;
let sha = Utils.textToSha(f);
let ok = Map.put(currencyStore, thash, sha, l);
Debug.print("I logged a PRICE quote " # debug_show (l));
return;
};

//get count of fx quotes
public query func getQuoteHistoryCount() : async Nat {
let count = Map.size(currencyStore);
public query func getQuoteHistoryCount() : async Nat {
let count = Map.size(currencyStore);
return count;
};

//get top n fx quotes ordered by date desc
public query func getQuoteHistory(fx_symbol : Text, page_size : Nat) : async ?[SupdTypes.CurrencyQuote] {
assert(Text.size(fx_symbol) == 3);
assert(page_size > 0 and page_size <= 100);
let filter = Text.toUppercase(fx_symbol);
let ok = Map.filterDesc(currencyStore, thash, func(k : Text, yo : SupdTypes.CurrencyQuote) : Bool {
yo.name == filter;
});
assert (Text.size(fx_symbol) == 3);
assert (page_size > 0 and page_size <= 100);
let filter = Text.toUppercase(fx_symbol);
let ok = Map.filterDesc(
currencyStore,
thash,
func(k : Text, yo : SupdTypes.CurrencyQuote) : Bool {
yo.name == filter;
},
);
let filteredQuoteHistory = Map.vals(ok);
let result : [SupdTypes.CurrencyQuote] = Iter.toArray(filteredQuoteHistory);
let top10_result = List.take(List.fromArray(result), page_size);
let a = List.toArray(top10_result);
return ?a;
};

//get most recent fx quote ttl FX_CACHE_TIME_SECONDS
private func getQuoteCached(fx_symbol : Text) : ?SupdTypes.CurrencyQuote {
assert (Text.size(fx_symbol) == 3);
let filter = Text.toUppercase(fx_symbol);
let ok = Map.filterDesc(
currencyStore,
thash,
func(k : Text, yo : SupdTypes.CurrencyQuote) : Bool {
Text.toUppercase(yo.name) == filter;
},
);
if (Map.size(ok) > 0) {
let most_recent = Iter.toArray(Map.vals(ok))[0];
switch (?most_recent) {
case null return null;
case (?most_recent) {
let now = Nat64.toNat(Utils.now_seconds());
let cached_ts = Nat64.toNat(most_recent.created_at);
let diff = Nat.sub(now, cached_ts);
//Debug.print("DIFFERENCE: " # debug_show(diff));
if (diff > FX_CACHE_TIME_SECONDS) {
Debug.print("CACHE KEY: " # debug_show (filter));
return null;
};
//Debug.print("SERVING YOU FROM currencyStore CACHE for key: " # debug_show(filter));
return ?most_recent;
};
};
};
return null;
};

//get token with most recent price from cache FX_CACHE_TIME_SECONDS
private func getTokenQuoteCached(token : Text) : ?SupdTypes.TokenQuote {
let filter = Text.toUppercase(token);
let ok = Map.filterDesc(quoteStore, thash, func(k : Text, yo : SupdTypes.TokenQuote) : Bool {
Text.toUppercase(yo.name) == filter;
});
if(Map.size(ok) > 0){
private func getTokenQuoteCached(token : Text) : ?SupdTypes.TokenQuote {
let filter = Text.toUppercase(token);
let ok = Map.filterDesc(
quoteStore,
thash,
func(k : Text, yo : SupdTypes.TokenQuote) : Bool {
Text.toUppercase(yo.name) == filter;
},
);
if (Map.size(ok) > 0) {
let most_recent = Iter.toArray(Map.vals(ok))[0];
switch(?most_recent){
switch (?most_recent) {
case null return null;
case(?most_recent){
case (?most_recent) {
let now = Nat64.toNat(Utils.now_seconds());
let cached_ts = Nat64.toNat(most_recent.created_at);
let cached_ts = Nat64.toNat(most_recent.created_at);
let diff = Nat.sub(now, cached_ts);
if(diff > FX_CACHE_TIME_SECONDS){
if (diff > FX_CACHE_TIME_SECONDS) {
return null;
};
//Debug.print("SERVING YOU FROM quoteStore CACHE for key: " # debug_show(filter));
Expand All @@ -283,22 +351,22 @@ actor {
};

/* ------------------- CANISTERGEEK ----------------------- */
public query ({caller}) func getCanisterMetrics(parameters: Canistergeek.GetMetricsParameters): async ?Canistergeek.CanisterMetrics {
public query ({ caller }) func getCanisterMetrics(parameters : Canistergeek.GetMetricsParameters) : async ?Canistergeek.CanisterMetrics {
validateCaller(caller);
canistergeekMonitor.getMetrics(parameters);
};

public shared ({caller}) func collectCanisterMetrics(): async () {
public shared ({ caller }) func collectCanisterMetrics() : async () {
validateCaller(caller);
canistergeekMonitor.collectMetrics();
};
public query ({caller}) func getCanisterLog(request: ?Canistergeek.CanisterLogRequest) : async ?Canistergeek.CanisterLogResponse {

public query ({ caller }) func getCanisterLog(request : ?Canistergeek.CanisterLogRequest) : async ?Canistergeek.CanisterLogResponse {
validateCaller(caller);
return canistergeekLogger.getLog(request);
};
private func validateCaller(principal: Principal) : () {

private func validateCaller(principal : Principal) : () {
//data is available only for specific principal
if (not (Principal.toText(principal) == adminPrincipal)) {
Prelude.unreachable();
Expand Down

0 comments on commit b32e617

Please sign in to comment.