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

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 +56 -0
  2. package/CLI.md +378 -0
  3. package/LICENSE +201 -0
  4. package/NOTICE +7 -0
  5. package/README.md +581 -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 +116 -0
  21. package/dist/browser/login.d.ts +1 -0
  22. package/dist/browser/login.js +177 -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 +99 -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 +18 -0
  79. package/dist/node/browser-login.js +212 -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 +147 -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 +393 -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 +135 -3
@@ -0,0 +1,426 @@
1
+ import { isTransient } from '../errors.js';
2
+ import { getFile, getPurchase, rpcReply, view } from '../near/rpc.js';
3
+ import { registerText } from '../near/texts.js';
4
+ // Preserve dependency evaluation order before the split modules enter the relay cycle.
5
+ import '../net/retry.js';
6
+ import '../pay/base.js';
7
+ import '../pay/split.js';
8
+ import { identityJson, verifyAuthorization } from '../settle-near/identity.js';
9
+ import { parsePurchaseMessage } from '../settle-near/purchase.js';
10
+ import { termsHash } from '../settle-near/terms.js';
11
+ import { admissionBalance, checkAdmissionFloor } from './admission.js';
12
+ import './fees.js';
13
+ import { runKeeper } from './keeper-runner.js';
14
+ import { requestHash } from './ledger.js';
15
+ import './reservation.js';
16
+ import './sponsorship.js';
17
+ import { checkRelay } from './relay-checks.js';
18
+ import { durableStatus } from './relay-reconciliation.js';
19
+ import { canonicalRequest, contractArgs } from './relay-request.js';
20
+ import { send, sign } from './relay-transactions.js';
21
+ import { fail, RelayError, } from './relay-types.js';
22
+ import { admitTerms, relayLimits, temporary } from './sponsorship.js';
23
+ export { checkRelay } from './relay-checks.js';
24
+ export { reconcileRelayStorage } from './relay-reconciliation.js';
25
+ export { canonicalRequest, contractArgs } from './relay-request.js';
26
+ export { RelayError } from './relay-types.js';
27
+ const asRelayError = (e) => {
28
+ if (e instanceof RelayError)
29
+ throw e;
30
+ return fail(isTransient(e) ? 'transport' : 'bad_request', e.message);
31
+ };
32
+ /** Gas for every call: `unlock` needs 150 Tgas, MPC's callbacks use the rest; unused gas returns. */
33
+ const GAS = 300000000000000n;
34
+ /**
35
+ * Prepaid storage counted per write (`register`, `unlock`, `reopen`): more than any of them takes
36
+ * (an unlock reserves 0.005 NEAR, a reopen marker under 0.001).
37
+ */
38
+ const STORAGE_PER_WRITE = 10n ** 22n;
39
+ // 0.01 NEAR
40
+ /** An operation is held this long after it is sent, so a repeat doesn't burn gas on a refusal. */
41
+ const HOLD = 15 * 60_000;
42
+ /** How many more tries after the first send: the same bytes again, or after PENDING a lookup. */
43
+ const RESENDS = 2;
44
+ /** The first wait before trying again when NEAR says "later" or "pending"; it grows each try. */
45
+ const RETRY_WAIT = 1_000;
46
+ /** Requests waiting for their turn; past this the relayer answers busy. */
47
+ const MAX_WAITING = 32;
48
+ const accounts = new Map();
49
+ const verifiedBaseTerms = new Set();
50
+ const refusedBaseTerms = new Map();
51
+ const queues = new Map();
52
+ const statusReads = new WeakMap();
53
+ const reconciliationCursor = new WeakMap();
54
+ let waiting = 0;
55
+ let checking = 0;
56
+ const isObject = (v) => typeof v === 'object' && v !== null;
57
+ /** The statuses of a transaction in a block (`NONE`: known, not yet in one). */
58
+ const IN_BLOCK = new Set([
59
+ 'INCLUDED',
60
+ 'EXECUTED_OPTIMISTIC',
61
+ 'INCLUDED_FINAL',
62
+ 'EXECUTED',
63
+ 'FINAL',
64
+ ]);
65
+ const inBlock = (result) => {
66
+ const s = result
67
+ ?.final_execution_status;
68
+ return typeof s === 'string' && IN_BLOCK.has(s);
69
+ };
70
+ /** Ask for FINAL evidence by hash; a lost reply never proves rejection. */
71
+ const lookUp = (url, tx, accountId) => rpcReply(url, 'tx', { tx_hash: tx, sender_account_id: accountId, wait_until: 'FINAL' }).catch(() => null);
72
+ const senderKey = (o) => `${o.nearRpc}|${o.sender.accountId}|${o.sender.key}`;
73
+ /**
74
+ * What makes two requests the same operation. Only a party that could make the request succeed
75
+ * can hold it: wallet signatures are checked against their authoritative identities before
76
+ * anything is held, and an unlock
77
+ * includes the buyer's key, which only the buyer knows before it is sent.
78
+ */
79
+ const identity = (r) => {
80
+ if (r.method === 'unlock')
81
+ return `unlock:${JSON.parse(r.args.purchase).purchase.purchase_id}`;
82
+ if (r.method === 'claim_key')
83
+ return `claim:${r.args.purchase_id}:${r.args.grant ?? 0}`;
84
+ if (r.method === 'reopen')
85
+ return `reopen:${r.args.purchase_id}:${r.args.fresh_key}:${r.args.nonce}`;
86
+ if (r.method === 'register')
87
+ return `register:${r.args.terms.file_id}`;
88
+ if (r.method === 'fund_file_storage')
89
+ return `fund_file_storage:${r.args.file_id}:${r.args.nonce}`;
90
+ return `${r.method}:${r.args.file_id}:${r.args.key ?? ''}`;
91
+ };
92
+ const signedByItsWallet = (r, contract, assets) => {
93
+ if (r.method === 'register' &&
94
+ !verifyAuthorization(r.args.terms.seller, registerText(contract, r.args, assets), r.auth))
95
+ fail('bad_signature');
96
+ };
97
+ const quota = (r, p) => r.method === 'register'
98
+ ? [`register:${identityJson(r.args.terms.seller)}`, p.registrationsPerSeller ?? 20]
99
+ : r.method === 'claim_key'
100
+ ? [`claim:${r.args.purchase_id}:${r.args.grant ?? 0}`, p.claimsPerGrant ?? 3]
101
+ : r.method === 'reopen'
102
+ ? [`reopen:${r.args.purchase_id}`, p.reopensPerWalletFile ?? 10]
103
+ : null;
104
+ const requestBinding = (r) => {
105
+ if (r.method !== 'register')
106
+ return r;
107
+ const { hostingReservation: _reservation, ...binding } = r;
108
+ return binding;
109
+ };
110
+ /** Runs jobs one at a time, in order: checks and nonces never interleave. */
111
+ function enqueue(job, scope = 'relay') {
112
+ const next = (queues.get(scope) ?? Promise.resolve()).then(job, job);
113
+ queues.set(scope, next.catch(() => { }));
114
+ return next;
115
+ }
116
+ /** Read an already admitted operation without charging admission limits or sending anything. */
117
+ export async function relayStatus(input, o) {
118
+ try {
119
+ const r = canonicalRequest(input, {
120
+ baseUsdc: o.usdc,
121
+ ...(o.nearUsdc ? { nearUsdc: o.nearUsdc } : {}),
122
+ });
123
+ return o.ledger ? durableStatus(r, o) : null;
124
+ }
125
+ catch (e) {
126
+ return asRelayError(e);
127
+ }
128
+ }
129
+ /** Every sponsored send reserves durable admission before signing. */
130
+ export async function relay(input, o) {
131
+ const r = canonicalRequest(input, {
132
+ baseUsdc: o.usdc,
133
+ ...(o.nearUsdc ? { nearUsdc: o.nearUsdc } : {}),
134
+ });
135
+ if (!o.ledger)
136
+ return temporary(r.method === 'unlock');
137
+ try {
138
+ return await durableRelay(r, o, o.ledger);
139
+ }
140
+ catch (e) {
141
+ if (r.method === 'unlock' &&
142
+ e instanceof RelayError &&
143
+ (isTransient(e) ||
144
+ ['settlement_pending', 'relayer_unavailable', 'unavailable'].includes(e.code))) {
145
+ await deferPaidUnlock(r, o, e.retryAt ?? Date.now() + 30000);
146
+ if (e.code !== 'settlement_pending')
147
+ return temporary(true, e.retryAt ?? Date.now() + 30000);
148
+ }
149
+ throw e;
150
+ }
151
+ }
152
+ /** Retain bounded paid work while admission or the service is temporarily paused. */
153
+ export async function deferPaidUnlock(input, o, nextAt) {
154
+ if (!o.ledger)
155
+ return;
156
+ const r = canonicalRequest(input, {
157
+ baseUsdc: o.usdc,
158
+ ...(o.nearUsdc ? { nearUsdc: o.nearUsdc } : {}),
159
+ });
160
+ if (r.method !== 'unlock')
161
+ return;
162
+ try {
163
+ await o.ledger.defer(identity(r), r, requestHash(requestBinding(r)), nextAt, o.policy.maxWaiting ?? 32);
164
+ }
165
+ catch (e) {
166
+ o.alert?.('paid_work_full');
167
+ throw e;
168
+ }
169
+ o.alert?.('paid_unlock_delayed');
170
+ }
171
+ async function durableRelay(r, o, ledger) {
172
+ if (!(await ledger.cutover())) {
173
+ o.alert?.('cutover_unsealed');
174
+ return temporary(true);
175
+ }
176
+ const previous = await durableStatus(r, o);
177
+ if (previous === 'pending')
178
+ return fail('in_flight');
179
+ if (previous)
180
+ return previous;
181
+ const deferred = (await ledger.read()).work[identity(r)];
182
+ if (deferred && deferred.nextAt > Date.now()) {
183
+ if (deferred.requestHash !== requestHash(requestBinding(r)))
184
+ return fail('bad_request');
185
+ const e = new RelayError('settlement_pending');
186
+ e.retryAt = deferred.nextAt;
187
+ throw e;
188
+ }
189
+ if (waiting >= (o.policy.maxWaiting ?? MAX_WAITING) ||
190
+ checking >= (o.policy.maxChecking ?? MAX_WAITING))
191
+ return temporary(r.method === 'unlock');
192
+ checking++;
193
+ try {
194
+ await checkRelay(r, o);
195
+ await checkAdmissionFloor(r.method, {
196
+ ...o,
197
+ admissionBalance: o.admissionBalance ?? (() => admissionBalance(o.nearRpc, o.sender.accountId)),
198
+ });
199
+ }
200
+ catch (e) {
201
+ if (r.method === 'unlock' &&
202
+ e instanceof RelayError &&
203
+ (isTransient(e) || ['unavailable', 'relayer_unavailable'].includes(e.code)))
204
+ return temporary(true);
205
+ throw e;
206
+ }
207
+ finally {
208
+ checking--;
209
+ }
210
+ waiting++;
211
+ try {
212
+ return await enqueue(async () => {
213
+ const previous = await durableStatus(r, o);
214
+ if (previous === 'pending')
215
+ return fail('in_flight');
216
+ if (previous)
217
+ return previous;
218
+ if ('deadline' in r.args && r.args.deadline < (o.now ?? Math.floor(Date.now() / 1000)))
219
+ return fail('bad_deadline');
220
+ if (r.method === 'register' &&
221
+ r.hostingReservation &&
222
+ r.hostingReservation.expiresAt <= (o.now ?? Math.floor(Date.now() / 1000)))
223
+ return fail('not_sponsored');
224
+ const purchase = r.method === 'unlock'
225
+ ? parsePurchaseMessage(r.args.purchase, { baseUsdc: o.usdc })
226
+ : r.method === 'claim_key' || r.method === 'reopen'
227
+ ? await getPurchase(o.nearRpc, o.contractId, r.args.purchase_id)
228
+ : null;
229
+ const fileId = r.method === 'register'
230
+ ? r.args.terms.file_id
231
+ : 'file_id' in r.args
232
+ ? r.args.file_id
233
+ : purchase?.file_id;
234
+ if (!fileId)
235
+ return fail('unknown_file');
236
+ const file = r.method === 'register' ? null : await getFile(o.nearRpc, o.contractId, fileId);
237
+ const terms = r.method === 'register' ? r.args.terms : file?.terms;
238
+ if (!terms)
239
+ return fail('unknown_file');
240
+ const hash = termsHash(terms, {
241
+ baseUsdc: o.usdc,
242
+ ...(o.nearUsdc ? { nearUsdc: o.nearUsdc } : {}),
243
+ });
244
+ let decision = await ledger.decision(fileId, hash);
245
+ if (r.method === 'register') {
246
+ const listed = await o.policy.sellerAllowed?.(identityJson(terms.seller));
247
+ decision = await admitTerms(terms, o, listed ? 'listed' : 'pool');
248
+ }
249
+ const listed = decision?.listed ?? false;
250
+ const allocation = r.method === 'register'
251
+ ? listed
252
+ ? (o.policy.listedFileBudget ?? 5n * 10n ** 22n)
253
+ : (o.policy.fileBudget ?? 41n * 10n ** 21n)
254
+ : r.method === 'fund_file_storage'
255
+ ? BigInt(r.args.amount)
256
+ : 0n;
257
+ if (r.method === 'register') {
258
+ // Query the unchanged contract's current admission floor before making an offer usable.
259
+ const protocol = await rpcReply(o.nearRpc, 'EXPERIMENTAL_protocol_config', {
260
+ finality: 'final',
261
+ });
262
+ const byteCost = protocol.result?.runtime_config?.storage_amount_per_byte;
263
+ if (protocol.error ||
264
+ typeof byteCost !== 'string' ||
265
+ !/^[0-9]+$/.test(byteCost) ||
266
+ BigInt(byteCost) <= 0n ||
267
+ allocation < BigInt(byteCost) * 4096n)
268
+ return temporary();
269
+ }
270
+ const write = ['register', 'unlock', 'reopen', 'reopen_seller', 'fund_file_storage'].includes(r.method);
271
+ const storage = write
272
+ ? (r.method === 'fund_file_storage'
273
+ ? 0n
274
+ : (o.policy.storagePerWrite ?? STORAGE_PER_WRITE)) + allocation
275
+ : 0n;
276
+ if (write) {
277
+ const balance = await view(o.nearRpc, o.contractId, 'storage_balance_of', {
278
+ account_id: o.sender.accountId,
279
+ });
280
+ const floor = r.method === 'unlock' ? 0n : (o.policy.prepaidUnlockFloor ?? 10n ** 24n);
281
+ if (typeof balance !== 'string' ||
282
+ !/^[0-9]+$/.test(balance) ||
283
+ BigInt(balance) < floor + storage + (await ledger.pendingStorage()))
284
+ return temporary(r.method === 'unlock');
285
+ }
286
+ if (decision && r.method === 'register')
287
+ decision.allocated = String(allocation);
288
+ const id = identity(r), binding = requestHash(requestBinding(r));
289
+ const q = quota(r, o.policy);
290
+ try {
291
+ const prepared = await ledger.prepare({
292
+ id,
293
+ requestHash: binding,
294
+ method: r.method,
295
+ fileId,
296
+ termsHash: hash,
297
+ seller: identityJson(terms.seller),
298
+ listed,
299
+ ip: o.ip ?? 'unknown',
300
+ storage: String(storage),
301
+ allocation: String(allocation),
302
+ limits: relayLimits(o),
303
+ ...(decision && r.method === 'register' ? { decision } : {}),
304
+ ...(q && r.method !== 'register' ? { quota: { key: q[0], limit: q[1] } } : {}),
305
+ ...(r.method === 'register' && r.hostingReservation
306
+ ? {
307
+ reservation: {
308
+ key: `${r.hostingReservation.hostOrigin}|${r.hostingReservation.uploadId}`,
309
+ binding,
310
+ },
311
+ }
312
+ : {}),
313
+ ...(r.method === 'fund_file_storage'
314
+ ? {
315
+ fundingBalanceBefore: String(file?.purchaseStorageBalance ?? 0n),
316
+ nonce: `${identityJson(terms.seller)}|${r.args.nonce}`,
317
+ nonceDeadline: r.args.deadline,
318
+ topup: {
319
+ lifetime: String(listed
320
+ ? (o.policy.listedTopupPerFile ?? 15n * 10n ** 22n)
321
+ : (o.policy.topupPerFile ?? 82n * 10n ** 21n)),
322
+ fileDay: String(listed
323
+ ? (o.policy.listedTopupPerFileDay ?? 5n * 10n ** 22n)
324
+ : (o.policy.topupPerFileDay ?? 41n * 10n ** 21n)),
325
+ totalDay: String(o.policy.topupPerDay ?? 5n * 10n ** 23n),
326
+ listedReserved: String(o.policy.topupListedReserved ?? 25n * 10n ** 22n),
327
+ },
328
+ }
329
+ : {}),
330
+ });
331
+ if (prepared.capacityReached)
332
+ o.alert?.('ledger_capacity');
333
+ if (!prepared.fresh)
334
+ return (prepared.operation.result ??
335
+ (prepared.operation.tx
336
+ ? { txHash: prepared.operation.tx.txHash, uncertain: true }
337
+ : fail('in_flight')));
338
+ }
339
+ catch (e) {
340
+ if (e instanceof RelayError && e.code === 'relayer_unavailable')
341
+ o.alert?.('ledger_capacity');
342
+ if (r.method === 'unlock' && e instanceof RelayError && e.code === 'limited') {
343
+ const pending = new RelayError('settlement_pending');
344
+ if (e.retryAt !== undefined)
345
+ pending.retryAt = e.retryAt;
346
+ throw pending;
347
+ }
348
+ throw e;
349
+ }
350
+ let signed = false;
351
+ try {
352
+ const args = r.method === 'register'
353
+ ? {
354
+ args: {
355
+ ...r.args,
356
+ ...(allocation > 0n ? { file_budget: String(allocation) } : {}),
357
+ },
358
+ auth: r.auth,
359
+ }
360
+ : contractArgs(r);
361
+ const tx = await sign(o, senderKey(o), r.method, args);
362
+ await ledger.signed(id, { ...tx, signer: o.sender.accountId, args });
363
+ signed = true;
364
+ const result = await send(o, senderKey(o), tx.txHash, tx.signed);
365
+ if (result.rejected) {
366
+ await ledger.reject(id);
367
+ return temporary(r.method === 'unlock');
368
+ }
369
+ await ledger.result(id, result);
370
+ if (r.method === 'register') {
371
+ const final = await getFile(o.nearRpc, o.contractId, fileId).catch(() => null);
372
+ if (final?.termsHash === hash)
373
+ await ledger.registered(fileId, hash);
374
+ }
375
+ if (result.uncertain)
376
+ o.alert?.('uncertain');
377
+ return result;
378
+ }
379
+ catch (e) {
380
+ if (!signed && !(await ledger.operation(id))?.tx)
381
+ await ledger.reject(id);
382
+ throw e;
383
+ }
384
+ }, senderKey(o));
385
+ }
386
+ finally {
387
+ waiting--;
388
+ }
389
+ }
390
+ /** Resume due paid work without creating a second purchase or losing its frozen policy. */
391
+ export async function retryPaidUnlocks(o) {
392
+ if (!o.ledger)
393
+ return;
394
+ for (const [id, work] of Object.entries((await o.ledger.read()).work)) {
395
+ if (work.nextAt > Date.now())
396
+ continue;
397
+ try {
398
+ await relay(work.request, o);
399
+ }
400
+ catch (e) {
401
+ if (e instanceof RelayError &&
402
+ [
403
+ 'taken_down',
404
+ 'sales_stopped',
405
+ 'payment_used',
406
+ 'payment_not_found',
407
+ 'bad_request',
408
+ 'not_sponsored',
409
+ 'unknown_file',
410
+ 'file_mismatch',
411
+ 'bad_signature',
412
+ ].includes(e.code))
413
+ await o.ledger.finishWork(id);
414
+ }
415
+ }
416
+ }
417
+ /** A dedicated account/key has its own nonce queue, independent from relay traffic. */
418
+ export async function keep(o) {
419
+ const key = senderKey(o);
420
+ return enqueue(() => runKeeper(o, {
421
+ sign: (method, args, gas) => sign(o, key, method, args, gas),
422
+ send: (hash, signed) => send(o, key, hash, signed, true),
423
+ }), key);
424
+ }
425
+ // Load-time declarations stay together in their original evaluation order.
426
+ export { accounts, asRelayError, enqueue, fail, GAS, HOLD, IN_BLOCK, identity, inBlock, isObject, lookUp, quota, RESENDS, RETRY_WAIT, reconciliationCursor, refusedBaseTerms, requestBinding, senderKey, signedByItsWallet, statusReads, verifiedBaseTerms, };
@@ -0,0 +1,29 @@
1
+ import { type Identity } from '../settle-near/identity.js';
2
+ import { type Terms } from '../settle-near/terms.js';
3
+ export interface HostingReservation {
4
+ v: 1;
5
+ uploadId: string;
6
+ network: string;
7
+ contract: string;
8
+ fileId: string;
9
+ termsHash: string;
10
+ seller: Identity;
11
+ cipherHash: string;
12
+ byteSize: number;
13
+ plaintextSize: number;
14
+ headerSize: number;
15
+ metadataHash: string;
16
+ hostOrigin: string;
17
+ issuedAt: number;
18
+ expiresAt: number;
19
+ signature: string;
20
+ }
21
+ export declare const reservationText: (r: Omit<HostingReservation, "signature">) => string;
22
+ export declare function parseReservation(input: unknown): HostingReservation;
23
+ export declare function verifyReservation(input: unknown, terms: Terms, o: {
24
+ hostOrigin: string;
25
+ publicKey: string;
26
+ now: number;
27
+ fetch?: typeof fetch;
28
+ maxFileBytes?: number;
29
+ }): Promise<HostingReservation>;
@@ -0,0 +1,132 @@
1
+ /** Verify host attestations against a fixed pinned origin and Ed25519 key. */
2
+ import { ed25519 } from '@noble/curves/ed25519.js';
3
+ import { base64urlnopad } from '@scure/base';
4
+ import { bytes, exactKeys } from '../settle-near/codec.js';
5
+ import { identityJson, identityText } from '../settle-near/identity.js';
6
+ import { termsHash } from '../settle-near/terms.js';
7
+ export const reservationText = (r) => [
8
+ 'Annon: hosting reservation v1',
9
+ `upload: ${r.uploadId}`,
10
+ `network: ${r.network}`,
11
+ `contract: ${r.contract}`,
12
+ `file: ${r.fileId}`,
13
+ `terms: ${r.termsHash}`,
14
+ `seller: ${identityText(r.seller)}`,
15
+ `cipher: ${r.cipherHash}`,
16
+ `bytes: ${r.byteSize}`,
17
+ `plaintext bytes: ${r.plaintextSize}`,
18
+ `header bytes: ${r.headerSize}`,
19
+ `metadata: ${r.metadataHash}`,
20
+ `host: ${r.hostOrigin}`,
21
+ `issued: ${r.issuedAt}`,
22
+ `expires: ${r.expiresAt}`,
23
+ ].join('\n');
24
+ export function parseReservation(input) {
25
+ exactKeys(input, [
26
+ 'v',
27
+ 'uploadId',
28
+ 'network',
29
+ 'contract',
30
+ 'fileId',
31
+ 'termsHash',
32
+ 'seller',
33
+ 'cipherHash',
34
+ 'byteSize',
35
+ 'plaintextSize',
36
+ 'headerSize',
37
+ 'metadataHash',
38
+ 'hostOrigin',
39
+ 'issuedAt',
40
+ 'expiresAt',
41
+ 'signature',
42
+ ]);
43
+ const r = input;
44
+ if (r.v !== 1 ||
45
+ !/^[0-9a-f]{64}$/.test(r.uploadId) ||
46
+ typeof r.signature !== 'string' ||
47
+ !/^[A-Za-z0-9_-]{86}$/.test(r.signature))
48
+ throw new TypeError('Invalid hosting reservation.');
49
+ for (const k of ['fileId', 'termsHash', 'cipherHash', 'metadataHash'])
50
+ bytes(r[k], 32);
51
+ for (const k of ['byteSize', 'plaintextSize', 'headerSize', 'issuedAt', 'expiresAt'])
52
+ if (!Number.isSafeInteger(r[k]) || r[k] < 0)
53
+ throw new TypeError('Invalid hosting reservation.');
54
+ const origin = new URL(r.hostOrigin);
55
+ if (origin.protocol !== 'https:' ||
56
+ origin.origin !== r.hostOrigin ||
57
+ r.byteSize < 1 ||
58
+ r.byteSize > 50086016 ||
59
+ r.plaintextSize > 50000000 ||
60
+ r.headerSize < 1 ||
61
+ r.headerSize > 65536 ||
62
+ r.byteSize <= r.headerSize ||
63
+ r.expiresAt !== r.issuedAt + 600)
64
+ throw new TypeError('Invalid hosting reservation.');
65
+ identityText(r.seller);
66
+ return r;
67
+ }
68
+ export async function verifyReservation(input, terms, o) {
69
+ const r = parseReservation(input);
70
+ if (r.plaintextSize > (o.maxFileBytes ?? 50000000) ||
71
+ r.hostOrigin !== o.hostOrigin ||
72
+ r.network !== terms.network ||
73
+ r.contract !== terms.contract ||
74
+ r.fileId !== terms.file_id ||
75
+ r.cipherHash !== terms.cipher_hash ||
76
+ r.termsHash !==
77
+ termsHash(terms, {
78
+ nearUsdc: terms.near.token,
79
+ ...(terms.base ? { baseUsdc: terms.base.usdc } : {}),
80
+ }) ||
81
+ identityJson(r.seller) !== identityJson(terms.seller) ||
82
+ r.issuedAt > o.now ||
83
+ r.expiresAt <= o.now ||
84
+ !/^[0-9a-f]{64}$/.test(o.publicKey) ||
85
+ !ed25519.verify(base64urlnopad.decode(r.signature), new TextEncoder().encode(reservationText(r)), bytes(`0x${o.publicKey}`, 32), { zip215: false }))
86
+ throw new TypeError('Invalid hosting reservation.');
87
+ const response = await (o.fetch ?? fetch)(`${o.hostOrigin}/v1/upload-reservations/${r.uploadId}`, {
88
+ credentials: 'omit',
89
+ redirect: 'error',
90
+ signal: AbortSignal.timeout(5000),
91
+ });
92
+ if (!response.ok ||
93
+ response.redirected ||
94
+ response.headers.get('content-type')?.split(';')[0]?.trim().toLowerCase() !== 'application/json')
95
+ throw new TypeError('Hosting reservation unavailable.');
96
+ const reader = response.body?.getReader();
97
+ if (!reader)
98
+ throw new TypeError('Hosting reservation unavailable.');
99
+ const parts = [];
100
+ let size = 0;
101
+ try {
102
+ for (;;) {
103
+ const { done, value } = await reader.read();
104
+ if (done)
105
+ break;
106
+ size += value.length;
107
+ if (size > 16384)
108
+ throw new TypeError('Hosting reservation unavailable.');
109
+ parts.push(value);
110
+ }
111
+ }
112
+ finally {
113
+ await reader.cancel();
114
+ }
115
+ const raw = new Uint8Array(size);
116
+ let at = 0;
117
+ for (const part of parts) {
118
+ raw.set(part, at);
119
+ at += part.length;
120
+ }
121
+ const live = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(raw));
122
+ exactKeys(live, ['state', 'fileId', 'termsHash', 'expiresAt', 'reservation']);
123
+ const liveReservation = parseReservation(live.reservation);
124
+ if (live.state !== 'reserved' ||
125
+ live.fileId !== r.fileId ||
126
+ live.termsHash !== r.termsHash ||
127
+ live.expiresAt !== r.expiresAt ||
128
+ reservationText(liveReservation) !== reservationText(r) ||
129
+ liveReservation.signature !== r.signature)
130
+ throw new TypeError('Hosting reservation is not live.');
131
+ return r;
132
+ }
@@ -0,0 +1,42 @@
1
+ /** Durable sponsorship admission. A later policy change cannot revoke a checked listing. */
2
+ import { type FileInfo } from '../near/rpc.js';
3
+ import { type Terms } from '../settle-near/terms.js';
4
+ import type { RelayLimits, SponsorshipDecision } from './ledger.js';
5
+ import { type CheckOptions } from './relay.js';
6
+ export declare const ZERO_FEE: {
7
+ fixed: string;
8
+ bps: number;
9
+ treasuryNear: null;
10
+ treasuryEvm: null;
11
+ };
12
+ export declare const relayLimits: (o: CheckOptions) => RelayLimits;
13
+ export declare const temporary: (paid?: boolean, retryAt?: number) => never;
14
+ export declare function admitTerms(terms: Terms, o: CheckOptions, tier: SponsorshipDecision['tier'], skipFee?: boolean): Promise<SponsorshipDecision>;
15
+ export declare function checkedDecision(file: FileInfo, o: CheckOptions, paid?: boolean): Promise<SponsorshipDecision>;
16
+ export declare function baseEligibility(fileId: string, hash: string, o: CheckOptions, paid?: boolean): Promise<{
17
+ network: string;
18
+ contract: string;
19
+ fileId: string;
20
+ termsHash: string;
21
+ eligible: true;
22
+ tier: "legacy" | "listed" | "pool" | "external";
23
+ policyVersion: string;
24
+ minPrice: string;
25
+ fee: import("./fees.js").ProtocolFee;
26
+ decidedAt: number;
27
+ checkedAt: number;
28
+ reason?: never;
29
+ } | {
30
+ network: string;
31
+ contract: string;
32
+ fileId: string;
33
+ termsHash: string;
34
+ eligible: false;
35
+ reason: "not_sponsored";
36
+ tier: "external";
37
+ policyVersion: string;
38
+ minPrice: string;
39
+ fee: import("./fees.js").ProtocolFee;
40
+ decidedAt: number;
41
+ checkedAt: number;
42
+ }>;