KaspaWidget(iOS)5.14

Updated on May 14th.

⬆️Display⬆️
⬆️Display⬆️
//Usage

//Download and install Scriptable on your phone.https://apps.apple.com/us/app/scriptable/id1405459188 You can copy and open the App Store address, or simply search for it.
//Long press on a blank area of your phone's home screen, and select the "+" icon in the upper left or right corner.
//Scroll down to find Scriptable in the menu, and select it. Then, slide the widget to the largest size, and click "Add Widget" at the bottom.
//Open Scriptable, select the "+" icon in the upper right corner, and copy the entire contents of this script into it. Then, click "Done."
//Long press on the widget on your home screen, and select the script you just saved. Wait for a moment, and it will display the information.
//Feel free to donate whatever amount you want 🍺/🥤/🥛/☕️ kaspa:qpz2zwxd4krj8ju3q9nueu5yc7hjld0z6c7ythrrdd0k3awy3td2c2hzvslke 👈❤️ . Also, if anyone has strong front-end experience, please feel free to use it and share it with me.

//👇Your Wallet Addresses👇
const walletAddresses = [
  "kaspa:qqetp7ct8kqss99fxmymyz5t3fezppxp0t58wl6pawp27elqd46uudme00cl0",
   "kaspa:qpjw6xx9x5dv90ju68msey9p2s87efqk7segu9k4a4lrr024qtthukum5kgyy",
   "kaspa:qpwxu5fwzj8etlngl5kyvcpx8yk3jkqwrtvga6f33xe97f4x5kvq6z8swne8f",
// You can add multiple wallet addresses, it is recommended not to exceed three. If you don't have more wallet addresses, you can delete or comment out the extra wallet addresses by adding "//" in front of them.
];

//👆Fill in the wallet addresses within the double quotation marks based on the format above. Considering the feature of compounding transactions, you may need to update your wallet addresses here after each compounding to get the latest balance.


const apiDomain = "https://api.kaspa.org";
const coingeckoDomain = "https://api.coingecko.com";
const apiUrls = {
  blockreward: `${apiDomain}/info/blockreward`,
  perGHreward: "https://api.minerstat.com/v2/coins?list=KAS",
  price: `${coingeckoDomain}/api/v3/simple/price?ids=kaspa&vs_currencies=usd&include_market_cap=true&include_24hr_vol=true&include_24hr_change=true&precision=6`,
  hashrate: `${apiDomain}/info/hashrate`,
  coinsupply: `${apiDomain}/info/coinsupply`,
  balance: `${apiDomain}/addresses/${walletAddresses}/balance`,
  halving: `${apiDomain}/info/halving`,
  marketCapRank: `${coingeckoDomain}/api/v3/coins/kaspa?tickers=false&market_data=true&community_data=false&developer_data=false&sparkline=false`
};


async function fetchData(url) {
  const response = await new Request(url).loadString();
  return JSON.parse(response);
}

const blockrewardData = await fetchData(apiUrls.blockreward);
const priceData = (await fetchData(apiUrls.price)).kaspa;
const hashrateData = await fetchData(apiUrls.hashrate);
const coinsupplyData = await fetchData(apiUrls.coinsupply);
const balanceData = await fetchData(apiUrls.balance);
const perGHrewardData = (await fetchData(apiUrls.perGHreward))[0];
const halvingData = await fetchData(apiUrls.halving);
const marketCapRankData = await fetchData(apiUrls.marketCapRank);
const marketCapRank = marketCapRankData.market_cap_rank;

const KAS_SYMBOL = "𐤊";

const widget = new ListWidget();
      widget.setPadding(16, 0, 0, 0);
      
const gradient = new LinearGradient();
      gradient.locations = [0, 1];
      gradient.colors = [
      new Color("#70dabf"),
      new Color("#87ebc8")
  ];
    widget.backgroundGradient = gradient;
  
//title      

const titleBox = widget.addStack();
      titleBox.setPadding(0, 16, 0, 16);

const title = titleBox.addText("                   Kaspa");
      title.textColor = new Color("#fff");
      title.font = Font.boldSystemFont(25);

      titleBox.addSpacer(null);

const blockrewardBox = titleBox.addStack();
blockrewardBox.layoutVertically();
blockrewardBox.spacing = 6;

const blockrewardTitle = blockrewardBox.addText("Block Reward ");
blockrewardTitle.textColor = new Color("#fff");
blockrewardTitle.font = Font.systemFont(10);

const blockreward = blockrewardBox.addText(blockrewardData.blockreward.toFixed(2).toString() + ` ${KAS_SYMBOL}`);
blockreward.textColor = new Color("#fff");
blockreward.font = Font.boldSystemFont(15);

const contentBox = widget.addStack();
      contentBox.backgroundColor = new Color("#F4F7FA");
      contentBox.cornerRadius = 18;
      contentBox.spacing = 15;
      contentBox.setPadding(16, 16, 16, 16);
      contentBox.layoutVertically();

//row1
const row1 = contentBox.addStack();
      row1.spacing = 15;    
      

const priceBox = row1.addStack();
const priceWrap = priceBox.addStack();
      priceWrap.spacing = 6;
      priceWrap.layoutVertically();
for (const key in priceData) {
    if(key === 'usd'){
        const row = priceWrap.addStack();
        row.spacing = 6;
        row.layoutVertically();
        const keyText = row.addText("Price");
        keyText.textColor = Color.gray();
        keyText.font = Font.systemFont(12);
        const valueText = row.addText(('$ ') + priceData[key].toFixed(4).toString());
        valueText.textColor = Color.black();
        valueText.font = Font.boldSystemFont(14);
    }
  
}
//row1.addSpacer(5);

const changeBox = row1.addStack();
const changeWrap = changeBox.addStack();
changeWrap.spacing = 5;
changeWrap.layoutVertically();

for (const key in priceData) {
    if (key === 'usd_24h_change') {
        let row = changeWrap.addStack()
        row.spacing = 6
        row.layoutVertically();
        let keyText = row.addText("24hChan")
        keyText.textColor = Color.gray();
        keyText.font = Font.systemFont(12);

        let value = priceData[key]
        let valueText
        if (value >= 0) {
            valueText = row.addText("+" + value.toFixed(2).toString() + '%' + ' ↑')
            valueText.textColor = Color.green()
            valueText.font = Font.boldSystemFont(13);
        } else {
            valueText = row.addText(value.toFixed(2).toString() + '%' + ' ↓' )
            valueText.textColor = Color.red()
            valueText.font = Font.boldSystemFont(13); 
        }
    }
}
//row1.addSpacer(5);

const volWrap = row1.addStack();
      volWrap.spacing = 6;
      volWrap.layoutVertically();
const volText = volWrap.addText("24hVol");
      volText.textColor = Color.gray();
      volText.font = Font.systemFont(12);
const volValue = priceData['usd_24h_vol'] / 1e6;
const volValueText = volWrap.addText('$ ' + volValue.toFixed(2).toString() + 'M');
      volValueText.textColor = Color.black();
      volValueText.font = Font.boldSystemFont(14);

      //row1.addSpacer(null);
      

const capWrap = row1.addStack();      
      for (const key in priceData){
      
      if (key === 'usd_market_cap'){
        let row = row1.addStack()
        row.spacing = 6
        row.layoutVertically()
        let keyText = row.addText("MarketCap")
        keyText.textColor = Color.gray()
        keyText.font = Font.systemFont(12)
        let value = priceData[key]
        let valueText
        if (value >= 1e9) {
            value = value / 1e9
            valueText = row.addText(value.toFixed(1).toString() + ' B')
        }
        else if (value >= 1e6) {
            value = value / 1e6
            valueText = row.addText(value.toFixed(1).toString() + ' M')
        }
        else {
            valueText = row.addText(value.toFixed(2).toString() + ' USD')
        }
        valueText.textColor = Color.black()
        valueText.font = Font.boldSystemFont(14)
    }
      
      }
      
      
//row2

row2 = contentBox.addStack();
row2.spacing = 15;
      
const hashrateWrap = row2.addStack();
      hashrateWrap.spacing = 6;
      hashrateWrap.layoutVertically();
for (const key in hashrateData) {
    if (key === 'hashrate') {
        const hashrate = Math.round(hashrateData[key]);
        let displayHashrate, unit;

        if (hashrate > 1000) {
            displayHashrate = (hashrate / 1000).toFixed(2);
            unit = ' PH/s';
        } else {
            displayHashrate = hashrate.toFixed(0);
            unit = ' TH/s';
        }

        const row = hashrateWrap.addStack();
        row.spacing = 6;
        row.layoutVertically();
        const keyText = row.addText("Hashrate");
        keyText.textColor = Color.gray();
        keyText.font = Font.systemFont(12);
        const valueText = row.addText(displayHashrate.toString() + unit);
        valueText.textColor = Color.black();
        valueText.font = Font.boldSystemFont(14);
    }
}
//row2.addSpacer(null);

const perGHrewardWrap = row2.addStack();
      perGHrewardWrap.spacing = 6;
      perGHrewardWrap.layoutVertically();
const rewardKey = 'reward';
if (perGHrewardData.hasOwnProperty(rewardKey)) {
  const perGHreward = perGHrewardData[rewardKey] * 1e9 * 24;
  let displayPerGHreward;

  const row = perGHrewardWrap.addStack();
  row.spacing = 6;
  row.layoutVertically();
  const keyText = row.addText("Re/D/GH");
  keyText.textColor = Color.gray();
  keyText.font = Font.systemFont(12);
  const valueText = row.addText(perGHreward.toFixed(2).toString() + ` ${KAS_SYMBOL}`);
  valueText.textColor = Color.black();
  valueText.font = Font.boldSystemFont(14);
}

