3:I[4707,[],""]
5:I[6423,[],""]
6:I[6665,["7281","static/chunks/7281-843e037a8e70f2dc.js","2972","static/chunks/2972-206c84f960364fc8.js","5315","static/chunks/5315-f69dee48ed012c1d.js","815","static/chunks/815-3b9d8cc363060263.js","2490","static/chunks/2490-f3452dfcdf496696.js","6529","static/chunks/app/(docs)/layout-4fa0829f760f53cf.js"],"Navbar"]
7:I[6812,["7281","static/chunks/7281-843e037a8e70f2dc.js","2972","static/chunks/2972-206c84f960364fc8.js","5315","static/chunks/5315-f69dee48ed012c1d.js","815","static/chunks/815-3b9d8cc363060263.js","2490","static/chunks/2490-f3452dfcdf496696.js","6529","static/chunks/app/(docs)/layout-4fa0829f760f53cf.js"],"Footer"]
4:["slug","architecture","d"]
8:{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"}
9:{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"}
a:{"display":"inline-block"}
b:{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0}
0:["lhuH3gKfmvR-zf5xG0U6e",[[["",{"children":["(docs)",{"children":["docs",{"children":[["slug","architecture","d"],{"children":["__PAGE__?{\"slug\":\"architecture\"}",{}]}]}]}]},"$undefined","$undefined",true],["",{"children":["(docs)",{"children":["docs",{"children":[["slug","architecture","d"],{"children":["__PAGE__",{},[["$L1","$L2",null],null],null]},[null,["$","$L3",null,{"parallelRouterKey":"children","segmentPath":["children","(docs)","children","docs","children","$4","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[null,["$","$L3",null,{"parallelRouterKey":"children","segmentPath":["children","(docs)","children","docs","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[null,["$","main",null,{"className":"min-h-screen relative z-10","children":[["$","$L6",null,{}],["$","div",null,{"className":"pt-24 pb-16 max-w-4xl mx-auto px-6","children":["$","$L3",null,{"parallelRouterKey":"children","segmentPath":["children","(docs)","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}],["$","$L7",null,{}]]}]],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/next/static/css/7d3295cdb652feda.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/next/static/css/c025d3bf1b5d959c.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","data-theme":"dark","className":"__variable_aa659e __variable_a607d1 __variable_ba5569 __variable_595324","children":["$","body",null,{"className":"bg-[var(--bg-base)]","children":["$","$L3",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$8","children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":"$9","children":"404"}],["$","div",null,{"style":"$a","children":["$","h2",null,{"style":"$b","children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$Lc",null]]]]
d:I[9836,["7281","static/chunks/7281-843e037a8e70f2dc.js","2972","static/chunks/2972-206c84f960364fc8.js","7775","static/chunks/app/(docs)/docs/%5Bslug%5D/page-edcecef592b5c2ea.js"],"DocArticle"]
e:I[2972,["7281","static/chunks/7281-843e037a8e70f2dc.js","2972","static/chunks/2972-206c84f960364fc8.js","7775","static/chunks/app/(docs)/docs/%5Bslug%5D/page-edcecef592b5c2ea.js"],""]
2:["$","$Ld",null,{"children":[["$","$Le",null,{"href":"/docs","className":"inline-flex items-center gap-1.5 text-sm text-[var(--text-tertiary)] hover:text-[var(--orange)] transition-colors mb-8","children":[["$","svg",null,{"width":"16","height":"16","viewBox":"0 0 24 24","fill":"none","stroke":"currentColor","strokeWidth":"2","strokeLinecap":"round","children":[["$","path",null,{"d":"M19 12H5"}],["$","path",null,{"d":"M12 19l-7-7 7-7"}]]}],"Back to docs"]}],["$","article",null,{"className":"prose-zoff","children":[["$","h1","h1-0",{"children":"Architecture Overview"}],"\n",["$","p","p-0",{"children":"This section is for contributors and developers who want to understand how Zoff Wallet is built. It covers the extension architecture, the bridge sync system, and how Zoff integrates with Canton Network."}],"\n",["$","h2","h2-0",{"children":"High-Level Architecture"}],"\n",["$","p","p-1",{"children":"The wallet consists of three layers:"}],"\n",["$","ul","ul-0",{"children":["\n",["$","li","li-0",{"children":[["$","strong","strong-0",{"children":"Browser"}]," — Extension popup (Next.js), web app (zoff.app), and content script (injected provider)"]}],"\n",["$","li","li-1",{"children":[["$","strong","strong-0",{"children":"Backend"}]," — Fastify API at ",["$","code","code-0",{"children":"api.zoff.app"}]," that proxies requests to Canton"]}],"\n",["$","li","li-2",{"children":[["$","strong","strong-0",{"children":"Canton"}]," — Participant node on Canton Network (gRPC + JSON Ledger API)"]}],"\n"]}],"\n",["$","p","p-2",{"children":["The extension popup and web app communicate with the service worker (background.js), which routes messages and syncs state. The service worker connects to ",["$","code","code-0",{"children":"api.zoff.app"}]," over HTTPS, and the backend communicates with the Canton participant node via gRPC."]}],"\n",["$","p","p-3",{"children":["Key principle: ",["$","strong","strong-0",{"children":"no key material ever leaves the popup context"}],". The service worker and backend only see public keys and pre-signed transactions."]}],"\n",["$","h2","h2-1",{"children":"Extension Architecture"}],"\n",["$","p","p-4",{"children":"Zoff follows Chrome's Manifest V3 extension model with three execution contexts:"}],"\n",["$","h3","h3-0",{"children":"Popup Page"}],"\n",["$","p","p-5",{"children":["The popup is the primary wallet UI. It's a Next.js application rendered in the extension popup window (or side panel). This is the ",["$","strong","strong-0",{"children":"only context"}]," where key material is ever decrypted."]}],"\n",["$","p","p-6",{"children":"Responsibilities:"}],"\n",["$","ul","ul-1",{"children":["\n",["$","li","li-0",{"children":"Display dashboard, balances, activity."}],"\n",["$","li","li-1",{"children":"Handle wallet creation and seed phrase display."}],"\n",["$","li","li-2",{"children":"Decrypt the keystore when the user enters their password."}],"\n",["$","li","li-3",{"children":"Sign transactions (Ed25519 signing happens here, in memory, then the key is zeroed)."}],"\n",["$","li","li-4",{"children":"Manage account selection and dApp approvals."}],"\n"]}],"\n",["$","h3","h3-1",{"children":"Service Worker (background.js)"}],"\n",["$","p","p-7",{"children":["The service worker is the message bus and sync coordinator. It runs in Chrome's background context with ",["$","strong","strong-0",{"children":"no access to the DOM"}]," and ",["$","strong","strong-1",{"children":"no access to decrypted key material"}],"."]}],"\n",["$","p","p-8",{"children":"Responsibilities:"}],"\n",["$","ul","ul-2",{"children":["\n",["$","li","li-0",{"children":"Route messages between popup, web app, and content scripts."}],"\n",["$","li","li-1",{"children":"Manage the bidirectional account sync between web app and extension."}],"\n",["$","li","li-2",{"children":["Maintain session state for ",["$","code","code-0",{"children":"api.zoff.app"}]," authentication."]}],"\n"]}],"\n",["$","p","p-9",{"children":["The service worker ",["$","strong","strong-0",{"children":"never"}]," has access to the unencrypted seed, private keys, or password. This is a deliberate security boundary. If Chrome's service worker process is compromised, the attacker gets account metadata and session tokens — not keys."]}],"\n",["$","h3","h3-2",{"children":"Content Script"}],"\n",["$","p","p-10",{"children":["Injected into every page the user visits (when the extension is active). Its sole job is to expose the ",["$","code","code-0",{"children":"window.cantonWallet"}]," provider for dApp integration."]}],"\n",["$","p","p-11",{"children":"Responsibilities:"}],"\n",["$","ul","ul-3",{"children":["\n",["$","li","li-0",{"children":"Inject the provider into the page's JavaScript context."}],"\n",["$","li","li-1",{"children":["Relay ",["$","code","code-0",{"children":"window.cantonWallet"}]," method calls from the dApp to the service worker via ",["$","code","code-1",{"children":"window.postMessage"}],"."]}],"\n",["$","li","li-2",{"children":"Relay responses and events back to the page."}],"\n"]}],"\n",["$","h2","h2-2",{"children":"Bridge Sync System"}],"\n",["$","p","p-12",{"children":"Zoff maintains bidirectional sync between the web app and extension popup. When you create an account in one interface, it appears in the other."}],"\n",["$","p","p-13",{"children":"The sync mechanism:"}],"\n",["$","ol","ol-0",{"children":["\n",["$","li","li-0",{"children":["Both interfaces write to a shared store backed by ",["$","code","code-0",{"children":"chrome.storage.local"}]," and mirrored to ",["$","code","code-1",{"children":"localStorage"}],"."]}],"\n",["$","li","li-1",{"children":["The bridge script (",["$","code","code-0",{"children":"bridge.js"}],") listens for storage change events."]}],"\n",["$","li","li-2",{"children":"On change, it broadcasts the updated state to all connected interfaces (popup, web app tabs)."}],"\n",["$","li","li-3",{"children":"Each interface reconciles the incoming state with its local state."}],"\n"]}],"\n",["$","p","p-14",{"children":"Conflict resolution is last-write-wins at the account level. Since account creation is append-only (you never modify an existing account's party ID), conflicts are rare in practice."}],"\n",["$","h2","h2-3",{"children":"Canton Integration"}],"\n",["$","h3","h3-3",{"children":"Party Allocation"}],"\n",["$","p","p-15",{"children":"When a user creates an account, Zoff needs a Canton party ID. The flow:"}],"\n",["$","ol","ol-1",{"children":["\n",["$","li","li-0",{"children":"Popup generates an Ed25519 keypair from the seed (BIP32 derivation)."}],"\n",["$","li","li-1",{"children":["Popup sends the ",["$","strong","strong-0",{"children":"public key"}]," to ",["$","code","code-0",{"children":"api.zoff.app"}],"."]}],"\n",["$","li","li-2",{"children":["Backend calls the Canton SDK's ",["$","code","code-0",{"children":"allocateParty"}]," via gRPC, associating the public key with the new party."]}],"\n",["$","li","li-3",{"children":"Canton participant node returns the allocated party ID."}],"\n",["$","li","li-4",{"children":"Backend returns the party ID to the popup."}],"\n",["$","li","li-5",{"children":"Popup stores the party ID alongside the account metadata."}],"\n"]}],"\n",["$","p","p-16",{"children":"The private key never leaves the popup context. The backend only ever sees public keys."}],"\n",["$","h3","h3-4",{"children":"Authentication"}],"\n",["$","p","p-17",{"children":"Zoff uses a challenge-response protocol:"}],"\n",["$","ol","ol-2",{"children":["\n",["$","li","li-0",{"children":["Extension requests a challenge nonce from ",["$","code","code-0",{"children":"api.zoff.app"}],"."]}],"\n",["$","li","li-1",{"children":"Backend generates a random nonce and stores it temporarily."}],"\n",["$","li","li-2",{"children":"Extension signs the nonce with the user's Ed25519 key (in the popup context)."}],"\n",["$","li","li-3",{"children":"Extension sends the signature back to the backend."}],"\n",["$","li","li-4",{"children":"Backend verifies the signature against the known public key."}],"\n",["$","li","li-5",{"children":"On success, backend issues a short-lived JWT (30-minute expiry, HS256)."}],"\n"]}],"\n",["$","h3","h3-5",{"children":"Balance Queries"}],"\n",["$","p","p-18",{"children":"Balances are read via the Canton Ledger API through the backend:"}],"\n",["$","ol","ol-3",{"children":["\n",["$","li","li-0",{"children":["Extension sends an authenticated request to ",["$","code","code-0",{"children":"api.zoff.app/balances"}],"."]}],"\n",["$","li","li-1",{"children":"Backend queries the Canton participant node for the user's party."}],"\n",["$","li","li-2",{"children":"Returns CC and CBTC balances."}],"\n"]}],"\n",["$","p","p-19",{"children":["On DevNet, balances for external parties are fetched via the UpdateStreamService (",["$","code","code-0",{"children":"/v2/updates/flats"}],") since the Active Contract Service returns empty for external parties."]}],"\n",["$","h3","h3-6",{"children":"Transaction Submission"}],"\n",["$","p","p-20",{"children":"When sending CC via the TransferPreapproval flow:"}],"\n",["$","ol","ol-4",{"children":["\n",["$","li","li-0",{"children":"Popup constructs the Daml command (TransferPreapproval creation + exercise)."}],"\n",["$","li","li-1",{"children":"Popup signs the command with the user's Ed25519 key."}],"\n",["$","li","li-2",{"children":["Popup sends the signed command to ",["$","code","code-0",{"children":"api.zoff.app"}],"."]}],"\n",["$","li","li-3",{"children":"Backend submits the signed command to the Canton participant node via the Ledger API."}],"\n",["$","li","li-4",{"children":"Canton's DSO (Decentralized Synchronizer Operator) automation processes the transfer."}],"\n",["$","li","li-5",{"children":"Both parties' balances update accordingly."}],"\n"]}],"\n",["$","hr","hr-0",{}],"\n",["$","p","p-21",{"children":["Next: ",["$","a","a-0",{"href":"/docs/security","children":"Security Model →"}]]}]]}]]}]
c:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"name":"theme-color","content":"#1C1410"}],["$","meta","2",{"charSet":"utf-8"}],["$","title","3",{"children":"Architecture Overview | Zoff Wallet"}],["$","meta","4",{"name":"description","content":"Self-custodial web wallet for Canton Network"}],["$","link","5",{"rel":"manifest","href":"/manifest.json","crossOrigin":"use-credentials"}],["$","meta","6",{"property":"og:title","content":"Zoff Wallet"}],["$","meta","7",{"property":"og:description","content":"Self-custodial web wallet for Canton Network"}],["$","meta","8",{"property":"og:site_name","content":"Zoff Wallet"}],["$","meta","9",{"property":"og:type","content":"website"}],["$","meta","10",{"name":"twitter:card","content":"summary"}],["$","meta","11",{"name":"twitter:title","content":"Zoff Wallet"}],["$","meta","12",{"name":"twitter:description","content":"Self-custodial web wallet for Canton Network"}],["$","link","13",{"rel":"icon","href":"/favicon.ico","sizes":"32x32"}],["$","link","14",{"rel":"icon","href":"/favicon.svg","type":"image/svg+xml"}],["$","link","15",{"rel":"apple-touch-icon","href":"/apple-touch-icon.png"}],["$","meta","16",{"name":"next-size-adjust"}]]
1:null
