chain-insights 0.73.1 → 0.73.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs CHANGED
@@ -122,7 +122,7 @@ function fail(err) {
122
122
  function registerSubscriptionCommands(program, command) {
123
123
  program.command("subscription").description("Buy and check the Chain Insights subscription, paid in $CIA on Robinhood Chain").addCommand(command("topup").description("Show the wallet address and QR code to fund with $CIA and ETH on Robinhood Chain").option("--no-open", "Print the top-up page URL without opening a browser").option("--json", "Print machine-readable top-up facts").action(async (opts) => {
124
124
  try {
125
- const { runSubscriptionTopup } = await Promise.resolve().then(() => require("./topup-CWFRNQIx.cjs"));
125
+ const { runSubscriptionTopup } = await Promise.resolve().then(() => require("./topup--nWtDnWP.cjs"));
126
126
  await runSubscriptionTopup(opts);
127
127
  } catch (err) {
128
128
  fail(err);
@@ -592,7 +592,7 @@ program.command("wallet").description("Manage the local Base USDC payment wallet
592
592
  })).addCommand(createCliCommand("topup").description("Open a local browser page to top up the payment wallet").option("--no-open", "Print the top-up URL without opening a browser").option("--json", "Print machine-readable top-up metadata").action(async (opts) => {
593
593
  try {
594
594
  const { assertWalletKeyNotPublic, buildTopupInfo, getWalletAccount } = await Promise.resolve().then(() => require("./tools-Cmnq7yip.cjs")).then((n) => n.tools_exports);
595
- const { startTopupServer } = await Promise.resolve().then(() => require("./topup-server-CcW4OZJv.cjs")).then((n) => n.topup_server_exports);
595
+ const { startTopupServer } = await Promise.resolve().then(() => require("./topup-server-BkGM8P99.cjs")).then((n) => n.topup_server_exports);
596
596
  const account = await getWalletAccount();
597
597
  assertWalletKeyNotPublic(account);
598
598
  const url = await startTopupServer(account);
package/dist/cli.mjs CHANGED
@@ -120,7 +120,7 @@ function fail(err) {
120
120
  function registerSubscriptionCommands(program, command) {
121
121
  program.command("subscription").description("Buy and check the Chain Insights subscription, paid in $CIA on Robinhood Chain").addCommand(command("topup").description("Show the wallet address and QR code to fund with $CIA and ETH on Robinhood Chain").option("--no-open", "Print the top-up page URL without opening a browser").option("--json", "Print machine-readable top-up facts").action(async (opts) => {
122
122
  try {
123
- const { runSubscriptionTopup } = await import("./topup-BE8WP69S.mjs");
123
+ const { runSubscriptionTopup } = await import("./topup-CfFe1bGS.mjs");
124
124
  await runSubscriptionTopup(opts);
125
125
  } catch (err) {
126
126
  fail(err);
@@ -590,7 +590,7 @@ program.command("wallet").description("Manage the local Base USDC payment wallet
590
590
  })).addCommand(createCliCommand("topup").description("Open a local browser page to top up the payment wallet").option("--no-open", "Print the top-up URL without opening a browser").option("--json", "Print machine-readable top-up metadata").action(async (opts) => {
591
591
  try {
592
592
  const { assertWalletKeyNotPublic, buildTopupInfo, getWalletAccount } = await import("./tools-D9ep9E7Q.mjs").then((n) => n.p);
593
- const { startTopupServer } = await import("./topup-server-BsYYbgBA.mjs").then((n) => n.r);
593
+ const { startTopupServer } = await import("./topup-server-B0VKHslr.mjs").then((n) => n.r);
594
594
  const account = await getWalletAccount();
595
595
  assertWalletKeyNotPublic(account);
596
596
  const url = await startTopupServer(account);
package/dist/index.cjs CHANGED
@@ -3,8 +3,8 @@ const require_config = require("./config-BxtMoo9c.cjs");
3
3
  const require_wallet = require("./wallet-CgIAfdxL.cjs");
4
4
  const require_create = require("./create-DdggkF04.cjs");
5
5
  const require_tools = require("./tools-Cmnq7yip.cjs");
6
- const require_topup_server = require("./topup-server-COy0toY-.cjs");
7
- const require_topup_server$1 = require("./topup-server-CcW4OZJv.cjs");
6
+ const require_topup_server = require("./topup-server-C58eWflv.cjs");
7
+ const require_topup_server$1 = require("./topup-server-BkGM8P99.cjs");
8
8
  const require_client = require("./client-C9uwHnBS.cjs");
9
9
  exports.buildTopupInfo = require_tools.buildTopupInfo;
10
10
  exports.createMcpFetchClient = require_client.createMcpFetchClient;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","names":[],"sources":["../src/config/schema.ts","../src/config/index.ts","../src/wallet/index.ts","../src/wallet/create.ts","../src/wallet/tools.ts","../src/wallet/mcp-proxy/topup-server.ts","../src/wallet/topup-server.ts","../src/mcp/client.ts"],"mappings":";;;cA4Da,cAAY,EAAA;;;;;;;;;;;;;;;;GAcvB,EAAA,KAAA;KAEU,qBAAqB,EAAE,aAAa;;;wBC3B1B,cAAc,QAAQ;wBAMtB,WAAW,SAAS,QAAQ,sBAAsB;wBAsDlD,oBAAoB;;;wBCnF1B,0BAA0B,gBAAgB;wBAO1C,4BAA4B,qBAAqB;;;;;;;;wBAW3C,WAAW,qBAAqB;UAwBrC;;EAEf;;;;;;;;wBAuCoB,oBACpB,oBACA,UAAS,6BACR,QAAQ;;;;;;;wBAkEW,cAAc;;;;;wBAwCd,sBAAsB;;;UCnN3B;EACf;EACA;;wBAGc,4BAA4B;wBAI5B,wBAAwB;wBAQxB,0BACd,oBACA,UAAS;;;cCXE;cACA;UAgDI;EACf,SAAS;EACT,YAAY;;EAEZ;;UAGe;EACf;EACA;EACA,iBAAiB;EACjB;EACA,uBAAuB;EACvB;;UAoDe;EACf,SAAS;EACT;EACA;EACA,iBAAiB;EACjB;EACA;EACA;EACA;;UAGe;EACf;EACA;EACA;EACA;IACE,QAAQ;;;;;;;;wBASU,oBAAoB,QAAQ;wBAoB5B,eACpB,SAAS,kBACT,8BACC;wBAsBmB,cACpB,SAAS,kBACT,8BACC;wBAuQa,oBACd,iBACA,qBACA;wBAaoB,uBACpB,UAAU,uBACT,QAAQ;wBAyBK,0BAA0B,QAAQ;wBAK5B,qBAAqB,UAAU,uBAAuB;wBAI5D,eAAe,iBAAiB,oBAAoB;;;wBC1UpD,qBAAqB,4BAA4B;;;wBCjFjD;wBAcM,iBAAiB,SAAS,gCAAgC;;;;;;;;;;;;;;;wBCkKhE,qBAAqB,2BAA2B,4BAAkB"}
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../src/config/schema.ts","../src/config/index.ts","../src/wallet/index.ts","../src/wallet/create.ts","../src/wallet/tools.ts","../src/wallet/mcp-proxy/topup-server.ts","../src/wallet/topup-server.ts","../src/mcp/client.ts"],"mappings":";;;cA4Da,cAAY,EAAA;;;;;;;;;;;;;;;;GAcvB,EAAA,KAAA;KAEU,qBAAqB,EAAE,aAAa;;;wBC3B1B,cAAc,QAAQ;wBAMtB,WAAW,SAAS,QAAQ,sBAAsB;wBAsDlD,oBAAoB;;;wBCnF1B,0BAA0B,gBAAgB;wBAO1C,4BAA4B,qBAAqB;;;;;;;;wBAW3C,WAAW,qBAAqB;UAwBrC;;EAEf;;;;;;;;wBAuCoB,oBACpB,oBACA,UAAS,6BACR,QAAQ;;;;;;;wBAkEW,cAAc;;;;;wBAwCd,sBAAsB;;;UCnN3B;EACf;EACA;;wBAGc,4BAA4B;wBAI5B,wBAAwB;wBAQxB,0BACd,oBACA,UAAS;;;cCXE;cACA;UAgDI;EACf,SAAS;EACT,YAAY;;EAEZ;;UAGe;EACf;EACA;EACA,iBAAiB;EACjB;EACA,uBAAuB;EACvB;;UAoDe;EACf,SAAS;EACT;EACA;EACA,iBAAiB;EACjB;EACA;EACA;EACA;;UAGe;EACf;EACA;EACA;EACA;IACE,QAAQ;;;;;;;;wBASU,oBAAoB,QAAQ;wBAoB5B,eACpB,SAAS,kBACT,8BACC;wBAsBmB,cACpB,SAAS,kBACT,8BACC;wBAuQa,oBACd,iBACA,qBACA;wBAaoB,uBACpB,UAAU,uBACT,QAAQ;wBAyBK,0BAA0B,QAAQ;wBAK5B,qBAAqB,UAAU,uBAAuB;wBAI5D,eAAe,iBAAiB,oBAAoB;;;wBC3UpD,qBAAqB,4BAA4B;;;wBChFjD;wBAcM,iBAAiB,SAAS,gCAAgC;;;;;;;;;;;;;;;wBCkKhE,qBAAqB,2BAA2B,4BAAkB"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/config/schema.ts","../src/config/index.ts","../src/wallet/index.ts","../src/wallet/create.ts","../src/wallet/tools.ts","../src/wallet/mcp-proxy/topup-server.ts","../src/wallet/topup-server.ts","../src/mcp/client.ts"],"mappings":";;;cA4Da,cAAY,EAAA;;;;;;;;;;;;;;;;GAcvB,EAAA,KAAA;KAEU,qBAAqB,EAAE,aAAa;;;wBC3B1B,cAAc,QAAQ;wBAMtB,WAAW,SAAS,QAAQ,sBAAsB;wBAsDlD,oBAAoB;;;wBCnF1B,0BAA0B,gBAAgB;wBAO1C,4BAA4B,qBAAqB;;;;;;;;wBAW3C,WAAW,qBAAqB;UAwBrC;;EAEf;;;;;;;;wBAuCoB,oBACpB,oBACA,UAAS,6BACR,QAAQ;;;;;;;wBAkEW,cAAc;;;;;wBAwCd,sBAAsB;;;UCnN3B;EACf;EACA;;wBAGc,4BAA4B;wBAI5B,wBAAwB;wBAQxB,0BACd,oBACA,UAAS;;;cCXE;cACA;UAgDI;EACf,SAAS;EACT,YAAY;;EAEZ;;UAGe;EACf;EACA;EACA,iBAAiB;EACjB;EACA,uBAAuB;EACvB;;UAoDe;EACf,SAAS;EACT;EACA;EACA,iBAAiB;EACjB;EACA;EACA;EACA;;UAGe;EACf;EACA;EACA;EACA;IACE,QAAQ;;;;;;;;wBASU,oBAAoB,QAAQ;wBAoB5B,eACpB,SAAS,kBACT,8BACC;wBAsBmB,cACpB,SAAS,kBACT,8BACC;wBAuQa,oBACd,iBACA,qBACA;wBAaoB,uBACpB,UAAU,uBACT,QAAQ;wBAyBK,0BAA0B,QAAQ;wBAK5B,qBAAqB,UAAU,uBAAuB;wBAI5D,eAAe,iBAAiB,oBAAoB;;;wBC1UpD,qBAAqB,4BAA4B;;;wBCjFjD;wBAcM,iBAAiB,SAAS,gCAAgC;;;;;;;;;;;;;;;wBCkKhE,qBAAqB,2BAA2B,4BAAkB"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/config/schema.ts","../src/config/index.ts","../src/wallet/index.ts","../src/wallet/create.ts","../src/wallet/tools.ts","../src/wallet/mcp-proxy/topup-server.ts","../src/wallet/topup-server.ts","../src/mcp/client.ts"],"mappings":";;;cA4Da,cAAY,EAAA;;;;;;;;;;;;;;;;GAcvB,EAAA,KAAA;KAEU,qBAAqB,EAAE,aAAa;;;wBC3B1B,cAAc,QAAQ;wBAMtB,WAAW,SAAS,QAAQ,sBAAsB;wBAsDlD,oBAAoB;;;wBCnF1B,0BAA0B,gBAAgB;wBAO1C,4BAA4B,qBAAqB;;;;;;;;wBAW3C,WAAW,qBAAqB;UAwBrC;;EAEf;;;;;;;;wBAuCoB,oBACpB,oBACA,UAAS,6BACR,QAAQ;;;;;;;wBAkEW,cAAc;;;;;wBAwCd,sBAAsB;;;UCnN3B;EACf;EACA;;wBAGc,4BAA4B;wBAI5B,wBAAwB;wBAQxB,0BACd,oBACA,UAAS;;;cCXE;cACA;UAgDI;EACf,SAAS;EACT,YAAY;;EAEZ;;UAGe;EACf;EACA;EACA,iBAAiB;EACjB;EACA,uBAAuB;EACvB;;UAoDe;EACf,SAAS;EACT;EACA;EACA,iBAAiB;EACjB;EACA;EACA;EACA;;UAGe;EACf;EACA;EACA;EACA;IACE,QAAQ;;;;;;;;wBASU,oBAAoB,QAAQ;wBAoB5B,eACpB,SAAS,kBACT,8BACC;wBAsBmB,cACpB,SAAS,kBACT,8BACC;wBAuQa,oBACd,iBACA,qBACA;wBAaoB,uBACpB,UAAU,uBACT,QAAQ;wBAyBK,0BAA0B,QAAQ;wBAK5B,qBAAqB,UAAU,uBAAuB;wBAI5D,eAAe,iBAAiB,oBAAoB;;;wBC3UpD,qBAAqB,4BAA4B;;;wBChFjD;wBAcM,iBAAiB,SAAS,gCAAgC;;;;;;;;;;;;;;;wBCkKhE,qBAAqB,2BAA2B,4BAAkB"}
package/dist/index.mjs CHANGED
@@ -2,7 +2,7 @@ import { i as saveConfig, n as loadConfig, r as resetConfigCache } from "./confi
2
2
  import { a as setWalletPrivateKey, i as normalizeWalletPrivateKey, n as encryptKey, o as walletAddressFromPrivateKey, r as isWalletConfigured, t as decryptKey } from "./wallet-QTo5YCFR.mjs";
3
3
  import { i as isWalletBackupConfirmed, n as formatWalletBackupWarning, r as generateWalletPrivateKey } from "./create-CpfZq2fx.mjs";
4
4
  import { a as formatWalletBalanceResult, c as getWalletAccount, i as formatWalletBalance, l as getWalletBalanceResult, o as getBalanceEth, r as buildTopupInfo, s as getBalanceUsdc, u as getWalletBalanceText } from "./tools-D9ep9E7Q.mjs";
5
- import { n as generateArtifactHtml } from "./topup-server-9rA3qLgf.mjs";
6
- import { n as startTopupServer, t as getTopupUrl } from "./topup-server-BsYYbgBA.mjs";
5
+ import { n as generateArtifactHtml } from "./topup-server-RQ6-c92F.mjs";
6
+ import { n as startTopupServer, t as getTopupUrl } from "./topup-server-B0VKHslr.mjs";
7
7
  import { i as createMcpFetchClient } from "./client-7qOfAdpH.mjs";
8
8
  export { buildTopupInfo, createMcpFetchClient, decryptKey, encryptKey, formatWalletBackupWarning, formatWalletBalance, formatWalletBalanceResult, generateArtifactHtml, generateWalletPrivateKey, getBalanceEth, getBalanceUsdc, getTopupUrl, getWalletAccount, getWalletBalanceResult, getWalletBalanceText, isWalletBackupConfirmed, isWalletConfigured, loadConfig, normalizeWalletPrivateKey, resetConfigCache, saveConfig, setWalletPrivateKey, startTopupServer, walletAddressFromPrivateKey };
@@ -1833,7 +1833,7 @@ async function createProxy() {
1833
1833
  };
1834
1834
  server.registerTool(tool.name, toolConfig, handler);
1835
1835
  }
1836
- const { registerTopupView, TOPUP_VIEW_URI } = await Promise.resolve().then(() => require("./topup-server-COy0toY-.cjs")).then((n) => n.topup_server_exports);
1836
+ const { registerTopupView, TOPUP_VIEW_URI } = await Promise.resolve().then(() => require("./topup-server-C58eWflv.cjs")).then((n) => n.topup_server_exports);
1837
1837
  await registerTopupView(server, { walletAddress: localTopupWalletAddress });
1838
1838
  server.registerResource("Chain Insights view", CLAUDE_VIEW_URI, {
1839
1839
  title: "Chain Insights view",
@@ -1829,7 +1829,7 @@ async function createProxy() {
1829
1829
  };
1830
1830
  server.registerTool(tool.name, toolConfig, handler);
1831
1831
  }
1832
- const { registerTopupView, TOPUP_VIEW_URI } = await import("./topup-server-9rA3qLgf.mjs").then((n) => n.a);
1832
+ const { registerTopupView, TOPUP_VIEW_URI } = await import("./topup-server-RQ6-c92F.mjs").then((n) => n.a);
1833
1833
  await registerTopupView(server, { walletAddress: localTopupWalletAddress });
1834
1834
  server.registerResource("Chain Insights view", CLAUDE_VIEW_URI, {
1835
1835
  title: "Chain Insights view",
@@ -1,4 +1,4 @@
1
- const require_topup_server = require("./topup-server-COy0toY-.cjs");
1
+ const require_topup_server = require("./topup-server-C58eWflv.cjs");
2
2
  const require_robinhood = require("./robinhood-ChiT69sc.cjs");
3
3
  //#region src/subscription/topup.ts
4
4
  function subscriptionTopupFacts(walletAddress, topupUrl) {
@@ -1,4 +1,4 @@
1
- import { i as startTopupServer, o as qrTerminalText, t as SUBSCRIPTION_TOPUP_TEXT } from "./topup-server-9rA3qLgf.mjs";
1
+ import { i as startTopupServer, o as qrTerminalText, t as SUBSCRIPTION_TOPUP_TEXT } from "./topup-server-RQ6-c92F.mjs";
2
2
  import { a as ROBINHOOD_NETWORK_DISPLAY, n as CIA_TOKEN_ADDRESS, r as ROBINHOOD_CHAIN_ID, t as CIA_SYMBOL } from "./robinhood-CCTMagwM.mjs";
3
3
  //#region src/subscription/topup.ts
4
4
  function subscriptionTopupFacts(walletAddress, topupUrl) {
@@ -42,4 +42,4 @@ async function runSubscriptionTopup(opts) {
42
42
  //#endregion
43
43
  export { runSubscriptionTopup };
44
44
 
45
- //# sourceMappingURL=topup-BE8WP69S.mjs.map
45
+ //# sourceMappingURL=topup-CfFe1bGS.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"topup-BE8WP69S.mjs","names":[],"sources":["../src/subscription/topup.ts"],"sourcesContent":["import { qrTerminalText } from '../wallet/mcp-proxy/qr.js'\nimport { SUBSCRIPTION_TOPUP_TEXT, startTopupServer } from '../wallet/mcp-proxy/topup-server.js'\nimport {\n CIA_SYMBOL,\n CIA_TOKEN_ADDRESS,\n ROBINHOOD_CHAIN_ID,\n ROBINHOOD_NETWORK_DISPLAY,\n} from '../wallet/robinhood.js'\n\nexport interface SubscriptionTopupFacts {\n wallet_address: string\n network: typeof ROBINHOOD_NETWORK_DISPLAY\n chain_id: typeof ROBINHOOD_CHAIN_ID\n token: typeof CIA_SYMBOL\n token_contract: typeof CIA_TOKEN_ADDRESS\n gas_token: 'ETH'\n topup_url?: string\n}\n\nexport function subscriptionTopupFacts(walletAddress: string, topupUrl?: string): SubscriptionTopupFacts {\n return {\n wallet_address: walletAddress,\n network: ROBINHOOD_NETWORK_DISPLAY,\n chain_id: ROBINHOOD_CHAIN_ID,\n token: CIA_SYMBOL,\n token_contract: CIA_TOKEN_ADDRESS,\n gas_token: 'ETH',\n ...(topupUrl ? { topup_url: topupUrl } : {}),\n }\n}\n\n/** The terminal text of `cia subscription topup`: facts, then the QR code. */\nexport function subscriptionTopupText(facts: SubscriptionTopupFacts): string {\n return [\n ...(facts.topup_url ? [`Top-up page: ${facts.topup_url}`] : []),\n `Wallet: ${facts.wallet_address}`,\n `Network: ${facts.network} (chain ID ${facts.chain_id})`,\n `Token: ${facts.token} ${facts.token_contract}, and a little ETH for gas`,\n '',\n qrTerminalText(facts.wallet_address),\n '',\n SUBSCRIPTION_TOPUP_TEXT,\n ].join('\\n')\n}\n\n/** `cia subscription topup`: the user's own wallet, never the payment address. */\nexport async function runSubscriptionTopup(opts: { open?: boolean; json?: boolean }): Promise<void> {\n const { assertWalletKeyNotPublic, getWalletAccount } = await import('../wallet/tools.js')\n const account = await getWalletAccount()\n assertWalletKeyNotPublic(account)\n const url = await startTopupServer(account.address, 'robinhood-cia')\n const facts = subscriptionTopupFacts(account.address, url)\n console.log(opts.json ? JSON.stringify(facts, null, 2) : subscriptionTopupText(facts))\n if (!opts.json) console.log('Press Ctrl+C to stop the top-up page.')\n if (opts.open !== false) {\n const open = (await import('open')).default\n await open(url)\n }\n}\n"],"mappings":";;;AAmBA,SAAgB,uBAAuB,eAAuB,UAA2C;CACvG,OAAO;EACL,gBAAgB;EAChB,SAAS;EACT,UAAU;EACV,OAAO;EACP,gBAAgB;EAChB,WAAW;EACX,GAAI,WAAW,EAAE,WAAW,SAAS,IAAI,CAAC;CAC5C;AACF;;AAGA,SAAgB,sBAAsB,OAAuC;CAC3E,OAAO;EACL,GAAI,MAAM,YAAY,CAAC,gBAAgB,MAAM,WAAW,IAAI,CAAC;EAC7D,gBAAgB,MAAM;EACtB,gBAAgB,MAAM,QAAQ,aAAa,MAAM,SAAS;EAC1D,gBAAgB,MAAM,MAAM,GAAG,MAAM,eAAe;EACpD;EACA,eAAe,MAAM,cAAc;EACnC;EACA;CACF,CAAC,CAAC,KAAK,IAAI;AACb;;AAGA,eAAsB,qBAAqB,MAAyD;CAClG,MAAM,EAAE,0BAA0B,qBAAqB,MAAM,OAAO,uBAAqB,CAAA,MAAA,MAAA,EAAA,CAAA;CACzF,MAAM,UAAU,MAAM,iBAAiB;CACvC,yBAAyB,OAAO;CAChC,MAAM,MAAM,MAAM,iBAAiB,QAAQ,SAAS,eAAe;CACnE,MAAM,QAAQ,uBAAuB,QAAQ,SAAS,GAAG;CACzD,QAAQ,IAAI,KAAK,OAAO,KAAK,UAAU,OAAO,MAAM,CAAC,IAAI,sBAAsB,KAAK,CAAC;CACrF,IAAI,CAAC,KAAK,MAAM,QAAQ,IAAI,uCAAuC;CACnE,IAAI,KAAK,SAAS,OAAO;EACvB,MAAM,QAAQ,MAAM,OAAO,QAAA,CAAS;EACpC,MAAM,KAAK,GAAG;CAChB;AACF"}
1
+ {"version":3,"file":"topup-CfFe1bGS.mjs","names":[],"sources":["../src/subscription/topup.ts"],"sourcesContent":["import { qrTerminalText } from '../wallet/mcp-proxy/qr.js'\nimport { SUBSCRIPTION_TOPUP_TEXT, startTopupServer } from '../wallet/mcp-proxy/topup-server.js'\nimport {\n CIA_SYMBOL,\n CIA_TOKEN_ADDRESS,\n ROBINHOOD_CHAIN_ID,\n ROBINHOOD_NETWORK_DISPLAY,\n} from '../wallet/robinhood.js'\n\nexport interface SubscriptionTopupFacts {\n wallet_address: string\n network: typeof ROBINHOOD_NETWORK_DISPLAY\n chain_id: typeof ROBINHOOD_CHAIN_ID\n token: typeof CIA_SYMBOL\n token_contract: typeof CIA_TOKEN_ADDRESS\n gas_token: 'ETH'\n topup_url?: string\n}\n\nexport function subscriptionTopupFacts(walletAddress: string, topupUrl?: string): SubscriptionTopupFacts {\n return {\n wallet_address: walletAddress,\n network: ROBINHOOD_NETWORK_DISPLAY,\n chain_id: ROBINHOOD_CHAIN_ID,\n token: CIA_SYMBOL,\n token_contract: CIA_TOKEN_ADDRESS,\n gas_token: 'ETH',\n ...(topupUrl ? { topup_url: topupUrl } : {}),\n }\n}\n\n/** The terminal text of `cia subscription topup`: facts, then the QR code. */\nexport function subscriptionTopupText(facts: SubscriptionTopupFacts): string {\n return [\n ...(facts.topup_url ? [`Top-up page: ${facts.topup_url}`] : []),\n `Wallet: ${facts.wallet_address}`,\n `Network: ${facts.network} (chain ID ${facts.chain_id})`,\n `Token: ${facts.token} ${facts.token_contract}, and a little ETH for gas`,\n '',\n qrTerminalText(facts.wallet_address),\n '',\n SUBSCRIPTION_TOPUP_TEXT,\n ].join('\\n')\n}\n\n/** `cia subscription topup`: the user's own wallet, never the payment address. */\nexport async function runSubscriptionTopup(opts: { open?: boolean; json?: boolean }): Promise<void> {\n const { assertWalletKeyNotPublic, getWalletAccount } = await import('../wallet/tools.js')\n const account = await getWalletAccount()\n assertWalletKeyNotPublic(account)\n const url = await startTopupServer(account.address, 'robinhood-cia')\n const facts = subscriptionTopupFacts(account.address, url)\n console.log(opts.json ? JSON.stringify(facts, null, 2) : subscriptionTopupText(facts))\n if (!opts.json) console.log('Press Ctrl+C to stop the top-up page.')\n if (opts.open !== false) {\n const open = (await import('open')).default\n await open(url)\n }\n}\n"],"mappings":";;;AAmBA,SAAgB,uBAAuB,eAAuB,UAA2C;CACvG,OAAO;EACL,gBAAgB;EAChB,SAAS;EACT,UAAU;EACV,OAAO;EACP,gBAAgB;EAChB,WAAW;EACX,GAAI,WAAW,EAAE,WAAW,SAAS,IAAI,CAAC;CAC5C;AACF;;AAGA,SAAgB,sBAAsB,OAAuC;CAC3E,OAAO;EACL,GAAI,MAAM,YAAY,CAAC,gBAAgB,MAAM,WAAW,IAAI,CAAC;EAC7D,gBAAgB,MAAM;EACtB,gBAAgB,MAAM,QAAQ,aAAa,MAAM,SAAS;EAC1D,gBAAgB,MAAM,MAAM,GAAG,MAAM,eAAe;EACpD;EACA,eAAe,MAAM,cAAc;EACnC;EACA;CACF,CAAC,CAAC,KAAK,IAAI;AACb;;AAGA,eAAsB,qBAAqB,MAAyD;CAClG,MAAM,EAAE,0BAA0B,qBAAqB,MAAM,OAAO,uBAAqB,CAAA,MAAA,MAAA,EAAA,CAAA;CACzF,MAAM,UAAU,MAAM,iBAAiB;CACvC,yBAAyB,OAAO;CAChC,MAAM,MAAM,MAAM,iBAAiB,QAAQ,SAAS,eAAe;CACnE,MAAM,QAAQ,uBAAuB,QAAQ,SAAS,GAAG;CACzD,QAAQ,IAAI,KAAK,OAAO,KAAK,UAAU,OAAO,MAAM,CAAC,IAAI,sBAAsB,KAAK,CAAC;CACrF,IAAI,CAAC,KAAK,MAAM,QAAQ,IAAI,uCAAuC;CACnE,IAAI,KAAK,SAAS,OAAO;EACvB,MAAM,QAAQ,MAAM,OAAO,QAAA,CAAS;EACpC,MAAM,KAAK,GAAG;CAChB;AACF"}
@@ -1,5 +1,5 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.mjs";
2
- import { i as startTopupServer$1, n as generateArtifactHtml, r as getTopupUrl$1 } from "./topup-server-9rA3qLgf.mjs";
2
+ import { i as startTopupServer$1, n as generateArtifactHtml, r as getTopupUrl$1 } from "./topup-server-RQ6-c92F.mjs";
3
3
  import { isAddress } from "viem";
4
4
  import { createServer } from "node:http";
5
5
  //#region src/wallet/topup-server.ts
@@ -128,4 +128,4 @@ async function startTopupServer(account) {
128
128
  //#endregion
129
129
  export { startTopupServer as n, topup_server_exports as r, getTopupUrl as t };
130
130
 
131
- //# sourceMappingURL=topup-server-BsYYbgBA.mjs.map
131
+ //# sourceMappingURL=topup-server-B0VKHslr.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"topup-server-BsYYbgBA.mjs","names":["getCopiedTopupUrl","startCopiedTopupServer"],"sources":["../src/wallet/topup-server.ts"],"sourcesContent":["import {\n generateArtifactHtml,\n getTopupUrl as getCopiedTopupUrl,\n startTopupServer as startCopiedTopupServer,\n} from './mcp-proxy/topup-server.js'\nimport { createServer, type Server, type ServerResponse } from 'node:http'\nimport { isAddress } from 'viem'\nimport type { PaymentWalletAccount } from './tools.js'\n\ninterface ArtifactServerState {\n address: string\n assetServerUrl: string\n server: Server\n url: string\n}\n\nlet artifactServerState: ArtifactServerState | null = null\nconst REQUEST_TARGET_BASE = 'http://localhost'\n\nclass ProxyRequestError extends Error {\n status: number\n\n constructor(status: number, message: string) {\n super(message)\n this.name = 'ProxyRequestError'\n this.status = status\n }\n}\n\ninterface NormalizedProxyTarget {\n pathAndSearch: string\n pathname: string\n}\n\nfunction toWalletAddress(account: PaymentWalletAccount | string): string {\n const address = typeof account === 'string' ? account : account.address\n if (!isAddress(address)) {\n throw new Error('Wallet address must be a valid 0x-prefixed 20-byte EVM address')\n }\n return address\n}\n\nfunction send(\n res: ServerResponse,\n status: number,\n body: string | Buffer,\n contentType: string\n): void {\n res.writeHead(status, {\n 'content-type': contentType,\n 'cache-control': 'no-store',\n 'access-control-allow-origin': '*',\n })\n res.end(body)\n}\n\nfunction normalizeProxyTarget(reqUrl: string): NormalizedProxyTarget {\n if (!reqUrl.startsWith('/') || reqUrl.startsWith('//')) {\n throw new ProxyRequestError(400, 'Proxy request target must be an origin-form path')\n }\n\n const parsed = new URL(reqUrl, REQUEST_TARGET_BASE)\n if (parsed.origin !== REQUEST_TARGET_BASE) {\n throw new ProxyRequestError(400, 'Absolute and protocol-relative proxy targets are not allowed')\n }\n\n let decodedPathname: string\n try {\n decodedPathname = decodeURIComponent(parsed.pathname)\n } catch {\n throw new ProxyRequestError(400, 'Proxy request target contains invalid encoding')\n }\n\n if (decodedPathname.startsWith('//') || /^\\/[a-z][a-z0-9+.-]*:\\/\\//i.test(decodedPathname)) {\n throw new ProxyRequestError(400, 'Encoded host override targets are not allowed')\n }\n\n return {\n pathAndSearch: `${parsed.pathname}${parsed.search}`,\n pathname: parsed.pathname,\n }\n}\n\nasync function proxyToCopiedServer(\n proxyTarget: NormalizedProxyTarget,\n res: ServerResponse,\n assetServerUrl: string\n): Promise<void> {\n const allowedOrigin = new URL(assetServerUrl).origin\n const upstreamUrl = new URL(proxyTarget.pathAndSearch, assetServerUrl)\n if (upstreamUrl.origin !== allowedOrigin) {\n throw new ProxyRequestError(403, 'Upstream origin is not allowed')\n }\n\n const upstream = await fetch(upstreamUrl)\n const contentType = upstream.headers.get('content-type') ?? 'application/octet-stream'\n const body = Buffer.from(await upstream.arrayBuffer())\n send(res, upstream.status, body, contentType)\n}\n\nexport { generateArtifactHtml }\n\nexport function getTopupArtifactUrl(): string | null {\n return artifactServerState?.url ?? null\n}\n\nexport function getTopupUrl(): string | null {\n return getTopupArtifactUrl() ?? getCopiedTopupUrl()\n}\n\nexport async function stopTopupServer(): Promise<void> {\n if (!artifactServerState) {\n return\n }\n\n const { server } = artifactServerState\n artifactServerState = null\n await new Promise<void>((resolve) => server.close(() => resolve()))\n}\n\nexport async function startTopupServer(account: PaymentWalletAccount | string): Promise<string> {\n const walletAddress = toWalletAddress(account)\n\n if (\n artifactServerState &&\n artifactServerState.address.toLowerCase() === walletAddress.toLowerCase()\n ) {\n return artifactServerState.url\n }\n\n const assetServerUrl = await startCopiedTopupServer(walletAddress)\n\n if (artifactServerState) {\n await stopTopupServer()\n }\n\n const server = createServer((req, res) => {\n const reqUrl = req.url ?? '/'\n let proxyTarget: NormalizedProxyTarget\n try {\n proxyTarget = normalizeProxyTarget(reqUrl)\n } catch (err) {\n if (err instanceof ProxyRequestError) {\n send(\n res,\n err.status,\n JSON.stringify({ error: err.message }) + '\\n',\n 'application/json; charset=utf-8'\n )\n return\n }\n send(\n res,\n 400,\n JSON.stringify({ error: 'Invalid request target' }) + '\\n',\n 'application/json; charset=utf-8'\n )\n return\n }\n const { pathname } = proxyTarget\n\n if (pathname === '/' || pathname === '/index.html') {\n const artifactUrl = artifactServerState?.url ?? assetServerUrl\n send(res, 200, generateArtifactHtml(walletAddress, artifactUrl), 'text/html; charset=utf-8')\n return\n }\n\n if (pathname.startsWith('/assets/') || pathname.startsWith('/api/')) {\n void proxyToCopiedServer(proxyTarget, res, assetServerUrl).catch((err) => {\n if (err instanceof ProxyRequestError) {\n send(\n res,\n err.status,\n JSON.stringify({ error: err.message }) + '\\n',\n 'application/json; charset=utf-8'\n )\n return\n }\n send(\n res,\n 502,\n JSON.stringify({ error: (err as Error).message }) + '\\n',\n 'application/json; charset=utf-8'\n )\n })\n return\n }\n\n send(res, 404, JSON.stringify({ error: 'Not found' }) + '\\n', 'application/json; charset=utf-8')\n })\n\n const url = await new Promise<string>((resolve, reject) => {\n server.once('error', reject)\n server.listen(0, '127.0.0.1', () => {\n const addressInfo = server.address()\n if (!addressInfo || typeof addressInfo === 'string') {\n reject(new Error('Failed to start topup artifact server'))\n return\n }\n resolve(`http://localhost:${addressInfo.port}`)\n })\n })\n\n artifactServerState = {\n address: walletAddress,\n assetServerUrl,\n server,\n url,\n }\n\n return url\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"topup-server-B0VKHslr.mjs","names":["getCopiedTopupUrl","startCopiedTopupServer"],"sources":["../src/wallet/topup-server.ts"],"sourcesContent":["import {\n generateArtifactHtml,\n getTopupUrl as getCopiedTopupUrl,\n startTopupServer as startCopiedTopupServer,\n} from './mcp-proxy/topup-server.js'\nimport { createServer, type Server, type ServerResponse } from 'node:http'\nimport { isAddress } from 'viem'\nimport type { PaymentWalletAccount } from './tools.js'\n\ninterface ArtifactServerState {\n address: string\n assetServerUrl: string\n server: Server\n url: string\n}\n\nlet artifactServerState: ArtifactServerState | null = null\nconst REQUEST_TARGET_BASE = 'http://localhost'\n\nclass ProxyRequestError extends Error {\n status: number\n\n constructor(status: number, message: string) {\n super(message)\n this.name = 'ProxyRequestError'\n this.status = status\n }\n}\n\ninterface NormalizedProxyTarget {\n pathAndSearch: string\n pathname: string\n}\n\nfunction toWalletAddress(account: PaymentWalletAccount | string): string {\n const address = typeof account === 'string' ? account : account.address\n if (!isAddress(address)) {\n throw new Error('Wallet address must be a valid 0x-prefixed 20-byte EVM address')\n }\n return address\n}\n\nfunction send(\n res: ServerResponse,\n status: number,\n body: string | Buffer,\n contentType: string\n): void {\n res.writeHead(status, {\n 'content-type': contentType,\n 'cache-control': 'no-store',\n 'access-control-allow-origin': '*',\n })\n res.end(body)\n}\n\nfunction normalizeProxyTarget(reqUrl: string): NormalizedProxyTarget {\n if (!reqUrl.startsWith('/') || reqUrl.startsWith('//')) {\n throw new ProxyRequestError(400, 'Proxy request target must be an origin-form path')\n }\n\n const parsed = new URL(reqUrl, REQUEST_TARGET_BASE)\n if (parsed.origin !== REQUEST_TARGET_BASE) {\n throw new ProxyRequestError(400, 'Absolute and protocol-relative proxy targets are not allowed')\n }\n\n let decodedPathname: string\n try {\n decodedPathname = decodeURIComponent(parsed.pathname)\n } catch {\n throw new ProxyRequestError(400, 'Proxy request target contains invalid encoding')\n }\n\n if (decodedPathname.startsWith('//') || /^\\/[a-z][a-z0-9+.-]*:\\/\\//i.test(decodedPathname)) {\n throw new ProxyRequestError(400, 'Encoded host override targets are not allowed')\n }\n\n return {\n pathAndSearch: `${parsed.pathname}${parsed.search}`,\n pathname: parsed.pathname,\n }\n}\n\nasync function proxyToCopiedServer(\n proxyTarget: NormalizedProxyTarget,\n res: ServerResponse,\n assetServerUrl: string\n): Promise<void> {\n const allowedOrigin = new URL(assetServerUrl).origin\n const upstreamUrl = new URL(proxyTarget.pathAndSearch, assetServerUrl)\n if (upstreamUrl.origin !== allowedOrigin) {\n throw new ProxyRequestError(403, 'Upstream origin is not allowed')\n }\n\n const upstream = await fetch(upstreamUrl)\n const contentType = upstream.headers.get('content-type') ?? 'application/octet-stream'\n const body = Buffer.from(await upstream.arrayBuffer())\n send(res, upstream.status, body, contentType)\n}\n\nexport { generateArtifactHtml }\n\nexport function getTopupArtifactUrl(): string | null {\n return artifactServerState?.url ?? null\n}\n\nexport function getTopupUrl(): string | null {\n return getTopupArtifactUrl() ?? getCopiedTopupUrl()\n}\n\nexport async function stopTopupServer(): Promise<void> {\n if (!artifactServerState) {\n return\n }\n\n const { server } = artifactServerState\n artifactServerState = null\n await new Promise<void>((resolve) => server.close(() => resolve()))\n}\n\nexport async function startTopupServer(account: PaymentWalletAccount | string): Promise<string> {\n const walletAddress = toWalletAddress(account)\n\n if (\n artifactServerState &&\n artifactServerState.address.toLowerCase() === walletAddress.toLowerCase()\n ) {\n return artifactServerState.url\n }\n\n const assetServerUrl = await startCopiedTopupServer(walletAddress)\n\n if (artifactServerState) {\n await stopTopupServer()\n }\n\n const server = createServer((req, res) => {\n const reqUrl = req.url ?? '/'\n let proxyTarget: NormalizedProxyTarget\n try {\n proxyTarget = normalizeProxyTarget(reqUrl)\n } catch (err) {\n if (err instanceof ProxyRequestError) {\n send(\n res,\n err.status,\n JSON.stringify({ error: err.message }) + '\\n',\n 'application/json; charset=utf-8'\n )\n return\n }\n send(\n res,\n 400,\n JSON.stringify({ error: 'Invalid request target' }) + '\\n',\n 'application/json; charset=utf-8'\n )\n return\n }\n const { pathname } = proxyTarget\n\n if (pathname === '/' || pathname === '/index.html') {\n const artifactUrl = artifactServerState?.url ?? assetServerUrl\n send(res, 200, generateArtifactHtml(walletAddress, artifactUrl), 'text/html; charset=utf-8')\n return\n }\n\n if (pathname.startsWith('/assets/') || pathname.startsWith('/api/')) {\n void proxyToCopiedServer(proxyTarget, res, assetServerUrl).catch((err) => {\n if (err instanceof ProxyRequestError) {\n send(\n res,\n err.status,\n JSON.stringify({ error: err.message }) + '\\n',\n 'application/json; charset=utf-8'\n )\n return\n }\n send(\n res,\n 502,\n JSON.stringify({ error: (err as Error).message }) + '\\n',\n 'application/json; charset=utf-8'\n )\n })\n return\n }\n\n send(res, 404, JSON.stringify({ error: 'Not found' }) + '\\n', 'application/json; charset=utf-8')\n })\n\n const url = await new Promise<string>((resolve, reject) => {\n server.once('error', reject)\n server.listen(0, '127.0.0.1', () => {\n const addressInfo = server.address()\n if (!addressInfo || typeof addressInfo === 'string') {\n reject(new Error('Failed to start topup artifact server'))\n return\n }\n resolve(`http://localhost:${addressInfo.port}`)\n })\n })\n\n artifactServerState = {\n address: walletAddress,\n assetServerUrl,\n server,\n url,\n }\n\n return url\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  const require_rolldown_runtime = require("./rolldown-runtime-C0BPl7ul.cjs");
2
- const require_topup_server = require("./topup-server-COy0toY-.cjs");
2
+ const require_topup_server = require("./topup-server-C58eWflv.cjs");
3
3
  let viem = require("viem");
4
4
  let node_http = require("node:http");
5
5
  //#region src/wallet/topup-server.ts
@@ -291,6 +291,7 @@ var topup_server_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll(
291
291
  extAppsBrowserScript: () => extAppsBrowserScript,
292
292
  generateArtifactHtml: () => generateArtifactHtml,
293
293
  generateSubscriptionPage: () => generateSubscriptionPage,
294
+ generateTopupPage: () => generateTopupPage,
294
295
  getTopupUrl: () => getTopupUrl,
295
296
  qrModulesSource: () => qrModulesSource,
296
297
  registerTopupView: () => registerTopupView,
@@ -300,7 +301,6 @@ var topup_server_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll(
300
301
  topupViewHtml: () => topupViewHtml
301
302
  });
302
303
  const USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
303
- const BASE_CHAIN_ID = "0x2105";
304
304
  const __dirname$1 = (0, node_path.dirname)((0, node_url.fileURLToPath)(require("url").pathToFileURL(__filename).href));
305
305
  function loadAsset(name) {
306
306
  const paths = [(0, node_path.join)(__dirname$1, "assets", name), (0, node_path.join)(__dirname$1, "..", "src", "assets", name)];
@@ -626,15 +626,77 @@ function selectAndCopy() {
626
626
  </body>
627
627
  </html>`;
628
628
  }
629
- function generatePage(walletAddressInput) {
629
+ /** The text of the subscription top-up page and of `cia subscription topup`. */
630
+ const SUBSCRIPTION_TOPUP_TEXT = "Send $CIA and a little ETH on Robinhood Chain to your own wallet. Then run `cia subscription buy --days N`.";
631
+ const TOPUP_PAGE_TEXT = {
632
+ "base-usdc": {
633
+ title: "Chain Insights — Fund Wallet",
634
+ subtitle: "Fund your wallet to use blockchain intelligence tools",
635
+ badge: "Base Network &middot; USDC",
636
+ balances: [{
637
+ id: "balance",
638
+ label: "Current balance",
639
+ currency: "USDC",
640
+ field: "balance_usdc",
641
+ decimals: 2
642
+ }, {
643
+ id: "gasBalance",
644
+ label: "Gas balance",
645
+ currency: "ETH",
646
+ field: "balance_eth",
647
+ decimals: 6
648
+ }],
649
+ gasNote: "Base ETH is used for one-time payment setup gas.",
650
+ info: "Send Base USDC to the wallet address above."
651
+ },
652
+ "robinhood-cia": {
653
+ title: "Chain Insights — Fund the subscription wallet",
654
+ subtitle: "Fund your wallet for the Chain Insights subscription",
655
+ badge: "Robinhood Chain &middot; $CIA",
656
+ balances: [{
657
+ id: "balance",
658
+ label: "Current balance",
659
+ currency: "$CIA",
660
+ field: "balance_cia",
661
+ decimals: 2
662
+ }, {
663
+ id: "gasBalance",
664
+ label: "Gas balance",
665
+ currency: "ETH",
666
+ field: "balance_eth",
667
+ decimals: 6
668
+ }],
669
+ gasNote: "Robinhood Chain ETH pays the gas of the two buy transactions.",
670
+ info: `Send \$CIA and a little ETH on Robinhood Chain to your own wallet. Then run <code>cia subscription buy --days N</code>.<br>Robinhood Chain (chain ID 4663) &middot; $CIA <code>${require_robinhood.CIA_TOKEN_ADDRESS}</code><br>This page shows your own address only. It signs nothing and sends nothing.`
671
+ }
672
+ };
673
+ /**
674
+ * The one local top-up page: the wallet address, its QR code (drawn here, no
675
+ * script from outside), the network and the balances. It names only the
676
+ * user's own wallet, never where a payment goes.
677
+ */
678
+ function generateTopupPage(walletAddressInput, rail = "base-usdc") {
630
679
  const walletAddress = assertWalletAddress(walletAddressInput);
680
+ const safeWalletAddress = escapeHtml(walletAddress);
681
+ const walletAddressJson = jsonForScript(walletAddress);
682
+ const text = TOPUP_PAGE_TEXT[rail];
683
+ const qrSvg = generateQrSvg(walletAddress, { cellSize: 5 });
684
+ const balanceRows = text.balances.map((row) => ` <div class="balance-row">
685
+ <span class="balance-label">${row.label}</span>
686
+ <span class="balance-value" id="${row.id}">—<span class="currency">${row.currency}</span></span>
687
+ </div>`).join("\n");
688
+ const balancesJson = JSON.stringify(text.balances.map(({ id, currency, field, decimals }) => ({
689
+ id,
690
+ currency,
691
+ field,
692
+ decimals
693
+ })));
631
694
  return `<!DOCTYPE html>
632
695
  <html lang="en">
633
696
  <head>
634
697
  <meta charset="utf-8">
635
698
  <meta name="viewport" content="width=device-width, initial-scale=1">
636
- <title>Chain Insights — Fund Wallet</title>
637
- <script src="https://cdn.jsdelivr.net/npm/qrcode-generator@1.4.4/qrcode.min.js"><\/script>
699
+ <title>${text.title}</title>
638
700
  <style>
639
701
  * { margin: 0; padding: 0; box-sizing: border-box; }
640
702
 
@@ -686,7 +748,7 @@ function generatePage(walletAddressInput) {
686
748
  margin-bottom: 24px;
687
749
  }
688
750
 
689
- .qr-container canvas, .qr-container img {
751
+ .qr-container svg, .qr-container canvas, .qr-container img {
690
752
  border-radius: 12px;
691
753
  background: #fff;
692
754
  padding: 12px;
@@ -841,67 +903,53 @@ function generatePage(walletAddressInput) {
841
903
  color: rgba(255, 255, 255, 0.3);
842
904
  line-height: 1.6;
843
905
  }
906
+
907
+ .info code {
908
+ font-family: 'SF Mono', 'Fira Code', monospace;
909
+ color: #f2dda6;
910
+ word-break: break-all;
911
+ }
844
912
  </style>
845
913
  </head>
846
914
  <body>
847
915
  <div class="container">
848
916
  <div class="logo">
849
917
  <h1>Chain Insights</h1>
850
- <p>Fund your wallet to use blockchain intelligence tools</p>
918
+ <p>${text.subtitle}</p>
851
919
  </div>
852
920
 
853
921
  <div class="card">
854
- <div class="qr-container" id="qr"></div>
922
+ <div class="qr-container" id="qr">${qrSvg}</div>
855
923
 
856
924
  <div class="address-box">
857
- <code id="address">${escapeHtml(walletAddress)}</code>
925
+ <code id="address">${safeWalletAddress}</code>
858
926
  <button class="copy-btn" onclick="copyAddress()" id="copyBtn" title="Copy address">&#x2398;</button>
859
927
  </div>
860
928
 
861
929
  <div class="network-badge">
862
930
  <span class="dot"></span>
863
- Base Network &middot; USDC
931
+ ${text.badge}
864
932
  </div>
865
933
 
866
- <div class="balance-row">
867
- <span class="balance-label">Current balance</span>
868
- <span class="balance-value" id="balance">—<span class="currency">USDC</span></span>
869
- </div>
870
- <div class="balance-row">
871
- <span class="balance-label">Gas balance</span>
872
- <span class="balance-value" id="gasBalance">—<span class="currency">ETH</span></span>
873
- </div>
874
- <p class="gas-note">Base ETH is used for one-time payment setup gas.</p>
934
+ ${balanceRows}
935
+ <p class="gas-note">${text.gasNote}</p>
875
936
 
876
937
  <div class="status" id="status"></div>
877
938
  </div>
878
939
 
879
- <p class="info">Send Base USDC to the wallet address above.</p>
940
+ <p class="info">${text.info}</p>
880
941
  </div>
881
942
 
882
943
  <script>
883
- const WALLET = ${jsonForScript(walletAddress)};
884
- const USDC = '${USDC_ADDRESS}';
885
- const CHAIN_ID = '${BASE_CHAIN_ID}';
886
-
887
- // QR code
888
- (function() {
889
- var qr = qrcode(0, 'M');
890
- qr.addData(WALLET);
891
- qr.make();
892
- document.getElementById('qr').innerHTML = qr.createSvgTag(5, 0);
893
- var svg = document.querySelector('#qr svg');
894
- if (svg) { svg.style.borderRadius = '12px'; svg.style.background = '#fff'; svg.style.padding = '12px'; }
895
- })();
944
+ const WALLET = ${walletAddressJson};
945
+ const BALANCES = ${balancesJson};
896
946
 
897
947
  // Copy address — fallback for sandboxed iframes where navigator.clipboard is blocked
898
948
  function copyAddress() {
899
949
  var ok = false;
900
- // Try modern clipboard API first
901
950
  if (navigator.clipboard && navigator.clipboard.writeText) {
902
951
  navigator.clipboard.writeText(WALLET).then(function() { ok = true; }).catch(function() {});
903
952
  }
904
- // Fallback: hidden textarea + execCommand
905
953
  if (!ok) {
906
954
  var ta = document.createElement('textarea');
907
955
  ta.value = WALLET;
@@ -918,20 +966,18 @@ function copyAddress() {
918
966
  setTimeout(function() { btn.classList.remove('copied'); btn.innerHTML = '&#x2398;'; }, 2000);
919
967
  }
920
968
 
921
- // Fetch balance
969
+ function showBalance(row, value) {
970
+ document.getElementById(row.id).innerHTML = value + '<span class="currency">' + row.currency + '</span>';
971
+ }
972
+
973
+ // Fetch balances
922
974
  async function fetchBalance() {
923
- try {
924
- var resp = await fetch('/api/balance');
925
- var json = await resp.json();
926
- if (json.balance_usdc === 'unknown') throw new Error('balance unavailable');
927
- var balance = Number(json.balance_usdc || 0).toFixed(2);
928
- document.getElementById('balance').innerHTML = balance + '<span class="currency">USDC</span>';
929
- var gasBalance = json.balance_eth === 'unknown' ? '—' : Number(json.balance_eth || 0).toFixed(6);
930
- document.getElementById('gasBalance').innerHTML = gasBalance + '<span class="currency">ETH</span>';
931
- } catch(e) {
932
- document.getElementById('balance').innerHTML = '—<span class="currency">USDC</span>';
933
- document.getElementById('gasBalance').innerHTML = '—<span class="currency">ETH</span>';
934
- }
975
+ var json = {};
976
+ try { json = await (await fetch('/api/balance')).json(); } catch(e) {}
977
+ BALANCES.forEach(function(row) {
978
+ var raw = json[row.field];
979
+ showBalance(row, raw === undefined || raw === 'unknown' ? '—' : Number(raw || 0).toFixed(row.decimals));
980
+ });
935
981
  }
936
982
  fetchBalance();
937
983
  setInterval(fetchBalance, 15000);
@@ -940,64 +986,13 @@ setInterval(fetchBalance, 15000);
940
986
  </body>
941
987
  </html>`;
942
988
  }
943
- /** The text of the subscription top-up page and of `cia subscription topup`. */
944
- const SUBSCRIPTION_TOPUP_TEXT = "Send $CIA and a little ETH on Robinhood Chain to your own wallet. Then run `cia subscription buy --days N`.";
945
- /**
946
- * The robinhood-cia page: the wallet address, its QR code, Robinhood Chain,
947
- * $CIA and the balances. It names only the user's own wallet, never where the
948
- * subscription payment goes: that address is read by the buy alone.
949
- */
950
- function generateSubscriptionPage(walletAddressInput) {
951
- const walletAddress = assertWalletAddress(walletAddressInput);
952
- const safeWalletAddress = escapeHtml(walletAddress);
953
- return `<!DOCTYPE html>
954
- <html lang="en">
955
- <head>
956
- <meta charset="utf-8">
957
- <meta name="viewport" content="width=device-width, initial-scale=1">
958
- <title>Chain Insights — Fund the subscription wallet</title>
959
- <style>
960
- * { margin: 0; padding: 0; box-sizing: border-box; }
961
- body { font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif; background: #0a0c11; color: rgba(255,255,255,0.9); display: flex; justify-content: center; padding: 24px; line-height: 1.4; }
962
- .card { max-width: 420px; width: 100%; background: #131318; border: 1px solid rgba(255,255,255,0.06); border-radius: 12px; padding: 28px 24px; text-align: center; }
963
- h1 { font-size: 18px; margin-bottom: 8px; }
964
- .lead { color: rgba(255,255,255,0.7); font-size: 14px; margin-bottom: 20px; }
965
- .qr svg { border-radius: 12px; background: #fff; padding: 10px; }
966
- .address { margin: 16px 0; font-family: 'SF Mono', 'Fira Code', monospace; font-size: 12px; color: #f2dda6; word-break: break-all; user-select: all; }
967
- dl { display: grid; grid-template-columns: auto 1fr; gap: 6px 12px; text-align: left; font-size: 13px; margin-bottom: 16px; }
968
- dt { color: rgba(255,255,255,0.5); }
969
- dd { word-break: break-all; }
970
- code { font-family: 'SF Mono', 'Fira Code', monospace; }
971
- .note { color: rgba(255,255,255,0.5); font-size: 12px; }
972
- </style>
973
- </head>
974
- <body>
975
- <div class="card">
976
- <h1>Fund your Chain Insights subscription wallet</h1>
977
- <p class="lead">Send $CIA and a little ETH on Robinhood Chain to your own wallet. Then run <code>cia subscription buy --days N</code>.</p>
978
- <div class="qr">${generateQrSvg(walletAddress, { cellSize: 6 })}</div>
979
- <p class="address">${safeWalletAddress}</p>
980
- <dl>
981
- <dt>Network</dt><dd>Robinhood Chain (chain ID 4663)</dd>
982
- <dt>Token</dt><dd>$CIA <code>${require_robinhood.CIA_TOKEN_ADDRESS}</code></dd>
983
- <dt>$CIA balance</dt><dd id="cia">—</dd>
984
- <dt>ETH balance</dt><dd id="eth">—</dd>
985
- </dl>
986
- <p class="note">ETH pays the gas of the two buy transactions. This page shows your own address only. It signs nothing and sends nothing.</p>
987
- </div>
988
- <script>
989
- async function fetchBalance() {
990
- try {
991
- var json = await (await fetch('/api/balance')).json();
992
- document.getElementById('cia').textContent = json.balance_cia === 'unknown' ? '—' : json.balance_cia + ' $CIA';
993
- document.getElementById('eth').textContent = json.balance_eth === 'unknown' ? '—' : json.balance_eth + ' ETH';
994
- } catch (e) {}
989
+ /** The Base USDC page. */
990
+ function generatePage(walletAddressInput) {
991
+ return generateTopupPage(walletAddressInput, "base-usdc");
995
992
  }
996
- fetchBalance();
997
- setInterval(fetchBalance, 15000);
998
- <\/script>
999
- </body>
1000
- </html>`;
993
+ /** The robinhood-cia page: the same page, the subscription text. */
994
+ function generateSubscriptionPage(walletAddressInput) {
995
+ return generateTopupPage(walletAddressInput, "robinhood-cia");
1001
996
  }
1002
997
  const TOPUP_TOOL_NAME = "wallet_topup";
1003
998
  const TOPUP_VIEW_URI = "ui://chain-insights/topup";
@@ -291,6 +291,7 @@ var topup_server_exports = /* @__PURE__ */ __exportAll({
291
291
  extAppsBrowserScript: () => extAppsBrowserScript,
292
292
  generateArtifactHtml: () => generateArtifactHtml,
293
293
  generateSubscriptionPage: () => generateSubscriptionPage,
294
+ generateTopupPage: () => generateTopupPage,
294
295
  getTopupUrl: () => getTopupUrl,
295
296
  qrModulesSource: () => qrModulesSource,
296
297
  registerTopupView: () => registerTopupView,
@@ -300,7 +301,6 @@ var topup_server_exports = /* @__PURE__ */ __exportAll({
300
301
  topupViewHtml: () => topupViewHtml
301
302
  });
302
303
  const USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
303
- const BASE_CHAIN_ID = "0x2105";
304
304
  const __dirname = dirname(fileURLToPath(import.meta.url));
305
305
  function loadAsset(name) {
306
306
  const paths = [join(__dirname, "assets", name), join(__dirname, "..", "src", "assets", name)];
@@ -626,15 +626,77 @@ function selectAndCopy() {
626
626
  </body>
627
627
  </html>`;
628
628
  }
629
- function generatePage(walletAddressInput) {
629
+ /** The text of the subscription top-up page and of `cia subscription topup`. */
630
+ const SUBSCRIPTION_TOPUP_TEXT = "Send $CIA and a little ETH on Robinhood Chain to your own wallet. Then run `cia subscription buy --days N`.";
631
+ const TOPUP_PAGE_TEXT = {
632
+ "base-usdc": {
633
+ title: "Chain Insights — Fund Wallet",
634
+ subtitle: "Fund your wallet to use blockchain intelligence tools",
635
+ badge: "Base Network &middot; USDC",
636
+ balances: [{
637
+ id: "balance",
638
+ label: "Current balance",
639
+ currency: "USDC",
640
+ field: "balance_usdc",
641
+ decimals: 2
642
+ }, {
643
+ id: "gasBalance",
644
+ label: "Gas balance",
645
+ currency: "ETH",
646
+ field: "balance_eth",
647
+ decimals: 6
648
+ }],
649
+ gasNote: "Base ETH is used for one-time payment setup gas.",
650
+ info: "Send Base USDC to the wallet address above."
651
+ },
652
+ "robinhood-cia": {
653
+ title: "Chain Insights — Fund the subscription wallet",
654
+ subtitle: "Fund your wallet for the Chain Insights subscription",
655
+ badge: "Robinhood Chain &middot; $CIA",
656
+ balances: [{
657
+ id: "balance",
658
+ label: "Current balance",
659
+ currency: "$CIA",
660
+ field: "balance_cia",
661
+ decimals: 2
662
+ }, {
663
+ id: "gasBalance",
664
+ label: "Gas balance",
665
+ currency: "ETH",
666
+ field: "balance_eth",
667
+ decimals: 6
668
+ }],
669
+ gasNote: "Robinhood Chain ETH pays the gas of the two buy transactions.",
670
+ info: `Send \$CIA and a little ETH on Robinhood Chain to your own wallet. Then run <code>cia subscription buy --days N</code>.<br>Robinhood Chain (chain ID 4663) &middot; $CIA <code>${CIA_TOKEN_ADDRESS}</code><br>This page shows your own address only. It signs nothing and sends nothing.`
671
+ }
672
+ };
673
+ /**
674
+ * The one local top-up page: the wallet address, its QR code (drawn here, no
675
+ * script from outside), the network and the balances. It names only the
676
+ * user's own wallet, never where a payment goes.
677
+ */
678
+ function generateTopupPage(walletAddressInput, rail = "base-usdc") {
630
679
  const walletAddress = assertWalletAddress(walletAddressInput);
680
+ const safeWalletAddress = escapeHtml(walletAddress);
681
+ const walletAddressJson = jsonForScript(walletAddress);
682
+ const text = TOPUP_PAGE_TEXT[rail];
683
+ const qrSvg = generateQrSvg(walletAddress, { cellSize: 5 });
684
+ const balanceRows = text.balances.map((row) => ` <div class="balance-row">
685
+ <span class="balance-label">${row.label}</span>
686
+ <span class="balance-value" id="${row.id}">—<span class="currency">${row.currency}</span></span>
687
+ </div>`).join("\n");
688
+ const balancesJson = JSON.stringify(text.balances.map(({ id, currency, field, decimals }) => ({
689
+ id,
690
+ currency,
691
+ field,
692
+ decimals
693
+ })));
631
694
  return `<!DOCTYPE html>
632
695
  <html lang="en">
633
696
  <head>
634
697
  <meta charset="utf-8">
635
698
  <meta name="viewport" content="width=device-width, initial-scale=1">
636
- <title>Chain Insights — Fund Wallet</title>
637
- <script src="https://cdn.jsdelivr.net/npm/qrcode-generator@1.4.4/qrcode.min.js"><\/script>
699
+ <title>${text.title}</title>
638
700
  <style>
639
701
  * { margin: 0; padding: 0; box-sizing: border-box; }
640
702
 
@@ -686,7 +748,7 @@ function generatePage(walletAddressInput) {
686
748
  margin-bottom: 24px;
687
749
  }
688
750
 
689
- .qr-container canvas, .qr-container img {
751
+ .qr-container svg, .qr-container canvas, .qr-container img {
690
752
  border-radius: 12px;
691
753
  background: #fff;
692
754
  padding: 12px;
@@ -841,67 +903,53 @@ function generatePage(walletAddressInput) {
841
903
  color: rgba(255, 255, 255, 0.3);
842
904
  line-height: 1.6;
843
905
  }
906
+
907
+ .info code {
908
+ font-family: 'SF Mono', 'Fira Code', monospace;
909
+ color: #f2dda6;
910
+ word-break: break-all;
911
+ }
844
912
  </style>
845
913
  </head>
846
914
  <body>
847
915
  <div class="container">
848
916
  <div class="logo">
849
917
  <h1>Chain Insights</h1>
850
- <p>Fund your wallet to use blockchain intelligence tools</p>
918
+ <p>${text.subtitle}</p>
851
919
  </div>
852
920
 
853
921
  <div class="card">
854
- <div class="qr-container" id="qr"></div>
922
+ <div class="qr-container" id="qr">${qrSvg}</div>
855
923
 
856
924
  <div class="address-box">
857
- <code id="address">${escapeHtml(walletAddress)}</code>
925
+ <code id="address">${safeWalletAddress}</code>
858
926
  <button class="copy-btn" onclick="copyAddress()" id="copyBtn" title="Copy address">&#x2398;</button>
859
927
  </div>
860
928
 
861
929
  <div class="network-badge">
862
930
  <span class="dot"></span>
863
- Base Network &middot; USDC
931
+ ${text.badge}
864
932
  </div>
865
933
 
866
- <div class="balance-row">
867
- <span class="balance-label">Current balance</span>
868
- <span class="balance-value" id="balance">—<span class="currency">USDC</span></span>
869
- </div>
870
- <div class="balance-row">
871
- <span class="balance-label">Gas balance</span>
872
- <span class="balance-value" id="gasBalance">—<span class="currency">ETH</span></span>
873
- </div>
874
- <p class="gas-note">Base ETH is used for one-time payment setup gas.</p>
934
+ ${balanceRows}
935
+ <p class="gas-note">${text.gasNote}</p>
875
936
 
876
937
  <div class="status" id="status"></div>
877
938
  </div>
878
939
 
879
- <p class="info">Send Base USDC to the wallet address above.</p>
940
+ <p class="info">${text.info}</p>
880
941
  </div>
881
942
 
882
943
  <script>
883
- const WALLET = ${jsonForScript(walletAddress)};
884
- const USDC = '${USDC_ADDRESS}';
885
- const CHAIN_ID = '${BASE_CHAIN_ID}';
886
-
887
- // QR code
888
- (function() {
889
- var qr = qrcode(0, 'M');
890
- qr.addData(WALLET);
891
- qr.make();
892
- document.getElementById('qr').innerHTML = qr.createSvgTag(5, 0);
893
- var svg = document.querySelector('#qr svg');
894
- if (svg) { svg.style.borderRadius = '12px'; svg.style.background = '#fff'; svg.style.padding = '12px'; }
895
- })();
944
+ const WALLET = ${walletAddressJson};
945
+ const BALANCES = ${balancesJson};
896
946
 
897
947
  // Copy address — fallback for sandboxed iframes where navigator.clipboard is blocked
898
948
  function copyAddress() {
899
949
  var ok = false;
900
- // Try modern clipboard API first
901
950
  if (navigator.clipboard && navigator.clipboard.writeText) {
902
951
  navigator.clipboard.writeText(WALLET).then(function() { ok = true; }).catch(function() {});
903
952
  }
904
- // Fallback: hidden textarea + execCommand
905
953
  if (!ok) {
906
954
  var ta = document.createElement('textarea');
907
955
  ta.value = WALLET;
@@ -918,20 +966,18 @@ function copyAddress() {
918
966
  setTimeout(function() { btn.classList.remove('copied'); btn.innerHTML = '&#x2398;'; }, 2000);
919
967
  }
920
968
 
921
- // Fetch balance
969
+ function showBalance(row, value) {
970
+ document.getElementById(row.id).innerHTML = value + '<span class="currency">' + row.currency + '</span>';
971
+ }
972
+
973
+ // Fetch balances
922
974
  async function fetchBalance() {
923
- try {
924
- var resp = await fetch('/api/balance');
925
- var json = await resp.json();
926
- if (json.balance_usdc === 'unknown') throw new Error('balance unavailable');
927
- var balance = Number(json.balance_usdc || 0).toFixed(2);
928
- document.getElementById('balance').innerHTML = balance + '<span class="currency">USDC</span>';
929
- var gasBalance = json.balance_eth === 'unknown' ? '—' : Number(json.balance_eth || 0).toFixed(6);
930
- document.getElementById('gasBalance').innerHTML = gasBalance + '<span class="currency">ETH</span>';
931
- } catch(e) {
932
- document.getElementById('balance').innerHTML = '—<span class="currency">USDC</span>';
933
- document.getElementById('gasBalance').innerHTML = '—<span class="currency">ETH</span>';
934
- }
975
+ var json = {};
976
+ try { json = await (await fetch('/api/balance')).json(); } catch(e) {}
977
+ BALANCES.forEach(function(row) {
978
+ var raw = json[row.field];
979
+ showBalance(row, raw === undefined || raw === 'unknown' ? '—' : Number(raw || 0).toFixed(row.decimals));
980
+ });
935
981
  }
936
982
  fetchBalance();
937
983
  setInterval(fetchBalance, 15000);
@@ -940,64 +986,13 @@ setInterval(fetchBalance, 15000);
940
986
  </body>
941
987
  </html>`;
942
988
  }
943
- /** The text of the subscription top-up page and of `cia subscription topup`. */
944
- const SUBSCRIPTION_TOPUP_TEXT = "Send $CIA and a little ETH on Robinhood Chain to your own wallet. Then run `cia subscription buy --days N`.";
945
- /**
946
- * The robinhood-cia page: the wallet address, its QR code, Robinhood Chain,
947
- * $CIA and the balances. It names only the user's own wallet, never where the
948
- * subscription payment goes: that address is read by the buy alone.
949
- */
950
- function generateSubscriptionPage(walletAddressInput) {
951
- const walletAddress = assertWalletAddress(walletAddressInput);
952
- const safeWalletAddress = escapeHtml(walletAddress);
953
- return `<!DOCTYPE html>
954
- <html lang="en">
955
- <head>
956
- <meta charset="utf-8">
957
- <meta name="viewport" content="width=device-width, initial-scale=1">
958
- <title>Chain Insights — Fund the subscription wallet</title>
959
- <style>
960
- * { margin: 0; padding: 0; box-sizing: border-box; }
961
- body { font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif; background: #0a0c11; color: rgba(255,255,255,0.9); display: flex; justify-content: center; padding: 24px; line-height: 1.4; }
962
- .card { max-width: 420px; width: 100%; background: #131318; border: 1px solid rgba(255,255,255,0.06); border-radius: 12px; padding: 28px 24px; text-align: center; }
963
- h1 { font-size: 18px; margin-bottom: 8px; }
964
- .lead { color: rgba(255,255,255,0.7); font-size: 14px; margin-bottom: 20px; }
965
- .qr svg { border-radius: 12px; background: #fff; padding: 10px; }
966
- .address { margin: 16px 0; font-family: 'SF Mono', 'Fira Code', monospace; font-size: 12px; color: #f2dda6; word-break: break-all; user-select: all; }
967
- dl { display: grid; grid-template-columns: auto 1fr; gap: 6px 12px; text-align: left; font-size: 13px; margin-bottom: 16px; }
968
- dt { color: rgba(255,255,255,0.5); }
969
- dd { word-break: break-all; }
970
- code { font-family: 'SF Mono', 'Fira Code', monospace; }
971
- .note { color: rgba(255,255,255,0.5); font-size: 12px; }
972
- </style>
973
- </head>
974
- <body>
975
- <div class="card">
976
- <h1>Fund your Chain Insights subscription wallet</h1>
977
- <p class="lead">Send $CIA and a little ETH on Robinhood Chain to your own wallet. Then run <code>cia subscription buy --days N</code>.</p>
978
- <div class="qr">${generateQrSvg(walletAddress, { cellSize: 6 })}</div>
979
- <p class="address">${safeWalletAddress}</p>
980
- <dl>
981
- <dt>Network</dt><dd>Robinhood Chain (chain ID 4663)</dd>
982
- <dt>Token</dt><dd>$CIA <code>${CIA_TOKEN_ADDRESS}</code></dd>
983
- <dt>$CIA balance</dt><dd id="cia">—</dd>
984
- <dt>ETH balance</dt><dd id="eth">—</dd>
985
- </dl>
986
- <p class="note">ETH pays the gas of the two buy transactions. This page shows your own address only. It signs nothing and sends nothing.</p>
987
- </div>
988
- <script>
989
- async function fetchBalance() {
990
- try {
991
- var json = await (await fetch('/api/balance')).json();
992
- document.getElementById('cia').textContent = json.balance_cia === 'unknown' ? '—' : json.balance_cia + ' $CIA';
993
- document.getElementById('eth').textContent = json.balance_eth === 'unknown' ? '—' : json.balance_eth + ' ETH';
994
- } catch (e) {}
989
+ /** The Base USDC page. */
990
+ function generatePage(walletAddressInput) {
991
+ return generateTopupPage(walletAddressInput, "base-usdc");
995
992
  }
996
- fetchBalance();
997
- setInterval(fetchBalance, 15000);
998
- <\/script>
999
- </body>
1000
- </html>`;
993
+ /** The robinhood-cia page: the same page, the subscription text. */
994
+ function generateSubscriptionPage(walletAddressInput) {
995
+ return generateTopupPage(walletAddressInput, "robinhood-cia");
1001
996
  }
1002
997
  const TOPUP_TOOL_NAME = "wallet_topup";
1003
998
  const TOPUP_VIEW_URI = "ui://chain-insights/topup";
@@ -1431,4 +1426,4 @@ async function registerTopupView(server, deps) {
1431
1426
  //#endregion
1432
1427
  export { topup_server_exports as a, startTopupServer as i, generateArtifactHtml as n, qrTerminalText as o, getTopupUrl as r, SUBSCRIPTION_TOPUP_TEXT as t };
1433
1428
 
1434
- //# sourceMappingURL=topup-server-9rA3qLgf.mjs.map
1429
+ //# sourceMappingURL=topup-server-RQ6-c92F.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"topup-server-RQ6-c92F.mjs","names":["USDC_ADDRESS"],"sources":["../src/wallet/mcp-proxy/qr.ts","../src/wallet/mcp-proxy/tools.ts","../src/wallet/mcp-proxy/topup-server.ts"],"sourcesContent":["/**\n * Minimal QR code generator. No external dependencies.\n *\n * Byte mode, error correction level L, versions 1 to 4 (single block), mask 0.\n * Version 3 holds a 42-character EVM address; version 4 holds up to 78 bytes.\n *\n * `qrModules` is self-contained on purpose: the top-up view inlines its source\n * (`qrModules.toString()`) and draws the same QR code inside the Claude view,\n * so the function must not reference anything outside its own body.\n */\n\n/**\n * The QR code modules for `text`: a square matrix, 1 for a dark module and 0\n * for a light one, without the quiet zone. Throws when `text` is longer than\n * 78 UTF-8 bytes.\n */\nexport function qrModules(text: string): number[][] {\n // Level L, single-block versions: [version, total codewords, EC codewords].\n const versions = [\n [1, 26, 7],\n [2, 44, 10],\n [3, 70, 15],\n [4, 100, 20],\n ]\n // Format information for level L and mask 0, BCH-coded and masked.\n const formatBits = 0x77c4\n const bytes = Array.from(new TextEncoder().encode(text))\n\n let version = 0\n let totalCodewords = 0\n let ecCodewords = 0\n for (const entry of versions) {\n const dataCodewords = entry[1] - entry[2]\n if (4 + 8 + bytes.length * 8 <= dataCodewords * 8) {\n version = entry[0]\n totalCodewords = entry[1]\n ecCodewords = entry[2]\n break\n }\n }\n if (version === 0) throw new Error('QR text is too long: at most 78 bytes')\n\n const dataCodewords = totalCodewords - ecCodewords\n const size = version * 4 + 17\n\n // Data bits: mode, count, bytes, terminator, byte padding, pad codewords.\n const bits: number[] = []\n const pushBits = (value: number, length: number) => {\n for (let i = length - 1; i >= 0; i--) bits.push((value >>> i) & 1)\n }\n pushBits(0b0100, 4)\n pushBits(bytes.length, 8)\n for (const value of bytes) pushBits(value, 8)\n const capacity = dataCodewords * 8\n pushBits(0, Math.min(4, capacity - bits.length))\n while (bits.length % 8 !== 0) bits.push(0)\n for (let pad = 0xec; bits.length < capacity; pad ^= 0xec ^ 0x11) pushBits(pad, 8)\n const data: number[] = []\n for (let i = 0; i < bits.length; i += 8) {\n let value = 0\n for (let j = 0; j < 8; j++) value = (value << 1) | bits[i + j]\n data.push(value)\n }\n\n // Reed-Solomon error correction over GF(256), polynomial 0x11d.\n const exp: number[] = []\n const log: number[] = []\n for (let i = 0, x = 1; i < 255; i++) {\n exp[i] = x\n log[x] = i\n x <<= 1\n if (x & 0x100) x ^= 0x11d\n }\n const multiply = (a: number, b: number) => (a === 0 || b === 0 ? 0 : exp[(log[a] + log[b]) % 255])\n let generator = [1]\n for (let i = 0; i < ecCodewords; i++) {\n const next = new Array(generator.length + 1).fill(0)\n for (let j = 0; j < generator.length; j++) {\n next[j] ^= generator[j]\n next[j + 1] ^= multiply(generator[j], exp[i])\n }\n generator = next\n }\n const remainder = [...data, ...new Array(ecCodewords).fill(0)]\n for (let i = 0; i < data.length; i++) {\n const factor = remainder[i]\n if (factor === 0) continue\n for (let j = 0; j < generator.length; j++) remainder[i + j] ^= multiply(generator[j], factor)\n }\n const codewords = [...data, ...remainder.slice(data.length)]\n\n // Function patterns. `reserved` marks every module data may not use.\n const modules: number[][] = []\n const reserved: boolean[][] = []\n for (let row = 0; row < size; row++) {\n modules.push(new Array(size).fill(0))\n reserved.push(new Array(size).fill(false))\n }\n const setFunction = (row: number, col: number, dark: boolean) => {\n modules[row][col] = dark ? 1 : 0\n reserved[row][col] = true\n }\n for (let i = 0; i < size; i++) {\n setFunction(6, i, i % 2 === 0)\n setFunction(i, 6, i % 2 === 0)\n }\n const finder = (centerRow: number, centerCol: number) => {\n for (let dr = -4; dr <= 4; dr++) {\n for (let dc = -4; dc <= 4; dc++) {\n const row = centerRow + dr\n const col = centerCol + dc\n if (row < 0 || row >= size || col < 0 || col >= size) continue\n const distance = Math.max(Math.abs(dr), Math.abs(dc))\n setFunction(row, col, distance !== 2 && distance !== 4)\n }\n }\n }\n finder(3, 3)\n finder(3, size - 4)\n finder(size - 4, 3)\n if (version >= 2) {\n const center = size - 7\n for (let dr = -2; dr <= 2; dr++) {\n for (let dc = -2; dc <= 2; dc++) {\n setFunction(center + dr, center + dc, Math.max(Math.abs(dr), Math.abs(dc)) !== 1)\n }\n }\n }\n const formatBit = (i: number) => ((formatBits >>> i) & 1) === 1\n for (let i = 0; i <= 5; i++) setFunction(i, 8, formatBit(i))\n setFunction(7, 8, formatBit(6))\n setFunction(8, 8, formatBit(7))\n setFunction(8, 7, formatBit(8))\n for (let i = 9; i < 15; i++) setFunction(8, 14 - i, formatBit(i))\n for (let i = 0; i < 8; i++) setFunction(8, size - 1 - i, formatBit(i))\n for (let i = 8; i < 15; i++) setFunction(size - 15 + i, 8, formatBit(i))\n setFunction(size - 8, 8, true)\n\n // Data in the zigzag order, then mask 0 on every data module.\n let bitIndex = 0\n for (let right = size - 1; right >= 1; right -= 2) {\n if (right === 6) right = 5\n const upward = ((right + 1) & 2) === 0\n for (let step = 0; step < size; step++) {\n const row = upward ? size - 1 - step : step\n for (let j = 0; j < 2; j++) {\n const col = right - j\n if (reserved[row][col]) continue\n if (bitIndex < codewords.length * 8) {\n modules[row][col] = (codewords[bitIndex >>> 3] >>> (7 - (bitIndex & 7))) & 1\n bitIndex++\n }\n if ((row + col) % 2 === 0) modules[row][col] ^= 1\n }\n }\n }\n return modules\n}\n\nexport interface QrOptions {\n cellSize?: number\n fgColor?: string\n bgColor?: string\n finderColor?: string\n logoBase64?: string // data URI for center logo\n logoWidth?: number // logo width in modules (default: 7)\n logoHeight?: number // logo height in modules (default: 5)\n}\n\nexport function generateQrSvg(text: string, opts: QrOptions | number = 4): string {\n // Backward compat: accept bare cellSize number\n const options: QrOptions = typeof opts === 'number' ? { cellSize: opts } : opts\n const cellSize = options.cellSize ?? 4\n const fgColor = options.fgColor ?? '#000'\n const bgColor = options.bgColor ?? '#fff'\n const finderColor = options.finderColor ?? fgColor\n\n const matrix = qrModules(text)\n const size = matrix.length\n\n // Logo exclusion zone (center of QR)\n const logoW = options.logoWidth ?? 7\n const logoH = options.logoHeight ?? 5\n const logoStartC = Math.floor((size - logoW) / 2)\n const logoStartR = Math.floor((size - logoH) / 2)\n const hasLogo = !!options.logoBase64\n\n const svgSize = size * cellSize\n let svg = `<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"${svgSize}\" height=\"${svgSize}\" viewBox=\"0 0 ${svgSize} ${svgSize}\">`\n svg += `<rect width=\"${svgSize}\" height=\"${svgSize}\" fill=\"${bgColor}\" rx=\"4\"/>`\n\n const isFinderModule = (r: number, c: number): boolean =>\n (r < 7 && c < 7) || (r < 7 && c >= size - 7) || (r >= size - 7 && c < 7)\n\n for (let r = 0; r < size; r++) {\n for (let c = 0; c < size; c++) {\n if (\n hasLogo &&\n r >= logoStartR &&\n r < logoStartR + logoH &&\n c >= logoStartC &&\n c < logoStartC + logoW\n ) {\n continue\n }\n if (matrix[r][c] === 1) {\n const color = isFinderModule(r, c) ? finderColor : fgColor\n svg += `<rect x=\"${c * cellSize}\" y=\"${r * cellSize}\" width=\"${cellSize}\" height=\"${cellSize}\" fill=\"${color}\" rx=\"0.5\"/>`\n }\n }\n }\n\n if (hasLogo && options.logoBase64) {\n const lx = logoStartC * cellSize\n const ly = logoStartR * cellSize\n const lw = logoW * cellSize\n const lh = logoH * cellSize\n svg += `<rect x=\"${lx - 1}\" y=\"${ly - 1}\" width=\"${lw + 2}\" height=\"${lh + 2}\" fill=\"${bgColor}\" rx=\"3\"/>`\n svg += `<image x=\"${lx + 2}\" y=\"${ly + 2}\" width=\"${lw - 4}\" height=\"${lh - 4}\" href=\"${options.logoBase64}\" xlink:href=\"${options.logoBase64}\" preserveAspectRatio=\"xMidYMid meet\"/>`\n }\n\n svg += '</svg>'\n return svg\n}\n\nexport interface QrTerminalOptions {\n /** Quiet zone in modules on each side (default 4, the QR standard). */\n quietZone?: number\n /** Draw light modules as blocks instead of dark ones, for a light-on-dark read. */\n invert?: boolean\n}\n\n/**\n * The QR code of `text` as terminal text. Each character covers two module\n * rows with half blocks (▀ ▄ █ and space), so the code keeps its square shape.\n * A block is a dark module; the quiet zone is light.\n */\nexport function qrTerminalText(text: string, options: QrTerminalOptions = {}): string {\n const quiet = options.quietZone ?? 4\n const modules = qrModules(text)\n const size = modules.length + quiet * 2\n const dark = (row: number, col: number): boolean => {\n const r = row - quiet\n const c = col - quiet\n const on = r >= 0 && c >= 0 && r < modules.length && c < modules.length && modules[r][c] === 1\n return options.invert ? !on : on\n }\n const lines: string[] = []\n for (let row = 0; row < size; row += 2) {\n let line = ''\n for (let col = 0; col < size; col++) {\n const top = dark(row, col)\n const bottom = row + 1 < size ? dark(row + 1, col) : options.invert === true\n line += top && bottom ? '█' : top ? '▀' : bottom ? '▄' : ' '\n }\n lines.push(line)\n }\n return lines.join('\\n')\n}\n","import type { WalletData } from './types.js'\nimport { createPublicClient, http, formatEther, formatUnits } from 'viem'\nimport { base } from 'viem/chains'\n\nconst USDC_ADDRESS = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913' as const\nconst DEFAULT_BASE_RPC_URL = 'https://mainnet.base.org'\nconst PUBLIC_BASE_RPC_URLS = [\n DEFAULT_BASE_RPC_URL,\n 'https://base-rpc.publicnode.com',\n 'https://base.drpc.org',\n 'https://1rpc.io/base',\n] as const\nconst USDC_ABI = [\n {\n name: 'balanceOf',\n type: 'function',\n stateMutability: 'view',\n inputs: [{ name: 'account', type: 'address' }],\n outputs: [{ name: '', type: 'uint256' }],\n },\n] as const\n\nfunction walletAddress(wallet: WalletData | string): `0x${string}` {\n return (typeof wallet === 'string' ? wallet : wallet.address) as `0x${string}`\n}\n\nexport async function getBalanceUsdc(wallet: WalletData | string): Promise<string> {\n const envRpcUrl = process.env.BASE_RPC_URL\n const rpcUrls = [\n ...(envRpcUrl ? [envRpcUrl] : []),\n ...PUBLIC_BASE_RPC_URLS.filter((url) => url !== envRpcUrl),\n ]\n\n for (const rpcUrl of rpcUrls) {\n try {\n const client = createPublicClient({ chain: base, transport: http(rpcUrl) })\n const balance = await client.readContract({\n address: USDC_ADDRESS,\n abi: USDC_ABI,\n functionName: 'balanceOf',\n args: [walletAddress(wallet)],\n })\n return formatUnits(balance, 6)\n } catch {\n // Try the next public Base RPC endpoint.\n }\n }\n\n return 'unknown'\n}\n\nexport async function getBalanceEth(wallet: WalletData | string): Promise<string> {\n const envRpcUrl = process.env.BASE_RPC_URL\n const rpcUrls = [\n ...(envRpcUrl ? [envRpcUrl] : []),\n ...PUBLIC_BASE_RPC_URLS.filter((url) => url !== envRpcUrl),\n ]\n\n for (const rpcUrl of rpcUrls) {\n try {\n const client = createPublicClient({ chain: base, transport: http(rpcUrl) })\n const balance = await client.getBalance({ address: walletAddress(wallet) })\n return formatEther(balance)\n } catch {\n // Try the next public Base RPC endpoint.\n }\n }\n\n return 'unknown'\n}\n\nexport async function getBalance(wallet: WalletData): Promise<string> {\n const [balanceUsdc, balanceEth] = await Promise.all([\n getBalanceUsdc(wallet),\n getBalanceEth(wallet),\n ])\n return [\n `Payment wallet: ${wallet.address}`,\n `USDC on Base: ${balanceUsdc}`,\n `Gas on Base: ${balanceEth} ETH`,\n `Payment network: Base`,\n `Base ETH is used only for one-time payment setup gas.`,\n ]\n .filter(Boolean)\n .join('\\n')\n}\n\nexport function getTopupInfo(wallet: WalletData): string {\n return JSON.stringify({\n wallet_address: wallet.address,\n network: 'Base (Chain ID 8453)',\n token: 'USDC',\n contract: USDC_ADDRESS,\n instructions: [`Send USDC on Base network to: ${wallet.address}`],\n })\n}\n","import { createServer, type Server } from 'node:http'\nimport { readFileSync } from 'node:fs'\nimport { createRequire } from 'node:module'\nimport { join, dirname } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { isAddress } from 'viem'\nimport type { WalletData } from './types.js'\nimport { generateQrSvg, qrModules } from './qr.js'\nimport { getBalanceEth, getBalanceUsdc } from './tools.js'\nimport {\n CIA_SYMBOL,\n CIA_TOKEN_ADDRESS,\n ROBINHOOD_CHAIN_ID,\n ROBINHOOD_NETWORK_DISPLAY,\n getCiaBalance,\n getRobinhoodEthBalance,\n} from '../robinhood.js'\nimport {\n SUBSCRIPTION_TOPUP_DESCRIPTION,\n SUBSCRIPTION_TOPUP_TOOL,\n WALLET_TOPUP_DESCRIPTION,\n} from '../../subscription/rules.js'\n\nconst USDC_ADDRESS = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'\n\nconst __dirname = dirname(fileURLToPath(import.meta.url))\n\n// Pre-load assets — try dist/assets first (installed), then src/assets (dev)\nfunction loadAsset(name: string): Buffer {\n const paths = [\n join(__dirname, 'assets', name), // dist/assets/ (global install)\n join(__dirname, '..', 'src', 'assets', name), // src/assets/ (dev)\n ]\n for (const p of paths) {\n try {\n return readFileSync(p)\n } catch {\n /* try next */\n }\n }\n console.error(`[chain-insights] Warning: asset ${name} not found`)\n return Buffer.alloc(0)\n}\n\nconst logoPng = loadAsset('logo.png')\nconst bgPatternPng = loadAsset('bg-pattern.png')\n\n/**\n * The top-up rails. base-usdc funds pay-per-call queries with USDC on Base;\n * robinhood-cia funds the subscription with $CIA and ETH on Robinhood Chain.\n * One local server per rail.\n */\nexport type TopupRail = 'base-usdc' | 'robinhood-cia'\n\nconst servers = new Map<TopupRail, { server: Server; port: number }>()\n\nfunction assertWalletAddress(wallet: WalletData | string): string {\n const walletAddress = typeof wallet === 'string' ? wallet : wallet.address\n if (!isAddress(walletAddress)) {\n throw new Error('Wallet address must be a valid 0x-prefixed 20-byte EVM address')\n }\n return walletAddress\n}\n\nfunction escapeHtml(value: string): string {\n return value\n .replaceAll('&', '&amp;')\n .replaceAll('<', '&lt;')\n .replaceAll('>', '&gt;')\n .replaceAll('\"', '&quot;')\n .replaceAll(\"'\", '&#39;')\n}\n\nfunction jsonForScript(value: string): string {\n return JSON.stringify(value)\n .replaceAll('<', '\\\\u003c')\n .replaceAll('>', '\\\\u003e')\n .replaceAll('&', '\\\\u0026')\n}\n\nexport function getTopupUrl(rail: TopupRail = 'base-usdc'): string | null {\n const running = servers.get(rail)\n return running ? `http://localhost:${running.port}` : null\n}\n\nasync function railBalance(rail: TopupRail, walletAddress: string): Promise<Record<string, string>> {\n if (rail === 'robinhood-cia') {\n const [balanceCia, balanceEth] = await Promise.all([\n getCiaBalance(walletAddress),\n getRobinhoodEthBalance(walletAddress),\n ])\n return { balance_cia: balanceCia, balance_eth: balanceEth }\n }\n const [balanceUsdc, balanceEth] = await Promise.all([\n getBalanceUsdc(walletAddress),\n getBalanceEth(walletAddress),\n ])\n return { balance_usdc: balanceUsdc, balance_eth: balanceEth }\n}\n\nconst UNKNOWN_BALANCE: Record<TopupRail, Record<string, string>> = {\n 'base-usdc': { balance_usdc: 'unknown', balance_eth: 'unknown' },\n 'robinhood-cia': { balance_cia: 'unknown', balance_eth: 'unknown' },\n}\n\nexport async function startTopupServer(\n wallet: WalletData | string,\n rail: TopupRail = 'base-usdc'\n): Promise<string> {\n const walletAddress = assertWalletAddress(wallet)\n\n const running = getTopupUrl(rail)\n if (running) {\n return running\n }\n\n return new Promise((resolve, reject) => {\n const server = createServer((req, res) => {\n if (req.url === '/api/wallet') {\n res.writeHead(200, {\n 'Content-Type': 'application/json',\n 'Access-Control-Allow-Origin': '*',\n })\n res.end(JSON.stringify({ address: walletAddress }))\n return\n }\n\n if (req.url === '/api/balance') {\n railBalance(rail, walletAddress)\n .then((balance) => {\n res.writeHead(200, {\n 'Content-Type': 'application/json',\n 'Access-Control-Allow-Origin': '*',\n })\n res.end(JSON.stringify(balance))\n })\n .catch(() => {\n res.writeHead(200, {\n 'Content-Type': 'application/json',\n 'Access-Control-Allow-Origin': '*',\n })\n res.end(JSON.stringify(UNKNOWN_BALANCE[rail]))\n })\n return\n }\n\n if (req.url === '/assets/logo.png') {\n res.writeHead(200, {\n 'Content-Type': 'image/png',\n 'Cache-Control': 'public, max-age=86400',\n 'Access-Control-Allow-Origin': '*',\n })\n res.end(logoPng)\n return\n }\n\n if (req.url === '/assets/bg-pattern.png') {\n res.writeHead(200, {\n 'Content-Type': 'image/png',\n 'Cache-Control': 'public, max-age=86400',\n 'Access-Control-Allow-Origin': '*',\n })\n res.end(bgPatternPng)\n return\n }\n\n res.writeHead(200, { 'Content-Type': 'text/html' })\n res.end(rail === 'robinhood-cia' ? generateSubscriptionPage(walletAddress) : generatePage(walletAddress))\n })\n\n server.listen(0, '127.0.0.1', () => {\n const addr = server.address()\n if (addr && typeof addr === 'object') {\n servers.set(rail, { server, port: addr.port })\n const url = `http://localhost:${addr.port}`\n console.error(`[chain-insights] Topup server running at ${url}`)\n resolve(url)\n } else {\n reject(new Error('Failed to start topup server'))\n }\n })\n\n server.on('error', reject)\n })\n}\n\nexport function generateArtifactHtml(walletAddressInput: string, topupUrl: string): string {\n const walletAddress = assertWalletAddress(walletAddressInput)\n const safeWalletAddress = escapeHtml(walletAddress)\n const safeTopupUrl = escapeHtml(topupUrl)\n const topupUrlJson = jsonForScript(topupUrl)\n\n const LOGO_B64 =\n 'data:image/png;base64,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'\n\n // Generate QR code SVG server-side — standard B&W, 2x size\n const qrSvg = generateQrSvg(walletAddress, { cellSize: 8 })\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n body {\n font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;\n background: rgba(10, 12, 17, 1) url('${safeTopupUrl}/assets/bg-pattern.png') top left / contain no-repeat;\n color: rgba(255, 255, 255, 0.9);\n display: flex;\n justify-content: center;\n padding: 24px;\n line-height: 1.3;\n }\n .card {\n max-width: 400px;\n width: 100%;\n background: rgba(19, 19, 24, 1);\n border: 1px solid rgba(255, 255, 255, 0.06);\n border-radius: 12px;\n padding: 28px 24px;\n text-align: center;\n }\n .logo { margin-bottom: 8px; }\n .logo img { height: 24px; }\n .subtitle { color: rgba(255, 255, 255, 0.5); font-size: 13px; margin-bottom: 20px; }\n .qr { margin-bottom: 16px; }\n .qr svg { border-radius: 12px; background: #fff; padding: 10px; }\n .address-wrap {\n position: relative;\n margin-bottom: 16px;\n }\n .address {\n width: 100%;\n background: rgba(10, 12, 17, 1);\n border: 1px solid rgba(255, 255, 255, 0.06);\n border-radius: 8px;\n padding: 10px 14px;\n font-family: 'SF Mono', 'Fira Code', monospace;\n font-size: 11px; color: #f2dda6; word-break: break-all;\n cursor: pointer; text-align: center;\n transition: border-color 0.3s linear;\n -webkit-user-select: all; user-select: all;\n outline: none;\n }\n .address:focus { border-color: #ae9d71; }\n .address-hint {\n font-size: 10px; color: rgba(255,255,255,0.3);\n text-align: center; margin-top: 4px;\n }\n .badge {\n display: inline-flex; align-items: center; gap: 6px;\n background: rgba(255, 255, 255, 0.04);\n border: 1px solid rgba(255, 255, 255, 0.06);\n border-radius: 20px; padding: 5px 12px;\n font-size: 11px; color: rgba(255, 255, 255, 0.5); margin-bottom: 20px;\n }\n .badge .dot { width: 7px; height: 7px; border-radius: 50%; background: #f2dda6; }\n .balance-line {\n font-size: 13px;\n color: rgba(255, 255, 255, 0.5);\n margin-bottom: 20px;\n line-height: 1.4;\n }\n .balance-line .amount {\n color: #4feb69;\n font-weight: 600;\n }\n .balance-line .gas {\n color: #f2dda6;\n font-weight: 600;\n }\n @keyframes flash { 0%{opacity:0.4} 100%{opacity:1} }\n .balance-line.flash .amount { animation: flash 1s ease-out; }\n .hint {\n margin-top: 14px; font-size: 12px;\n color: rgba(255, 255, 255, 0.3);\n line-height: 1.5;\n }\n</style>\n</head>\n<body>\n<div class=\"card\">\n <div class=\"logo\"><img src=\"${safeTopupUrl}/assets/logo.png\" alt=\"Chain Insights\"></div>\n <p class=\"subtitle\">Fund your wallet with USDC on Base</p>\n <div class=\"qr\">${qrSvg}</div>\n <div class=\"address-wrap\">\n <div class=\"address\" id=\"addr\" onclick=\"selectAndCopy()\">${safeWalletAddress}</div>\n <div class=\"address-hint\" id=\"addrHint\">Click to copy address</div>\n </div>\n <div class=\"badge\"><span class=\"dot\"></span>Base Network &middot; USDC</div>\n <p class=\"balance-line\" id=\"balLine\">Current balance: <span class=\"amount\" id=\"bal\">--</span> USDC<br>Gas balance: <span class=\"gas\" id=\"gas\">--</span> ETH<br>Base ETH is used for one-time payment setup gas.</p>\n</div>\n<script>\n// MCP Apps protocol handshake for the wallet top-up page.\n(function() {\n var initId = 1;\n\n // Listen for host messages\n window.addEventListener('message', function(event) {\n var data = event.data;\n if (!data || data.jsonrpc !== '2.0') return;\n\n // Initialize response\n if (data.id === initId && data.result) {\n // Send initialized notification\n window.parent.postMessage({\n jsonrpc: '2.0',\n method: 'ui/notifications/initialized',\n params: {}\n }, '*');\n\n // Send initial size\n var rect = document.documentElement.getBoundingClientRect();\n window.parent.postMessage({\n jsonrpc: '2.0',\n method: 'ui/notifications/size-changed',\n params: { width: Math.ceil(rect.width), height: Math.ceil(rect.height) }\n }, '*');\n }\n\n // Respond to pings\n if (data.method === 'ping' && data.id != null) {\n window.parent.postMessage({\n jsonrpc: '2.0',\n id: data.id,\n result: {}\n }, '*');\n }\n });\n\n // Send initialize request (must match App class protocol)\n window.parent.postMessage({\n jsonrpc: '2.0',\n id: initId,\n method: 'ui/initialize',\n params: {\n appInfo: { name: 'Chain Insights Topup', version: '1.0.0' },\n appCapabilities: {},\n protocolVersion: '2026-01-26'\n }\n }, '*');\n})();\n\n// Live balance polling\nvar lastBal = null;\nvar TOPUP_URL = ${topupUrlJson};\nfunction fetchBal() {\n fetch(TOPUP_URL + '/api/balance')\n .then(function(r) { return r.json(); })\n .then(function(d) {\n var el = document.getElementById('bal');\n var gas = document.getElementById('gas');\n var line = document.getElementById('balLine');\n var val = parseFloat(d.balance_usdc || '0').toFixed(2);\n var gasVal = d.balance_eth === 'unknown' ? '--' : parseFloat(d.balance_eth || '0').toFixed(6);\n if (d.balance_usdc === 'unknown') { el.textContent = '--'; gas.textContent = gasVal; return; }\n el.textContent = val;\n gas.textContent = gasVal;\n if (lastBal !== null && val !== lastBal) {\n line.classList.remove('flash');\n void line.offsetWidth;\n line.classList.add('flash');\n }\n lastBal = val;\n })\n .catch(function() {});\n}\nfetchBal();\nsetInterval(fetchBal, 10000);\n\nfunction selectAndCopy() {\n var addr = document.getElementById('addr');\n var hint = document.getElementById('addrHint');\n // Select the text\n var r = document.createRange();\n r.selectNodeContents(addr);\n var s = window.getSelection();\n s.removeAllRanges();\n s.addRange(r);\n // Try every clipboard method available\n var copied = false;\n try { copied = document.execCommand('copy'); } catch(e) {}\n if (!copied && navigator.clipboard && navigator.clipboard.writeText) {\n navigator.clipboard.writeText(addr.textContent).then(function() {\n hint.textContent = 'Copied!'; hint.style.color = '#4feb69';\n setTimeout(function() { hint.textContent = 'Click to copy address'; hint.style.color = ''; }, 2000);\n }).catch(function() {});\n }\n if (copied) {\n hint.textContent = 'Copied!'; hint.style.color = '#4feb69';\n setTimeout(function() { hint.textContent = 'Click to copy address'; hint.style.color = ''; }, 2000);\n } else {\n hint.textContent = 'Selected — press Ctrl+C'; hint.style.color = '#f2dda6';\n setTimeout(function() { hint.textContent = 'Click to copy address'; hint.style.color = ''; }, 3000);\n }\n}\n<\\/script>\n</body>\n</html>`\n}\n\n/** One balance row of the top-up page: the /api/balance field it shows. */\ninterface TopupBalanceRow {\n id: string\n label: string\n currency: string\n field: string\n decimals: number\n}\n\n/**\n * The text of the one top-up page, per rail. The page is the same for both\n * rails (aphex5, 2026-10-28: \"identical, just different text\"): the layout,\n * the style and the script never fork by rail, only these strings do.\n */\ninterface TopupPageText {\n title: string\n subtitle: string\n badge: string\n balances: TopupBalanceRow[]\n gasNote: string\n /** HTML: constant text only, never a value from outside. */\n info: string\n}\n\n/** The text of the subscription top-up page and of `cia subscription topup`. */\nexport const SUBSCRIPTION_TOPUP_TEXT =\n 'Send $CIA and a little ETH on Robinhood Chain to your own wallet. Then run `cia subscription buy --days N`.'\n\nconst TOPUP_PAGE_TEXT: Record<TopupRail, TopupPageText> = {\n 'base-usdc': {\n title: 'Chain Insights — Fund Wallet',\n subtitle: 'Fund your wallet to use blockchain intelligence tools',\n badge: 'Base Network &middot; USDC',\n balances: [\n { id: 'balance', label: 'Current balance', currency: 'USDC', field: 'balance_usdc', decimals: 2 },\n { id: 'gasBalance', label: 'Gas balance', currency: 'ETH', field: 'balance_eth', decimals: 6 },\n ],\n gasNote: 'Base ETH is used for one-time payment setup gas.',\n info: 'Send Base USDC to the wallet address above.',\n },\n 'robinhood-cia': {\n title: 'Chain Insights — Fund the subscription wallet',\n subtitle: 'Fund your wallet for the Chain Insights subscription',\n badge: 'Robinhood Chain &middot; $CIA',\n balances: [\n { id: 'balance', label: 'Current balance', currency: '$CIA', field: 'balance_cia', decimals: 2 },\n { id: 'gasBalance', label: 'Gas balance', currency: 'ETH', field: 'balance_eth', decimals: 6 },\n ],\n gasNote: 'Robinhood Chain ETH pays the gas of the two buy transactions.',\n info:\n 'Send $CIA and a little ETH on Robinhood Chain to your own wallet. Then run <code>cia subscription buy --days N</code>.' +\n `<br>Robinhood Chain (chain ID 4663) &middot; $CIA <code>${CIA_TOKEN_ADDRESS}</code>` +\n '<br>This page shows your own address only. It signs nothing and sends nothing.',\n },\n}\n\n/**\n * The one local top-up page: the wallet address, its QR code (drawn here, no\n * script from outside), the network and the balances. It names only the\n * user's own wallet, never where a payment goes.\n */\nexport function generateTopupPage(walletAddressInput: string, rail: TopupRail = 'base-usdc'): string {\n const walletAddress = assertWalletAddress(walletAddressInput)\n const safeWalletAddress = escapeHtml(walletAddress)\n const walletAddressJson = jsonForScript(walletAddress)\n const text = TOPUP_PAGE_TEXT[rail]\n const qrSvg = generateQrSvg(walletAddress, { cellSize: 5 })\n const balanceRows = text.balances\n .map(\n (row) => ` <div class=\"balance-row\">\n <span class=\"balance-label\">${row.label}</span>\n <span class=\"balance-value\" id=\"${row.id}\">—<span class=\"currency\">${row.currency}</span></span>\n </div>`\n )\n .join('\\n')\n const balancesJson = JSON.stringify(text.balances.map(({ id, currency, field, decimals }) => ({ id, currency, field, decimals })))\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>${text.title}</title>\n<style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n\n body {\n font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;\n background: rgba(10, 12, 17, 1);\n color: rgba(255, 255, 255, 0.9);\n min-height: 100vh;\n display: flex;\n justify-content: center;\n align-items: center;\n line-height: 1.3;\n }\n\n .container {\n max-width: 480px;\n width: 100%;\n padding: 24px;\n }\n\n .logo {\n text-align: center;\n margin-bottom: 32px;\n }\n\n .logo h1 {\n font-size: 24px;\n font-weight: 600;\n color: #f2dda6;\n letter-spacing: -0.5px;\n }\n\n .logo p {\n color: rgba(255, 255, 255, 0.5);\n font-size: 14px;\n margin-top: 4px;\n }\n\n .card {\n background: rgba(19, 19, 24, 1);\n border: 1px solid rgba(255, 255, 255, 0.06);\n border-radius: 12px;\n padding: 32px 24px;\n }\n\n .qr-container {\n display: flex;\n justify-content: center;\n margin-bottom: 24px;\n }\n\n .qr-container svg, .qr-container canvas, .qr-container img {\n border-radius: 12px;\n background: #fff;\n padding: 12px;\n }\n\n .address-box {\n background: rgba(10, 12, 17, 1);\n border: 1px solid rgba(255, 255, 255, 0.06);\n border-radius: 8px;\n padding: 12px 16px;\n display: flex;\n align-items: center;\n gap: 8px;\n margin-bottom: 24px;\n }\n\n .address-box code {\n font-family: 'SF Mono', 'Fira Code', monospace;\n font-size: 12px;\n color: #f2dda6;\n word-break: break-all;\n flex: 1;\n }\n\n .copy-btn {\n background: none;\n border: none;\n color: rgba(255, 255, 255, 0.4);\n cursor: pointer;\n padding: 4px;\n font-size: 18px;\n transition: color 0.3s linear;\n }\n\n .copy-btn:hover { color: #f2dda6; }\n .copy-btn.copied { color: #4feb69; }\n\n .network-badge {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n background: rgba(255, 255, 255, 0.04);\n border: 1px solid rgba(255, 255, 255, 0.06);\n border-radius: 20px;\n padding: 6px 12px;\n font-size: 12px;\n color: rgba(255, 255, 255, 0.5);\n margin-bottom: 24px;\n }\n\n .network-badge .dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: #f2dda6;\n }\n\n .balance-row {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 12px 0;\n border-top: 1px solid rgba(255, 255, 255, 0.06);\n margin-bottom: 16px;\n }\n\n .balance-label { color: rgba(255, 255, 255, 0.5); font-size: 14px; }\n\n .balance-value {\n font-size: 20px;\n font-weight: 600;\n color: rgba(255, 255, 255, 0.9);\n }\n\n .balance-value .currency {\n font-size: 14px;\n color: rgba(255, 255, 255, 0.5);\n font-weight: 400;\n margin-left: 4px;\n }\n\n .gas-note {\n margin-top: -8px;\n margin-bottom: 16px;\n color: rgba(255,255,255,0.45);\n font-size: 12px;\n line-height: 1.4;\n text-align: left;\n }\n\n .metamask-btn {\n width: 100%;\n padding: 14px;\n border: none;\n border-radius: 10px;\n font-size: 15px;\n font-weight: 600;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 10px;\n transition: all 0.2s;\n background: #f6851b;\n color: #fff;\n }\n\n .metamask-btn:hover { background: #e2761b; transform: translateY(-1px); }\n .metamask-btn:disabled { opacity: 0.5; cursor: not-allowed; transform: none; }\n\n .metamask-btn svg { width: 22px; height: 22px; }\n\n .amount-input {\n width: 100%;\n padding: 12px 16px;\n background: #0a0c11;\n border: 1px solid rgba(255, 255, 255, 0.06);\n border-radius: 8px;\n color: rgba(255, 255, 255, 0.9);\n font-size: 16px;\n margin-bottom: 16px;\n outline: none;\n transition: border-color 0.2s;\n }\n\n .amount-input:focus { border-color: #f2dda6; }\n\n .amount-label {\n font-size: 13px;\n color: rgba(255, 255, 255, 0.5);\n margin-bottom: 6px;\n display: block;\n }\n\n .status {\n text-align: center;\n padding: 12px;\n border-radius: 8px;\n font-size: 14px;\n margin-top: 16px;\n display: none;\n }\n\n .status.success { display: block; background: #0a2e1a; color: #4feb69; border: 1px solid #1a4a2e; }\n .status.error { display: block; background: #2e0a0a; color: #eb4f4f; border: 1px solid #4a1a1a; }\n .status.pending { display: block; background: #1a1a0a; color: #f2dda6; border: 1px solid #4a4a1a; }\n\n .info {\n text-align: center;\n margin-top: 24px;\n font-size: 12px;\n color: rgba(255, 255, 255, 0.3);\n line-height: 1.6;\n }\n\n .info code {\n font-family: 'SF Mono', 'Fira Code', monospace;\n color: #f2dda6;\n word-break: break-all;\n }\n</style>\n</head>\n<body>\n<div class=\"container\">\n <div class=\"logo\">\n <h1>Chain Insights</h1>\n <p>${text.subtitle}</p>\n </div>\n\n <div class=\"card\">\n <div class=\"qr-container\" id=\"qr\">${qrSvg}</div>\n\n <div class=\"address-box\">\n <code id=\"address\">${safeWalletAddress}</code>\n <button class=\"copy-btn\" onclick=\"copyAddress()\" id=\"copyBtn\" title=\"Copy address\">&#x2398;</button>\n </div>\n\n <div class=\"network-badge\">\n <span class=\"dot\"></span>\n ${text.badge}\n </div>\n\n${balanceRows}\n <p class=\"gas-note\">${text.gasNote}</p>\n\n <div class=\"status\" id=\"status\"></div>\n </div>\n\n <p class=\"info\">${text.info}</p>\n</div>\n\n<script>\nconst WALLET = ${walletAddressJson};\nconst BALANCES = ${balancesJson};\n\n// Copy address — fallback for sandboxed iframes where navigator.clipboard is blocked\nfunction copyAddress() {\n var ok = false;\n if (navigator.clipboard && navigator.clipboard.writeText) {\n navigator.clipboard.writeText(WALLET).then(function() { ok = true; }).catch(function() {});\n }\n if (!ok) {\n var ta = document.createElement('textarea');\n ta.value = WALLET;\n ta.style.position = 'fixed';\n ta.style.left = '-9999px';\n document.body.appendChild(ta);\n ta.select();\n try { document.execCommand('copy'); } catch(e) {}\n document.body.removeChild(ta);\n }\n var btn = document.getElementById('copyBtn');\n btn.classList.add('copied');\n btn.innerHTML = '&#x2713;';\n setTimeout(function() { btn.classList.remove('copied'); btn.innerHTML = '&#x2398;'; }, 2000);\n}\n\nfunction showBalance(row, value) {\n document.getElementById(row.id).innerHTML = value + '<span class=\"currency\">' + row.currency + '</span>';\n}\n\n// Fetch balances\nasync function fetchBalance() {\n var json = {};\n try { json = await (await fetch('/api/balance')).json(); } catch(e) {}\n BALANCES.forEach(function(row) {\n var raw = json[row.field];\n showBalance(row, raw === undefined || raw === 'unknown' ? '—' : Number(raw || 0).toFixed(row.decimals));\n });\n}\nfetchBalance();\nsetInterval(fetchBalance, 15000);\n\n</script>\n</body>\n</html>`\n}\n\n/** The Base USDC page. */\nfunction generatePage(walletAddressInput: string): string {\n return generateTopupPage(walletAddressInput, 'base-usdc')\n}\n\n/** The robinhood-cia page: the same page, the subscription text. */\nexport function generateSubscriptionPage(walletAddressInput: string): string {\n return generateTopupPage(walletAddressInput, 'robinhood-cia')\n}\n\n// ---------------------------------------------------------------------------\n// The local top-up view: an MCP App drawn by Claude Desktop next to the\n// wallet_topup answer of the local proxy. It shows the wallet address, its QR\n// code, Base Mainnet and USDC. It fetches nothing, signs nothing and sends\n// nothing: the page holds no wallet code, only an address to read.\n// The hosted connector never serves it (ruling 2026-10-05).\n// ---------------------------------------------------------------------------\n\nexport const TOPUP_TOOL_NAME = 'wallet_topup'\nexport const TOPUP_VIEW_URI = 'ui://chain-insights/topup'\nexport const TOPUP_NETWORK_DISPLAY = 'Base Mainnet'\nconst TOPUP_CHAIN_ID = 8453\nconst EXT_APPS_GLOBAL = '__chainInsightsExtApps'\nconst NO_WALLET_TEXT =\n 'No local payment wallet exists yet. Run `cia wallet create` to make one, then ask to top up again.'\n\nexport interface TopupViewFacts {\n wallet_address: string\n network: 'Base'\n network_display: typeof TOPUP_NETWORK_DISPLAY\n chain_id: typeof TOPUP_CHAIN_ID\n token: 'USDC'\n token_contract: typeof USDC_ADDRESS\n}\n\nexport interface TopupToolResult {\n [key: string]: unknown\n content: Array<{ type: 'text'; text: string }>\n structuredContent: {\n schema: 'chain-insights.result.v1'\n tool: typeof TOPUP_TOOL_NAME\n hint: string | null\n facts: { wallet_configured: boolean; topup: TopupViewFacts | null }\n }\n isError: false\n}\n\n/**\n * The wallet_topup answer. The text is complete on its own, for hosts that\n * draw no view. `walletAddress` null means no local wallet exists.\n */\nexport function topupToolResult(walletAddress: string | null): TopupToolResult {\n if (walletAddress === null) {\n return {\n content: [{ type: 'text', text: NO_WALLET_TEXT }],\n structuredContent: {\n schema: 'chain-insights.result.v1',\n tool: TOPUP_TOOL_NAME,\n hint: 'Run `cia wallet create`.',\n facts: { wallet_configured: false, topup: null },\n },\n isError: false,\n }\n }\n const address = assertWalletAddress(walletAddress)\n const topup: TopupViewFacts = {\n wallet_address: address,\n network: 'Base',\n network_display: TOPUP_NETWORK_DISPLAY,\n chain_id: TOPUP_CHAIN_ID,\n token: 'USDC',\n token_contract: USDC_ADDRESS,\n }\n return {\n content: [\n {\n type: 'text',\n text: [\n `Payment wallet: ${address}`,\n `Network: ${TOPUP_NETWORK_DISPLAY} (chain ID ${TOPUP_CHAIN_ID})`,\n `Token: USDC (contract ${USDC_ADDRESS})`,\n `To top up, send USDC on ${TOPUP_NETWORK_DISPLAY} to the wallet address. Chain Insights makes no payment from here.`,\n ].join('\\n'),\n },\n ],\n structuredContent: {\n schema: 'chain-insights.result.v1',\n tool: TOPUP_TOOL_NAME,\n hint: null,\n facts: { wallet_configured: true, topup },\n },\n isError: false,\n }\n}\n\nexport interface SubscriptionTopupViewFacts {\n wallet_address: string\n network: typeof ROBINHOOD_NETWORK_DISPLAY\n network_display: typeof ROBINHOOD_NETWORK_DISPLAY\n chain_id: typeof ROBINHOOD_CHAIN_ID\n token: typeof CIA_SYMBOL\n token_contract: typeof CIA_TOKEN_ADDRESS\n gas_token: 'ETH'\n cia_balance: string\n eth_balance: string\n}\n\nexport interface SubscriptionTopupToolResult {\n [key: string]: unknown\n content: Array<{ type: 'text'; text: string }>\n structuredContent: {\n schema: 'chain-insights.result.v1'\n tool: typeof SUBSCRIPTION_TOPUP_TOOL\n hint: string | null\n facts: { wallet_configured: boolean; topup: SubscriptionTopupViewFacts | null }\n }\n isError: false\n}\n\n/** The rule the subscription_topup answer ends with. */\nexport const SUBSCRIPTION_TOPUP_RULE =\n 'Fund this wallet. The user buys days in a terminal with `cia subscription buy --days N`. An agent cannot buy. Never send $CIA by hand to any other address: it is not credited.'\n\n/**\n * The subscription_topup answer: the local wallet on Robinhood Chain, $CIA,\n * the balances ('unknown' when no RPC answers). It names the user's own\n * wallet only. `walletAddress` null means no local wallet exists.\n */\nexport function subscriptionTopupToolResult(\n walletAddress: string | null,\n balances: { cia: string; eth: string } = { cia: 'unknown', eth: 'unknown' }\n): SubscriptionTopupToolResult {\n if (walletAddress === null) {\n return {\n content: [{ type: 'text', text: 'No local wallet exists yet. Run `cia wallet create` to make one, then ask to top up again.' }],\n structuredContent: {\n schema: 'chain-insights.result.v1',\n tool: SUBSCRIPTION_TOPUP_TOOL,\n hint: 'Run `cia wallet create`.',\n facts: { wallet_configured: false, topup: null },\n },\n isError: false,\n }\n }\n const address = assertWalletAddress(walletAddress)\n const topup: SubscriptionTopupViewFacts = {\n wallet_address: address,\n network: ROBINHOOD_NETWORK_DISPLAY,\n network_display: ROBINHOOD_NETWORK_DISPLAY,\n chain_id: ROBINHOOD_CHAIN_ID,\n token: CIA_SYMBOL,\n token_contract: CIA_TOKEN_ADDRESS,\n gas_token: 'ETH',\n cia_balance: balances.cia,\n eth_balance: balances.eth,\n }\n return {\n content: [\n {\n type: 'text',\n text: [\n `Subscription wallet: ${address}`,\n `Network: ${ROBINHOOD_NETWORK_DISPLAY} (chain ID ${ROBINHOOD_CHAIN_ID})`,\n `Token: ${CIA_SYMBOL} (contract ${CIA_TOKEN_ADDRESS}), with ETH for gas`,\n `Balance: ${balances.cia} ${CIA_SYMBOL}, ${balances.eth} ETH`,\n SUBSCRIPTION_TOPUP_RULE,\n ].join('\\n'),\n },\n ],\n structuredContent: {\n schema: 'chain-insights.result.v1',\n tool: SUBSCRIPTION_TOPUP_TOOL,\n hint: null,\n facts: { wallet_configured: true, topup },\n },\n isError: false,\n }\n}\n\nlet extAppsScript: string | null = null\n\n/**\n * The browser build of the ext-apps App client (the same 1.x pin as\n * the Chain Insights UI package), turned from an ES module into a script that publishes\n * its exports on globalThis, so the view stays one self-contained file.\n */\nexport function extAppsBrowserScript(): string {\n if (extAppsScript !== null) return extAppsScript\n const require = createRequire(import.meta.url)\n const source = readFileSync(\n require.resolve('@modelcontextprotocol/ext-apps/app-with-deps'),\n 'utf8'\n )\n const match = /export\\s*\\{([^}]*)\\}\\s*;?\\s*(?:\\/\\/# sourceMappingURL=\\S*\\s*)?$/.exec(source)\n if (!match) throw new Error('The ext-apps app bundle does not end with one export list')\n const fields = match[1]\n .split(',')\n .map((part) => part.trim())\n .filter(Boolean)\n .map((part) => {\n const [local, exported] = part.split(/\\s+as\\s+/)\n return `${JSON.stringify(exported ?? local)}:${local}`\n })\n extAppsScript = `${source.slice(0, match.index)}\\nglobalThis.${EXT_APPS_GLOBAL}={${fields.join(',')}};\\n`\n extAppsScript = extAppsScript.replaceAll('</script', '<\\\\/script')\n return extAppsScript\n}\n\nconst TOPUP_VIEW_STYLE = `\n:root {\n color-scheme: light;\n --ci-text: var(--color-text-primary, #1d1b16);\n --ci-muted: var(--color-text-secondary, #5c5647);\n --ci-surface: var(--color-background-secondary, #f7f4ec);\n --ci-border: var(--color-border-primary, #d9d2c0);\n --ci-accent: #8a6a12;\n --ci-on-accent: #ffffff;\n --ci-qr-dark: #000000;\n --ci-qr-light: #ffffff;\n --ci-font: var(--font-sans, ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif);\n --ci-mono: var(--font-mono, ui-monospace, 'SF Mono', Menlo, Consolas, monospace);\n}\nhtml[data-theme='dark'] {\n color-scheme: dark;\n --ci-text: var(--color-text-primary, #f2eee4);\n --ci-muted: var(--color-text-secondary, #b8b09c);\n --ci-surface: var(--color-background-secondary, #1f1d19);\n --ci-border: var(--color-border-primary, #3d392f);\n --ci-accent: #e3c46a;\n --ci-on-accent: #1d1b16;\n}\n* { box-sizing: border-box; margin: 0; padding: 0; }\nhtml, body { background: transparent; }\nbody { font-family: var(--ci-font); color: var(--ci-text); font-size: 14px; line-height: 1.45; padding: 4px; }\n.card { max-width: 420px; margin: 0 auto; padding: 20px; border: 1px solid var(--ci-border); border-radius: 12px; background: var(--ci-surface); }\nh1 { font-size: 16px; font-weight: 600; margin-bottom: 4px; }\n.lead, .note, .muted { color: var(--ci-muted); }\n.lead { margin-bottom: 16px; }\n.qr-tile { display: flex; justify-content: center; margin-bottom: 16px; }\n.qr { width: 200px; height: 200px; border-radius: 8px; }\n.qr-dark { fill: var(--ci-qr-dark); }\n.qr-light { fill: var(--ci-qr-light); }\ndl { display: grid; grid-template-columns: max-content 1fr; gap: 6px 12px; margin-bottom: 16px; }\ndt { color: var(--ci-muted); }\ndd { overflow-wrap: anywhere; }\ncode { font-family: var(--ci-mono); font-size: 12px; }\n.address { display: block; margin-bottom: 6px; }\nbutton { font: inherit; font-size: 13px; padding: 4px 12px; border-radius: 6px; border: 0; background: var(--ci-accent); color: var(--ci-on-accent); cursor: pointer; }\nbutton:focus-visible { outline: 2px solid var(--ci-accent); outline-offset: 2px; }\n.copy-state { margin-left: 8px; font-size: 12px; color: var(--ci-muted); }\n.skeleton { height: 14px; border-radius: 4px; background: var(--ci-border); margin-bottom: 10px; }\n.skeleton.short { width: 60%; }\n`\n\nconst TOPUP_VIEW_SCRIPT = `\n(function () {\n var extApps = globalThis.${EXT_APPS_GLOBAL};\n var qrModules = __QR_MODULES__;\n var root = document.getElementById('topup');\n var ADDRESS = /^0x[0-9a-fA-F]{40}$/;\n\n function el(tag, attrs, children) {\n var node = document.createElement(tag);\n Object.keys(attrs || {}).forEach(function (key) {\n if (key === 'text') node.textContent = attrs[key];\n else node.setAttribute(key, attrs[key]);\n });\n (children || []).forEach(function (child) {\n node.append(typeof child === 'string' ? document.createTextNode(child) : child);\n });\n return node;\n }\n\n function qrSvg(address) {\n var modules = qrModules(address);\n var quiet = 4;\n var total = modules.length + quiet * 2;\n var ns = 'http://www.w3.org/2000/svg';\n var svg = document.createElementNS(ns, 'svg');\n svg.setAttribute('class', 'qr');\n svg.setAttribute('viewBox', '0 0 ' + total + ' ' + total);\n svg.setAttribute('shape-rendering', 'crispEdges');\n svg.setAttribute('role', 'img');\n svg.setAttribute('aria-label', 'QR code of the wallet address ' + address);\n var light = document.createElementNS(ns, 'rect');\n light.setAttribute('class', 'qr-light');\n light.setAttribute('width', String(total));\n light.setAttribute('height', String(total));\n svg.appendChild(light);\n var d = '';\n for (var row = 0; row < modules.length; row++) {\n for (var col = 0; col < modules.length; col++) {\n if (modules[row][col] === 1) d += 'M' + (col + quiet) + ' ' + (row + quiet) + 'h1v1h-1z';\n }\n }\n var dark = document.createElementNS(ns, 'path');\n dark.setAttribute('class', 'qr-dark');\n dark.setAttribute('d', d);\n svg.appendChild(dark);\n return svg;\n }\n\n function show(children) {\n root.replaceChildren(el('div', { class: 'card' }, children));\n }\n\n function resultText(result) {\n return ((result && result.content) || [])\n .filter(function (item) { return item && item.type === 'text' && typeof item.text === 'string'; })\n .map(function (item) { return item.text; })\n .join('\\\\n');\n }\n\n function copyButton(address, target) {\n var state = el('span', { class: 'copy-state', 'aria-live': 'polite' });\n var button = el('button', { type: 'button', text: 'Copy address' });\n button.addEventListener('click', function () {\n function selectIt(message) {\n var range = document.createRange();\n range.selectNodeContents(target);\n var selection = window.getSelection();\n selection.removeAllRanges();\n selection.addRange(range);\n state.textContent = message;\n }\n if (navigator.clipboard && navigator.clipboard.writeText) {\n navigator.clipboard.writeText(address).then(\n function () { state.textContent = 'Copied'; },\n function () { selectIt('Selected. Press Ctrl+C to copy.'); }\n );\n } else {\n selectIt('Selected. Press Ctrl+C to copy.');\n }\n });\n return el('div', {}, [button, state]);\n }\n\n function render(result) {\n var structured = result && result.structuredContent;\n var facts = structured && (structured.tool === '${TOPUP_TOOL_NAME}' || structured.tool === '${SUBSCRIPTION_TOPUP_TOOL}') ? structured.facts : null;\n if (!result || result.isError || !facts) {\n show([el('h1', { text: 'Wallet top-up' }), el('p', { class: 'muted', text: resultText(result) || 'The wallet details did not arrive.' })]);\n return;\n }\n if (!facts.topup) {\n show([\n el('h1', { text: 'No payment wallet yet' }),\n el('p', { class: 'lead' }, ['Create one with ', el('code', { text: 'cia wallet create' }), ', then ask to top up again.'])\n ]);\n return;\n }\n var topup = facts.topup;\n if (!ADDRESS.test(topup.wallet_address)) {\n show([el('h1', { text: 'Wallet top-up' }), el('p', { class: 'muted', text: 'The wallet address is not a valid 0x address.' })]);\n return;\n }\n var address = el('code', { class: 'address', text: topup.wallet_address });\n show([\n el('h1', { text: 'Top up your Chain Insights wallet' }),\n el('p', { class: 'lead', text: 'Send ' + topup.token + ' on ' + topup.network_display + ' to this address.' }),\n el('div', { class: 'qr-tile' }, [qrSvg(topup.wallet_address)]),\n el('dl', {}, [\n el('dt', { text: 'Network' }), el('dd', { text: topup.network_display + ' (chain ID ' + topup.chain_id + ')' }),\n el('dt', { text: 'Token' }), el('dd', { text: topup.token }),\n el('dt', { text: 'Token contract' }), el('dd', {}, [el('code', { text: topup.token_contract })]),\n el('dt', { text: 'Wallet' }), el('dd', {}, [address, copyButton(topup.wallet_address, address)])\n ]),\n el('p', { class: 'note', text: 'This view shows an address only. It signs nothing and sends nothing.' })\n ]);\n }\n\n function applyContext(context) {\n if (!context) return;\n if (context.theme === 'light' || context.theme === 'dark') extApps.applyDocumentTheme(context.theme);\n var styles = context.styles;\n if (styles && styles.variables) extApps.applyHostStyleVariables(styles.variables);\n if (styles && styles.css && styles.css.fonts) extApps.applyHostFonts(styles.css.fonts);\n }\n\n // Until the host names its theme, follow the system; the host's theme wins.\n if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {\n document.documentElement.setAttribute('data-theme', 'dark');\n }\n\n var app = new extApps.App({ name: 'chain-insights-topup', version: '1.0.0' }, {});\n app.ontoolresult = function (result) { render(result); };\n app.onhostcontextchanged = function (change) { applyContext(change); };\n app.onteardown = function () { return {}; };\n app.connect(new extApps.PostMessageTransport(window.parent, window.parent)).then(\n function () { applyContext(app.getHostContext()); },\n function () {\n show([el('h1', { text: 'Wallet top-up' }), el('p', { class: 'muted', text: 'This view opens inside Claude, next to the ${TOPUP_TOOL_NAME} answer.' })]);\n }\n );\n})();\n`\n\n/**\n * The QR encoder as browser source: an expression that evaluates to\n * `qrModules`. A transform that keeps function names (esbuild keepNames, as\n * tsx runs it) wraps inner functions in `__name(...)`; the wrapper gives that\n * helper a do-nothing body so the source runs anywhere.\n */\nexport function qrModulesSource(): string {\n return `(function () { var __name = function (target) { return target; }; return (${qrModules.toString()}); })()`\n}\n\n/** The one self-contained HTML file of ui://chain-insights/topup. */\nexport function topupViewHtml(): string {\n const script = TOPUP_VIEW_SCRIPT.replace('__QR_MODULES__', () => qrModulesSource())\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Wallet top-up</title>\n<style>${TOPUP_VIEW_STYLE}</style>\n</head>\n<body>\n<main id=\"topup\" aria-live=\"polite\"><div class=\"card\"><div class=\"skeleton\"></div><div class=\"skeleton short\"></div><p class=\"muted\">Waiting for the wallet details.</p></div></main>\n<script type=\"module\">${extAppsBrowserScript()}</script>\n<script type=\"module\">${script.replaceAll('</script', '<\\\\/script')}</script>\n</body>\n</html>`\n}\n\n/** The parts of McpServer the top-up view registers on. */\nexport type TopupViewServer = Pick<\n import('@modelcontextprotocol/sdk/server/mcp.js').McpServer,\n 'registerTool' | 'registerResource'\n>\n\nexport interface TopupViewDependencies {\n /** The local wallet address, or null when no wallet exists. Throws when it cannot be read. */\n walletAddress: () => Promise<string | null>\n /** The $CIA and ETH balances on Robinhood Chain, 'unknown' when no RPC answers. */\n robinhoodBalances?: (address: string) => Promise<{ cia: string; eth: string }>\n}\n\nasync function readRobinhoodBalances(address: string): Promise<{ cia: string; eth: string }> {\n const [cia, eth] = await Promise.all([getCiaBalance(address), getRobinhoodEthBalance(address)])\n return { cia, eth }\n}\n\n/**\n * Register the wallet_topup tool and its view on the local proxy. The tool\n * reads the local wallet address and nothing else.\n */\nexport async function registerTopupView(\n server: TopupViewServer,\n deps: TopupViewDependencies\n): Promise<void> {\n const { registerAppResource, registerAppTool, RESOURCE_MIME_TYPE } =\n await import('@modelcontextprotocol/ext-apps/server')\n const uiMeta = { prefersBorder: false, csp: { resourceDomains: ['https://assets.claude.ai'] } }\n\n registerAppTool(\n server,\n TOPUP_TOOL_NAME,\n {\n title: 'Top Up Wallet',\n description: WALLET_TOPUP_DESCRIPTION,\n annotations: {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n _meta: { ui: { resourceUri: TOPUP_VIEW_URI } },\n },\n async () => {\n try {\n return topupToolResult(await deps.walletAddress())\n } catch (err) {\n return {\n content: [{ type: 'text' as const, text: `Top-up failed: ${(err as Error).message}` }],\n isError: true,\n }\n }\n }\n )\n\n registerAppTool(\n server,\n SUBSCRIPTION_TOPUP_TOOL,\n {\n title: 'Top Up Subscription Wallet',\n description: SUBSCRIPTION_TOPUP_DESCRIPTION,\n annotations: {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n // It reads the balances on Robinhood Chain.\n openWorldHint: true,\n },\n _meta: { ui: { resourceUri: TOPUP_VIEW_URI } },\n },\n async () => {\n try {\n const address = await deps.walletAddress()\n if (address === null) return subscriptionTopupToolResult(null)\n const balances = await (deps.robinhoodBalances ?? readRobinhoodBalances)(address)\n return subscriptionTopupToolResult(address, balances)\n } catch (err) {\n return {\n content: [{ type: 'text' as const, text: `Top-up failed: ${(err as Error).message}` }],\n isError: true,\n }\n }\n }\n )\n\n registerAppResource(\n server,\n 'Chain Insights wallet top-up',\n TOPUP_VIEW_URI,\n {\n description:\n 'The local wallet address and its QR code: on Base Mainnet with USDC, or on Robinhood Chain with $CIA.',\n _meta: { ui: uiMeta },\n },\n async () => ({\n contents: [\n {\n uri: TOPUP_VIEW_URI,\n mimeType: RESOURCE_MIME_TYPE,\n text: topupViewHtml(),\n _meta: { ui: uiMeta },\n },\n ],\n })\n )\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chain-insights",
3
- "version": "0.73.1",
3
+ "version": "0.73.2",
4
4
  "description": "AML investigation CLI and MCP proxy for blockchain risk screening and Chain Insights Graph access",
5
5
  "homepage": "https://chain-insights.ai",
6
6
  "repository": {
@@ -1 +0,0 @@
1
- {"version":3,"file":"topup-server-9rA3qLgf.mjs","names":["USDC_ADDRESS"],"sources":["../src/wallet/mcp-proxy/qr.ts","../src/wallet/mcp-proxy/tools.ts","../src/wallet/mcp-proxy/topup-server.ts"],"sourcesContent":["/**\n * Minimal QR code generator. No external dependencies.\n *\n * Byte mode, error correction level L, versions 1 to 4 (single block), mask 0.\n * Version 3 holds a 42-character EVM address; version 4 holds up to 78 bytes.\n *\n * `qrModules` is self-contained on purpose: the top-up view inlines its source\n * (`qrModules.toString()`) and draws the same QR code inside the Claude view,\n * so the function must not reference anything outside its own body.\n */\n\n/**\n * The QR code modules for `text`: a square matrix, 1 for a dark module and 0\n * for a light one, without the quiet zone. Throws when `text` is longer than\n * 78 UTF-8 bytes.\n */\nexport function qrModules(text: string): number[][] {\n // Level L, single-block versions: [version, total codewords, EC codewords].\n const versions = [\n [1, 26, 7],\n [2, 44, 10],\n [3, 70, 15],\n [4, 100, 20],\n ]\n // Format information for level L and mask 0, BCH-coded and masked.\n const formatBits = 0x77c4\n const bytes = Array.from(new TextEncoder().encode(text))\n\n let version = 0\n let totalCodewords = 0\n let ecCodewords = 0\n for (const entry of versions) {\n const dataCodewords = entry[1] - entry[2]\n if (4 + 8 + bytes.length * 8 <= dataCodewords * 8) {\n version = entry[0]\n totalCodewords = entry[1]\n ecCodewords = entry[2]\n break\n }\n }\n if (version === 0) throw new Error('QR text is too long: at most 78 bytes')\n\n const dataCodewords = totalCodewords - ecCodewords\n const size = version * 4 + 17\n\n // Data bits: mode, count, bytes, terminator, byte padding, pad codewords.\n const bits: number[] = []\n const pushBits = (value: number, length: number) => {\n for (let i = length - 1; i >= 0; i--) bits.push((value >>> i) & 1)\n }\n pushBits(0b0100, 4)\n pushBits(bytes.length, 8)\n for (const value of bytes) pushBits(value, 8)\n const capacity = dataCodewords * 8\n pushBits(0, Math.min(4, capacity - bits.length))\n while (bits.length % 8 !== 0) bits.push(0)\n for (let pad = 0xec; bits.length < capacity; pad ^= 0xec ^ 0x11) pushBits(pad, 8)\n const data: number[] = []\n for (let i = 0; i < bits.length; i += 8) {\n let value = 0\n for (let j = 0; j < 8; j++) value = (value << 1) | bits[i + j]\n data.push(value)\n }\n\n // Reed-Solomon error correction over GF(256), polynomial 0x11d.\n const exp: number[] = []\n const log: number[] = []\n for (let i = 0, x = 1; i < 255; i++) {\n exp[i] = x\n log[x] = i\n x <<= 1\n if (x & 0x100) x ^= 0x11d\n }\n const multiply = (a: number, b: number) => (a === 0 || b === 0 ? 0 : exp[(log[a] + log[b]) % 255])\n let generator = [1]\n for (let i = 0; i < ecCodewords; i++) {\n const next = new Array(generator.length + 1).fill(0)\n for (let j = 0; j < generator.length; j++) {\n next[j] ^= generator[j]\n next[j + 1] ^= multiply(generator[j], exp[i])\n }\n generator = next\n }\n const remainder = [...data, ...new Array(ecCodewords).fill(0)]\n for (let i = 0; i < data.length; i++) {\n const factor = remainder[i]\n if (factor === 0) continue\n for (let j = 0; j < generator.length; j++) remainder[i + j] ^= multiply(generator[j], factor)\n }\n const codewords = [...data, ...remainder.slice(data.length)]\n\n // Function patterns. `reserved` marks every module data may not use.\n const modules: number[][] = []\n const reserved: boolean[][] = []\n for (let row = 0; row < size; row++) {\n modules.push(new Array(size).fill(0))\n reserved.push(new Array(size).fill(false))\n }\n const setFunction = (row: number, col: number, dark: boolean) => {\n modules[row][col] = dark ? 1 : 0\n reserved[row][col] = true\n }\n for (let i = 0; i < size; i++) {\n setFunction(6, i, i % 2 === 0)\n setFunction(i, 6, i % 2 === 0)\n }\n const finder = (centerRow: number, centerCol: number) => {\n for (let dr = -4; dr <= 4; dr++) {\n for (let dc = -4; dc <= 4; dc++) {\n const row = centerRow + dr\n const col = centerCol + dc\n if (row < 0 || row >= size || col < 0 || col >= size) continue\n const distance = Math.max(Math.abs(dr), Math.abs(dc))\n setFunction(row, col, distance !== 2 && distance !== 4)\n }\n }\n }\n finder(3, 3)\n finder(3, size - 4)\n finder(size - 4, 3)\n if (version >= 2) {\n const center = size - 7\n for (let dr = -2; dr <= 2; dr++) {\n for (let dc = -2; dc <= 2; dc++) {\n setFunction(center + dr, center + dc, Math.max(Math.abs(dr), Math.abs(dc)) !== 1)\n }\n }\n }\n const formatBit = (i: number) => ((formatBits >>> i) & 1) === 1\n for (let i = 0; i <= 5; i++) setFunction(i, 8, formatBit(i))\n setFunction(7, 8, formatBit(6))\n setFunction(8, 8, formatBit(7))\n setFunction(8, 7, formatBit(8))\n for (let i = 9; i < 15; i++) setFunction(8, 14 - i, formatBit(i))\n for (let i = 0; i < 8; i++) setFunction(8, size - 1 - i, formatBit(i))\n for (let i = 8; i < 15; i++) setFunction(size - 15 + i, 8, formatBit(i))\n setFunction(size - 8, 8, true)\n\n // Data in the zigzag order, then mask 0 on every data module.\n let bitIndex = 0\n for (let right = size - 1; right >= 1; right -= 2) {\n if (right === 6) right = 5\n const upward = ((right + 1) & 2) === 0\n for (let step = 0; step < size; step++) {\n const row = upward ? size - 1 - step : step\n for (let j = 0; j < 2; j++) {\n const col = right - j\n if (reserved[row][col]) continue\n if (bitIndex < codewords.length * 8) {\n modules[row][col] = (codewords[bitIndex >>> 3] >>> (7 - (bitIndex & 7))) & 1\n bitIndex++\n }\n if ((row + col) % 2 === 0) modules[row][col] ^= 1\n }\n }\n }\n return modules\n}\n\nexport interface QrOptions {\n cellSize?: number\n fgColor?: string\n bgColor?: string\n finderColor?: string\n logoBase64?: string // data URI for center logo\n logoWidth?: number // logo width in modules (default: 7)\n logoHeight?: number // logo height in modules (default: 5)\n}\n\nexport function generateQrSvg(text: string, opts: QrOptions | number = 4): string {\n // Backward compat: accept bare cellSize number\n const options: QrOptions = typeof opts === 'number' ? { cellSize: opts } : opts\n const cellSize = options.cellSize ?? 4\n const fgColor = options.fgColor ?? '#000'\n const bgColor = options.bgColor ?? '#fff'\n const finderColor = options.finderColor ?? fgColor\n\n const matrix = qrModules(text)\n const size = matrix.length\n\n // Logo exclusion zone (center of QR)\n const logoW = options.logoWidth ?? 7\n const logoH = options.logoHeight ?? 5\n const logoStartC = Math.floor((size - logoW) / 2)\n const logoStartR = Math.floor((size - logoH) / 2)\n const hasLogo = !!options.logoBase64\n\n const svgSize = size * cellSize\n let svg = `<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"${svgSize}\" height=\"${svgSize}\" viewBox=\"0 0 ${svgSize} ${svgSize}\">`\n svg += `<rect width=\"${svgSize}\" height=\"${svgSize}\" fill=\"${bgColor}\" rx=\"4\"/>`\n\n const isFinderModule = (r: number, c: number): boolean =>\n (r < 7 && c < 7) || (r < 7 && c >= size - 7) || (r >= size - 7 && c < 7)\n\n for (let r = 0; r < size; r++) {\n for (let c = 0; c < size; c++) {\n if (\n hasLogo &&\n r >= logoStartR &&\n r < logoStartR + logoH &&\n c >= logoStartC &&\n c < logoStartC + logoW\n ) {\n continue\n }\n if (matrix[r][c] === 1) {\n const color = isFinderModule(r, c) ? finderColor : fgColor\n svg += `<rect x=\"${c * cellSize}\" y=\"${r * cellSize}\" width=\"${cellSize}\" height=\"${cellSize}\" fill=\"${color}\" rx=\"0.5\"/>`\n }\n }\n }\n\n if (hasLogo && options.logoBase64) {\n const lx = logoStartC * cellSize\n const ly = logoStartR * cellSize\n const lw = logoW * cellSize\n const lh = logoH * cellSize\n svg += `<rect x=\"${lx - 1}\" y=\"${ly - 1}\" width=\"${lw + 2}\" height=\"${lh + 2}\" fill=\"${bgColor}\" rx=\"3\"/>`\n svg += `<image x=\"${lx + 2}\" y=\"${ly + 2}\" width=\"${lw - 4}\" height=\"${lh - 4}\" href=\"${options.logoBase64}\" xlink:href=\"${options.logoBase64}\" preserveAspectRatio=\"xMidYMid meet\"/>`\n }\n\n svg += '</svg>'\n return svg\n}\n\nexport interface QrTerminalOptions {\n /** Quiet zone in modules on each side (default 4, the QR standard). */\n quietZone?: number\n /** Draw light modules as blocks instead of dark ones, for a light-on-dark read. */\n invert?: boolean\n}\n\n/**\n * The QR code of `text` as terminal text. Each character covers two module\n * rows with half blocks (▀ ▄ █ and space), so the code keeps its square shape.\n * A block is a dark module; the quiet zone is light.\n */\nexport function qrTerminalText(text: string, options: QrTerminalOptions = {}): string {\n const quiet = options.quietZone ?? 4\n const modules = qrModules(text)\n const size = modules.length + quiet * 2\n const dark = (row: number, col: number): boolean => {\n const r = row - quiet\n const c = col - quiet\n const on = r >= 0 && c >= 0 && r < modules.length && c < modules.length && modules[r][c] === 1\n return options.invert ? !on : on\n }\n const lines: string[] = []\n for (let row = 0; row < size; row += 2) {\n let line = ''\n for (let col = 0; col < size; col++) {\n const top = dark(row, col)\n const bottom = row + 1 < size ? dark(row + 1, col) : options.invert === true\n line += top && bottom ? '█' : top ? '▀' : bottom ? '▄' : ' '\n }\n lines.push(line)\n }\n return lines.join('\\n')\n}\n","import type { WalletData } from './types.js'\nimport { createPublicClient, http, formatEther, formatUnits } from 'viem'\nimport { base } from 'viem/chains'\n\nconst USDC_ADDRESS = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913' as const\nconst DEFAULT_BASE_RPC_URL = 'https://mainnet.base.org'\nconst PUBLIC_BASE_RPC_URLS = [\n DEFAULT_BASE_RPC_URL,\n 'https://base-rpc.publicnode.com',\n 'https://base.drpc.org',\n 'https://1rpc.io/base',\n] as const\nconst USDC_ABI = [\n {\n name: 'balanceOf',\n type: 'function',\n stateMutability: 'view',\n inputs: [{ name: 'account', type: 'address' }],\n outputs: [{ name: '', type: 'uint256' }],\n },\n] as const\n\nfunction walletAddress(wallet: WalletData | string): `0x${string}` {\n return (typeof wallet === 'string' ? wallet : wallet.address) as `0x${string}`\n}\n\nexport async function getBalanceUsdc(wallet: WalletData | string): Promise<string> {\n const envRpcUrl = process.env.BASE_RPC_URL\n const rpcUrls = [\n ...(envRpcUrl ? [envRpcUrl] : []),\n ...PUBLIC_BASE_RPC_URLS.filter((url) => url !== envRpcUrl),\n ]\n\n for (const rpcUrl of rpcUrls) {\n try {\n const client = createPublicClient({ chain: base, transport: http(rpcUrl) })\n const balance = await client.readContract({\n address: USDC_ADDRESS,\n abi: USDC_ABI,\n functionName: 'balanceOf',\n args: [walletAddress(wallet)],\n })\n return formatUnits(balance, 6)\n } catch {\n // Try the next public Base RPC endpoint.\n }\n }\n\n return 'unknown'\n}\n\nexport async function getBalanceEth(wallet: WalletData | string): Promise<string> {\n const envRpcUrl = process.env.BASE_RPC_URL\n const rpcUrls = [\n ...(envRpcUrl ? [envRpcUrl] : []),\n ...PUBLIC_BASE_RPC_URLS.filter((url) => url !== envRpcUrl),\n ]\n\n for (const rpcUrl of rpcUrls) {\n try {\n const client = createPublicClient({ chain: base, transport: http(rpcUrl) })\n const balance = await client.getBalance({ address: walletAddress(wallet) })\n return formatEther(balance)\n } catch {\n // Try the next public Base RPC endpoint.\n }\n }\n\n return 'unknown'\n}\n\nexport async function getBalance(wallet: WalletData): Promise<string> {\n const [balanceUsdc, balanceEth] = await Promise.all([\n getBalanceUsdc(wallet),\n getBalanceEth(wallet),\n ])\n return [\n `Payment wallet: ${wallet.address}`,\n `USDC on Base: ${balanceUsdc}`,\n `Gas on Base: ${balanceEth} ETH`,\n `Payment network: Base`,\n `Base ETH is used only for one-time payment setup gas.`,\n ]\n .filter(Boolean)\n .join('\\n')\n}\n\nexport function getTopupInfo(wallet: WalletData): string {\n return JSON.stringify({\n wallet_address: wallet.address,\n network: 'Base (Chain ID 8453)',\n token: 'USDC',\n contract: USDC_ADDRESS,\n instructions: [`Send USDC on Base network to: ${wallet.address}`],\n })\n}\n","import { createServer, type Server } from 'node:http'\nimport { readFileSync } from 'node:fs'\nimport { createRequire } from 'node:module'\nimport { join, dirname } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { isAddress } from 'viem'\nimport type { WalletData } from './types.js'\nimport { generateQrSvg, qrModules } from './qr.js'\nimport { getBalanceEth, getBalanceUsdc } from './tools.js'\nimport {\n CIA_SYMBOL,\n CIA_TOKEN_ADDRESS,\n ROBINHOOD_CHAIN_ID,\n ROBINHOOD_NETWORK_DISPLAY,\n getCiaBalance,\n getRobinhoodEthBalance,\n} from '../robinhood.js'\nimport {\n SUBSCRIPTION_TOPUP_DESCRIPTION,\n SUBSCRIPTION_TOPUP_TOOL,\n WALLET_TOPUP_DESCRIPTION,\n} from '../../subscription/rules.js'\n\nconst USDC_ADDRESS = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'\nconst BASE_CHAIN_ID = '0x2105' // 8453\n\nconst __dirname = dirname(fileURLToPath(import.meta.url))\n\n// Pre-load assets — try dist/assets first (installed), then src/assets (dev)\nfunction loadAsset(name: string): Buffer {\n const paths = [\n join(__dirname, 'assets', name), // dist/assets/ (global install)\n join(__dirname, '..', 'src', 'assets', name), // src/assets/ (dev)\n ]\n for (const p of paths) {\n try {\n return readFileSync(p)\n } catch {\n /* try next */\n }\n }\n console.error(`[chain-insights] Warning: asset ${name} not found`)\n return Buffer.alloc(0)\n}\n\nconst logoPng = loadAsset('logo.png')\nconst bgPatternPng = loadAsset('bg-pattern.png')\n\n/**\n * The top-up rails. base-usdc funds pay-per-call queries with USDC on Base;\n * robinhood-cia funds the subscription with $CIA and ETH on Robinhood Chain.\n * One local server per rail.\n */\nexport type TopupRail = 'base-usdc' | 'robinhood-cia'\n\nconst servers = new Map<TopupRail, { server: Server; port: number }>()\n\nfunction assertWalletAddress(wallet: WalletData | string): string {\n const walletAddress = typeof wallet === 'string' ? wallet : wallet.address\n if (!isAddress(walletAddress)) {\n throw new Error('Wallet address must be a valid 0x-prefixed 20-byte EVM address')\n }\n return walletAddress\n}\n\nfunction escapeHtml(value: string): string {\n return value\n .replaceAll('&', '&amp;')\n .replaceAll('<', '&lt;')\n .replaceAll('>', '&gt;')\n .replaceAll('\"', '&quot;')\n .replaceAll(\"'\", '&#39;')\n}\n\nfunction jsonForScript(value: string): string {\n return JSON.stringify(value)\n .replaceAll('<', '\\\\u003c')\n .replaceAll('>', '\\\\u003e')\n .replaceAll('&', '\\\\u0026')\n}\n\nexport function getTopupUrl(rail: TopupRail = 'base-usdc'): string | null {\n const running = servers.get(rail)\n return running ? `http://localhost:${running.port}` : null\n}\n\nasync function railBalance(rail: TopupRail, walletAddress: string): Promise<Record<string, string>> {\n if (rail === 'robinhood-cia') {\n const [balanceCia, balanceEth] = await Promise.all([\n getCiaBalance(walletAddress),\n getRobinhoodEthBalance(walletAddress),\n ])\n return { balance_cia: balanceCia, balance_eth: balanceEth }\n }\n const [balanceUsdc, balanceEth] = await Promise.all([\n getBalanceUsdc(walletAddress),\n getBalanceEth(walletAddress),\n ])\n return { balance_usdc: balanceUsdc, balance_eth: balanceEth }\n}\n\nconst UNKNOWN_BALANCE: Record<TopupRail, Record<string, string>> = {\n 'base-usdc': { balance_usdc: 'unknown', balance_eth: 'unknown' },\n 'robinhood-cia': { balance_cia: 'unknown', balance_eth: 'unknown' },\n}\n\nexport async function startTopupServer(\n wallet: WalletData | string,\n rail: TopupRail = 'base-usdc'\n): Promise<string> {\n const walletAddress = assertWalletAddress(wallet)\n\n const running = getTopupUrl(rail)\n if (running) {\n return running\n }\n\n return new Promise((resolve, reject) => {\n const server = createServer((req, res) => {\n if (req.url === '/api/wallet') {\n res.writeHead(200, {\n 'Content-Type': 'application/json',\n 'Access-Control-Allow-Origin': '*',\n })\n res.end(JSON.stringify({ address: walletAddress }))\n return\n }\n\n if (req.url === '/api/balance') {\n railBalance(rail, walletAddress)\n .then((balance) => {\n res.writeHead(200, {\n 'Content-Type': 'application/json',\n 'Access-Control-Allow-Origin': '*',\n })\n res.end(JSON.stringify(balance))\n })\n .catch(() => {\n res.writeHead(200, {\n 'Content-Type': 'application/json',\n 'Access-Control-Allow-Origin': '*',\n })\n res.end(JSON.stringify(UNKNOWN_BALANCE[rail]))\n })\n return\n }\n\n if (req.url === '/assets/logo.png') {\n res.writeHead(200, {\n 'Content-Type': 'image/png',\n 'Cache-Control': 'public, max-age=86400',\n 'Access-Control-Allow-Origin': '*',\n })\n res.end(logoPng)\n return\n }\n\n if (req.url === '/assets/bg-pattern.png') {\n res.writeHead(200, {\n 'Content-Type': 'image/png',\n 'Cache-Control': 'public, max-age=86400',\n 'Access-Control-Allow-Origin': '*',\n })\n res.end(bgPatternPng)\n return\n }\n\n res.writeHead(200, { 'Content-Type': 'text/html' })\n res.end(rail === 'robinhood-cia' ? generateSubscriptionPage(walletAddress) : generatePage(walletAddress))\n })\n\n server.listen(0, '127.0.0.1', () => {\n const addr = server.address()\n if (addr && typeof addr === 'object') {\n servers.set(rail, { server, port: addr.port })\n const url = `http://localhost:${addr.port}`\n console.error(`[chain-insights] Topup server running at ${url}`)\n resolve(url)\n } else {\n reject(new Error('Failed to start topup server'))\n }\n })\n\n server.on('error', reject)\n })\n}\n\nexport function generateArtifactHtml(walletAddressInput: string, topupUrl: string): string {\n const walletAddress = assertWalletAddress(walletAddressInput)\n const safeWalletAddress = escapeHtml(walletAddress)\n const safeTopupUrl = escapeHtml(topupUrl)\n const topupUrlJson = jsonForScript(topupUrl)\n\n const LOGO_B64 =\n 'data:image/png;base64,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'\n\n // Generate QR code SVG server-side — standard B&W, 2x size\n const qrSvg = generateQrSvg(walletAddress, { cellSize: 8 })\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n body {\n font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;\n background: rgba(10, 12, 17, 1) url('${safeTopupUrl}/assets/bg-pattern.png') top left / contain no-repeat;\n color: rgba(255, 255, 255, 0.9);\n display: flex;\n justify-content: center;\n padding: 24px;\n line-height: 1.3;\n }\n .card {\n max-width: 400px;\n width: 100%;\n background: rgba(19, 19, 24, 1);\n border: 1px solid rgba(255, 255, 255, 0.06);\n border-radius: 12px;\n padding: 28px 24px;\n text-align: center;\n }\n .logo { margin-bottom: 8px; }\n .logo img { height: 24px; }\n .subtitle { color: rgba(255, 255, 255, 0.5); font-size: 13px; margin-bottom: 20px; }\n .qr { margin-bottom: 16px; }\n .qr svg { border-radius: 12px; background: #fff; padding: 10px; }\n .address-wrap {\n position: relative;\n margin-bottom: 16px;\n }\n .address {\n width: 100%;\n background: rgba(10, 12, 17, 1);\n border: 1px solid rgba(255, 255, 255, 0.06);\n border-radius: 8px;\n padding: 10px 14px;\n font-family: 'SF Mono', 'Fira Code', monospace;\n font-size: 11px; color: #f2dda6; word-break: break-all;\n cursor: pointer; text-align: center;\n transition: border-color 0.3s linear;\n -webkit-user-select: all; user-select: all;\n outline: none;\n }\n .address:focus { border-color: #ae9d71; }\n .address-hint {\n font-size: 10px; color: rgba(255,255,255,0.3);\n text-align: center; margin-top: 4px;\n }\n .badge {\n display: inline-flex; align-items: center; gap: 6px;\n background: rgba(255, 255, 255, 0.04);\n border: 1px solid rgba(255, 255, 255, 0.06);\n border-radius: 20px; padding: 5px 12px;\n font-size: 11px; color: rgba(255, 255, 255, 0.5); margin-bottom: 20px;\n }\n .badge .dot { width: 7px; height: 7px; border-radius: 50%; background: #f2dda6; }\n .balance-line {\n font-size: 13px;\n color: rgba(255, 255, 255, 0.5);\n margin-bottom: 20px;\n line-height: 1.4;\n }\n .balance-line .amount {\n color: #4feb69;\n font-weight: 600;\n }\n .balance-line .gas {\n color: #f2dda6;\n font-weight: 600;\n }\n @keyframes flash { 0%{opacity:0.4} 100%{opacity:1} }\n .balance-line.flash .amount { animation: flash 1s ease-out; }\n .hint {\n margin-top: 14px; font-size: 12px;\n color: rgba(255, 255, 255, 0.3);\n line-height: 1.5;\n }\n</style>\n</head>\n<body>\n<div class=\"card\">\n <div class=\"logo\"><img src=\"${safeTopupUrl}/assets/logo.png\" alt=\"Chain Insights\"></div>\n <p class=\"subtitle\">Fund your wallet with USDC on Base</p>\n <div class=\"qr\">${qrSvg}</div>\n <div class=\"address-wrap\">\n <div class=\"address\" id=\"addr\" onclick=\"selectAndCopy()\">${safeWalletAddress}</div>\n <div class=\"address-hint\" id=\"addrHint\">Click to copy address</div>\n </div>\n <div class=\"badge\"><span class=\"dot\"></span>Base Network &middot; USDC</div>\n <p class=\"balance-line\" id=\"balLine\">Current balance: <span class=\"amount\" id=\"bal\">--</span> USDC<br>Gas balance: <span class=\"gas\" id=\"gas\">--</span> ETH<br>Base ETH is used for one-time payment setup gas.</p>\n</div>\n<script>\n// MCP Apps protocol handshake for the wallet top-up page.\n(function() {\n var initId = 1;\n\n // Listen for host messages\n window.addEventListener('message', function(event) {\n var data = event.data;\n if (!data || data.jsonrpc !== '2.0') return;\n\n // Initialize response\n if (data.id === initId && data.result) {\n // Send initialized notification\n window.parent.postMessage({\n jsonrpc: '2.0',\n method: 'ui/notifications/initialized',\n params: {}\n }, '*');\n\n // Send initial size\n var rect = document.documentElement.getBoundingClientRect();\n window.parent.postMessage({\n jsonrpc: '2.0',\n method: 'ui/notifications/size-changed',\n params: { width: Math.ceil(rect.width), height: Math.ceil(rect.height) }\n }, '*');\n }\n\n // Respond to pings\n if (data.method === 'ping' && data.id != null) {\n window.parent.postMessage({\n jsonrpc: '2.0',\n id: data.id,\n result: {}\n }, '*');\n }\n });\n\n // Send initialize request (must match App class protocol)\n window.parent.postMessage({\n jsonrpc: '2.0',\n id: initId,\n method: 'ui/initialize',\n params: {\n appInfo: { name: 'Chain Insights Topup', version: '1.0.0' },\n appCapabilities: {},\n protocolVersion: '2026-01-26'\n }\n }, '*');\n})();\n\n// Live balance polling\nvar lastBal = null;\nvar TOPUP_URL = ${topupUrlJson};\nfunction fetchBal() {\n fetch(TOPUP_URL + '/api/balance')\n .then(function(r) { return r.json(); })\n .then(function(d) {\n var el = document.getElementById('bal');\n var gas = document.getElementById('gas');\n var line = document.getElementById('balLine');\n var val = parseFloat(d.balance_usdc || '0').toFixed(2);\n var gasVal = d.balance_eth === 'unknown' ? '--' : parseFloat(d.balance_eth || '0').toFixed(6);\n if (d.balance_usdc === 'unknown') { el.textContent = '--'; gas.textContent = gasVal; return; }\n el.textContent = val;\n gas.textContent = gasVal;\n if (lastBal !== null && val !== lastBal) {\n line.classList.remove('flash');\n void line.offsetWidth;\n line.classList.add('flash');\n }\n lastBal = val;\n })\n .catch(function() {});\n}\nfetchBal();\nsetInterval(fetchBal, 10000);\n\nfunction selectAndCopy() {\n var addr = document.getElementById('addr');\n var hint = document.getElementById('addrHint');\n // Select the text\n var r = document.createRange();\n r.selectNodeContents(addr);\n var s = window.getSelection();\n s.removeAllRanges();\n s.addRange(r);\n // Try every clipboard method available\n var copied = false;\n try { copied = document.execCommand('copy'); } catch(e) {}\n if (!copied && navigator.clipboard && navigator.clipboard.writeText) {\n navigator.clipboard.writeText(addr.textContent).then(function() {\n hint.textContent = 'Copied!'; hint.style.color = '#4feb69';\n setTimeout(function() { hint.textContent = 'Click to copy address'; hint.style.color = ''; }, 2000);\n }).catch(function() {});\n }\n if (copied) {\n hint.textContent = 'Copied!'; hint.style.color = '#4feb69';\n setTimeout(function() { hint.textContent = 'Click to copy address'; hint.style.color = ''; }, 2000);\n } else {\n hint.textContent = 'Selected — press Ctrl+C'; hint.style.color = '#f2dda6';\n setTimeout(function() { hint.textContent = 'Click to copy address'; hint.style.color = ''; }, 3000);\n }\n}\n<\\/script>\n</body>\n</html>`\n}\n\nfunction generatePage(walletAddressInput: string): string {\n const walletAddress = assertWalletAddress(walletAddressInput)\n const safeWalletAddress = escapeHtml(walletAddress)\n const walletAddressJson = jsonForScript(walletAddress)\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Chain Insights — Fund Wallet</title>\n<script src=\"https://cdn.jsdelivr.net/npm/qrcode-generator@1.4.4/qrcode.min.js\"></script>\n<style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n\n body {\n font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;\n background: rgba(10, 12, 17, 1);\n color: rgba(255, 255, 255, 0.9);\n min-height: 100vh;\n display: flex;\n justify-content: center;\n align-items: center;\n line-height: 1.3;\n }\n\n .container {\n max-width: 480px;\n width: 100%;\n padding: 24px;\n }\n\n .logo {\n text-align: center;\n margin-bottom: 32px;\n }\n\n .logo h1 {\n font-size: 24px;\n font-weight: 600;\n color: #f2dda6;\n letter-spacing: -0.5px;\n }\n\n .logo p {\n color: rgba(255, 255, 255, 0.5);\n font-size: 14px;\n margin-top: 4px;\n }\n\n .card {\n background: rgba(19, 19, 24, 1);\n border: 1px solid rgba(255, 255, 255, 0.06);\n border-radius: 12px;\n padding: 32px 24px;\n }\n\n .qr-container {\n display: flex;\n justify-content: center;\n margin-bottom: 24px;\n }\n\n .qr-container canvas, .qr-container img {\n border-radius: 12px;\n background: #fff;\n padding: 12px;\n }\n\n .address-box {\n background: rgba(10, 12, 17, 1);\n border: 1px solid rgba(255, 255, 255, 0.06);\n border-radius: 8px;\n padding: 12px 16px;\n display: flex;\n align-items: center;\n gap: 8px;\n margin-bottom: 24px;\n }\n\n .address-box code {\n font-family: 'SF Mono', 'Fira Code', monospace;\n font-size: 12px;\n color: #f2dda6;\n word-break: break-all;\n flex: 1;\n }\n\n .copy-btn {\n background: none;\n border: none;\n color: rgba(255, 255, 255, 0.4);\n cursor: pointer;\n padding: 4px;\n font-size: 18px;\n transition: color 0.3s linear;\n }\n\n .copy-btn:hover { color: #f2dda6; }\n .copy-btn.copied { color: #4feb69; }\n\n .network-badge {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n background: rgba(255, 255, 255, 0.04);\n border: 1px solid rgba(255, 255, 255, 0.06);\n border-radius: 20px;\n padding: 6px 12px;\n font-size: 12px;\n color: rgba(255, 255, 255, 0.5);\n margin-bottom: 24px;\n }\n\n .network-badge .dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: #f2dda6;\n }\n\n .balance-row {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 12px 0;\n border-top: 1px solid rgba(255, 255, 255, 0.06);\n margin-bottom: 16px;\n }\n\n .balance-label { color: rgba(255, 255, 255, 0.5); font-size: 14px; }\n\n .balance-value {\n font-size: 20px;\n font-weight: 600;\n color: rgba(255, 255, 255, 0.9);\n }\n\n .balance-value .currency {\n font-size: 14px;\n color: rgba(255, 255, 255, 0.5);\n font-weight: 400;\n margin-left: 4px;\n }\n\n .gas-note {\n margin-top: -8px;\n margin-bottom: 16px;\n color: rgba(255,255,255,0.45);\n font-size: 12px;\n line-height: 1.4;\n text-align: left;\n }\n\n .metamask-btn {\n width: 100%;\n padding: 14px;\n border: none;\n border-radius: 10px;\n font-size: 15px;\n font-weight: 600;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 10px;\n transition: all 0.2s;\n background: #f6851b;\n color: #fff;\n }\n\n .metamask-btn:hover { background: #e2761b; transform: translateY(-1px); }\n .metamask-btn:disabled { opacity: 0.5; cursor: not-allowed; transform: none; }\n\n .metamask-btn svg { width: 22px; height: 22px; }\n\n .amount-input {\n width: 100%;\n padding: 12px 16px;\n background: #0a0c11;\n border: 1px solid rgba(255, 255, 255, 0.06);\n border-radius: 8px;\n color: rgba(255, 255, 255, 0.9);\n font-size: 16px;\n margin-bottom: 16px;\n outline: none;\n transition: border-color 0.2s;\n }\n\n .amount-input:focus { border-color: #f2dda6; }\n\n .amount-label {\n font-size: 13px;\n color: rgba(255, 255, 255, 0.5);\n margin-bottom: 6px;\n display: block;\n }\n\n .status {\n text-align: center;\n padding: 12px;\n border-radius: 8px;\n font-size: 14px;\n margin-top: 16px;\n display: none;\n }\n\n .status.success { display: block; background: #0a2e1a; color: #4feb69; border: 1px solid #1a4a2e; }\n .status.error { display: block; background: #2e0a0a; color: #eb4f4f; border: 1px solid #4a1a1a; }\n .status.pending { display: block; background: #1a1a0a; color: #f2dda6; border: 1px solid #4a4a1a; }\n\n .info {\n text-align: center;\n margin-top: 24px;\n font-size: 12px;\n color: rgba(255, 255, 255, 0.3);\n line-height: 1.6;\n }\n</style>\n</head>\n<body>\n<div class=\"container\">\n <div class=\"logo\">\n <h1>Chain Insights</h1>\n <p>Fund your wallet to use blockchain intelligence tools</p>\n </div>\n\n <div class=\"card\">\n <div class=\"qr-container\" id=\"qr\"></div>\n\n <div class=\"address-box\">\n <code id=\"address\">${safeWalletAddress}</code>\n <button class=\"copy-btn\" onclick=\"copyAddress()\" id=\"copyBtn\" title=\"Copy address\">&#x2398;</button>\n </div>\n\n <div class=\"network-badge\">\n <span class=\"dot\"></span>\n Base Network &middot; USDC\n </div>\n\n <div class=\"balance-row\">\n <span class=\"balance-label\">Current balance</span>\n <span class=\"balance-value\" id=\"balance\">—<span class=\"currency\">USDC</span></span>\n </div>\n <div class=\"balance-row\">\n <span class=\"balance-label\">Gas balance</span>\n <span class=\"balance-value\" id=\"gasBalance\">—<span class=\"currency\">ETH</span></span>\n </div>\n <p class=\"gas-note\">Base ETH is used for one-time payment setup gas.</p>\n\n <div class=\"status\" id=\"status\"></div>\n </div>\n\n <p class=\"info\">Send Base USDC to the wallet address above.</p>\n</div>\n\n<script>\nconst WALLET = ${walletAddressJson};\nconst USDC = '${USDC_ADDRESS}';\nconst CHAIN_ID = '${BASE_CHAIN_ID}';\n\n// QR code\n(function() {\n var qr = qrcode(0, 'M');\n qr.addData(WALLET);\n qr.make();\n document.getElementById('qr').innerHTML = qr.createSvgTag(5, 0);\n var svg = document.querySelector('#qr svg');\n if (svg) { svg.style.borderRadius = '12px'; svg.style.background = '#fff'; svg.style.padding = '12px'; }\n})();\n\n// Copy address — fallback for sandboxed iframes where navigator.clipboard is blocked\nfunction copyAddress() {\n var ok = false;\n // Try modern clipboard API first\n if (navigator.clipboard && navigator.clipboard.writeText) {\n navigator.clipboard.writeText(WALLET).then(function() { ok = true; }).catch(function() {});\n }\n // Fallback: hidden textarea + execCommand\n if (!ok) {\n var ta = document.createElement('textarea');\n ta.value = WALLET;\n ta.style.position = 'fixed';\n ta.style.left = '-9999px';\n document.body.appendChild(ta);\n ta.select();\n try { document.execCommand('copy'); } catch(e) {}\n document.body.removeChild(ta);\n }\n var btn = document.getElementById('copyBtn');\n btn.classList.add('copied');\n btn.innerHTML = '&#x2713;';\n setTimeout(function() { btn.classList.remove('copied'); btn.innerHTML = '&#x2398;'; }, 2000);\n}\n\n// Fetch balance\nasync function fetchBalance() {\n try {\n var resp = await fetch('/api/balance');\n var json = await resp.json();\n if (json.balance_usdc === 'unknown') throw new Error('balance unavailable');\n var balance = Number(json.balance_usdc || 0).toFixed(2);\n document.getElementById('balance').innerHTML = balance + '<span class=\"currency\">USDC</span>';\n var gasBalance = json.balance_eth === 'unknown' ? '—' : Number(json.balance_eth || 0).toFixed(6);\n document.getElementById('gasBalance').innerHTML = gasBalance + '<span class=\"currency\">ETH</span>';\n } catch(e) {\n document.getElementById('balance').innerHTML = '—<span class=\"currency\">USDC</span>';\n document.getElementById('gasBalance').innerHTML = '—<span class=\"currency\">ETH</span>';\n }\n}\nfetchBalance();\nsetInterval(fetchBalance, 15000);\n\n</script>\n</body>\n</html>`\n}\n\n/** The text of the subscription top-up page and of `cia subscription topup`. */\nexport const SUBSCRIPTION_TOPUP_TEXT =\n 'Send $CIA and a little ETH on Robinhood Chain to your own wallet. Then run `cia subscription buy --days N`.'\n\n/**\n * The robinhood-cia page: the wallet address, its QR code, Robinhood Chain,\n * $CIA and the balances. It names only the user's own wallet, never where the\n * subscription payment goes: that address is read by the buy alone.\n */\nexport function generateSubscriptionPage(walletAddressInput: string): string {\n const walletAddress = assertWalletAddress(walletAddressInput)\n const safeWalletAddress = escapeHtml(walletAddress)\n const qrSvg = generateQrSvg(walletAddress, { cellSize: 6 })\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Chain Insights — Fund the subscription wallet</title>\n<style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n body { font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif; background: #0a0c11; color: rgba(255,255,255,0.9); display: flex; justify-content: center; padding: 24px; line-height: 1.4; }\n .card { max-width: 420px; width: 100%; background: #131318; border: 1px solid rgba(255,255,255,0.06); border-radius: 12px; padding: 28px 24px; text-align: center; }\n h1 { font-size: 18px; margin-bottom: 8px; }\n .lead { color: rgba(255,255,255,0.7); font-size: 14px; margin-bottom: 20px; }\n .qr svg { border-radius: 12px; background: #fff; padding: 10px; }\n .address { margin: 16px 0; font-family: 'SF Mono', 'Fira Code', monospace; font-size: 12px; color: #f2dda6; word-break: break-all; user-select: all; }\n dl { display: grid; grid-template-columns: auto 1fr; gap: 6px 12px; text-align: left; font-size: 13px; margin-bottom: 16px; }\n dt { color: rgba(255,255,255,0.5); }\n dd { word-break: break-all; }\n code { font-family: 'SF Mono', 'Fira Code', monospace; }\n .note { color: rgba(255,255,255,0.5); font-size: 12px; }\n</style>\n</head>\n<body>\n<div class=\"card\">\n <h1>Fund your Chain Insights subscription wallet</h1>\n <p class=\"lead\">Send $CIA and a little ETH on Robinhood Chain to your own wallet. Then run <code>cia subscription buy --days N</code>.</p>\n <div class=\"qr\">${qrSvg}</div>\n <p class=\"address\">${safeWalletAddress}</p>\n <dl>\n <dt>Network</dt><dd>Robinhood Chain (chain ID 4663)</dd>\n <dt>Token</dt><dd>$CIA <code>${CIA_TOKEN_ADDRESS}</code></dd>\n <dt>$CIA balance</dt><dd id=\"cia\">—</dd>\n <dt>ETH balance</dt><dd id=\"eth\">—</dd>\n </dl>\n <p class=\"note\">ETH pays the gas of the two buy transactions. This page shows your own address only. It signs nothing and sends nothing.</p>\n</div>\n<script>\nasync function fetchBalance() {\n try {\n var json = await (await fetch('/api/balance')).json();\n document.getElementById('cia').textContent = json.balance_cia === 'unknown' ? '—' : json.balance_cia + ' $CIA';\n document.getElementById('eth').textContent = json.balance_eth === 'unknown' ? '—' : json.balance_eth + ' ETH';\n } catch (e) {}\n}\nfetchBalance();\nsetInterval(fetchBalance, 15000);\n</script>\n</body>\n</html>`\n}\n\n// ---------------------------------------------------------------------------\n// The local top-up view: an MCP App drawn by Claude Desktop next to the\n// wallet_topup answer of the local proxy. It shows the wallet address, its QR\n// code, Base Mainnet and USDC. It fetches nothing, signs nothing and sends\n// nothing: the page holds no wallet code, only an address to read.\n// The hosted connector never serves it (ruling 2026-10-05).\n// ---------------------------------------------------------------------------\n\nexport const TOPUP_TOOL_NAME = 'wallet_topup'\nexport const TOPUP_VIEW_URI = 'ui://chain-insights/topup'\nexport const TOPUP_NETWORK_DISPLAY = 'Base Mainnet'\nconst TOPUP_CHAIN_ID = 8453\nconst EXT_APPS_GLOBAL = '__chainInsightsExtApps'\nconst NO_WALLET_TEXT =\n 'No local payment wallet exists yet. Run `cia wallet create` to make one, then ask to top up again.'\n\nexport interface TopupViewFacts {\n wallet_address: string\n network: 'Base'\n network_display: typeof TOPUP_NETWORK_DISPLAY\n chain_id: typeof TOPUP_CHAIN_ID\n token: 'USDC'\n token_contract: typeof USDC_ADDRESS\n}\n\nexport interface TopupToolResult {\n [key: string]: unknown\n content: Array<{ type: 'text'; text: string }>\n structuredContent: {\n schema: 'chain-insights.result.v1'\n tool: typeof TOPUP_TOOL_NAME\n hint: string | null\n facts: { wallet_configured: boolean; topup: TopupViewFacts | null }\n }\n isError: false\n}\n\n/**\n * The wallet_topup answer. The text is complete on its own, for hosts that\n * draw no view. `walletAddress` null means no local wallet exists.\n */\nexport function topupToolResult(walletAddress: string | null): TopupToolResult {\n if (walletAddress === null) {\n return {\n content: [{ type: 'text', text: NO_WALLET_TEXT }],\n structuredContent: {\n schema: 'chain-insights.result.v1',\n tool: TOPUP_TOOL_NAME,\n hint: 'Run `cia wallet create`.',\n facts: { wallet_configured: false, topup: null },\n },\n isError: false,\n }\n }\n const address = assertWalletAddress(walletAddress)\n const topup: TopupViewFacts = {\n wallet_address: address,\n network: 'Base',\n network_display: TOPUP_NETWORK_DISPLAY,\n chain_id: TOPUP_CHAIN_ID,\n token: 'USDC',\n token_contract: USDC_ADDRESS,\n }\n return {\n content: [\n {\n type: 'text',\n text: [\n `Payment wallet: ${address}`,\n `Network: ${TOPUP_NETWORK_DISPLAY} (chain ID ${TOPUP_CHAIN_ID})`,\n `Token: USDC (contract ${USDC_ADDRESS})`,\n `To top up, send USDC on ${TOPUP_NETWORK_DISPLAY} to the wallet address. Chain Insights makes no payment from here.`,\n ].join('\\n'),\n },\n ],\n structuredContent: {\n schema: 'chain-insights.result.v1',\n tool: TOPUP_TOOL_NAME,\n hint: null,\n facts: { wallet_configured: true, topup },\n },\n isError: false,\n }\n}\n\nexport interface SubscriptionTopupViewFacts {\n wallet_address: string\n network: typeof ROBINHOOD_NETWORK_DISPLAY\n network_display: typeof ROBINHOOD_NETWORK_DISPLAY\n chain_id: typeof ROBINHOOD_CHAIN_ID\n token: typeof CIA_SYMBOL\n token_contract: typeof CIA_TOKEN_ADDRESS\n gas_token: 'ETH'\n cia_balance: string\n eth_balance: string\n}\n\nexport interface SubscriptionTopupToolResult {\n [key: string]: unknown\n content: Array<{ type: 'text'; text: string }>\n structuredContent: {\n schema: 'chain-insights.result.v1'\n tool: typeof SUBSCRIPTION_TOPUP_TOOL\n hint: string | null\n facts: { wallet_configured: boolean; topup: SubscriptionTopupViewFacts | null }\n }\n isError: false\n}\n\n/** The rule the subscription_topup answer ends with. */\nexport const SUBSCRIPTION_TOPUP_RULE =\n 'Fund this wallet. The user buys days in a terminal with `cia subscription buy --days N`. An agent cannot buy. Never send $CIA by hand to any other address: it is not credited.'\n\n/**\n * The subscription_topup answer: the local wallet on Robinhood Chain, $CIA,\n * the balances ('unknown' when no RPC answers). It names the user's own\n * wallet only. `walletAddress` null means no local wallet exists.\n */\nexport function subscriptionTopupToolResult(\n walletAddress: string | null,\n balances: { cia: string; eth: string } = { cia: 'unknown', eth: 'unknown' }\n): SubscriptionTopupToolResult {\n if (walletAddress === null) {\n return {\n content: [{ type: 'text', text: 'No local wallet exists yet. Run `cia wallet create` to make one, then ask to top up again.' }],\n structuredContent: {\n schema: 'chain-insights.result.v1',\n tool: SUBSCRIPTION_TOPUP_TOOL,\n hint: 'Run `cia wallet create`.',\n facts: { wallet_configured: false, topup: null },\n },\n isError: false,\n }\n }\n const address = assertWalletAddress(walletAddress)\n const topup: SubscriptionTopupViewFacts = {\n wallet_address: address,\n network: ROBINHOOD_NETWORK_DISPLAY,\n network_display: ROBINHOOD_NETWORK_DISPLAY,\n chain_id: ROBINHOOD_CHAIN_ID,\n token: CIA_SYMBOL,\n token_contract: CIA_TOKEN_ADDRESS,\n gas_token: 'ETH',\n cia_balance: balances.cia,\n eth_balance: balances.eth,\n }\n return {\n content: [\n {\n type: 'text',\n text: [\n `Subscription wallet: ${address}`,\n `Network: ${ROBINHOOD_NETWORK_DISPLAY} (chain ID ${ROBINHOOD_CHAIN_ID})`,\n `Token: ${CIA_SYMBOL} (contract ${CIA_TOKEN_ADDRESS}), with ETH for gas`,\n `Balance: ${balances.cia} ${CIA_SYMBOL}, ${balances.eth} ETH`,\n SUBSCRIPTION_TOPUP_RULE,\n ].join('\\n'),\n },\n ],\n structuredContent: {\n schema: 'chain-insights.result.v1',\n tool: SUBSCRIPTION_TOPUP_TOOL,\n hint: null,\n facts: { wallet_configured: true, topup },\n },\n isError: false,\n }\n}\n\nlet extAppsScript: string | null = null\n\n/**\n * The browser build of the ext-apps App client (the same 1.x pin as\n * the Chain Insights UI package), turned from an ES module into a script that publishes\n * its exports on globalThis, so the view stays one self-contained file.\n */\nexport function extAppsBrowserScript(): string {\n if (extAppsScript !== null) return extAppsScript\n const require = createRequire(import.meta.url)\n const source = readFileSync(\n require.resolve('@modelcontextprotocol/ext-apps/app-with-deps'),\n 'utf8'\n )\n const match = /export\\s*\\{([^}]*)\\}\\s*;?\\s*(?:\\/\\/# sourceMappingURL=\\S*\\s*)?$/.exec(source)\n if (!match) throw new Error('The ext-apps app bundle does not end with one export list')\n const fields = match[1]\n .split(',')\n .map((part) => part.trim())\n .filter(Boolean)\n .map((part) => {\n const [local, exported] = part.split(/\\s+as\\s+/)\n return `${JSON.stringify(exported ?? local)}:${local}`\n })\n extAppsScript = `${source.slice(0, match.index)}\\nglobalThis.${EXT_APPS_GLOBAL}={${fields.join(',')}};\\n`\n extAppsScript = extAppsScript.replaceAll('</script', '<\\\\/script')\n return extAppsScript\n}\n\nconst TOPUP_VIEW_STYLE = `\n:root {\n color-scheme: light;\n --ci-text: var(--color-text-primary, #1d1b16);\n --ci-muted: var(--color-text-secondary, #5c5647);\n --ci-surface: var(--color-background-secondary, #f7f4ec);\n --ci-border: var(--color-border-primary, #d9d2c0);\n --ci-accent: #8a6a12;\n --ci-on-accent: #ffffff;\n --ci-qr-dark: #000000;\n --ci-qr-light: #ffffff;\n --ci-font: var(--font-sans, ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif);\n --ci-mono: var(--font-mono, ui-monospace, 'SF Mono', Menlo, Consolas, monospace);\n}\nhtml[data-theme='dark'] {\n color-scheme: dark;\n --ci-text: var(--color-text-primary, #f2eee4);\n --ci-muted: var(--color-text-secondary, #b8b09c);\n --ci-surface: var(--color-background-secondary, #1f1d19);\n --ci-border: var(--color-border-primary, #3d392f);\n --ci-accent: #e3c46a;\n --ci-on-accent: #1d1b16;\n}\n* { box-sizing: border-box; margin: 0; padding: 0; }\nhtml, body { background: transparent; }\nbody { font-family: var(--ci-font); color: var(--ci-text); font-size: 14px; line-height: 1.45; padding: 4px; }\n.card { max-width: 420px; margin: 0 auto; padding: 20px; border: 1px solid var(--ci-border); border-radius: 12px; background: var(--ci-surface); }\nh1 { font-size: 16px; font-weight: 600; margin-bottom: 4px; }\n.lead, .note, .muted { color: var(--ci-muted); }\n.lead { margin-bottom: 16px; }\n.qr-tile { display: flex; justify-content: center; margin-bottom: 16px; }\n.qr { width: 200px; height: 200px; border-radius: 8px; }\n.qr-dark { fill: var(--ci-qr-dark); }\n.qr-light { fill: var(--ci-qr-light); }\ndl { display: grid; grid-template-columns: max-content 1fr; gap: 6px 12px; margin-bottom: 16px; }\ndt { color: var(--ci-muted); }\ndd { overflow-wrap: anywhere; }\ncode { font-family: var(--ci-mono); font-size: 12px; }\n.address { display: block; margin-bottom: 6px; }\nbutton { font: inherit; font-size: 13px; padding: 4px 12px; border-radius: 6px; border: 0; background: var(--ci-accent); color: var(--ci-on-accent); cursor: pointer; }\nbutton:focus-visible { outline: 2px solid var(--ci-accent); outline-offset: 2px; }\n.copy-state { margin-left: 8px; font-size: 12px; color: var(--ci-muted); }\n.skeleton { height: 14px; border-radius: 4px; background: var(--ci-border); margin-bottom: 10px; }\n.skeleton.short { width: 60%; }\n`\n\nconst TOPUP_VIEW_SCRIPT = `\n(function () {\n var extApps = globalThis.${EXT_APPS_GLOBAL};\n var qrModules = __QR_MODULES__;\n var root = document.getElementById('topup');\n var ADDRESS = /^0x[0-9a-fA-F]{40}$/;\n\n function el(tag, attrs, children) {\n var node = document.createElement(tag);\n Object.keys(attrs || {}).forEach(function (key) {\n if (key === 'text') node.textContent = attrs[key];\n else node.setAttribute(key, attrs[key]);\n });\n (children || []).forEach(function (child) {\n node.append(typeof child === 'string' ? document.createTextNode(child) : child);\n });\n return node;\n }\n\n function qrSvg(address) {\n var modules = qrModules(address);\n var quiet = 4;\n var total = modules.length + quiet * 2;\n var ns = 'http://www.w3.org/2000/svg';\n var svg = document.createElementNS(ns, 'svg');\n svg.setAttribute('class', 'qr');\n svg.setAttribute('viewBox', '0 0 ' + total + ' ' + total);\n svg.setAttribute('shape-rendering', 'crispEdges');\n svg.setAttribute('role', 'img');\n svg.setAttribute('aria-label', 'QR code of the wallet address ' + address);\n var light = document.createElementNS(ns, 'rect');\n light.setAttribute('class', 'qr-light');\n light.setAttribute('width', String(total));\n light.setAttribute('height', String(total));\n svg.appendChild(light);\n var d = '';\n for (var row = 0; row < modules.length; row++) {\n for (var col = 0; col < modules.length; col++) {\n if (modules[row][col] === 1) d += 'M' + (col + quiet) + ' ' + (row + quiet) + 'h1v1h-1z';\n }\n }\n var dark = document.createElementNS(ns, 'path');\n dark.setAttribute('class', 'qr-dark');\n dark.setAttribute('d', d);\n svg.appendChild(dark);\n return svg;\n }\n\n function show(children) {\n root.replaceChildren(el('div', { class: 'card' }, children));\n }\n\n function resultText(result) {\n return ((result && result.content) || [])\n .filter(function (item) { return item && item.type === 'text' && typeof item.text === 'string'; })\n .map(function (item) { return item.text; })\n .join('\\\\n');\n }\n\n function copyButton(address, target) {\n var state = el('span', { class: 'copy-state', 'aria-live': 'polite' });\n var button = el('button', { type: 'button', text: 'Copy address' });\n button.addEventListener('click', function () {\n function selectIt(message) {\n var range = document.createRange();\n range.selectNodeContents(target);\n var selection = window.getSelection();\n selection.removeAllRanges();\n selection.addRange(range);\n state.textContent = message;\n }\n if (navigator.clipboard && navigator.clipboard.writeText) {\n navigator.clipboard.writeText(address).then(\n function () { state.textContent = 'Copied'; },\n function () { selectIt('Selected. Press Ctrl+C to copy.'); }\n );\n } else {\n selectIt('Selected. Press Ctrl+C to copy.');\n }\n });\n return el('div', {}, [button, state]);\n }\n\n function render(result) {\n var structured = result && result.structuredContent;\n var facts = structured && (structured.tool === '${TOPUP_TOOL_NAME}' || structured.tool === '${SUBSCRIPTION_TOPUP_TOOL}') ? structured.facts : null;\n if (!result || result.isError || !facts) {\n show([el('h1', { text: 'Wallet top-up' }), el('p', { class: 'muted', text: resultText(result) || 'The wallet details did not arrive.' })]);\n return;\n }\n if (!facts.topup) {\n show([\n el('h1', { text: 'No payment wallet yet' }),\n el('p', { class: 'lead' }, ['Create one with ', el('code', { text: 'cia wallet create' }), ', then ask to top up again.'])\n ]);\n return;\n }\n var topup = facts.topup;\n if (!ADDRESS.test(topup.wallet_address)) {\n show([el('h1', { text: 'Wallet top-up' }), el('p', { class: 'muted', text: 'The wallet address is not a valid 0x address.' })]);\n return;\n }\n var address = el('code', { class: 'address', text: topup.wallet_address });\n show([\n el('h1', { text: 'Top up your Chain Insights wallet' }),\n el('p', { class: 'lead', text: 'Send ' + topup.token + ' on ' + topup.network_display + ' to this address.' }),\n el('div', { class: 'qr-tile' }, [qrSvg(topup.wallet_address)]),\n el('dl', {}, [\n el('dt', { text: 'Network' }), el('dd', { text: topup.network_display + ' (chain ID ' + topup.chain_id + ')' }),\n el('dt', { text: 'Token' }), el('dd', { text: topup.token }),\n el('dt', { text: 'Token contract' }), el('dd', {}, [el('code', { text: topup.token_contract })]),\n el('dt', { text: 'Wallet' }), el('dd', {}, [address, copyButton(topup.wallet_address, address)])\n ]),\n el('p', { class: 'note', text: 'This view shows an address only. It signs nothing and sends nothing.' })\n ]);\n }\n\n function applyContext(context) {\n if (!context) return;\n if (context.theme === 'light' || context.theme === 'dark') extApps.applyDocumentTheme(context.theme);\n var styles = context.styles;\n if (styles && styles.variables) extApps.applyHostStyleVariables(styles.variables);\n if (styles && styles.css && styles.css.fonts) extApps.applyHostFonts(styles.css.fonts);\n }\n\n // Until the host names its theme, follow the system; the host's theme wins.\n if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {\n document.documentElement.setAttribute('data-theme', 'dark');\n }\n\n var app = new extApps.App({ name: 'chain-insights-topup', version: '1.0.0' }, {});\n app.ontoolresult = function (result) { render(result); };\n app.onhostcontextchanged = function (change) { applyContext(change); };\n app.onteardown = function () { return {}; };\n app.connect(new extApps.PostMessageTransport(window.parent, window.parent)).then(\n function () { applyContext(app.getHostContext()); },\n function () {\n show([el('h1', { text: 'Wallet top-up' }), el('p', { class: 'muted', text: 'This view opens inside Claude, next to the ${TOPUP_TOOL_NAME} answer.' })]);\n }\n );\n})();\n`\n\n/**\n * The QR encoder as browser source: an expression that evaluates to\n * `qrModules`. A transform that keeps function names (esbuild keepNames, as\n * tsx runs it) wraps inner functions in `__name(...)`; the wrapper gives that\n * helper a do-nothing body so the source runs anywhere.\n */\nexport function qrModulesSource(): string {\n return `(function () { var __name = function (target) { return target; }; return (${qrModules.toString()}); })()`\n}\n\n/** The one self-contained HTML file of ui://chain-insights/topup. */\nexport function topupViewHtml(): string {\n const script = TOPUP_VIEW_SCRIPT.replace('__QR_MODULES__', () => qrModulesSource())\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Wallet top-up</title>\n<style>${TOPUP_VIEW_STYLE}</style>\n</head>\n<body>\n<main id=\"topup\" aria-live=\"polite\"><div class=\"card\"><div class=\"skeleton\"></div><div class=\"skeleton short\"></div><p class=\"muted\">Waiting for the wallet details.</p></div></main>\n<script type=\"module\">${extAppsBrowserScript()}</script>\n<script type=\"module\">${script.replaceAll('</script', '<\\\\/script')}</script>\n</body>\n</html>`\n}\n\n/** The parts of McpServer the top-up view registers on. */\nexport type TopupViewServer = Pick<\n import('@modelcontextprotocol/sdk/server/mcp.js').McpServer,\n 'registerTool' | 'registerResource'\n>\n\nexport interface TopupViewDependencies {\n /** The local wallet address, or null when no wallet exists. Throws when it cannot be read. */\n walletAddress: () => Promise<string | null>\n /** The $CIA and ETH balances on Robinhood Chain, 'unknown' when no RPC answers. */\n robinhoodBalances?: (address: string) => Promise<{ cia: string; eth: string }>\n}\n\nasync function readRobinhoodBalances(address: string): Promise<{ cia: string; eth: string }> {\n const [cia, eth] = await Promise.all([getCiaBalance(address), getRobinhoodEthBalance(address)])\n return { cia, eth }\n}\n\n/**\n * Register the wallet_topup tool and its view on the local proxy. The tool\n * reads the local wallet address and nothing else.\n */\nexport async function registerTopupView(\n server: TopupViewServer,\n deps: TopupViewDependencies\n): Promise<void> {\n const { registerAppResource, registerAppTool, RESOURCE_MIME_TYPE } =\n await import('@modelcontextprotocol/ext-apps/server')\n const uiMeta = { prefersBorder: false, csp: { resourceDomains: ['https://assets.claude.ai'] } }\n\n registerAppTool(\n server,\n TOPUP_TOOL_NAME,\n {\n title: 'Top Up Wallet',\n description: WALLET_TOPUP_DESCRIPTION,\n annotations: {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n _meta: { ui: { resourceUri: TOPUP_VIEW_URI } },\n },\n async () => {\n try {\n return topupToolResult(await deps.walletAddress())\n } catch (err) {\n return {\n content: [{ type: 'text' as const, text: `Top-up failed: ${(err as Error).message}` }],\n isError: true,\n }\n }\n }\n )\n\n registerAppTool(\n server,\n SUBSCRIPTION_TOPUP_TOOL,\n {\n title: 'Top Up Subscription Wallet',\n description: SUBSCRIPTION_TOPUP_DESCRIPTION,\n annotations: {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n // It reads the balances on Robinhood Chain.\n openWorldHint: true,\n },\n _meta: { ui: { resourceUri: TOPUP_VIEW_URI } },\n },\n async () => {\n try {\n const address = await deps.walletAddress()\n if (address === null) return subscriptionTopupToolResult(null)\n const balances = await (deps.robinhoodBalances ?? readRobinhoodBalances)(address)\n return subscriptionTopupToolResult(address, balances)\n } catch (err) {\n return {\n content: [{ type: 'text' as const, text: `Top-up failed: ${(err as Error).message}` }],\n isError: true,\n }\n }\n }\n )\n\n registerAppResource(\n server,\n 'Chain Insights wallet top-up',\n TOPUP_VIEW_URI,\n {\n description:\n 'The local wallet address and its QR code: on Base Mainnet with USDC, or on Robinhood Chain with $CIA.',\n _meta: { ui: uiMeta },\n },\n async () => ({\n contents: [\n {\n uri: TOPUP_VIEW_URI,\n mimeType: RESOURCE_MIME_TYPE,\n text: topupViewHtml(),\n _meta: { ui: uiMeta },\n },\n ],\n })\n )\n}\n"],"mappings":"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"}