@defdone/annon 0.0.0-stage → 0.1.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/CLI.md +378 -0
  3. package/LICENSE +201 -0
  4. package/NOTICE +7 -0
  5. package/README.md +591 -2
  6. package/dist/api/annon-types.d.ts +108 -0
  7. package/dist/api/annon-types.js +2 -0
  8. package/dist/api/annon.d.ts +117 -0
  9. package/dist/api/annon.js +1152 -0
  10. package/dist/api/hosted-record.d.ts +13 -0
  11. package/dist/api/hosted-record.js +149 -0
  12. package/dist/api/sharing-types.d.ts +109 -0
  13. package/dist/api/sharing-types.js +1 -0
  14. package/dist/api/sharing.d.ts +36 -0
  15. package/dist/api/sharing.js +879 -0
  16. package/dist/browser/download.d.ts +19 -0
  17. package/dist/browser/download.js +52 -0
  18. package/dist/browser/index.d.ts +23 -0
  19. package/dist/browser/index.js +22 -0
  20. package/dist/browser/login-bundle.js +151 -0
  21. package/dist/browser/login.d.ts +1 -0
  22. package/dist/browser/login.js +216 -0
  23. package/dist/browser/purchase-bundle.js +84 -0
  24. package/dist/browser/purchases.d.ts +98 -0
  25. package/dist/browser/purchases.js +735 -0
  26. package/dist/browser/record.d.ts +55 -0
  27. package/dist/browser/record.js +16 -0
  28. package/dist/browser/selling.d.ts +146 -0
  29. package/dist/browser/selling.js +418 -0
  30. package/dist/browser/wallets.d.ts +53 -0
  31. package/dist/browser/wallets.js +286 -0
  32. package/dist/bytes.d.ts +4 -0
  33. package/dist/bytes.js +16 -0
  34. package/dist/download/download.d.ts +59 -0
  35. package/dist/download/download.js +384 -0
  36. package/dist/download/host.d.ts +56 -0
  37. package/dist/download/host.js +161 -0
  38. package/dist/download/http.d.ts +23 -0
  39. package/dist/download/http.js +87 -0
  40. package/dist/download/proof.d.ts +20 -0
  41. package/dist/download/proof.js +79 -0
  42. package/dist/download/upload.d.ts +130 -0
  43. package/dist/download/upload.js +408 -0
  44. package/dist/envelope/evm.d.ts +4 -0
  45. package/dist/envelope/evm.js +32 -0
  46. package/dist/envelope/pay2.d.ts +176 -0
  47. package/dist/envelope/pay2.js +541 -0
  48. package/dist/envelope/replay.d.ts +45 -0
  49. package/dist/envelope/replay.js +186 -0
  50. package/dist/envelope/stream.d.ts +78 -0
  51. package/dist/envelope/stream.js +372 -0
  52. package/dist/errors.d.ts +21 -0
  53. package/dist/errors.js +300 -0
  54. package/dist/index.d.ts +25 -0
  55. package/dist/index.js +23 -0
  56. package/dist/keys/near-ckd.d.ts +47 -0
  57. package/dist/keys/near-ckd.js +96 -0
  58. package/dist/limits.d.ts +4 -0
  59. package/dist/limits.js +4 -0
  60. package/dist/near/nep413.d.ts +22 -0
  61. package/dist/near/nep413.js +39 -0
  62. package/dist/near/rpc.d.ts +170 -0
  63. package/dist/near/rpc.js +423 -0
  64. package/dist/near/texts.d.ts +39 -0
  65. package/dist/near/texts.js +60 -0
  66. package/dist/net/ip.d.ts +7 -0
  67. package/dist/net/ip.js +62 -0
  68. package/dist/net/retry.d.ts +3 -0
  69. package/dist/net/retry.js +31 -0
  70. package/dist/node/adapter-checks.d.ts +6 -0
  71. package/dist/node/adapter-checks.js +13 -0
  72. package/dist/node/adapter.d.ts +8 -0
  73. package/dist/node/adapter.js +98 -0
  74. package/dist/node/agent.d.ts +49 -0
  75. package/dist/node/agent.js +266 -0
  76. package/dist/node/amounts.d.ts +2 -0
  77. package/dist/node/amounts.js +20 -0
  78. package/dist/node/browser-login.d.ts +25 -0
  79. package/dist/node/browser-login.js +219 -0
  80. package/dist/node/budget.d.ts +24 -0
  81. package/dist/node/budget.js +59 -0
  82. package/dist/node/buy-commands.d.ts +24 -0
  83. package/dist/node/buy-commands.js +301 -0
  84. package/dist/node/cli.d.ts +60 -0
  85. package/dist/node/cli.js +201 -0
  86. package/dist/node/command-context.d.ts +82 -0
  87. package/dist/node/command-context.js +77 -0
  88. package/dist/node/config.d.ts +37 -0
  89. package/dist/node/config.js +189 -0
  90. package/dist/node/core-adapter.d.ts +43 -0
  91. package/dist/node/core-adapter.js +122 -0
  92. package/dist/node/errors.d.ts +8 -0
  93. package/dist/node/errors.js +12 -0
  94. package/dist/node/index.d.ts +7 -0
  95. package/dist/node/index.js +5 -0
  96. package/dist/node/io.d.ts +13 -0
  97. package/dist/node/io.js +112 -0
  98. package/dist/node/keychain.d.ts +10 -0
  99. package/dist/node/keychain.js +67 -0
  100. package/dist/node/main.d.ts +2 -0
  101. package/dist/node/main.js +21 -0
  102. package/dist/node/offers.d.ts +16 -0
  103. package/dist/node/offers.js +91 -0
  104. package/dist/node/origin.d.ts +11 -0
  105. package/dist/node/origin.js +80 -0
  106. package/dist/node/paths.d.ts +3 -0
  107. package/dist/node/paths.js +71 -0
  108. package/dist/node/payment-terms.d.ts +26 -0
  109. package/dist/node/payment-terms.js +127 -0
  110. package/dist/node/purchase-commands.d.ts +16 -0
  111. package/dist/node/purchase-commands.js +237 -0
  112. package/dist/node/purchase-guard.d.ts +19 -0
  113. package/dist/node/purchase-guard.js +53 -0
  114. package/dist/node/relay-transport.d.ts +6 -0
  115. package/dist/node/relay-transport.js +173 -0
  116. package/dist/node/resources.d.ts +2 -0
  117. package/dist/node/resources.js +26 -0
  118. package/dist/node/retry-gate.d.ts +15 -0
  119. package/dist/node/retry-gate.js +53 -0
  120. package/dist/node/seller-commands.d.ts +12 -0
  121. package/dist/node/seller-commands.js +207 -0
  122. package/dist/node/sharing.d.ts +3 -0
  123. package/dist/node/sharing.js +28 -0
  124. package/dist/node/shops.d.ts +17 -0
  125. package/dist/node/shops.js +74 -0
  126. package/dist/node/signer-mock.d.ts +11 -0
  127. package/dist/node/signer-mock.js +79 -0
  128. package/dist/node/signer-wallet.d.ts +8 -0
  129. package/dist/node/signer-wallet.js +149 -0
  130. package/dist/node/signing.d.ts +29 -0
  131. package/dist/node/signing.js +200 -0
  132. package/dist/node/storage-commands.d.ts +11 -0
  133. package/dist/node/storage-commands.js +50 -0
  134. package/dist/node/sync-dir.d.ts +1 -0
  135. package/dist/node/sync-dir.js +11 -0
  136. package/dist/node/wallets.d.ts +13 -0
  137. package/dist/node/wallets.js +146 -0
  138. package/dist/oneclick/oneclick.d.ts +132 -0
  139. package/dist/oneclick/oneclick.js +342 -0
  140. package/dist/pay/backup.d.ts +70 -0
  141. package/dist/pay/backup.js +402 -0
  142. package/dist/pay/base.d.ts +135 -0
  143. package/dist/pay/base.js +386 -0
  144. package/dist/pay/eip3009.d.ts +59 -0
  145. package/dist/pay/eip3009.js +72 -0
  146. package/dist/pay/pay.d.ts +86 -0
  147. package/dist/pay/pay.js +297 -0
  148. package/dist/pay/purchase.d.ts +103 -0
  149. package/dist/pay/purchase.js +20 -0
  150. package/dist/pay/recover.d.ts +31 -0
  151. package/dist/pay/recover.js +34 -0
  152. package/dist/pay/seller.d.ts +130 -0
  153. package/dist/pay/seller.js +444 -0
  154. package/dist/pay/settle.d.ts +96 -0
  155. package/dist/pay/settle.js +308 -0
  156. package/dist/pay/split.d.ts +115 -0
  157. package/dist/pay/split.js +256 -0
  158. package/dist/pay/store-node.d.ts +8 -0
  159. package/dist/pay/store-node.js +224 -0
  160. package/dist/pay/store.d.ts +54 -0
  161. package/dist/pay/store.js +135 -0
  162. package/dist/pay/x402.d.ts +95 -0
  163. package/dist/pay/x402.js +136 -0
  164. package/dist/relay/admission.d.ts +8 -0
  165. package/dist/relay/admission.js +58 -0
  166. package/dist/relay/capabilities.d.ts +36 -0
  167. package/dist/relay/capabilities.js +232 -0
  168. package/dist/relay/fees.d.ts +17 -0
  169. package/dist/relay/fees.js +105 -0
  170. package/dist/relay/index.d.ts +10 -0
  171. package/dist/relay/index.js +8 -0
  172. package/dist/relay/keeper-balance.d.ts +17 -0
  173. package/dist/relay/keeper-balance.js +46 -0
  174. package/dist/relay/keeper-runner.d.ts +114 -0
  175. package/dist/relay/keeper-runner.js +703 -0
  176. package/dist/relay/keeper.d.ts +38 -0
  177. package/dist/relay/keeper.js +73 -0
  178. package/dist/relay/ledger.d.ts +155 -0
  179. package/dist/relay/ledger.js +392 -0
  180. package/dist/relay/relay-checks.d.ts +3 -0
  181. package/dist/relay/relay-checks.js +244 -0
  182. package/dist/relay/relay-reconciliation.d.ts +15 -0
  183. package/dist/relay/relay-reconciliation.js +293 -0
  184. package/dist/relay/relay-request.d.ts +6 -0
  185. package/dist/relay/relay-request.js +139 -0
  186. package/dist/relay/relay-transactions.d.ts +23 -0
  187. package/dist/relay/relay-transactions.js +182 -0
  188. package/dist/relay/relay-types.d.ts +128 -0
  189. package/dist/relay/relay-types.js +17 -0
  190. package/dist/relay/relay.d.ts +102 -0
  191. package/dist/relay/relay.js +426 -0
  192. package/dist/relay/reservation.d.ts +29 -0
  193. package/dist/relay/reservation.js +132 -0
  194. package/dist/relay/sponsorship.d.ts +42 -0
  195. package/dist/relay/sponsorship.js +184 -0
  196. package/dist/routes.d.ts +2 -0
  197. package/dist/routes.js +1 -0
  198. package/dist/settle-near/codec.d.ts +17 -0
  199. package/dist/settle-near/codec.js +86 -0
  200. package/dist/settle-near/identity.d.ts +27 -0
  201. package/dist/settle-near/identity.js +80 -0
  202. package/dist/settle-near/purchase.d.ts +40 -0
  203. package/dist/settle-near/purchase.js +153 -0
  204. package/dist/settle-near/terms.d.ts +37 -0
  205. package/dist/settle-near/terms.js +89 -0
  206. package/dist/shops/discovery.d.ts +95 -0
  207. package/dist/shops/discovery.js +384 -0
  208. package/dist/wallet/account-only.d.ts +11 -0
  209. package/dist/wallet/account-only.js +6 -0
  210. package/dist/wallet/uncertain.d.ts +8 -0
  211. package/dist/wallet/uncertain.js +13 -0
  212. package/dist/wallet/wallet.d.ts +100 -0
  213. package/dist/wallet/wallet.js +204 -0
  214. package/package.json +136 -3
package/README.md CHANGED
@@ -1,3 +1,592 @@
1
- # Temporary Holding Version
1
+ # @defdone/annon
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The Annon SDK and the `annon` command line tool. A seller encrypts a file into PAY2 and registers
4
+ its terms on the Annon contract on NEAR. A buyer pays in USDC on NEAR, on Base through x402, or from
5
+ another chain through NEAR Intents, and the contract releases the file key to the buyer alone. The
6
+ SDK runs in Node.js and in browsers; the CLI is built on it for developers and agents. People buying
7
+ files in a browser should use the browser purchase flow.
8
+
9
+ Protocol overview: the [repository README](https://github.com/defdone/annon/blob/master/README.md).
10
+ Walkthroughs: [buying](https://github.com/defdone/annon/blob/master/docs/quickstart.md),
11
+ [selling](https://github.com/defdone/annon/blob/master/docs/selling.md) and
12
+ [agents](https://github.com/defdone/annon/blob/master/docs/agents.md). Command reference:
13
+ [CLI.md](CLI.md). Security model and known limits:
14
+ [SECURITY.md](https://github.com/defdone/annon/blob/master/SECURITY.md).
15
+
16
+ The SDK and CLI have no usage fees or daily upload or sales quotas. Shops set their own hosting and
17
+ sponsorship limits ([shops and limits](https://github.com/defdone/annon/blob/master/docs/shops-and-limits.md)).
18
+
19
+ ## Contents
20
+
21
+ - [Install](#install)
22
+ - [Quick start](#quick-start)
23
+ - [Browser purchases](#browser-purchases)
24
+ - [Hosted selling](#hosted-selling)
25
+ - [CLI browser wallet login](#cli-browser-wallet-login)
26
+ - [Agent initialization and spending policy](#agent-initialization-and-spending-policy)
27
+ - [Remote signer, HTTP JSON v1](#remote-signer-http-json-v1)
28
+ - [Durable keeper](#durable-keeper)
29
+ - [RPC configuration](#rpc-configuration)
30
+ - [Base splits](#base-splits)
31
+ - [Previews](#previews)
32
+ - [Security notes](#security-notes)
33
+
34
+ ## Install
35
+
36
+ Node.js 22 or newer. The package is on the npm `alpha` tag.
37
+
38
+ ```sh
39
+ npm i @defdone/annon@alpha # the SDK
40
+ npm i -g @defdone/annon@alpha # the annon CLI
41
+ ```
42
+
43
+ | Entry point | Use |
44
+ | --- | --- |
45
+ | `@defdone/annon` | SDK core: `Annon`, wallets, identities, terms, 1Click, encrypted stores and backups, storage funding, sponsorship checks and types, size/retry limits, error classes |
46
+ | `@defdone/annon/browser` | Browser purchase controller, downloads, wallet adapters, sponsorship checks and types, size/retry limits, error classes |
47
+ | `@defdone/annon/browser/purchase-bundle.js` | The same browser entry as one standalone ESM bundle |
48
+ | `@defdone/annon/node` | Node only: `createAdapter` (the engine behind the CLI and the MCP server), `publicError`, `AdapterError`, `BudgetError` |
49
+ | `@defdone/annon/relay` | Relayer admission, durable sponsorship and settlement: `relay`, `RelayLedger`, `keep` and their types |
50
+ | `@defdone/annon/pay/store-node` | `fileStore`, a durable file-backed record store for Node |
51
+ | `@defdone/annon/node/signer-mock` | `signerMock`, the reference signing handler for tests |
52
+ | `@defdone/annon/pay/seller`, `pay/settle`, `pay/store`, `pay/split`, `download/host`, `download/http`, `envelope/pay2`, `near/rpc` | Building blocks for a server that sells files: x402 offers and payment checks, settlement, record stores, revenue splits, grant downloads, the PAY2 format and NEAR RPC reads |
53
+ | `@defdone/annon/node/adapter`, `node/cli`, `node/errors` | Deprecated: use `@defdone/annon/node` |
54
+
55
+ ### From source
56
+
57
+ Build with `pnpm build`. In a checkout, run `node packages/annon/dist/node/main.js` in place of
58
+ `annon`. Running the source with a TypeScript runner reads the generated
59
+ `dist/browser/login-bundle.js`, so run `pnpm --filter @defdone/annon build` first; without the bundle,
60
+ `annon login` prints that build instruction.
61
+
62
+ ## Quick start
63
+
64
+ The shortest path is the CLI: [docs/quickstart.md](https://github.com/defdone/annon/blob/master/docs/quickstart.md)
65
+ buys, opens and recovers a demo file in a few commands, and [CLI.md](CLI.md) covers configuration,
66
+ encrypted purchase backups and every command.
67
+
68
+ In code, an `Annon` instance needs a deployment, a durable record store, a persisted backup secret, a
69
+ place to write encrypted backups, and a transport for contract calls. In Node, with a NEAR account
70
+ that signs its own calls:
71
+
72
+ ```ts
73
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
74
+ import { join } from 'node:path';
75
+ import { Annon, nearKeyPairWallet, storageDeposit } from '@defdone/annon';
76
+ import { fileStore } from '@defdone/annon/pay/store-node';
77
+
78
+ const contractId = 'v1.annon.defdone.near';
79
+ const stateDir = '/absolute/private/annon-state';
80
+ const backups = join(stateDir, 'backups');
81
+ for (const dir of [join(stateDir, 'records'), backups])
82
+ await mkdir(dir, { recursive: true, mode: 0o700 });
83
+
84
+ // 64 hex characters (32 random bytes), created once with a cryptographic generator and kept
85
+ // outside the repository. Records and backups are unreadable without it.
86
+ const backupSecret = Buffer.from(
87
+ (await readFile('/absolute/private/annon-backup-secret', 'utf8')).trim(),
88
+ 'hex',
89
+ );
90
+
91
+ // `account` is a near-api-js Account for your NEAR account.
92
+ const near = nearKeyPairWallet(account);
93
+
94
+ const annon = new Annon({
95
+ contractId,
96
+ network: 'near:mainnet',
97
+ nearRpc: 'https://rpc.mainnet.fastnear.com',
98
+ baseRpc: process.env.BASE_RPC!, // an endpoint with an API key, see RPC configuration
99
+ rawStore: fileStore(join(stateDir, 'records')),
100
+ backupSecret,
101
+ writeBackup: (name, encrypted) =>
102
+ writeFile(join(backups, encodeURIComponent(name)), encrypted, { mode: 0o600 }),
103
+ // Contract calls (register, claim_key, reopen and others) are signed by the call's NEAR owner,
104
+ // or by your account when the caller is not a NEAR wallet.
105
+ send: (method, args, owner) => (owner ?? near).call({ receiverId: contractId, method, args }),
106
+ });
107
+
108
+ // Direct calls use your prepaid storage balance on the contract. Fund it once, with an amount you
109
+ // choose (yoctoNEAR); insufficient storage is reported as `storage_required`.
110
+ await storageDeposit(near, contractId, 100_000_000_000_000_000_000_000n);
111
+ ```
112
+
113
+ `annon.sell`, `annon.buy`, `annon.open`, `annon.openTo` and `annon.recover` then drive listings and
114
+ purchases with the wallet you pass in (`evmWallet`, `solanaWallet`, `nearKeyPairWallet`,
115
+ `nearSelectorWallet`). Without `send`, a call whose signer is not a NEAR wallet fails with
116
+ "configure a transaction transport for relayed calls".
117
+
118
+ For sponsored calls through a relayer instead of your own NEAR account, `send` posts to the
119
+ relayer's `/v1/relay` endpoint and waits for its result. The CLI implements that transport in
120
+ [`packages/annon/src/node/relay-transport.ts`](https://github.com/defdone/annon/blob/master/packages/annon/src/node/relay-transport.ts).
121
+ Its adapter,
122
+ [`packages/annon/src/node/core-adapter.ts`](https://github.com/defdone/annon/blob/master/packages/annon/src/node/core-adapter.ts),
123
+ and the command modules it calls are the complete reference integration, including trust, prompts
124
+ and backups.
125
+
126
+ ## Browser purchases
127
+
128
+ Import the headless purchase controller from `@defdone/annon/browser`. A seller or marketplace
129
+ supplies its own screen, a verified offer and wallet adapters. There is no hosted checkout.
130
+ `dist/browser/purchase-bundle.js` is built with the same CSP checks as the CLI approval bridge.
131
+
132
+ ```ts
133
+ import {
134
+ browserAnnon,
135
+ browserEvmWallet,
136
+ browserPurchaseStorage,
137
+ BrowserPurchaseController,
138
+ } from '@defdone/annon/browser';
139
+
140
+ const storage = await browserPurchaseStorage();
141
+ const sdk = browserAnnon(storage, trustedSdkOptions);
142
+ const controller = await BrowserPurchaseController.create({ storage, sdk }, {
143
+ id: stableCheckoutId,
144
+ fileId,
145
+ header: checkedHeader,
146
+ resourceUrl,
147
+ route: '1click',
148
+ maxPrice: approvedUsdcCap,
149
+ });
150
+ controller.subscribe(renderProgress);
151
+ await controller.start({ recovery: recoveryWallet, refund: refundIdentity, origin });
152
+ const instructions = await controller.instructions();
153
+
154
+ // Later, once the purchase is accepted:
155
+ const { meta, data } = await controller.open({ onProgress: renderDownload });
156
+ ```
157
+
158
+ ### Checkouts, reloads and tabs
159
+
160
+ - Use a stable, caller-owned checkout ID for each terms review. Creation refuses an existing ID.
161
+ - `route` is `'near'`, `'base'` or `'1click'`, the same names as in CLI and MCP results. Offer
162
+ `'base'` only when the file's final on-chain terms include a Base payee; otherwise the SDK refuses
163
+ it with `route_unavailable` before any wallet step. For a file without Base terms, a buyer holding
164
+ USDC on Base uses `'1click'` with Base as the source chain.
165
+ - After a reload, construct `new BrowserPurchaseController({ storage, sdk, id })` and call
166
+ `resume()`. Never call `buy()` or create another checkout to resume an uncertain payment.
167
+ - `storage.list()` supplies the purchases list.
168
+ - A renewable two-minute lease protects an active checkout across tabs. `poll`, `recover` and `open`
169
+ refuse a live lease held by another tab with `PurchaseLeaseBusy` (code `lease_busy`, with
170
+ `waitUntil`); `resume` returns without writing. `snapshot()` and progress events carry
171
+ `leaseWaitUntil` (Unix seconds) so the page can say why it is waiting, for example after a crashed
172
+ tab.
173
+ - After an expired lease, an unsigned preparation that never reached the payment boundary becomes
174
+ `abandoned`; the UI may then explicitly create a fresh checkout ID. Signed Base payloads and
175
+ purchases that crossed the payment boundary remain resumable and must never be replaced.
176
+ - Base starts require `submitBase`. Signed payloads become uncertain before the SDK dry run.
177
+ - `snapshot()` and progress events contain public metadata only; persistence records contain private
178
+ recovery material.
179
+ - `instructions()` releases only a saved, proved 1Click quote with the required time margin.
180
+
181
+ ### Paying from another chain (1Click)
182
+
183
+ - Use `OneClick.tokens()` and `originFromToken(tokens, assetId, blockchain, refundTo)` for exact
184
+ source selection. Supported sources are the explicitly listed EVM chains, Solana and NEAR.
185
+ - Catalog selection refuses unsupported families and slow chains. Legacy caller-specified origins in
186
+ the other family keep the existing nonempty, whitespace-free refund validation.
187
+ - NEAR catalog selections explicitly request `ORIGIN_CHAIN` refunds. Legacy direct `quote()` callers
188
+ keep their existing NEAR `INTENTS` default.
189
+ - A 1Click source refund and an Annon rejection refund are different obligations. The latter stays
190
+ in NEAR Intents.
191
+ - `OneClick.preview()` returns amounts and timing without a deposit address.
192
+ - `poll()` awaits progress callbacks, saves memo-aware checkpoints and retries transport errors and
193
+ rate limits with backoff. The controller persists checkpoints automatically.
194
+ - Polling remains available after expiry to track late deposits and refunds. Expiry never extends
195
+ the purchase or permits a new deposit. `SUCCESS` still requires contract acceptance.
196
+
197
+ ### Storage and receipts
198
+
199
+ - Each checkout is stored in its own encrypted IndexedDB record, with a small metadata index
200
+ committed atomically alongside it. Index contention retries without weakening purchase revision
201
+ checks.
202
+ - Latest purchase and recovery snapshots are kept alongside every recovery revision archive created
203
+ before an expired key is replaced. Portable receipts carry those archives too.
204
+ - The local random backup secret is persisted in IndexedDB for reload recovery. It protects
205
+ encrypted storage from accidental disclosure and shares the embedding page's trust boundary.
206
+ - Export with `controller.exportReceipt(portableSecret)`, using a separate random 32-byte
207
+ `newBackupSecret()` that you keep separately. Import with
208
+ `storage.importReceipt(file, portableSecret)`. Import restores the header, resource URL and
209
+ recovery records, and requires a fresh recovery proof before releasing deposit instructions.
210
+ - IndexedDB failures and stale writes reject before any payment effect.
211
+
212
+ ### Downloading and opening
213
+
214
+ - `recover(wallet)` resumes recovery of an accepted purchase on a new device.
215
+ - `open()` downloads the PAY2 file from the saved resource URL with the buyer's grant proof. It
216
+ checks the header against the one pinned before payment and each chunk against that header while
217
+ downloading; the SDK then checks the on-chain listing and grant before it decrypts in memory.
218
+ `open(bytes)` opens a PAY2 file the page already has.
219
+ - The result is `{ meta, data }`. Plaintext and file keys are never written to storage; the record
220
+ only moves to `file_ready`.
221
+ - Every path caps a file at 50 MB of plaintext (`MAX_PLAINTEXT_BYTES`, 50,000,000 bytes). The
222
+ download cap `BROWSER_DOWNLOAD_LIMIT` equals `MAX_PAY2_BYTES`: those 50 MB plus the largest PAY2
223
+ header, metadata and tags. A known larger size is refused before downloading, and a stream that
224
+ passes the cap is aborted. Both raise `DownloadError` with code `too_large`.
225
+ - Pass `signal` to cancel and `onProgress` to follow the `downloading`, `decrypting` and `ready`
226
+ phases. A failed or cancelled download leaves the purchase accepted, so `open` can be retried
227
+ without paying again.
228
+ - `browserDownload` is the same bounded, proof-authorized download without the controller.
229
+
230
+ ### Wallet adapters
231
+
232
+ The adapters wrap the provider object a page already has. The SDK imports no wallet library.
233
+
234
+ | Adapter | Provider | Used for |
235
+ | --- | --- | --- |
236
+ | `browserEvmWallet(provider, address)` | EIP-1193 `request` (MetaMask, Coinbase Wallet and others) | Recovery signatures with `personal_sign` on any chain; Base x402 payments with `eth_signTypedData_v4`, after checking or requesting a switch to Base |
237
+ | `browserSolanaWallet(provider, address)` | Injected `publicKey` and `signMessage` | Recovery signatures |
238
+ | `browserNearWallet(provider, accountId)` | NEAR Wallet Selector style: required `getAccounts`, `signMessage` (NEP-413) and `signAndSendTransaction` | Recovery signatures and USDC payments on the NEAR route |
239
+
240
+ Each adapter checks what the wallet returns: the signature is verified against the expected
241
+ identity, the account is still the selected one, and Base payments are signed for chain 8453.
242
+ Failures raise `BrowserWalletError` with a code a page can show: `rejected`, `wrong_chain`,
243
+ `wrong_identity`, `invalid_signature`, `missing_method`, `invalid_response` or `wallet_error`.
244
+
245
+ ## Hosted selling
246
+
247
+ `annon.share(options)` sells a file through a shop's host: it quotes the final terms without writing
248
+ anything, calls `onSplit` with the split and fee for approval, saves the pending encrypted backup
249
+ (`confirmBackup` must resolve only after you stored it and its separate secret), then uploads,
250
+ creates the shop's Base split for an EVM seller, registers, funds a NEAR seller's storage and
251
+ publishes. It returns `{ fileId, url, header, expiresAt }`. `expiresAt` is the hosting expiry the
252
+ shop states, in Unix seconds, or `null` when the shop plans no expiry; the SDK sets no maximum of its
253
+ own and refuses a time that has already passed with `file_expired`. A saved completed sale stays
254
+ readable after that time. `hosting.origin` is the host of a shop you approved; there is no default host. Each step is checkpointed in the encrypted sell record, and
255
+ `annon.resumeShare({ fileId, data, seller, hosting })` continues a stopped sale with the original
256
+ file and terms, reconciling any uncertain transaction before it sends another.
257
+
258
+ - Without `near.recipients` the split pays the seller; sponsored EVM and Solana sellers get the
259
+ relayer's fee share added, as with `sell`.
260
+ - Only EVM sellers get Base terms. NEAR and Solana sellers list with `base: null`.
261
+ - A NEAR seller signs the upload with NEP-413 (`hosting.signUpload`; `signNearUpload` in the browser
262
+ entry), sends its own transactions, and pays `listingStorage` and `storageBudget` in yoctoNEAR.
263
+ - `UploadClient` and `uploadText` are exported for shops that drive the upload protocol themselves.
264
+
265
+ In a page, `createBrowserSeller(options)` from `@defdone/annon/browser` returns a `SellerFacade`:
266
+
267
+ ```ts
268
+ const seller = createBrowserSeller({ ...trustedSdkOptions, host, indexStore, wallet });
269
+ const quote = await seller.quote(connection, { file, title, description, priceUsdc: '1.00' });
270
+ const sale = await seller.begin(quote.quoteId); // after the person approved the quote
271
+ const backup = await sale.exportBackup(); // let the person save it, then:
272
+ await sale.confirmBackup();
273
+ let view = await sale.view();
274
+ while (view.next) view = await sale.next(); // the only method that signs or writes
275
+ ```
276
+
277
+ - `quote` signs and writes nothing. It shows the shares, fee, NEAR funding, routes and steps.
278
+ - `next()` runs one step. Each step is persisted before its effect, and a lease keeps two tabs from
279
+ running the same sale.
280
+ - After a reload, `active()` returns the open sale. `attachWallet`, `attachFile` and `unlock` supply
281
+ what `view().needs` lists; `discard()` drops a sale.
282
+ - `view().result` holds `{ fileId, url, expiresAt }` once the file is published; `expiresAt` may be
283
+ `null` (no planned expiry). `quote.hostedDays` shows the optional `fileLifetimeDays` option, which
284
+ a page fills from the selected shop's rules; it is absent when the page does not pass it.
285
+
286
+ ### Shop discovery
287
+
288
+ `listShops(config, query)` and `catalog(config, query)`, from both entries, read shop manifests and
289
+ catalogs with client-side search and paging and return `{ items, total, nextOffset }`. `config` is the
290
+ public discovery configuration (`shops`, `defaultShop`, `shopDirectoryUrl`); there is no built-in
291
+ shop or directory. A manifest's optional `rules` (`ShopRules`) is validated strictly and returned as
292
+ shop-provided data: on each shop's `info` from `listShops`, and as `rules` with `shop` (`manifest`,
293
+ `name`) from `catalog`. Show it as text from that shop; it grants no trust. Limits and fields:
294
+ [CLI.md](CLI.md#shop-discovery).
295
+
296
+ ## CLI browser wallet login
297
+
298
+ ```sh
299
+ annon login --wallet evm
300
+ annon login --wallet near --network near:testnet
301
+ annon login --wallet near --no-open
302
+ ```
303
+
304
+ Login proves control of a browser wallet you already use. It installs no wallet key and grants no
305
+ ongoing signing authority. Every later signing action opens a new local session and needs approval
306
+ in the wallet.
307
+
308
+ | Wallet | How | Signing supported |
309
+ | --- | --- | --- |
310
+ | EVM | Injected EIP-1193 wallet on Base | `personal_sign` for the SDK's authorization texts, `eth_signTypedData_v4` for the exact EIP-3009/x402 payload |
311
+ | NEAR | Meteor Wallet (extension or web popup; mainnet and testnet) or HOT Wallet (mainnet only) through Wallet Selector, picked on the page; account-only sign-in (no `contractId`, no method list) | NEP-413 and `signAndSendTransaction`, including the required deposit `"1"` on `ft_transfer_call` |
312
+
313
+ Browser and remote EVM split-deployment transactions and browser and remote Solana signing are not
314
+ included; the local-key adapters remain available for those workflows.
315
+
316
+ **The local page.** The listener binds `127.0.0.1` on an OS-assigned port before opening the browser.
317
+ Match the short pairing code with the terminal, and review the action, amount, recipients, file and
318
+ chain on the page before approving in the wallet.
319
+
320
+ - Host (including port), Origin and an unpredictable RAM-only capability are checked on action and
321
+ result API calls.
322
+ - In open mode, a same-origin POST bootstraps the capability once into the opened page, then the
323
+ bootstrap route closes. With `--no-open`, bootstrap is disabled and the capability exists only in
324
+ the printed URL fragment. It is never passed to the browser launcher in argv.
325
+ - Bundled scripts, a restrictive CSP, no framing, `no-store` and `no-referrer` are enforced. The page
326
+ connects only to itself, the Wallet Selector's NEAR RPC (`free.rpc.fastnear.com` on mainnet,
327
+ `test.rpc.fastnear.com` on testnet, fixed in the page and independent of your configured RPC) and
328
+ HOT's relay `h4n.app`, and frames only `hot-labs.org`.
329
+ - Actions expire after two minutes and are verified once. Identical popup and opener result
330
+ submissions return the same outcome.
331
+ - Sessions close after the command or after five idle minutes. Active approvals keep their own
332
+ expiry, and each completed approval restarts the idle timer.
333
+ - A rejected or uncertain action must be reviewed at the terminal before another session starts.
334
+ - No wallet result is accepted without a local signature check or an independent finalized RPC
335
+ outcome check.
336
+
337
+ **Remote machines.** `--no-open` prints the full URL and pairing code to stderr. Over SSH, forward
338
+ the printed port with `ssh -L PORT:127.0.0.1:PORT host` and open that exact URL locally. Keep the
339
+ capability URL private.
340
+
341
+ **NEAR wallets.** MyNearWallet is retired and no longer connects to apps, so the page offers Meteor
342
+ Wallet and HOT Wallet. HOT's Wallet Selector module sends no network with its requests, so the page
343
+ offers it for `near:mainnet` only and refuses it, before loading it, for any other network; requests
344
+ for chains other than `near:mainnet` and `near:testnet` are refused. Both return results to the page itself: Meteor through its extension or a
345
+ popup on `wallet.meteorwallet.app`, HOT through its extension or the HOT widget, which the page
346
+ frames from `hot-labs.org` (the only `frame-src` the CSP allows) and which exchanges the request
347
+ through HOT's relay at `h4n.app`. The page removes the capability from browser history immediately,
348
+ and no capability is sent to a wallet. NEP-413 requests carry no callback URL, so the signed hash
349
+ covers the message, nonce and recipient, as [NEP-413](https://github.com/near/NEPs/blob/master/neps/nep-0413.md)
350
+ specifies. Meteor's SDK creates a local key pair during sign-in and sends its public key to the
351
+ wallet; the page never names a contract or methods. The secret stays in the localStorage of the
352
+ session's own `127.0.0.1` origin, is never logged (the bundled SDK's key-pair log is removed) and
353
+ is removed when the page signs out of Meteor before a new sign-in.
354
+
355
+ **What is saved.** Only public identity, network and configuration go into the config file. Login
356
+ works before you choose a contract; NEAR ownership checks use a public mainnet or testnet RPC by
357
+ default, for login only. Configure and trust your reviewed deployment before other commands. Wallet
358
+ and browser internals have their own storage behaviour. Browser approval is still required with
359
+ `--json`; terminal prompts are disabled in that mode.
360
+
361
+ ## Agent initialization and spending policy
362
+
363
+ Choose one signer source. Secrets are referenced, never copied into configuration:
364
+
365
+ ```sh
366
+ annon init --agent --wallet evm --recovery evm:0x1111111111111111111111111111111111111111 --key-env BOT_EVM_KEY --daily-budget 2
367
+ annon init --agent --wallet near --near-account bot.near --near-key-file /absolute/credentials.json --daily-budget 2
368
+ annon init --agent --wallet near --near-account bot.near --keychain-entry 'near-mainnet-bot.near/bot.near:ed25519:PUBLIC_KEY' --daily-budget 2
369
+ annon init --agent --wallet near --near-account bot.near --new-key --keychain-entry annon-bot --daily-budget 2
370
+ annon init --agent --signer https://signer.example --recovery evm:0x1111111111111111111111111111111111111111 --credential-env SIGNER_TOKEN --daily-budget 2
371
+ ```
372
+
373
+ ### Signer sources
374
+
375
+ - Saved file and environment signers require a public identity (`--recovery` or `--near-account`),
376
+ which is checked against the loaded key.
377
+ - When a signer is configured, per-call `--evm-key-file`, `--near-key-file` and `--solana-key-file`
378
+ overrides are refused.
379
+ - `--key-env` stores only an environment variable's name. EVM variables and files contain hex
380
+ private keys; NEAR variables and files contain credential JSON, as in CLI.md.
381
+ - `--new-key` generates directly into an injectable OS keychain interface and returns only public
382
+ identity and key metadata. The shipped OS adapter supports macOS; other platforms can inject an
383
+ adapter or use file, environment or remote references. Tests always inject a fake keychain and
384
+ process.
385
+ - A new NEAR key is **not installed on-chain** by this command: the owner must authorize its public
386
+ key separately. A function-call key cannot attach the one yoctoNEAR required for an FT purchase.
387
+ Agent payment keys must have the authority the action requires.
388
+
389
+ **macOS Keychain.** New entries use `security`'s `-T ""` ACL, which removes automatic access for the
390
+ creator executable, so every agent key read prompts for macOS user approval. **Never click Always
391
+ Allow for agent keys**: doing so restores automatic access. A generic `security` executable cannot
392
+ isolate trusted callers. Review the ACL of existing entries in Keychain Access. For unattended use,
393
+ choose a policy-enforcing remote signer rather than granting `security` automatic access.
394
+
395
+ ### Spending policy
396
+
397
+ Agent configuration includes `agent: true`, `dailyBudget` (decimal USDC), `signer` and `workDir`, a
398
+ directory the owner creates and every agent file path must stay inside (`work_dir_required` without
399
+ it). Keep the deployment settings, `maxPrice`, `allowedResources`, named `origins` and trusted
400
+ `moderators` in that same public config.
401
+
402
+ Order matters, because agent mode refuses `init`, `login` and per-call trust or deployment flags:
403
+
404
+ 1. Create the working directory.
405
+ 2. Write the deployment, `maxPrice`, `workDir` and, after reviewing the contract,
406
+ `trustContract: true` (or trust it once with a human command and `--trust-contract`). Do not set
407
+ `agent: true` yourself.
408
+ 3. Run one `annon init --agent ...` from the examples above. It saves the signer, the daily budget
409
+ and `agent: true`.
410
+
411
+ - Agents never prompt. Missing trust, price cap or daily budget causes a refusal.
412
+ - `allowedResources` is optional. Without it, any secure resource URL is allowed within the
413
+ spending limits; an empty list denies every purchase.
414
+ - The full price is reserved atomically before any payment or deposit instructions are prepared.
415
+ - UTC daily usage lives in `stateDir/budgets`, survives restarts, and is shared across chains and
416
+ identities that use that state directory. Concurrent writers use the file store's CAS and locking.
417
+ - Over-budget requests return `budget_exceeded`; missing policy returns `budget_required`.
418
+ - NEAR and Base reservations stay charged after failures and ambiguous outcomes. A 1Click failure
419
+ before deposit instructions are returned releases its reservation; returned instructions keep it
420
+ reserved. There is no other automatic refund.
421
+
422
+ ### Pinning the configuration
423
+
424
+ Protect the config and state directory from changes by untrusted tools. The trusted launcher pins
425
+ `ANNON_CONFIG` (or the default config path) and `ANNON_STATE_DIR` (or the configured or default state
426
+ directory).
427
+
428
+ - Once that config has `agent: true`, per-call `--config` changes, `--state-dir`, `init` and `login`
429
+ are refused, and TTY prompts stay disabled.
430
+ - Launch with `ANNON_AGENT=true` to force the policy even if the config lacks `agent: true`. In this
431
+ mode the pinned config is `~/.config/annon/config.json`, and its saved `stateDir` (or
432
+ `~/.local/share/annon`) pins the ledger. A differing `ANNON_CONFIG` is an error
433
+ (`config_conflict`); `ANNON_STATE_DIR` is ignored, and the saved configuration or the default
434
+ owns the ledger location.
435
+ - Custom agent config paths must be set through `ANNON_CONFIG` before initialization; alternate
436
+ `init --config` paths are rejected. Human initialization must happen before handing off this
437
+ launcher environment.
438
+
439
+ **Limit.** An agent with shell access as the same OS user can edit config and ledgers, change
440
+ environment variables, call the SDK directly or use accessible signing credentials. It is outside
441
+ this protection, and CLI budgets are not a security boundary against it. Use OS isolation and a
442
+ remote signer with its own spending policy for that boundary.
443
+
444
+ ### Human payments
445
+
446
+ Before a human payment the CLI confirms the current price, split recipients and remainder, resource
447
+ and file, token and network. Browser mode shows these exact terms with each signing request for
448
+ approval in the page and the wallet. Local and remote human signing require an explicit terminal
449
+ confirmation. Purchase and recovery secrets still need the independent backup secret described in
450
+ CLI.md; login does not derive one from a signature.
451
+
452
+ ## Remote signer, HTTP JSON v1
453
+
454
+ `init --agent --signer` persists an HTTPS origin and either `--credential-env NAME` or
455
+ `--credential-keychain SERVICE/ACCOUNT`.
456
+
457
+ - Signer origins and credential references can only be set during `init`. Every per-command
458
+ override is rejected before credential lookup.
459
+ - URL credentials, query or fragment tokens, plaintext credential fields and HTTP are rejected.
460
+ Redirects are refused.
461
+ - The bearer value is read at use time and never saved or printed.
462
+
463
+ A provider adapter implements:
464
+
465
+ | Request | Response |
466
+ | --- | --- |
467
+ | `GET /v1/capabilities` | `{version:1, chain, account, operations:[...]}` |
468
+ | `POST /v1/requests` | `{version:1, id, status:"pending"\|"approved"\|"rejected", result?}` |
469
+ | `GET /v1/requests/{id}` | Same status response; idempotent polling, no new signing |
470
+
471
+ Every request uses `Authorization: Bearer <credential>` over HTTPS. JSON request fields are
472
+ `version:1`, a unique `id`, `operation`, `chain`, `account`, `expiresAt` (Unix milliseconds),
473
+ `payload`, and optional `terms` (display and policy context). Integers too large for JSON numbers are
474
+ decimal strings; nonces are byte arrays.
475
+
476
+ - An ID is never reused for a different exact request. Repeated POSTs must return the same job,
477
+ including during approval.
478
+ - Expired, rejected and mismatched responses are refused locally. A transport failure does not
479
+ authorize a new signing request.
480
+ - The client discovers capabilities before posting, retries a lost create response with the same
481
+ immutable ID and payload (up to three attempts), and polls that ID every second until its
482
+ deadline. HTTP refusals are not retried.
483
+
484
+ | Operation | Exact payload | Approved result |
485
+ | --- | --- | --- |
486
+ | `evm.personalSign` | `{message}`: literal SDK authorization text | `{signature}`: 65-byte hex EIP-191 signature |
487
+ | `evm.signTypedData` | EIP-712 `{domain,types,primaryType,message}` | `{signature}`: checked against the domain, chain and selected account |
488
+ | `near.nep413` | `{message,recipient,nonce,callbackUrl?}` | `{accountId,publicKey,signature}`: base64 Ed25519 signature, checked with final full-access membership |
489
+ | `near.signTransaction` | `{receiverId,method,args,gas,deposit,transaction}`; `transaction` is the exact unsigned Borsh bytes in base64 | `{signedTransaction}`: signed Borsh bytes in base64; exact bytes, fields, signature and final full-access key membership are checked before submission |
490
+
491
+ Chains are `eip155:8453` for the supported EVM payment route and `near:mainnet` or `near:testnet` for
492
+ NEAR. Remote NEAR transaction configuration also needs `signer.publicKey` (`--near-public-key`
493
+ during init), so the CLI builds the exact nonce, block and actions before approval. Providers must
494
+ apply their own independent policy to these semantic operations; discovery is not permission to
495
+ sign arbitrary bytes.
496
+
497
+ **Transaction outcomes.** The browser bridge returns `{txHash}` for wallet-submitted NEAR calls, and
498
+ the CLI independently checks the final signer, receiver, single action, args, gas and deposit through
499
+ RPC. A returned transaction hash alone never proves success.
500
+
501
+ - Definite mismatches and failed finalized transactions are rejected without saving the hash as
502
+ `nearSubmission` evidence. Only RPC unavailability or a non-final status keeps the outcome
503
+ uncertain.
504
+ - If submission or finalized verification is ambiguous, the CLI returns `state: "uncertain"`,
505
+ `purchaseId` and `txHash`, and keeps `nearSubmission: {state:"uncertain",txHash}` in the encrypted
506
+ purchase backup. Check status for that saved purchase before buying again.
507
+ - Signing errors with a known hash also expose safe hash and purchase evidence, with exit code 4.
508
+
509
+ **Reference handler.** `node/signer-mock` exports `signerMock`, an authenticated HTTP
510
+ `Request -> Response` handler with injected approval and signing, duplicate-ID claims, immutable
511
+ request checks, and `decide(id, result)` for pending approvals. Use it in tests or wrap it in your
512
+ provider's HTTPS server. It keeps jobs in RAM and is not a durable production signer: production
513
+ adapters must persist idempotency state and provide an approval and status UI as appropriate. See
514
+ [`node/signing.test.ts`](https://github.com/defdone/annon/blob/master/packages/annon/src/node/signing.test.ts) for fake-key examples; tests need no provider credentials or real signing
515
+ service.
516
+
517
+ ## Durable keeper
518
+
519
+ `keep` is exported from `@defdone/annon/relay`. Supply a stable `network` (`near:mainnet`,
520
+ `near:testnet` or `near:sandbox`), a dedicated account and key, durable `store` and `budgetStore`,
521
+ hourly and daily yoctoNEAR budgets, and a maximum gas price. In Node, create private directories and
522
+ use `fileStore<KeeperRecord>` and `fileStore<BudgetRecord>`. Environment configuration is in the
523
+ [relayer README](https://github.com/defdone/annon/blob/master/apps/relayer/README.md).
524
+
525
+ ### Discovery and settlement
526
+
527
+ - Discovery reads `open_delivery_count` and `list_open_deliveries` against one finalized block. The
528
+ block, count and cursor are persisted across bounded passes (default 20 pages of 50).
529
+ - A new finalized snapshot begins after each complete sweep. This covers swap-removals and later
530
+ materializations without a historical-ID eligibility cap.
531
+ - Current finalized state is re-read before sending. `settle` uses 600 Tgas while unfunded and 450
532
+ Tgas when funded.
533
+ - InFlight funding and slots are never reset or newly submitted. Calls for other pending slots can
534
+ proceed; the contract guards InFlight slots.
535
+
536
+ ### Running the keeper
537
+
538
+ Run the keeper with a dedicated funded account and durable state. Configure its hourly and daily
539
+ spending limits, retain its state after a timeout, and check the saved transaction before retrying.
540
+ The relayer README lists the service settings. A `RefundSource` integration supplies refund leaves
541
+ and current proofs for `materializeRefund`. Run one keeper process per signing key.
542
+
543
+ ## RPC configuration
544
+
545
+ | Who | Needs | Setting |
546
+ | --- | --- | --- |
547
+ | A file host, the CLI for Base purchases, the relayer | A Base RPC endpoint with an API key | `baseRpc` in SDK and host options or CLI config; `ANNON_BASE_RPC` for the CLI; `BASE_RPC` for the relayer |
548
+ | A keeper polling every few seconds | A NEAR RPC endpoint with an API key | `nearRpc` in SDK and CLI config; `ANNON_NEAR_RPC` for the CLI; `NEAR_RPC` for the relayer |
549
+
550
+ The public `https://mainnet.base.org` rate-limits the burst of reads made during settlement and
551
+ unlocking. Keep API keys out of public files.
552
+
553
+ ## Base splits
554
+
555
+ When a file has Base terms, `sell` results include `baseSplit: { address, kind, salt }` beside
556
+ `terms`. Public results omit `baseSplit` for a file without Base terms; its encrypted sell record
557
+ stores `null`. This metadata stays with the sell record through resume and publication and is
558
+ outside the on-chain terms and their hash.
559
+
560
+ To call `readSplit` later, pass `address` and `{ salt }`, and rebuild the split from
561
+ `terms.near.recipients` in their saved order:
562
+
563
+ | Split field | Value |
564
+ | --- | --- |
565
+ | `recipients` | The recipient identity IDs |
566
+ | `allocations` | Each recipient's `bps` |
567
+ | `totalAllocation` | `10000n` |
568
+ | `distributionIncentive` | `0` |
569
+
570
+ ## Previews
571
+
572
+ A seller can build a PAY2 file with a preview by passing `previewLen` to `sell` (format:
573
+ [specs/pay2.md](https://github.com/defdone/annon/blob/master/specs/pay2.md) section 7). The SDK does
574
+ not verify preview packages for buyers in this release, so a page must not present a preview as
575
+ checked.
576
+
577
+ ## Security notes
578
+
579
+ - The browser build fails if a post-build scan of the emitted JavaScript finds `eval`, `Function`
580
+ construction or calls, or string-argument timers.
581
+ - CSP compatibility is also checked statically against the actual built, minified bundle and the HTTP
582
+ page and CSP header: no dynamic `eval` or `Function` calls, string timers, inline scripts or HTML
583
+ event handlers.
584
+ - The browser build removes legacy dynamic-code fallbacks from the pinned Wallet Selector and
585
+ js-sha256 copies, with guards that require review when their source changes.
586
+ - VM tests disable string and Wasm code generation and exercise fake wallet flows.
587
+ - **Real-browser execution is not covered by the test suite.** Tests never launch browsers or GUI
588
+ applications and always stub the login opener; no real wallet, RPC, transaction or Keychain is
589
+ used.
590
+
591
+ The trust model, the upgrade key during the alpha and the known protocol limits are in
592
+ [SECURITY.md](https://github.com/defdone/annon/blob/master/SECURITY.md).