<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Haruki Kondo</title>
    <description>The latest articles on DEV Community by Haruki Kondo (@mashharuki).</description>
    <link>https://dev.to/mashharuki</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F986561%2Fded098d5-9fd3-49b5-8045-594b7174fec3.JPG</url>
      <title>DEV Community: Haruki Kondo</title>
      <link>https://dev.to/mashharuki</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mashharuki"/>
    <language>en</language>
    <item>
      <title>Prediction Markets Show Your Bet Instantly — So I Hid Mine With Zero-Knowledge Proofs</title>
      <dc:creator>Haruki Kondo</dc:creator>
      <pubDate>Fri, 24 Jul 2026 12:31:51 +0000</pubDate>
      <link>https://dev.to/midnight-aliit/prediction-markets-show-your-bet-instantly-so-i-hid-mine-with-zero-knowledge-proofs-47h4</link>
      <guid>https://dev.to/midnight-aliit/prediction-markets-show-your-bet-instantly-so-i-hid-mine-with-zero-knowledge-proofs-47h4</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Polymarket&lt;/strong&gt;, and on-chain prediction markets like it, kept bothering me for one reason.&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://polymarket.com/ja" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpolymarket.com%2Fimages%2Fhomepage-twitter-card.png" height="420" class="m-0" width="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://polymarket.com/ja" rel="noopener noreferrer" class="c-link"&gt;
            Polymarket |世界最大の予測市場™
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Polymarketは世界最大の予測市場であり、さまざまなトピックにわたって将来のイベントを取引することで、最新情報を入手し、知識から利益を得ることができます。
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpolymarket.com%2Ficons%2Ffavicon-32x32.png" width="32" height="32"&gt;
          polymarket.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Who bet on what is visible in near real time.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The moment a whale places a big bet on one outcome, everyone watching piles in behind them, and the odds move accordingly. That's not manipulation — it's just what happens with a public ledger. But it doesn't satisfy the simple wish to not reveal your prediction before everyone else does.&lt;/p&gt;

&lt;p&gt;So: could you build a prediction market that keeps your pick hidden until voting closes?&lt;/p&gt;

&lt;p&gt;To find out, I built &lt;code&gt;Hidden League Forecast&lt;/code&gt; on &lt;strong&gt;Midnight&lt;/strong&gt;, a privacy-focused blockchain.&lt;/p&gt;

&lt;p&gt;It's an MVP where you just guess the winner of a fictional soccer league (the World Cup just ended, so soccer was on my mind).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;What's a prediction market?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A mechanism that expresses predictions about future events as prices. Think "which team will win the World Cup match," for example.&lt;/p&gt;

&lt;p&gt;If you want to learn more about prediction markets, this resource (Japanese) is a great start:&lt;br&gt;
&lt;a href="https://zenn.dev/barabara/books/prediction-markets-structure" rel="noopener noreferrer"&gt;https://zenn.dev/barabara/books/prediction-markets-structure&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The backend is written in &lt;strong&gt;Compact&lt;/strong&gt;, Midnight's smart contract language.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;&lt;br&gt;
It combines the commit-reveal pattern with zero-knowledge proofs so that "the content of your prediction stays hidden, while only the aggregate stake becomes public."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In this article, I'll walk through the contract code, showing what stays hidden and what becomes public at each step.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;&lt;br&gt;
This app runs on testnet.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Demo Video
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/G4T-L-rVgzU"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsrpufb3bwtpapfrakbon.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsrpufb3bwtpapfrakbon.jpg" alt="Top screen: connect Lace Wallet and choose to deploy or join a market" width="800" height="850"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;After connecting Lace Wallet, you see your Shielded Address and balance. From here you can deploy a new market or enter an existing contract address to join one.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The Overall Flow
&lt;/h2&gt;