//row2.addSpacer(null);

const supplyBox = row2.addStack();
const circulatingSupply = coinsupplyData.circulatingSupply;
const maxSupply = coinsupplyData.maxSupply;
const percentage = (circulatingSupply / maxSupply) * 100;
const row = supplyBox.addStack();
row.spacing = 6;
row.layoutVertically();
const keyText = row.addText("    Mined");
keyText.textColor = Color.gray('');
keyText.font = Font.systemFont(12);
const valueText = row.addText(`   ${percentage.toFixed(2)} %`);
valueText.textColor = Color.black();
valueText.font = Font.boldSystemFont(14);

const marketCapRankRow = row2.addStack();
marketCapRankRow.spacing = 6;
marketCapRankRow.layoutVertically();

const marketCapRankKeyText = marketCapRankRow.addText("   MarketRank");
marketCapRankKeyText.textColor = Color.gray('');
marketCapRankKeyText.font = Font.systemFont(12);

const marketCapRankValueText = marketCapRankRow.addText(`     # ${marketCapRank}`);
marketCapRankValueText.textColor = Color.black();
marketCapRankValueText.font = Font.boldSystemFont(14);

//row2.addSpacer(null);
      
//row3
const row3 = contentBox.addStack();
      row3.spacing = 15;
      
const nextHalvingTimestamp = halvingData.nextHalvingTimestamp;
const nextHalvingDate = new Date(nextHalvingTimestamp * 1000);
const nextHalvingAmount = halvingData.nextHalvingAmount;

const now = new Date();
const msToNextHalving = nextHalvingDate - now;
const daysToNextHalving = msToNextHalving / (1000 * 60 * 60 * 24);

const nextHalvingWrap = row3.addStack();
nextHalvingWrap.spacing = 16;
nextHalvingWrap.layoutHorizontally();

const nextHalvingDateRow = nextHalvingWrap.addStack();
nextHalvingDateRow.spacing = 6;
nextHalvingDateRow.layoutVertically();
const nextHalvingDateKeyText = nextHalvingDateRow.addText("Halving Date");
nextHalvingDateKeyText.textColor = Color.gray();
nextHalvingDateKeyText.font = Font.systemFont(12);
const nextHalvingDateValueText = nextHalvingDateRow.addText(nextHalvingDate.toLocaleDateString());
nextHalvingDateValueText.textColor = Color.black();
nextHalvingDateValueText.font = Font.boldSystemFont(14);

row3.addSpacer(null);

const nextHalvingDateCountRow = nextHalvingWrap.addStack();
nextHalvingDateCountRow.spacing = 6;
nextHalvingDateCountRow.layoutVertically();

const nextHalvingDateCountKeyText = nextHalvingDateCountRow.addText("Remaining");
nextHalvingDateCountKeyText.textColor = Color.gray();
nextHalvingDateCountKeyText.font = Font.systemFont(12);

const unit = daysToNextHalving > 1 ? "Days" : "Day"; 
const nextHalvingDateCountValueText = nextHalvingDateCountRow.addText(daysToNextHalving.toFixed(1).toString() + " " + unit);
nextHalvingDateCountValueText.textColor = Color.black();
nextHalvingDateCountValueText.font = Font.boldSystemFont(14);


const daysInCycle = 30;
const percentageToNextHalving = (daysToNextHalving / daysInCycle) * 100;


let countdownColor;
if (percentageToNextHalving >= 60) {
  countdownColor = Color.black();
} else if (percentageToNextHalving >= 30) {
  countdownColor = Color.yellow();
} else {
  countdownColor = Color.red();
}


nextHalvingDateCountValueText.textColor = countdownColor;

row3.addSpacer(null);

const nextHalvingAmountRow = nextHalvingWrap.addStack();
nextHalvingAmountRow.spacing = 6;
nextHalvingAmountRow.layoutVertically();
const nextHalvingAmountKeyText = nextHalvingAmountRow.addText("Next Reward");
nextHalvingAmountKeyText.textColor = Color.gray();
nextHalvingAmountKeyText.font = Font.systemFont(12);
const nextHalvingAmountValueText = nextHalvingAmountRow.addText(nextHalvingAmount.toFixed(2).toString() + ` ${KAS_SYMBOL}     `);
nextHalvingAmountValueText.textColor = Color.black();
nextHalvingAmountValueText.font = Font.boldSystemFont(14);

//row4
const row4 = contentBox.addStack();
row4.spacing = 15;

