@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,170 @@
1
+ import { AnnonError } from '../errors.js';
2
+ import { type Identity } from '../settle-near/identity.js';
3
+ import { type Terms } from '../settle-near/terms.js';
4
+ /**
5
+ * Reading the Annon contract over NEAR JSON-RPC: views, and the outcome of a transaction sent to
6
+ * it. Two rules, from how NEAR executes a call:
7
+ * - Whether a payment or a listing was recorded is read from contract state (`get_purchase`,
8
+ * `get_file`), never from logs: a receipt that fails keeps its logs while its changes roll back,
9
+ * and a callback can fail after an earlier receipt committed.
10
+ * - Events count only from successful receipts executed by the trusted contract: any contract can
11
+ * log `EVENT_JSON` with standard `annon`.
12
+ * The RPC is trusted to report the chain; answers that don't fit the request are refused.
13
+ */
14
+ export type NearErrorCode =
15
+ /** The RPC couldn't be reached or answered something malformed; safe to retry. */
16
+ 'transport' | 'near_rpc_rate_limited'
17
+ /** The contract refused (a panic); retrying the same call gives the same answer. */
18
+ | 'refused';
19
+ export declare class NearError extends AnnonError {
20
+ readonly code: NearErrorCode;
21
+ constructor(code: NearErrorCode, message?: string);
22
+ }
23
+ /** A typed RPC rate limit; callers retry through isTransient. */
24
+ export declare class NearRpcRateLimited extends NearError {
25
+ constructor(message?: string);
26
+ }
27
+ /** A stalled RPC is given up on after this long, as unreachable. */
28
+ export declare const RPC_TIMEOUT_MS = 20000;
29
+ export interface RpcReply {
30
+ result?: unknown;
31
+ error?: {
32
+ name?: string;
33
+ cause?: {
34
+ name?: string;
35
+ info?: unknown;
36
+ };
37
+ data?: unknown;
38
+ message?: string;
39
+ };
40
+ }
41
+ /**
42
+ * One JSON-RPC call, one fetch: the reply to this request (its id checked), result or error
43
+ * uninterpreted. Throws `transport` when there is no such reply.
44
+ */
45
+ export declare function rpcReply(url: string, method: string, params: unknown): Promise<RpcReply>;
46
+ /** A view call; the contract's panic becomes `refused`. The caller validates the shape. */
47
+ export declare function view(url: string, contractId: string, method: string, args: object, blockId?: string | number): Promise<unknown>;
48
+ /** Validated final contract state; price/payTo describe the optional Base route. */
49
+ export interface FileInfo {
50
+ purchaseStorageBalance: bigint;
51
+ terms: Terms;
52
+ termsHash: string;
53
+ seller: Identity;
54
+ price: bigint;
55
+ payTo: string;
56
+ cipherHash: string;
57
+ moderator: string | null;
58
+ headerHash: string | null;
59
+ salesStoppedAt: number | null;
60
+ takenDown: string | null;
61
+ }
62
+ export declare function getFile(url: string, contractId: string, fileId: string, blockId?: string | number): Promise<FileInfo | null>;
63
+ export interface PurchaseInfo {
64
+ purchase_id: string;
65
+ file_id: string;
66
+ terms_hash: string;
67
+ recovery: Identity;
68
+ buyer_key: string;
69
+ route: 'near-usdc' | 'base-usdc';
70
+ delivery: number | null;
71
+ accepted_at: number;
72
+ grants: number;
73
+ }
74
+ export interface GrantInfo {
75
+ purchase_id: string;
76
+ grant: number;
77
+ buyer_key: string;
78
+ }
79
+ export declare function getPurchase(url: string, contract: string, id: string): Promise<PurchaseInfo | null>;
80
+ export declare function getGrant(url: string, contract: string, id: string, grant?: number): Promise<GrantInfo | null>;
81
+ export declare function purchasesOf(url: string, contract: string, recovery: Identity, from?: number, limit?: number): Promise<string[]>;
82
+ /** The contract's `get_config`: the token it accepts on Base and the MPC it asks. */
83
+ export interface Config {
84
+ mpc: string;
85
+ ckdDomain: number;
86
+ foreignTxDomain: number;
87
+ usdc: string;
88
+ network: string;
89
+ nearUsdc: string;
90
+ intents: string;
91
+ }
92
+ export declare function getConfig(url: string, contractId: string): Promise<Config>;
93
+ /** An NEP-297 event the trusted contract logged in a receipt that succeeded. */
94
+ export interface AnnonEvent {
95
+ event: string;
96
+ data: Record<string, unknown>;
97
+ }
98
+ export type Outcome =
99
+ /** Not finished yet: ask again. */
100
+ {
101
+ state: 'pending';
102
+ method: string;
103
+ events: AnnonEvent[];
104
+ }
105
+ /** CKD answered: `answer` holds `big_y`/`big_c` for `keys/near-ckd`. */
106
+ | {
107
+ state: 'released';
108
+ method: string;
109
+ answer: unknown;
110
+ events: AnnonEvent[];
111
+ }
112
+ /**
113
+ * `register`, `unlock` or `reopen` ended without a key. Whether the listing or the payment was
114
+ * recorded is a question for the contract (`get_file`, `get_purchase`): if it was, `reopen`
115
+ * gets the key; if not, the call can be sent again.
116
+ */
117
+ | {
118
+ state: 'not_released';
119
+ method: string;
120
+ reason: string;
121
+ events: AnnonEvent[];
122
+ }
123
+ /** Any other call: it succeeded, with its return value. */
124
+ | {
125
+ state: 'done';
126
+ method: string;
127
+ value: unknown;
128
+ events: AnnonEvent[];
129
+ }
130
+ /** Any other call: the contract refused it. */
131
+ | {
132
+ state: 'refused';
133
+ method: string;
134
+ reason: string;
135
+ events: AnnonEvent[];
136
+ };
137
+ /** What a caller asked a relayer to send: the outcome must be of exactly this call. */
138
+ export interface ExpectedCall {
139
+ txHash: string;
140
+ signer: string;
141
+ method?: string;
142
+ /** The call's JSON arguments, compared as values (key order aside). */
143
+ args?: unknown;
144
+ /** The exact attached deposit in yoctoNEAR, when the caller requires it. */
145
+ deposit?: string;
146
+ }
147
+ /**
148
+ * Reads an `EXPERIMENTAL_tx_status` / `tx` result. With `expect`, the answer must be about that
149
+ * transaction and, when given, that method with those arguments and deposit. Reads exactly
150
+ * one function call; register may include a positive
151
+ * caller-funded file_budget when the expected arguments omit it.
152
+ */
153
+ export declare function readOutcome(result: unknown, contractId: string, expect?: ExpectedCall): Outcome;
154
+ /** Fetches a transaction's outcome; `wait` as NEAR's `wait_until`. */
155
+ export declare function txOutcome(url: string, txHash: string, signer: string, contractId: string, wait?: 'EXECUTED_OPTIMISTIC' | 'FINAL', call?: {
156
+ method: string;
157
+ args: unknown;
158
+ deposit?: string;
159
+ /** Untrusted candidates must have been included at or after this Unix time in seconds. */
160
+ notBefore?: number;
161
+ }): Promise<Outcome>;
162
+ /** The current access key at finality, never an optimistic or wallet-supplied permission. */
163
+ export declare function isFullAccessKey(url: string, accountId: string, publicKey: string): Promise<boolean>;
164
+ export interface FileStorageBalance {
165
+ available: bigint;
166
+ admissionRequirement: bigint;
167
+ }
168
+ export declare function fileStorageBalance(url: string, contractId: string, fileId: string): Promise<FileStorageBalance>;
169
+ /** Contract storage refusal diagnostics, shared by transaction transports. */
170
+ export declare function isStorageRequired(reason: unknown): boolean;
@@ -0,0 +1,423 @@
1
+ import { AnnonError } from '../errors.js';
2
+ import { parseIdentity } from '../settle-near/identity.js';
3
+ import { buyerKey } from '../settle-near/purchase.js';
4
+ import { termsHash } from '../settle-near/terms.js';
5
+ export class NearError extends AnnonError {
6
+ code;
7
+ constructor(code, message = code) {
8
+ super(code, message);
9
+ this.code = code;
10
+ this.name = 'NearError';
11
+ }
12
+ }
13
+ /** A typed RPC rate limit; callers retry through isTransient. */
14
+ export class NearRpcRateLimited extends NearError {
15
+ constructor(message = 'NEAR RPC rate limited (429)') {
16
+ super('near_rpc_rate_limited', message);
17
+ this.name = 'NearRpcRateLimited';
18
+ }
19
+ }
20
+ const fail = (code, message) => {
21
+ throw new NearError(code, message);
22
+ };
23
+ const malformed = (what) => fail('transport', `malformed RPC answer: ${what}`);
24
+ const toBase64 = (s) => btoa(String.fromCharCode(...new TextEncoder().encode(s)));
25
+ const fromBase64 = (s) => new TextDecoder().decode(Uint8Array.from(atob(s), (c) => c.charCodeAt(0)));
26
+ /** A stalled RPC is given up on after this long, as unreachable. */
27
+ export const RPC_TIMEOUT_MS = 20_000;
28
+ let nextId = 0;
29
+ /**
30
+ * One JSON-RPC call, one fetch: the reply to this request (its id checked), result or error
31
+ * uninterpreted. Throws `transport` when there is no such reply.
32
+ */
33
+ export async function rpcReply(url, method, params) {
34
+ const id = `annon-${++nextId}`;
35
+ let body;
36
+ let response;
37
+ const abort = new AbortController();
38
+ const timer = setTimeout(() => abort.abort(new Error('timed out')), RPC_TIMEOUT_MS);
39
+ try {
40
+ response = await fetch(url, {
41
+ credentials: 'omit',
42
+ method: 'POST',
43
+ headers: { 'content-type': 'application/json' },
44
+ body: JSON.stringify({ jsonrpc: '2.0', id, method, params }),
45
+ signal: abort.signal,
46
+ });
47
+ const httpFailure = () => {
48
+ if (response.status === 429)
49
+ throw new NearRpcRateLimited('NEAR RPC HTTP 429: rate limited (429)');
50
+ return fail('transport', `NEAR RPC HTTP ${response.status}`);
51
+ };
52
+ try {
53
+ body = await response.json();
54
+ }
55
+ catch (error) {
56
+ if (!response.ok)
57
+ return httpFailure();
58
+ throw error;
59
+ }
60
+ // NEAR also returns structured JSON-RPC errors on HTTP 400/408/500. Preserve them for
61
+ // callers that distinguish pending work from rejection; use HTTP only without an RPC body.
62
+ // A 429 is rate limiting whatever its body says, including replies without an id.
63
+ if (response.status === 429)
64
+ return httpFailure();
65
+ if (!response.ok &&
66
+ (!body ||
67
+ typeof body !== 'object' ||
68
+ Array.isArray(body) ||
69
+ (body.jsonrpc !== '2.0' && body.id !== id) ||
70
+ (!('result' in body) && !('error' in body))))
71
+ return httpFailure();
72
+ }
73
+ catch (e) {
74
+ if (e instanceof NearError)
75
+ throw e;
76
+ return fail('transport', `NEAR RPC unreachable: ${e.message}`);
77
+ }
78
+ finally {
79
+ clearTimeout(timer);
80
+ }
81
+ // Rate-limit answers carry no request id; name them instead of calling them someone else's reply.
82
+ if (body?.error?.code === -429)
83
+ throw new NearRpcRateLimited();
84
+ if (body?.id !== id)
85
+ return malformed('a reply to another request');
86
+ return body;
87
+ }
88
+ async function rpc(url, method, params) {
89
+ const body = (await rpcReply(url, method, params));
90
+ if (body.error) {
91
+ const msg = JSON.stringify(body.error.cause?.info ?? body.error.data ?? body.error.message);
92
+ // A view that panics comes back as an RPC error on some providers.
93
+ if (['CONTRACT_EXECUTION_ERROR', 'UNKNOWN_ACCESS_KEY', 'UNKNOWN_ACCOUNT'].includes(body.error.cause?.name ?? ''))
94
+ fail('refused', msg);
95
+ fail('transport', `NEAR RPC error: ${msg}`);
96
+ }
97
+ if (typeof body?.result !== 'object' || body.result === null)
98
+ return malformed('no result');
99
+ return body.result;
100
+ }
101
+ /** A view call; the contract's panic becomes `refused`. The caller validates the shape. */
102
+ export async function view(url, contractId, method, args, blockId) {
103
+ const r = await rpc(url, 'query', {
104
+ request_type: 'call_function',
105
+ ...(blockId === undefined ? { finality: 'final' } : { block_id: blockId }),
106
+ account_id: contractId,
107
+ method_name: method,
108
+ args_base64: toBase64(JSON.stringify(args)),
109
+ });
110
+ if (typeof r.error === 'string')
111
+ fail('refused', r.error);
112
+ const bytes = r.result;
113
+ if (!Array.isArray(bytes) || !bytes.every((b) => Number.isInteger(b) && b >= 0 && b <= 255)) {
114
+ return malformed('view bytes');
115
+ }
116
+ try {
117
+ return JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(Uint8Array.from(bytes)));
118
+ }
119
+ catch {
120
+ return malformed('view bytes are not JSON');
121
+ }
122
+ }
123
+ const isHex = (v, bytes) => typeof v === 'string' && new RegExp(`^0x[0-9a-f]{${2 * bytes}}$`).test(v);
124
+ /** NEAR's account id rule. */
125
+ const isAccount = (v) => typeof v === 'string' &&
126
+ /^(?=.{2,64}$)(([a-z\d]+[-_])*[a-z\d]+\.)*([a-z\d]+[-_])*[a-z\d]+$/.test(v);
127
+ const TAKEDOWN = ['illegal_content', 'court_order', 'rights_holder_notice'];
128
+ export async function getFile(url, contractId, fileId, blockId) {
129
+ const f = (await view(url, contractId, 'get_file', { file_id: fileId }, blockId));
130
+ if (f === null)
131
+ return null;
132
+ try {
133
+ const commitment = termsHash(f.terms, {
134
+ nearUsdc: f.terms.near.token,
135
+ ...(f.terms.base ? { baseUsdc: f.terms.base.usdc } : {}),
136
+ });
137
+ if (f.terms_hash !== commitment)
138
+ throw new Error();
139
+ if ((f.purchase_storage_balance !== undefined &&
140
+ (typeof f.purchase_storage_balance !== 'string' ||
141
+ !/^(0|[1-9][0-9]*)$/.test(f.purchase_storage_balance) ||
142
+ BigInt(f.purchase_storage_balance) >= 2n ** 128n)) ||
143
+ f.terms.contract !== contractId ||
144
+ f.terms.file_id !== fileId ||
145
+ !(f.header_hash === null || isHex(f.header_hash, 32)) ||
146
+ !(f.sales_stopped_at === null ||
147
+ (Number.isSafeInteger(f.sales_stopped_at) && f.sales_stopped_at > 0)) ||
148
+ !(f.taken_down === null ||
149
+ (typeof f.taken_down === 'string' &&
150
+ TAKEDOWN.includes(f.taken_down))))
151
+ throw new Error();
152
+ }
153
+ catch {
154
+ return malformed('get_file');
155
+ }
156
+ const t = f.terms;
157
+ return {
158
+ purchaseStorageBalance: BigInt(f.purchase_storage_balance ?? '0'),
159
+ terms: t,
160
+ termsHash: f.terms_hash,
161
+ seller: parseIdentity(t.seller),
162
+ price: BigInt(t.base?.price ?? t.near.price),
163
+ payTo: t.base?.pay_to ?? '',
164
+ cipherHash: t.cipher_hash,
165
+ moderator: t.moderator,
166
+ headerHash: f.header_hash,
167
+ salesStoppedAt: f.sales_stopped_at,
168
+ takenDown: f.taken_down,
169
+ };
170
+ }
171
+ export async function getPurchase(url, contract, id) {
172
+ const p = (await view(url, contract, 'get_purchase', { purchase_id: id }));
173
+ if (p === null)
174
+ return null;
175
+ try {
176
+ if (p.purchase_id !== id ||
177
+ !isHex(p.file_id, 32) ||
178
+ !isHex(p.terms_hash, 32) ||
179
+ !Number.isSafeInteger(p.grants) ||
180
+ p.grants < 1 ||
181
+ p.grants > 0xffffffff ||
182
+ !Number.isSafeInteger(p.accepted_at) ||
183
+ p.accepted_at < 0 ||
184
+ !['near-usdc', 'base-usdc'].includes(p.route) ||
185
+ (p.route === 'base-usdc'
186
+ ? p.delivery !== null
187
+ : !Number.isSafeInteger(p.delivery) || p.delivery < 0))
188
+ throw new Error();
189
+ buyerKey(p.buyer_key);
190
+ parseIdentity(p.recovery);
191
+ }
192
+ catch {
193
+ return malformed('get_purchase');
194
+ }
195
+ return p;
196
+ }
197
+ export async function getGrant(url, contract, id, grant = 0) {
198
+ if (!Number.isInteger(grant) || grant < 0 || grant > 0xffffffff)
199
+ throw new TypeError('invalid grant');
200
+ const g = (await view(url, contract, 'get_grant', {
201
+ purchase_id: id,
202
+ grant,
203
+ }));
204
+ if (g === null)
205
+ return null;
206
+ try {
207
+ if (g.purchase_id !== id || g.grant !== grant)
208
+ throw new Error();
209
+ buyerKey(g.buyer_key);
210
+ }
211
+ catch {
212
+ return malformed('get_grant');
213
+ }
214
+ return g;
215
+ }
216
+ export async function purchasesOf(url, contract, recovery, from = 0, limit = 50) {
217
+ if (!Number.isSafeInteger(from) ||
218
+ from < 0 ||
219
+ from > 0xffffffff ||
220
+ !Number.isSafeInteger(limit) ||
221
+ limit < 1 ||
222
+ limit > 100)
223
+ throw new TypeError('invalid page');
224
+ const ids = await view(url, contract, 'purchases_of', {
225
+ recovery: parseIdentity(recovery),
226
+ from,
227
+ limit,
228
+ });
229
+ if (!Array.isArray(ids) || ids.length > limit || !ids.every((id) => isHex(id, 32)))
230
+ return malformed('purchases_of');
231
+ return ids;
232
+ }
233
+ export async function getConfig(url, contractId) {
234
+ const c = (await view(url, contractId, 'get_config', {}));
235
+ if (!isAccount(c?.mpc) ||
236
+ !Number.isSafeInteger(c?.ckd_domain) ||
237
+ !Number.isSafeInteger(c?.foreign_tx_domain) ||
238
+ !isHex(c?.usdc, 20) ||
239
+ !['near:mainnet', 'near:testnet', 'near:sandbox'].includes(c?.network) ||
240
+ !isAccount(c?.near_usdc) ||
241
+ !isAccount(c?.intents) ||
242
+ c?.ckd_domain < 0 ||
243
+ c?.foreign_tx_domain < 0) {
244
+ return malformed('get_config');
245
+ }
246
+ return {
247
+ mpc: c?.mpc,
248
+ ckdDomain: c?.ckd_domain,
249
+ foreignTxDomain: c?.foreign_tx_domain,
250
+ usdc: c?.usdc,
251
+ network: c?.network,
252
+ nearUsdc: c?.near_usdc,
253
+ intents: c?.intents,
254
+ };
255
+ }
256
+ /** Calls whose result is the CKD answer. */
257
+ const RELEASES = new Set(['register', 'unlock', 'reopen', 'reopen_seller', 'claim_key']);
258
+ const succeeded = (s) => typeof s === 'object' && s !== null && ('SuccessValue' in s || 'SuccessReceiptId' in s);
259
+ const canonical = (v) => v === null || typeof v !== 'object'
260
+ ? JSON.stringify(v)
261
+ : Array.isArray(v)
262
+ ? `[${v.map(canonical).join(',')}]`
263
+ : `{${Object.keys(v)
264
+ .sort()
265
+ .map((k) => `${JSON.stringify(k)}:${canonical(v[k])}`)
266
+ .join(',')}}`;
267
+ /**
268
+ * Reads an `EXPERIMENTAL_tx_status` / `tx` result. With `expect`, the answer must be about that
269
+ * transaction and, when given, that method with those arguments and deposit. Reads exactly
270
+ * one function call; register may include a positive
271
+ * caller-funded file_budget when the expected arguments omit it.
272
+ */
273
+ export function readOutcome(result, contractId, expect) {
274
+ const r = result;
275
+ const t = r?.transaction;
276
+ if (expect && (t?.hash !== expect.txHash || t?.signer_id !== expect.signer)) {
277
+ return malformed('an outcome for another transaction');
278
+ }
279
+ if (t?.receiver_id !== contractId) {
280
+ fail('transport', 'the transaction was not sent to the Annon contract');
281
+ }
282
+ const actions = Array.isArray(t?.actions) ? t.actions : [];
283
+ const method = actions[0]?.FunctionCall?.method_name;
284
+ if (actions.length !== 1 || typeof method !== 'string')
285
+ return malformed('not a single function call');
286
+ if (expect?.method !== undefined && method !== expect.method) {
287
+ return malformed('an outcome of another method');
288
+ }
289
+ if (expect?.deposit !== undefined && actions[0]?.FunctionCall?.deposit !== expect.deposit)
290
+ return malformed('an outcome with another deposit');
291
+ if (expect?.args !== undefined) {
292
+ let sent;
293
+ try {
294
+ sent = JSON.parse(fromBase64(String(actions[0]?.FunctionCall?.args)));
295
+ }
296
+ catch {
297
+ return malformed('call arguments are not JSON');
298
+ }
299
+ // Compared as JSON would send them: an `undefined` field is no field.
300
+ const asked = JSON.parse(JSON.stringify(expect.args) ?? 'null');
301
+ if (method === 'register' &&
302
+ sent &&
303
+ typeof sent === 'object' &&
304
+ 'args' in sent &&
305
+ !Object.hasOwn(asked.args ?? {}, 'file_budget')) {
306
+ const a = sent.args;
307
+ if (Object.hasOwn(a, 'file_budget')) {
308
+ if (typeof a.file_budget !== 'string' ||
309
+ !/^[1-9][0-9]*$/.test(a.file_budget) ||
310
+ BigInt(a.file_budget) >= 2n ** 128n)
311
+ return malformed('invalid registration budget');
312
+ delete a.file_budget;
313
+ }
314
+ }
315
+ if (canonical(sent) !== canonical(asked))
316
+ return malformed('an outcome of another call');
317
+ }
318
+ const events = [];
319
+ for (const o of Array.isArray(r.receipts_outcome) ? r.receipts_outcome : []) {
320
+ if (o?.outcome?.executor_id !== contractId || !succeeded(o.outcome.status))
321
+ continue;
322
+ for (const log of Array.isArray(o.outcome.logs) ? o.outcome.logs : []) {
323
+ if (typeof log !== 'string' || !log.startsWith('EVENT_JSON:'))
324
+ continue;
325
+ try {
326
+ const e = JSON.parse(log.slice('EVENT_JSON:'.length));
327
+ if (e.standard === 'annon' && e.version === '2.0.0' && typeof e.event === 'string') {
328
+ events.push({ event: e.event, data: e.data?.[0] ?? {} });
329
+ }
330
+ }
331
+ catch { }
332
+ }
333
+ }
334
+ const status = r.status;
335
+ if (status === 'NotStarted' || status === 'Started')
336
+ return { state: 'pending', method, events };
337
+ if (typeof status !== 'object' || status === null)
338
+ return malformed('status');
339
+ const releases = RELEASES.has(method);
340
+ if ('Failure' in status) {
341
+ const reason = panicMessage(status.Failure);
342
+ return releases
343
+ ? { state: 'not_released', method, reason, events }
344
+ : { state: 'refused', method, reason, events };
345
+ }
346
+ if (typeof status.SuccessValue !== 'string')
347
+ return malformed('status');
348
+ let value = null;
349
+ if (status.SuccessValue !== '') {
350
+ try {
351
+ value = JSON.parse(fromBase64(status.SuccessValue));
352
+ }
353
+ catch {
354
+ return malformed('return value is not JSON');
355
+ }
356
+ }
357
+ if (!releases)
358
+ return { state: 'done', method, value, events };
359
+ const v = value;
360
+ if (v && typeof v.big_y === 'string' && typeof v.big_c === 'string') {
361
+ return { state: 'released', method, answer: value, events };
362
+ }
363
+ const reason = v && typeof v.error === 'string' ? v.error : 'no key in the result';
364
+ return { state: 'not_released', method, reason, events };
365
+ }
366
+ function panicMessage(failure) {
367
+ const kind = failure?.ActionError?.kind?.FunctionCallError?.ExecutionError;
368
+ if (typeof kind === 'string')
369
+ return kind.replace(/^Smart contract panicked: /, '');
370
+ return JSON.stringify(failure);
371
+ }
372
+ /** Fetches a transaction's outcome; `wait` as NEAR's `wait_until`. */
373
+ export async function txOutcome(url, txHash, signer, contractId, wait = 'FINAL', call) {
374
+ const r = await rpc(url, 'EXPERIMENTAL_tx_status', {
375
+ tx_hash: txHash,
376
+ sender_account_id: signer,
377
+ wait_until: wait,
378
+ });
379
+ const outcome = readOutcome(r, contractId, { txHash, signer, ...call });
380
+ if (call?.notBefore !== undefined) {
381
+ const blockHash = r.transaction_outcome?.block_hash;
382
+ if (!Number.isSafeInteger(call.notBefore) ||
383
+ call.notBefore < 0 ||
384
+ typeof blockHash !== 'string' ||
385
+ !blockHash)
386
+ return malformed('missing transaction inclusion checkpoint');
387
+ const block = await rpc(url, 'block', { block_id: blockHash });
388
+ const timestamp = block.header
389
+ ?.timestamp_nanosec;
390
+ if (typeof timestamp !== 'string' || !/^(0|[1-9][0-9]*)$/.test(timestamp))
391
+ return malformed('transaction inclusion time');
392
+ if (BigInt(timestamp) < BigInt(call.notBefore) * 1000000000n)
393
+ return malformed('a transaction before the current attempt');
394
+ }
395
+ return outcome;
396
+ }
397
+ /** The current access key at finality, never an optimistic or wallet-supplied permission. */
398
+ export async function isFullAccessKey(url, accountId, publicKey) {
399
+ const key = await rpc(url, 'query', {
400
+ request_type: 'view_access_key',
401
+ finality: 'final',
402
+ account_id: accountId,
403
+ public_key: publicKey,
404
+ });
405
+ return key.permission === 'FullAccess';
406
+ }
407
+ export async function fileStorageBalance(url, contractId, fileId) {
408
+ const value = (await view(url, contractId, 'file_storage_balance', { file_id: fileId }));
409
+ const amount = (v) => typeof v === 'string' && /^(0|[1-9][0-9]*)$/.test(v) && BigInt(v) < 2n ** 128n;
410
+ if (!value ||
411
+ !amount(value.available) ||
412
+ !amount(value.admission_requirement) ||
413
+ BigInt(value.admission_requirement) === 0n)
414
+ return malformed('file_storage_balance');
415
+ return {
416
+ available: BigInt(value.available),
417
+ admissionRequirement: BigInt(value.admission_requirement),
418
+ };
419
+ }
420
+ /** Contract storage refusal diagnostics, shared by transaction transports. */
421
+ export function isStorageRequired(reason) {
422
+ return typeof reason === 'string' && /prepaid|storage balance|storage[_ ]deposit/i.test(reason);
423
+ }
@@ -0,0 +1,39 @@
1
+ export { usdc, utc } from '../settle-near/codec.js';
2
+ /** Authoritative contract arguments and texts (near-settlement §§3,9). */
3
+ import { type Identity } from '../settle-near/identity.js';
4
+ import { type ReopenV2 } from '../settle-near/purchase.js';
5
+ import { type SettlementAssets, type Terms } from '../settle-near/terms.js';
6
+ export interface RegisterArgs {
7
+ /** Optional caller-paid prepaid allocation; not part of seller authorization. Relay clients omit it. */
8
+ file_budget?: string;
9
+ terms: Terms;
10
+ seller_key: string;
11
+ deadline: number;
12
+ }
13
+ export interface ReopenArgs {
14
+ purchase_id: string;
15
+ fresh_key: string;
16
+ nonce: string;
17
+ deadline: number;
18
+ }
19
+ export interface SellerArgs {
20
+ file_id: string;
21
+ key: string | null;
22
+ deadline: number;
23
+ }
24
+ export declare function registerText(contractId: string, a: RegisterArgs, assets?: SettlementAssets): string;
25
+ export declare const reopenText: (a: ReopenV2) => string;
26
+ export declare function reopenSellerText(contract: string, a: SellerArgs, seller: Identity): string;
27
+ export declare function stopSalesText(contract: string, a: SellerArgs, seller: Identity): string;
28
+ export interface FileFundingArgs {
29
+ file_id: string;
30
+ amount: string;
31
+ nonce: string;
32
+ deadline: number;
33
+ }
34
+ /** Seller authorization for a sponsored top-up; not forwarded to the contract. */
35
+ export declare function fileFundingText(a: FileFundingArgs & {
36
+ network: string;
37
+ contract: string;
38
+ seller: Identity;
39
+ }): string;
@@ -0,0 +1,60 @@
1
+ import { bytes, uint, utc } from '../settle-near/codec.js';
2
+ export { usdc, utc } from '../settle-near/codec.js';
3
+ /** Authoritative contract arguments and texts (near-settlement §§3,9). */
4
+ import { identityText } from '../settle-near/identity.js';
5
+ import { buyerKey, reopenTextV2 } from '../settle-near/purchase.js';
6
+ import { registerTextV2 } from '../settle-near/terms.js';
7
+ /** The contract's `hex0x(bytes::<N>(s))`: optional lowercase `0x`, any case, printed lowercase. */
8
+ function hex0x(s, bytes, name) {
9
+ const h = s.startsWith('0x') ? s.slice(2) : s;
10
+ if (!new RegExp(`^[0-9a-fA-F]{${2 * bytes}}$`).test(h)) {
11
+ throw new TypeError(`${name} must be ${bytes} bytes of hex`);
12
+ }
13
+ return `0x${h.toLowerCase()}`;
14
+ }
15
+ export function registerText(contractId, a, assets) {
16
+ if (contractId !== a.terms.contract)
17
+ throw new TypeError('terms contract mismatch');
18
+ buyerKey(a.seller_key);
19
+ return registerTextV2(a.terms, a.seller_key, a.deadline, assets);
20
+ }
21
+ export const reopenText = (a) => reopenTextV2(a);
22
+ export function reopenSellerText(contract, a, seller) {
23
+ buyerKey(a.key ?? '');
24
+ return [
25
+ 'Annon: open a file again',
26
+ 'Signing this lets whoever holds the one-time key below open the file.',
27
+ `contract: ${contract}`,
28
+ `file: ${hex0x(a.file_id, 32, 'file_id')}`,
29
+ `seller: ${identityText(seller)}`,
30
+ `one-time key: ${a.key}`,
31
+ `valid until: ${utc(a.deadline)}`,
32
+ ].join('\n');
33
+ }
34
+ export function stopSalesText(contract, a, seller) {
35
+ return [
36
+ 'Annon: stop selling a file',
37
+ "Signing this is final: the file can't be sold again. Past buyers keep access.",
38
+ `contract: ${contract}`,
39
+ `file: ${hex0x(a.file_id, 32, 'file_id')}`,
40
+ `seller: ${identityText(seller)}`,
41
+ `valid until: ${utc(a.deadline)}`,
42
+ ].join('\n');
43
+ }
44
+ /** Seller authorization for a sponsored top-up; not forwarded to the contract. */
45
+ export function fileFundingText(a) {
46
+ bytes(a.file_id, 32);
47
+ bytes(a.nonce, 32);
48
+ if (uint(a.amount, 128) <= 0n || !Number.isSafeInteger(a.deadline) || a.deadline < 0)
49
+ throw new TypeError('Invalid file funding authorization.');
50
+ return [
51
+ 'Annon: request sponsored file storage',
52
+ `network: ${a.network}`,
53
+ `contract: ${a.contract}`,
54
+ `seller: ${identityText(a.seller)}`,
55
+ `file: ${a.file_id}`,
56
+ `amount: ${a.amount}`,
57
+ `nonce: ${a.nonce}`,
58
+ `expires: ${a.deadline}`,
59
+ ].join('\n');
60
+ }