&lt;p&gt;What's actually happening is simple.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OPEN → REVEAL → AWAITING RESULT → RESOLVED → CLAIM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;Connect Lace Wallet, then deploy a market or join an existing one&lt;/li&gt;
&lt;li&gt;Pick one of 4 teams (Amber Foxes / Cedar Owls / Harbor Whales / Meadow Bears) and stake 10–500 points&lt;/li&gt;
&lt;li&gt;Once the admin closes predictions, participants "reveal" their picks&lt;/li&gt;
&lt;li&gt;The admin records the result&lt;/li&gt;
&lt;li&gt;Participants who picked the winning team claim a pari-mutuel payout (the losing side's stakes get split among the winners)
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;reward = floor(total_pool × your_stake / winning_team_pool)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Key point&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;At step 2, which team you bet on is never recorded anywhere on-chain. Only the stake amount and a commitment (a hash used to verify the pick later) are recorded.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Nobody — not even the admin — can see it until the reveal in step 3.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Smart Contract, Written in Compact
&lt;/h2&gt;

&lt;p&gt;Midnight's Compact lets you declare variables (witnesses) that &lt;strong&gt;"are used inside the proof but never written to the ledger."&lt;/strong&gt; Conversely, any value you do write to the ledger must go through &lt;code&gt;disclose()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That guards against the "oops, I accidentally logged a secret value" class of mistake at the type level.&lt;/p&gt;

&lt;p&gt;The secret key, the chosen team, and the salt are all defined as witnesses.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;witness local_secret_key(): Bytes&amp;lt;32&amp;gt;;
witness get_selected_team(): Team;
witness get_prediction_salt(): Bytes&amp;lt;32&amp;gt;;
witness store_prediction(team: Team, stake: Uint&amp;lt;64&amp;gt;, salt: Bytes&amp;lt;32&amp;gt;): [];
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Here's the ledger side.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export ledger phase:                 MarketPhase;
export ledger admin_key:             Bytes&amp;lt;32&amp;gt;;
export ledger commitments:           Map&amp;lt;Bytes&amp;lt;32&amp;gt;, Bytes&amp;lt;32&amp;gt;&amp;gt;;
export ledger stakes:                Map&amp;lt;Bytes&amp;lt;32&amp;gt;, Uint&amp;lt;64&amp;gt;&amp;gt;;
export ledger participants:          Set&amp;lt;Bytes&amp;lt;32&amp;gt;&amp;gt;;
export ledger revealed:              Set&amp;lt;Bytes&amp;lt;32&amp;gt;&amp;gt;;
// ... per-team pools, etc.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;code&gt;commitments&lt;/code&gt; only holds hashes, and &lt;code&gt;stakes&lt;/code&gt; only holds amounts. "Who" bet and "how much" is public, but "which team" doesn't exist anywhere yet at this point.&lt;/p&gt;
&lt;h2&gt;
  
  
  Commit: Only the Hash Hits the Ledger
&lt;/h2&gt;

&lt;p&gt;Here's the circuit called when you place a bet.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export circuit commit_prediction(stake: Uint&amp;lt;64&amp;gt;): [] {
  const public_stake = disclose(stake);
  assert(phase == MarketPhase.open, "Predictions are closed");
  assert(public_stake &amp;gt;= 10 &amp;amp;&amp;amp; public_stake &amp;lt;= 500, "Stake must be between 10 and 500 points");

  const key = derive_participant_key(local_secret_key());
  assert(!participants.member(disclose(key)), "Participant already committed");
  const team = get_selected_team();
  const salt = get_prediction_salt();
  const commitment = make_prediction_commitment(key, team, public_stake, salt);
  store_prediction(team, public_stake, salt);

  participants.insert(disclose(key));
  commitments.insert(disclose(key), disclose(commitment));
  stakes.insert(disclose(key), public_stake);
  participant_count.increment(1);
  total_pool = (total_pool + public_stake) as Uint&amp;lt;64&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;code&gt;team&lt;/code&gt; is never &lt;code&gt;disclose()&lt;/code&gt;d here. It's only used as an input to &lt;code&gt;make_prediction_commitment()&lt;/code&gt;, and only that function's return value (a hash) gets written to the ledger, via &lt;code&gt;disclose(commitment)&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pure circuit make_prediction_commitment(
  participant_key: Bytes&amp;lt;32&amp;gt;,
  team: Team,
  stake: Uint&amp;lt;64&amp;gt;,
  salt: Bytes&amp;lt;32&amp;gt;
): Bytes&amp;lt;32&amp;gt; {
  const team_bytes = ((team as Field) as Bytes&amp;lt;32&amp;gt;);
  const stake_bytes = ((stake as Field) as Bytes&amp;lt;32&amp;gt;);
  return persistentHash&amp;lt;Vector&amp;lt;5, Bytes&amp;lt;32&amp;gt;&amp;gt;&amp;gt;([
    pad(32, "forecast:pick:v1"), participant_key, team_bytes, stake_bytes, salt
  ]);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Mixing the fixed string &lt;code&gt;"forecast:pick:v1"&lt;/code&gt; into the first slot of &lt;code&gt;persistentHash&lt;/code&gt; is a standard trick called domain separation. Tagging each circuit's hash inputs differently prevents an accidental collision where two different circuits produce the same hash for the same underlying values. The participant key derivation (&lt;code&gt;derive_participant_key&lt;/code&gt;) uses its own tag, &lt;code&gt;"forecast:participant:v1"&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;&lt;br&gt;
The salt isn't there because the stake only ranges from 10 to 500. &lt;code&gt;stake&lt;/code&gt; is already written to &lt;code&gt;stakes&lt;/code&gt; as plaintext at &lt;code&gt;commit_prediction&lt;/code&gt; time, so it's public information an observer doesn't even need to guess.&lt;/p&gt;

&lt;p&gt;The real weak point is that &lt;code&gt;team&lt;/code&gt; only has 4 possible values. Without a salt, an observer could take the (already public) participant key and stake, hash all 4 team candidates, and instantly find which one matches the on-chain commitment. Mixing in a random 256-bit salt makes that 4-way brute force infeasible.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;On screen, you pick a team and a stake (Confidence points), then hit "Seal my forecast."&lt;/p&gt;

&lt;p&gt;At this point, the team breakdown on the left is still frozen.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faldwwvapc4ydl0i0zxis.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faldwwvapc4ydl0i0zxis.jpg" alt="Committing a prediction: about to press Seal my forecast" width="800" height="713"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Staking 370 points on Harbor Whales. The team list on the left still just shows "SEALED" — nothing about the breakdown is public yet.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Pressing "Seal my forecast" kicks off ZK proof generation, and you'll see a toast like this while it runs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkjwdq9ufma71s3zqdfpb.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkjwdq9ufma71s3zqdfpb.jpg" alt="Toast reading " width="800" height="654"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The browser is generating a proof for the &lt;code&gt;commit_prediction&lt;/code&gt; circuit. Behind the scenes, the witnesses (&lt;code&gt;get_selected_team&lt;/code&gt; / &lt;code&gt;get_prediction_salt&lt;/code&gt;) read their values out of local private state.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Reveal: Prove It, Then Publish It
&lt;/h2&gt;

&lt;p&gt;After the deadline, participants "reveal" their prediction.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export circuit reveal_prediction(): [] {
  assert(phase == MarketPhase.reveal, "Market is not accepting reveals");
  const key = derive_participant_key(local_secret_key());
  const public_key = disclose(key);
  assert(participants.member(public_key), "Participant did not commit");
  assert(!revealed.member(public_key), "Prediction already revealed");

  const team = get_selected_team();
  const salt = get_prediction_salt();
  const stake = stakes.lookup(public_key);
  const expected = make_prediction_commitment(key, team, stake, salt);
  assert(disclose(expected == commitments.lookup(public_key)), "Prediction commitment mismatch");

  if (disclose(team == Team.amber_foxes)) { amber_foxes_pool = (amber_foxes_pool + stake) as Uint&amp;lt;64&amp;gt;; }
  // ... same for the other teams
  revealed.insert(public_key);
  revealed_count.increment(1);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;All this circuit does is recompute the exact same hash from the values read back out of the witnesses, and prove it matches the hash already sitting on the ledger. Only once that matches does a comparison like &lt;code&gt;if (disclose(team == Team.amber_foxes))&lt;/code&gt; finally turn the team into public information and add the stake to that team's pool.&lt;/p&gt;

&lt;p&gt;This guarantees that "the team you're revealing really is the one you picked at commit time." Rewriting the locally stored &lt;code&gt;selectedTeam&lt;/code&gt; or &lt;code&gt;salt&lt;/code&gt; afterward to switch to a more favorable team gets rejected by this assert. There's actually a Vitest simulator test for exactly this case:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rejects a modified private prediction&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;market&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setPrediction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;alice&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Team&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;amber_foxes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;market&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;alice&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;market&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;closePredictions&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;market&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setPrediction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;alice&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Team&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cedar_owls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;99&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;market&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reveal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;alice&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toThrow&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Rewriting the local selection to &lt;code&gt;cedar_owls&lt;/code&gt; after committing, then trying to reveal, gets rejected as expected. Without this check, the whole point of the privacy mechanism — preventing after-the-fact switching to whichever side looks better — would collapse. I consider this the single most important test in the suite, unglamorous as it is.&lt;/p&gt;

&lt;p&gt;As reveals come in, the per-team breakdown that was invisible at commit time finally starts showing up on screen.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb3ywpsplbdmlr8eyc9k3.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb3ywpsplbdmlr8eyc9k3.jpg" alt="Reveal screen after some reveals: Cedar Owls at 22%, Meadow Bears at 77%" width="800" height="657"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;"YOUR POSITION" on the right shows your own prediction (Cedar Owls, 100pt) as "revealed, awaiting the match result." The list on the left updates its per-team breakdown (% and points) as more reveals come in.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Verifying Division Without Doing Division
&lt;/h2&gt;

&lt;p&gt;The pari-mutuel payout formula, &lt;code&gt;floor(total_pool × stake / winning_team_pool)&lt;/code&gt;, was a genuinely annoying part to get right. I didn't want to write division directly inside a ZK circuit, so the design instead has the client compute the division result, and the circuit only verifies that the result is correct.&lt;/p&gt;

&lt;p&gt;Before the division check, though, there's actually a "is this person even allowed to claim?" section. Here's the whole circuit, nothing omitted.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export circuit claim_reward(reward: Uint&amp;lt;64&amp;gt;): [] {
  const public_reward = disclose(reward);
  assert(phase == MarketPhase.resolved &amp;amp;&amp;amp; result_set, "Market is not resolved");
  const key = derive_participant_key(local_secret_key());
  const public_key = disclose(key);
  assert(revealed.member(public_key), "Prediction was not revealed");
  assert(!claimed.member(public_key), "Reward already claimed");
  const team = get_selected_team();
  const salt = get_prediction_salt();
  const stake = stakes.lookup(public_key);
  const expected = make_prediction_commitment(key, team, stake, salt);
  assert(disclose(expected == commitments.lookup(public_key)), "Prediction commitment mismatch");
  assert(disclose(team == winning_team), "Prediction did not win");

  const winning_pool = pool_for(winning_team);
  const payout_numerator = total_pool * stake;
  assert(public_reward * winning_pool &amp;lt;= payout_numerator, "Reward exceeds pari-mutuel entitlement");
  assert((public_reward + 1) * winning_pool &amp;gt; payout_numerator, "Reward is below floor-rounded entitlement");
  assert(total_claimed_rewards + public_reward &amp;lt;= total_pool, "Reward conservation violated");
  rewards.insert(public_key, public_reward);
  claimed.insert(public_key);
  total_claimed_rewards = (total_claimed_rewards + public_reward) as Uint&amp;lt;64&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The first five lines repeat the same pattern as &lt;code&gt;reveal_prediction&lt;/code&gt; — recompute the commitment from values read back out of the witnesses, and check it against the ledger. That's what stops anyone from claiming with someone else's &lt;code&gt;stake&lt;/code&gt; or &lt;code&gt;team&lt;/code&gt;. On top of that, &lt;code&gt;revealed.member&lt;/code&gt; checks that the prediction was actually revealed, &lt;code&gt;!claimed.member&lt;/code&gt; checks it hasn't already been claimed, and &lt;code&gt;team == winning_team&lt;/code&gt; checks the caller actually picked the winner. All four asserts together are what prevent double claims and stealing someone else's winnings.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;&lt;br&gt;
Even though &lt;code&gt;total_pool&lt;/code&gt; and &lt;code&gt;stake&lt;/code&gt; are both &lt;code&gt;Uint&amp;lt;64&amp;gt;&lt;/code&gt;, a multiplication like &lt;code&gt;total_pool * stake&lt;/code&gt; doesn't silently wrap around at 64 bits — Compact's type system carries the full product range (&lt;code&gt;Uint&amp;lt;0..(2^64-1)^2&amp;gt;&lt;/code&gt;) through the expression. I confirmed this by compiling a small overflow test with &lt;code&gt;compact compile&lt;/code&gt;. In the actual contract, &lt;code&gt;as Uint&amp;lt;64&amp;gt;&lt;/code&gt; casts only appear where a value is deliberately narrowed back down.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;r = floor(n / p)&lt;/code&gt; is equivalent to the inequality &lt;code&gt;r × p ≤ n &amp;lt; (r + 1) × p&lt;/code&gt;. That's exactly what these two asserts check, using only multiplication and comparison to validate "is this the correct floor-division result." Whether the caller supplies a suspiciously large &lt;code&gt;reward&lt;/code&gt; or a suspiciously small one, one of these two inequalities catches it.&lt;/p&gt;

&lt;p&gt;The last check, &lt;code&gt;total_claimed_rewards + public_reward &amp;lt;= total_pool&lt;/code&gt;, is a conservation rule: even summing every winner's claim, the total can never exceed the pool. Any remainder left over from floor rounding simply stays in the pool unclaimed by design — I confirmed this with a test that covers the multiple-winners case too.&lt;/p&gt;

&lt;p&gt;Once a market resolves, participants who picked the winning team see a computed reward amount and a "Claim forecast points" button.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flxfs92vp2dvvq8ps7hzi.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flxfs92vp2dvvq8ps7hzi.jpg" alt="Post-resolution screen: Estimated reward 440 pts, Claim forecast points button enabled" width="800" height="654"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;A 100pt bet on Cedar Owls paid off, for an estimated reward of 440pts. That 440 is only a number the frontend computed — what actually lands on the ledger is whatever value you pass into &lt;code&gt;claim_reward&lt;/code&gt; and gets past the two asserts above.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Private State Only Lives in the Browser
&lt;/h2&gt;

&lt;p&gt;For the commitment check to work at all, &lt;code&gt;selectedTeam&lt;/code&gt; and &lt;code&gt;salt&lt;/code&gt; have to survive in the browser from commit time through reveal time. Here's the frontend implementation (&lt;code&gt;pkgs/app/src/lib/prediction-market.ts&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;savePrediction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PredictionMarketProviders&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;team&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Team&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;stake&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;privateStateProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PredictionMarketPrivateStateId&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;initialPrivateState&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;salt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;salt&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRandomValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;next&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PredictionMarketPrivateState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;selectedTeam&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;team&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stake&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;salt&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;privateStateProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PredictionMarketPrivateStateId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Before calling the circuit, the chosen team and a random salt get saved to the local private state store first. The &lt;code&gt;get_selected_team&lt;/code&gt; and &lt;code&gt;get_prediction_salt&lt;/code&gt; witnesses just read those values back out of that local store at proof-generation time.&lt;/p&gt;

&lt;p&gt;The flip side is that if this local store gets wiped, you're locked out for good. The README carries this warning as-is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Warning&lt;/strong&gt;&lt;br&gt;
This app has a constraint if you want to use multiple markets at once. &lt;code&gt;PredictionMarketPrivateStateId&lt;/code&gt; is a fixed key scoped per network + wallet — not per contract address. That means committing to a second market with the same wallet overwrites the &lt;code&gt;selectedTeam&lt;/code&gt; / &lt;code&gt;salt&lt;/code&gt; that the first market needs, and you lose the ability to reveal your first prediction.&lt;/p&gt;

&lt;p&gt;On top of that, &lt;code&gt;derive_participant_key&lt;/code&gt; is derived purely from &lt;code&gt;local_secret_key()&lt;/code&gt;, so the same wallet produces the same &lt;code&gt;participant_key&lt;/code&gt; (and the same public &lt;code&gt;admin_key&lt;/code&gt;, if you're the deployer) across every market. Within a single market, who bet on what stays hidden — but the fact that the same wallet is involved in multiple markets does not. That's an acceptable tradeoff for a single-market demo, but real multi-market use would need &lt;code&gt;privateStateId&lt;/code&gt; scoped per contract address, plus market-specific data mixed into the key derivation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Warning&lt;/strong&gt;&lt;br&gt;
Clearing your browser data, or switching devices, between committing and revealing means that prediction can never be revealed again.&lt;/p&gt;

&lt;p&gt;Neither the secret key nor the salt is backed up anywhere server-side, so this is a genuine UX gap, not an oversight.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is deliberately "temporary privacy," not "permanent anonymity." Revealing is something you choose to do, and once you reveal, that team becomes public information as a matter of course. The design doesn't try to preserve anonymity past the point where the market ends.&lt;/p&gt;

&lt;p&gt;This "it only exists locally" feeling is easiest to see by putting two screens side by side.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F25grxgmoom8qxkvbpowi.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F25grxgmoom8qxkvbpowi.jpg" alt="Right after committing: only this browser remembers picking Harbor Whales" width="800" height="708"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;In the browser that committed, "Forecast: Harbor Whales" is right there. That's not read from the ledger — this tab is just displaying its own private state.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy3o59bvjgmlfthkr5sv4.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy3o59bvjgmlfthkr5sv4.jpg" alt="A different browser/wallet opening the same resolved market shows nothing stored" width="800" height="460"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Same resolved market, but a browser profile that never predicted just shows "No personal forecast is stored in this browser profile." The ledger only has the public result (Amber Foxes won) — who picked what is known only to whoever holds that private state.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Actually Running It
&lt;/h2&gt;

&lt;p&gt;Just staring at the code, it's hard to tell whether the commit-reveal check is really doing its job. If you're curious, the fastest way to find out is to run the simulator tests yourself.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A devcontainer config is included — I'd recommend using it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Prerequisites:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bun 1.2.x&lt;/li&gt;
&lt;li&gt;Compact CLI 0.30.0
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bun &lt;span class="nb"&gt;install
&lt;/span&gt;bun run contract compact   &lt;span class="c"&gt;# generate circuits, proving keys, and ZKIR from .compact&lt;/span&gt;
bun run &lt;span class="nb"&gt;test&lt;/span&gt;                &lt;span class="c"&gt;# contract simulator tests&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Example test output:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✓ src/test/prediction-market.test.ts (10 tests) 1142ms

 Test Files  1 passed (1)
      Tests  10 passed (10)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Those 10 tests include the cases mentioned earlier:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;"rewriting local state before revealing gets rejected"&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;"stakes outside the allowed range, or duplicate commits, get rejected"&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;"a team with zero winning pool can't be selected as the result"&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;"floor division across multiple winners never pays out more than the pool"&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to see it running in a browser, deploy the contract, run &lt;code&gt;bun run build&lt;/code&gt; and then &lt;code&gt;bun run dev&lt;/code&gt; to start the Vite dev server, and connect Lace Wallet to the Preview network — that's the quickest way to try it.&lt;/p&gt;

&lt;p&gt;The bundled headless CLI's (&lt;code&gt;bun run cli standalone&lt;/code&gt;) "Show market state" command lets you follow the same 4 phases from a terminal too.&lt;/p&gt;

Watching phase transitions from the CLI (open → reveal → awaiting_result → resolved)

![CLI Show market state: Phase: open, Participants: 2, Total pool: 560](https://raw.githubusercontent.com/mashharuki/zenn-docs/main/images/midnight_simple_app-5/6.jpg)
*Right after committing. 2 participants, 560 points total in the pool, but every per-team pool is still 0.*

![CLI Show market state: Phase: reveal, Revealed: 2, Team pools: cedar=100, meadow=340](https://raw.githubusercontent.com/mashharuki/zenn-docs/main/images/midnight_simple_app-5/8.jpg)
*Both participants have revealed. This is the first time the per-team pools (cedar=100, meadow=340) get filled in.*

![CLI Show market state: Phase: awaiting_result](https://raw.githubusercontent.com/mashharuki/zenn-docs/main/images/midnight_simple_app-5/9.jpg)
*The admin ran Close reveal, moving the market into the awaiting-result phase.*

![CLI Show market state: Phase: resolved, Result: cedar_owls](https://raw.githubusercontent.com/mashharuki/zenn-docs/main/images/midnight_simple_app-5/10.jpg)
*The admin ran Resolve market, recording Cedar Owls as the winner. From here, only winners can call `claim_reward`.*


&lt;h2&gt;
  
  
  What This Design Doesn't Cover
&lt;/h2&gt;

&lt;p&gt;Since this is a prototype, there are several things missing before it could become a real prediction market app — including anything that ingests outside information.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Trusts a single admin (steward).&lt;/strong&gt; Whoever deployed the market has full authority to finalize the result. There's no external oracle or dispute-resolution mechanism.&lt;/li&gt;
&lt;li&gt;Phase transitions only go one way — &lt;code&gt;open → reveal → awaiting_result → resolved&lt;/code&gt; — &lt;strong&gt;with no undo or rollback&lt;/strong&gt;. In particular, if reveals get closed while zero predictions have been revealed, that market becomes permanently unclaimable. That's a known area for future improvement.&lt;/li&gt;
&lt;li&gt;What's at stake isn't a real asset — it's &lt;strong&gt;demo points specific to this app&lt;/strong&gt;. There's no secondary market and no permissionless market creation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Building a real privacy-preserving prediction market on this pattern would require multisig or optimistic-oracle result finalization, explicit deadline management, proper asset custody design, and an external audit.&lt;/p&gt;

&lt;p&gt;This project's scope stops at "actually get hands-on with commit-reveal plus zero-knowledge proofs and understand it," since that's what I set out to learn.&lt;/p&gt;
&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;Following up on the anonymous rock-paper-scissors app, I built a prediction market app on Midnight too.&lt;/p&gt;

&lt;p&gt;Midnight also just released an Agent Skill for AI coding tools, which makes development a lot smoother.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://midskills.sevryn.xyz/" rel="noopener noreferrer"&gt;https://midskills.sevryn.xyz/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you're looking for something to build, give Midnight a try.&lt;/p&gt;

&lt;p&gt;Everything is open source, so if anything caught your interest, go read the code while running &lt;code&gt;bun run test&lt;/code&gt;.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/mashharuki" rel="noopener noreferrer"&gt;
        mashharuki
      &lt;/a&gt; / &lt;a href="https://github.com/mashharuki/midnight-prediction-market-sample-app" rel="noopener noreferrer"&gt;
        midnight-prediction-market-sample-app
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Midnight Prediction Market sample dApp
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Hidden League Forecast&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;A privacy-preserving, four-outcome football prediction market built with Midnight Compact, Midnight.js, and Lace Wallet.&lt;/p&gt;

&lt;p&gt;Players predict which fictional club will win the Lantern Cup. During the open phase the team and salt stay in the browser's Midnight private-state store; only a salted commitment, pseudonymous participant key, and public demo-point stake reach the ledger. Picks are revealed only after predictions close, preventing followers from copying the crowd.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Educational example only. It uses demo points and a trusted market steward—not real assets or a production oracle.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Demo&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;&lt;a href="https://youtu.be/G4T-L-rVgzU" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmashharuki%2Fmidnight-prediction-market-sample-app%2FHEAD%2F.%2Fdocs%2Fyoutube-thumbnail.png" alt="'altテキスト'"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Screen Shots&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/mashharuki/midnight-prediction-market-sample-app/./docs/0.jpg"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmashharuki%2Fmidnight-prediction-market-sample-app%2FHEAD%2F.%2Fdocs%2F0.jpg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/mashharuki/midnight-prediction-market-sample-app/./docs/1.jpg"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmashharuki%2Fmidnight-prediction-market-sample-app%2FHEAD%2F.%2Fdocs%2F1.jpg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/mashharuki/midnight-prediction-market-sample-app/./docs/2.jpg"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmashharuki%2Fmidnight-prediction-market-sample-app%2FHEAD%2F.%2Fdocs%2F2.jpg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/mashharuki/midnight-prediction-market-sample-app/./docs/3.jpg"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmashharuki%2Fmidnight-prediction-market-sample-app%2FHEAD%2F.%2Fdocs%2F3.jpg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/mashharuki/midnight-prediction-market-sample-app/./docs/4.jpg"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmashharuki%2Fmidnight-prediction-market-sample-app%2FHEAD%2F.%2Fdocs%2F4.jpg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/mashharuki/midnight-prediction-market-sample-app/./docs/5.jpg"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmashharuki%2Fmidnight-prediction-market-sample-app%2FHEAD%2F.%2Fdocs%2F5.jpg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/mashharuki/midnight-prediction-market-sample-app/./docs/6.jpg"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmashharuki%2Fmidnight-prediction-market-sample-app%2FHEAD%2F.%2Fdocs%2F6.jpg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/mashharuki/midnight-prediction-market-sample-app/./docs/7.jpg"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmashharuki%2Fmidnight-prediction-market-sample-app%2FHEAD%2F.%2Fdocs%2F7.jpg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/mashharuki/midnight-prediction-market-sample-app/./docs/8.jpg"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmashharuki%2Fmidnight-prediction-market-sample-app%2FHEAD%2F.%2Fdocs%2F8.jpg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/mashharuki/midnight-prediction-market-sample-app/./docs/9.jpg"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmashharuki%2Fmidnight-prediction-market-sample-app%2FHEAD%2F.%2Fdocs%2F9.jpg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/mashharuki/midnight-prediction-market-sample-app/./docs/10.jpg"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmashharuki%2Fmidnight-prediction-market-sample-app%2FHEAD%2F.%2Fdocs%2F10.jpg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/mashharuki/midnight-prediction-market-sample-app/./docs/11.jpg"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmashharuki%2Fmidnight-prediction-market-sample-app%2FHEAD%2F.%2Fdocs%2F11.jpg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Market flow&lt;/h2&gt;

&lt;/div&gt;

&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;OPEN → REVEAL → AWAITING RESULT → RESOLVED → CLAIM
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;Connect Lace and deploy or join a market.&lt;/li&gt;
&lt;li&gt;Select Amber Foxes, Cedar Owls, Harbor Whales, or Meadow Bears.&lt;/li&gt;
&lt;li&gt;Commit 10–500 public demo points. The team and salt remain private.&lt;/li&gt;
&lt;li&gt;The steward closes predictions; participants reveal their committed picks.&lt;/li&gt;
&lt;li&gt;The steward records the fictional match winner.&lt;/li&gt;
&lt;li&gt;Correct predictions claim…&lt;/li&gt;
&lt;/ol&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/mashharuki/midnight-prediction-market-sample-app" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;Thanks for reading!&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://midnight.network/" rel="noopener noreferrer"&gt;Midnight Network&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/mashharuki/midnight-prediction-market-sample-app" rel="noopener noreferrer"&gt;This article's repository (contract, frontend, CLI)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>web3</category>
      <category>zeroknowledge</category>
      <category>privacy</category>
      <category>cardano</category>
    </item>
    <item>
      <title>Building a Safe Telegram FX Bot with the Sera Protocol API and Privy</title>
      <dc:creator>Haruki Kondo</dc:creator>
      <pubDate>Sun, 12 Jul 2026 07:33:48 +0000</pubDate>
      <link>https://dev.to/mashharuki/building-a-safe-telegram-fx-bot-with-the-sera-protocol-api-and-privy-2hd</link>
      <guid>https://dev.to/mashharuki/building-a-safe-telegram-fx-bot-with-the-sera-protocol-api-and-privy-2hd</guid>
      <description>&lt;p&gt;I have previously explored Sera Protocol through its architecture, GraphQL API, smart contracts, a React frontend, an MCP server, and an Agent Skill.&lt;/p&gt;

&lt;p&gt;For the next step, I wanted to move from “calling an API” to building something that a real user could operate without a dedicated trading interface. The result is a &lt;strong&gt;Telegram bot for stablecoin FX&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This article is for developers who know the basics of TypeScript and Web3. You do not need prior experience with Sera Protocol. We will cover the complete architecture, Sera REST API v2, wallet creation with Privy, EIP-712 signing, and the confirmation boundary that prevents the AI agent from moving funds by itself.&lt;/p&gt;

&lt;p&gt;Here are the demo and source code first:&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/vDMDAgapwSU"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/mashharuki/SeraProtocol-Sample/tree/main/telegram-bot" rel="noopener noreferrer"&gt;https://github.com/mashharuki/SeraProtocol-Sample/tree/main/telegram-bot&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; This article reflects the implementation as of July 2026. Sera APIs, supported currencies, and contract addresses may change. Check the &lt;a href="https://docs.testnet.sera.cx/" rel="noopener noreferrer"&gt;official documentation&lt;/a&gt; and the live response from &lt;code&gt;GET /config&lt;/code&gt; before integrating it into your own application.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;

&lt;p&gt;The Sera FX Telegram Bot is an onchain trading bot that uses &lt;strong&gt;Sera Protocol REST API v2 and Privy server wallets to perform stablecoin FX from Telegram&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Users do not need to open a separate web app or connect a browser extension wallet. Running &lt;code&gt;/start&lt;/code&gt; creates an Ethereum wallet through Privy and takes the user directly into the guided trading flow.&lt;/p&gt;

&lt;p&gt;The bot has four core characteristics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Seed-phrase-free onboarding&lt;/li&gt;
&lt;li&gt;English and Japanese support on Ethereum Mainnet and Sepolia&lt;/li&gt;
&lt;li&gt;Swaps, cross-currency transfers, limit orders, and liquidity provision inside Telegram&lt;/li&gt;
&lt;li&gt;The AI can prepare an action, but only the user's Confirm tap can trigger signing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The last point is the most important design decision in this project.&lt;/p&gt;

&lt;p&gt;Giving an LLM direct access to a private key or unrestricted signing method would create an unacceptable security boundary. I separated the agent that interprets natural language from the deterministic code that authorizes and submits transactions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature overview
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Onboarding&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/start&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Select a language, create a Privy wallet, and issue a Sera API key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Assets&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;/wallet&lt;/code&gt;, &lt;code&gt;/balance&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Show the address, deposit QR code, wallet balance, and Vault balance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Market data&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;/rate&lt;/code&gt;, &lt;code&gt;/liquidity&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Show FX rates and probe which major pairs currently have executable liquidity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Instant trade&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/swap&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Execute an instant swap from a gas-inclusive quote&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transfer&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/send&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Convert a stablecoin and deliver the output to another address&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Limit orders&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;/order&lt;/code&gt;, &lt;code&gt;/orders&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Create, inspect, and cancel resting orders&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Liquidity&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/provide&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Quote multiple markets with a Virtual Liquidity batch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vault&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/deposit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Approve a token and deposit it into the Sera Vault&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wallet keys&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;/exportkey&lt;/code&gt;, &lt;code&gt;/importkey&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Export or import a private key through the guarded Privy flow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Settings&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;/network&lt;/code&gt;, &lt;code&gt;/language&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Switch between Mainnet/Sepolia and English/Japanese&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI&lt;/td&gt;
&lt;td&gt;Free-form text&lt;/td&gt;
&lt;td&gt;Ask the Mastra agent for an explanation or to prepare an action&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;/swap&lt;/code&gt; returns the output to the user's own wallet. &lt;code&gt;/send&lt;/code&gt; uses the same swap flow but changes &lt;code&gt;recipient&lt;/code&gt; to another address. This makes a request such as “convert 100 USDC to EURC and send it to this address” one signed Sera Intent.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Testnet warning:&lt;/strong&gt; Sepolia liquidity can be thin. A &lt;code&gt;NO_LIQUIDITY&lt;/code&gt; response does not necessarily indicate a bot failure. Use &lt;code&gt;/liquidity&lt;/code&gt; to discover executable pairs, or test the &lt;code&gt;/deposit&lt;/code&gt; → &lt;code&gt;/order&lt;/code&gt; flow instead.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Technology stack
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Technology&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Runtime&lt;/td&gt;
&lt;td&gt;Bun + TypeScript&lt;/td&gt;
&lt;td&gt;Bot, API server, and tests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Telegram&lt;/td&gt;
&lt;td&gt;grammY 1.44&lt;/td&gt;
&lt;td&gt;Commands, sessions, and inline keyboards&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTTP API&lt;/td&gt;
&lt;td&gt;Hono 4.12 + Zod OpenAPI&lt;/td&gt;
&lt;td&gt;Webhook, health endpoint, and Swagger UI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trading&lt;/td&gt;
&lt;td&gt;Sera REST API v2&lt;/td&gt;
&lt;td&gt;Rates, swaps, orders, Vault, and Virtual Liquidity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wallet&lt;/td&gt;
&lt;td&gt;Privy Node SDK 0.25&lt;/td&gt;
&lt;td&gt;Ethereum wallets, EIP-712 signatures, and transaction signing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Web3&lt;/td&gt;
&lt;td&gt;viem 2.55&lt;/td&gt;
&lt;td&gt;Addresses, typed data, and unit conversion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI agent&lt;/td&gt;
&lt;td&gt;Mastra 1.x + Claude&lt;/td&gt;
&lt;td&gt;Natural-language interpretation and read/prepare tools&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database&lt;/td&gt;
&lt;td&gt;LibSQL / Turso&lt;/td&gt;
&lt;td&gt;Users, API keys, orders, pending actions, and agent memory&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Validation&lt;/td&gt;
&lt;td&gt;Zod 4&lt;/td&gt;
&lt;td&gt;Environment and API response validation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Quality&lt;/td&gt;
&lt;td&gt;Bun Test, TypeScript, Biome&lt;/td&gt;
&lt;td&gt;Tests, type checking, and linting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deployment&lt;/td&gt;
&lt;td&gt;Docker + Cloud Run&lt;/td&gt;
&lt;td&gt;Polling locally and webhook mode in production&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The bot is built with grammY and runs a Hono HTTP server in the same process. Local development uses Telegram polling, while Cloud Run uses webhooks.&lt;/p&gt;

&lt;p&gt;The HTTP server also exposes read-only public endpoints and Swagger UI. User-specific data and every fund-moving operation remain inside the Telegram flow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;

&lt;p&gt;The main data flow looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Telegram user
    |
    v
grammY Bot ---------------------&amp;gt; Mastra Agent
    |                                  |
    v                                  v
Command / Flow Layer             Read / Prepare Tools
    |                                  |
    +---------------+------------------+
                    v
               Service Layer
              /      |       \
             v       v        v
     Sera REST API  Privy   LibSQL / Turso
                    |
                    v
              EIP-712 / Tx signing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Command handlers and Mastra tools call the same service layer. Rate lookup, swap preparation, order management, deposits, and liquidity logic all live there.&lt;/p&gt;

&lt;p&gt;The critical property is what the diagram does &lt;strong&gt;not&lt;/strong&gt; contain: there is no direct route from the Mastra agent to &lt;code&gt;PrivySigner&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The AI can read data and create a pending action. Signing only becomes reachable when the user taps Confirm on a Telegram confirmation card.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the bot uses Sera REST API v2
&lt;/h2&gt;

&lt;p&gt;My earlier Sera examples used the GraphQL subgraph and the v1 &lt;code&gt;Router&lt;/code&gt;, &lt;code&gt;OrderBook&lt;/code&gt;, and &lt;code&gt;PriceBook&lt;/code&gt; contracts.&lt;/p&gt;

&lt;p&gt;This bot uses a different integration surface: &lt;strong&gt;Sera REST API v2&lt;/strong&gt;. In v2, users sign an EIP-712 &lt;code&gt;Order&lt;/code&gt; or &lt;code&gt;Intent&lt;/code&gt;, matching happens offchain, and settlement uses the onchain Vault and Sera contracts.&lt;/p&gt;

&lt;p&gt;For a new application, the REST API removes a large amount of integration work by constructing order typed data, routing swaps, and building transactions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Main endpoints
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;Endpoint&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;System&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;GET /health&lt;/code&gt;, &lt;code&gt;/system/time&lt;/code&gt;, &lt;code&gt;/config&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Currencies and markets&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;GET /tokens&lt;/code&gt;, &lt;code&gt;/markets&lt;/code&gt;, &lt;code&gt;/fx/rate&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API keys&lt;/td&gt;
&lt;td&gt;&lt;code&gt;POST /api-keys&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Swaps&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;POST /swap/quote&lt;/code&gt;, &lt;code&gt;/swap&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Limit orders&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;POST /orders/preview&lt;/code&gt;, &lt;code&gt;/orders&lt;/code&gt;, &lt;code&gt;/orders/cancel&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Order status&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GET /orders/{order_id}&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Virtual Liquidity&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;POST /orders/vl/batch&lt;/code&gt;, &lt;code&gt;/orders/vl/cancel&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Balances&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GET /balances&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vault deposits&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;POST /approve&lt;/code&gt;, &lt;code&gt;/deposit&lt;/code&gt;, &lt;code&gt;/tx/send&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  A typed API client
&lt;/h3&gt;

&lt;p&gt;All Sera access goes through &lt;code&gt;SeraClient&lt;/code&gt;. Its shared request method handles API-key authentication, rate-limit retries, JSON parsing, and Zod response validation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/sera/client.ts&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ZodType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RequestOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;baseUrl&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;{}))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;accept&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;content-type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;API key not configured&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;authorization&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="na"&gt;res&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;attempt&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchImpl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="mi"&gt;429&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;retryAfter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;retry-after&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;waitSec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isFinite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;retryAfter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;retryAfter&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;waitSec&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SeraApiError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;extractErrorCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safeParse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SeraApiError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SCHEMA_MISMATCH&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;GET /config&lt;/code&gt; is cached for ten minutes. Chain IDs, contract addresses, and the EIP-712 domain are not hardcoded because they can differ by network and deployment.&lt;/p&gt;

&lt;p&gt;Address casing is another subtle integration detail:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/sera/client.ts&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;getBalances&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ownerAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SeraBalanceRow&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/balances&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;balancesResponseSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;owner_address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ownerAddress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;balances&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read endpoints expect lowercase &lt;code&gt;owner_address&lt;/code&gt; values. Signed payloads use the values supplied by the API or a checksummed address. Do not run both through the same normalization function.&lt;/p&gt;

&lt;h2&gt;
  
  
  From quote to swap execution
&lt;/h2&gt;

&lt;p&gt;The swap flow is the core of the bot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User              Bot / Service            Sera / Privy
 |                      |                        |
 | /swap                |                        |
 |---------------------&amp;gt;| POST /swap/quote      |
 |                      |-----------------------&amp;gt;|
 |                      | uuid + route_params    |
 |                      |&amp;lt;-----------------------|
 |  Quote + Confirm     |                        |
 |&amp;lt;---------------------|                        |
 | Confirm              |                        |
 |---------------------&amp;gt;| atomic consume         |
 |                      | EIP-712 sign via Privy |
 |                      |-----------------------&amp;gt;|
 |                      | POST /swap             |
 |                      |-----------------------&amp;gt;|
 |  Result              |                        |
 |&amp;lt;---------------------|                        |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 1: Request a quote
&lt;/h3&gt;

&lt;p&gt;The bot validates the user's amount, converts it to raw units, reads Sera's server time, and calls &lt;code&gt;POST /swap/quote&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/services/swap-service.ts&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;prepareSwap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UserRow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PrepareSwapInput&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SwapCard&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;fromToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;toToken&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rateService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;network&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fromSymbol&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rateService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;network&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toSymbol&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;fromToken&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;toToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Unknown token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;check&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;validateAmount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fromToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;decimals&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;check&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Invalid amount (&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;check&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rawAmount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;toRawUnits&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fromToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;decimals&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sera&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publicSera&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;network&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;serverTime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sera&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSystemTime&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;quote&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sera&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;swapQuote&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;from_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fromToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;to_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;toToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;from_amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;rawAmount&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;owner_address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;walletAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;recipient&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;recipient&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;walletAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;expiration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;serverTime&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;gas_mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;receive_less&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;quoteToCard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;fromSymbol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fromToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;toSymbol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;toToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;fromAmount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;toDecimals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;toToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;decimals&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;recipient&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;recipient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using &lt;code&gt;GET /system/time&lt;/code&gt; prevents local clock drift from invalidating the signature deadline.&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;gas_mode: "receive_less"&lt;/code&gt;, execution cost is reflected in the quoted output. A swap user does not need a separate ETH balance for gas. Depositing into the Vault for limit orders is a different flow and does require ETH.&lt;/p&gt;

&lt;p&gt;No signing or asset movement happens here. The bot stores the quote's &lt;code&gt;route_params&lt;/code&gt;, &lt;code&gt;uuid&lt;/code&gt;, and expiration in a pending action, then sends a confirmation card.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Sign only after confirmation
&lt;/h3&gt;

&lt;p&gt;Privy is called only after the user taps Confirm.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/services/swap-service.ts&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;executeSwap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UserRow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SwapActionPayload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sera&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publicSera&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;network&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sera&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getConfig&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;signTypedData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;walletId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eip712_domain&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;INTENT_EIP712_TYPES&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;primaryType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Intent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;routeParams&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;permitSignature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;permitEip712&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;signTypedData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;walletId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;permitEip712&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sera&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submitSwap&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;...(&lt;/span&gt;&lt;span class="nx"&gt;permitSignature&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;permitDeadline&lt;/span&gt;
        &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;permit_signature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;permitSignature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;permit_deadline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;permitDeadline&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{}),&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;success&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;SeraApiError&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isStaleQuote&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;requoted&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;card&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepareSwap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;fromSymbol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fromSymbol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;toSymbol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toSymbol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fromAmount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;recipient&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;recipient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is an important wire-format distinction here:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Signing warning:&lt;/strong&gt; &lt;code&gt;route_params&lt;/code&gt; from &lt;code&gt;/swap/quote&lt;/code&gt; is the &lt;code&gt;Intent&lt;/code&gt; message, not a complete typed-data envelope. The bot adds the domain from &lt;code&gt;GET /config&lt;/code&gt;, plus the known &lt;code&gt;Intent&lt;/code&gt; types and primary type. In contrast, &lt;code&gt;permit.eip712&lt;/code&gt; is a complete permit payload and must be signed without reconstruction.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Treating every API response as the same typed-data shape will break signatures. The implementation preserves the signature field values exactly as returned.&lt;/p&gt;

&lt;p&gt;If the quote expires, the bot requests a new quote. It never silently executes at the new price; it sends a fresh confirmation card and asks the user to confirm again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I chose Privy
&lt;/h2&gt;

&lt;p&gt;Wallet connection is awkward in a chat interface. A web app can redirect to WalletConnect or a browser extension, but repeatedly switching applications makes a Telegram flow difficult to use.&lt;/p&gt;

&lt;p&gt;The bot creates an Ethereum server wallet for each Telegram user with the Privy Node SDK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/privy/signer.ts&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;createWallet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;telegramUserId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CreatedWallet&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;externalId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`tg-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;telegramUserId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wallet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;privy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wallets&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;chain_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ethereum&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;external_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;externalId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;display_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Telegram user &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;telegramUserId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;idempotency_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`create-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;externalId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;...(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;walletAuth&lt;/span&gt;
      &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;public_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;walletAuth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;publicKeySpki&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{}),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;walletId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;external_id&lt;/code&gt; creates a durable mapping to the Telegram user. &lt;code&gt;idempotency_key&lt;/code&gt; prevents a double tap or retry from creating duplicate wallets.&lt;/p&gt;

&lt;p&gt;The database stores &lt;code&gt;walletId&lt;/code&gt; and the address. The normal trading path never retrieves or persists the private key.&lt;/p&gt;

&lt;p&gt;EIP-712 signing is delegated to Privy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/privy/signer.ts&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;signTypedData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;walletId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;typedData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SeraTypedDataPayload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;privy&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wallets&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ethereum&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;signTypedData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;walletId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;typed_data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;typedData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;typedData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;types&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;primary_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;typedData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;primaryType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;typedData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;No signature returned&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An optional P-256 authorization key can be configured as the wallet owner to enable &lt;code&gt;/exportkey&lt;/code&gt; and &lt;code&gt;/importkey&lt;/code&gt;. Those flows use Privy's high-level SDK methods and apply additional Telegram safeguards:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An exported key message is deleted after 60 seconds&lt;/li&gt;
&lt;li&gt;An import message is deleted immediately&lt;/li&gt;
&lt;li&gt;Private keys are never stored in the database or logs&lt;/li&gt;
&lt;li&gt;Key transfer is disabled when the authorization key is not configured&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The backend authorization key is highly sensitive. It belongs in a managed secret store, not source control or a committed &lt;code&gt;.env&lt;/code&gt; file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limit orders and the Vault
&lt;/h2&gt;

&lt;p&gt;Swaps can spend from the wallet directly. Limit orders first require funds in the Sera Vault.&lt;/p&gt;

&lt;p&gt;The order flow is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Read &lt;code&gt;tick_precision&lt;/code&gt; and &lt;code&gt;quantity_precision&lt;/code&gt; from &lt;code&gt;GET /markets&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Validate price and amount without silently rounding&lt;/li&gt;
&lt;li&gt;Check the available Vault balance&lt;/li&gt;
&lt;li&gt;Call &lt;code&gt;POST /orders/preview&lt;/code&gt; to obtain the EIP-712 Order payload&lt;/li&gt;
&lt;li&gt;Sign the API-provided typed data through Privy&lt;/li&gt;
&lt;li&gt;Submit it to &lt;code&gt;POST /orders&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Persist both &lt;code&gt;order_id&lt;/code&gt; and &lt;code&gt;uuid_int&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The client creates the order UUID. A retry reuses the same &lt;code&gt;order_id&lt;/code&gt;, allowing the server to deduplicate the request. &lt;code&gt;uuid_int&lt;/code&gt; is the uint256 value bound into the onchain signature and must match the encoding of &lt;code&gt;order_id&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;When the Vault balance is insufficient, &lt;code&gt;/deposit&lt;/code&gt; executes this sequence:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POST /approve  -&amp;gt; unsigned approve transaction
Privy signs it
POST /tx/send  -&amp;gt; broadcast approve

POST /deposit  -&amp;gt; unsigned deposit transaction
Privy signs it
POST /tx/send  -&amp;gt; broadcast deposit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;A swap can include gas cost in its quote and therefore does not require the user to hold ETH. Vault &lt;code&gt;approve&lt;/code&gt; and &lt;code&gt;deposit&lt;/code&gt; are transactions from the user's wallet and require a small amount of ETH.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Order cancellation has a cooldown. The bot stores the placement time and reports the remaining wait instead of repeatedly submitting a request that the API will reject.&lt;/p&gt;

&lt;h2&gt;
  
  
  Virtual Liquidity across multiple markets
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;/provide&lt;/code&gt; uses Sera v2 Virtual Liquidity.&lt;/p&gt;

&lt;p&gt;Suppose a market maker wants to place three orders, each spending up to 1,000 USDC:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1,000 USDC -&amp;gt; EURC/USDC bid
1,000 USDC -&amp;gt; XSGD/USDC bid
1,000 USDC -&amp;gt; JPYC/USDC bid

Shared collateral budget: 1,000 USDC
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A Virtual Liquidity batch groups 2–50 orders across distinct markets under one collateral budget. The Vault freezes the largest single leg rather than the sum. When one order consumes budget, sibling orders are automatically reduced.&lt;/p&gt;

&lt;p&gt;Each leg shares a VL group ID and receives a distinct &lt;code&gt;leg_id&lt;/code&gt; inside &lt;code&gt;uuid_int&lt;/code&gt;. The preview endpoint accepts standalone encodings, so the implementation replaces only the &lt;code&gt;uuid&lt;/code&gt; field with the VL encoding before signing. Every other API-provided EIP-712 field remains unchanged.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pending-action security boundary
&lt;/h2&gt;

&lt;p&gt;Every operation that can move funds follows one path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Prepare action
    -&amp;gt; store pending action
    -&amp;gt; render Telegram confirmation card
    -&amp;gt; user taps Confirm
    -&amp;gt; atomically consume the one-time action
    -&amp;gt; Privy signs
    -&amp;gt; submit to Sera
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A pending action stores the payload, Telegram user ID, network, expiration, and consumption timestamp.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/services/pending-actions.ts&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;consume&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;telegramUserId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ConsumeResult&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;not_found&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;telegramUserId&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;telegramUserId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;wrong_user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;consumedAt&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;already_used&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expiresAt&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;expired&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;won&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;consume&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;won&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;already_used&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ok&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;repo.consume()&lt;/code&gt; updates &lt;code&gt;consumed_at&lt;/code&gt; only when the row is still unused. Even if Telegram resends a callback or the user taps repeatedly, only the request that wins the atomic update continues.&lt;/p&gt;

&lt;p&gt;The action ID is a short random value that fits inside Telegram's &lt;code&gt;callback_data&lt;/code&gt; limit. The payload stays in the database rather than being embedded in the button.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Prepared --Confirm--&amp;gt; Executing --success--&amp;gt; Success
    |                     |
    |                     +--stale quote--&amp;gt; Requoted --&amp;gt; Prepared
    +--Cancel-----------&amp;gt; Cancelled
    +--TTL elapsed------&amp;gt; Expired
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This boundary rejects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An action confirmed by a different Telegram user&lt;/li&gt;
&lt;li&gt;An expired quote&lt;/li&gt;
&lt;li&gt;A double Confirm tap&lt;/li&gt;
&lt;li&gt;Reuse of a cancelled action&lt;/li&gt;
&lt;li&gt;An AI-prepared action that the user never approved&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Integrating a Mastra agent without giving it a wallet
&lt;/h2&gt;

&lt;p&gt;Free-form messages are sent to a Mastra agent. For example, “swap 100 USDC to EURC” causes the agent to select a tool and prepare a confirmation card.&lt;/p&gt;

&lt;p&gt;Only these tool categories are registered:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Read: FX rate, markets, liquidity, balances, and orders&lt;/li&gt;
&lt;li&gt;Prepare: swap, send, and limit order&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The prepare tool looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/mastra/tools/prepare-tools.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prepareSwapTool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createTool&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;prepare-swap&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Prepare a swap. The user must tap Confirm before execution.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;inputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;fromSymbol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;toSymbol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="na"&gt;outputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="na"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resolved&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;requireUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;requestContext&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;resolved&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;resolved&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;card&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;resolved&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;swaps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepareSwap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;resolved&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;getCardCollector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;requestContext&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;swap&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;actionId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;actionId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Swap prepared. Ask the user to review and tap Confirm.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The bot places the verified Telegram user ID, language, network, and wallet address into &lt;code&gt;requestContext&lt;/code&gt;. Identity never comes from model-generated tool arguments.&lt;/p&gt;

&lt;p&gt;Cards created by the agent use the same confirmation callback as command-driven cards. There is no special AI signing route.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The AI prepares a trade. The human approves it. Deterministic application code performs the signature and submission.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This separation preserves the convenience of natural language while reducing the chance that a model error or prompt injection becomes an unauthorized asset movement.&lt;/p&gt;

&lt;p&gt;Without &lt;code&gt;ANTHROPIC_API_KEY&lt;/code&gt;, free-form chat is disabled but every guided command continues to work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Git&lt;/li&gt;
&lt;li&gt;Bun 1.x&lt;/li&gt;
&lt;li&gt;A Telegram Bot token&lt;/li&gt;
&lt;li&gt;A Privy App ID and App Secret&lt;/li&gt;
&lt;li&gt;Sepolia ETH and supported test tokens when using testnet&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  1. Clone and install
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/mashharuki/SeraProtocol-Sample.git
&lt;span class="nb"&gt;cd &lt;/span&gt;SeraProtocol-Sample/telegram-bot
bun &lt;span class="nb"&gt;install
cp&lt;/span&gt; .env.example .env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Configure the required environment variables
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# From Telegram @BotFather
TELEGRAM_BOT_TOKEN=

# From the Privy Dashboard
PRIVY_APP_ID=
PRIVY_APP_SECRET=

DEFAULT_NETWORK=sepolia
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The AI chat, persistent production database, and key import/export flows are optional:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ANTHROPIC_API_KEY=

# Local SQLite; use Turso or another persistent LibSQL service on Cloud Run
DATABASE_URL=file:./data/bot.db
DATABASE_AUTH_TOKEN=

# Generate with: bun scripts/gen-wallet-auth-key.ts
WALLET_AUTH_PRIVATE_KEY=
WALLET_AUTH_PUBLIC_KEY=
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Security warning:&lt;/strong&gt; &lt;code&gt;.env&lt;/code&gt; contains a Telegram token, Privy secret, and potentially an authorization private key. Never commit it. Use a managed secret store in production.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  3. Start the bot
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bun run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Polling mode is the default. Open the bot in Telegram and send &lt;code&gt;/start&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Walk through the demo
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Run &lt;code&gt;/start&lt;/code&gt;, select a language, and create a wallet&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;/wallet&lt;/code&gt; to inspect the address and deposit QR code&lt;/li&gt;
&lt;li&gt;Fund the wallet with Sepolia ETH and supported test tokens&lt;/li&gt;
&lt;li&gt;Check &lt;code&gt;/balance&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Inspect a currency pair with &lt;code&gt;/rate&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;/swap&lt;/code&gt; or &lt;code&gt;/order&lt;/code&gt; to create a confirmation card&lt;/li&gt;
&lt;li&gt;Review the details and tap Confirm&lt;/li&gt;
&lt;li&gt;Send a free-form message to test the agent's prepare-only flow&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Start with small amounts if you switch to Mainnet.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Verify the project
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bun run typecheck
bun &lt;span class="nb"&gt;test
&lt;/span&gt;bun run check
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The test suite covers precision rules, UUID encoding, pending-action single use, stale quotes, orders, deposits, Virtual Liquidity, and wallet key import/export.&lt;/p&gt;

&lt;h3&gt;
  
  
  Run with Docker
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;--build&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;
docker compose logs &lt;span class="nt"&gt;-f&lt;/span&gt; sera-fx-bot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Local SQLite data is mounted at &lt;code&gt;./data&lt;/code&gt;. A &lt;code&gt;file:&lt;/code&gt; database on Cloud Run is ephemeral, so production should use a persistent LibSQL service such as Turso.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons from the implementation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Signed payloads differ by endpoint
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;/orders/preview&lt;/code&gt; returns a complete EIP-712 payload. &lt;code&gt;/swap/quote&lt;/code&gt; returns &lt;code&gt;route_params&lt;/code&gt; as an Intent message. Similar-looking responses are not necessarily interchangeable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Never “clean up” EIP-712 values
&lt;/h3&gt;

&lt;p&gt;Formatting an address or numeric field changes the signed message. Display formatting and signing payloads must remain separate.&lt;/p&gt;

&lt;h3&gt;
  
  
  Design for quote expiration
&lt;/h3&gt;

&lt;p&gt;A user may leave a confirmation card open for several minutes. Requoting and asking for confirmation again creates a safer experience than either failing permanently or silently accepting a new price.&lt;/p&gt;

&lt;h3&gt;
  
  
  Telegram is a constrained UI
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;callback_data&lt;/code&gt; must stay short, and thousands of markets cannot fit in an inline keyboard. Guided buttons focus on major currencies, while the agent can search the wider market set.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cloud Run scale-out needs shared session state
&lt;/h3&gt;

&lt;p&gt;Orders, users, and pending actions are stored in LibSQL, but part of an in-progress guided flow is currently in memory. The deployment therefore uses &lt;code&gt;max-instances=1&lt;/code&gt;. Multi-instance deployment would require moving session state into shared storage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This project combines Sera REST API v2, Privy, grammY, and Mastra to create a Telegram interface for onchain stablecoin FX.&lt;/p&gt;

&lt;p&gt;The main takeaways are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sera REST API v2 makes it possible to integrate swaps, transfers, limit orders, Vault operations, and Virtual Liquidity from TypeScript&lt;/li&gt;
&lt;li&gt;Privy provides server-wallet creation and EIP-712 signing without putting private keys in the normal application database&lt;/li&gt;
&lt;li&gt;A one-time confirmation action separates AI assistance from actual authorization&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal should not be “an AI that can trade whenever it wants.” A safer division of responsibilities is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The AI prepares. The human confirms. Trusted deterministic code signs and executes.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Check out the demo and repository if you want to experiment with the implementation:&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/vDMDAgapwSU"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/mashharuki/SeraProtocol-Sample/tree/main/telegram-bot" rel="noopener noreferrer"&gt;https://github.com/mashharuki/SeraProtocol-Sample/tree/main/telegram-bot&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.testnet.sera.cx/" rel="noopener noreferrer"&gt;Sera Protocol documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.testnet.sera.cx/api-reference/" rel="noopener noreferrer"&gt;Sera Protocol API reference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/sera-cx/orderbook-contract-v2" rel="noopener noreferrer"&gt;Sera Orderbook Contract v2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.privy.io/" rel="noopener noreferrer"&gt;Privy documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.privy.io/wallets/wallets/create/create-a-wallet" rel="noopener noreferrer"&gt;Privy: Create a wallet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://grammy.dev/guide/" rel="noopener noreferrer"&gt;grammY documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mastra.ai/docs/agents/overview" rel="noopener noreferrer"&gt;Mastra Agents overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://zenn.dev/mashharuki" rel="noopener noreferrer"&gt;My previous Sera Protocol articles&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>web3</category>
      <category>typescript</category>
      <category>blockchain</category>
    </item>
    <item>
      <title>What We Did to Survive and Succeed in the AWS Summit AI-DLC Hackathon</title>
      <dc:creator>Haruki Kondo</dc:creator>
      <pubDate>Sun, 28 Jun 2026 14:47:54 +0000</pubDate>
      <link>https://dev.to/aws-builders/what-we-did-to-survive-and-succeed-in-the-aws-summit-ai-dlc-hackathon-1bf9</link>
      <guid>https://dev.to/aws-builders/what-we-did-to-survive-and-succeed-in-the-aws-summit-ai-dlc-hackathon-1bf9</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;Hello everyone!&lt;/p&gt;

&lt;p&gt;I recently participated in the &lt;strong&gt;AWS Summit Japan 2026 AI-DLC Hackathon&lt;/strong&gt;, which was held alongside &lt;strong&gt;AWS Summit Japan 2026&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The theme of this hackathon was extremely challenging: build a service that “makes people useless,” while following the &lt;strong&gt;AI-DLC&lt;/strong&gt; approach!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://pages.awscloud.com/summit-japan-2026-hackathon-reg.html" rel="noopener noreferrer"&gt;https://pages.awscloud.com/summit-japan-2026-hackathon-reg.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It was a highly competitive event with 117 teams entering, but in the end, we were able to win 3rd place!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://x.com/haruki_web3/status/2070433400918553035" rel="noopener noreferrer"&gt;https://x.com/haruki_web3/status/2070433400918553035&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Product GitHub Repository
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/mashharuki/AWS-SummitHackathon-2026" rel="noopener noreferrer"&gt;https://github.com/mashharuki/AWS-SummitHackathon-2026&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this technical blog post, I would like to share the things we did to make the most of &lt;strong&gt;AI-DLC&lt;/strong&gt; and maximize its benefits throughout the hackathon.&lt;/p&gt;

&lt;h1&gt;
  
  
  What Is AI-DLC?
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Overview of AI-DLC
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;AI-DLC&lt;/strong&gt; stands for &lt;strong&gt;AI-Driven Development Lifecycle&lt;/strong&gt;, a new development methodology proposed by AWS for the AI era.&lt;/p&gt;

&lt;p&gt;It is a software development approach that restructures the entire development lifecycle around a division of roles where &lt;strong&gt;AI executes and humans supervise&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The details are explained in the following article and GitHub repository, so I will only briefly summarize the concept here.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://zenn.dev/aws_japan/articles/aidlc-workflows" rel="noopener noreferrer"&gt;https://zenn.dev/aws_japan/articles/aidlc-workflows&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/awslabs/aidlc-workflows/tree/main" rel="noopener noreferrer"&gt;https://github.com/awslabs/aidlc-workflows/tree/main&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The AI-DLC workflow is broadly divided into three phases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Inception phase&lt;/strong&gt;, which covers requirements definition and design&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Construction phase&lt;/strong&gt;, which covers implementation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Operation phase&lt;/strong&gt;, which covers operations and deployment&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How Is It Different from cc-sdd?
&lt;/h2&gt;

&lt;p&gt;Another well-known approach for practicing spec-driven development is &lt;strong&gt;cc-sdd&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/gotalab/cc-sdd" rel="noopener noreferrer"&gt;https://github.com/gotalab/cc-sdd&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At a high level, the overall flow of cc-sdd and AI-DLC is similar.&lt;/p&gt;

&lt;p&gt;Both generally follow the process of requirements definition → design → implementation, with differences in the finer details.&lt;/p&gt;

&lt;p&gt;After actually using AI-DLC extensively, I felt that &lt;strong&gt;AI-DLC is designed with practical enterprise use cases very much in mind&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The instructions given to AI and summaries of the outputs generated as a result are recorded in great detail in a file called &lt;code&gt;audit.md&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/mashharuki/AWS-SummitHackathon-2026/blob/main/aidlc-docs/audit.md" rel="noopener noreferrer"&gt;https://github.com/mashharuki/AWS-SummitHackathon-2026/blob/main/aidlc-docs/audit.md&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This makes it possible to look back later and understand who collaborated with AI, at what timing, and based on what kind of thinking during the development of the product. I found this aspect extremely compelling.&lt;/p&gt;

&lt;p&gt;This &lt;code&gt;audit.md&lt;/code&gt; file is something cc-sdd does not provide by default. I also felt that the volume and richness of the documents generated during the Inception phase, such as requirements and design documents, were far more comprehensive compared to cc-sdd.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/mashharuki/AWS-SummitHackathon-2026/tree/main/aidlc-docs" rel="noopener noreferrer"&gt;https://github.com/mashharuki/AWS-SummitHackathon-2026/tree/main/aidlc-docs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Another difference is the Operation phase. AI-DLC generates documents that even cover the deployment of the product you build.&lt;/p&gt;

&lt;p&gt;For example, when using AWS CDK, it carefully summarizes the CDK stack for the product, setup procedures, and deployment commands.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/mashharuki/AWS-SummitHackathon-2026/blob/main/aidlc-docs/operations/deploy-e2e-verification.md" rel="noopener noreferrer"&gt;https://github.com/mashharuki/AWS-SummitHackathon-2026/blob/main/aidlc-docs/operations/deploy-e2e-verification.md&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can do something similar with cc-sdd as well, but in that case, you need to explicitly instruct the AI to do so.&lt;/p&gt;

&lt;h1&gt;
  
  
  What We Did in the AI-DLC Hackathon
&lt;/h1&gt;

&lt;p&gt;The standard features of AI-DLC are as described above.&lt;/p&gt;

&lt;p&gt;From here, I will summarize the specific things we did during the hackathon.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Quality of Requirements Documents and Shared Goal Alignment Are Crucial
&lt;/h2&gt;

&lt;p&gt;The first thing our team focused on was repeatedly running the Inception phase and aligning the goal image among team members.&lt;/p&gt;

&lt;p&gt;Unlike cc-sdd, one of the strengths of AI-DLC is that it generates a rich set of documents during the requirements definition and design phase, making it easy for the team to review them together.&lt;/p&gt;

&lt;p&gt;Before advancing to the first screening round, we repeated the process of creating, reviewing, and updating the documents more than four or five times, thoroughly improving their quality toward our final goal.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating Subagents and Skills for AI-DLC
&lt;/h3&gt;

&lt;p&gt;To practice AI-DLC effectively, we prepared and tested subagents and Skills in advance.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/mashharuki/AWS-SummitHackathon-2026/blob/main/.claude/agents/aidlc-specialist.md" rel="noopener noreferrer"&gt;https://github.com/mashharuki/AWS-SummitHackathon-2026/blob/main/.claude/agents/aidlc-specialist.md&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/mashharuki/AWS-SummitHackathon-2026/tree/main/.claude/skills/aidlc-workflow-guide" rel="noopener noreferrer"&gt;https://github.com/mashharuki/AWS-SummitHackathon-2026/tree/main/.claude/skills/aidlc-workflow-guide&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We also created a dedicated review Skill that summarized the hackathon overview and judging criteria, as well as a strategy Skill for optimizing the product specifically for the hackathon.&lt;/p&gt;

&lt;p&gt;These helped us improve the quality of the outputs during the Inception phase.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/mashharuki/AWS-SummitHackathon-2026/tree/main/.claude/skills/aws-summit-hackathon-reviewer" rel="noopener noreferrer"&gt;https://github.com/mashharuki/AWS-SummitHackathon-2026/tree/main/.claude/skills/aws-summit-hackathon-reviewer&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/mashharuki/AWS-SummitHackathon-2026/tree/main/.claude/skills/hackathon-strategist" rel="noopener noreferrer"&gt;https://github.com/mashharuki/AWS-SummitHackathon-2026/tree/main/.claude/skills/hackathon-strategist&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Environment Setup Matters!
&lt;/h2&gt;

&lt;p&gt;The second thing we did was manually set up the development environment in advance.&lt;/p&gt;

&lt;p&gt;Specifically, we prepared the pnpm monorepo project setup and implemented the minimum base structure for the frontend, backend, and CDK projects before running the AI-DLC implementation phase.&lt;/p&gt;

&lt;p&gt;Environment setup places a very heavy burden on AI, so we handled that part ourselves on the human side and let the AI focus on logic design and implementation.&lt;/p&gt;

&lt;p&gt;We also thoroughly prepared Agent Skills for the technology stack we planned to use and expanded our subagents before moving into implementation.&lt;/p&gt;

&lt;p&gt;We kept the number of MCPs to a minimum and focused mainly on using &lt;strong&gt;AWS MCP Server&lt;/strong&gt;, &lt;strong&gt;Serena MCP&lt;/strong&gt;, and &lt;strong&gt;CodeGraph MCP&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/mashharuki/AWS-SummitHackathon-2026/blob/main/.claude/.mcp.json" rel="noopener noreferrer"&gt;https://github.com/mashharuki/AWS-SummitHackathon-2026/blob/main/.claude/.mcp.json&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As a result, this preparation allowed us to steadily develop the product toward our goal, even under an extremely busy schedule.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Lean to Mathematically Ensure Program Safety
&lt;/h2&gt;

&lt;p&gt;The final thing we did was incorporate a &lt;strong&gt;formal verification phase using Lean&lt;/strong&gt; into AI-DLC in order to mathematically ensure the safety of the logic we planned to implement.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/leanprover/lean4" rel="noopener noreferrer"&gt;https://github.com/leanprover/lean4&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Lean is a programming language developed for &lt;strong&gt;theorem proving&lt;/strong&gt;, which is one of several approaches used to achieve formal verification.&lt;/p&gt;

&lt;p&gt;I believe many people practice standard test-driven development together with spec-driven development, including AI-DLC.&lt;/p&gt;

&lt;p&gt;We decided to go one step further and integrate formal verification checks into that process.&lt;/p&gt;

&lt;p&gt;Unlike tests, formal verification proves the absence of bugs using mathematical formulas, allowing exhaustive verification. This is one of its major characteristics.&lt;/p&gt;

&lt;p&gt;In fact, some source code auditing companies use formal verification in their work.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.certik.com/" rel="noopener noreferrer"&gt;https://www.certik.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our product was designed to handle privacy-sensitive information such as Slack chat histories, account names, and email addresses.&lt;/p&gt;

&lt;p&gt;Because of that, we planned to implement encryption logic to protect this information.&lt;/p&gt;

&lt;p&gt;However, there was a potential bug where the output values of the hash function used for encryption could match under certain conditions. We were able to &lt;strong&gt;discover this issue in advance through formal verification using Lean&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;By incorporating a formal verification phase, I believe &lt;strong&gt;AI-DLC became an even more robust development methodology&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Of course, we also thoroughly researched formal verification and Lean in advance, and prepared subagents and Skills for them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/mashharuki/AWS-SummitHackathon-2026/tree/main/.agents/skills/lean-formal-verification/agents" rel="noopener noreferrer"&gt;https://github.com/mashharuki/AWS-SummitHackathon-2026/tree/main/.agents/skills/lean-formal-verification/agents&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/mashharuki/AWS-SummitHackathon-2026/blob/main/.agents/skills/lean-formal-verification/SKILL.md" rel="noopener noreferrer"&gt;https://github.com/mashharuki/AWS-SummitHackathon-2026/blob/main/.agents/skills/lean-formal-verification/SKILL.md&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;That concludes my summary of &lt;strong&gt;what we did to survive and succeed in the AI-DLC hackathon&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;There are many different approaches for achieving goals with AI, such as cc-sdd, plan mode, and the &lt;code&gt;/goal&lt;/code&gt; command. Among them, I feel that &lt;strong&gt;AI-DLC was proposed especially with practical enterprise use in mind&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;After using it extensively in this hackathon, I would like to actively consider applying it in real-world development projects as well.&lt;/p&gt;

&lt;h1&gt;
  
  
  Final Thoughts
&lt;/h1&gt;

&lt;p&gt;I am truly grateful to my teammates who fought alongside me, Katryo-san for mentoring us, and everyone from the hackathon organizing team.&lt;/p&gt;

&lt;p&gt;I sincerely appreciate the opportunity to pitch on such a major stage as AWS Summit Japan and to stand on stage as a finalist.&lt;/p&gt;

&lt;p&gt;I will continue taking on new challenges together with AI-DLC!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>aws</category>
      <category>aidlc</category>
      <category>hackathon</category>
    </item>
    <item>
      <title>Monetizing AI Agents with x402, CloudFront, and Lambda@Edge</title>
      <dc:creator>Haruki Kondo</dc:creator>
      <pubDate>Mon, 11 May 2026 14:45:39 +0000</pubDate>
      <link>https://dev.to/aws-builders/monetizing-ai-agents-with-x402-cloudfront-and-lambdaedge-39hf</link>
      <guid>https://dev.to/aws-builders/monetizing-ai-agents-with-x402-cloudfront-and-lambdaedge-39hf</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Recently, have you been hearing the term &lt;strong&gt;x402&lt;/strong&gt; more often?&lt;/p&gt;

&lt;p&gt;As payments between AI Agents become a reality, &lt;strong&gt;x402&lt;/strong&gt; is attracting attention as a micropayment method.&lt;/p&gt;

&lt;p&gt;While there are several ways to "x402-ify" existing APIs, AWS has come up with a very cool architectural proposal. This time, I've used it to create an AI Agent with a chat UI!&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://builder.aws.com/content/38fLQk6zKRfLnaUNzcLPsUexUlZ/monetize-any-http-application-with-x402-and-cloudfront-lambdaedge" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fbuilder.aws.com%2Fassets%2Fog-hiXAX-on.png" height="420" class="m-0" width="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://builder.aws.com/content/38fLQk6zKRfLnaUNzcLPsUexUlZ/monetize-any-http-application-with-x402-and-cloudfront-lambdaedge" rel="noopener noreferrer" class="c-link"&gt;
            AWS Builder Center
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Connect with builders who understand your journey. Share solutions, influence AWS product development, and access useful content that accelerates your growth. Your community starts here.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fbuilder.aws.com%2Fassets%2Fbuilder-favicon-sq0js-4n.svg" width="16" height="16"&gt;
          builder.aws.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;I've also explained the tech stack, so please read until the end!&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Overview
&lt;/h3&gt;

&lt;p&gt;This is a sample implementation that monetizes HTTP requests with micropayments using AWS CloudFront + Lambda@Edge and the &lt;strong&gt;x402 protocol&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Specifically, it's an app that integrates &lt;strong&gt;Strands Agent × AgentCore Gateway (MCP)&lt;/strong&gt;, where an &lt;strong&gt;AI agent autonomously pays in USDC while fetching content&lt;/strong&gt;!&lt;/p&gt;

&lt;p&gt;It supports both &lt;strong&gt;Base Sepolia (EVM)&lt;/strong&gt; and &lt;strong&gt;Solana Devnet&lt;/strong&gt;, and clients can pay on either network.&lt;/p&gt;

&lt;h3&gt;
  
  
  Slide Summary of the App
&lt;/h3&gt;

&lt;p&gt;If you want to know just the main points, please see the slides below!&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://speakerdeck.com/mashharuki/building-ai-agents-on-solana-mastra-framework-wohuo-yong-sitaci-shi-dai-ezientokai-fa" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ffiles.speakerdeck.com%2Fpresentations%2F5af3d0fb0cab4f57a7a00ee706f7073f%2Fslide_0.jpg%3F39311305" height="1080" class="m-0" width="1920"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://speakerdeck.com/mashharuki/building-ai-agents-on-solana-mastra-framework-wohuo-yong-sitaci-shi-dai-ezientokai-fa" rel="noopener noreferrer" class="c-link"&gt;
            Building AI Agents on Solana〜Mastra Framework を活用した次世代エージェント開発〜 - Speaker Deck
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Solana Bootcamp 2026 day3 にて登壇した際の資料になります。

自分のセクションではSolana上で動くAI Agentの実装方法と選定技術スタックについてお話しさせていただきました。

Solana Bootcamp 2026 day3のイベントページ
https:/&amp;amp;hellip;
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fd1eu30co0ohy4w.cloudfront.net%2Fassets%2Ffavicon-bdd5839d46040a50edf189174e6f7aacc8abb3aaecd56a4711cf00d820883f47.png" width="512" height="512"&gt;
          speakerdeck.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;h3&gt;
  
  
  Screenshots
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv8b38oey82226j4r7509.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv8b38oey82226j4r7509.png" width="800" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn5cuivtvi671ghcbw1c0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn5cuivtvi671ghcbw1c0.png" width="800" height="926"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq7n8azoqlv8qa7be6tr1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq7n8azoqlv8qa7be6tr1.png" width="800" height="929"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Big Picture
&lt;/h3&gt;

&lt;p&gt;The app I created is composed of &lt;strong&gt;5&lt;/strong&gt; layers as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────────────────────────────────────────────────────────────┐
│  [FrontendStack]  CloudFront (S3)                                │
│  React "Neon Noir Payment Terminal" UI                           │
│  ・Left pane: AI Chat  ・Right pane: Payment Ledger               │
└────────────────────────┬─────────────────────────────────────────┘
                         │ POST /invoke
┌────────────────────────▼─────────────────────────────────────────┐
│  [StrandsAgentStack]  API GW → Strands Agent Lambda (Python)     │
│  ・Interpret user natural language with Bedrock claude-3-5-sonnet │
│  ・Call MCP tools to fetch content                                 │
└────────────────────────┬─────────────────────────────────────────┘
                         │ MCP protocol (HTTP/Streamable)
                         │ AWS IAM Authentication
┌────────────────────────▼─────────────────────────────────────────┐
│  [AgentCoreGatewayStack]  AgentCore Gateway (MCP Server)         │
│  ・Expose Payment Proxy API as MCP tools                          │
│  ・getHelloContent / getPremiumData / getArticleContent          │
└────────────────────────┬─────────────────────────────────────────┘
                         │ HTTP call
┌────────────────────────▼─────────────────────────────────────────┐
│  [PaymentProxyStack]  API GW → Payment Proxy Lambda (TypeScript) │
│  ・Internally handle x402 flow (Recv 402 → Sign with @x402/fetch  │
│    → Retry)                                                      │
│  ・Fetch EVM private key from SecretsManager                     │
└────────────────────────┬─────────────────────────────────────────┘
                         │ HTTPS + X-Payment-Token header
┌────────────────────────▼─────────────────────────────────────────┐
│  [CdkStack]  CloudFront → Lambda@Edge → API GW → Lambda          │
│  ・origin-request: Token verification / Return 402               │
│  ・origin-response: Payment settlement (Only on origin success)  │
└──────────────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The responsibilities of each stack are as follows:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stack&lt;/th&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SecretsStack&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;lib/secrets-stack.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Manage EVM / Solana private keys in SecretsManager&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CdkStack&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;lib/cdk-stack.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;CloudFront + Lambda@Edge (x402 Edge Gateway)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PaymentProxyStack&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;lib/payment-proxy-stack.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;x402 Auto-payment Proxy Lambda + API GW&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;AgentCoreGatewayStack&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;lib/agent-core-gateway-stack.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;AgentCore Gateway (MCP Server)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;StrandsAgentStack&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;lib/strands-agent-stack.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Strands Agent Lambda (Python) + API GW&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;FrontendStack&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;lib/frontend-stack.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;CloudFront + S3 delivery of React UI&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Sequence Diagrams for Each Feature
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1. x402 Basic Flow — Unpaid → 402
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fpako%3AeNptUl2P0kAU_Ss388QmpbRQBBpD1C7dxLirAkZjeJltL2VCO7dOp-si4dUf4E_0lzj9oERlniZzzrn33DP3yCKKkfmswG8lyghvBU8UzzYSzMm50iISOZcaAuAFBKlAqa-AYYNSGYeKrjHeLz9WlHc8e4z5q0Wc4MtHNZj3SIlEyL6q2hf65n9h-Lru_Ow5Qwh5JFKhuSbVqKtXm1RidI0y6M_nQejD3WINA56LwQ7TlFosNKDx4cPfTQGfuqEMfCYFO4z28KX_gR8yg_fXtEcJO-Qxqob8QBqBnlBBLXgg0DXn989fcIcSFTe4cXgp3bqrZmnLwtKYEArjeqBLtyUWOckCfTjmSkRogUT9ndTeMukc1mSBbdunbrCq8PW6_1o1tGU7d95St4oy2F6yvURpwve7iue4ep9WtwGQhDe8QFhhTqng7c8ZQevlrGpi6739vL5hFstQZVzEZuGOlWDD9A4z3DDfXGOu9hu2kSfDK_PYhLeIhbHD_C1PC7QYLzWtDjJivlYlnkntxnYsrEX3zVrX220xRWWy6xhmr74SZecyiaoMNXeF0nxvQKXUzPfGNZf5R_bMfNcd2e50MnRm49nQ9TzXs9jBPE8cezxyPG_mzkauM30xOVnsR13esaeT8ekPQZkWBA%3Ftype%3Dpng" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fpako%3AeNptUl2P0kAU_Ss388QmpbRQBBpD1C7dxLirAkZjeJltL2VCO7dOp-si4dUf4E_0lzj9oERlniZzzrn33DP3yCKKkfmswG8lyghvBU8UzzYSzMm50iISOZcaAuAFBKlAqa-AYYNSGYeKrjHeLz9WlHc8e4z5q0Wc4MtHNZj3SIlEyL6q2hf65n9h-Lru_Ow5Qwh5JFKhuSbVqKtXm1RidI0y6M_nQejD3WINA56LwQ7TlFosNKDx4cPfTQGfuqEMfCYFO4z28KX_gR8yg_fXtEcJO-Qxqob8QBqBnlBBLXgg0DXn989fcIcSFTe4cXgp3bqrZmnLwtKYEArjeqBLtyUWOckCfTjmSkRogUT9ndTeMukc1mSBbdunbrCq8PW6_1o1tGU7d95St4oy2F6yvURpwve7iue4ep9WtwGQhDe8QFhhTqng7c8ZQevlrGpi6739vL5hFstQZVzEZuGOlWDD9A4z3DDfXGOu9hu2kSfDK_PYhLeIhbHD_C1PC7QYLzWtDjJivlYlnkntxnYsrEX3zVrX220xRWWy6xhmr74SZecyiaoMNXeF0nxvQKXUzPfGNZf5R_bMfNcd2e50MnRm49nQ9TzXs9jBPE8cezxyPG_mzkauM30xOVnsR13esaeT8ekPQZkWBA%3Ftype%3Dpng" width="1103" height="635"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  2. x402 Basic Flow — Paid → Success
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fpako%3AeNp9k11v2jAUhv-K5atW4yOBdEBUoaFAULVWdKTapokbNzkNXhM7cxwoQ_z3nXwCAi1Xls_znvP6jb2nvgyA2jSFPxkIH6achYrFK0HwS5jS3OcJE5o4hKXEiTgIfaXollWZBa6S14jF8luOPLL4NWBfZkEI96-qO76RiodctFU-PtW3l0J3Ukz-sIwecZnPI66ZluoSnDw_zH_kKC7InGnYst01H97_faSJFClcMTKdPS2O0lI1hViSRSFFRalx2uOx49pkPnshXZbw7hqiSBb8z_Yz28WYYPtFvoOwyf3vrR5XMhd1mJJNziMhsGkixzJCmIhNigZkA4q_cZ9pLgWpBCWKULtu-P2UWnw99qqculJtmQrqBuRmEgSkdurxUDCdKSBrYAGo2xO7ReSNviwUe1jL47LJg9ig0bKS77SPqp5hoBnyiTh4YZojlvLKWImc5eOd5FP-qvOAvDqgyj9JQesI4gZpgsFOXlM7icWrpy_rAUUex3-3BB94oi_yyGWXx6ItGoOKGQ_wle1zeEX1GoeuqI3LgKn3FV2JA3JZEuC1nQUc7ze131iUQouyTEtvJ3xqa5VBDVXPtKGgED2Vb7l40i2qZBauGwLv8C8p47pNqHJD5VqBwIM4MkO3tjUqWGrv6Qe1TbPfMYeDnjG6G_VMyzKtFt3h9sDo3PUNyxqZo75pDD8PDi36t2hvdIaDO-PkMw__AFO3YcA%3Ftype%3Dpng" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fpako%3AeNp9k11v2jAUhv-K5atW4yOBdEBUoaFAULVWdKTapokbNzkNXhM7cxwoQ_z3nXwCAi1Xls_znvP6jb2nvgyA2jSFPxkIH6achYrFK0HwS5jS3OcJE5o4hKXEiTgIfaXollWZBa6S14jF8luOPLL4NWBfZkEI96-qO76RiodctFU-PtW3l0J3Ukz-sIwecZnPI66ZluoSnDw_zH_kKC7InGnYst01H97_faSJFClcMTKdPS2O0lI1hViSRSFFRalx2uOx49pkPnshXZbw7hqiSBb8z_Yz28WYYPtFvoOwyf3vrR5XMhd1mJJNziMhsGkixzJCmIhNigZkA4q_cZ9pLgWpBCWKULtu-P2UWnw99qqculJtmQrqBuRmEgSkdurxUDCdKSBrYAGo2xO7ReSNviwUe1jL47LJg9ig0bKS77SPqp5hoBnyiTh4YZojlvLKWImc5eOd5FP-qvOAvDqgyj9JQesI4gZpgsFOXlM7icWrpy_rAUUex3-3BB94oi_yyGWXx6ItGoOKGQ_wle1zeEX1GoeuqI3LgKn3FV2JA3JZEuC1nQUc7ze131iUQouyTEtvJ3xqa5VBDVXPtKGgED2Vb7l40i2qZBauGwLv8C8p47pNqHJD5VqBwIM4MkO3tjUqWGrv6Qe1TbPfMYeDnjG6G_VMyzKtFt3h9sDo3PUNyxqZo75pDD8PDi36t2hvdIaDO-PkMw__AFO3YcA%3Ftype%3Dpng" width="1713" height="819"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  3. AI Agent Flow — End-to-End
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fpako%3AeNqNVGtv0zAU_StXFh-G6CPdOrZF00TJ1qoaZRFhDFAkZOJLFzWxg-NsK9P-O9ePVgM2iX5K7XPOfZx7fc8KJZDFrMWfHcoCT0u-1LzOJdCv4dqURdlwaeByDryFD8gL-338XQ9PdqZaSYNSZIYXq5f_crKJ5WRGcylamCxRGk8MR-7kOfLsypIdJFEaYcYN3vK1F1gkKWSob1A_wUxTy0z5ukb7T6u7wApH7uS5sMnUkpNKdcKVB6_AkeEdr78L_uZMLNGrJWL1bOELVzgWGk274JIvUYfK_dmG6KmX8_7JSTaJIb3IPsKwlDdq5YPc56zGtiV-zmLI2RRNcQ2NxrrsahDc8Jw9eJFsEkTm5IkmiIHSumPgtjTX8BaFVsUKdoqKdwL7rZISzctH3NlVDLavRqkKCl5VMSyRmuVinVIoX8HkKoP5ZAFtuZQogsDsigTSNIbZGRXQ2AYPQ5YekKY2uwUBKK-zTwuqobwhR2GFa9iZlro1YMoaQclqvclq0Q-q0d0X1enUU85x_UgzmYagvCk3IYe2MbDzXpEpzvEgmEw3guNodzsgH2j0S43ikairBCVqm6Ev1EpVigvfzTd3pDD8Yd34n1xc5z73Q8T-R7JXxnDslU_-Tm43iuDinAYvcetlthGCSU_fkwVhALYmamy7yvw1Htu66LZRssU_5mMLJvTlPAYawA3OT2CYB7ADEcNgMMhZz29IzmzQb11LrXTQP6eHcH6RchZcIagoPDQaRKPtJLt1sMEnTUOvC7ywt3CZnSZUlo8VFKBCWkfNerQluualoJfs3opQLtdYh5QF16uc5fKBcF1DfuCZKI3SLP7BqxZ7jHdGZWtZsNjoDjeg8BRuUehIC_9eumezx7TqltdbBO3-V6XqjcxS24T8t6ZCUCeqk4bF-3sOy-J7dsfi0WhvMDo82I2O9o92R-PxaNxjazo-iAb7e9F4fDQ62htFh68PHnrsl5OPBocH-w-_AbMY1FQ%3Ftype%3Dpng" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fpako%3AeNqNVGtv0zAU_StXFh-G6CPdOrZF00TJ1qoaZRFhDFAkZOJLFzWxg-NsK9P-O9ePVgM2iX5K7XPOfZx7fc8KJZDFrMWfHcoCT0u-1LzOJdCv4dqURdlwaeByDryFD8gL-338XQ9PdqZaSYNSZIYXq5f_crKJ5WRGcylamCxRGk8MR-7kOfLsypIdJFEaYcYN3vK1F1gkKWSob1A_wUxTy0z5ukb7T6u7wApH7uS5sMnUkpNKdcKVB6_AkeEdr78L_uZMLNGrJWL1bOELVzgWGk274JIvUYfK_dmG6KmX8_7JSTaJIb3IPsKwlDdq5YPc56zGtiV-zmLI2RRNcQ2NxrrsahDc8Jw9eJFsEkTm5IkmiIHSumPgtjTX8BaFVsUKdoqKdwL7rZISzctH3NlVDLavRqkKCl5VMSyRmuVinVIoX8HkKoP5ZAFtuZQogsDsigTSNIbZGRXQ2AYPQ5YekKY2uwUBKK-zTwuqobwhR2GFa9iZlro1YMoaQclqvclq0Q-q0d0X1enUU85x_UgzmYagvCk3IYe2MbDzXpEpzvEgmEw3guNodzsgH2j0S43ikairBCVqm6Ev1EpVigvfzTd3pDD8Yd34n1xc5z73Q8T-R7JXxnDslU_-Tm43iuDinAYvcetlthGCSU_fkwVhALYmamy7yvw1Htu66LZRssU_5mMLJvTlPAYawA3OT2CYB7ADEcNgMMhZz29IzmzQb11LrXTQP6eHcH6RchZcIagoPDQaRKPtJLt1sMEnTUOvC7ywt3CZnSZUlo8VFKBCWkfNerQluualoJfs3opQLtdYh5QF16uc5fKBcF1DfuCZKI3SLP7BqxZ7jHdGZWtZsNjoDjeg8BRuUehIC_9eumezx7TqltdbBO3-V6XqjcxS24T8t6ZCUCeqk4bF-3sOy-J7dsfi0WhvMDo82I2O9o92R-PxaNxjazo-iAb7e9F4fDQ62htFh68PHnrsl5OPBocH-w-_AbMY1FQ%3Ftype%3Dpng" width="1553" height="1128"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  4. On Origin Error (No Charge)
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fpako%3AeNp9U11v2jAU_StXfmolPhIIA6wObQIyTWvL1lTTNPHiJrfBI7GzG4eWIV73A_YT90vmfECZhuYny_ece865tncs1BEyznL8XqAKcSZFTCJdKrArE2RkKDOhDExB5DBNJCpzpujXVV1EPulziMXdpxJyLdKHSLyZRzFePVB3cqFJxlK1qZTPzeW_RP9tpfzsOT3wRSgTaYTRdE4h-L9CnmmV4xmJ2fxm8UKtWTNMNSwqqmXUnGl7Mpn6HN7N76ErMtldYZLoCv-l_VFsUzub9r1eo-Jw9e3JTBqab3k2P4e_wwJujsO0ZQuyWTlUDWCDJB9lKIzUjbottg-NPp9UYfHhpUfj0Nf0JCiCRunER5mVw3u1sSr1cXly4A0cx9YMkhIJBEjWBcyJDuNukgQnSeqhnka51QZBl8wKmdvrKnKYvAbPNv_98xcEa5lBjsYkmJ4MIDiYuENTkKq8HPvb25H50UQJrM1e3GoIV4JivGQtliKlQkb2Oe9K7JKZlZVYMm63kaD1ki3V3uKKLBIG55G0D4nxR5Hk2GKiMDrYqpBxQwUeQM1_OKKwIt3Un6b6Oy1GuohXR4R9Ul-1Tg9tYioN1XtCFSFNdaEM44NhhWV8x54Zd91-xx0Ne854MO65nud6Lba1x0OnM-g7njd2x33XGb0a7lvsR9Xe6YyGg_0f5lIyzA%3Ftype%3Dpng" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fpako%3AeNp9U11v2jAU_StXfmolPhIIA6wObQIyTWvL1lTTNPHiJrfBI7GzG4eWIV73A_YT90vmfECZhuYny_ece865tncs1BEyznL8XqAKcSZFTCJdKrArE2RkKDOhDExB5DBNJCpzpujXVV1EPulziMXdpxJyLdKHSLyZRzFePVB3cqFJxlK1qZTPzeW_RP9tpfzsOT3wRSgTaYTRdE4h-L9CnmmV4xmJ2fxm8UKtWTNMNSwqqmXUnGl7Mpn6HN7N76ErMtldYZLoCv-l_VFsUzub9r1eo-Jw9e3JTBqab3k2P4e_wwJujsO0ZQuyWTlUDWCDJB9lKIzUjbottg-NPp9UYfHhpUfj0Nf0JCiCRunER5mVw3u1sSr1cXly4A0cx9YMkhIJBEjWBcyJDuNukgQnSeqhnka51QZBl8wKmdvrKnKYvAbPNv_98xcEa5lBjsYkmJ4MIDiYuENTkKq8HPvb25H50UQJrM1e3GoIV4JivGQtliKlQkb2Oe9K7JKZlZVYMm63kaD1ki3V3uKKLBIG55G0D4nxR5Hk2GKiMDrYqpBxQwUeQM1_OKKwIt3Un6b6Oy1GuohXR4R9Ul-1Tg9tYioN1XtCFSFNdaEM44NhhWV8x54Zd91-xx0Ne854MO65nud6Lba1x0OnM-g7njd2x33XGb0a7lvsR9Xe6YyGg_0f5lIyzA%3Ftype%3Dpng" width="1285" height="688"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  System Architecture Diagram
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv43llhhc3y4diph8f6x5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv43llhhc3y4diph8f6x5.png" width="800" height="442"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Implementation Points
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Two Lambda Functions to x402-ify Any Origin
&lt;/h3&gt;

&lt;h4&gt;
  
  
  lambda-edge
&lt;/h4&gt;

&lt;p&gt;The &lt;strong&gt;lambda@Edge&lt;/strong&gt; function is the key component here.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/mashharuki/x402-Cloudfront-LambdaEdge-Sample/tree/main/cdk/functions/lambda-edge" rel="noopener noreferrer"&gt;lambda-edge functions on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It stands between the origin and the client, handling all x402-related logic.&lt;br&gt;
In this case, we've set how much stablecoin payment to request for each path accessed on the origin.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// cdk/functions/lambda-edge/config.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;RoutesConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@x402/core/server&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Replaced at build time via esbuild&lt;/span&gt;
&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;__PAY_TO_ADDRESS__&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;__SVM_PAY_TO_ADDRESS__&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;__X402_NETWORK__&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;__SOLANA_NETWORK__&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;__FACILITATOR_URL__&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;FACILITATOR_URL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;__FACILITATOR_URL__&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PAY_TO&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;__PAY_TO_ADDRESS__&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SVM_PAY_TO&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;__SVM_PAY_TO_ADDRESS__&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;NETWORK&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;__X402_NETWORK__&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SOLANA_NETWORK&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;__SOLANA_NETWORK__&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Route configuration — which paths require payment and at what price.&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ROUTES&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RoutesConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;accepts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;scheme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;exact&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;network&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NETWORK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;payTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PAY_TO&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$0.001&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;scheme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;exact&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;network&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SOLANA_NETWORK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;payTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SVM_PAY_TO&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$0.001&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;API access ($0.001 USDC)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/premium/**&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;accepts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;scheme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;exact&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;network&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NETWORK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;payTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PAY_TO&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$0.01&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;scheme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;exact&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;network&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SOLANA_NETWORK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;payTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SVM_PAY_TO&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$0.01&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Premium API access ($0.01 USDC)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/content/**&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;accepts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;scheme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;exact&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;network&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NETWORK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;payTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PAY_TO&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$0.005&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;scheme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;exact&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;network&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SOLANA_NETWORK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;payTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SVM_PAY_TO&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$0.005&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Premium content ($0.005 USDC)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By handling x402 requests/responses here, the origin side doesn't need to be aware of x402 at all.&lt;/p&gt;

&lt;h4&gt;
  
  
  payment-proxy
&lt;/h4&gt;

&lt;p&gt;This is the Lambda function for the x402 automatic payment proxy.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// cdk/functions/payment-proxy/index.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;GetSecretValueCommand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SecretsManagerClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-sdk/client-secrets-manager&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createKeyPairSignerFromBytes&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@solana/kit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;x402Client&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@x402/core/client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;wrapFetchWithPayment&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@x402/fetch&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ExactSvmScheme&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@x402/svm/exact/client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;APIGatewayProxyEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;APIGatewayProxyResult&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aws-lambda&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;bs58&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bs58&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CLOUDFRONT_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CLOUDFRONT_URL&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SVM_SECRET_ARN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SVM_PRIVATE_KEY_SECRET_ARN&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ROUTE_MAP&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/proxy/hello&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/hello&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/proxy/premium&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/premium/data&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/proxy/article&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/content/article&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;payFetch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getPayFetch&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payFetch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;payFetch&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SecretsManagerClient&lt;/span&gt;&lt;span class="p"&gt;({});&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;svmSecret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;GetSecretValueCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;SecretId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SVM_SECRET_ARN&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;svmSecret&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SecretString&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Solana private key secret is empty&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;svmSigner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createKeyPairSignerFromBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bs58&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;svmSecret&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SecretString&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;x402Client&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;solana:*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ExactSvmScheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;svmSigner&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="nx"&gt;payFetch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;wrapFetchWithPayment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;payFetch&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;APIGatewayProxyEvent&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;APIGatewayProxyResult&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;proxyPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;targetPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ROUTE_MAP&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;proxyPath&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;targetPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Unknown proxy path: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;proxyPath&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fetchFn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getPayFetch&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchFn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;CLOUDFRONT_URL&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;targetPath&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Payment proxy request failed:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  MCP Server
&lt;/h3&gt;

&lt;p&gt;In &lt;strong&gt;AgentCore Gateway&lt;/strong&gt;, you can "MCP-ify" your APIs.&lt;br&gt;
What you need for that is an OpenAPI specification (YAML).&lt;/p&gt;

&lt;p&gt;We'll turn the above proxy server into an MCP server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# cdk/mcp/openapi.yaml&lt;/span&gt;
&lt;span class="na"&gt;openapi&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.0.1"&lt;/span&gt;
&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;x402&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Payment&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Proxy&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;API"&lt;/span&gt;
  &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1.0.0"&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MCP&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;tools&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;accessing&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;x402-protected&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;via&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;auto-payment&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;proxy"&lt;/span&gt;
&lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;/proxy/hello&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;operationId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;getHelloContent"&lt;/span&gt;
      &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Get&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;hello&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;content"&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Get&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;hello&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;(costs&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;$0.001&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;USDC&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;on&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Base&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Sepolia).&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Payment&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;is&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;handled&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;automatically."&lt;/span&gt;
      &lt;span class="na"&gt;responses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;200"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Success"&lt;/span&gt;
          &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;application/json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;object&lt;/span&gt;
  &lt;span class="na"&gt;/proxy/premium&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;operationId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;getPremiumData"&lt;/span&gt;
      &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Get&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;premium&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;analytics&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;data"&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Get&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;premium&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;analytics&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;(costs&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;$0.01&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;USDC&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;on&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Base&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Sepolia).&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Payment&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;is&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;handled&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;automatically."&lt;/span&gt;
      &lt;span class="na"&gt;responses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;200"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Success"&lt;/span&gt;
          &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;application/json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;object&lt;/span&gt;
  &lt;span class="na"&gt;/proxy/article&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;operationId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;getArticleContent"&lt;/span&gt;
      &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Get&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;article&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;content"&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Get&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;article&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;(costs&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;$0.005&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;USDC&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;on&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Base&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Sepolia).&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Payment&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;is&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;handled&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;automatically."&lt;/span&gt;
      &lt;span class="na"&gt;responses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;200"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Success"&lt;/span&gt;
          &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;application/json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;object&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  AI Agent Implemented with Strands Agent
&lt;/h3&gt;

&lt;p&gt;This part handles the implementation of the AI Agent that accesses x402-supported content via MCP.&lt;br&gt;
It's implemented using &lt;strong&gt;Python&lt;/strong&gt; and the &lt;strong&gt;Strands Agent SDK&lt;/strong&gt;, with &lt;strong&gt;Bedrock AgentCore&lt;/strong&gt; as the execution environment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# strands_agent/lambda_function.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;mcp_proxy_for_aws.client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;aws_iam_streamablehttp_client&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BedrockModel&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands.tools.mcp&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MCPClient&lt;/span&gt;

&lt;span class="n"&gt;GATEWAY_MCP_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AGENT_CORE_GATEWAY_MCP_URL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;AWS_REGION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AWS_REGION&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;us-east-1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BedrockModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;us.anthropic.claude-3-5-sonnet-20240620-v1:0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;region_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;AWS_REGION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;mcp_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MCPClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;aws_iam_streamablehttp_client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;GATEWAY_MCP_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;aws_region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;AWS_REGION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;aws_service&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bedrock-agentcore&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;mcp_client&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;system_prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
        You are an AI assistant that can access x402-protected premium content.
&lt;/span&gt;&lt;span class="gp"&gt;        ...&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;omitted&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;brevity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="bp"&gt;...&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# ... (omitted for brevity) ...
&lt;/span&gt;    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;statusCode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;headers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;CORS_HEADERS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;session_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;response&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)}),&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;statusCode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;headers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;CORS_HEADERS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)})}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Frontend
&lt;/h3&gt;

&lt;p&gt;The frontend is implemented with &lt;strong&gt;React.js&lt;/strong&gt; and &lt;strong&gt;Vite&lt;/strong&gt;.&lt;br&gt;
The AI Agent functionality is implemented as a &lt;strong&gt;React Hook&lt;/strong&gt; to be called as an API.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// frontend/src/hooks/useAgent.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;loadConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../lib/config&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;PaymentRecord&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../types&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;useAgent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setMessages&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;([]);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;payments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setPayments&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;PaymentRecord&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;([]);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setIsLoading&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sessionId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randomUUID&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sendMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;loadConfig&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="c1"&gt;// ... (omitted for brevity) ...&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;strandsAgentApiUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/invoke`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;session_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="c1"&gt;// ... (omitted for brevity) ...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setIsLoading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;payments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sendMessage&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How to Run
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Phase A: Deploy Existing x402 Edge Gateway
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Clone the Repo&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/mashharuki/x402-Cloudfront-LambdaEdge-Sample.git
&lt;span class="nb"&gt;cd &lt;/span&gt;x402-Cloudfront-LambdaEdge-Sample
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Install Dependencies&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;cdk &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; bun &lt;span class="nb"&gt;install
&lt;/span&gt;bun &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--cwd&lt;/span&gt; functions/lambda-edge
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Batch Deploy All Stacks&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bunx cdk deploy SecretsStack
&lt;span class="c"&gt;# Set private keys in SecretsManager...&lt;/span&gt;
&lt;span class="nv"&gt;PAY_TO_ADDRESS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0xYourEVMAddress &lt;span class="nv"&gt;SVM_PAY_TO_ADDRESS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;YourSolanaAddress npx cdk deploy CdkStack PaymentProxyStack AgentCoreGatewayStack StrandsAgentStack
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Cleanup
&lt;/h3&gt;

&lt;p&gt;Don't forget to delete resources after verification!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx cdk destroy &lt;span class="nt"&gt;--all&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note: Lambda@Edge functions may need to be deleted manually after a few hours once replicas are gone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;I've tried a way to "x402-ify" any origin using &lt;strong&gt;CloudFront&lt;/strong&gt; + &lt;strong&gt;Lambda@Edge&lt;/strong&gt;!&lt;/p&gt;

&lt;p&gt;The architecture is very cool because it fully utilizes managed services while requiring almost no changes to the origin-side code where the core logic resides.&lt;/p&gt;

&lt;p&gt;While steps like blockchain knowledge, wallets, and stablecoin preparation are still necessary, it would be even more powerful if environment setup was also covered.&lt;/p&gt;

&lt;p&gt;I expect this kind of implementation to increase in the future.&lt;/p&gt;

&lt;p&gt;Thank you for reading!&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/mashharuki/x402-Cloudfront-LambdaEdge-Sample" rel="noopener noreferrer"&gt;GitHub x402-Cloudfront-LambdaEdge-Sample&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://builder.aws.com/content/38fLQk6zKRfLnaUNzcLPsUexUlZ/monetize-any-http-application-with-x402-and-cloudfront-lambdaedge" rel="noopener noreferrer"&gt;Monetize Any HTTP Application with x402 and CloudFront + Lambda@Edge&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://x402.org" rel="noopener noreferrer"&gt;x402 Protocol Specification&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://modelcontextprotocol.io/" rel="noopener noreferrer"&gt;Model Context Protocol (MCP)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>solana</category>
      <category>ai</category>
      <category>web3</category>
      <category>x402</category>
    </item>
    <item>
      <title>Building a Solana AI Agent with AWS, Mastra, and Phantom Wallet</title>
      <dc:creator>Haruki Kondo</dc:creator>
      <pubDate>Mon, 11 May 2026 14:37:45 +0000</pubDate>
      <link>https://dev.to/aws-builders/building-a-solana-ai-agent-with-aws-mastra-and-phantom-wallet-24da</link>
      <guid>https://dev.to/aws-builders/building-a-solana-ai-agent-with-aws-mastra-and-phantom-wallet-24da</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Hello everyone! Are you building AI Agents?&lt;/p&gt;

&lt;p&gt;I'm sure many of you are creating various workflows to improve business efficiency.&lt;/p&gt;

&lt;p&gt;While know-how and sample code for AI Agents are increasing, implementation examples of AI Agents that can interact with the blockchain are still relatively few, don't you think?&lt;/p&gt;

&lt;p&gt;In this article, I've explained the construction method and technical stack of an AI Agent that operates digital assets on the &lt;strong&gt;Solana&lt;/strong&gt; blockchain!&lt;/p&gt;

&lt;p&gt;I hope you find this useful as an example!&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Overview
&lt;/h3&gt;

&lt;p&gt;I created a chat application where you can perform operations on Solana DevNet (SOL transfer, NFT minting, DeFi Swap, smart contract calls) using natural language!&lt;/p&gt;

&lt;p&gt;When a user enters natural language in the chat screen, the backend Mastra AI Agent interprets the intent and calls the appropriate Solana tool (&lt;code&gt;transferSolTool&lt;/code&gt; / &lt;code&gt;mintNftTool&lt;/code&gt; / &lt;code&gt;jupiterSwapTool&lt;/code&gt;, etc.). Instead of a &lt;strong&gt;signed transaction&lt;/strong&gt;, it returns an &lt;strong&gt;unsigned serialized transaction&lt;/strong&gt; to the frontend. The user confirms, signs, and sends it using their Phantom Wallet, then passes the result back to the Agent to receive an explanation of the execution result.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Femacxbp8lyrspvlcfjeh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Femacxbp8lyrspvlcfjeh.png" width="800" height="442"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feod3jvqy0owz1do0n3cu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feod3jvqy0owz1do0n3cu.png" width="800" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl5nc5h0jupu1ptevvpbi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl5nc5h0jupu1ptevvpbi.png" width="800" height="443"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjw8cy3xzpqi42u8ckxu3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjw8cy3xzpqi42u8ckxu3.png" width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Demo Video
&lt;/h3&gt;

&lt;p&gt;Here's how the app I created works!&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/mHLzj2YTkQk"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  Feature List
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Feature Name&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Agent Tool&lt;/th&gt;
&lt;th&gt;Signature&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;SOL Balance Inquiry&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Fetches and displays the SOL balance of a wallet address&lt;/td&gt;
&lt;td&gt;&lt;code&gt;getBalanceTool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Not required&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;SOL Transfer&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Constructs a SOL transfer transaction to a specified address&lt;/td&gt;
&lt;td&gt;&lt;code&gt;transferSolTool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Signed with Phantom&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;NFT List Acquisition&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Fetches NFTs owned by the wallet using Metaplex DAS API&lt;/td&gt;
&lt;td&gt;&lt;code&gt;getNftsTool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Not required&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;NFT Minting&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Constructs an NFT minting transaction using Metaplex MPL Core&lt;/td&gt;
&lt;td&gt;&lt;code&gt;mintNftTool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Signed with Phantom&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Token Swap&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Constructs an SPL token swap TX via Jupiter v6&lt;/td&gt;
&lt;td&gt;&lt;code&gt;jupiterSwapTool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Signed with Phantom&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;DevNet Airdrop&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Airdrops SOL on DevNet only (Max 2 SOL / request)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;airdropTool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Not required&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Smart Contract Call&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Constructs a general Solana Program call TX&lt;/td&gt;
&lt;td&gt;&lt;code&gt;callProgramTool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Signed with Phantom&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Wallet Connection&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Connecting/disconnecting Phantom Wallet and network confirmation&lt;/td&gt;
&lt;td&gt;wallet-adapter&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TX Result Explanation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Explains success/failure after signing and sending in an easy-to-understand way&lt;/td&gt;
&lt;td&gt;Agent Follow-up&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Asset Panel&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;SOL balance and owned NFTs are constantly displayed in the side panel&lt;/td&gt;
&lt;td&gt;hooks (UI)&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Sequence Diagrams for Each Feature
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1. SOL Balance Inquiry
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fpako%3AeNplksGOmzAQhl9l5FMiZbOwSZqEw0pt0kPUbne1JNKq4jKBKUHFmNqmaop4946B0GjDAXnG34x__55axCohEQhDvyoqYtpmmGqUUQH8YWyVhoMh3cUlapvFWYmFhcMO0MArMcPr2_2PKfGfkSc0VmMX32J7pXJHpWQ_YY6swGVuudeXjcNCxQy20WhLv7-RHUdFRzuZd4-Ph10Au6KsLERiT3kOkkCe4dg1j0QP7xhtJQXw8hzu4T4-ob2v0aV2SQMjScZwNO74FuUSJy54J3ZUAyaJZh6aHndpplnmNTwqq2OexV_o3GO8fzc0zVGWSltz1eG_xvpygdChRSWPpKG51tZfPRKbSmtnfV8AmYE3CJ-_RgJG_BKEMivS8WCDq2PnAthmpsxxcEpMhCQtMUt4OmqHR8KeSLKF7pQE9U9nZsNcVSZo6XOS8bSI4AfmhiYCK6vCcxGLwOqKLlA_XgNFbdFTN4PtKE6EVlV6Ggh-_e9KyUubVDtB3ZqvmZDeqKqwIvCXLSuCWvzhyJ9N_dXywVsv1g_-fO7PJ-LsIG-6mHnz-dpfz3xv9WHZTMTftr03XS0XzT9mlfz9%3Ftype%3Dpng" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fpako%3AeNplksGOmzAQhl9l5FMiZbOwSZqEw0pt0kPUbne1JNKq4jKBKUHFmNqmaop4946B0GjDAXnG34x__55axCohEQhDvyoqYtpmmGqUUQH8YWyVhoMh3cUlapvFWYmFhcMO0MArMcPr2_2PKfGfkSc0VmMX32J7pXJHpWQ_YY6swGVuudeXjcNCxQy20WhLv7-RHUdFRzuZd4-Ph10Au6KsLERiT3kOkkCe4dg1j0QP7xhtJQXw8hzu4T4-ob2v0aV2SQMjScZwNO74FuUSJy54J3ZUAyaJZh6aHndpplnmNTwqq2OexV_o3GO8fzc0zVGWSltz1eG_xvpygdChRSWPpKG51tZfPRKbSmtnfV8AmYE3CJ-_RgJG_BKEMivS8WCDq2PnAthmpsxxcEpMhCQtMUt4OmqHR8KeSLKF7pQE9U9nZsNcVSZo6XOS8bSI4AfmhiYCK6vCcxGLwOqKLlA_XgNFbdFTN4PtKE6EVlV6Ggh-_e9KyUubVDtB3ZqvmZDeqKqwIvCXLSuCWvzhyJ9N_dXywVsv1g_-fO7PJ-LsIG-6mHnz-dpfz3xv9WHZTMTftr03XS0XzT9mlfz9%3Ftype%3Dpng" width="1299" height="537"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h4&gt;
  
  
  2. SOL Transfer
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fpako%3AeNqNlN1u2jAUx1_lyFdBS1PS0lJyUamlvUBaVwRUm6bcuM4hRHXizHb6hZB61xfYE24vsuMkMBi9GEKJY__OOX___bFkQiXIImbwR4WFwKuMp5rncQH048IqDXcGdfNdcm0zkZW8sHA3Am5ggsRQe3_8IkV6EnLDjdW8-d7HZkpJRxFSmDnqqZKuax-cjIeOo3Fe8PrLu8LHL2g7--x4QU-VO37d_MqlRKrfwG5KB-fnd6MIYjbFIoFuEML09jNYBb_f3ukfsxYdEViLj2B8O53BoVhwe7jkrmuUrBqqBgh02qN_J-MtYa5V7lNyH3iuqsLSCKxa5Q6hUJpSBCnaS04zFOi5kA78evsJbQ-IBYqHJobgg005yfNSaWs-TDdBQdIupRIPC24WXueDBPfr0a0Mf2e9BHIr4zJ7xWT2DN49N3ja6_iQoBE6K22mCtjxobV29g2GXCfwiUweS6QwMFlaxAxyNIYM3HK4XaeoJi6KxC3KzNlI-4vye1RrLcLbltNp59PG16VpPIKhKuaZznmtLiFepfQypeQvO3tgU_iiLLV6xN10rY-G1Ez405agHRd3xHNbadwXNaqNXI_7YCw1jNt_olGKCRmz-r9NBx55q9FU0sJcSameDqqy88ESxGzW7kUQKi_pCGAA18-lVNqZFATB1j7feHfV-NRWYD7LkZzMEronlo6OmV1gjjFzBRKuH1ySFXFVmXCL10lG9waL5lwa9BmvrJq-FIJFVle4htqLZkNhHXTT3Eb1peQzrap0sSHoaH9XKl-nSbUT1LQ1rQ_qoTtZLDoKa5ZFS_bMojA8DsKz_lF3cDI4Cnu9sOezF-rud4OT426vNwgHx2H37LS_8tlrnb4bnPVPVn8ALjqt3w%3Ftype%3Dpng" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fpako%3AeNqNlN1u2jAUx1_lyFdBS1PS0lJyUamlvUBaVwRUm6bcuM4hRHXizHb6hZB61xfYE24vsuMkMBi9GEKJY__OOX___bFkQiXIImbwR4WFwKuMp5rncQH048IqDXcGdfNdcm0zkZW8sHA3Am5ggsRQe3_8IkV6EnLDjdW8-d7HZkpJRxFSmDnqqZKuax-cjIeOo3Fe8PrLu8LHL2g7--x4QU-VO37d_MqlRKrfwG5KB-fnd6MIYjbFIoFuEML09jNYBb_f3ukfsxYdEViLj2B8O53BoVhwe7jkrmuUrBqqBgh02qN_J-MtYa5V7lNyH3iuqsLSCKxa5Q6hUJpSBCnaS04zFOi5kA78evsJbQ-IBYqHJobgg005yfNSaWs-TDdBQdIupRIPC24WXueDBPfr0a0Mf2e9BHIr4zJ7xWT2DN49N3ja6_iQoBE6K22mCtjxobV29g2GXCfwiUweS6QwMFlaxAxyNIYM3HK4XaeoJi6KxC3KzNlI-4vye1RrLcLbltNp59PG16VpPIKhKuaZznmtLiFepfQypeQvO3tgU_iiLLV6xN10rY-G1Ez405agHRd3xHNbadwXNaqNXI_7YCw1jNt_olGKCRmz-r9NBx55q9FU0sJcSameDqqy88ESxGzW7kUQKi_pCGAA18-lVNqZFATB1j7feHfV-NRWYD7LkZzMEronlo6OmV1gjjFzBRKuH1ySFXFVmXCL10lG9waL5lwa9BmvrJq-FIJFVle4htqLZkNhHXTT3Eb1peQzrap0sSHoaH9XKl-nSbUT1LQ1rQ_qoTtZLDoKa5ZFS_bMojA8DsKz_lF3cDI4Cnu9sOezF-rud4OT426vNwgHx2H37LS_8tlrnb4bnPVPVn8ALjqt3w%3Ftype%3Dpng" width="1667" height="993"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h4&gt;
  
  
  3. NFT List Acquisition
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fpako%3AeNplkk9v4jAQxb_KyCcqhZTwZ4EcKrFlK7HaAmrg0rKHUTyAtYmdtR21FPHd13Yoi0QOlsf-zfPTyxxZrjixlBn6W5PMaSpwp7HcSHAf5lZpWBvSTV2htiIXFUoL6xmggRdyjNvf3k925FaHPKOxGpv6FlspVXhqR3a-tcaXt9B0kgUlslgV9BHqydI92qDeYPvhYT1LYcOyvXqHkqA8wPxpZTbszMwcETyksFxkK7jP92jvj-iPZvzUUAFwoLeRXntqHUG9S9ITzjUZA6e7psHfOd4ZCvjEGLLm-2HhWWj9zBbz9svy8Qw7qn0RF5ZK8_Y7AqssFldq_306__BLGAstiSVFIErnNoJcFQXlVigZx_HdtfFzCE9Kl2hDu5BbBSg5aKqKwyUKD7rQUpgKUxUYooIcNYfCvcciVpKTENwNxtE3bZjdU0kb5hPmqP_4WE-OqyuOln5w4QaFpVssDEUMa6uyg8xZanVNX9B5si4UhabnZvzCFEZMq3q3vxDu378qVX7J7LQ31Ow1SU76UdXSsrQ7CCxLj-yDpUnSi5PRsNsZD8bdpN9P-hE7uONhJx70Ov3-OBn3ks7o2_AUsc8g34lHw8HpH9YZ-To%3Ftype%3Dpng" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fpako%3AeNplkk9v4jAQxb_KyCcqhZTwZ4EcKrFlK7HaAmrg0rKHUTyAtYmdtR21FPHd13Yoi0QOlsf-zfPTyxxZrjixlBn6W5PMaSpwp7HcSHAf5lZpWBvSTV2htiIXFUoL6xmggRdyjNvf3k925FaHPKOxGpv6FlspVXhqR3a-tcaXt9B0kgUlslgV9BHqydI92qDeYPvhYT1LYcOyvXqHkqA8wPxpZTbszMwcETyksFxkK7jP92jvj-iPZvzUUAFwoLeRXntqHUG9S9ITzjUZA6e7psHfOd4ZCvjEGLLm-2HhWWj9zBbz9svy8Qw7qn0RF5ZK8_Y7AqssFldq_306__BLGAstiSVFIErnNoJcFQXlVigZx_HdtfFzCE9Kl2hDu5BbBSg5aKqKwyUKD7rQUpgKUxUYooIcNYfCvcciVpKTENwNxtE3bZjdU0kb5hPmqP_4WE-OqyuOln5w4QaFpVssDEUMa6uyg8xZanVNX9B5si4UhabnZvzCFEZMq3q3vxDu378qVX7J7LQ31Ow1SU76UdXSsrQ7CCxLj-yDpUnSi5PRsNsZD8bdpN9P-hE7uONhJx70Ov3-OBn3ks7o2_AUsc8g34lHw8HpH9YZ-To%3Ftype%3Dpng" width="1293" height="537"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h4&gt;
  
  
  4. NFT Minting
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fpako%3AeNptVFFv2jAQ_iuWn5iWptDSUvJQidFNQloYKkGrpry48QHWEjuznbYp4r_v7CQURPMQ-eLvvrvv88U7mikONKIG_lUgM3gQbKNZkUqCD8us0mRlQDdxybQVmSiZtGQ1I8yQR0AMrs_3JxvAN0JiZqxmTXwOS5TKHaoQ0s7X1oWf1Ip9sRgsK3N48_EliRc_yVRpOMc_LqYOv1Q5k8xHvQd4mYP9co5dbPGtCofvlr9ZngP22oCd_Iv7-9UsIimNhRMlyfxHEpI5KyAicY1BSlvwDKFeakQWv5YJucy2zF7umPs04_sG5QEIdGqjY-m9HZFIGhBTF88qD0ilRUC0qllu62-lCYh6laAnnGswhuxbPS7VtRhjj5kGZmFViB6HF4mSyVdixAazZhyrClv3pFJlm4kpmIgOdYmk19lKhMSDqzIrlGzRiLvo6qArguXiHThJnj7IDqqemYHb4WHPt_jhze4oP3kLCAeTaVG6WuTEpNb55IlMmeaoJaWLHJDbizqxvT2-yO9MJF-C5Ilm0jCvoReGYaujRXpy7CNCvXItdMF8fY59qc3J4R-oJ2Wp1Quc0rQGGqz3yF6PSp64dtIes5WG82Zm3pluH8fA4sK4wcuaDoGntDPoeNbQH5yIKrdkrfJcvV5U5ScuphRH1c8b8JD4WW5HKSLozpGbB2cehClzVrfsNKAFoE-C45Wxc-iU2i0UkFLHzpn-60j2iKtKjuP0nQu8Qmi0ZrmBgLLKqmUtMxrhZEEHau-cAwp8UtxcTP5-CqhW1WZ7QOCf-0epoqPZaNdQs9Z4CqCnqpKWRldjj6XRjr7RaDC4Dgd3o6v--GZ8NRgOB8OA1vh51A9vrvvD4Xgwvh70725H-4C-e_p-eDe62f8HWmKqfA%3Ftype%3Dpng" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fpako%3AeNptVFFv2jAQ_iuWn5iWptDSUvJQidFNQloYKkGrpry48QHWEjuznbYp4r_v7CQURPMQ-eLvvrvv88U7mikONKIG_lUgM3gQbKNZkUqCD8us0mRlQDdxybQVmSiZtGQ1I8yQR0AMrs_3JxvAN0JiZqxmTXwOS5TKHaoQ0s7X1oWf1Ip9sRgsK3N48_EliRc_yVRpOMc_LqYOv1Q5k8xHvQd4mYP9co5dbPGtCofvlr9ZngP22oCd_Iv7-9UsIimNhRMlyfxHEpI5KyAicY1BSlvwDKFeakQWv5YJucy2zF7umPs04_sG5QEIdGqjY-m9HZFIGhBTF88qD0ilRUC0qllu62-lCYh6laAnnGswhuxbPS7VtRhjj5kGZmFViB6HF4mSyVdixAazZhyrClv3pFJlm4kpmIgOdYmk19lKhMSDqzIrlGzRiLvo6qArguXiHThJnj7IDqqemYHb4WHPt_jhze4oP3kLCAeTaVG6WuTEpNb55IlMmeaoJaWLHJDbizqxvT2-yO9MJF-C5Ilm0jCvoReGYaujRXpy7CNCvXItdMF8fY59qc3J4R-oJ2Wp1Quc0rQGGqz3yF6PSp64dtIes5WG82Zm3pluH8fA4sK4wcuaDoGntDPoeNbQH5yIKrdkrfJcvV5U5ScuphRH1c8b8JD4WW5HKSLozpGbB2cehClzVrfsNKAFoE-C45Wxc-iU2i0UkFLHzpn-60j2iKtKjuP0nQu8Qmi0ZrmBgLLKqmUtMxrhZEEHau-cAwp8UtxcTP5-CqhW1WZ7QOCf-0epoqPZaNdQs9Z4CqCnqpKWRldjj6XRjr7RaDC4Dgd3o6v--GZ8NRgOB8OA1vh51A9vrvvD4Xgwvh70725H-4C-e_p-eDe62f8HWmKqfA%3Ftype%3Dpng" width="1904" height="905"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h4&gt;
  
  
  5. Token Swap (Jupiter v6)
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fpako%3AeNp1VNtuGjEQ_RXLT0RdCAQCYR-oKEQVbdIgLmpV8eKsJ-B213Z9ISGIf-94dyGQyz6sbM_xmePj8WxpojjQmFr450EmMBRsaVi2kAQ_ljhlyNyCKeaaGScSoZl0ZD4izJIJIAbHb-P9JeAfIbfMOsOK-VvYTKk0oP54LRyY6SPTYekt8FsBCNj9cN0m_fE7qccr_KssQPfDnyxN4Z30k_EgwKYqZZLls8oQ1j_AnS1kgQ6nr_Z681FMFjTII9O7G-IUmU-HgwUtQSOE5CeMyfhuOiPnyYq58y0LSyO-K1A5AIHhgPHrE1e2REjt3a2QLiLKu8OYZcpLd8MyrYyzEbGp0BqZv2hLdmcFdWBA5tKZmHy9Rg3r9vk_rxx8rtVqBayMVw8i8vgErFbSAqlg2n6eLSLaiARGmcb7xe0fpCnOinlsMGZ7ShcRj96N_X0qku-wIbsPNIS9M8OkxVRCSVK5ZxbareOUL-5uCXIKlopn4LOniHCwiRE633hic3lns19kwAwnn0h-dxwcE6nF6YKOU8BExIqlPLnHsmbiPNKXfAqSH-mrvNhRIvNcNvgxUPJBmIzlcjjKVEtSOUnMhdUp2wA_OymvQ86-1kat4ZS_18PKRD0oZMIej7QUMAxWX-tmzht4q3KUO7iPYy05HNhQ2kkhHfiC7o08rmr00YD1qSMPKk3VY9Xrd9wuX0iiMo3PDY5sPVg0LAwo2WhEM0DDBMcetA3oBXUryHBrYOPM_A0kO8R5zZmDay6wJ9H4gaVYYZR5p6YbmdDYGQ97UNnEDijIN90WnS5veBE1yi9XBwQ2g99KZXuapQmCirFB18EMwqugcbOZY2m8pU80bjSatcZV56LevexeNFqtRiuiG1zu1GuXzXqr1W10m436Vbuzi-hzTl-vXXUud_8BxgvN5Q%3Ftype%3Dpng" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fpako%3AeNp1VNtuGjEQ_RXLT0RdCAQCYR-oKEQVbdIgLmpV8eKsJ-B213Z9ISGIf-94dyGQyz6sbM_xmePj8WxpojjQmFr450EmMBRsaVi2kAQ_ljhlyNyCKeaaGScSoZl0ZD4izJIJIAbHb-P9JeAfIbfMOsOK-VvYTKk0oP54LRyY6SPTYekt8FsBCNj9cN0m_fE7qccr_KssQPfDnyxN4Z30k_EgwKYqZZLls8oQ1j_AnS1kgQ6nr_Z681FMFjTII9O7G-IUmU-HgwUtQSOE5CeMyfhuOiPnyYq58y0LSyO-K1A5AIHhgPHrE1e2REjt3a2QLiLKu8OYZcpLd8MyrYyzEbGp0BqZv2hLdmcFdWBA5tKZmHy9Rg3r9vk_rxx8rtVqBayMVw8i8vgErFbSAqlg2n6eLSLaiARGmcb7xe0fpCnOinlsMGZ7ShcRj96N_X0qku-wIbsPNIS9M8OkxVRCSVK5ZxbareOUL-5uCXIKlopn4LOniHCwiRE633hic3lns19kwAwnn0h-dxwcE6nF6YKOU8BExIqlPLnHsmbiPNKXfAqSH-mrvNhRIvNcNvgxUPJBmIzlcjjKVEtSOUnMhdUp2wA_OymvQ86-1kat4ZS_18PKRD0oZMIej7QUMAxWX-tmzht4q3KUO7iPYy05HNhQ2kkhHfiC7o08rmr00YD1qSMPKk3VY9Xrd9wuX0iiMo3PDY5sPVg0LAwo2WhEM0DDBMcetA3oBXUryHBrYOPM_A0kO8R5zZmDay6wJ9H4gaVYYZR5p6YbmdDYGQ97UNnEDijIN90WnS5veBE1yi9XBwQ2g99KZXuapQmCirFB18EMwqugcbOZY2m8pU80bjSatcZV56LevexeNFqtRiuiG1zu1GuXzXqr1W10m436Vbuzi-hzTl-vXXUud_8BxgvN5Q%3Ftype%3Dpng" width="1904" height="879"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h4&gt;
  
  
  6. DevNet Airdrop
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fpako%3AeNptklFv2jAQx7-K5SeQUkpKOsAPlVDZA9q6VgWkqcrLNb4Ga4md2c40FuW795xQykr9EPmc3_39v_M1PDMSueAOf9eoM1wqyC2UqWa0IPPGsq1D28cVWK8yVYH2bLti4NgjEkP78_-LHOlLyB04b6GPz7GNMUWgQFlpTRXCc-jx4TYwa1OAhi4aLPHPD_TDVPd08Hhxc7NdCZbyRa_Feoat77-n_ICtCOqcCPZwv96wy2wH_rKBcLSSbU91AIHBjDh1NmgYSGnRuYhBaWrtyRJrh31aICiL7AlmQzudPzgZVPVzobJvuI9YAWVlrHeHJKIvjlc5lWvwtcVzwczoF2XLjQXtqOXK6MGR_kzqwKM8kXqvvXm_6r9KTuv_2M4KJfv5oZmBoc4LtlSuKmBPdbu68GywqL0pgR6QPQM9WoasriR4HPKIl2hLUJKGrglCKfc7LDHl4S4J9lfQb4nrM75KRUPIxQsUDiMOJLze64wLb2t8gw5Te6SwS7rrR7ub8IhbU-e7I0Fz9WRM-SaT22Co31vUEu1taAoXk2nHctHwv1zE8WQUz6ZX4_n1_CpOkjiJ-J6Op-PR9WScJPN4PonHsy_TNuL_OvnxaDZN5qerfQWivh8g%3Ftype%3Dpng" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fpako%3AeNptklFv2jAQx7-K5SeQUkpKOsAPlVDZA9q6VgWkqcrLNb4Ga4md2c40FuW795xQykr9EPmc3_39v_M1PDMSueAOf9eoM1wqyC2UqWa0IPPGsq1D28cVWK8yVYH2bLti4NgjEkP78_-LHOlLyB04b6GPz7GNMUWgQFlpTRXCc-jx4TYwa1OAhi4aLPHPD_TDVPd08Hhxc7NdCZbyRa_Feoat77-n_ICtCOqcCPZwv96wy2wH_rKBcLSSbU91AIHBjDh1NmgYSGnRuYhBaWrtyRJrh31aICiL7AlmQzudPzgZVPVzobJvuI9YAWVlrHeHJKIvjlc5lWvwtcVzwczoF2XLjQXtqOXK6MGR_kzqwKM8kXqvvXm_6r9KTuv_2M4KJfv5oZmBoc4LtlSuKmBPdbu68GywqL0pgR6QPQM9WoasriR4HPKIl2hLUJKGrglCKfc7LDHl4S4J9lfQb4nrM75KRUPIxQsUDiMOJLze64wLb2t8gw5Te6SwS7rrR7ub8IhbU-e7I0Fz9WRM-SaT22Co31vUEu1taAoXk2nHctHwv1zE8WQUz6ZX4_n1_CpOkjiJ-J6Op-PR9WScJPN4PonHsy_TNuL_OvnxaDZN5qerfQWivh8g%3Ftype%3Dpng" width="1421" height="629"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  System Architecture
&lt;/h2&gt;

&lt;p&gt;This app is composed of three major layers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Frontend Layer&lt;/li&gt;
&lt;li&gt;AI Agent Layer&lt;/li&gt;
&lt;li&gt;Storage Layer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The system configuration on the AWS side is as follows:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpot0hsypy4t1q13v185o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpot0hsypy4t1q13v185o.png" width="800" height="458"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's a serverless + fully managed configuration!&lt;/p&gt;

&lt;p&gt;The list of services used and their purposes are as follows:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;AWS Service&lt;/th&gt;
&lt;th&gt;CDK Stack&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Amazon API Gateway&lt;/strong&gt; (HTTP API v2)&lt;/td&gt;
&lt;td&gt;BackendStack&lt;/td&gt;
&lt;td&gt;Request routing to Mastra Agent, CORS settings&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;AWS Lambda&lt;/strong&gt; (Container Image)&lt;/td&gt;
&lt;td&gt;BackendStack&lt;/td&gt;
&lt;td&gt;Mastra AI Agent runtime (Docker-based)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Amazon ECR&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;BackendStack&lt;/td&gt;
&lt;td&gt;Registry for Lambda Docker container images&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Amazon S3&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FrontendStack&lt;/td&gt;
&lt;td&gt;Static hosting of React SPA build artifacts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Amazon CloudFront&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FrontendStack&lt;/td&gt;
&lt;td&gt;CDN delivery, reverse proxy for &lt;code&gt;/chat/*&lt;/code&gt; path to API Gateway&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Amazon DynamoDB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;StorageStack&lt;/td&gt;
&lt;td&gt;Persistence of Agent memory/session history (with TTL)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AWS Secrets Manager&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;StorageStack&lt;/td&gt;
&lt;td&gt;Secure management of LLM API keys/Solana RPC keys&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Amazon EventBridge&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;BackendStack&lt;/td&gt;
&lt;td&gt;Scheduled execution, asynchronous event routing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Amazon CloudWatch Logs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;BackendStack&lt;/td&gt;
&lt;td&gt;Collection and storage of Lambda logs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AWS IAM&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;All Stacks&lt;/td&gt;
&lt;td&gt;Lambda execution roles, Bedrock calling permissions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Amazon Bedrock&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;AgentRuntime construct&lt;/td&gt;
&lt;td&gt;Auxiliary LLM (via Anthropic Claude Haiku, for future expansion)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AWS CDK&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Manage all AWS resources with TypeScript IaC&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Key Implementation Points
&lt;/h2&gt;

&lt;p&gt;I'll pick up and introduce some important implementation parts.&lt;/p&gt;

&lt;h3&gt;
  
  
  React Integration
&lt;/h3&gt;

&lt;p&gt;We are integrating &lt;strong&gt;React&lt;/strong&gt; and &lt;strong&gt;Mastra&lt;/strong&gt;.&lt;br&gt;
When checking the operation locally, you need to add settings like the following to the &lt;strong&gt;Vite&lt;/strong&gt; configuration file so that the backend processing is called correctly when the specified path is called.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In this case, it's set to call the API-side processing when &lt;code&gt;/chat/&lt;/code&gt; is called.&lt;br&gt;
&lt;/p&gt;


&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// vite.config.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;tailwindcss&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@tailwindcss/vite&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;react&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@vitejs/plugin-react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;path&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vitest/config&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Vite configuration&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;react&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nf"&gt;tailwindcss&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt;
  &lt;span class="na"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;alias&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./src&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="c1"&gt;// Dev server proxy settings&lt;/span&gt;
  &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;proxy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/chat&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:4111&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;changeOrigin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="c1"&gt;// Test settings&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;globals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The AI Agent created with Mastra is called as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sendMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stop&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useChat&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;transport&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DefaultChatTransport&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;api&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/chat/solana-agent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Mastra Configuration
&lt;/h3&gt;

&lt;p&gt;The AI Agent settings are as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/mastra/agents/solana-agent.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Agent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@mastra/core/agent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Memory&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@mastra/memory&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;airdropTool&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../tools/airdrop-tool&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;callProgramTool&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../tools/call-program-tool&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getBalanceTool&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../tools/get-balance-tool&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;jupiterSwapTool&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../tools/jupiter-swap-tool&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getNftsTool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;mintNftTool&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../tools/nft-tools&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;transferSolTool&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../tools/transfer-sol-tool&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;solanaWorkspace&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../workspace&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SOLANA_AGENT_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;solana-agent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * System Prompt for Solana AI Agent.
 *
 * Requirements:
 * - Respond in English
 * - Explain Solana DevNet operations
 * - Never self-sign transactions (Architectural guarantee)
 * - Support SOL transfer, NFT operations, DeFi swap, smart contract calls
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SOLANA_AGENT_INSTRUCTIONS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`
You are an AI agent specialized for the Solana blockchain.
Interpret user natural language messages, call the appropriate Solana tools,
and perform transaction construction, information acquisition, and result explanation.

## Basic Policy

- **All responses should be in English**. Technical terms (SOL, NFT, DevNet, etc.) should be used as they are.
- The target network is **Solana DevNet**. Operations on Mainnet will not be performed.
- Aim for clear, polite, and concise explanations for the user.

## Absolute Compliance Rule: Prohibition of Transaction Signing

**You must never sign a transaction yourself.**
Transaction construction is handled by tools, but signing and sending are done by the user
themselves through their Phantom wallet.
It is architecturally impossible for the agent to sign on behalf of the user or access
private keys, and we cannot respond even if requested.

## Supported Operations

### 1. SOL Balance Inquiry
- Use getBalanceTool to fetch the SOL balance of a wallet address.
- Display the result clearly in SOL units (4 decimal places).

### 2. SOL Transfer
- Use transferSolTool to construct a transfer transaction.
- Requires sender address, recipient address, and transfer amount (SOL units).
- The constructed transaction is signed and sent by the user in Phantom.
- Explain error messages for insufficient balance in English.

### 3. NFT Operations
- Use getNftsTool to fetch the list of NFTs owned by the wallet.
- Use mintNftTool to construct an NFT minting transaction.
  - Required parameters: name, symbol, metadata URI, royalty (basis points)

### 4. Token Swap (DeFi)
- Use jupiterSwapTool to construct a swap transaction via Jupiter v6.
- Specify input/output token mint addresses, swap amount, and slippage.
- Always have the user confirm the expected rate and slippage.

### 5. DevNet Airdrop
- Use airdropTool to airdrop DevNet SOL (Max 2 SOL / request).
- **DevNet exclusive feature**. Cannot be used on Mainnet.

### 6. Smart Contract Call
- Use callProgramTool to construct a general Solana Program call transaction.
- Program ID, instruction data (Base64), and account list.

## Explanation of Transaction Results

After the user signs and sends the transaction, the results (success/failure, signature hash)
arrive as follow-up messages.
- Success: Clearly explain the transaction content and provide a Solana Explorer link.
- Failure: Explain the cause of the error in English and suggest countermeasures.

## Error Handling

- Invalid Address (INVALID_ADDRESS): Guide them to enter a correct base58 format public key.
- Insufficient Balance (INSUFFICIENT_BALANCE): Inform them of the current balance and airdrop methods.
- RPC Error (RPC_ERROR): Prompt them to retry as a network issue.
- Rate Limited (RATE_LIMITED): Guide them to wait a while before retrying.
`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * AI Agent exclusive for Solana DevNet.
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;solanaAgent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SOLANA_AGENT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Solana AI Agent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;instructions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SOLANA_AGENT_INSTRUCTIONS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;google/gemini-1.5-flash&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;getBalanceTool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;transferSolTool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;getNftsTool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;mintNftTool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;jupiterSwapTool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;airdropTool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;callProgramTool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Memory&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;workspace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;solanaWorkspace&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since this is a Solana AI Agent, I've also configured an additional Agent SKILL (&lt;code&gt;solanaWorkspace&lt;/code&gt;).&lt;br&gt;
This allows the agent to refer to SKILL.md files in a specific folder to gain specialized knowledge.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/mastra/workspace.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;LocalSkillSource&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Workspace&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@mastra/core/workspace&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Workspace for Solana AI Agent.
 * Reads domain knowledge from SKILL.md files under src/mastra/skills/
 * to ensure the agent accurately understands Solana concepts, tools, and error codes.
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;solanaWorkspace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Workspace&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Solana AI Agent Workspace&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;skills&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;src/mastra/skills&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;skillSource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;LocalSkillSource&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;basePath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Various Tools for On-Chain Operations
&lt;/h3&gt;

&lt;p&gt;We've implemented a group of tools for on-chain operations.&lt;br&gt;
Here's an explanation of the balance acquisition and transaction creation tools.&lt;/p&gt;
&lt;h4&gt;
  
  
  Balance Inquiry Tool
&lt;/h4&gt;

&lt;p&gt;For balance inquiry, it basically calls the Solana RPC API to fetch and return the balance of the wallet address passed from the prompt.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/mastra/tools/get-balance-tool.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createTool&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@mastra/core/tools&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Connection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;PublicKey&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@solana/web3.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;GetBalanceInputSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;GetBalanceOutputSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;GetBalanceOutput&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../../types/solana&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;LAMPORTS_PER_SOL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;_000_000_000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;executeGetBalance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;getBalanceFn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;GetBalanceOutput&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lamports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getBalanceFn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;lamports&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;sol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;lamports&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;LAMPORTS_PER_SOL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;network&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;devnet&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * SOL balance inquiry tool for Solana DevNet.
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getBalanceTool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createTool&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;get-balance&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Fetches the SOL balance of a Solana DevNet wallet address.
Input: address — base58 Solana public key (wallet address)
Output: Balance in lamports and sol, and target network (devnet)
Note: This tool is exclusive to Solana DevNet.`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="na"&gt;inputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GetBalanceInputSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;outputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GetBalanceOutputSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="na"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;address&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rpcUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SOLANA_RPC_URL&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://api.devnet.solana.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;connection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Connection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rpcUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;confirmed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="na"&gt;publicKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PublicKey&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;publicKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PublicKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;`Invalid address format: "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;". Please specify a base58 Solana public key.`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;executeGetBalance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getBalance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;publicKey&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Transaction Creation Tool
&lt;/h4&gt;

&lt;p&gt;The key part is this transaction creation tool!&lt;br&gt;
To ensure safety, the AI Agent side does not perform electronic signatures; it only handles transaction creation.&lt;br&gt;
The transaction is sent to the blockchain only when the user signs it of their own will.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/mastra/tools/transfer-sol-tool.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createTool&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@mastra/core/tools&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;Connection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;PublicKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;SystemProgram&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Transaction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@solana/web3.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;SolanaTxRequestSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;TransferSolInputSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;SolanaTxRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../../types/solana&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;LAMPORTS_PER_SOL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;_000_000_000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;TransferDeps&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;getBalanceFn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;getRecentBlockhashFn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;buildTransferTransaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;fromAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;toAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;amountSol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;deps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TransferDeps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SolanaTxRequest&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lamports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;amountSol&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;LAMPORTS_PER_SOL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;deps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getBalanceFn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fromAddress&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;lamports&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;currentSol&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;LAMPORTS_PER_SOL&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`INSUFFICIENT_BALANCE: Insufficient balance. Current balance: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;currentSol&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; SOL, required: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;amountSol&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; SOL`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fromPubkey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PublicKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fromAddress&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;toPubkey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PublicKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;toAddress&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;blockhash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;deps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRecentBlockhashFn&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;transaction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Transaction&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;SystemProgram&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transfer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;fromPubkey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;toPubkey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lamports&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
  &lt;span class="nx"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;recentBlockhash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;blockhash&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;feePayer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fromPubkey&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;serializedTx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;transaction&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;serialize&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;requireAllSignatures&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;toShort&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;toAddress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;...&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;toAddress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`Transfer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;amountSol&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; SOL to &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;toShort&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;solana_tx_request&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;serializedTx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;txType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;transfer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * SOL transfer transaction construction tool.
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;transferSolTool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createTool&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;transfer-sol&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Constructs a SOL transfer transaction (signing is done in user's Phantom wallet).
Input:
  - fromAddress: Sender wallet address (base58)
  - toAddress: Recipient wallet address (base58)
  - amountSol: Transfer amount (SOL units)
Output: SolanaTxRequest signable in Phantom (serialized transaction)`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="na"&gt;inputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TransferSolInputSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;outputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SolanaTxRequestSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="na"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;fromAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;toAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;amountSol&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rpcUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SOLANA_RPC_URL&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://api.devnet.solana.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;connection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Connection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rpcUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;confirmed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PublicKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fromAddress&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PublicKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;toAddress&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid address format. Please specify a base58 Solana public key.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;buildTransferTransaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fromAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;toAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;amountSol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;getBalanceFn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getBalance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PublicKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
      &lt;span class="na"&gt;getRecentBlockhashFn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;blockhash&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getLatestBlockhash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;confirmed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;blockhash&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Solana Dev Agent SKILL
&lt;/h3&gt;

&lt;p&gt;In implementing this AI Agent, the following Agent SKILL was very helpful.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/solana-foundation/solana-dev-skill" rel="noopener noreferrer"&gt;Solana Dev Skill GitHub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Why not try using it when you develop apps on Solana?&lt;br&gt;
There's also Deepwiki: &lt;a href="https://deepwiki.com/solana-foundation/solana-dev-skill" rel="noopener noreferrer"&gt;Solana Dev Skill on Deepwiki&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can install it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add https://github.com/solana-foundation/solana-dev-skill
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How to Run
&lt;/h2&gt;

&lt;p&gt;Let's share how to run this AI Agent!&lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://bun.sh/" rel="noopener noreferrer"&gt;Bun&lt;/a&gt; v1.x&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://nodejs.org/" rel="noopener noreferrer"&gt;Node.js&lt;/a&gt; v23+&lt;/li&gt;
&lt;li&gt;Phantom Wallet browser extension installed&lt;/li&gt;
&lt;li&gt;Google Gemini API key issued&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Setup
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Clone the repository&lt;/span&gt;
git clone https://github.com/your-org/solana-agent-repo.git
&lt;span class="nb"&gt;cd &lt;/span&gt;solana-agent-repo/mastra-react

&lt;span class="c"&gt;# Install dependencies&lt;/span&gt;
bun &lt;span class="nb"&gt;install&lt;/span&gt;

&lt;span class="c"&gt;# Configure environment variables&lt;/span&gt;
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env
&lt;span class="c"&gt;# Edit .env and set GOOGLE_GENERATIVE_AI_API_KEY etc.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Start Development Server
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Terminal 1: Frontend (localhost:5173)&lt;/span&gt;
bun run dev

&lt;span class="c"&gt;# Terminal 2: Mastra Backend (localhost:4111)&lt;/span&gt;
bun run dev:mastra
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Local setup is complete! Try asking for your balance by passing your wallet address.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploy Resources to AWS
&lt;/h3&gt;

&lt;p&gt;Next, I'll explain the steps to deploy to AWS.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Must be executed under the &lt;code&gt;cdk&lt;/code&gt; folder.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Since we're using CDK, deployment is completed with a single command!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install dependencies first&lt;/span&gt;
bun &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;span class="c"&gt;# Deploy&lt;/span&gt;
bun run deploy &lt;span class="s1"&gt;'*'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once deployment is successful, all three layers mentioned above will be expanded. The app works exactly the same as locally!&lt;/p&gt;

&lt;h3&gt;
  
  
  Cleanup Resources on AWS
&lt;/h3&gt;

&lt;p&gt;Don't forget to delete resources after verification!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bun run destroy &lt;span class="s1"&gt;'*'&lt;/span&gt; &lt;span class="nt"&gt;--force&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;That's all for now!&lt;br&gt;
This was an explanation of implementing a Solana AI Agent using a modern tech stack. I'll continue to add interesting features as this can be expanded upon!&lt;/p&gt;

&lt;p&gt;Thank you for reading!&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://mastra.ai/guides/guide/code-review-bot" rel="noopener noreferrer"&gt;Mastra docs — code-review-bot guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mcp.solana.com/" rel="noopener noreferrer"&gt;Solana Developer MCP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://solana.com/ja/skills" rel="noopener noreferrer"&gt;Solana Agent SKILL&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/solana-foundation/solana-dev-skill" rel="noopener noreferrer"&gt;GitHub — Solana Agent SKILL&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://deepwiki.com/solana-foundation/solana-dev-skill" rel="noopener noreferrer"&gt;DeepWiki — Solana Agent SKILL&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/sendaifun/solana-agent-kit" rel="noopener noreferrer"&gt;GitHub — Solana Agent Kit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/solana-foundation/awesome-solana-ai" rel="noopener noreferrer"&gt;GitHub — awesome-solana-ai&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://station.jup.ag/docs/apis/swap-api" rel="noopener noreferrer"&gt;Jupiter v6 API Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.metaplex.com/core" rel="noopener noreferrer"&gt;Metaplex MPL Core Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=mHLzj2YTkQk" rel="noopener noreferrer"&gt;AI Agent Demo Video for Solana Bootcamp&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://speakerdeck.com/mashharuki/building-ai-agents-on-solana-mastra-framework-wohuo-yong-sitaci-shi-dai-ezientokai-fa" rel="noopener noreferrer"&gt;AI Agent Pitch Slides for Solana Bootcamp&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>aws</category>
      <category>solana</category>
      <category>web3</category>
    </item>
    <item>
      <title>Mathematically Prohibiting 'Cheating' in On-Chain RPS: A Midnight ZK dApp Case Study</title>
      <dc:creator>Haruki Kondo</dc:creator>
      <pubDate>Sun, 03 May 2026 23:31:12 +0000</pubDate>
      <link>https://dev.to/midnight-aliit/mathematically-prohibiting-cheating-in-on-chain-rps-a-midnight-x-zk-dapp-case-study-3l9g</link>
      <guid>https://dev.to/midnight-aliit/mathematically-prohibiting-cheating-in-on-chain-rps-a-midnight-x-zk-dapp-case-study-3l9g</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Implementing Rock-Paper-Scissors (RPS) on-chain is surprisingly tricky.&lt;/p&gt;

&lt;p&gt;The moment you choose "Rock" and send a transaction, your opponent can read your move from the public ledger. The game is over before it even starts.&lt;/p&gt;

&lt;p&gt;I tried implementing a commit-reveal pattern manually, but managing salts, preventing front-running, and ensuring fair judging logic... it quickly became a rabbit hole.&lt;/p&gt;

&lt;p&gt;That's when I turned to &lt;strong&gt;Midnight&lt;/strong&gt;. I built a full-stack RPS dApp where "cheating" (looking at the opponent's move before playing) is mathematically impossible!&lt;/p&gt;

&lt;p&gt;In this article, I'll share the ZK circuit design using Midnight's smart contract language, &lt;strong&gt;Compact&lt;/strong&gt;, and the hurdles I faced during development.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;What ZK protects&lt;/strong&gt;: The ZK proof ensures "confidentiality of the move during the commit phase" (fairness). After the reveal, both moves are recorded on the public ledger.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here is what the dApp looks like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgfa16txxqbm5req1spcy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgfa16txxqbm5req1spcy.png" alt="RPS dApp Hero" width="800" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What You'll Learn
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Designing the "Hidden" vs. "Visible" split in Midnight.&lt;/li&gt;
&lt;li&gt;Mastering the commit-reveal pattern with the Compact language.&lt;/li&gt;
&lt;li&gt;Frontend UX strategies for handling ZK proof generation time (the 10-second hurdle).&lt;/li&gt;
&lt;li&gt;Robust provider configuration with the Midnight JS SDK.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Repository
&lt;/h2&gt;

&lt;p&gt;You can find the full source code here:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/mashharuki" rel="noopener noreferrer"&gt;
        mashharuki
      &lt;/a&gt; / &lt;a href="https://github.com/mashharuki/midnight-rps-sample-app" rel="noopener noreferrer"&gt;
        midnight-rps-sample-app
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Midnight RPS sample dApp
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;midnight-rps-sample-app&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;Midnight RPS sample dApp&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Overview&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;midnight-rps-sample-app is a sample Rock-Paper-Scissors dApp project built on Midnight, a privacy-focused blockchain.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Key Features&lt;/h3&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Fair Gameplay via Zero-Knowledge Proofs (ZK Proofs)&lt;/strong&gt;&lt;br&gt;
The app utilizes a "commit/reveal" scheme powered by Compact smart contracts. Players commit to their move (Rock, Paper, or Scissors) by submitting a hashed value. Once all players have committed, the moves are revealed. This ensures a tamper-proof gaming experience where "sniping" or reacting to an opponent's move is impossible.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Powered by the Midnight Blockchain&lt;/strong&gt;&lt;br&gt;
Contracts are deployed on the Midnight PreProd testnet, with all transactions recorded on-chain.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Full-Stack Architecture&lt;/strong&gt;&lt;/p&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Package&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pkgs/contract&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Smart contracts written in the Compact language&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pkgs/cli&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;CLI tools for contract deployment and interaction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pkgs/app&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Frontend UI built with React + Vite&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Lace Wallet Integration&lt;/strong&gt;&lt;br&gt;
Connects with Lace Wallet via the &lt;code&gt;@midnight-ntwrk/dapp-connector-api&lt;/code&gt; for secure signing and transaction processing.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Game Flow&lt;/h3&gt;

&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Commit Phase&lt;/strong&gt; — Each player commits their move…&lt;/li&gt;
&lt;/ol&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/mashharuki/midnight-rps-sample-app" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;





&lt;h2&gt;
  
  
  Quick Start
&lt;/h2&gt;

&lt;p&gt;Here are the steps to get the app running locally.&lt;/p&gt;

&lt;p&gt;To be honest, &lt;strong&gt;it takes about 15-20 minutes&lt;/strong&gt; including ZK circuit compilation and the initial Docker image pull. Get your coffee ready!&lt;/p&gt;

&lt;h3&gt;
  
  
  Environment
&lt;/h3&gt;

&lt;p&gt;Tested with the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Docker version 27.4.0
compact 0.2.0
bun 1.3.13
node 23.3.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Install Lace Wallet to your browser
&lt;/h3&gt;

&lt;p&gt;If you have not yet installed Lace Wallet, you must go to below page &amp;amp; need to install Lace Wallet&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://www.lace.io/" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.sanity.io%2Fimages%2Ftnb8oobx%2Fproduction%2F434b20b0ce779725521c1890bd857b5b2dac6c65-1201x628.jpg" height="418" class="m-0" width="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://www.lace.io/" rel="noopener noreferrer" class="c-link"&gt;
            Lace | The light wallet platform to explore Web3
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Discover the new light wallet platform from Input Output Global. Manage digital assets, and access NFTs, DApps, and DeFi services. Start your Web3 journey.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.lace.io%2Ffavicon.ico" width="16" height="16"&gt;
          lace.io
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;Next, you need to create wallet account of Midnight&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Please switch to PreProd Network&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  0. Clone the Repository
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/mashharuki/midnight-rps-sample-app.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  1. Install Dependencies
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bun &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  2. Compile and Build Contracts
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Generate ZK circuit assets (This takes the most time)&lt;/span&gt;
bun contract compact  

&lt;span class="c"&gt;# Build CLI and Frontend&lt;/span&gt;
bun cli build
bun app build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;The &lt;code&gt;bun contract compact&lt;/code&gt; step is where the Compact compiler generates the ZK proving and verification keys.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  3. Start the Proof Server (Initial Pull: ~3 mins)
&lt;/h3&gt;

&lt;p&gt;We need to run the server that handles ZK proof generation via Docker. &lt;/p&gt;

&lt;p&gt;Without this server, you won't be able to deploy contracts or send commit/reveal transactions.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Version &lt;code&gt;8.0.3&lt;/code&gt; is verified with Compact &lt;code&gt;0.2.0&lt;/code&gt;. &lt;br&gt;
Ensure your SDK and Proof Server versions match, or proof generation will fail.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 127.0.0.1:6300:6300 midnightntwrk/proof-server:8.0.3 midnight-proof-server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  4. Deploy Contract to PreProd Network
&lt;/h3&gt;

&lt;p&gt;If you don't have testnet NIGHT Token, you can get some token from below site.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://faucet.preprod.midnight.network/" rel="noopener noreferrer"&gt;https://faucet.preprod.midnight.network/&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bun cli preprod-pts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;If you deploy successfull, contract address shows like below.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;19:24:25.997] INFO &lt;span class="o"&gt;(&lt;/span&gt;40223&lt;span class="o"&gt;)&lt;/span&gt;: Deploying RPS contract...
  ⠇ Deploying RPS contract[19:24:51.416] INFO &lt;span class="o"&gt;(&lt;/span&gt;40223&lt;span class="o"&gt;)&lt;/span&gt;: Deployed RPS contract at: 23149945fed06aa010cc3e48e9f5df91625567300fae4e09371bb788d07a6bd8
  ✓ Deploying RPS contract
  Contract deployed at: 23149945fed06aa010cc3e48e9f5df91625567300fae4e09371bb788d07a6bd8
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Essence of Midnight Architecture: "Localizing" Information
&lt;/h2&gt;

&lt;p&gt;Developing with Midnight requires a different mindset compared to &lt;strong&gt;Solidity&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;As mentioned in my previous articles, Midnight has &lt;strong&gt;two types of states&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Public State&lt;/strong&gt;: Visible to everyone (on-chain ledger).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Private State&lt;/strong&gt;: Visible only to you (local storage).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;strong&gt;Compact&lt;/strong&gt; ZK circuits act as the bridge between these two.&lt;/p&gt;
&lt;h3&gt;
  
  
  App Architecture
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6karayn0s6lg82gbdbe5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6karayn0s6lg82gbdbe5.png" alt=" " width="800" height="403"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Code Deep Dive: Mathematically Preventing Cheating
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1. Security Core: Domain Separation
&lt;/h3&gt;

&lt;p&gt;When deriving a public key from a secret key, Midnight recommends using domain separation:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="n"&gt;pure&lt;/span&gt; &lt;span class="n"&gt;circuit&lt;/span&gt; &lt;span class="nf"&gt;derive_pk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Domain separation with a fixed string "rps:pk:v1"&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;persistentHash&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Vector&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nf"&gt;pad&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"rps:pk:v1"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;sk&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;If you use the secret key as-is, using the same key in another app would result in the same public key, risking data leakage. By adding a prefix like "app name + version", we ensure that the public keys derived from the same secret key are unique to each app.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. The Commit-Reveal Circuits
&lt;/h3&gt;
&lt;h4&gt;
  
  
  Commit Phase (Declaring a move while keeping it hidden)
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="n"&gt;export&lt;/span&gt; &lt;span class="n"&gt;circuit&lt;/span&gt; &lt;span class="nf"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;game_over&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                 &lt;span class="s"&gt;"Game is already over"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;GameState&lt;/span&gt;&lt;span class="py"&gt;.waiting&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Not in waiting state"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;sk&lt;/span&gt;         &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;local_secret_key&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// witness: private input&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;pk&lt;/span&gt;         &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;derive_pk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sk&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;my_move&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_my_move&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;my_salt&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_my_salt&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;commitment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;make_commit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;my_move&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;my_salt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;store_move_and_salt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;my_move&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;my_salt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// ⭐ Save move and salt to private state&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;p1_joined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;p1_key&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;p1_commit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;commitment&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;p1_joined&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;p2_joined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Both players already committed"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;p2_key&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;p2_commit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;commitment&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;p2_joined&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;state&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;GameState&lt;/span&gt;&lt;span class="py"&gt;.committed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Transition to 'committed' when both joined&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Pay close attention to &lt;strong&gt;&lt;code&gt;store_move_and_salt&lt;/code&gt;&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;This is a witness function that saves the selected move and salt to the browser's IndexedDB (private state). To prove that the move revealed later is the same one committed earlier, this step is absolutely necessary.&lt;/p&gt;

&lt;p&gt;The use of &lt;code&gt;disclose()&lt;/code&gt; is also critical. It allows developers to explicitly control which results of private computations are written to the public ledger.&lt;/p&gt;
&lt;h4&gt;
  
  
  Game State Transitions
&lt;/h4&gt;

&lt;p&gt;The coordination between commit and reveal phases is managed by these states:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2qx9f9jympddv6oecgxn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2qx9f9jympddv6oecgxn.png" alt="State Transitions" width="800" height="499"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  3. Reveal Phase: Where ZK Proofs Shine
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;If the commit is the "declaration," the reveal is the "proof."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is where Midnight's ZK stack really pays off.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="n"&gt;export&lt;/span&gt; &lt;span class="n"&gt;circuit&lt;/span&gt; &lt;span class="nf"&gt;reveal&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;game_over&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                   &lt;span class="s"&gt;"Game is already over"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;GameState&lt;/span&gt;&lt;span class="py"&gt;.committed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Not in committed state"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;sk&lt;/span&gt;      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;local_secret_key&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;pk&lt;/span&gt;      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;derive_pk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sk&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;my_move&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_my_move&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;   &lt;span class="c1"&gt;// Restore from private state&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;my_salt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_my_salt&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;   &lt;span class="c1"&gt;// Restore from private state&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;computed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;make_commit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;my_move&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;my_salt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;is_p1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p1_key&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;is_p2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p2_key&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;is_p1&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;is_p2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Caller is not a registered player"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;is_p1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;p1_revealed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Player 1 already revealed"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;computed&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;p1_commit&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&gt;"Commitment mismatch for P1"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;p1_move&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;my_move&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Move is written to ledger only now&lt;/span&gt;
    &lt;span class="n"&gt;p1_revealed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;is_p2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;p2_revealed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Player 2 already revealed"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;computed&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;p2_commit&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&gt;"Commitment mismatch for P2"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;p2_move&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;my_move&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;p2_revealed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p1_revealed&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;p2_revealed&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;who_wins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p1_move&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p2_move&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Judge winner&lt;/span&gt;
    &lt;span class="n"&gt;game_over&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;state&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;GameState&lt;/span&gt;&lt;span class="py"&gt;.finished&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The line &lt;code&gt;assert(disclose(computed == p1_commit), "Commitment mismatch for P1")&lt;/code&gt; is the heart of the logic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;my_move&lt;/code&gt; and &lt;code&gt;my_salt&lt;/code&gt; exist only in your local private state.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;make_commit(my_move, my_salt)&lt;/code&gt; is re-calculated, and the ZK circuit verifies it matches the &lt;code&gt;p1_commit&lt;/code&gt; recorded on-chain during the commit phase.&lt;/li&gt;
&lt;li&gt;You mathematically prove that "this move is the one I committed to" &lt;strong&gt;without revealing the move itself until the proof is valid.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once the reveal is successful, &lt;code&gt;p1_move&lt;/code&gt; and &lt;code&gt;p2_move&lt;/code&gt; are finally written on-chain, and &lt;code&gt;who_wins()&lt;/code&gt; determines the outcome.&lt;/p&gt;

&lt;p&gt;During the commit phase, your move is hidden by ZK and remains unknown to the opponent until it is revealed. This is the mechanism that &lt;strong&gt;"mathematically prohibits cheating."&lt;/strong&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Overcoming the ZK dApp UX Hurdle
&lt;/h2&gt;

&lt;p&gt;The biggest challenge during implementation was handling the &lt;strong&gt;"ZK proof generation time."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk0xkb1sfw157aswyepqm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk0xkb1sfw157aswyepqm.png" alt="ZK Proof Loading" width="800" height="440"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Filling the 10-Second Silence
&lt;/h3&gt;

&lt;p&gt;In Midnight, before sending a transaction, you must generate a proof on your machine (or the Proof Server). This takes about 5-10 seconds.&lt;/p&gt;

&lt;p&gt;UX improvements are essential to prevent users from thinking the app has frozen:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Optimistic UI Updates&lt;/strong&gt;: Switch the UI state to "Generating Proof..." as soon as the process starts to communicate clearly what is happening.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Persistent Providers&lt;/strong&gt;: Use &lt;code&gt;levelPrivateStateProvider&lt;/code&gt; to ensure that even if the user reloads the browser during proof generation, the generated data and chosen moves are not lost.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fph5mtdd7r94v0ai1xqzq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fph5mtdd7r94v0ai1xqzq.png" alt="Waiting for Opponent" width="800" height="557"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Example levelPrivateStateProvider configuration&lt;/span&gt;
&lt;span class="nx"&gt;privateStateProvider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;levelPrivateStateProvider&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; 
  &lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rpsPrivateState&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Separate namespace for each game&lt;/span&gt;
  &lt;span class="na"&gt;privateStoragePasswordProvider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;storagePassword&lt;/span&gt; &lt;span class="c1"&gt;// Secure storage&lt;/span&gt;
&lt;span class="p"&gt;}),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Sequence: From Commit to Game End
&lt;/h2&gt;

&lt;p&gt;Let's recap the full flow:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://mermaid.live/edit#pako:eNrFVuFq20gQfpW5LcE2VYxlO00saOHSlOOuJJi4LaUYzFoayUukXd3uKk3O-H72AfqIfZLOrizbqZ02f45bEJZ2vpn5ZuaTV0sWqwRZxI6OlkIKG8GyZRdYYCuCVsL1TSuAeuMD14LPczQth-GxVfod3tnXKlfagZ-lfjm8EZnk-dbC_dpaHvPL-RwfNypVPmaTyuLPbOc32dbU530-rNng3xXKGK-qYo76kDOVKW65FUqeK53sYtY1rVaro6OpbCJdCJ5pXkwl0OKVVdKHrp9Lrq2IRcmlhfcGNXDjf_etv5elM14j5XcP-4jJxVuHuBSJFNnCwl-Tfcx44iBjrVQKE9S3hzJd0fQfBHIbU1kDNVJ6nc3bg14AQ7pe9Dq1xa0raiwoiuqLCJxjBFP2WhWFsFMG4wU3uIU70PGrV1RNRGxyCm0Ifa3iG8JymYCp5uRoth4EJQeqNILYB72kbG1NHjssyNyADEkSZwWBZhRvZnhuPToAd9uBb1--wp8ywTtMLs5_TOPoRXAhTJnze8fsD5SoafQyg9K1sNvtQvvfsAcGYyUT05myH1mMJxHNjJRgbO0DqdJr7u0OxELHFbXmYQe1bzthnfcld-8aZY15Dqr0BJQ0Ps6nt3XQrf94ctzUTsbxQ-NuWe_LhFs0YBUVNkGZuKqs5tI4gStJte1XUw_UoXehu-wTPG4m-u4j1SlToQtMDnNolOH6UeZo8bcmJWXYU1yf1HbiVfdrxV3jLfK8URy0eWoJMld2se69Aa5xk7dzWJPrwZlNQHBK2m3Ljh61R3g9HtbiNVotCOSDwHOvQEi1Kv4PBdZsn6bAD6hFeu-bVVlM4OVLKMNZ3ccnKu8_ks96Kk-Sj5POwF9PkY9ReeXJHZJQ3bxHJfSgjDd3GFPX4PNCzT4LadrUOieAAMq-v-n8dOiQ8YLGgabK7aY2FrACdcFFQqf00m1PmT-Qp8yxTzDlDk7NWBG08m_6m0TQfyGLUiKOAXMH0eRexiyyusIGtD6rNij0Tpf154D_KgiYVlW22CDoxPikVNGEybTjVN9rYuqOx0paFo08lEVLdseiMBx0w7PTfm90MuqHw2E4DNg9bZ_2uieD3nA4CkeDsHf24nQVsH989F737PRk9R18hbOG" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fpako%3AeNrFVuFq20gQfpW5LcE2VYxlO00saOHSlOOuJJi4LaUYzFoayUukXd3uKk3O-H72AfqIfZLOrizbqZ02f45bEJZ2vpn5ZuaTV0sWqwRZxI6OlkIKG8GyZRdYYCuCVsL1TSuAeuMD14LPczQth-GxVfod3tnXKlfagZ-lfjm8EZnk-dbC_dpaHvPL-RwfNypVPmaTyuLPbOc32dbU530-rNng3xXKGK-qYo76kDOVKW65FUqeK53sYtY1rVaro6OpbCJdCJ5pXkwl0OKVVdKHrp9Lrq2IRcmlhfcGNXDjf_etv5elM14j5XcP-4jJxVuHuBSJFNnCwl-Tfcx44iBjrVQKE9S3hzJd0fQfBHIbU1kDNVJ6nc3bg14AQ7pe9Dq1xa0raiwoiuqLCJxjBFP2WhWFsFMG4wU3uIU70PGrV1RNRGxyCm0Ifa3iG8JymYCp5uRoth4EJQeqNILYB72kbG1NHjssyNyADEkSZwWBZhRvZnhuPToAd9uBb1--wp8ywTtMLs5_TOPoRXAhTJnze8fsD5SoafQyg9K1sNvtQvvfsAcGYyUT05myH1mMJxHNjJRgbO0DqdJr7u0OxELHFbXmYQe1bzthnfcld-8aZY15Dqr0BJQ0Ps6nt3XQrf94ctzUTsbxQ-NuWe_LhFs0YBUVNkGZuKqs5tI4gStJte1XUw_UoXehu-wTPG4m-u4j1SlToQtMDnNolOH6UeZo8bcmJWXYU1yf1HbiVfdrxV3jLfK8URy0eWoJMld2se69Aa5xk7dzWJPrwZlNQHBK2m3Ljh61R3g9HtbiNVotCOSDwHOvQEi1Kv4PBdZsn6bAD6hFeu-bVVlM4OVLKMNZ3ccnKu8_ks96Kk-Sj5POwF9PkY9ReeXJHZJQ3bxHJfSgjDd3GFPX4PNCzT4LadrUOieAAMq-v-n8dOiQ8YLGgabK7aY2FrACdcFFQqf00m1PmT-Qp8yxTzDlDk7NWBG08m_6m0TQfyGLUiKOAXMH0eRexiyyusIGtD6rNij0Tpf154D_KgiYVlW22CDoxPikVNGEybTjVN9rYuqOx0paFo08lEVLdseiMBx0w7PTfm90MuqHw2E4DNg9bZ_2uieD3nA4CkeDsHf24nQVsH989F737PRk9R18hbOG%3Ftype%3Dpng" width="1417" height="1507"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Remaining Challenges
&lt;/h2&gt;

&lt;p&gt;There's still a lot to learn by building with Midnight and Compact. Here are some issues I'm still working on:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. The Abandonment Problem (Griefing Attack)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If Player 1 commits and Player 2 never joins, the contract stays locked. I need to implement a timeout/cancel mechanism in a future update.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Version Management of Build Assets&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;ZK circuit assets (proving keys) generated by &lt;code&gt;bun contract compact&lt;/code&gt; can become invalid with even minor updates to the Compact version or compiler. &lt;/p&gt;

&lt;p&gt;I actually ran into this during development. If you're building a similar app, double-check your library and Proof Server versions!&lt;/p&gt;

&lt;p&gt;Solving these two will open up even more possibilities for ZK dApps on &lt;strong&gt;Midnight&lt;/strong&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Implementing this "fair" RPS app taught me three key lessons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Verify Versions&lt;/strong&gt;: Proof generation can fail due to mismatched Proof Server versions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't Forget &lt;code&gt;store_move_and_salt&lt;/code&gt;&lt;/strong&gt;: Saving private state is mandatory for the reveal phase.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;UX is Priority #1&lt;/strong&gt;: Designing a UI that handles the unique "waiting time" of ZK is critical.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Next, I'm planning to tackle multi-round support and timeout handling!&lt;/p&gt;

&lt;p&gt;If you're interested in Midnight's privacy tech, check out the repository and my other technical blogs!&lt;/p&gt;

&lt;p&gt;Thanks for reading!&lt;/p&gt;
&lt;h2&gt;
  
  
  Follow me on X!
&lt;/h2&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://x.com/haruki_web3" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;x.com&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;






&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://docs.midnight.network/" rel="noopener noreferrer"&gt;Midnight Documentation&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://docs.midnight.network/develop/reference/compact/lang-ref" rel="noopener noreferrer"&gt;Compact Language Reference&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://github.com/mashharuki/midnight-rps-sample-app" rel="noopener noreferrer"&gt;Midnight RPS Sample Code (GitHub)&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>zk</category>
      <category>blockchain</category>
      <category>typescript</category>
      <category>react</category>
    </item>
    <item>
      <title>Unleashing IronClaw: The Secure, Rust-Based AI Agent Framework by NearAI</title>
      <dc:creator>Haruki Kondo</dc:creator>
      <pubDate>Wed, 29 Apr 2026 07:29:12 +0000</pubDate>
      <link>https://dev.to/mashharuki/unleashing-ironclaw-the-secure-rust-based-ai-agent-framework-by-nearai-29ac</link>
      <guid>https://dev.to/mashharuki/unleashing-ironclaw-the-secure-rust-based-ai-agent-framework-by-nearai-29ac</guid>
      <description>&lt;h2&gt;
  
  
  Introduction: The Rise of Secure AI Agents
&lt;/h2&gt;

&lt;p&gt;By now, most of you in the AI space have probably heard of &lt;strong&gt;OpenClaw&lt;/strong&gt;. It’s the AI Agent framework that took the community by storm in early 2026.&lt;/p&gt;

&lt;p&gt;But have you heard of &lt;strong&gt;IronClaw&lt;/strong&gt;?&lt;/p&gt;

&lt;p&gt;While OpenClaw focused on accessibility and rapid prototyping, &lt;strong&gt;IronClaw&lt;/strong&gt; is its security-hardened sibling, built from the ground up in &lt;strong&gt;Rust&lt;/strong&gt;. Developed by the team at &lt;strong&gt;Near AI&lt;/strong&gt;, it’s designed for developers who need autonomous agents that can handle sensitive data—like passwords and API keys—without exposing them to the LLM.&lt;/p&gt;

&lt;p&gt;In this post, we’ll dive deep into what makes IronClaw the "Iron Man suit" for your AI agents.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is IronClaw?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr06066obdlyj1i4fsmns.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr06066obdlyj1i4fsmns.png" alt="IronClaw Overview" width="800" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;IronClaw&lt;/strong&gt; is an open-source AI Agent framework developed by &lt;strong&gt;Near AI&lt;/strong&gt;. It is designed to run in secure environments like &lt;strong&gt;NEAR AI Cloud&lt;/strong&gt;, allowing agents to autonomously operate tools (Google Workspace, GitHub, etc.) while keeping user credentials strictly confidential.&lt;/p&gt;

&lt;h3&gt;
  
  
  About Near AI
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.near.org/ai" rel="noopener noreferrer"&gt;Near AI Official Website&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Near AI started as a prominent Layer 1 blockchain project (once considered a potential "Ethereum killer"). Today, it has evolved into an AI-native public blockchain. &lt;/p&gt;

&lt;p&gt;Fun fact: One of its founders, Illia Polosukhin, is a co-author of the seminal &lt;strong&gt;"Attention Is All You Need"&lt;/strong&gt; paper—the research that introduced the &lt;strong&gt;Transformer&lt;/strong&gt; architecture to the world.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Features of IronClaw
&lt;/h2&gt;

&lt;p&gt;Running IronClaw on the NEAR AI Cloud provides several hardware-level security benefits that standard frameworks lack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;TEE (Trusted Execution Environment)&lt;/strong&gt;: Agents run in encrypted, isolated enclaves.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data Confidentiality&lt;/strong&gt;: Memory data is always encrypted. Not even the cloud provider (NEAR AI) can peek into your conversations or memory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verifiability&lt;/strong&gt;: Hardware-level isolation (like GPU attestation) ensures private and verifiable computing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secrets Protection&lt;/strong&gt;: An "Encrypted Vault" integrates with the infrastructure to inject credentials into tools without the LLM ever seeing the raw API keys.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Capabilities at a Glance
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Benefit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Security&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Encrypted Vault / WASM Isolation&lt;/td&gt;
&lt;td&gt;Secrets are stored safely; tools run in sandboxes.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Connectivity&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Multi-channel Support&lt;/td&gt;
&lt;td&gt;Operate via Terminal, Telegram, Discord, or Webhooks.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Memory&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Persistent Memory&lt;/td&gt;
&lt;td&gt;Agents remember past conversations and documents for RAG.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Automation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Scheduled Jobs&lt;/td&gt;
&lt;td&gt;Automate tasks like "Generate a report every morning at 9 AM."&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Extensibility&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Tool Ecosystem&lt;/td&gt;
&lt;td&gt;Easily add tools for Google Docs, Calendar, GitHub, and Web Search.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The 4-Layer Security Architecture
&lt;/h2&gt;

&lt;p&gt;IronClaw employs a "Defense in Depth" strategy, ensuring data passes through four independent protection layers before reaching any external service.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Safety Layer&lt;/strong&gt;: Prevents prompt injection, sanitizes content, and scans for over 15 types of secret patterns.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;WASM Sandbox&lt;/strong&gt;: A lightweight execution environment for tools with strict memory limits (default 16MB) and "Fuel Metering" to prevent infinite loops.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Docker Sandbox&lt;/strong&gt;: Container-level isolation for complex jobs and untrusted script execution.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Secret Management&lt;/strong&gt;: A "Zero-Exposure" model using AES-256-GCM encryption where credentials are only injected at the host boundary.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  WASM vs. Docker Sandbox: Which is which?
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;WASM Sandbox&lt;/th&gt;
&lt;th&gt;Docker Sandbox&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Runtime&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;wasmtime&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Container Virtualization&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Primary Use&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Secure Tool Execution&lt;/td&gt;
&lt;td&gt;Code Execution, Builds, Scripts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Footprint&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Extremely Lightweight&lt;/td&gt;
&lt;td&gt;Moderate Resource Usage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Limits&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Fuel Metering (Instructions)&lt;/td&gt;
&lt;td&gt;CPU / Memory (Standard)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Secret Access&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Injected via Proxy&lt;/td&gt;
&lt;td&gt;Environment Variables / Volumes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Getting Started with IronClaw
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Access the Agent Dashboard
&lt;/h3&gt;

&lt;p&gt;Head over to the &lt;a href="https://agent.near.ai/" rel="noopener noreferrer"&gt;Near AI Agent Dashboard&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Choose Your Plan
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkoqxx2jm7ajt3j9zhmrn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkoqxx2jm7ajt3j9zhmrn.png" alt="Plan Selection" width="800" height="443"&gt;&lt;/a&gt;&lt;br&gt;
The &lt;strong&gt;Starter&lt;/strong&gt; plan is free and perfect for getting your feet wet.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. One-Click Deployment
&lt;/h3&gt;

&lt;p&gt;Click the &lt;strong&gt;Deploy&lt;/strong&gt; button to spin up your IronClaw instance on NEAR AI Cloud.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F85spgeh1e69q9740jh8r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F85spgeh1e69q9740jh8r.png" alt="Deployment" width="800" height="444"&gt;&lt;/a&gt;&lt;br&gt;
The default LLM is typically set to &lt;strong&gt;Qwen&lt;/strong&gt;, but you can customize this.&lt;/p&gt;
&lt;h3&gt;
  
  
  4. Experiment!
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwzanjbggmb9ehrj7hhjc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwzanjbggmb9ehrj7hhjc.png" alt="Dashboard" width="800" height="444"&gt;&lt;/a&gt;&lt;br&gt;
Try chatting with your agent, adding new Skills, or connecting MCP (Model Context Protocol) servers.&lt;/p&gt;
&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;With a team led by a Transformer co-author, it’s no surprise that IronClaw is a masterclass in combining AI utility with rigorous security. Its deployment process is arguably one of the smoothest in the ecosystem.&lt;/p&gt;

&lt;p&gt;If you’re looking to build an agent that does more than just chat—an agent you can actually trust with your digital life—IronClaw is the way to go.&lt;/p&gt;

&lt;p&gt;Thanks for reading!&lt;/p&gt;
&lt;h2&gt;
  
  
  Join the Hackathon!
&lt;/h2&gt;

&lt;p&gt;Near AI is proud to sponsor the &lt;strong&gt;OpenClaw Hackathon&lt;/strong&gt; on May 2nd! &lt;/p&gt;

&lt;p&gt;&lt;a href="https://luma.com/zw01ink4?tk=VucpiH" rel="noopener noreferrer"&gt;Register Here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Using IronClaw in your project will definitely put you on the radar for the security-focused tracks. We can't wait to see what you build!&lt;/p&gt;
&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.ironclaw.com" rel="noopener noreferrer"&gt;IronClaw Official Site&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.ironclaw.com" rel="noopener noreferrer"&gt;Developer Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/nearai" rel="noopener noreferrer"&gt;
        nearai
      &lt;/a&gt; / &lt;a href="https://github.com/nearai/ironclaw" rel="noopener noreferrer"&gt;
        ironclaw
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      IronClaw is OpenClaw inspired implementation in Rust focused on privacy and security
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;p&gt;
  &lt;a rel="noopener noreferrer" href="https://github.com/nearai/ironclaw/ironclaw.png?v=2"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnearai%2Fironclaw%2FHEAD%2Fironclaw.png%3Fv%3D2" alt="IronClaw" width="200"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;IronClaw&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;
  &lt;strong&gt;Your secure personal AI assistant, always on your side&lt;/strong&gt;
&lt;/p&gt;

&lt;p&gt;
  &lt;a href="https://github.com/nearai/ironclaw#license" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/613ed9d36c84080e94c4e08acaa9d4500246bf9d9880c085ad985e106e433ce9/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542532304f52253230417061636865253230322e302d626c75652e737667" alt="License: MIT OR Apache-2.0"&gt;&lt;/a&gt;
  &lt;a href="https://t.me/ironclawAI" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/72fd9d04ce3bf72a9fc87ef546f2fd9a9c381e9fc9a2c54cf7baf9c3506c3e10/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f54656c656772616d2d25343069726f6e636c617741492d3236413545343f7374796c653d666c6174266c6f676f3d74656c656772616d266c6f676f436f6c6f723d7768697465" alt="Telegram: @ironclawAI"&gt;&lt;/a&gt;
  &lt;a href="https://www.reddit.com/r/ironclawAI/" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/6281a01b5238915cb37e1dc9ccd546a1637136d546e7d47b01081ea00f11b64d/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5265646469742d7225324669726f6e636c617741492d4646343530303f7374796c653d666c6174266c6f676f3d726564646974266c6f676f436f6c6f723d7768697465" alt="Reddit: r/ironclawAI"&gt;&lt;/a&gt;
  &lt;a href="https://gitcgr.com/nearai/ironclaw" rel="nofollow noopener noreferrer"&gt;
    &lt;img src="https://camo.githubusercontent.com/c783ae9a3bcbeeefa734bc325cbafbe39ebd1e2d8c21e171c28ea52f2ea91d1c/68747470733a2f2f6769746367722e636f6d2f62616467652f6e65617261692f69726f6e636c61772e737667" alt="gitcgr"&gt;
  &lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;
  &lt;a href="https://github.com/nearai/ironclaw/README.md" rel="noopener noreferrer"&gt;English&lt;/a&gt; |
  &lt;a href="https://github.com/nearai/ironclaw/README.zh-CN.md" rel="noopener noreferrer"&gt;简体中文&lt;/a&gt; |
  &lt;a href="https://github.com/nearai/ironclaw/README.ru.md" rel="noopener noreferrer"&gt;Русский&lt;/a&gt; |
  &lt;a href="https://github.com/nearai/ironclaw/README.ja.md" rel="noopener noreferrer"&gt;日本語&lt;/a&gt; |
  &lt;a href="https://github.com/nearai/ironclaw/README.ko.md" rel="noopener noreferrer"&gt;한국어&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;
  &lt;a href="https://github.com/nearai/ironclaw#philosophy" rel="noopener noreferrer"&gt;Philosophy&lt;/a&gt; •
  &lt;a href="https://github.com/nearai/ironclaw#features" rel="noopener noreferrer"&gt;Features&lt;/a&gt; •
  &lt;a href="https://github.com/nearai/ironclaw#installation" rel="noopener noreferrer"&gt;Installation&lt;/a&gt; •
  &lt;a href="https://github.com/nearai/ironclaw#configuration" rel="noopener noreferrer"&gt;Configuration&lt;/a&gt; •
  &lt;a href="https://github.com/nearai/ironclaw#security" rel="noopener noreferrer"&gt;Security&lt;/a&gt; •
  &lt;a href="https://github.com/nearai/ironclaw#architecture" rel="noopener noreferrer"&gt;Architecture&lt;/a&gt;
&lt;/p&gt;




&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Philosophy&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;IronClaw is built on a simple principle: &lt;strong&gt;your AI assistant should work for you, not against you&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In a world where AI systems are increasingly opaque about data handling and aligned with corporate interests, IronClaw takes a different approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Your data stays yours&lt;/strong&gt; - All information is stored locally, encrypted, and never leaves your control&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transparency by design&lt;/strong&gt; - Open source, auditable, no hidden telemetry or data harvesting&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Self-expanding capabilities&lt;/strong&gt; - Build new tools on the fly without waiting for vendor updates&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Defense in depth&lt;/strong&gt; - Multiple security layers protect against prompt injection and data exfiltration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;IronClaw is the AI assistant you can actually trust with your personal and professional life.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Features&lt;/h2&gt;
&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Security First&lt;/h3&gt;

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;WASM Sandbox&lt;/strong&gt; - Untrusted…&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/nearai/ironclaw" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://agent.near.ai/" rel="noopener noreferrer"&gt;Near AI Agent Dashboard&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>rust</category>
      <category>security</category>
      <category>openclaw</category>
    </item>
    <item>
      <title>Building a Full-Stack ZK-Privacy App on Midnight: A Step-by-Step Guide</title>
      <dc:creator>Haruki Kondo</dc:creator>
      <pubDate>Sun, 26 Apr 2026 13:11:02 +0000</pubDate>
      <link>https://dev.to/midnight-aliit/building-a-full-stack-zk-privacy-app-on-midnight-a-step-by-step-guide-1nne</link>
      <guid>https://dev.to/midnight-aliit/building-a-full-stack-zk-privacy-app-on-midnight-a-step-by-step-guide-1nne</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Hello everyone! 🚀&lt;/p&gt;

&lt;p&gt;In this post, we are diving deep into &lt;strong&gt;Midnight&lt;/strong&gt;, the privacy-focused blockchain!&lt;/p&gt;

&lt;p&gt;Previously, we covered how to connect a frontend application to &lt;strong&gt;Lace Wallet&lt;/strong&gt;. Now, we’re taking the next big step: &lt;strong&gt;connecting to a smart contract!&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;Implementing a full-stack ZK (Zero-Knowledge) application can be tricky, but I’ve navigated the pitfalls and version mismatches so you don't have to. Let’s get started!&lt;/p&gt;

&lt;h3&gt;
  
  
  What You Will Learn
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;How to develop a full-stack application on Midnight.&lt;/li&gt;
&lt;li&gt;Concrete source code for calling smart contract functions via Lace Wallet.&lt;/li&gt;
&lt;li&gt;Real-world "gotchas" and how to solve them.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Sample App: A Full-Stack Counter
&lt;/h2&gt;

&lt;p&gt;We’ll build a simple app that connects to Lace Wallet, displays balances, and interacts with a &lt;strong&gt;Counter&lt;/strong&gt; smart contract.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.lace.io/" rel="noopener noreferrer"&gt;Lace Wallet&lt;/a&gt; browser extension (Midnight-compatible version).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PreProd&lt;/strong&gt; network selected in Lace settings.&lt;/li&gt;
&lt;li&gt;Some test &lt;strong&gt;NIGHT&lt;/strong&gt; tokens from the &lt;a href="https://midnight-tmnight-preprod.nethermind.dev/" rel="noopener noreferrer"&gt;official faucet&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Docker installed (for running the Proof Server).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  GitHub Repository
&lt;/h3&gt;

&lt;p&gt;Check out the full source code here:&lt;br&gt;
&lt;a href="https://github.com/mashharuki/midnight-sample-fullstack-app" rel="noopener noreferrer"&gt;https://github.com/mashharuki/midnight-sample-fullstack-app&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Application Preview
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fin5ossccqr2661fx4mbo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fin5ossccqr2661fx4mbo.png" width="800" height="1139"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Technical Features
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Contract&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Build&lt;/td&gt;
&lt;td&gt;Compiles Compact files into WASM/ZKIR/Managed Code.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Contract&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Simulator&lt;/td&gt;
&lt;td&gt;Logic verification using &lt;code&gt;CounterSimulator&lt;/code&gt; without a live network.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CLI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Deploy&lt;/td&gt;
&lt;td&gt;Deploys to Standalone/TestNet and retrieves the contract address.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CLI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Interaction&lt;/td&gt;
&lt;td&gt;Direct CLI commands to increment and check counter values.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;App&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Wallet Connect&lt;/td&gt;
&lt;td&gt;Integration with Lace Wallet extension to fetch account data.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;App&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Sync&lt;/td&gt;
&lt;td&gt;Automatically joins and fetches current state from a contract address.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;App&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;ZK Increment&lt;/td&gt;
&lt;td&gt;One-click UI to generate ZK proofs and send transactions.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h3&gt;
  
  
  Tech Stack
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Technology&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Frontend&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;React 19, Vite 5, Tailwind CSS v4, Lucide React, RxJS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Contract&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Compact (Midnight's ZK DSL)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SDK&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;@midnight-ntwrk/*&lt;/code&gt; (SDK v2 / DApp Connector API v4)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Infrastructure&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Midnight Node, Indexer, Proof Server (via Docker)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tooling&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Bun, Biome, Vitest, TypeScript&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;


&lt;h2&gt;
  
  
  Deep Dive: Key Implementation Logic
&lt;/h2&gt;

&lt;p&gt;The core logic resides in &lt;code&gt;src/lib/counter.ts&lt;/code&gt; and &lt;code&gt;src/hooks/useCounter.ts&lt;/code&gt;. This was the most challenging part of the build due to SDK version transitions and compatibility issues with Lace Wallet.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;code&gt;src/lib/counter.ts&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This file handles the contract instance creation, state querying, and transaction calls.&lt;/p&gt;
&lt;h4&gt;
  
  
  1. Contract Instance Creation
&lt;/h4&gt;

&lt;p&gt;We use the SDK to wrap the compiled Compact contract.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;CompactJs&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@midnight-ntwrk/compact-js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Initialize the compiled contract instance&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;counterContractInstance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CounterContract&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CompactJs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CompiledContract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;counter&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Contract&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;CompactJs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CompiledContract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;withVacantWitnesses&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  2. Querying State
&lt;/h4&gt;

&lt;p&gt;Using &lt;code&gt;queryContractState&lt;/code&gt;, we can fetch the ledger data (in this case, the &lt;code&gt;round&lt;/code&gt; number of our counter).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getCounterValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CounterProviders&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;contractAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ContractAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;bigint&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;assertIsContractAddress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;contractAddress&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;contractState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;publicDataProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;queryContractState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;contractAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;contractState&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ledger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;contractState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;round&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  3. Calling Updates (Transactions)
&lt;/h4&gt;

&lt;p&gt;The magic happens in &lt;code&gt;callTx&lt;/code&gt;. This triggers the ZK proof generation and requests a signature from Lace Wallet.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;incrementCounter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;counterContract&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DeployedCounterContract&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Call the increment() method on the contract&lt;/span&gt;
  &lt;span class="k"&gt;await &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;counterContract&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;callTx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Getting Started: Run it Locally
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Clone &amp;amp; Install
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/mashharuki/midnight-lace-react-sample-app.git
&lt;span class="nb"&gt;cd &lt;/span&gt;midnight-lace-react-sample-app
bun &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Start the Proof Server
&lt;/h3&gt;

&lt;p&gt;Midnight requires a &lt;strong&gt;Proof Server&lt;/strong&gt; to generate ZK Proofs locally before sending transactions to the network.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;pkgs/cli
&lt;span class="c"&gt;# Start the Proof Server (Ensure it's version 8.0.3)&lt;/span&gt;
docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; standalone.yml up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Build &amp;amp; Deploy the Contract
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bun contract build

&lt;span class="c"&gt;# Deploy to the PreProd network&lt;/span&gt;
bun cli preprod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Note: Copy the contract address displayed in the terminal!&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Setup &amp;amp; Start the Frontend
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Set environment variables&lt;/span&gt;
&lt;span class="nb"&gt;cp &lt;/span&gt;pkgs/app/.env.local.example pkgs/app/.env

&lt;span class="c"&gt;# Build and start&lt;/span&gt;
bun app build
bun app dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Visit &lt;code&gt;localhost:5173&lt;/code&gt;. You should see the following:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq5dy8nm5b72k3r841vv8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq5dy8nm5b72k3r841vv8.png" width="800" height="443"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Connect your wallet.&lt;/li&gt;
&lt;li&gt;Enter your deployed contract address and click &lt;strong&gt;Join&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Increment&lt;/strong&gt;. You will be prompted to sign the transaction.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4k71ervscjq4y4p5uqvp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4k71ervscjq4y4p5uqvp.png" width="702" height="1296"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Wait a few moments for the ZK proof generation and network finalization. Once done, refresh to see the updated value!&lt;/p&gt;




&lt;h2&gt;
  
  
  Sequence Diagrams
&lt;/h2&gt;

&lt;p&gt;Unlike traditional blockchains, Midnight involves a local &lt;strong&gt;ZK Proof&lt;/strong&gt; generation step in every state-changing transaction.&lt;/p&gt;

&lt;h3&gt;
  
  
  Contract Deployment Flow
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://mermaid.live/edit#pako:eNptkt9v2jAQx_8Vyw9TJ6UIktAQP1TqYJtQ12oalSZVefHsI1hN7MyxEQzxv-9MEtSW8hAud5_v_codqDASKKMt_PWgBSwULy2vC03w13DrlFAN144sYEt4G_6gMg1YcjX_sfx8yf3mVQUuoKvFff92ST1i1cA8KKlVuekcl9hSS9hhrddk7yt0h2ND17e3-GRkbvRalWQEeks-kW9eyzf1O7LzMCKhqcweJc5y4a6E8dqBXerWcVxDPxhG1JY7eJOns1-l-uU1mSsrvHJY9ztosEHzfE9-WmPW71RhUkZW_k-N-JPlug1VTD-OhA9rBtH1MOfTjtwJAY0DOUQRN1tcVOCiYUWMfKmMeMHVaad0ib2dAmh2sh475115zNq2IZ1aK8FDVygadkTupLQYpxGtwdZcSbybQ0hVULeBGgrK0JTcvhS00EfkfCNxkq9SOWMpW_OqhYhy78xqrwVlznoYoP7wBiecNA_dcZ5uNKLW-HJzToMH8mzMWVDa0E9nW8DJ7Dx8UsryE0rZge4oy7JRPEtv8kmW5DdJlqQR3VMWJ9kom6TjSZKn01k6TY4R_XfKPRmNx3E-izEynWXpOE6O_wELMw1I" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fpako%3AeNptkt9v2jAQx_8Vyw9TJ6UIktAQP1TqYJtQ12oalSZVefHsI1hN7MyxEQzxv-9MEtSW8hAud5_v_codqDASKKMt_PWgBSwULy2vC03w13DrlFAN144sYEt4G_6gMg1YcjX_sfx8yf3mVQUuoKvFff92ST1i1cA8KKlVuekcl9hSS9hhrddk7yt0h2ND17e3-GRkbvRalWQEeks-kW9eyzf1O7LzMCKhqcweJc5y4a6E8dqBXerWcVxDPxhG1JY7eJOns1-l-uU1mSsrvHJY9ztosEHzfE9-WmPW71RhUkZW_k-N-JPlug1VTD-OhA9rBtH1MOfTjtwJAY0DOUQRN1tcVOCiYUWMfKmMeMHVaad0ib2dAmh2sh475115zNq2IZ1aK8FDVygadkTupLQYpxGtwdZcSbybQ0hVULeBGgrK0JTcvhS00EfkfCNxkq9SOWMpW_OqhYhy78xqrwVlznoYoP7wBiecNA_dcZ5uNKLW-HJzToMH8mzMWVDa0E9nW8DJ7Dx8UsryE0rZge4oy7JRPEtv8kmW5DdJlqQR3VMWJ9kom6TjSZKn01k6TY4R_XfKPRmNx3E-izEynWXpOE6O_wELMw1I%3Ftype%3Dpng" width="943" height="548"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Increment Function Call Flow
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://mermaid.live/edit#pako:eNptkl1P2zAUhv-K5atOCpULSWh8gTTBNKGNKSKrkFBuvOQ0sUjszHH4WNX_jo9dSkfxjY_sx6_f87Ghla6BcjrC3wlUBVdSNEb0pSJuDcJYWclBKEtWIxgixrDP7uAPWV1_OcZ-igoQ8_ud6Dqwx9DXYUCmxn12C6KynyjlBTK50XpNCjCPYI6ZX848UjeyVrJpw0GpAohOTy4u3CecXHayeiAlvVaVgR6ULWmA3K1j0C0nt1iD0ZLfz6SQjRJ2MhAovD9xHEpyUrT6CR8a_Sg6kuthGv77MagF4IOAN4PiUJMrYcWhibx4txDS_g4KjLBSKzK7_xEOd5XKi73cjnKKHjiUxHK4_0DVmNSTtO0hg7fvWV3qSVnX29VQezHXmHUHFYZSYbNpRHswvZC1m5cNSpTUtq6aJeUurIV5wKpuHTd5jW-1tNpQvhbdCBEVk9XFi6oot2aCN2g3cHsK_KObMJV-OCNq9NS0e8J1_l7r_k2mMWgoxMZlCsZnQvk58yzlG_pMebaYJyyLGVskWZbF2TKiL5SfptmcnSUsTrMkSTHYRvSfV2fz5XmapXG8XLAzxrLT7Suq3QUO" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fpako%3AeNptkl1P2zAUhv-K5atOCpULSWh8gTTBNKGNKSKrkFBuvOQ0sUjszHH4WNX_jo9dSkfxjY_sx6_f87Ghla6BcjrC3wlUBVdSNEb0pSJuDcJYWclBKEtWIxgixrDP7uAPWV1_OcZ-igoQ8_ud6Dqwx9DXYUCmxn12C6KynyjlBTK50XpNCjCPYI6ZX848UjeyVrJpw0GpAohOTy4u3CecXHayeiAlvVaVgR6ULWmA3K1j0C0nt1iD0ZLfz6SQjRJ2MhAovD9xHEpyUrT6CR8a_Sg6kuthGv77MagF4IOAN4PiUJMrYcWhibx4txDS_g4KjLBSKzK7_xEOd5XKi73cjnKKHjiUxHK4_0DVmNSTtO0hg7fvWV3qSVnX29VQezHXmHUHFYZSYbNpRHswvZC1m5cNSpTUtq6aJeUurIV5wKpuHTd5jW-1tNpQvhbdCBEVk9XFi6oot2aCN2g3cHsK_KObMJV-OCNq9NS0e8J1_l7r_k2mMWgoxMZlCsZnQvk58yzlG_pMebaYJyyLGVskWZbF2TKiL5SfptmcnSUsTrMkSTHYRvSfV2fz5XmapXG8XLAzxrLT7Suq3QUO%3Ftype%3Dpng" width="1185" height="579"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Building with ZK-privacy is a paradigm shift. While there were hurdles—especially around SDK versions and wallet compatibility—overcoming them allows you to build applications that truly respect user privacy.&lt;/p&gt;

&lt;p&gt;I even created a custom AI Agent skill to help debug the Midnight SDK logic, which made the final stretch much smoother! You can find my Claude skills in the repo if you're interested.&lt;/p&gt;

&lt;p&gt;Happy coding, and see you in the next one where we might tackle some hackathon challenges! 🛡️✨&lt;/p&gt;

&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.midnight.network/" rel="noopener noreferrer"&gt;Midnight Official Site&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.midnight.network/" rel="noopener noreferrer"&gt;Midnight Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>typescript</category>
      <category>blockchain</category>
      <category>zk</category>
    </item>
    <item>
      <title>Build a Simple App That Connects to Midnight Lace Wallet</title>
      <dc:creator>Haruki Kondo</dc:creator>
      <pubDate>Fri, 24 Apr 2026 12:35:58 +0000</pubDate>
      <link>https://dev.to/midnight-aliit/build-a-simple-app-that-connects-to-midnight-lace-wallet-4682</link>
      <guid>https://dev.to/midnight-aliit/build-a-simple-app-that-connects-to-midnight-lace-wallet-4682</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;Hello everyone!&lt;/p&gt;

&lt;p&gt;In this article, we will look at &lt;strong&gt;Midnight&lt;/strong&gt;, a privacy-focused blockchain, and its dedicated wallet, &lt;strong&gt;Lace Wallet&lt;/strong&gt;.&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://www.lace.io/" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.sanity.io%2Fimages%2Ftnb8oobx%2Fproduction%2F434b20b0ce779725521c1890bd857b5b2dac6c65-1201x628.jpg" height="418" class="m-0" width="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://www.lace.io/" rel="noopener noreferrer" class="c-link"&gt;
            Lace | The light wallet platform to explore Web3
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Discover the new light wallet platform from Input Output Global. Manage digital assets, and access NFTs, DApps, and DeFi services. Start your Web3 journey.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.lace.io%2Ffavicon.ico" width="16" height="16"&gt;
          lace.io
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;The official documentation explains how to deploy and run contracts, but I could not find clear guidance on connecting &lt;strong&gt;Lace Wallet&lt;/strong&gt; to a frontend app. I ran into a few tricky points while trying it, so I created a simple beginner-friendly sample app.&lt;/p&gt;

&lt;p&gt;I hope you enjoy it!&lt;/p&gt;

&lt;h2&gt;
  
  
  What You Will Learn
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A quick overview of Lace Wallet&lt;/li&gt;
&lt;li&gt;How to build a simple app that connects to Lace Wallet&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  What Is Lace Wallet?
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Lace Wallet&lt;/strong&gt; is a lightweight wallet for storing and managing Cardano-related digital assets. It was announced by &lt;strong&gt;IOG&lt;/strong&gt;, the team behind Cardano, on June 9, 2022.&lt;/p&gt;

&lt;p&gt;It can manage digital assets on blockchains in the Cardano ecosystem, including &lt;strong&gt;Midnight&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Even though it is lightweight, it offers rich features such as staking and asset transfers.&lt;/p&gt;

&lt;p&gt;It is currently optimized for the Cardano ecosystem, and it appears support for other ecosystems is planned in the future.&lt;/p&gt;

&lt;h1&gt;
  
  
  Build a Simple App That Connects to Lace Wallet
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before running the sample code, make sure you have the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;a href="https://www.lace.io/" rel="noopener noreferrer"&gt;Lace Wallet&lt;/a&gt; browser extension installed (a version with Midnight support)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PreProd&lt;/strong&gt; selected in Lace network settings&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Sample Code Repository
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/mashharuki" rel="noopener noreferrer"&gt;
        mashharuki
      &lt;/a&gt; / &lt;a href="https://github.com/mashharuki/midnight-lace-react-sample-app" rel="noopener noreferrer"&gt;
        midnight-lace-react-sample-app
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      midnight-lace-react-sample-app
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;midnight-lace-react-sample-app&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;A sample dApp frontend for connecting to the Midnight Network (PreProd).&lt;br&gt;
Use Lace Wallet to connect your wallet, view your shielded address, and check your balance.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Screenshots&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/mashharuki/midnight-lace-react-sample-app/./docs/imgs/1.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmashharuki%2Fmidnight-lace-react-sample-app%2FHEAD%2F.%2Fdocs%2Fimgs%2F1.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/mashharuki/midnight-lace-react-sample-app/./docs/imgs/2.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmashharuki%2Fmidnight-lace-react-sample-app%2FHEAD%2F.%2Fdocs%2Fimgs%2F2.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Features&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Lace Wallet Connection&lt;/td&gt;
&lt;td&gt;Detects and connects to &lt;code&gt;window.midnight.mnLace&lt;/code&gt; via 100ms polling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Version Validation&lt;/td&gt;
&lt;td&gt;Verifies that the Connector API version is &lt;code&gt;&amp;gt;=1.0.0&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Network Detection&lt;/td&gt;
&lt;td&gt;Automatically tries networks in order: PreProd / mainnet / undeployed / preview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Address Display&lt;/td&gt;
&lt;td&gt;Shows the shielded address in a copyable format&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Balance Display&lt;/td&gt;
&lt;td&gt;Shows Shielded / Unshielded / Dust balances in tDUST units&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language Toggle&lt;/td&gt;
&lt;td&gt;Instantly switch between Japanese and English via the top-right button (persisted in localStorage)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Tech Stack&lt;/h2&gt;

&lt;/div&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Library / Tool&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Framework&lt;/td&gt;
&lt;td&gt;React 19 + TypeScript&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Build&lt;/td&gt;
&lt;td&gt;Vite 8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Styling&lt;/td&gt;
&lt;td&gt;Tailwind CSS v4 (&lt;code&gt;@tailwindcss/vite&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UI Components&lt;/td&gt;
&lt;td&gt;shadcn/ui (Button, Badge, Card) + Lucide React&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Internationalization&lt;/td&gt;
&lt;td&gt;i18next 26 + react-i18next 17&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wallet Integration&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@midnight-ntwrk/dapp-connector-api&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Async Processing&lt;/td&gt;
&lt;td&gt;RxJS 7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Package Manager&lt;/td&gt;
&lt;td&gt;Bun&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Formatter&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;…&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/mashharuki/midnight-lace-react-sample-app" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  App Preview
&lt;/h2&gt;

&lt;p&gt;This sample app is intentionally simple: connect Lace Wallet and display balances.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4g2tingzcgy201qvk6df.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4g2tingzcgy201qvk6df.png" width="800" height="443"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft5r4okw69gai74xgnkpx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft5r4okw69gai74xgnkpx.png" width="800" height="442"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature List
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Lace Wallet connection&lt;/td&gt;
&lt;td&gt;Detects and connects to &lt;code&gt;window.midnight.mnLace&lt;/code&gt; using 100ms polling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Version check&lt;/td&gt;
&lt;td&gt;Confirms Connector API version is &lt;code&gt;&amp;gt;=1.0.0&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Network validation&lt;/td&gt;
&lt;td&gt;Automatically tries PreProd / mainnet / undeployed / preview in order&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Address display&lt;/td&gt;
&lt;td&gt;Shows a copyable shielded address&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Balance display&lt;/td&gt;
&lt;td&gt;Shows Shielded / Unshielded / Dust balances in tDUST&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language switch&lt;/td&gt;
&lt;td&gt;Instantly toggles Japanese ⇆ English from the top-right button (persisted via localStorage)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Tech Stack
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Library / Tool&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Framework&lt;/td&gt;
&lt;td&gt;React 19 + TypeScript&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Build&lt;/td&gt;
&lt;td&gt;Vite 8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Styling&lt;/td&gt;
&lt;td&gt;Tailwind CSS v4 (&lt;code&gt;@tailwindcss/vite&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UI Components&lt;/td&gt;
&lt;td&gt;shadcn/ui (Button, Badge, Card) + Lucide React&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Internationalization&lt;/td&gt;
&lt;td&gt;i18next 26 + react-i18next 17&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wallet Integration&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@midnight-ntwrk/dapp-connector-api&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Async Processing&lt;/td&gt;
&lt;td&gt;RxJS 7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Package Manager&lt;/td&gt;
&lt;td&gt;Bun&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Formatter&lt;/td&gt;
&lt;td&gt;Biome&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Key Source Code Explanations
&lt;/h2&gt;

&lt;p&gt;The overall architecture is similar to many other blockchain apps.&lt;/p&gt;

&lt;p&gt;If you understand the SDK and API specifications, you can build a working connection app quickly.&lt;/p&gt;

&lt;p&gt;The key SDK in this project is &lt;code&gt;@midnight-ntwrk/dapp-connector-api&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It is used to implement the wallet connection logic.&lt;/p&gt;

&lt;p&gt;All important connection-related logic is consolidated in the file below.&lt;/p&gt;

&lt;p&gt;A key point is around line 103.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Attempt connection. If a network mismatch occurs, fall back to the next candidate.&lt;/span&gt;
&lt;span class="nx"&gt;walletAPI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;connector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;networkId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Lace v4: getConfiguration() is on walletAPI (not connector)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;walletRaw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;walletAPI&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From &lt;code&gt;walletRaw&lt;/code&gt;, you can extract wallet information such as addresses.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;coinPublicKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;encryptionPublicKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;walletRaw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getShieldedAddresses&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;function&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// getShieldedAddresses() may return an array (old versions) or a single object (new versions), so handle both cases&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;walletRaw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getShieldedAddresses&lt;/span&gt; &lt;span class="k"&gt;as &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)();&lt;/span&gt;
  &lt;span class="c1"&gt;// Lace v4 returns a single object (not array):&lt;/span&gt;
  &lt;span class="c1"&gt;// { shieldedAddress, shieldedCoinPublicKey, shieldedEncryptionPublicKey }&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt;
    &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shieldedAddress&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;coinPublicKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shieldedCoinPublicKey&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;coinPublicKey&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;encryptionPublicKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shieldedEncryptionPublicKey&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;encryptionPublicKey&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Balance retrieval is implemented as a dedicated React hook.&lt;/p&gt;

&lt;p&gt;The most important part is the &lt;code&gt;fetchBalances&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;It fetches balances from three wallet-related methods:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;raw.getShieldedBalances&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;raw.getUnshieldedBalances&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;raw.getDustBalance&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;formatBalance&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/lib/utils&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;BalanceState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/utils/types&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;DAppConnectorWalletAPI&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@midnight-ntwrk/dapp-connector-api&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;BalanceState&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Fetch shielded / unshielded / dust balances in parallel from the wallet API.
 *
 * The Lace SDK type definitions do not include these balance methods,
 * so this function casts to unknown and checks method availability dynamically.
 * Promise.allSettled allows us to receive remaining results even if one call fails.
 */&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fetchBalances&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;walletAPI&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DAppConnectorWalletAPI&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;shielded&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;unshielded&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;dust&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;walletAPI&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Try fetching balances in parallel. If a method is unavailable, return null.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;shieldedResult&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unshieldedResult&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dustResult&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;allSettled&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
      &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getShieldedBalances&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;function&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getShieldedBalances&lt;/span&gt; &lt;span class="k"&gt;as &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)()&lt;/span&gt;
        &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getUnshieldedBalances&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;function&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getUnshieldedBalances&lt;/span&gt; &lt;span class="k"&gt;as &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)()&lt;/span&gt;
        &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getDustBalance&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;function&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getDustBalance&lt;/span&gt; &lt;span class="k"&gt;as &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)()&lt;/span&gt;
        &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;shielded&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nx"&gt;shieldedResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fulfilled&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;formatBalance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shieldedResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;--&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;unshielded&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nx"&gt;unshieldedResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fulfilled&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;formatBalance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;unshieldedResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;--&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;dust&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nx"&gt;dustResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fulfilled&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;formatBalance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dustResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;--&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Custom hook to manage loading and refreshing balances.
 *
 * @param walletAPI - Connected wallet API. If null, no action is taken.
 * @returns balanceState: current balance state / refresh: manual refresh trigger
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;useBalance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;walletAPI&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DAppConnectorWalletAPI&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;balanceState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setBalanceState&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;BalanceState&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;idle&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="cm"&gt;/**
   * Fetch balances from the wallet API and update state.
   * If walletAPI is null, do nothing.
   * Set status to "loading" while fetching, "loaded" on success with balances,
   * and "error" if anything fails.
   */&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;refresh&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;walletAPI&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;setBalanceState&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;loading&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;balances&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchBalances&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;walletAPI&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;setBalanceState&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;loaded&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;balances&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;[useBalance] Failed to fetch balances:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;setBalanceState&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;walletAPI&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;balanceState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;refresh&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  How to Run the App
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Clone
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/mashharuki/midnight-lace-react-sample-app.git

&lt;span class="c"&gt;# Move to the frontend app directory&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bun &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Build
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bun run build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Start
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bun run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;That is all for this tutorial.&lt;/p&gt;

&lt;p&gt;Now you have a clear path to connect a &lt;strong&gt;React + Vite&lt;/strong&gt; app with &lt;strong&gt;Lace Wallet&lt;/strong&gt;, which is a solid step toward building a full-stack Midnight application.&lt;/p&gt;

&lt;p&gt;In a future article, I plan to cover how to call functions on a smart contract deployed on &lt;strong&gt;Midnight&lt;/strong&gt; and continue toward a full-stack implementation.&lt;/p&gt;

&lt;p&gt;Thank you for reading!&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.midnight.network/" rel="noopener noreferrer"&gt;Midnight Official Website&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.midnight.network/" rel="noopener noreferrer"&gt;Midnight Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nagamaru-panda.blog/?p=828" rel="noopener noreferrer"&gt;What Is Lace, the New Lightweight Wallet Developed by IOG?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>web3</category>
      <category>tutorial</category>
      <category>typescript</category>
      <category>midnight</category>
    </item>
    <item>
      <title>[Hands-on] Midnight Deep Dive: Start Building Smart Contracts with Compact</title>
      <dc:creator>Haruki Kondo</dc:creator>
      <pubDate>Tue, 14 Apr 2026 15:25:20 +0000</pubDate>
      <link>https://dev.to/midnight-aliit/hands-on-midnight-deep-dive-start-building-smart-contracts-with-compact-1inb</link>
      <guid>https://dev.to/midnight-aliit/hands-on-midnight-deep-dive-start-building-smart-contracts-with-compact-1inb</guid>
      <description>&lt;h1&gt;
  
  
  Introduction: The "Too Transparent" Problem of Blockchains
&lt;/h1&gt;

&lt;p&gt;Since the Bitcoin whitepaper was published, blockchain technology has transformed many industries, from finance to supply chains, thanks to its trustless design, transparency, and tamper resistance.&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://bitcoin.org/bitcoin.pdf" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;bitcoin.org&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;The idea that anyone can validate the same distributed ledger and form a trust network without centralized administrators was truly revolutionary.&lt;/p&gt;

&lt;p&gt;But complete transparency can also become a major weakness.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What if confidential enterprise data is exposed to competitors?&lt;/li&gt;
&lt;li&gt;What if personal transaction history is visible to the entire world?&lt;/li&gt;
&lt;li&gt;What if private medical records or voting behavior can be inspected by anyone?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That "too transparent" problem has been one of the biggest barriers preventing public blockchain technology from being widely adopted in enterprise and daily consumer use cases.&lt;/p&gt;

&lt;p&gt;To solve this dilemma, a breakthrough project has emerged from the Cardano ecosystem.&lt;/p&gt;

&lt;p&gt;That project is &lt;strong&gt;Midnight&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Midnight is a Cardano sidechain focused on data protection and privacy.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;By leveraging cutting-edge cryptography known as Zero-Knowledge Proofs (ZKPs)[^1], Midnight enables us to prove only the facts we need, without revealing anything else.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;ZKPs are often discussed in the context of privacy, but they can also reduce computational overhead depending on how they are used.&lt;/p&gt;

&lt;p&gt;Representative examples include &lt;strong&gt;ZkEVM&lt;/strong&gt; and &lt;strong&gt;INTMAX&lt;/strong&gt;, both of which use ZK-based approaches for EVM-related scaling.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://intmax.io/" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fframerusercontent.com%2Fimages%2FLLXZAAN66nJM7LQNKLniS8xz0.png" height="auto" class="m-0"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://intmax.io/" rel="noopener noreferrer" class="c-link"&gt;
            INTMAX – Stateless Layer for Billions
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            INTMAX is a stateless, privacy-centric Layer 2 on Ethereum that delivers fast, secure, and cost-efficient transactions. Explore the future of private digital payments and empower your transactions with unmatched anonymity and speed.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fframerusercontent.com%2Fimages%2FMdj4kIdvZCP2j0CzIFQ18v2DLcQ.svg"&gt;
          intmax.io
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;I recently joined the Midnight Hackathon in London and spent a lot of time exploring Compact. In this article, I share what I learned in a hands-on format: from environment setup to contract implementation, testing, and deployment.&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://midnightsummit.io/" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmidnightsummit.io%2Fwp-content%2Fuploads%2F2025%2F10%2Fwebsite-image-1450x984-1-scaled.png" height="auto" class="m-0"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://midnightsummit.io/" rel="noopener noreferrer" class="c-link"&gt;
            Home - Midnight Summit 2025
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Midnight Summit 2025: An invitation-only ecosystem event with talks, breakouts and a live Hackathon shaping the future of privacy-first blockchain.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmidnightsummit.io%2Fwp-content%2Fuploads%2F2025%2F10%2FArtboard-1-1-150x150.png"&gt;
          midnightsummit.io
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;If you want a conceptual introduction to Midnight first, check this article:&lt;/p&gt;


&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/midnight-aliit/the-future-of-privacy-a-deep-dive-into-cardanos-midnight-zero-knowledge-proofs-1nn9" class="crayons-story__hidden-navigation-link"&gt;The Future of Privacy: A Deep Dive into Cardano's Midnight &amp;amp; Zero-Knowledge Proofs&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;
          &lt;a class="crayons-logo crayons-logo--l" href="/midnight-aliit"&gt;
            &lt;img alt="Midnight Aliit Fellowship logo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F12781%2Fd7b48f1d-42ed-4b7a-9dd2-6595b3b750f7.png" class="crayons-logo__image"&gt;
          &lt;/a&gt;

          &lt;a href="/mashharuki" class="crayons-avatar  crayons-avatar--s absolute -right-2 -bottom-2 border-solid border-2 border-base-inverted  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F986561%2Fded098d5-9fd3-49b5-8045-594b7174fec3.JPG" alt="mashharuki profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/mashharuki" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Haruki Kondo
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Haruki Kondo
                
              
              &lt;div id="story-author-preview-content-3495075" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/mashharuki" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F986561%2Fded098d5-9fd3-49b5-8045-594b7174fec3.JPG" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Haruki Kondo&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

            &lt;span&gt;
              &lt;span class="crayons-story__tertiary fw-normal"&gt; for &lt;/span&gt;&lt;a href="/midnight-aliit" class="crayons-story__secondary fw-medium"&gt;Midnight Aliit Fellowship&lt;/a&gt;
            &lt;/span&gt;
          &lt;/div&gt;
          &lt;a href="https://dev.to/midnight-aliit/the-future-of-privacy-a-deep-dive-into-cardanos-midnight-zero-knowledge-proofs-1nn9" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Apr 13&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/midnight-aliit/the-future-of-privacy-a-deep-dive-into-cardanos-midnight-zero-knowledge-proofs-1nn9" id="article-link-3495075"&gt;
          The Future of Privacy: A Deep Dive into Cardano's Midnight &amp;amp; Zero-Knowledge Proofs
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/blockchain"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;blockchain&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/web3"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;web3&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/typescript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;typescript&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/zkp"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;zkp&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/midnight-aliit/the-future-of-privacy-a-deep-dive-into-cardanos-midnight-zero-knowledge-proofs-1nn9" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;2&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/midnight-aliit/the-future-of-privacy-a-deep-dive-into-cardanos-midnight-zero-knowledge-proofs-1nn9#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            4 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;[^1]: Zero-Knowledge Proof (ZKP) is a cryptographic method that lets you prove a statement is true without revealing any additional information (including why it is true).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Compact: A Privacy Smart Contract Language with TypeScript-like Syntax
&lt;/h1&gt;

&lt;p&gt;Another core pillar behind Midnight's innovation is its smart contract language, &lt;strong&gt;Compact&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Isn't zero-knowledge technology only for cryptography experts?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Compact is designed to dramatically lower that barrier.&lt;/p&gt;

&lt;h2&gt;
  
  
  TypeScript-based syntax
&lt;/h2&gt;

&lt;p&gt;The biggest feature of Compact is that it is a &lt;strong&gt;TypeScript-based domain-specific language (DSL)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That means a large number of web developers can build privacy-preserving applications with familiar syntax, instead of learning an entirely new language from scratch.&lt;/p&gt;

&lt;p&gt;The Compact compiler translates your logic into the cryptographic components needed for zero-knowledge proofs, so you do not have to deal with the underlying math directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three data states: Public, Private, Witness
&lt;/h2&gt;

&lt;p&gt;The core of data handling in Compact is clear separation of privacy levels.&lt;/p&gt;

&lt;p&gt;Data is mainly handled in three states:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://mermaid.live/edit#pako:eNp9Ustu2zAQ_JUFL20BWbAejmy5CJDYzi1FgaSXRjnQ0loiIpECH3Zcw9-QQ3stChT9wH5CKdE23BQoD-KSO5oZDnZHclEgSUkpaVvB_TzjYJcyS3eRkU8KJYjVapBXlHFAvmZS8Aa5zogDd-vq4fePby_QSramGlP46ApQ2n7fL-Ul-qUPxnK9UdCiVILTGnJhuEb5CIPBJVxPHR3y4h8T95JyRXPNBAfGW3OmfW2Vf32HDdMcleqUhVg50Em32VpnuGbCKFjT2iBsqIKR053tZkzmhmnAZ8xNp7H_j5VbVnBWVhqWtcifXChvBXfxvDvPBObW2tef0JplzXLrrN9fRaKFtkGYtujS6vM4uHptYdZfLx4-4ObIdNcxPU7P-zeuf0j_L8CiB8wPp5v-dDUlHmlQNpQVdgh2XS8jusIGM5LasqDyqXvT3uKcyUXBtJAkXdFaoUeo0eJuy3OSamnwCJozavNqTijsf7p1o9ZPnEekMGV1QrSUfxaiOdKUsjPkamkzQDnrsiFpEPVYku7IM0mTxA_H8cUkSKLJRZREsUe2JA2jxE-CeBhEk3g0jkfR3iNfevLAHw7DyTi0ndE4iYdhtP8DPDP2hw" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fpako%3AeNp9Ustu2zAQ_JUFL20BWbAejmy5CJDYzi1FgaSXRjnQ0loiIpECH3Zcw9-QQ3stChT9wH5CKdE23BQoD-KSO5oZDnZHclEgSUkpaVvB_TzjYJcyS3eRkU8KJYjVapBXlHFAvmZS8Aa5zogDd-vq4fePby_QSramGlP46ApQ2n7fL-Ul-qUPxnK9UdCiVILTGnJhuEb5CIPBJVxPHR3y4h8T95JyRXPNBAfGW3OmfW2Vf32HDdMcleqUhVg50Em32VpnuGbCKFjT2iBsqIKR053tZkzmhmnAZ8xNp7H_j5VbVnBWVhqWtcifXChvBXfxvDvPBObW2tef0JplzXLrrN9fRaKFtkGYtujS6vM4uHptYdZfLx4-4ObIdNcxPU7P-zeuf0j_L8CiB8wPp5v-dDUlHmlQNpQVdgh2XS8jusIGM5LasqDyqXvT3uKcyUXBtJAkXdFaoUeo0eJuy3OSamnwCJozavNqTijsf7p1o9ZPnEekMGV1QrSUfxaiOdKUsjPkamkzQDnrsiFpEPVYku7IM0mTxA_H8cUkSKLJRZREsUe2JA2jxE-CeBhEk3g0jkfR3iNfevLAHw7DyTi0ndE4iYdhtP8DPDP2hw%3Ftype%3Dpng"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;public&lt;/code&gt; (public state)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Data visible on the blockchain.&lt;/li&gt;
&lt;li&gt;Similar to state variables in conventional smart contracts.&lt;/li&gt;
&lt;li&gt;Defined with the &lt;code&gt;ledger&lt;/code&gt; keyword.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;private&lt;/code&gt; (private state)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Confidential data managed only in a user's local (off-chain) environment.&lt;/li&gt;
&lt;li&gt;The raw private data itself is not recorded on-chain.&lt;/li&gt;
&lt;li&gt;Defined with the &lt;code&gt;private&lt;/code&gt; keyword.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;witness&lt;/code&gt; (proof input)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Input supplied during transaction execution to prove, "I know this data."&lt;/li&gt;
&lt;li&gt;Used as evidence when updating private state.&lt;/li&gt;
&lt;li&gt;Defined with the &lt;code&gt;witness&lt;/code&gt; keyword.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Basic syntax and a Counter smart contract example
&lt;/h3&gt;

&lt;p&gt;Let's look at these concepts through a simple Counter contract.&lt;/p&gt;

&lt;p&gt;This contract only increments a number, and is also introduced in official tutorials.&lt;/p&gt;

&lt;p&gt;It includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A state variable stored on the public ledger&lt;/li&gt;
&lt;li&gt;A method to increment that state
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;pragma&lt;/span&gt; &lt;span class="nx"&gt;language_version&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.16&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mf"&gt;0.25&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;CompactStandardLibrary&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Public state stored on the on-chain ledger&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="nx"&gt;ledger&lt;/span&gt; &lt;span class="nx"&gt;round&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Transition function that updates public state&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="nx"&gt;circuit&lt;/span&gt; &lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;round&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;ledger&lt;/code&gt;&lt;/strong&gt;:
A publicly visible on-chain state variable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;circuit&lt;/code&gt;&lt;/strong&gt;:
A transaction-invoked state transition function where validation and updates happen.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With Compact, you can write logic in TypeScript-like syntax while controlling data privacy in detail and proving correctness intuitively.&lt;/p&gt;
&lt;h2&gt;
  
  
  Hands-on: Set up your Midnight development environment
&lt;/h2&gt;

&lt;p&gt;Now let's move from theory to practice.&lt;/p&gt;

&lt;p&gt;In this section, we build the environment needed to run &lt;code&gt;counter.compact&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;As of November 2025, frontend integration is still unstable.&lt;/p&gt;

&lt;p&gt;So in this hands-on, the goal is to deploy a smart contract and interact with it via CLI.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Required components:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Compact CLI&lt;/strong&gt;
Command-line tool for compiling and testing smart contracts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lace Midnight Preview Wallet&lt;/strong&gt;
Browser extension wallet for Midnight Testnet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testnet Faucet&lt;/strong&gt;
Service to get test tokens.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ZK Proof Server&lt;/strong&gt;
Local server for generating and verifying zero-knowledge proofs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sample repository&lt;/strong&gt;
Source code used in this tutorial.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  Step 1: Install Compact CLI
&lt;/h3&gt;

&lt;p&gt;First, install the &lt;code&gt;compact&lt;/code&gt; CLI compiler:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--proto&lt;/span&gt; &lt;span class="s1"&gt;'=https'&lt;/span&gt; &lt;span class="nt"&gt;--tlsv1&lt;/span&gt;.2 &lt;span class="nt"&gt;-LsSf&lt;/span&gt; https://github.com/midnightntwrk/compact/releases/latest/download/compact-installer.sh | sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Then pin a specific version (&lt;code&gt;0.25.0&lt;/code&gt; in this article):&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;compact update 0.25.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Check installation:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;compact &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;span class="c"&gt;# compact 0.2.0 or similar&lt;/span&gt;
compact compile &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;span class="c"&gt;# 0.25.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;If &lt;code&gt;compact compile --version&lt;/code&gt; returns &lt;code&gt;0.25.0&lt;/code&gt;, you're good.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 2: Prepare Lace Wallet and get Testnet tokens
&lt;/h2&gt;

&lt;p&gt;Next, set up a wallet and receive test tokens.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Install Lace Wallet&lt;/strong&gt;
Add &lt;a href="https://chromewebstore.google.com/detail/lace-midnight-preview/hgeekaiplokcnmakghbdfbgnlfheichg" rel="noopener noreferrer"&gt;Lace Midnight Preview&lt;/a&gt; from the Chrome Web Store.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create your wallet&lt;/strong&gt;
Follow the setup wizard. Store your recovery phrase securely.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Copy your address&lt;/strong&gt;
From the wallet home screen, click "Receive" and copy your address.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Request faucet funds&lt;/strong&gt;
Open &lt;a href="https://midnight.network/test-faucet" rel="noopener noreferrer"&gt;Midnight Testnet Faucet&lt;/a&gt;, paste your address, and click "Request funds". After a short wait, test &lt;code&gt;tDUST&lt;/code&gt; tokens should arrive.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  Step 3: Start ZK Proof Server
&lt;/h3&gt;

&lt;p&gt;Private processing (including proof generation) is handled through a local Proof Server.&lt;/p&gt;

&lt;p&gt;We'll start the official Midnight Docker image:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Docker Desktop must be installed for this step.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-p&lt;/span&gt; 6300:6300 midnightnetwork/proof-server &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="s1"&gt;'midnight-proof-server --network testnet'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;If logs start streaming, it launched successfully. Keep this terminal running.&lt;/p&gt;

&lt;p&gt;You can also verify with:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; GET &lt;span class="s2"&gt;"http://localhost:6300"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Expected response:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;We&lt;span class="s1"&gt;'re alive 🎉!
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Step 4: Prepare sample repository
&lt;/h2&gt;

&lt;p&gt;Finally, set up the repository used in this article:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/mashharuki" rel="noopener noreferrer"&gt;
        mashharuki
      &lt;/a&gt; / &lt;a href="https://github.com/mashharuki/midnight-sample" rel="noopener noreferrer"&gt;
        midnight-sample
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      midnightでの開発事前検証用リポジトリ
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;midnight-sample&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;midnightでの開発事前検証用リポジトリ&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;環境&lt;/h2&gt;
&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;nodejs&lt;/li&gt;
&lt;li&gt;yarn&lt;/li&gt;
&lt;li&gt;docker&lt;/li&gt;
&lt;li&gt;compact CLI&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;compact CLIのインストール&lt;/h2&gt;
&lt;/div&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;curl --proto &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;=https&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt; --tlsv1.2 -LsSf https://github.com/midnightntwrk/compact/releases/latest/download/compact-installer.sh &lt;span class="pl-k"&gt;|&lt;/span&gt; sh&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;その後、以下でバージョン指定&lt;/p&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;compact update 0.25.0&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;インストールされているかの確認&lt;/p&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;compact --version
compact compile --version&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;それぞれ以下のようになればOK!&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;compact 0.2.0
0.25.0&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Testnet用のZKProof serverの起動&lt;/h2&gt;

&lt;/div&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;docker run -p 6300:6300 midnightnetwork/proof-server -- &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;midnight-proof-server --network testnet&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;docker ps&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;localhost:6300でサーバーが起動していればOK&lt;/p&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;CONTAINER ID   IMAGE                          COMMAND                  CREATED          STATUS          PORTS                                         NAMES
a62d9787f7a1   midnightnetwork/proof-server   &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;/nix/store/qa9fb15p…&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;   25 seconds ago   Up 24 seconds   0.0.0.0:6300-&lt;span class="pl-k"&gt;&amp;gt;&lt;/span&gt;6300/tcp, [::]:6300-&lt;span class="pl-k"&gt;&amp;gt;&lt;/span&gt;6300/tcp   flamboyant_roentgen&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;念の為以下のコマンドでも稼働確認&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;curl -X GET &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;http://localhost:6300&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;We&lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;re alive 🎉!&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;サンプルプログラムのコンパイル＆デプロイ手順&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;まず、依存関係をインストールする&lt;/p&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;yarn&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;以下のコマンドでコントラクトをビルドする&lt;/p&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;yarn contract compact&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;以下のようになればOK!&lt;/p&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;Fetching public parameters &lt;span class="pl-k"&gt;for&lt;/span&gt; k=10 [&lt;span class="pl-k"&gt;====================&lt;/span&gt;] 192.38 KiB / 192.38 KiB
  circuit &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;increment&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt; (k=10, rows=29)  
Overall progress [&lt;span class="pl-k"&gt;====================&lt;/span&gt;] 1/1   &lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;コントラクトのユニットテストコードを実行する&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;yarn contract &lt;span class="pl-c1"&gt;test&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;以下のようになればOK!&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt; RUN  v4.0.8 /workspaces/midnight-sample/my-mn-app/pkgs/contract
 ✓ test/counter.test.ts (3 tests) 44ms
   ✓ Counter smart contract (3)
     ✓ generates initial ledger state deterministically 36ms
     ✓ properly initializes ledger&lt;/pre&gt;…
&lt;/div&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/mashharuki/midnight-sample" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Clone your own fork&lt;/span&gt;
&lt;span class="c"&gt;# (fork the repository first)&lt;/span&gt;
git clone https://github.com/&amp;lt;user-name&amp;gt;/midnight-sample.git
&lt;span class="nb"&gt;cd &lt;/span&gt;midnight-sample

&lt;span class="c"&gt;# Install dependencies&lt;/span&gt;
yarn
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Replace the &lt;code&gt;git clone&lt;/code&gt; URL with your actual repository URL.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Environment setup is now complete.&lt;/p&gt;

&lt;p&gt;Next, let's implement and test the smart contract.&lt;/p&gt;

&lt;h1&gt;
  
  
  Implement and test the Counter contract
&lt;/h1&gt;

&lt;p&gt;With the environment ready, let's build and test.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code walkthrough
&lt;/h2&gt;

&lt;p&gt;Put this contract in &lt;code&gt;pkgs/contract/src/counter.compact&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;pragma&lt;/span&gt; &lt;span class="nx"&gt;language_version&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.16&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mf"&gt;0.25&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;CompactStandardLibrary&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Public state stored on the on-chain ledger&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="nx"&gt;ledger&lt;/span&gt; &lt;span class="nx"&gt;round&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Transition function that updates public state&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="nx"&gt;circuit&lt;/span&gt; &lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;round&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then compile with &lt;code&gt;compact&lt;/code&gt; CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn contract compact
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Under the hood:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;compact compile ./src/counter.compact ./src/managed/counter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On success, you will see output like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Fetching public parameters &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="nv"&gt;k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10 &lt;span class="o"&gt;[====================]&lt;/span&gt; 192.38 KiB / 192.38 KiB
  circuit &lt;span class="s2"&gt;"increment"&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10, &lt;span class="nv"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;29&lt;span class="o"&gt;)&lt;/span&gt;
Overall progress &lt;span class="o"&gt;[====================]&lt;/span&gt; 1/1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Unit test implementation
&lt;/h2&gt;

&lt;p&gt;Compact lets you simulate contract logic off-chain for testing.&lt;/p&gt;

&lt;p&gt;See &lt;code&gt;pkgs/contract/src/test/counter.test.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CounterSimulator&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./counter-simulator.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;NetworkId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;setNetworkId&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@midnight-ntwrk/midnight-js-network-id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vitest&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;setNetworkId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;NetworkId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Undeployed&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Unit tests for the Counter contract
 */&lt;/span&gt;
&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Counter smart contract&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;generates initial ledger state deterministically&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;simulator0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CounterSimulator&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;simulator1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CounterSimulator&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;simulator0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getLedger&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;simulator1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getLedger&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;properly initializes ledger state and private state&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;simulator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CounterSimulator&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;initialLedgerState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;simulator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getLedger&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;initialLedgerState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;round&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;initialPrivateState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;simulator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPrivateState&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;initialPrivateState&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;privateCounter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;increments the counter correctly&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;simulator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CounterSimulator&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nextLedgerState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;simulator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nextLedgerState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;round&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nextPrivateState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;simulator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPrivateState&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nextPrivateState&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;privateCounter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These tests verify three scenarios:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Contract initialization is deterministic.&lt;/li&gt;
&lt;li&gt;Initial ledger value is &lt;code&gt;0&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;increment&lt;/code&gt; updates the counter correctly.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Run tests
&lt;/h2&gt;

&lt;p&gt;Run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn contract &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If all tests pass, output looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;RUN  v4.0.8 /workspaces/midnight-sample/my-mn-app/pkgs/contract

 ✓ &lt;span class="nb"&gt;test&lt;/span&gt;/counter.test.ts &lt;span class="o"&gt;(&lt;/span&gt;3 tests&lt;span class="o"&gt;)&lt;/span&gt; 44ms
   ✓ Counter smart contract &lt;span class="o"&gt;(&lt;/span&gt;3&lt;span class="o"&gt;)&lt;/span&gt;
     ✓ generates initial ledger state deterministically 36ms
     ✓ properly initializes ledger state and private state 3ms
     ✓ increments the counter correctly 4ms

 Test Files  1 passed &lt;span class="o"&gt;(&lt;/span&gt;1&lt;span class="o"&gt;)&lt;/span&gt;
      Tests  3 passed &lt;span class="o"&gt;(&lt;/span&gt;3&lt;span class="o"&gt;)&lt;/span&gt;
   Start at  08:27:47
   Duration  421ms &lt;span class="o"&gt;(&lt;/span&gt;transform 95ms, setup 0ms, collect 233ms, tests 44ms, environment 0ms, prepare 13ms&lt;span class="o"&gt;)&lt;/span&gt;

JUNIT report written to /workspaces/midnight-sample/my-mn-app/pkgs/contract/reports/report.xml
Done &lt;span class="k"&gt;in &lt;/span&gt;1.34s.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that we know the logic works, let's create the CLI flow to deploy on Testnet.&lt;/p&gt;

&lt;h1&gt;
  
  
  Deploy and execute from CLI on Testnet
&lt;/h1&gt;

&lt;p&gt;After local testing, it's time to deploy.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;pkgs/cli&lt;/code&gt; package contains scripts for deployment and contract interaction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generate TypeScript API
&lt;/h2&gt;

&lt;p&gt;First, build the &lt;code&gt;contract&lt;/code&gt; package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn contract build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This runs commands like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; dist &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; tsc &lt;span class="nt"&gt;--project&lt;/span&gt; tsconfig.build.json &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="nt"&gt;-Rf&lt;/span&gt; ./src/managed ./dist/managed &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cp&lt;/span&gt; ./src/counter.compact ./dist
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This generates typed APIs so circuits like &lt;code&gt;increment&lt;/code&gt; can be called safely from the &lt;code&gt;cli&lt;/code&gt; package.&lt;/p&gt;

&lt;h2&gt;
  
  
  Set environment variables
&lt;/h2&gt;

&lt;p&gt;Deploying to Testnet requires the wallet seed used to sign transactions.&lt;/p&gt;

&lt;p&gt;Create &lt;code&gt;.env&lt;/code&gt; from template in &lt;code&gt;pkgs/cli&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cp &lt;/span&gt;pkgs/cli/.env.example pkgs/cli/.env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Edit &lt;code&gt;pkgs/cli/.env&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NETWORK_ENV_VAR=testnet
SEED_ENV_VAR=
INITIAL_COUNTER_ENV_VAR=
CACHE_FILE_ENV_VAR=
CONTRACT_ADDRESS=
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Handle your seed with extreme care.&lt;/p&gt;

&lt;p&gt;Make sure this file is excluded by &lt;code&gt;.gitignore&lt;/code&gt; and never pushed to GitHub.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  CLI unit test walkthrough
&lt;/h2&gt;

&lt;p&gt;There are also unit tests for the CLI layer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// This file is part of midnightntwrk/example-counter.&lt;/span&gt;
&lt;span class="c1"&gt;// Copyright (C) 2025 Midnight Foundation&lt;/span&gt;
&lt;span class="c1"&gt;// SPDX-License-Identifier: Apache-2.0&lt;/span&gt;
&lt;span class="c1"&gt;// Licensed under the Apache License, Version 2.0 (the "License");&lt;/span&gt;
&lt;span class="c1"&gt;// You may not use this file except in compliance with the License.&lt;/span&gt;
&lt;span class="c1"&gt;// You may obtain a copy of the License at&lt;/span&gt;
&lt;span class="c1"&gt;//&lt;/span&gt;
&lt;span class="c1"&gt;// http://www.apache.org/licenses/LICENSE-2.0&lt;/span&gt;
&lt;span class="c1"&gt;//&lt;/span&gt;
&lt;span class="c1"&gt;// Unless required by applicable law or agreed to in writing, software&lt;/span&gt;
&lt;span class="c1"&gt;// distributed under the License is distributed on an "AS IS" BASIS,&lt;/span&gt;
&lt;span class="c1"&gt;// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.&lt;/span&gt;
&lt;span class="c1"&gt;// See the License for the specific language governing permissions and&lt;/span&gt;
&lt;span class="c1"&gt;// limitations under the License.&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Resource&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@midnight-ntwrk/wallet&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Wallet&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@midnight-ntwrk/wallet-api&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;path&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../api&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;CounterProviders&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../utils/common-types&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;currentDir&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createLogger&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../utils/logger-utils&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;TestEnvironment&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./commons&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;beforeAll&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;afterAll&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vitest&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;logDir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;..&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;logs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tests&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;.log`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;logDir&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;API&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="na"&gt;testEnvironment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TestEnvironment&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="na"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Wallet&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;Resource&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="na"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CounterProviders&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;beforeAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;testEnvironment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TestEnvironment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;testConfiguration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;testEnvironment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nx"&gt;wallet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;testEnvironment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getWallet&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nx"&gt;providers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configureProviders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;testConfiguration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dappConfig&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;afterAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;testEnvironment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;saveWalletCache&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;testEnvironment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shutdown&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should deploy the contract and increment the counter [@slow]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;counterContract&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deploy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;privateCounter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;counterContract&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;not&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toBeNull&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;displayCounterValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;counterContract&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;counterValue&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;BigInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;counterContract&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;txHash&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toMatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;0-9a-f&lt;/span&gt;&lt;span class="se"&gt;]{64}&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;blockHeight&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBeGreaterThan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;BigInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;counterAfter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;displayCounterValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;counterContract&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;counterAfter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;counterValue&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;BigInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;counterAfter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contractAddress&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contractAddress&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run these tests on both local and testnet environments.&lt;/p&gt;

&lt;h3&gt;
  
  
  Run unit tests locally
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn cli test-api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expected:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Test Files  1 passed &lt;span class="o"&gt;(&lt;/span&gt;1&lt;span class="o"&gt;)&lt;/span&gt;
      Tests  1 passed &lt;span class="o"&gt;(&lt;/span&gt;1&lt;span class="o"&gt;)&lt;/span&gt;
   Start at  08:41:12
   Duration  200.97s &lt;span class="o"&gt;(&lt;/span&gt;transform 180ms, setup 72ms, collect 1.11s, tests 199.62s, environment 0ms, prepare 10ms&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Run unit tests against testnet
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn cli test-against-testnet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expected:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;✓ src/test/counter.api.test.ts &lt;span class="o"&gt;(&lt;/span&gt;1 &lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; 151857ms
  ✓ API &lt;span class="o"&gt;(&lt;/span&gt;1&lt;span class="o"&gt;)&lt;/span&gt;
    ✓ should deploy the contract and increment the counter &lt;span class="o"&gt;[&lt;/span&gt;@slow]  125059ms

Test Files  1 passed &lt;span class="o"&gt;(&lt;/span&gt;1&lt;span class="o"&gt;)&lt;/span&gt;
    Tests  1 passed &lt;span class="o"&gt;(&lt;/span&gt;1&lt;span class="o"&gt;)&lt;/span&gt;
  Start at  08:47:54
  Duration  153.65s &lt;span class="o"&gt;(&lt;/span&gt;transform 205ms, setup 93ms, collect 1.56s, tests 151.86s, environment 0ms, prepare 8ms&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Deployment script walkthrough
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;pkgs/cli/scripts/deploy.ts&lt;/code&gt; deploys the contract to Testnet.&lt;/p&gt;

&lt;p&gt;It uses libraries such as &lt;code&gt;@midnight-ntwrk/midnight-sdk&lt;/code&gt; and performs the following steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Load wallet seed from &lt;code&gt;.env&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Build wallet object from the seed.&lt;/li&gt;
&lt;li&gt;Configure providers for Testnet connection.&lt;/li&gt;
&lt;li&gt;Execute deployment via &lt;code&gt;api.deploy&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Run deployment
&lt;/h2&gt;

&lt;p&gt;Deploy with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn cli deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On success, the deployed contract address appears in terminal output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;12:16:24.603] INFO &lt;span class="o"&gt;(&lt;/span&gt;39506&lt;span class="o"&gt;)&lt;/span&gt;: Deploying counter contract...
&lt;span class="o"&gt;[&lt;/span&gt;12:17:27.488] INFO &lt;span class="o"&gt;(&lt;/span&gt;39506&lt;span class="o"&gt;)&lt;/span&gt;: Deployed contract at address: 020050e6bdae4c9e65023a252a6aba74323c1d9c1ba6e520f00e84a5fc1c75b100f3
&lt;span class="o"&gt;[&lt;/span&gt;12:17:27.488] INFO &lt;span class="o"&gt;(&lt;/span&gt;39506&lt;span class="o"&gt;)&lt;/span&gt;: Deployment transaction: 00000000c408a293e4e287285649623774b2be950bf0d385a20117ce79a99eb7315aa547
&lt;span class="o"&gt;[&lt;/span&gt;12:17:27.489] INFO &lt;span class="o"&gt;(&lt;/span&gt;39506&lt;span class="o"&gt;)&lt;/span&gt;: Contract address: 020050e6bdae4c9e65023a252a6aba74323c1d9c1ba6e520f00e84a5fc1c75b100f3
Counter contract deployed at: 020050e6bdae4c9e65023a252a6aba74323c1d9c1ba6e520f00e84a5fc1c75b100f3
&lt;span class="o"&gt;[&lt;/span&gt;12:17:27.489] INFO &lt;span class="o"&gt;(&lt;/span&gt;39506&lt;span class="o"&gt;)&lt;/span&gt;: Not saving cache as &lt;span class="nb"&gt;sync &lt;/span&gt;cache was not defined
Done &lt;span class="k"&gt;in &lt;/span&gt;90.16s.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set that value in &lt;code&gt;CONTRACT_ADDRESS&lt;/code&gt; inside your &lt;code&gt;.env&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Execute &lt;code&gt;increment&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Finally, call the deployed contract's &lt;code&gt;increment&lt;/code&gt; circuit.&lt;/p&gt;

&lt;p&gt;Run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn cli increment
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This script reads &lt;code&gt;CONTRACT_ADDRESS&lt;/code&gt; from &lt;code&gt;.env&lt;/code&gt;, connects to the existing contract with &lt;code&gt;api.joinContract&lt;/code&gt;, then calls &lt;code&gt;api.increment&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If successful, you should see transaction info and the current counter value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;12:33:37.176] INFO &lt;span class="o"&gt;(&lt;/span&gt;47085&lt;span class="o"&gt;)&lt;/span&gt;: Incrementing...
&lt;span class="o"&gt;[&lt;/span&gt;12:34:34.270] INFO &lt;span class="o"&gt;(&lt;/span&gt;47085&lt;span class="o"&gt;)&lt;/span&gt;: Transaction 000000000202acbcd05e9f19e5144acc5f97953255840b8b932fc71b84520e715b7ca900 added &lt;span class="k"&gt;in &lt;/span&gt;block 2485067
&lt;span class="o"&gt;[&lt;/span&gt;12:34:34.271] INFO &lt;span class="o"&gt;(&lt;/span&gt;47085&lt;span class="o"&gt;)&lt;/span&gt;: Increment transaction: 000000000202acbcd05e9f19e5144acc5f97953255840b8b932fc71b84520e715b7ca900 &lt;span class="o"&gt;(&lt;/span&gt;block 2485067&lt;span class="o"&gt;)&lt;/span&gt;
Counter incremented. &lt;span class="nv"&gt;txId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;000000000202acbcd05e9f19e5144acc5f97953255840b8b932fc71b84520e715b7ca900 &lt;span class="nv"&gt;block&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2485067
&lt;span class="o"&gt;[&lt;/span&gt;12:34:34.271] INFO &lt;span class="o"&gt;(&lt;/span&gt;47085&lt;span class="o"&gt;)&lt;/span&gt;: Checking contract ledger state...
&lt;span class="o"&gt;[&lt;/span&gt;12:34:34.462] INFO &lt;span class="o"&gt;(&lt;/span&gt;47085&lt;span class="o"&gt;)&lt;/span&gt;: Ledger state: 1
&lt;span class="o"&gt;[&lt;/span&gt;12:34:34.463] INFO &lt;span class="o"&gt;(&lt;/span&gt;47085&lt;span class="o"&gt;)&lt;/span&gt;: Current counter value: 1
&lt;span class="o"&gt;[&lt;/span&gt;12:34:34.463] INFO &lt;span class="o"&gt;(&lt;/span&gt;47085&lt;span class="o"&gt;)&lt;/span&gt;: Current counter value: 1
Current counter value: 1
&lt;span class="o"&gt;[&lt;/span&gt;12:34:34.463] INFO &lt;span class="o"&gt;(&lt;/span&gt;47085&lt;span class="o"&gt;)&lt;/span&gt;: Not saving cache as &lt;span class="nb"&gt;sync &lt;/span&gt;cache was not defined
Done &lt;span class="k"&gt;in &lt;/span&gt;128.20s.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;Current counter value: 1&lt;/code&gt; appears, your public counter was incremented successfully.&lt;/p&gt;

&lt;p&gt;This completes the hands-on section.&lt;/p&gt;

&lt;h2&gt;
  
  
  Current limitations and what's next
&lt;/h2&gt;

&lt;p&gt;As of November 2025, Midnight is still in &lt;strong&gt;developer Testnet&lt;/strong&gt; phase, and Mainnet has not launched yet.&lt;/p&gt;

&lt;p&gt;So keep the following in mind:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Performance&lt;/strong&gt;
Finality on Testnet can take time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API changes&lt;/strong&gt;
SDK/CLI behavior may change during active development. Check official docs regularly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feature scope&lt;/strong&gt;
Available tooling is still limited, though development is moving quickly with community feedback.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frontend integration&lt;/strong&gt;
This was the area that took me the most research time during the hackathon. I also confirmed with the Midnight team onsite that stable frontend-contract integration libraries were not yet available at that time, so CLI was the practical path.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Midnight is a very ambitious project tackling one of Web3's most important challenges: privacy.&lt;/p&gt;

&lt;p&gt;With Cardano's strong security model and community behind it, this ecosystem is definitely worth watching.&lt;/p&gt;

&lt;h1&gt;
  
  
  Closing thoughts
&lt;/h1&gt;

&lt;p&gt;In this article, we walked through &lt;strong&gt;Midnight&lt;/strong&gt;, Cardano's privacy-focused sidechain, and &lt;strong&gt;Compact&lt;/strong&gt;, its smart contract language, in a practical hands-on format.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Midnight's approach to the blockchain "too transparent" problem.&lt;/li&gt;
&lt;li&gt;Compact language for intuitive private DApp development with TypeScript-like syntax.&lt;/li&gt;
&lt;li&gt;Data modeling with &lt;code&gt;public&lt;/code&gt;, &lt;code&gt;private&lt;/code&gt;, and &lt;code&gt;witness&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Full flow from environment setup to implementation, testing, and Testnet deployment.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I am very excited to see how this evolves.&lt;/p&gt;

&lt;p&gt;The hackathon itself was also an amazing experience, so I will keep following upcoming updates.&lt;/p&gt;

&lt;p&gt;Thanks for reading.&lt;/p&gt;

&lt;h1&gt;
  
  
  References
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.midnight.network/" rel="noopener noreferrer"&gt;Midnight official website&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.midnight.network/" rel="noopener noreferrer"&gt;Midnight documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/midnightntwrk/compact" rel="noopener noreferrer"&gt;Compact GitHub repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/midnightntwrk/midnight-awesome-dapps" rel="noopener noreferrer"&gt;Midnight Awesome DApps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://chromewebstore.google.com/detail/lace-midnight-preview/hgeekaiplokcnmakghbdfbgnlfheichg" rel="noopener noreferrer"&gt;Lace Midnight Preview Wallet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://midnight.network/test-faucet" rel="noopener noreferrer"&gt;Midnight Testnet Faucet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://midnight-hackathon.devpost.com/" rel="noopener noreferrer"&gt;Midnight Hackathon (Devpost)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>web3</category>
      <category>zkp</category>
      <category>blockchain</category>
      <category>cardano</category>
    </item>
    <item>
      <title>The Future of Privacy: A Deep Dive into Cardano's Midnight &amp; Zero-Knowledge Proofs</title>
      <dc:creator>Haruki Kondo</dc:creator>
      <pubDate>Mon, 13 Apr 2026 14:08:48 +0000</pubDate>
      <link>https://dev.to/midnight-aliit/the-future-of-privacy-a-deep-dive-into-cardanos-midnight-zero-knowledge-proofs-1nn9</link>
      <guid>https://dev.to/midnight-aliit/the-future-of-privacy-a-deep-dive-into-cardanos-midnight-zero-knowledge-proofs-1nn9</guid>
      <description>&lt;h1&gt;
  
  
  Introduction: The "Too Transparent" Problem in Blockchain
&lt;/h1&gt;

&lt;p&gt;Seventeen years have passed since the Bitcoin whitepaper was released.&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://bitcoin.org/bitcoin.pdf" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;bitcoin.org&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Blockchain's immutability and transparency have been praised as groundbreaking mechanisms for trustworthy transactions. Because anyone can verify the ledger, blockchains created a new model of tamper-resistant trust.&lt;/p&gt;

&lt;p&gt;However, that same "complete transparency" also creates barriers in areas involving business and personal privacy. This issue has been especially clear in enterprise settings, which is one reason private chain adoption became popular.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What if your full banking transaction history were visible to anyone in the world?&lt;/li&gt;
&lt;li&gt;What if a company's confidential supply chain information were exposed to competitors?&lt;/li&gt;
&lt;li&gt;What if personal medical records or voting history became public?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even imagining this is unsettling. I believe this "too transparent" problem is one of the key reasons blockchain technology has not yet fully penetrated all parts of society.&lt;/p&gt;

&lt;p&gt;Not everything can be public all the time. To solve this dilemma, a new light has emerged: &lt;strong&gt;Midnight&lt;/strong&gt;.&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://docs.midnight.network/" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdocs.midnight.network%2Fimg%2Fog-image.png" height="420" class="m-0" width="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://docs.midnight.network/" rel="noopener noreferrer" class="c-link"&gt;
            Midnight Documentation | Midnight Docs
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Build privacy-preserving applications with selective disclosure and zero-knowledge proofs on Midnight.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdocs.midnight.network%2Fimg%2Ffavicon.ico" width="48" height="48"&gt;
          docs.midnight.network
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Midnight is Cardano's partner chain (sidechain) specialized for data protection and privacy. In this article, I will guide you through Midnight in a story-driven way so you can understand its innovative technology and future potential.&lt;/p&gt;

&lt;h1&gt;
  
  
  Chapter 1: What Is Midnight? - A New Blockchain for Privacy
&lt;/h1&gt;

&lt;p&gt;In one sentence, Midnight is a data-protection blockchain that enables &lt;strong&gt;Selective Disclosure&lt;/strong&gt;. While many traditional blockchains force everything to be public, Midnight makes it possible to &lt;strong&gt;prove only the facts that need to be proven, without revealing anything else&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;&lt;br&gt;
There are other blockchains, such as &lt;strong&gt;Zcash&lt;/strong&gt;, that adopt similar privacy-oriented approaches.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The technology that enables this is &lt;strong&gt;Zero-Knowledge Proofs (ZKPs)&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture Overview
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://mermaid.live/edit#pako:eNp1kU1vwjAMhv9K5NMmFdYvWtpJk8bXZUJC2k7QHUJjmog2QWkKYxX_fSkMkCbNJ8d6_L6200KuGEIKhaY7Tj4mmSQ26mb9W9CUCSOUpCUZlSrf5pwKeYG6eF1ZQtY07xjCqKGfpNd7IaN2ukd9NFzIgoia7Jp1KfLT86UTJfvjMxdMioIbchCGkyVq1XuT6lAiK5AstFKb-m46_sd00lpyj0TJ8kgOnJrOWSIyZFfnLqarBeq6W-mpRlnb9WzPXWX2MGtKK8AFYygf7yODAxXqigpm79V25QwMxwozSG3KqN5mkMmT5Zqd1cOpPZ3SkG5oWaMDtDHq_ShzSI1u8ApNBLUnqG4Unpvml185f44DWjUFvxE7KpdKVVeZQncDXXJtx0Q9Vo00kMb-mYW0hS_7ivv-MIwSLw6SKIiD0IEjpH4Q92MvdL0gCQfDcBCcHPg-i3t91_WToe_FkesmsR9Fpx9VmbJ2" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fpako%3AeNp1kU1vwjAMhv9K5NMmFdYvWtpJk8bXZUJC2k7QHUJjmog2QWkKYxX_fSkMkCbNJ8d6_L6200KuGEIKhaY7Tj4mmSQ26mb9W9CUCSOUpCUZlSrf5pwKeYG6eF1ZQtY07xjCqKGfpNd7IaN2ukd9NFzIgoia7Jp1KfLT86UTJfvjMxdMioIbchCGkyVq1XuT6lAiK5AstFKb-m46_sd00lpyj0TJ8kgOnJrOWSIyZFfnLqarBeq6W-mpRlnb9WzPXWX2MGtKK8AFYygf7yODAxXqigpm79V25QwMxwozSG3KqN5mkMmT5Zqd1cOpPZ3SkG5oWaMDtDHq_ShzSI1u8ApNBLUnqG4Unpvml185f44DWjUFvxE7KpdKVVeZQncDXXJtx0Q9Vo00kMb-mYW0hS_7ivv-MIwSLw6SKIiD0IEjpH4Q92MvdL0gCQfDcBCcHPg-i3t91_WToe_FkesmsR9Fpx9VmbJ2%3Ftype%3Dpng" width="1204" height="432"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Comparison of Traditional Blockchain vs Midnight with ZKPs&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I have another article dedicated to &lt;strong&gt;Zero-Knowledge Proofs (ZKPs)&lt;/strong&gt;, so feel free to check that out as well:&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://zenn.dev/mashharuki/articles/zk_groth16-plonk" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fzenn%2Fimage%2Fupload%2Fs--yQUTHF49--%2Fc_fit%252Cg_north_west%252Cl_text%3Anotosansjp-medium.otf_55%3A%2525E3%252580%252590%2525E5%25259B%2525B3%2525E8%2525A7%2525A3%2525E3%252580%252591%2525E3%252582%2525BC%2525E3%252583%2525AD%2525E7%25259F%2525A5%2525E8%2525AD%252598%2525E8%2525A8%2525BC%2525E6%252598%25258E%2525E3%252581%2525AEPLONK%2525E3%252581%2525A8%2525E3%252581%2525AF%2525EF%2525BC%25259FGroth16%2525E3%252581%2525A8%2525E3%252581%2525AE%2525E9%252581%252595%2525E3%252581%252584%2525E3%252582%252592%2525E4%2525B8%252596%2525E7%252595%25258C%2525E4%2525B8%252580%2525E3%252582%25258F%2525E3%252581%25258B%2525E3%252582%25258A%2525E3%252582%252584%2525E3%252581%252599%2525E3%252581%25258F%2525E8%2525A7%2525A3%2525E8%2525AA%2525AC%2525EF%2525BC%252581%252Cw_1010%252Cx_90%252Cy_100%2Fg_south_west%252Cl_text%3Anotosansjp-medium.otf_37%3AHaruki%252Cx_203%252Cy_121%2Fg_south_west%252Ch_90%252Cl_fetch%3AaHR0cHM6Ly9saDMuZ29vZ2xldXNlcmNvbnRlbnQuY29tL2EtL0FPaDE0R2dIaXowNy12WWVodmV1RVRrNGZoU25LaldfYTVFdmJlYnprWG1XPXM5Ni1j%252Cr_max%252Cw_90%252Cx_87%252Cy_95%2Fv1627283836%2Fdefault%2Fog-base-w1200-v2.png%3F_a%3DBACAGSGT" height="630" class="m-0" width="1200"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://zenn.dev/mashharuki/articles/zk_groth16-plonk" rel="noopener noreferrer" class="c-link"&gt;
            【図解】ゼロ知識証明のPLONKとは？Groth16との違いを世界一わかりやすく解説！
          &lt;/a&gt;
        &lt;/h2&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.zenn.studio%2Fimages%2Flogo-transparent.png" width="315" height="315"&gt;
          zenn.dev
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  What Midnight aims to achieve
&lt;/h2&gt;

&lt;p&gt;Midnight's mission is to remove the long-standing trade-off between data protection, ownership, and data utility.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;For users&lt;/strong&gt;: They can fully control their own data and decide who can see what, and how much.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;For developers and enterprises&lt;/strong&gt;: They can build innovative data-driven services without taking on privacy-violation risk.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This opens the door to use cases that were previously difficult or impossible on public blockchains due to confidentiality requirements.&lt;/p&gt;

&lt;h2&gt;
  
  
  Midnight's core: architecture and consensus
&lt;/h2&gt;

&lt;p&gt;As a Cardano partner chain, Midnight is built on top of Cardano's robust security infrastructure.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Architecture&lt;/strong&gt;: Midnight splits smart contract state into two parts:

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Public state&lt;/strong&gt;: Data that remains publicly available on-chain.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Private state&lt;/strong&gt;: Data each user manages off-chain in their own local environment.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kachina protocol&lt;/strong&gt;: Public and private states are linked securely through &lt;strong&gt;Kachina&lt;/strong&gt;, a research-driven unified framework. Users generate ZK proofs locally using private data, then submit only the proof to the blockchain for verification.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consensus algorithm&lt;/strong&gt;: Midnight adopts a hybrid consensus model combining &lt;strong&gt;AURA&lt;/strong&gt; (block production) and &lt;strong&gt;GRANDPA&lt;/strong&gt; (finality). Cardano stake pool operators (SPOs) participate as block producers.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Two native tokens: NIGHT and DUST
&lt;/h2&gt;

&lt;p&gt;Midnight uses a unique dual-token model.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Token&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;Characteristics&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;NIGHT&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Governance, consensus&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Unshielded token&lt;/strong&gt;. Tradable and contributes to network security.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;DUST&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Transaction fees (gas)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Shielded resource&lt;/strong&gt;. Non-transferable and privacy-preserving.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;DUST&lt;/code&gt; acts as fuel, but because it is not a tradable asset, transaction metadata privacy is better preserved while keeping service costs more predictable.&lt;/p&gt;

&lt;h1&gt;
  
  
  Chapter 2: The Bond with Cardano - Why a Partner Chain?
&lt;/h1&gt;

&lt;p&gt;Midnight is an independent chain, but its deep integration with Cardano is what unlocks its full potential.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://mermaid.live/edit#pako:eNpdkU1zgjAQhv9KZs9oFVCU6XiQeqQ6Y0_VHiJZJaMkzCa0tY7_vQGKts1pk_fJvvtxgUwLhBgOxMuczV-2irljql37kHASXGm2yLQ5G4tFq9cn2XRiyqVSaN_u2nq1NJu15UdkK61Pjzt6mC1LJG41mR8Olfhnlkqh5CG37Bnth6bjPV-6uWlJ7tx-Wc1XZjM_6ezIVqRFleFfgzZMWK_HStLvUqBhBrOKpD2zva6U4FZq5fQZS1u4Lr7hOVmZyZJbZNw0hDNrmbQDFFIvq0tiUll0fdyyJeBBgVRwKdx8L_W_LdgcC9xC7ELBXYewVVfHVaUrAxdCuvlAvOcngx7wyur1WWUQW6qwg54kdy7FjcLmU9pusVmmB6SrQ34jSq5etS66NAeqC2pjcjNCStwYLMTRuGEhvsCnu0V9fxKOp8MomI6DKAg9OEPsB1E_GoaDYTANR5NwFFw9-GqSD_uDgT-d-E4ZTaJw4AfXb6Skvbw" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fpako%3AeNpdkU1zgjAQhv9KZs9oFVCU6XiQeqQ6Y0_VHiJZJaMkzCa0tY7_vQGKts1pk_fJvvtxgUwLhBgOxMuczV-2irljql37kHASXGm2yLQ5G4tFq9cn2XRiyqVSaN_u2nq1NJu15UdkK61Pjzt6mC1LJG41mR8Olfhnlkqh5CG37Bnth6bjPV-6uWlJ7tx-Wc1XZjM_6ezIVqRFleFfgzZMWK_HStLvUqBhBrOKpD2zva6U4FZq5fQZS1u4Lr7hOVmZyZJbZNw0hDNrmbQDFFIvq0tiUll0fdyyJeBBgVRwKdx8L_W_LdgcC9xC7ELBXYewVVfHVaUrAxdCuvlAvOcngx7wyur1WWUQW6qwg54kdy7FjcLmU9pusVmmB6SrQ34jSq5etS66NAeqC2pjcjNCStwYLMTRuGEhvsCnu0V9fxKOp8MomI6DKAg9OEPsB1E_GoaDYTANR5NwFFw9-GqSD_uDgT-d-E4ZTaJw4AfXb6Skvbw%3Ftype%3Dpng" width="633" height="322"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Ecosystem Integration
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Strategic integration between Cardano and Midnight Network&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Inheriting security
&lt;/h2&gt;

&lt;p&gt;Midnight addresses the challenge of network security by becoming a Cardano partner chain. It leverages Cardano's large, decentralized SPO network, allowing Midnight to access globally distributed, enterprise-grade infrastructure from day one.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Midnight differs from Cardano
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cardano&lt;/strong&gt;: Focuses on value storage/transfer and serving as a secure general-purpose decentralized platform.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Midnight&lt;/strong&gt;: Uses Cardano's security as a base while specializing in &lt;strong&gt;データ保護とプライバシー&lt;/strong&gt; as a dedicated computation layer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They are complementary: Cardano provides the trust foundation, while Midnight enables privacy-sensitive applications.&lt;/p&gt;

&lt;h1&gt;
  
  
  Chapter 3: Compact - ZKP Smart Contracts with TypeScript-like Syntax
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;&lt;br&gt;
"Aren't zero-knowledge proofs only for cryptography specialists?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Midnight addresses this challenge with a new smart contract language: &lt;strong&gt;Compact&lt;/strong&gt;.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/midnightntwrk" rel="noopener noreferrer"&gt;
        midnightntwrk
      &lt;/a&gt; / &lt;a href="https://github.com/midnightntwrk/compact" rel="noopener noreferrer"&gt;
        compact
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Compact Releases
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Compact&lt;/h1&gt;

&lt;/div&gt;
&lt;p&gt;Compact is the Midnight Network's smart contract language.
This repository is only used to host Compact releases.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Contributing to Compact&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;The project welcomes contributions.
If you would like to report a Compact bug, make a feature request, get the source code, etc.
then you should do so at the Minokawa project's &lt;a href="https://github.com/LFDT-Minokawa/compact" rel="noopener noreferrer"&gt;Compact repository&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/midnightntwrk/compact" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;h2&gt;
  
  
  Why Compact is exciting
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;TypeScript-based DSL&lt;/strong&gt;: Based on one of the world's most popular languages, allowing web developers to build privacy-focused apps with familiar syntax.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Abstraction of ZK complexity&lt;/strong&gt;: The Compact compiler translates contract logic into the cryptographic material required for proof generation.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Privacy by Design&lt;/strong&gt;: Data is treated as &lt;strong&gt;private by default&lt;/strong&gt;. To expose private data, you must explicitly wrap it with &lt;code&gt;disclose()&lt;/code&gt;.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Conceptual Compact example&lt;/span&gt;

&lt;span class="c1"&gt;// Private user vote&lt;/span&gt;
&lt;span class="nx"&gt;witness&lt;/span&gt; &lt;span class="nx"&gt;userVote&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Public vote result&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Voting circuit&lt;/span&gt;
&lt;span class="nx"&gt;circuit&lt;/span&gt; &lt;span class="nf"&gt;vote&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Update result when validation passes&lt;/span&gt;
  &lt;span class="c1"&gt;// Who voted for what remains private&lt;/span&gt;
  &lt;span class="nx"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// If you want to reveal vote content, do it explicitly&lt;/span&gt;
  &lt;span class="c1"&gt;// disclose(userVote);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h1&gt;
  
  
  Chapter 4: Use Cases Unlocked by Midnight
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Digital ID / KYC&lt;/strong&gt;: Prove you are over 18 without revealing your full birth date.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anonymous voting&lt;/strong&gt;: Truly fair voting systems with verified eligibility and ballot secrecy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Healthcare&lt;/strong&gt;: Private medical records used for aggregate analysis or AI research.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DeFi&lt;/strong&gt;: Access financial services without exposing portfolios or strategies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI and LLMs&lt;/strong&gt;: Use sensitive data for model training while preserving privacy.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;
  
  
  Summary: The Dawn of a New Privacy Era
&lt;/h1&gt;

&lt;p&gt;Midnight is foundational technology for a safer, fairer digital society where individuals retain data sovereignty and enterprises can innovate responsibly.&lt;/p&gt;

&lt;p&gt;Thank you for reading! 🚀&lt;/p&gt;
&lt;h1&gt;
  
  
  Developer Resources
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://midnight.network/developer-hub" rel="noopener noreferrer"&gt;Midnight Developer Hub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/midnightntwrk" rel="noopener noreferrer"&gt;
        midnightntwrk
      &lt;/a&gt; / &lt;a href="https://github.com/midnightntwrk/midnight-awesome-dapps" rel="noopener noreferrer"&gt;
        midnight-awesome-dapps
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Midnight Awesome Dapps
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Awesome Midnight dApps &lt;a href="https://awesome.re" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/9d49598b873146ec650fb3f275e8a532c765dabb1f61d5afa25be41e79891aa7/68747470733a2f2f617765736f6d652e72652f62616467652e737667" alt="Awesome"&gt;&lt;/a&gt;
&lt;/h1&gt;
&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;This project is built on the Midnight Network.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
&lt;p&gt;A curated list of awesome Midnight dApps, tools, and resources&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Contents&lt;/h2&gt;
&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/midnightntwrk/midnight-awesome-dapps#awesome-midnight-dapps-" rel="noopener noreferrer"&gt;Awesome Midnight dApps &lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/midnightntwrk/midnight-awesome-dapps#contents" rel="noopener noreferrer"&gt;Contents&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/midnightntwrk/midnight-awesome-dapps#getting-started" rel="noopener noreferrer"&gt;Getting Started&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/midnightntwrk/midnight-awesome-dapps#smart-contract-primitives" rel="noopener noreferrer"&gt;Smart Contract Primitives&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/midnightntwrk/midnight-awesome-dapps#starter-templates" rel="noopener noreferrer"&gt;Starter Templates&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/midnightntwrk/midnight-awesome-dapps#developer-tools" rel="noopener noreferrer"&gt;Developer Tools&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/midnightntwrk/midnight-awesome-dapps#finance--defi" rel="noopener noreferrer"&gt;Finance &amp;amp; DeFi&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/midnightntwrk/midnight-awesome-dapps#identity--privacy" rel="noopener noreferrer"&gt;Identity &amp;amp; Privacy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/midnightntwrk/midnight-awesome-dapps#gaming" rel="noopener noreferrer"&gt;Gaming&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/midnightntwrk/midnight-awesome-dapps#governance" rel="noopener noreferrer"&gt;Governance&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/midnightntwrk/midnight-awesome-dapps#dormant-projects" rel="noopener noreferrer"&gt;Dormant Projects&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/midnightntwrk/midnight-awesome-dapps#learning-resources" rel="noopener noreferrer"&gt;Learning Resources&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/midnightntwrk/midnight-awesome-dapps#documentation" rel="noopener noreferrer"&gt;Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/midnightntwrk/midnight-awesome-dapps#getting-started-1" rel="noopener noreferrer"&gt;Getting Started&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/midnightntwrk/midnight-awesome-dapps#tutorials" rel="noopener noreferrer"&gt;Tutorials&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/midnightntwrk/midnight-awesome-dapps#community" rel="noopener noreferrer"&gt;Community&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;a href="https://github.com/midnightntwrk/midnight-awesome-dapps#community-projects" rel="noopener noreferrer"&gt;Community Projects&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href="https://github.com/midnightntwrk/midnight-awesome-dapps#contributing" rel="noopener noreferrer"&gt;Contributing&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href="https://github.com/midnightntwrk/midnight-awesome-dapps#submission-criteria" rel="noopener noreferrer"&gt;Submission Criteria&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href="https://github.com/midnightntwrk/midnight-awesome-dapps#license" rel="noopener noreferrer"&gt;License&lt;/a&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;/ul&gt;

&lt;div class="markdown-alert markdown-alert-important"&gt;
&lt;p class="markdown-alert-title"&gt;Important&lt;/p&gt;
&lt;p&gt;Community-contributed projects are shared for inspiration and exploration. These repositories are not maintained by the Midnight team, and their functionality may vary.&lt;/p&gt;
&lt;/div&gt;

&lt;div class="markdown-alert markdown-alert-note"&gt;
&lt;p class="markdown-alert-title"&gt;Note&lt;/p&gt;
&lt;p&gt;🔹 = Official Midnight Ecosystem Partner&lt;br&gt;
🏆 = Hackathon winners&lt;/p&gt;
&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Getting Started&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Official dApps and tools maintained by the Midnight team (for education + onboarding)&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/midnightntwrk/example-counter" rel="noopener noreferrer"&gt;Example Counter&lt;/a&gt; - Simple increment/decrement app demonstrating state management&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/Olanetsoft/hello-world-compact" rel="noopener noreferrer"&gt;Hello World Compact&lt;/a&gt; - Minimal Hello World smart contract for Midnight. Deploy to Preprod and store/read messages on-chain&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/midnightntwrk/example-bboard" rel="noopener noreferrer"&gt;Example Bboard&lt;/a&gt; - Bulletin board with multi-user interactions and privacy patterns&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/midnightntwrk/example-zkloan" rel="noopener noreferrer"&gt;Example ZK Loan&lt;/a&gt; - ZK-powered…&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/midnightntwrk/midnight-awesome-dapps" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/MeshJS" rel="noopener noreferrer"&gt;
        MeshJS
      &lt;/a&gt; / &lt;a href="https://github.com/MeshJS/midnight-starter-template" rel="noopener noreferrer"&gt;
        midnight-starter-template
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Mesh Midnight starter template
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;🚀 EDDA - Midnight Starter Template&lt;/h1&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;A starter template for building on Midnight Network with React frontend and smart contract integration.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://counter.nebula.builders" rel="nofollow noopener noreferrer"&gt;Live Demo → counter.nebula.builders&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;📦 Prerequisites&lt;/h2&gt;
&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://nodejs.org/" rel="nofollow noopener noreferrer"&gt;Node.js&lt;/a&gt; (v23+) &amp;amp; &lt;a href="https://www.npmjs.com/" rel="nofollow noopener noreferrer"&gt;npm&lt;/a&gt; (v11+)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/get-docker/" rel="nofollow noopener noreferrer"&gt;Docker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://git-lfs.com/" rel="nofollow noopener noreferrer"&gt;Git LFS&lt;/a&gt; (for large files)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.midnight.network/relnotes/compact-tools" rel="nofollow noopener noreferrer"&gt;Compact&lt;/a&gt; (Midnight developer tools)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://chromewebstore.google.com/detail/hgeekaiplokcnmakghbdfbgnlfheichg?utm_source=item-share-cb" rel="nofollow noopener noreferrer"&gt;Lace&lt;/a&gt; (Browser wallet extension)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://faucet.preview.midnight.network/" rel="nofollow noopener noreferrer"&gt;Faucet&lt;/a&gt; (Preview Network Faucet)&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Known Issues&lt;/h2&gt;
&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;There’s a not-yet-fixed bug in the arm64 Docker image of the proof server.&lt;/li&gt;
&lt;li&gt;Workaround: Use Bricktower proof server. &lt;strong&gt;bricktowers/proof-server:6.1.0-alpha.6&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;🛠️ Setup&lt;/h2&gt;

&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;1️⃣ Install Git LFS&lt;/h3&gt;

&lt;/div&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; Install and initialize Git LFS&lt;/span&gt;
sudo dnf install git-lfs  &lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; For Fedora/RHEL&lt;/span&gt;
git lfs install&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;2️⃣ Install Compact Tools&lt;/h3&gt;

&lt;/div&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; Install the latest Compact tools&lt;/span&gt;
curl --proto &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;=https&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt; --tlsv1.2 -LsSf \
  https://github.com/midnightntwrk/compact/releases/latest/download/compact-installer.sh &lt;span class="pl-k"&gt;|&lt;/span&gt; sh&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; Install the latest compiler&lt;/span&gt;
&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; Compact compiler version 0.27 should be downloaded manually. Compact tools does not support it currently. &lt;/span&gt;
compact update +0.27.0&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;3️⃣ Install Node.js and docker&lt;/h3&gt;

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://nodejs.org/" rel="nofollow noopener noreferrer"&gt;Node.js&lt;/a&gt; &amp;amp; &lt;a href="https://www.npmjs.com/" rel="nofollow noopener noreferrer"&gt;npm&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/get-docker/" rel="nofollow noopener noreferrer"&gt;Docker&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;4️⃣ Verify Installation&lt;/h3&gt;…&lt;/div&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/MeshJS/midnight-starter-template" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h1&gt;
  
  
  References
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.midnight.network" rel="noopener noreferrer"&gt;Midnight Official Website&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.midnight.network" rel="noopener noreferrer"&gt;Midnight Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>blockchain</category>
      <category>web3</category>
      <category>typescript</category>
      <category>zkp</category>
    </item>
    <item>
      <title>Sera Protocol Tempo MPP: The Ultimate Cross-Border Payment Solution</title>
      <dc:creator>Haruki Kondo</dc:creator>
      <pubDate>Sun, 29 Mar 2026 00:40:51 +0000</pubDate>
      <link>https://dev.to/mashharuki/sera-protocol-x-tempo-x-mpp-the-ultimate-cross-border-payment-solution-3kn0</link>
      <guid>https://dev.to/mashharuki/sera-protocol-x-tempo-x-mpp-the-ultimate-cross-border-payment-solution-3kn0</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This article was written in collaboration with AI!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;Hello everyone!&lt;/p&gt;

&lt;p&gt;In our ongoing series exploring &lt;strong&gt;Sera Protocol&lt;/strong&gt;, we've built MCP servers and even developed a specialized &lt;a href="https://dev.to/mashharuki/building-an-agent-skill-for-seraprotocol-the-ultimate-guide-to-on-chain-fx-automation-4dh6"&gt;Agent SKILL&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Today, we're diving into a fascinating concept: the synergy between Sera Protocol and the recently announced &lt;strong&gt;Machine Payments Protocol (MPP)&lt;/strong&gt; from Stripe and Tempo!&lt;/p&gt;


&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/mashharuki/deep-dive-into-machine-payments-protocol-mpp-resurrecting-http-402-for-the-ai-agent-era-34oh" class="crayons-story__hidden-navigation-link"&gt;Deep Dive into Machine Payments Protocol (MPP): Resurrecting HTTP 402 for the AI Agent Era&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/mashharuki" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F986561%2Fded098d5-9fd3-49b5-8045-594b7174fec3.JPG" alt="mashharuki profile" class="crayons-avatar__image" width="800" height="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/mashharuki" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Haruki Kondo
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Haruki Kondo
                
              
              &lt;div id="story-author-preview-content-3377428" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/mashharuki" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F986561%2Fded098d5-9fd3-49b5-8045-594b7174fec3.JPG" class="crayons-avatar__image" alt="" width="800" height="800"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Haruki Kondo&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/mashharuki/deep-dive-into-machine-payments-protocol-mpp-resurrecting-http-402-for-the-ai-agent-era-34oh" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Mar 20&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/mashharuki/deep-dive-into-machine-payments-protocol-mpp-resurrecting-http-402-for-the-ai-agent-era-34oh" id="article-link-3377428"&gt;
          Deep Dive into Machine Payments Protocol (MPP): Resurrecting HTTP 402 for the AI Agent Era
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/stripe"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;stripe&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/blockchain"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;blockchain&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/web3"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;web3&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/mashharuki/deep-dive-into-machine-payments-protocol-mpp-resurrecting-http-402-for-the-ai-agent-era-34oh" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;1&lt;span class="hidden s:inline"&gt;&amp;nbsp;reaction&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/mashharuki/deep-dive-into-machine-payments-protocol-mpp-resurrecting-http-402-for-the-ai-agent-era-34oh#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            4 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


&lt;p&gt;MPP has finally given AI the "hands to pay." However, the moment they cross borders, they hit a hard reality: the &lt;strong&gt;"Currency Wall."&lt;/strong&gt; Let's explore how Sera Protocol smashes this barrier.&lt;/p&gt;

&lt;h1&gt;
  
  
  1. The "Exchange Wall" Facing AI Agents
&lt;/h1&gt;

&lt;p&gt;MPP (Machine Payments Protocol) brings HTTP 402 "Payment Required" to life after 20 years, allowing AI to discover, negotiate, and settle payments in a single request.&lt;/p&gt;

&lt;p&gt;But in practice, we encounter this tragic scenario:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;API Server (USA):&lt;/strong&gt;
"This image generation costs &lt;strong&gt;$0.01 USDX&lt;/strong&gt; (USD Stablecoin)."&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;AI Agent (Japan):&lt;/strong&gt;
"I only have &lt;strong&gt;JPYC&lt;/strong&gt; (Japanese Yen Stablecoin)..."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For humans, credit cards handle this behind the scenes. For an autonomous AI agent, this currency mismatch means &lt;strong&gt;"Service Unavailable."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In the machine world, someone must act as an instant, on-chain middleman. This is the necessity of &lt;strong&gt;"OnChain FX"&lt;/strong&gt; for machines.&lt;/p&gt;

&lt;h1&gt;
  
  
  2. The Tempo L1 "Highway" and Sera's "Gasoline"
&lt;/h1&gt;

&lt;p&gt;According to the Tempo official documentation (&lt;a href="https://docs.tempo.xyz/learn/tempo/fx#onchain-fx" rel="noopener noreferrer"&gt;OnChain FX&lt;/a&gt;), their goal is to eliminate the "Stablecoin Sandwich."&lt;/p&gt;

&lt;p&gt;Traditional cross-border payments often look like "Local Currency → USD Stablecoin → Local Currency," relying on off-chain banks and exchanges that are slow and expensive.&lt;/p&gt;

&lt;p&gt;This is where the synergy between &lt;strong&gt;Tempo L1&lt;/strong&gt; and &lt;strong&gt;Sera Protocol&lt;/strong&gt; explodes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tempo L1's "Payment Lanes"
&lt;/h3&gt;

&lt;p&gt;Tempo features dedicated block space called &lt;strong&gt;"Payment Lanes."&lt;/strong&gt; Even if a sudden NFT minting craze spikes gas fees elsewhere, payment traffic glides through at 0.1 cents, completely unaffected.&lt;/p&gt;

&lt;h3&gt;
  
  
  TIP-20 "Multi-Currency Fees"
&lt;/h3&gt;

&lt;p&gt;The genius of Tempo’s &lt;strong&gt;TIP-20&lt;/strong&gt; token standard is that &lt;strong&gt;"you can pay gas fees in any stablecoin—USDX, JPYC, you name it."&lt;/strong&gt; Agents don't need to hold a separate native token (like ETH) just for gas.&lt;/p&gt;

&lt;p&gt;On this "payment-optimized highway," Sera Protocol acts as the &lt;strong&gt;engine that atomically bridges different currencies.&lt;/strong&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  3. Why AI Agents Need a Central Limit Order Book (CLOB)
&lt;/h1&gt;

&lt;p&gt;You might wonder, "Why not just use an AMM like Uniswap?" For an AI agent, an AMM is often a &lt;strong&gt;"bundle of uncertainty."&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;"Rational" Limit Orders:&lt;/strong&gt;
AI operates on logic. It dislikes the "luck-based" slippage of AMMs. With Sera, an agent can make &lt;strong&gt;autonomous decisions&lt;/strong&gt;: "I will buy at this rate; otherwise, I will wait."&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;The Magic of Index Management:&lt;/strong&gt;
Sera’s Arithmetic Price Model manages prices using &lt;code&gt;uint16&lt;/code&gt; indices. For an AI, these are easy-to-process "numeric values," allowing for lightning-fast arbitrage and price comparisons across multiple markets.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Rights Transfer via Order NFTs:&lt;/strong&gt;
Sera orders are NFTs. Instead of sending the exchanged USDX itself, an AI could transfer the "right to the completed exchange (the NFT)" directly to the service provider, enabling next-generation payment flows.&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  4. Synergy Breakdown: The MPP + Sera Atomic Flow
&lt;/h1&gt;

&lt;p&gt;When an AI agent combines Sera and MPP for cross-border payments, the concept of "going to an exchange" disappears. Instead, &lt;strong&gt;"currency exchange becomes a seamless step within the payment process."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8ygr6uu3vg95j5m7wtu0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8ygr6uu3vg95j5m7wtu0.png" alt=" " width="800" height="407"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With this flow, the AI can &lt;strong&gt;buy resources worldwide without even caring which currency it holds.&lt;/strong&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  5. Implementation Concept: Autonomous Exchange &amp;amp; Payment Logic
&lt;/h1&gt;

&lt;p&gt;If you are giving your AI "hands and feet" using an &lt;a href="https://zenn.dev/mashharuki/articles/web3_sera_protocol-6" rel="noopener noreferrer"&gt;MCP Server&lt;/a&gt;, the code becomes surprisingly simple.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Internal logic for an AI agent receiving a 402 Challenge&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;onPaymentRequired&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;challenge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MPPChallenge&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;usdAmount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;currency&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;challenge&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 0.01 USDX&lt;/span&gt;

  &lt;span class="c1"&gt;// 1. Check the rate until satisfied&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bestRate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sera&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getBestPrice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;JPYC/USDX&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bestRate&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;MY_ACCEPTABLE_LIMIT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Exchange rate is too poor; aborting purchase for now.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// 2. Exchange exactly what is needed&lt;/span&gt;
  &lt;span class="c1"&gt;// Sera's Index-based precision handles fractions like 10.5 JPYC perfectly&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sera&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;swapAndClaim&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;JPYC&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;USDX&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;usdAmount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bestRate&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// 3. Pay immediately with the secured USDX&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;mpp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;challenge&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;useToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;USDX&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;gasToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;JPYC&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="c1"&gt;// The power of Tempo!&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The AI optimizes which currency to use and which currency to pay fees with based on real-time rates. This is the true face of the &lt;strong&gt;"Programmable Economy"&lt;/strong&gt; built by Stripe and Sera.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Dawn of the Machine Economy is Here.
&lt;/h1&gt;

&lt;p&gt;I’ve previously introduced Sera Protocol as a "new DEX," but seeing it paired with Tempo MPP reveals its full silhouette.&lt;/p&gt;

&lt;p&gt;Sera is the &lt;strong&gt;"Currency Translator" for the Machine Economy.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With the payment network expanded by Stripe, the highway paved by Tempo, and the "shape of currency" refined by Sera Protocol, AI agents are finally ready to roam the world freely.&lt;/p&gt;

&lt;p&gt;As an evangelist, I’m excited to implement this future with all of you. I look forward to showcasing a "Self-Exchanging Payment Agent" at an upcoming hackathon.&lt;/p&gt;

&lt;p&gt;Why not empower your AI with Sera—the ultimate money changer?&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;References:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.tempo.xyz/learn/tempo/fx#onchain-fx" rel="noopener noreferrer"&gt;Tempo Docs: Onchain FX&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://stripe.com/blog/machine-payments-protocol" rel="noopener noreferrer"&gt;Stripe Blog: Machine Payments Protocol&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.sera.cx/" rel="noopener noreferrer"&gt;Sera Protocol Official Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://zenn.dev/mashharuki" rel="noopener noreferrer"&gt;Series: Deep Dive into Sera Protocol&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>blockchain</category>
      <category>web3</category>
      <category>ai</category>
      <category>stablecoin</category>
    </item>
  </channel>
</rss>