const balanceBox = row4.addStack();
balanceBox.layoutVertically();
balanceBox.spacing = 6;


  const time = new Date();
  const balanceKeyText = balanceBox.addText("Wallet Balance " + time.toLocaleDateString() + " " + time.toLocaleTimeString());
  balanceKeyText.textColor = Color.gray();
  balanceKeyText.font = Font.systemFont(12);
  balanceKeyText.spacing=6;

const walletBalanceFontSize = walletAddresses.length <= 2 ? 16 : 15;
const walletValueFontSize = walletAddresses.length <= 2 ? 15 : 14;


for (
  
  const walletAddress of walletAddresses) {
  const balanceData = await fetchData(`${apiDomain}/addresses/${encodeURIComponent(walletAddress)}/balance`);
  const balance = balanceData.balance / 1e8;
  const balanceFormatted = new Intl.NumberFormat('en-US', { style: 'decimal', minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(balance
  
  );
  

  const walletBalanceBox = balanceBox.addStack();
  walletBalanceBox.spacing = 6;
  walletBalanceBox.layoutHorizontally();

const price = priceData.usd;
const walletValue = balance * price;
const walletValueFormatted = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(walletValue);



let balanceEmoji;
if (balance >= 1e9 && balance < 10e9) {
  balanceEmoji = "🔱";
} else if (balance >= 100e6 && balance < 1e9) {
  balanceEmoji = "🐋";
} else if (balance >= 10e6 && balance < 100e6) {
  balanceEmoji = "🐳";
} else if (balance >= 1e6 && balance < 10e6) {
  balanceEmoji = "🦈";
} else if (balance >= 100e3 && balance < 1e6) {
  balanceEmoji = "🐬";
} else if (balance >= 10e3 && balance < 100e3) {
  balanceEmoji = "🐟";
} else if (balance >= 1e3 && balance < 10e3) {
  balanceEmoji = "🐙";
} else if (balance >= 100 && balance < 1e3) {
  balanceEmoji = "🦀";
} else {
  balanceEmoji = "🦐";
}

const walletBalanceText = walletBalanceBox.addText(`${balanceEmoji} ${balanceFormatted} 𐤊`);
  walletBalanceText.textColor = new Color("#70dabf");
  walletBalanceText.font = new Font("Menlo-Bold", walletBalanceFontSize);

  const walletValueText = walletBalanceBox.addText(walletValueFormatted);
  walletValueText.textColor = Color.gray();
  walletValueText.font = new Font("Menlo", walletValueFontSize);
// API References
const apiBox = widget.addStack();
apiBox.setPadding(8, 8, 0, 0);
apiBox.layoutVertically();
apiBox.addSpacer();

const apiText = apiBox.addText("/* Kaspa, CoinGecko, Minerstat API/");
apiText.textColor = new Color("#808080");
apiText.font = new Font("Menlo-Regular", 7);

}

const twitterBox = widget.addStack();
twitterBox.spacing = 6;
twitterBox.setPadding(0, 8, 16, 0);
twitterBox.addSpacer();

const heart = twitterBox.addText("❤️");
heart.textColor = new Color("#fff");
heart.font = new Font("HelveticaNeue-Bold", 12);

const twitterImg = twitterBox.addImage(twitterLogo());
twitterImg.imageSize = new Size(16, 16);
const twitterUsername = twitterBox.addText("@Dodo13080274   ");
twitterUsername.textColor = new Color("#fff");
twitterUsername.font = new Font("HelveticaNeue-Bold", 14);



if (!config.runsInWidget) {
    await widget.presentLarge();
  }

widget.url = "https://twitter.com/Dodo13080274";

Script.setWidget(widget);
Script.complete();


function twitterLogo() {
  const data = Data.fromBase64String(
    "iVBORw0KGgoAAAANSUhEUgAAABsAAAAWCAYAAAAxSueLAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAErSURBVHgBvZWBdYIwEIYvvA7gCGxQNigjOAIblBHqBt0AukE7QegEugFsoBv8XkzwRQyYA/R773+RwOU/7yAheiGKFgJgw8OWlbqpg1Lq91FQ5gIlRjmrxT2tu7dxY8VK/cCatY81NMGIpzQxiRf/zspY+iaLcTTF0bBMYp9+pu2gDMVYtCu5hHq4gA48ZObygNkW8VR93Ju3xo41XNhcmwZ3PP65knQk4z84C/uSrE3er594RqbhmJH5I079DzUwy2ll+AO/eviv/o7Wp/EvEi+D5gmGP5N3uZwFwlvQHFIK/TOPD9aBllNztbrJJzibEstpEbflXQy/sYyCJMD2TrOOkPFFc4A9RvRTjWAPPOnWVUoWr2AbKy2bZmUkAbZkpk/7CIMjRo6fKc6UxWq3r/OykAAAAABJRU5ErkJggg=="
  );
  return Image.fromData(data);
}