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);
}