Kaspa桌面插件(iOS)5.14

5.14更新

⬆️显示效果⬆️
⬆️显示效果⬆️
//使用方法:
//                 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);
}