@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
@@ -0,0 +1,55 @@
1
+ import type { PollCheckpoint } from '../oneclick/oneclick.js';
2
+ import type { RecoveryRecord } from '../pay/backup.js';
3
+ import type { PurchaseRecord } from '../pay/purchase.js';
4
+ import type { Versioned } from '../pay/store.js';
5
+ import type { BuyRoute } from '../routes.js';
6
+ export declare const purchaseStates: readonly ["terms_review", "preparing", "payment_uncertain", "awaiting_deposit", "incomplete_deposit", "processing", "accepting", "accepted", "recovering", "file_ready", "refunded", "failed", "expired", "abandoned"];
7
+ export type BrowserPurchaseState = (typeof purchaseStates)[number];
8
+ export interface BrowserPurchaseRecord extends Versioned {
9
+ fileId: string;
10
+ header: string;
11
+ resourceUrl: string;
12
+ route: BuyRoute;
13
+ maxPrice: string;
14
+ state: BrowserPurchaseState;
15
+ purchase?: PurchaseRecord;
16
+ recovery?: RecoveryRecord;
17
+ recoveryArchives?: RecoveryRecord[];
18
+ paymentStarted?: boolean;
19
+ lease?: {
20
+ owner: string;
21
+ expiresAt: number;
22
+ } | undefined;
23
+ checkpoint?: PollCheckpoint;
24
+ /** Imported receipts require a fresh ownership proof before deposit instructions. */
25
+ needsProof?: boolean;
26
+ /** Set durably before entering a payment submission boundary. */
27
+ submission: 'not_submitted' | 'uncertain' | 'submitted';
28
+ }
29
+ export interface BrowserReceipt {
30
+ kind: 'browser-receipt';
31
+ entry: BrowserPurchaseRecord;
32
+ }
33
+ /** Durable metadata only. Full workflow records live under their own keys. */
34
+ interface BrowserIndexEntry extends Versioned {
35
+ fileId: string;
36
+ route: BuyRoute;
37
+ state: BrowserPurchaseState;
38
+ resourceUrl: string;
39
+ purchaseId?: string;
40
+ createdAt: number;
41
+ updatedAt: number;
42
+ }
43
+ export interface BrowserPurchaseIndex extends Versioned {
44
+ kind: 'browser-purchase-index';
45
+ entries: BrowserIndexEntry[];
46
+ }
47
+ export interface StoredBrowserPurchase extends BrowserPurchaseRecord {
48
+ kind: 'browser-purchase';
49
+ }
50
+ export interface BrowserRecoveryArchiveIndex extends Versioned {
51
+ kind: 'browser-recovery-archives';
52
+ purchaseId: string;
53
+ names: string[];
54
+ }
55
+ export {};
@@ -0,0 +1,16 @@
1
+ export const purchaseStates = [
2
+ 'terms_review',
3
+ 'preparing',
4
+ 'payment_uncertain',
5
+ 'awaiting_deposit',
6
+ 'incomplete_deposit',
7
+ 'processing',
8
+ 'accepting',
9
+ 'accepted',
10
+ 'recovering',
11
+ 'file_ready',
12
+ 'refunded',
13
+ 'failed',
14
+ 'expired',
15
+ 'abandoned',
16
+ ];
@@ -0,0 +1,146 @@
1
+ /** One browser sale, backed by the same hosted workflow as the SDK and command line. */
2
+ import { type AnnonOptions } from '../api/annon.js';
3
+ import type { ShareStep } from '../api/sharing.js';
4
+ import type { UploadAuth, UploadIntent } from '../download/upload.js';
5
+ import { type Spool } from '../envelope/replay.js';
6
+ import { type Store, type Versioned } from '../pay/store.js';
7
+ import type { BuyRoute } from '../routes.js';
8
+ import { type Identity } from '../settle-near/identity.js';
9
+ import type { IdentityWallet } from '../wallet/wallet.js';
10
+ export interface SellerConnection {
11
+ readonly id: string;
12
+ readonly kind: Identity['kind'];
13
+ readonly label: string;
14
+ }
15
+ export interface SellDraft {
16
+ file: Blob & {
17
+ readonly name: string;
18
+ };
19
+ title: string;
20
+ description: string;
21
+ priceUsdc: string;
22
+ listingStorageNear?: string;
23
+ storageBudgetNear?: string;
24
+ }
25
+ export type SellStep = 'backup' | ShareStep;
26
+ export interface SellTerms {
27
+ seller: {
28
+ kind: Identity['kind'];
29
+ label: string;
30
+ };
31
+ sellerIdentity: string;
32
+ filename: string;
33
+ title: string;
34
+ priceUsdc: string;
35
+ shares: {
36
+ label: string;
37
+ bps: number;
38
+ amountUsdc: string;
39
+ }[];
40
+ fee: {
41
+ fixedUsdc: string;
42
+ bps: number;
43
+ amountUsdc: string;
44
+ } | null;
45
+ funding: {
46
+ listingStorageNear: string;
47
+ purchaseStorageNear: string;
48
+ } | null;
49
+ routes: BuyRoute[];
50
+ baseSplit: boolean;
51
+ steps: SellStep[];
52
+ contract: string;
53
+ host: string;
54
+ }
55
+ export interface SellQuote extends SellTerms {
56
+ quoteId: string;
57
+ description: string;
58
+ byteSize: number;
59
+ /** The selected shop's stated lifetime in days, null for no planned expiry, absent when unknown. */
60
+ hostedDays?: number | null;
61
+ }
62
+ export interface SaleView extends SellTerms {
63
+ id: string;
64
+ revision: number;
65
+ done: SellStep[];
66
+ next: SellStep | null;
67
+ pending: boolean;
68
+ needs: ('wallet' | 'file' | 'secret')[];
69
+ registered: boolean;
70
+ /** expiresAt is Unix seconds, or null when the shop plans no hosting expiry. */
71
+ result: {
72
+ fileId: string;
73
+ url: string;
74
+ expiresAt: number | null;
75
+ } | null;
76
+ }
77
+ export interface SellProgress {
78
+ phase: 'encrypting' | 'uploading';
79
+ done: number;
80
+ total: number;
81
+ }
82
+ export interface SaleHandle {
83
+ readonly id: string;
84
+ view(): Promise<SaleView>;
85
+ subscribe(listener: (view: SaleView) => void): () => void;
86
+ exportBackup(): Promise<{
87
+ filename: string;
88
+ text: string;
89
+ secret: string;
90
+ }>;
91
+ confirmBackup(): Promise<void>;
92
+ attachWallet(connection: SellerConnection): Promise<void>;
93
+ attachFile(file: Blob & {
94
+ readonly name: string;
95
+ }): Promise<void>;
96
+ unlock(secret: string): Promise<void>;
97
+ /**
98
+ * A rejected txHash must not be passed again. Call next() later without txHash
99
+ * to reconcile from final contract state.
100
+ */
101
+ next(options?: {
102
+ signal?: AbortSignal;
103
+ /** Untrusted NEAR wallet return candidate, checked against the pending call and attempt. */
104
+ txHash?: string;
105
+ onProgress?: (progress: SellProgress) => void;
106
+ }): Promise<SaleView>;
107
+ discard(): Promise<void>;
108
+ }
109
+ export interface SellerFacade {
110
+ readonly kind: 'sdk';
111
+ readonly live: boolean;
112
+ active(): Promise<SaleHandle | null>;
113
+ quote(connection: SellerConnection, draft: SellDraft): Promise<SellQuote>;
114
+ begin(quoteId: string): Promise<SaleHandle>;
115
+ }
116
+ /** Metadata and an encrypted portable backup only. The separate export secret stays in memory. */
117
+ export interface BrowserSaleIndex extends Versioned {
118
+ fileId: string;
119
+ backup: string | null;
120
+ }
121
+ export interface BrowserSellerOptions extends AnnonOptions {
122
+ host: string;
123
+ /**
124
+ * Display only: the selected shop's stated file lifetime in days from its validated rules,
125
+ * null for no planned expiry. The per-file publication response states the actual expiry.
126
+ */
127
+ fileLifetimeDays?: number | null;
128
+ indexStore: Store<BrowserSaleIndex>;
129
+ /** Resolve a page-owned handle to its canonical wallet without signing. */
130
+ wallet(connection: SellerConnection): {
131
+ wallet: IdentityWallet;
132
+ signUpload?: (intent: UploadIntent) => Promise<UploadAuth>;
133
+ };
134
+ }
135
+ /** Only ciphertext is collected, using the existing bounded streaming encoder. */
136
+ export declare const browserSellerSpool: Spool;
137
+ export declare function browserSellerStorage(options?: {
138
+ name?: string;
139
+ factory?: IDBFactory;
140
+ }): Promise<{
141
+ rawStore: Store<import("../pay/backup.js").EncryptedRecord>;
142
+ backupSecret: Uint8Array<ArrayBufferLike>;
143
+ writeBackup: (name: string, encryptedFile: string) => Promise<void>;
144
+ indexStore: import("../pay/store.js").AtomicStore<BrowserSaleIndex>;
145
+ }>;
146
+ export declare function createBrowserSeller(options: BrowserSellerOptions): SellerFacade;
@@ -0,0 +1,418 @@
1
+ /** One browser sale, backed by the same hosted workflow as the SDK and command line. */
2
+ import { Annon } from '../api/annon.js';
3
+ import { blobSource } from '../envelope/replay.js';
4
+ import { AnnonError, present } from '../errors.js';
5
+ import { exportSell, importSell, newBackupSecret } from '../pay/backup.js';
6
+ import { indexedDbStore } from '../pay/store.js';
7
+ import { bytes, hex, usdc } from '../settle-near/codec.js';
8
+ import { identityJson, identityText, parseIdentity, } from '../settle-near/identity.js';
9
+ import { browserPurchaseStorage } from './purchases.js';
10
+ const indexKey = 'annon/browser/sale';
11
+ function decimal(value, places) {
12
+ if (typeof value !== 'string' ||
13
+ !new RegExp(`^(0|[1-9][0-9]*)(\\.[0-9]{1,${places}})?$`).test(value))
14
+ throw new AnnonError('invalid_arguments');
15
+ const [whole, fraction = ''] = value.split('.');
16
+ const amount = BigInt(present(whole)) * 10n ** BigInt(places) + BigInt(fraction.padEnd(places, '0'));
17
+ if (amount <= 0n || amount >= 2n ** 128n)
18
+ throw new AnnonError('invalid_arguments');
19
+ return amount;
20
+ }
21
+ function near(value) {
22
+ const v = BigInt(value);
23
+ const fraction = (v % 10n ** 24n).toString().padStart(24, '0').replace(/0+$/, '');
24
+ return `${v / 10n ** 24n}${fraction ? `.${fraction}` : ''}`;
25
+ }
26
+ /** Only ciphertext is collected, using the existing bounded streaming encoder. */
27
+ export const browserSellerSpool = async (stream, use) => {
28
+ const blob = await new Response(stream).blob();
29
+ return use(blobSource(blob));
30
+ };
31
+ function immutable(job) {
32
+ const h = present(job.hosting);
33
+ const { expiresAt: _expiresAt, ...intent } = h.intent;
34
+ return JSON.stringify([
35
+ intent,
36
+ job.terms,
37
+ job.baseSplit,
38
+ h.origin,
39
+ h.metadata,
40
+ h.manifest,
41
+ h.listingStorage,
42
+ job.storageBudget ?? null,
43
+ h.fee,
44
+ h.steps,
45
+ ]);
46
+ }
47
+ function keyMaterial(job) {
48
+ return JSON.stringify([job.fileKey, job.sellerSecret]);
49
+ }
50
+ function display(job) {
51
+ const h = present(job.hosting), t = job.terms;
52
+ const price = BigInt(t.near.price);
53
+ const amounts = t.near.recipients.map((r) => (price * BigInt(r.bps)) / 10000n);
54
+ amounts[t.near.remainder] =
55
+ present(amounts[t.near.remainder]) + price - amounts.reduce((a, b) => a + b, 0n);
56
+ const sellerIndex = t.near.recipients.findIndex((r) => identityJson(r.identity) === identityJson(t.seller));
57
+ const sellerAmount = amounts[sellerIndex] ?? 0n;
58
+ const indices = t.near.recipients
59
+ .map((_, i) => i)
60
+ .sort((a, b) => (a === sellerIndex ? -1 : b === sellerIndex ? 1 : a - b));
61
+ return {
62
+ seller: { kind: t.seller.kind, label: identityText(t.seller) },
63
+ sellerIdentity: identityText(t.seller),
64
+ filename: job.name,
65
+ title: h.metadata.title,
66
+ priceUsdc: usdc(price),
67
+ shares: indices.map((i) => ({
68
+ label: i === sellerIndex ? 'You' : 'Protocol fee',
69
+ bps: present(t.near.recipients[i]).bps,
70
+ amountUsdc: usdc(present(amounts[i])),
71
+ })),
72
+ fee: h.fee
73
+ ? { fixedUsdc: usdc(h.fee.fixed), bps: h.fee.bps, amountUsdc: usdc(price - sellerAmount) }
74
+ : null,
75
+ funding: t.seller.kind === 'near'
76
+ ? {
77
+ listingStorageNear: near(present(h.listingStorage)),
78
+ purchaseStorageNear: near(present(job.storageBudget)),
79
+ }
80
+ : null,
81
+ routes: t.base ? ['near', 'base', '1click'] : ['near', '1click'],
82
+ baseSplit: t.base !== null,
83
+ steps: ['backup', ...h.steps],
84
+ contract: t.contract,
85
+ host: h.origin,
86
+ };
87
+ }
88
+ export async function browserSellerStorage(options = {}) {
89
+ const location = { ...options, name: options.name ?? 'annon-selling' };
90
+ const { rawStore, backupSecret, writeBackup } = await browserPurchaseStorage(location);
91
+ return {
92
+ rawStore,
93
+ backupSecret,
94
+ writeBackup,
95
+ indexStore: indexedDbStore(location),
96
+ };
97
+ }
98
+ export function createBrowserSeller(options) {
99
+ const days = options.fileLifetimeDays;
100
+ if (days !== undefined && days !== null && (!Number.isSafeInteger(days) || days < 0))
101
+ throw new AnnonError('invalid_arguments');
102
+ const sdk = new Annon(options);
103
+ const sessions = new Map();
104
+ const quotes = new Map();
105
+ const listeners = new Map();
106
+ const session = (id) => {
107
+ let value = sessions.get(id);
108
+ if (!value) {
109
+ value = {};
110
+ sessions.set(id, value);
111
+ }
112
+ return value;
113
+ };
114
+ const index = async () => {
115
+ try {
116
+ const value = await options.indexStore.load(indexKey);
117
+ if (value &&
118
+ (value.id !== 'browser-sale' ||
119
+ !Number.isSafeInteger(value.revision) ||
120
+ value.revision < 0 ||
121
+ !/^0x[0-9a-f]{64}$/.test(value.fileId) ||
122
+ !(value.backup === null || typeof value.backup === 'string') ||
123
+ Object.keys(value).some((k) => !['id', 'revision', 'fileId', 'backup'].includes(k))))
124
+ throw new Error();
125
+ return value;
126
+ }
127
+ catch (cause) {
128
+ throw new AnnonError('invalid_record', undefined, { cause });
129
+ }
130
+ };
131
+ const read = async (id) => {
132
+ const saved = await index();
133
+ if (!saved || saved.fileId !== id)
134
+ throw new AnnonError('invalid_record');
135
+ const job = await sdk.hosted.load(id);
136
+ if (session(id).approved && session(id).approved !== immutable(job))
137
+ throw new AnnonError('invalid_record');
138
+ if (!job.published && session(id).approvedKeys && session(id).approvedKeys !== keyMaterial(job))
139
+ throw new AnnonError('invalid_record');
140
+ const secret = session(id).secret;
141
+ if (secret) {
142
+ if (!saved.backup)
143
+ throw new AnnonError('backup_secret_required');
144
+ let backup;
145
+ try {
146
+ backup = await importSell(saved.backup, secret);
147
+ }
148
+ catch (cause) {
149
+ throw new AnnonError('invalid_record', undefined, { cause });
150
+ }
151
+ if (immutable(job) !== immutable(backup) ||
152
+ (!job.published && keyMaterial(job) !== keyMaterial(backup)))
153
+ throw new AnnonError('invalid_record');
154
+ }
155
+ // No object loaded after authentication is substituted into this display or approval.
156
+ if ((await sdk.hosted.load(id)).revision !== job.revision ||
157
+ (await index())?.revision !== saved.revision)
158
+ throw new AnnonError('store_conflict');
159
+ return { saved, job };
160
+ };
161
+ const view = async (id) => {
162
+ const { job } = await read(id), h = present(job.hosting), s = session(id);
163
+ const result = h.result;
164
+ const shown = {
165
+ ...display(job),
166
+ id,
167
+ revision: job.revision,
168
+ done: [...(h.backupConfirmed ? ['backup'] : []), ...h.completed],
169
+ next: result ? null : h.backupConfirmed ? (h.steps[h.completed.length] ?? null) : 'backup',
170
+ pending: h.pending !== null,
171
+ needs: result
172
+ ? []
173
+ : [
174
+ ...(!s.connection ? ['wallet'] : []),
175
+ ...(!s.file ? ['file'] : []),
176
+ ...(h.backupConfirmed && !s.secret ? ['secret'] : []),
177
+ ],
178
+ registered: h.completed.includes('register'),
179
+ result: result
180
+ ? { fileId: result.fileId, url: result.url, expiresAt: result.expiresAt }
181
+ : null,
182
+ };
183
+ return structuredClone(shown);
184
+ };
185
+ const emit = async (id) => {
186
+ const current = await view(id);
187
+ for (const listener of listeners.get(id) ?? []) {
188
+ try {
189
+ listener(structuredClone(current));
190
+ }
191
+ catch {
192
+ /* Observers do not control the sale. */
193
+ }
194
+ }
195
+ return current;
196
+ };
197
+ const handle = (id) => ({
198
+ id,
199
+ view: () => view(id),
200
+ subscribe(listener) {
201
+ const set = listeners.get(id) ?? new Set();
202
+ listeners.set(id, set);
203
+ set.add(listener);
204
+ return () => {
205
+ set.delete(listener);
206
+ };
207
+ },
208
+ async exportBackup() {
209
+ const { job, saved } = await read(id);
210
+ if (present(job.hosting).backupConfirmed ||
211
+ present(job.hosting).pending ||
212
+ present(job.hosting).lease)
213
+ throw new AnnonError('invalid_arguments');
214
+ const secret = newBackupSecret();
215
+ const text = await exportSell(job, secret);
216
+ if ((await sdk.hosted.load(id)).revision !== job.revision)
217
+ throw new AnnonError('store_conflict');
218
+ if (!(await options.indexStore.compareAndSet(indexKey, saved, {
219
+ ...saved,
220
+ revision: saved.revision + 1,
221
+ backup: text,
222
+ })))
223
+ throw new AnnonError('store_conflict');
224
+ session(id).secret = secret;
225
+ return { filename: `annon-seller-${id.slice(2)}.json`, text, secret: hex(secret).slice(2) };
226
+ },
227
+ async confirmBackup() {
228
+ const { saved } = await read(id);
229
+ if (!session(id).secret || !saved.backup)
230
+ throw new AnnonError('backup_secret_required');
231
+ await sdk.hosted.confirmBackup(id);
232
+ await emit(id);
233
+ },
234
+ async attachWallet(connection) {
235
+ const { job } = await read(id);
236
+ const resolved = options.wallet(connection);
237
+ if (connection.kind !== job.terms.seller.kind ||
238
+ identityJson(parseIdentity(resolved.wallet.identity)) !== identityJson(job.terms.seller))
239
+ throw new AnnonError('wrong_identity');
240
+ session(id).connection = connection;
241
+ await emit(id);
242
+ },
243
+ async attachFile(file) {
244
+ await read(id);
245
+ await sdk.hosted.verifySource(id, blobSource(file));
246
+ session(id).file = file;
247
+ await emit(id);
248
+ },
249
+ async unlock(value) {
250
+ try {
251
+ if (!/^[0-9a-f]{64}$/.test(value))
252
+ throw new Error();
253
+ const { job, saved } = await read(id);
254
+ if (!saved.backup)
255
+ throw new Error();
256
+ const secret = bytes(`0x${value}`, 32), backup = await importSell(saved.backup, secret);
257
+ if (immutable(job) !== immutable(backup) ||
258
+ (!job.published && keyMaterial(job) !== keyMaterial(backup)) ||
259
+ (await sdk.hosted.load(id)).revision !== job.revision)
260
+ throw new Error();
261
+ session(id).secret = secret;
262
+ }
263
+ catch (cause) {
264
+ throw new AnnonError('invalid_record', undefined, { cause });
265
+ }
266
+ await emit(id);
267
+ },
268
+ async next(run = {}) {
269
+ if (run.signal?.aborted)
270
+ throw new AnnonError('cancelled');
271
+ const { job, saved } = await read(id), s = session(id);
272
+ if (!s.secret || !saved.backup || !present(job.hosting).backupConfirmed)
273
+ throw new AnnonError('backup_secret_required');
274
+ if (present(job.hosting).result)
275
+ return view(id);
276
+ if (!s.connection)
277
+ throw new AnnonError('signer_required');
278
+ if (!s.file)
279
+ throw new AnnonError('file_required');
280
+ const { wallet, signUpload } = options.wallet(s.connection);
281
+ if (identityJson(parseIdentity(wallet.identity)) !== identityJson(job.terms.seller))
282
+ throw new AnnonError('wrong_identity');
283
+ const progress = (phase, done) => {
284
+ try {
285
+ run.onProgress?.({ phase, done, total: present(s.file).size });
286
+ }
287
+ catch {
288
+ /* Observers do not control the sale. */
289
+ }
290
+ };
291
+ progress('encrypting', 0);
292
+ await sdk.hosted.next(id, {
293
+ expectedRevision: job.revision,
294
+ ...(run.txHash ? { txHash: run.txHash } : {}),
295
+ data: blobSource(s.file),
296
+ seller: wallet,
297
+ hosting: {
298
+ origin: options.host,
299
+ ...(signUpload ? { signUpload } : {}),
300
+ spool: browserSellerSpool,
301
+ body: async (source) => {
302
+ progress('encrypting', present(s.file).size);
303
+ progress('uploading', 0);
304
+ return new Response(source.read()).blob();
305
+ },
306
+ },
307
+ });
308
+ progress('uploading', s.file.size);
309
+ const shown = await emit(id);
310
+ if (shown.result)
311
+ delete s.approvedKeys;
312
+ return shown;
313
+ },
314
+ async discard() {
315
+ const saved = await index();
316
+ if (!saved || saved.fileId !== id)
317
+ throw new AnnonError('invalid_record');
318
+ if (await options.rawStore.load(`annon/sell/${id}`)) {
319
+ try {
320
+ await read(id);
321
+ await sdk.hosted.discard(id);
322
+ }
323
+ catch (error) {
324
+ if (!(error instanceof AnnonError) || error.code !== 'invalid_record')
325
+ throw error;
326
+ // Keep unreadable evidence and its journal; only forget the browser index.
327
+ }
328
+ }
329
+ if (!(await options.indexStore.compareAndSet(indexKey, saved, null)))
330
+ throw new AnnonError('store_conflict');
331
+ session(id).secret?.fill(0);
332
+ sessions.delete(id);
333
+ },
334
+ });
335
+ return {
336
+ kind: 'sdk',
337
+ live: true,
338
+ async active() {
339
+ const saved = await index();
340
+ return saved ? handle(saved.fileId) : null;
341
+ },
342
+ async quote(connection, draft) {
343
+ const { wallet, signUpload } = options.wallet(connection);
344
+ if (connection.kind !== wallet.identity.kind)
345
+ throw new AnnonError('wrong_identity');
346
+ const input = {
347
+ data: blobSource(draft.file),
348
+ name: draft.file.name,
349
+ mime: draft.file.type || 'application/octet-stream',
350
+ seller: wallet,
351
+ price: String(decimal(draft.priceUsdc, 6)),
352
+ title: draft.title,
353
+ description: draft.description,
354
+ hosting: { origin: options.host, ...(signUpload ? { signUpload } : {}) },
355
+ ...(wallet.identity.kind === 'near'
356
+ ? {
357
+ listingStorage: decimal(draft.listingStorageNear ?? '0.01', 24),
358
+ storageBudget: decimal(draft.storageBudgetNear ?? '0.05', 24),
359
+ }
360
+ : {}),
361
+ };
362
+ const q = await sdk.hosted.quote(input);
363
+ // This record is private to the engine; display projection uses the checked quote fields.
364
+ const shown = display({
365
+ terms: q.terms,
366
+ name: q.metadata.filename,
367
+ storageBudget: q.storageBudget ?? undefined,
368
+ hosting: {
369
+ origin: options.host,
370
+ metadata: q.metadata,
371
+ fee: q.fee,
372
+ steps: q.steps,
373
+ listingStorage: q.listingStorage,
374
+ },
375
+ });
376
+ quotes.set(q.quoteId, { quote: structuredClone(q), draft: { ...draft }, connection, shown });
377
+ return structuredClone({
378
+ ...shown,
379
+ quoteId: q.quoteId,
380
+ description: q.metadata.description,
381
+ byteSize: draft.file.size,
382
+ ...(options.fileLifetimeDays === undefined ? {} : { hostedDays: options.fileLifetimeDays }),
383
+ });
384
+ },
385
+ async begin(quoteId) {
386
+ const quoted = quotes.get(quoteId);
387
+ if (!quoted)
388
+ throw new AnnonError('invalid_arguments');
389
+ let existing = await index();
390
+ if (existing && !(await options.rawStore.load(`annon/sell/${existing.fileId}`))) {
391
+ if (!(await options.indexStore.compareAndSet(indexKey, existing, null)))
392
+ throw new AnnonError('store_conflict');
393
+ existing = null;
394
+ }
395
+ if (existing && !(await sdk.hosted.load(existing.fileId)).published)
396
+ throw new AnnonError('sell_incomplete');
397
+ const next = {
398
+ id: 'browser-sale',
399
+ revision: (existing?.revision ?? -1) + 1,
400
+ fileId: quoted.quote.fileId,
401
+ backup: null,
402
+ };
403
+ const begun = await sdk.hosted.begin(quoteId);
404
+ if (!(await options.indexStore.compareAndSet(indexKey, existing, next))) {
405
+ await sdk.hosted.discard(next.fileId);
406
+ throw new AnnonError('sell_incomplete');
407
+ }
408
+ sessions.set(next.fileId, {
409
+ approved: immutable(begun),
410
+ approvedKeys: keyMaterial(begun),
411
+ file: quoted.draft.file,
412
+ connection: quoted.connection,
413
+ });
414
+ quotes.delete(quoteId);
415
+ return handle(next.fileId);
416
+ },
417
+ };
418
+ }
@@ -0,0 +1,53 @@
1
+ import { type UploadAuth, type UploadIntent } from '../download/upload.js';
2
+ import { AnnonError } from '../errors.js';
3
+ import { type NearMessage, type NearSignedMessage } from '../near/nep413.js';
4
+ import { type EvmWallet, type IdentityWallet, type NearWallet } from '../wallet/wallet.js';
5
+ export type BrowserWalletErrorCode = 'rejected' | 'wrong_chain' | 'wrong_identity' | 'invalid_signature' | 'missing_method' | 'invalid_response' | 'wallet_error';
6
+ export declare class BrowserWalletError extends AnnonError {
7
+ readonly code: BrowserWalletErrorCode;
8
+ constructor(code: BrowserWalletErrorCode, message: string, options?: ErrorOptions);
9
+ }
10
+ export interface InjectedEvmProvider {
11
+ request(args: {
12
+ method: string;
13
+ params?: unknown;
14
+ }): Promise<unknown>;
15
+ }
16
+ /** Checks Base before signing and after the result. Switching always asks the page's provider. */
17
+ export declare function browserEvmWallet(provider: InjectedEvmProvider, address: string): EvmWallet;
18
+ export type InjectedSolanaKey = string | Uint8Array | {
19
+ toBytes(): Uint8Array;
20
+ };
21
+ export interface InjectedSolanaProvider {
22
+ publicKey?: InjectedSolanaKey | null;
23
+ signMessage?: (message: Uint8Array) => Promise<Uint8Array | {
24
+ signature: Uint8Array;
25
+ publicKey?: InjectedSolanaKey;
26
+ }>;
27
+ }
28
+ export declare function browserSolanaWallet(provider: InjectedSolanaProvider, address: string): IdentityWallet;
29
+ /** Selector v10 transaction shape, also used by the browser login transport. */
30
+ export interface BrowserNearTransaction {
31
+ signerId: string;
32
+ receiverId: string;
33
+ actions: {
34
+ functionCall: {
35
+ methodName: string;
36
+ args: Uint8Array;
37
+ gas: bigint;
38
+ deposit: bigint;
39
+ };
40
+ }[];
41
+ }
42
+ export interface BrowserNearProvider {
43
+ getAccounts?: () => Promise<readonly {
44
+ accountId: string;
45
+ }[]>;
46
+ signMessage?: (payload: NearMessage) => Promise<NearSignedMessage | undefined>;
47
+ signAndSendTransaction?: (transaction: BrowserNearTransaction) => Promise<unknown>;
48
+ }
49
+ export declare function browserNearWallet(provider: BrowserNearProvider, accountId: string): NearWallet & Required<Pick<NearWallet, 'proveControl'>>;
50
+ /** Sign only the hosting payload; no callback URL or page state is authorized. */
51
+ export declare function signNearUpload(provider: BrowserNearProvider, input: UploadIntent, nearRpc: string): Promise<UploadAuth & {
52
+ kind: 'near';
53
+ }>;