Skip to content

Instantly share code, notes, and snippets.

@arthur-remy
Last active February 19, 2025 22:15
Show Gist options
  • Save arthur-remy/dace82f4586944d43090eaa28527531f to your computer and use it in GitHub Desktop.
Save arthur-remy/dace82f4586944d43090eaa28527531f to your computer and use it in GitHub Desktop.

Metamask Debit Card Spend Cap Widget for iOS

This widget uses Scriptable.app on iOS to fetch your available balance for spending on the Metamask debit card in USDC and USDTon the Linea.

Setup

  1. Install Scriptable: Download Scriptable from the App Store.
  2. Create the Script:
    • Open Scriptable.
    • Create a new script and paste in the widget code.
    • Replace 0xYOUR_ADDRESS_HERE with your Ethereum address.
    • Adjust token addresses and metamask_card_contract if needed.
    • Save the script.
  3. Add Widget to Home Screen:
    • Long press on your home screen and tap the "+" icon.
    • Search for and select the Scriptable widget.
    • Choose your preferred widget size.
    • Tap Edit Widget.
    • Under Script, select the script you saved.
    • Tap Done to add the widget to your home screen.

Automation

  1. Open the Shortcuts app.
  2. Create a new automation (e.g., time-based or when unlocking your device).
  3. Add the "Run Script" action from Scriptable and select your widget script.
  4. Save the automation to have your widget update automatically.

Enjoy!

// Replace with your Ethereum address
const user_address = "0xYOUR_ADDRESS_HERE";
const metamask_card_contract = "0x9dd23a4a0845f10d65d293776b792af1131c7b30";
const tokens = [
{ symbol: "USDC", address: "0x176211869ca2b568f2a7d4ee941e073a821ee1ff" },
{ symbol: "USDT", address: "0xa219439258ca9da29e9cc4ce5596924745e12b93" }
];
const RPC_URL = "https://rpc.linea.build";
// Pads an address to 32 bytes (64 hex chars) without "0x"
function padAddress(addr) {
const a = addr.toLowerCase().replace("0x", "");
return "0".repeat(64 - a.length) + a;
}
// Makes an eth_call via the Linea RPC endpoint
async function ethCall(contract, data) {
let req = new Request(RPC_URL);
req.method = "POST";
req.headers = { "Content-Type": "application/json" };
req.body = JSON.stringify({
jsonrpc: "2.0",
id: 1,
method: "eth_call",
params: [{
to: contract,
data: data
}, "latest"]
});
let res = await req.loadJSON();
if (res.error || !res.result) {
throw new Error("eth_call error: " + JSON.stringify(res.error || res));
}
return BigInt(res.result);
}
// For a given token, fetch balance and allowance, then return the lower value
async function getTokenMin(token) {
const balanceData = "0x70a08231" + padAddress(user_address);
const allowanceData = "0xdd62ed3e" + padAddress(user_address) + padAddress(metamask_card_contract);
const [balance, allowance] = await Promise.all([
ethCall(token.address, balanceData),
ethCall(token.address, allowanceData)
]);
return balance < allowance ? balance : allowance;
}
(async () => {
const minValues = await Promise.all(tokens.map(getTokenMin));
const total = minValues.reduce((sum, value) => sum + value, BigInt(0));
// Assuming 6 decimals for both tokens
const displayValue = (Number(total) / 1e6).toLocaleString(undefined, {
minimumFractionDigits: 2,
maximumFractionDigits: 2
});
let widget = new ListWidget();
widget.addText("🦊💳 cap: $" + displayValue);
Script.setWidget(widget);
widget.presentSmall();
Script.complete();
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment