diff --git a/DESIGN.md b/DESIGN.md
index e5f18f7..ca9452b 100644
--- a/DESIGN.md
+++ b/DESIGN.md
@@ -97,6 +97,10 @@ Vault title, 660px and below: the picture's left edge is `max(0, min((panel - pi
Deposit and Redeem titles, 660px and below, while the heading shows the focus marker (`:focus-visible`: a 3px ink outline inside a 6px yellow band; iPhone browsers show it after a tab is tapped): her shelf line sits 6px under her feet instead of 3px and ends 6px before the heading's left edge, which is the marker's outer edge, so the line and the marker's bottom band are one 3px yellow line into the corner. The rule uses `:has()` and applies only when forced colours are off; without the marker the line is v14's.
+## v18 presentation details
+
+Add BASK to MetaMask (owner's decision, 2026-10-15). The Docs page's "Find Basket" panel ends with one button, "Add BASK to MetaMask", in the site's existing button style, after the "Contact:" line, and under it one sentence: "Asks your wallet to list BASK with its logo. No transaction." The answer appears in the page's status line like every other wallet message: "The wallet accepted the request to list BASK.", "The wallet did not add BASK.", the usual decline text, or "Switch your wallet to Robinhood Chain (4663), then press Add BASK to MetaMask again." when the wallet stays on another network. No new rule in styles.css; nothing else moves.
+
## v17 presentation details
Contact links (owner's decision, 2026-10-15). The footer's link row keeps its order and gains two entries after "Basket Protocol on X": "Basket Protocol on Telegram" (opens https://t.me/basket_protocol in a new tab, like the X link) and the project e-mail "protocolbasket@gmail.com" (a mailto link). The Docs page's "Find Basket" panel shows the same two under the X link, the e-mail as "Contact: protocolbasket@gmail.com". Same link style as the X link; no new rule in styles.css.
diff --git a/README.md b/README.md
index a2874f5..a1dd373 100644
--- a/README.md
+++ b/README.md
@@ -239,6 +239,14 @@ What the checks allow in this update:
- check-art keeps every earlier assertion (whole bubble words, the bubble off her face, hands and the crate and off every opaque picture pixel, the strip clear of the text, no taller Vault title) at every width from 320 to 2560px in Chromium and WebKit, and adds: the Vault phone layout at every phone width; the live v14 title beside it (nothing in a title moves above 660px; on phones the Deposit and Redeem titles equal v14 and the Vault title differs only sideways as decided), with the v15 check rejecting the v14 look; and the focus marker at 660, 430, 393, 375, 360, 359 and 320px (the line on the marker's bottom band up to its outer edge, the v14 line put back is rejected, the line returns after blur, no shelf line at 661px).
- The fork walk is unchanged in what it checks: all 91 v14 checks under the same names in the same order, none added, the same 70 page sends, the same 24 screenshot names at JPEG quality 18. check-motion, check-fixes, inspect-export, check-guards, check-preservation, check-interface, check-tokenomics, check-tokenomics-live and check-bask-guide are unchanged.
+## Site update v18
+
+The current patch is applied to v17. One thing, decided by the owner on 2026-10-15: an "Add BASK to MetaMask" button. The Docs page's "Find Basket" panel gains one button after the contact line; pressing it asks the user's wallet to list the BASK token with its logo through the EIP-747 request wallet_watchAsset (type ERC20: the vault address, symbol BASK, 18 decimals, the logo picture at its IPFS address). It signs nothing, sends no transaction and moves nothing; the wallet shows its own confirmation. Because a wallet adds the token on its current network, the button first reads the chain and runs the existing Switch to Robinhood Chain flow when needed; if the wallet is still elsewhere it says so and stops. One sentence under the button says what it does.
+- Three sources change: web/src/wallet.tsx (useWallet gains addToWallet(): two eth_chainId reads, the existing switchChain(), the one wallet_watchAsset request, two status messages; the Provider type admits an object as params; one module constant holds the logo address), web/src/main.tsx (hands addToWallet to the Docs page) and web/src/Docs.tsx (the button and its sentence). No style rule, picture or new file.
+- No transaction input, send preparation, storage key or application network call changes. send(), connect(), disconnect() and switchChain() keep their pins. Every other web/src file is byte-identical to v17, styles.css included.
+
+What the checks allow in this update: check-calldata freezes every web/src source except the three, pins each by its reviewed digest and by its generated pieces (putting the pieces back gives v17's text exactly), and writes out every addition with its place: the three guarded calls of addToWallet (expected), its provider uses (sendAdded: the window.ethereum read, two eth_chainId reads, the one wallet_watchAsset request), its named methods (methodsAdded), its two new uses of the setMessage state setter (callChanges), the complete ordered added-call list with its digest (addedCallPins), the two action attributes with their reasons (the Docs page receiving the handler; the button calling it), the one module constant (namesAdded) and the hook's return list with addToWallet after switchChain, both texts pinned. Nothing a page loads or opens changes; no storage use changes; Docs.tsx and main.tsx still contain no send or request. check-bundle limits source changes to the same three files.
+
## Site update v17
The current patch is applied to v16, commit 27315d9974a065935f260dec29a52fa30fc7a109. One thing, decided by the owner on 2026-10-15: the project's contact links. The footer and the Docs page's "Find Basket" panel each gain two links beside the existing "Basket Protocol on X": "Basket Protocol on Telegram" (https://t.me/basket_protocol) and the project e-mail address (protocolbasket@gmail.com, a mailto link). Explorers that list token information ask for the project's e-mail to be shown on its website; this is that line.
diff --git a/web/scripts/check-bundle.mjs b/web/scripts/check-bundle.mjs
index 0a9f371..6dfc9f9 100644
--- a/web/scripts/check-bundle.mjs
+++ b/web/scripts/check-bundle.mjs
@@ -6,9 +6,9 @@ import {createHash} from 'node:crypto';
const root=process.env.BASKET_SOURCE_ROOT||path.resolve('..');
const git=args=>execFileSync('git',args,{cwd:root,encoding:'utf8'});
const changed=git(['diff','--name-only','HEAD']).trim().split('\n').filter(Boolean);
-// v17 (the owner's contact links, 2026-10-15: Telegram and e-mail in the footer and the Docs Find Basket panel): only these two sources may change.
+// v18 (the owner's Add BASK to MetaMask button, 2026-10-15: wallet.tsx addToWallet, main.tsx hands it to the Docs page, Docs.tsx shows the button): only these three sources may change.
// check-calldata independently pins each reviewed source and freezes every other.
-const v17Source=['main.tsx','Docs.tsx'].map(f=>'web/src/'+f);
+const v18Source=['main.tsx','Docs.tsx','wallet.tsx'].map(f=>'web/src/'+f);
const SHELL_SHA256='82f66899a0c19f61299254c0005ee5117af0a912b97e8e57d249288e9dbc962d';
const SHARE_SHA256='dc14dd6f4068ec9325426583104b1c6ae8748fbb17d8326863c9612280eacd85';
// The new favicon: red panel, yellow frame, the header's basket in yellow.
@@ -23,10 +23,10 @@ for(const p of changed){
// v12 section 5: the exact page shell is pinned below; build configuration and validation fixtures stay unchanged.
assert.ok(!['web/vite.config.ts','web/tsconfig.json'].includes(p)&&(!p.startsWith('web/validation/')||p==='web/validation/README.md'),'Page shell, build configuration or validation fixture changed: '+p);
assert.ok(!/(^|\/)(lib|node_modules|\.github|\.git)(\/|$)|(^|\/)\.env(?:\.|$)|(^|\/)(foundry\.toml|foundry\.lock|remappings\.txt|\.gitmodules|package\.json|[^/]*lock[^/]*)$/.test(p),'Protected path changed: '+p);
- if(p.startsWith('web/src/'))assert.ok(v17Source.includes(p),'Source outside the v17 scope changed: '+p);
+ if(p.startsWith('web/src/'))assert.ok(v18Source.includes(p),'Source outside the v18 scope changed: '+p);
assert.ok(!p.startsWith('src/'),'Root contract source changed');
}
-// v17: no new source and no new public asset.
+// v18: no new source and no new public asset.
const untracked=git(['ls-files','--others','--exclude-standard','--','web/src','web/public']).trim().split('\n').filter(Boolean);
assert.deepEqual(untracked,[],'New files in web/src or web/public');
assert.equal(sha256('web/index.html'),SHELL_SHA256,'exact v12 source shell');
diff --git a/web/scripts/check-calldata.mjs b/web/scripts/check-calldata.mjs
index 332159b..71102df 100644
--- a/web/scripts/check-calldata.mjs
+++ b/web/scripts/check-calldata.mjs
@@ -11,10 +11,15 @@ const git=args=>execFileSync('git',args,{cwd:root,encoding:'utf8'});
const baseline=p=>git(['show','HEAD:'+p]);
const read=p=>fs.readFileSync(path.join(root,p),'utf8');
const sha256=s=>createHash('sha256').update(s).digest('hex');
-// v17 on HEAD=v16: one thing. The owner's contact links (2026-10-15): main.tsx gains two links in the footer and Docs.tsx
-// the same two in the "Find Basket" panel (the project's Telegram group and its e-mail address), beside the X link that
-// HEAD already has. No call, attribute that acts, import, state, storage, wallet request or chain read is added.
-// Every other web/src file is byte-identical to HEAD, styles.css included. Transactions and chain reads remain HEAD's.
+// v18 on HEAD=v17: one thing. The owner's "Add BASK to MetaMask" button (2026-10-15): the Docs page's "Find Basket"
+// panel gains one button; pressing it runs the one new wallet request of this update, wallet_watchAsset (EIP-747),
+// which asks the wallet to list the BASK token with its logo. It signs nothing and sends nothing. wallet.tsx gains
+// addToWallet() inside useWallet (two eth_chainId reads, the existing switchChain(), the one request, two status
+// messages), main.tsx hands it to DocsPage. Every addition is written out below with its place (sendAdded,
+// methodsAdded, expected, callChanges, attributeChanges, namesAdded, the hook's return list); no other call,
+// attribute that acts, import, state, storage use, wallet request or chain read is added. Every other web/src file
+// is byte-identical to HEAD, styles.css included. Transactions remain HEAD's: send(), connect(), disconnect() and
+// switchChain() keep their pins.
const protectedFiles=[
"BaskGuide.tsx",
"Flows.tsx",
@@ -25,7 +30,6 @@ const protectedFiles=[
"components.tsx",
"Tokenomics.tsx",
"tokenomicsData.ts",
- "wallet.tsx",
"baskGuideData.ts",
"chain.ts",
"deployment.ts",
@@ -63,26 +67,39 @@ assert.equal(sha256(fs.readFileSync(path.join(root,'web/src/styles.css')).subarr
// v16: no style rule after HEAD's text (the v15 rules are HEAD's own now).
const cssTail=fs.readFileSync(path.join(root,'web/src/styles.css')).subarray(Buffer.byteLength(baseline('web/src/styles.css'))).toString('utf8');
assert.equal(cssTail,'','v16: no style rule after the text of HEAD');
-// The v17 files: byte-identical to HEAD once their documented pieces [v16, HEAD] are put back (each exactly once), so
+// The v18 files: byte-identical to HEAD once their documented pieces [v18, HEAD] are put back (each exactly once), so
// every word, call and attribute in them is pinned. The list between the two marker lines was generated from the
// difference to HEAD; the pins further down do not depend on it: they compare with HEAD itself or with texts written
// out in this file, so a piece cannot carry a change to a transaction, a wallet request or a chain read past them.
-// >>> v17 pieces (generated from the difference to HEAD, not edited by hand)
+// >>> v18 pieces (generated from the difference to HEAD, not edited by hand)
const editedFiles={
+ "wallet.tsx":[
+ ["export interface Provider {\n request(args: {\n method: string;\n params?: unknown[] | Record
\n \n Basket Protocol on Telegram\n \n
\n\n Contact: protocolbasket@gmail.com\n
\n \n", + ["import { VAULT } from \"./deployment\";\nexport function DocsPage({\n onAddToWallet,\n}: {\n onAddToWallet: () => void;\n}) {\n const [tab, setTab] = useState(0);\n", + "import { VAULT } from \"./deployment\";\nexport function DocsPage() {\n const [tab, setTab] = useState(0);\n"], + [" \n\n \n
\nAsks your wallet to list BASK with its logo. No transaction.
\n \n", " \n \n"] ] }; -// <<< v17 pieces +// <<< v18 pieces assert.deepEqual(Object.keys(editedFiles),[ + "wallet.tsx", "main.tsx", "Docs.tsx" -],'the v17 files'); +],'the v18 files'); const putBack=(text,pieces,what)=>{for(const [n,o] of pieces){assert.equal(text.split(n).length,2,what+': expected once: '+n);text=text.split(n).join(o);}return text;}; for(const [f,edits] of Object.entries(editedFiles)) assert.equal(putBack(read('web/src/'+f),edits,f),baseline('web/src/'+f),f+' changed beyond the documented pieces'); // Calls that build, encode, simulate or send a transaction (or parse its arguments) or read the chain. @@ -136,17 +153,32 @@ function difference(a,b){ } // Guarded calls: the exact, documented differences from HEAD, each added call with the place it must sit in // (everything equals HEAD, in order and place). The existing receipt read and display-only amount parser are -// already part of HEAD. v16 adds no guarded call, wallet request or chain read. -const expected={}; +// already part of HEAD. v18 adds exactly the calls of addToWallet() listed here, each at its place. +const expected={ + "wallet.tsx":{"removed":[],"added":[ + ["p.request({ method: \"eth_chainId\" })", "useWallet > addToWallet"], + ["p.request({ method: \"eth_chainId\" })", "useWallet > addToWallet"], + ["p.request({ method: \"wallet_watchAsset\", params: { type: \"ERC20\", options: { address: VAULT, symbol: \"BASK\", decimals: 18, image: BASK_IMAGE, }, }, })", "useWallet > addToWallet"] + ]} +}; // Action attributes: the exact differences from HEAD, in page order, each [element, attribute, why]. Everything else // equals HEAD in order, text for text. None of them builds or starts a transaction (no onSubmit, getSpec, build or // claim) and none contains a guarded call (both asserted below). The list between the marker lines was generated // from the difference to HEAD, as the pieces were; each why was written by hand, and an entry without one fails. -// >>> v17 attributes (generated from the difference to HEAD; each why written by hand) +// >>> v18 attributes (generated from the difference to HEAD; each why written by hand) const attributeChanges={ - + "Docs.tsx":{ + removed:[], + added:[ + ["button","onClick={onAddToWallet}", + "v18, the owner (2026-10-15): the Add BASK to MetaMask button in the Find Basket panel calls the handler the page received; Docs.tsx itself makes no request, read or send (NO_SEND holds)."]]}, + "main.tsx":{ + removed:[], + added:[ + ["DocsPage","onAddToWallet={wallet.addToWallet}", + "v18, the owner (2026-10-15): main.tsx hands useWallet.addToWallet to the Docs page as the handler of its Add BASK to MetaMask button; the function itself lives in wallet.tsx and is pinned there (two eth_chainId reads, switchChain, one wallet_watchAsset request, no send)."]]} }; -// <<< v17 attributes +// <<< v18 attributes // Every call expression: HEAD's calls, in order and place, are all still there, except the removed ones listed here; // a call that only gained the documented piece inside it (put back before comparing) counts as kept. Every added // call, including display helpers and effects, is independently pinned below, not only the guarded calls. @@ -159,7 +191,16 @@ const attributeChanges={ // The guarded startup eth_accounts answer is part of HEAD and must remain exact. const restoreAccountReply=(x)=>x.text; // Reviewed v16 differences in calls and watched-name uses. Independent of the generated pieces. -const callChanges={}; +const callChanges={ + "wallet.tsx":{ + why:'v18 (the owner, 2026-10-15): addToWallet() asks the wallet to list BASK with its logo; its two status messages are the only new uses of a watched name (the setMessage state setter of HEAD). No call of HEAD is removed or moved; no piece sits inside a call.', + pieces:[], + removed:[], + uses:[ + ["setMessage(added ? \"The wallet accepted the request to list BASK.\" : \"The wallet did not add BASK.\")", "useWallet > addToWallet"], + ["setMessage(explain(e))", "useWallet > addToWallet"] + ]} +}; const WATCHED=[...names].filter(x=>!x.includes('.')).concat('client'); // The state setters and refs a file declares: const [x, setX] = useState(...) gives setX, const r = useRef(...) gives r. function stateNames(text,file){ @@ -210,7 +251,7 @@ function localDeclarations(file,text,fn,list){ const sources=git(['ls-tree','-r','--name-only','HEAD','web/src']).trim().split('\n').filter(p=>/\.tsx?$/.test(p)).map(p=>p.slice(8)); assert.deepEqual(git(['ls-files','--cached','--others','--exclude-standard','web/src']).trim().split('\n').filter(p=>/\.tsx?$/.test(p)).map(p=>p.slice(8)).sort(),[...sources,...NEW_SOURCES].sort(),'No source file added or removed'); // Every source is either frozen (byte-identical to HEAD) or a v16 file with its documented pieces (above). -assert.deepEqual([...sources].sort(),[...protectedFiles,...Object.keys(editedFiles)].filter(f=>/\.tsx?$/.test(f)).sort(),'every web/src source is frozen or a documented v17 file'); +assert.deepEqual([...sources].sort(),[...protectedFiles,...Object.keys(editedFiles)].filter(f=>/\.tsx?$/.test(f)).sort(),'every web/src source is frozen or a documented v18 file'); const scans={},facts={attributes:{},guarded:{},calls:{}}; for(const f of sources){ const old=calls(baseline('web/src/'+f),f),now=calls(read('web/src/'+f),f),c=callChanges[f]??{pieces:[],removed:[],uses:[]},used=c.pieces.map(()=>0); @@ -234,6 +275,10 @@ if(process.env.BASKET_GATE_FACTS)fs.writeFileSync(process.env.BASKET_GATE_FACTS, // includes these pins. Changing a callback, receiver, argument, location, order or count changes the digest. // >>> reviewed calls (manual; never refreshed by put-pieces) const addedCallPins={ + "wallet.tsx": { + "sha256": "5bf81673fe9064f2f5d8a5716931ca2808f203af2130f2240981397d7c0b2991", + "callees": "Number request switchChain Number request request setMessage setMessage explain" + }, "main.tsx": { "sha256": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", "callees": "" @@ -246,8 +291,9 @@ const addedCallPins={ // <<< reviewed calls // The exact reviewed source of the two v14 files (frozen now) and of the two v16 files, beyond either generated block. const reviewedSourcePins={ - "Docs.tsx": "80a96319c164bfecef20669b86f4cae043c98e714123ff97b065b884e21d228e", - "main.tsx": "57c087df660d9d60b3c6b2ca6d4708136cbb8850681ec9fdd3843ae4b9f0d53a", + "Docs.tsx": "2ab149e23a1bd4d566d8e56abc514fc203d4b3ebf247cc4e6182f0461c5d8467", + "main.tsx": "bf5e06e8f39a4c895fe485a5bcca9db9b9be8d61a5969ed4327297bb6423d5b1", + "wallet.tsx": "cb08bb0214a1b581a85da98c9974b5edb0b340e5c66e01bcdd4239149c7d6a91", "Flows.tsx": "b9452b067fabd93c4ce9718d19e4c70906263068bcb81589b2e151beb411c309", "Tokenomics.tsx": "6d57c2f9d8629f7207368b6d00618da9351e72aba0de52fcff225490be47c85a", "tokenomicsData.ts": "8e2cfc45c8055f2dbfdc7a7610e57856809845d52dd103712dd8398f3b11a99a" @@ -384,14 +430,28 @@ function sendsOf(text,file){ })(tree); return out; } +// v18: the provider uses of addToWallet(), written out with their place and in order: the read of window.ethereum, +// two eth_chainId reads and the one wallet_watchAsset request. wallet.tsx equals HEAD apart from exactly these; +// no other file gains any. Each is also a guarded call (expected) and a named method (methodsAdded) below. +const sendAdded={ + "wallet.tsx":[ + {"place":"useWallet > addToWallet","text":"window.ethereum"}, + {"place":"useWallet > addToWallet","text":"p.request({ method: \"eth_chainId\" })"}, + {"place":"useWallet > addToWallet","text":"p.request({ method: \"eth_chainId\" })"}, + {"place":"useWallet > addToWallet","text":"p.request({ method: \"wallet_watchAsset\", params: { type: \"ERC20\", options: { address: VAULT, symbol: \"BASK\", decimals: 18, image: BASK_IMAGE, }, }, })"} + ] +}; const NO_SEND=['Vault.tsx','Scenery.tsx','main.tsx','Docs.tsx'],sendUses={}; for(const f of sources){ - const h=sendsOf(baseline('web/src/'+f),f),n=sendsOf(read('web/src/'+f),f); + const h=sendsOf(baseline('web/src/'+f),f),n=sendsOf(read('web/src/'+f),f),added=sendAdded[f]??[]; + const isAdded=x=>added.some(a=>a.place===x.place&&a.text===x.text); // The startup request, its answer handling and all provider uses already belong to HEAD and remain exact. // Guarded calls and named methods independently compare the requests without an exception. const restored=n.map(x=>({...x,text:restoreAccountReply(x,f)})); - if(f==='wallet.tsx')assert.deepEqual(n,h,'wallet.tsx: provider uses including the guarded startup reply equal HEAD'); - assert.deepEqual(restored,h,f+': a wallet send, provider request, signing method or network call was added, removed, changed or moved'); + if(f==='wallet.tsx')assert.deepEqual(n.filter(x=>!isAdded(x)),h,'wallet.tsx: provider uses including the guarded startup reply equal HEAD apart from the documented addToWallet additions'); + assert.deepEqual(restored.filter(x=>!isAdded(x)),h,f+': a wallet send, provider request, signing method or network call was added, removed, changed or moved'); + assert.deepEqual(n.filter(isAdded),added,f+': the added provider uses differ from the documented ones in text, place, order or count'); + for(const a of added)assert.equal(h.filter(x=>x.place===a.place&&x.text===a.text).length,0,f+': a documented added provider use already existed at HEAD: '+a.text); if(f==='wallet.tsx')assert.deepEqual(n.filter(x=>x.place==='useWallet > disconnect'),[],'wallet.tsx disconnect(): no provider request'); if(NO_SEND.includes(f))assert.ok(n.every(x=>x.text==='window.ethereum'),f+' must contain no send or request (only the read of window.ethereum in main.tsx)'); if(n.length)sendUses[f]=n.map(x=>x.text); @@ -421,20 +481,10 @@ function loadsOf(text,file){ })(tree); return out; } -// v17 adds exactly four links a page opens, the owner's contact links (2026-10-15), each written out here with its place: -// the Telegram group and the e-mail address in the footer (main.tsx, after the X link) and in the Docs "Find Basket" -// panel (Docs.tsx, after the X link). Nothing else a page loads or opens changes in any file: HEAD's list stays complete, -// in order and in place, and the only additions are these, in this order. -const loadDisplayAdded={ - "main.tsx": [ - {"place": "App", "text": "href=\"https://t.me/basket_protocol\""}, - {"place": "App", "text": "href=\"mailto:protocolbasket@gmail.com\""} - ], - "Docs.tsx": [ - {"place": "DocsPage", "text": "href=\"https://t.me/basket_protocol\""}, - {"place": "DocsPage", "text": "href=\"mailto:protocolbasket@gmail.com\""} - ] -}; +// v18 adds nothing a page loads or opens: the button has no href, src or style, and the logo address is a string the +// wallet is handed, not a link the page opens. HEAD's list (v17's contact links included) stays complete, in order and +// in place, in every file, with no addition. +const loadDisplayAdded={}; const loadUses={}; for(const f of sources){ const h=loadsOf(baseline('web/src/'+f),f),n=loadsOf(read('web/src/'+f),f),added=loadDisplayAdded[f]??[]; @@ -446,6 +496,14 @@ for(const f of sources){ } // v12: the wallet and RPC methods the pages name. Every `method` given as a property (or assigned) in web/src, in order // with its place: exactly HEAD's list. Local-only Disconnect names no new wallet or RPC method. +// v18: wallet.tsx names three more, all inside addToWallet(), in this order; no other file names a new one. +const methodsAdded={ + "wallet.tsx":[ + {"method":"eth_chainId","place":"useWallet > addToWallet"}, + {"method":"eth_chainId","place":"useWallet > addToWallet"}, + {"method":"wallet_watchAsset","place":"useWallet > addToWallet"} + ] +}; function methodsOf(text,file){ const tree=parse(text,file),print=printerOf(tree),out=[],key=n=>ts.isIdentifier(n)||ts.isStringLiteralLike(n)?n.text:ts.isComputedPropertyName(n)?lit(n.expression):undefined; const put=(n,value)=>out.push({method:value?lit(value)??'not a plain string: '+print(value):'not a plain string: shorthand',place:place(n,tree)}); @@ -459,9 +517,12 @@ function methodsOf(text,file){ } const methods={}; for(const f of sources){ - const h=methodsOf(baseline('web/src/'+f),f),n=methodsOf(read('web/src/'+f),f); + const h=methodsOf(baseline('web/src/'+f),f),n=methodsOf(read('web/src/'+f),f),added=methodsAdded[f]??[]; + const isAdded=x=>added.some(a=>a.place===x.place&&a.method===x.method); if(f==='wallet.tsx')assert.deepEqual(n.filter(x=>x.place==='useWallet > disconnect'),[],'wallet.tsx: no wallet or RPC method inside disconnect()'); - assert.deepEqual(n,h,f+': a wallet or RPC method was added, removed, changed or moved'); + assert.deepEqual(n.filter(x=>!isAdded(x)),h,f+': a wallet or RPC method was added, removed, changed or moved'); + assert.deepEqual(n.filter(isAdded),added,f+': the added wallet or RPC methods differ from the documented ones in name, place, order or count'); + for(const a of added)assert.equal(h.filter(x=>x.place===a.place&&x.method===a.method).length,0,f+': a documented added method already existed at HEAD at that place: '+a.method); for(const x of n)methods[x.method]=(methods[x.method]||0)+1; } // v12: what the pages keep in the browser. Every use of localStorage, sessionStorage, indexedDB, cookie or caches, in @@ -558,7 +619,12 @@ function hookReturn(text){ assert.equal(returns.length,1,'wallet.tsx useWallet: one return'); return print(returns[0]); } -assert.equal(hookReturn(read('web/src/wallet.tsx')),hookReturn(baseline('web/src/wallet.tsx')),'wallet.tsx: hook returns HEAD\'s unchanged list'); +// v18: the hook's return list is HEAD's with addToWallet after switchChain. Both texts are pinned here, printed +// without comments; nothing else in the list may change. +const HOOK_RETURN_HEAD="return { account, connecting, progress, report, fail, notice, listingPending, waitPending, chainId, message, hash, busy, confirmed, connect, disconnect, switchChain, send, setMessage, };"; +const HOOK_RETURN_V18="return { account, connecting, progress, report, fail, notice, listingPending, waitPending, chainId, message, hash, busy, confirmed, connect, disconnect, switchChain, addToWallet, send, setMessage, };"; +assert.equal(hookReturn(baseline('web/src/wallet.tsx')),HOOK_RETURN_HEAD,'wallet.tsx: HEAD\'s hook return list as pinned'); +assert.equal(hookReturn(read('web/src/wallet.tsx')),HOOK_RETURN_V18,'wallet.tsx: hook returns HEAD\'s list plus addToWallet, as pinned'); // SPEC protected code: raw bytes, including entire buttons and effects, independent of generated differences. const protectedCode={sendTailWrites}; function rawProtected(label,file,select,count=1){ @@ -666,7 +732,9 @@ for(const f of Object.keys(editedFiles)){ assert.deepEqual(n,h,f+': a name used by the transaction code is declared around it differently from HEAD'); transactionCode[f]={pieces:n.count,namesDeclaredAroundThem:n.names}; } +// v18: one module constant in wallet.tsx, the logo address the wallet is asked to show. const namesAdded={ + "wallet.tsx": {"BASK_IMAGE": "BASK_IMAGE = \"https://gateway.pinata.cloud/ipfs/bafkreiczp2hrpey42mx5hngo37rrwwwexrt3mdm537pyztv22pn4skxsvi\""}, "main.tsx": {}, "Docs.tsx": {} }; @@ -870,21 +938,21 @@ const brief=x=>x.length>120?{start:x.slice(0,80),length:x.length,sha256:sha256(x fs.writeFileSync('../artifacts/calldata-comparison.json',JSON.stringify({result:'PASS',baseline:git(['rev-parse','HEAD']).trim(),protectedFiles,editedFiles:Object.fromEntries(Object.entries(editedFiles).map(([f,x])=>[f,x.length+' documented pieces [v12, HEAD]'])),v12Pieces:Object.fromEntries(Object.entries(editedFiles).map(([f,x])=>[f,x.map(pieceRecord)])),counts, documentedGuardedCallChanges:expected,documentedAttributeChanges:Object.fromEntries(Object.entries(attributeChanges).map(([f,a])=>[f,{removed:a.removed.map(x=>'<'+x[0]+'> '+x[1]),added:a.added.map(x=>'<'+x[0]+'> '+x[1]),why:'each has its why in scripts/check-calldata.mjs'}])), documentedCallChanges:Object.fromEntries(Object.entries(callChanges).map(([f,c])=>[f,{why:c.why,piecesPutBackInsideCalls:c.pieces.map(pieceRecord),removed:c.removed.map(brief),newUsesOfWatchedNames:c.uses.map(([x,where])=>[brief(x),where])}])), - vaultStockShelves:{attributes:vaultAttributes,handlers:vaultHandlers},pinnedFunctions,walletHookReturn:{equalToHead:true}, - walletAndRpcMethods:{named:methods,addedInV12:[]},browserStorageAddedInV12:storageAdded,namesAddedAtTheTopOfAFile:namesAdded,moduleDeclarationChanges:moduleChanges,namesAddedInsideComponents:newNames,transactionCode,reviewedAddedCalls:addedCallPins,reviewedSourcePins,transactionStateWrites,protectedCode, + vaultStockShelves:{attributes:vaultAttributes,handlers:vaultHandlers},pinnedFunctions,walletHookReturn:{equalToHeadPlusAddToWallet:true,head:HOOK_RETURN_HEAD,now:HOOK_RETURN_V18}, + walletAndRpcMethods:{named:methods,addedInV18:methodsAdded},browserStorageAddedInV12:storageAdded,namesAddedAtTheTopOfAFile:namesAdded,moduleDeclarationChanges:moduleChanges,namesAddedInsideComponents:newNames,transactionCode,reviewedAddedCalls:addedCallPins,reviewedSourcePins,transactionStateWrites,protectedCode, differentialEncoding:{cases:cases.length,...tally},sendBuilders:{clocks,...sends},pinnedDeclarations:pinned, - sendsAndRequests:{sendFreeFiles:NO_SEND,usesPerFile:Object.fromEntries(Object.entries(sendUses).map(([f,x])=>[f,x.length])),components:sendUses['components.tsx'].filter(x=>/\(/.test(x)),addedInV12:[]}, + sendsAndRequests:{sendFreeFiles:NO_SEND,usesPerFile:Object.fromEntries(Object.entries(sendUses).map(([f,x])=>[f,x.length])),components:sendUses['components.tsx'].filter(x=>/\(/.test(x)),addedInV18:sendAdded}, pageLoads:{names:[...LOADS],jsxAttributes:[...LOAD_ATTRIBUTES],usesPerFile:loadUses,equalToHeadExceptDisplayNames:loadDisplayAdded}, pinnedV9Declarations:Object.fromEntries(Object.entries(pinnedV9).map(([f,m])=>[f,Object.fromEntries(Object.entries(m).map(([k,x])=>[k,typeof x==='string'?'v9 text (also at HEAD)':'equal to HEAD ('+x.length+' pieces)']))])),importBindings:imports, checks:[ 'v12 source scope: every other TypeScript source and ABI equal HEAD; edited sources equal HEAD once their exact pieces are restored, and also match independent reviewed SHA-256 pins outside regeneration blocks', - 'Every guarded reading, parsing, encoding, simulation, sending and provider call equals HEAD in order and place; no added chain read, wallet request, storage key or network call', + 'Every guarded reading, parsing, encoding, simulation, sending and provider call equals HEAD in order and place apart from the documented addToWallet additions (two eth_chainId reads and the one wallet_watchAsset request, written out with their place); no other chain read, wallet request, storage key or network call is added', 'Action attributes equal HEAD except the documented approved presentation changes with a written reason; every onSubmit, getSpec, build and claim attribute equals HEAD', 'Every removed call and added watched-name use is independently listed; complete ordered added calls match reviewed hashes and ordered callee lists outside regeneration blocks', 'Direct and aliased transaction-state writes equal HEAD; send-tail receipt-hash writes are individually pinned with their ordered statements', 'Wallet send head is byte-identical to HEAD through the receipt wait; its tail equals HEAD after exact receipt-hash replacements; connect and disconnect equal reviewed text and the new counter is used only at the pinned sites', 'All protected wallet functions, flow effects, transaction buttons and handlers are byte-identical to HEAD; declaration and wrapper-scope checks prevent name rebinding', - 'Provider methods, browser storage, network and page-load sinks equal HEAD; Disconnect makes no request; components retains the same single wallet.send', + 'Provider methods and uses equal HEAD plus the documented addToWallet additions; browser storage, network and page-load sinks equal HEAD; Disconnect makes no request; components retains the same single wallet.send', 'The differential encoding corpus, six-clock send builders, parser declarations, pinned v9 decisions and imported bindings retain every prior assertion', 'Only approved v12 source files change; shell and share-card bytes are separately pinned by check-bundle.mjs' ]},null,2)+'\n'); diff --git a/web/src/Docs.tsx b/web/src/Docs.tsx index d0a20be..1968a9d 100644 --- a/web/src/Docs.tsx +++ b/web/src/Docs.tsx @@ -3,7 +3,11 @@ import { Tokenomics } from "./Tokenomics"; import { BaskGuide } from "./BaskGuide"; import { PageTitle, AddressLink } from "./components"; import { VAULT } from "./deployment"; -export function DocsPage() { +export function DocsPage({ + onAddToWallet, +}: { + onAddToWallet: () => void; +}) { const [tab, setTab] = useState(0); const buttons = useRef<(HTMLButtonElement | null)[]>([]); function move(event: KeyboardEventContact: protocolbasket@gmail.com
++ +
+Asks your wallet to list BASK with its logo. No transaction.
diff --git a/web/src/main.tsx b/web/src/main.tsx index 3a5c475..fbff755 100644 --- a/web/src/main.tsx +++ b/web/src/main.tsx @@ -252,7 +252,7 @@ function App() { ) : page === "Owner" ? (