# Kaspa桌面插件(iOS)5.14

By [Dodo](https://paragraph.com/@dodo-3) · 2023-05-10

---

5.14更新

![⬆️显示效果⬆️](https://storage.googleapis.com/papyrus_images/e6b21b37fe62bcac29768b60d815a7629443b5bdbf22a62f9032563e1d1c013a.jpg)

⬆️显示效果⬆️

    //使用方法：
    //                 1、手机上下载安装Scriptable；应用商店地址可复制去打开或者直接去搜https://apps.apple.com/cn/app/scriptable/id1405459188
    //                 2、手机桌面空白出长按，引出左上角+号，点添加；
    //                 3、出现的界面里下拉找到刚刚安装的Scriptable，选中后右滑到最大的显示效果，按下面添加小组件；
    //                 4、进入Scriptable，选择左上角或者右上角“+”号将本文全部复制进去，随后按右上角Done；
    //                 5、长按刚刚添加到桌面的小工具选择刚刚保存的脚本，等一会儿就可以显示了；
    //⚠️⚠️⚠️需科学上网环境，后面应该会考虑设置Server，我也在等rust😂
    //你们要是想投喂也是可以的，一块两块不嫌少 十万八万不嫌多 🍺 kaspa:qpz2zwxd4krj8ju3q9nueu5yc7hjld0z6c7ythrrdd0k3awy3td2c2hzvslke 👈❤️
    //另外有丰富前端经验的欢迎拿去砸碎重来只要做好记得发我用
    
    //👇你的钱包地址👇
    const walletAddresses = [
      "kaspa:qqetp7ct8kqss99fxmymyz5t3fezppxp0t58wl6pawp27elqd46uudme00cl0",
       "kaspa:qpjw6xx9x5dv90ju68msey9p2s87efqk7segu9k4a4lrr024qtthukum5kgyy",
       "kaspa:qpwxu5fwzj8etlngl5kyvcpx8yk3jkqwrtvga6f33xe97f4x5kvq6z8swne8f",
      // 可添加多个钱包地址，建议不超过三条
    ];
    
    //👆根据上面格式，在双引号内填入钱包地址，考虑到合并交易特性，每次合并之后你可能需要在这里更新你的钱包地址获取最新余额
    
    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/%walletAddress%/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 = new Font("HelveticaNeue-Bold", 25);
    
          titleBox.addSpacer(null);
    
    const blockrewardBox = titleBox.addStack();
    blockrewardBox.layoutVertically();
    blockrewardBox.spacing = 6;
    
    const blockrewardTitle = blockrewardBox.addText("    区块奖励");
    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("当前价格");
            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("24H涨跌")
            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(14);
            } else {
                valueText = row.addText(value.toFixed(2).toString() + '%' + ' ↓' )
                valueText.textColor = Color.red()
                valueText.font = Font.boldSystemFont(14); 
            }
        }
    }
    //row1.addSpacer(5);
    
    const volWrap = row1.addStack();
          volWrap.spacing = 6;
          volWrap.layoutVertically();
    const volText = volWrap.addText("24H成交");
          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("当前市值")
            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(2);
                unit = ' TH/s';
            }
    
            const row = hashrateWrap.addStack();
            row.spacing = 6;
            row.layoutVertically();
            const keyText = row.addText("全网算力");
            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("收益/日/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("已挖占比");
    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("   市值排名");
    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("下次减产");
    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("距减产");
    nextHalvingDateCountKeyText.textColor = Color.gray();
    nextHalvingDateCountKeyText.font = Font.systemFont(12);
    const nextHalvingDateCountValueText = nextHalvingDateCountRow.addText(daysToNextHalving.toFixed(1).toString() + " 天");
    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("减产后奖励");
    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("钱包余额 " + 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 verText = apiBox.addText("-更新于2023年5月14日-");
    verText.textColor = new Color("#000");
    verText.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);
    }

---

*Originally published on [Dodo](https://paragraph.com/@dodo-3/kaspa-ios-5-14)*
