@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,7 @@
1
+ export declare function clientKey(header: string | string[] | undefined, socket: string | undefined): string;
2
+ /** Bounded fixed windows; overflow shares a bucket instead of bypassing admission. */
3
+ export declare function fixedWindowLimiter(limit: number, windowMs?: number, now?: () => number, maxKeys?: number): (key: string) => {
4
+ allowed: boolean;
5
+ retryAfterSeconds: number;
6
+ };
7
+ export declare function limiter(perMinute: number, now?: () => number, maxKeys?: number): (key: string) => boolean;
package/dist/net/ip.js ADDED
@@ -0,0 +1,62 @@
1
+ /** Canonical proxy/socket client buckets shared by the services. IPv6 uses /48. */
2
+ function canonical(value) {
3
+ if (typeof value !== 'string' ||
4
+ value.length > 64 ||
5
+ value !== value.trim() ||
6
+ /[%\s,[\]]/.test(value))
7
+ return;
8
+ if (!value.includes(':')) {
9
+ const parts = value.split('.');
10
+ if (parts.length === 4 && parts.every((p) => /^(0|[1-9]\d{0,2})$/.test(p) && Number(p) <= 255))
11
+ return parts.join('.');
12
+ return;
13
+ }
14
+ try {
15
+ const host = new URL(`http://[${value}]/`).hostname.slice(1, -1);
16
+ const [head = '', tail = ''] = host.split('::');
17
+ const a = head ? head.split(':') : [];
18
+ const b = tail ? tail.split(':') : [];
19
+ const words = host.includes('::')
20
+ ? [...a, ...Array(8 - a.length - b.length).fill('0'), ...b]
21
+ : a;
22
+ if (words.length !== 8)
23
+ return;
24
+ const n = words.map((w) => Number.parseInt(w, 16));
25
+ if (n.slice(0, 5).every((w) => w === 0) && n[5] === 65535) {
26
+ const hi = n[6];
27
+ const lo = n[7];
28
+ return [hi >> 8, hi & 255, lo >> 8, lo & 255].join('.');
29
+ }
30
+ return `${n
31
+ .slice(0, 3)
32
+ .map((w) => w.toString(16))
33
+ .join(':')}::/48`;
34
+ }
35
+ catch {
36
+ return;
37
+ }
38
+ }
39
+ export function clientKey(header, socket) {
40
+ return canonical(header) ?? canonical(socket) ?? 'unknown';
41
+ }
42
+ /** Bounded fixed windows; overflow shares a bucket instead of bypassing admission. */
43
+ export function fixedWindowLimiter(limit, windowMs = 60_000, now = () => Date.now(), maxKeys = 50_000) {
44
+ let window = -1;
45
+ let hits = new Map();
46
+ return (key) => {
47
+ const t = now();
48
+ const w = Math.floor(t / windowMs);
49
+ if (w !== window) {
50
+ window = w;
51
+ hits = new Map();
52
+ }
53
+ const k = hits.has(key) || hits.size < maxKeys ? key : '*';
54
+ const n = (hits.get(k) ?? 0) + 1;
55
+ hits.set(k, n);
56
+ return { allowed: n <= limit, retryAfterSeconds: Math.ceil(((w + 1) * windowMs - t) / 1000) };
57
+ };
58
+ }
59
+ export function limiter(perMinute, now = () => Date.now(), maxKeys = 50_000) {
60
+ const check = fixedWindowLimiter(perMinute, 60_000, now, maxKeys);
61
+ return (key) => check(key).allowed;
62
+ }
@@ -0,0 +1,3 @@
1
+ /** Retry-After in milliseconds, preserving the full server wait. Unknown values stay unknown. */
2
+ export declare function parseRetryAfterMs(value: string | null | undefined, now?: number): number | undefined;
3
+ export declare function sleep(ms: number, signal?: AbortSignal): Promise<void>;
@@ -0,0 +1,31 @@
1
+ /** Retry-After in milliseconds, preserving the full server wait. Unknown values stay unknown. */
2
+ export function parseRetryAfterMs(value, now = Date.now()) {
3
+ if (!value?.trim())
4
+ return;
5
+ const text = value.trim();
6
+ if (/^\d+$/.test(text)) {
7
+ const n = Number(text) * 1000;
8
+ return Number.isSafeInteger(n) ? n : undefined;
9
+ }
10
+ if (!/^[A-Za-z]{3}, /.test(text))
11
+ return;
12
+ const time = Date.parse(text);
13
+ return Number.isFinite(time) ? Math.max(0, time - now) : undefined;
14
+ }
15
+ export function sleep(ms, signal) {
16
+ return new Promise((resolve, reject) => {
17
+ if (signal?.aborted) {
18
+ reject(signal.reason);
19
+ return;
20
+ }
21
+ const abort = () => {
22
+ clearTimeout(timer);
23
+ reject(signal?.reason);
24
+ };
25
+ const timer = setTimeout(() => {
26
+ signal?.removeEventListener('abort', abort);
27
+ resolve();
28
+ }, ms);
29
+ signal?.addEventListener('abort', abort, { once: true });
30
+ });
31
+ }
@@ -0,0 +1,6 @@
1
+ import type { AnnonErrorCode, PublicError } from '../errors.js';
2
+ import type { Config } from './config.js';
3
+ declare const fail: (code: AnnonErrorCode, message: string, exit?: PublicError["exitCode"]) => never;
4
+ declare const isId: (id: string) => boolean;
5
+ declare const backupPath: (c: Config, id: string) => string;
6
+ export { backupPath, fail, isId };
@@ -0,0 +1,13 @@
1
+ /** Typed adapter failures and the file ID and backup path checks used by the CLI commands. */
2
+ import { join } from 'node:path';
3
+ import { AdapterError, usage } from './errors.js';
4
+ const fail = (code, message, exit = 1) => {
5
+ throw new AdapterError(code, message, exit);
6
+ };
7
+ const isId = (id) => /^0x[0-9a-f]{64}$/.test(id);
8
+ const backupPath = (c, id) => {
9
+ if (!/^(recovery:|sell:|seller-key:)?0x[0-9a-f]{64}(:revision:[0-9]+)?$/.test(id))
10
+ throw usage();
11
+ return join(c.stateDir, 'backups', `${id.replaceAll(':', '-')}.json`);
12
+ };
13
+ export { backupPath, fail, isId };
@@ -0,0 +1,8 @@
1
+ /** Node I/O around the SDK. Settlement, commitments, encryption and proofs stay in the SDK. */
2
+ import { type Dependencies } from './core-adapter.js';
3
+ import type { Input } from './cli.js';
4
+ import { type Environment } from './wallets.js';
5
+ export { amount, nearAmount } from './amounts.js';
6
+ export type { Dependencies } from './core-adapter.js';
7
+ /** CLI session lifetime includes every wallet action and is always closed on exit. */
8
+ export declare function createAdapter(env?: Environment, d?: Dependencies): (o: Input) => Promise<Record<string, unknown>>;
@@ -0,0 +1,98 @@
1
+ /** Node I/O around the SDK. Settlement, commitments, encryption and proofs stay in the SDK. */
2
+ import { createCoreAdapter } from './core-adapter.js';
3
+ // core-adapter.js loads first: it imports the SDK modules in the order this file did, so they initialize as before.
4
+ import { identityJson, parseIdentity } from '../settle-near/identity.js';
5
+ import { agentPolicy, configPath, initAgent, savePublicConfig } from './agent.js';
6
+ import { startBrowserSession } from './browser-login.js';
7
+ import { loadConfig } from './config.js';
8
+ import { AdapterError } from './errors.js';
9
+ import { osKeychain } from './keychain.js';
10
+ import { confinePaths } from './paths.js';
11
+ import { discoverShops, isDiscovery } from './shops.js';
12
+ import { assertSignerInput, configuredWallet } from './signer-wallet.js';
13
+ import { signingRequest, verifySigningResult } from './signing.js';
14
+ import { identity } from './wallets.js';
15
+ export { amount, nearAmount } from './amounts.js';
16
+ /** CLI session lifetime includes every wallet action and is always closed on exit. */
17
+ export function createAdapter(env = process.env, d = {}) {
18
+ return async (o) => {
19
+ if (o.command !== 'init' && (o.signer || o.credentialEnv || o.credentialKeychain))
20
+ throw new AdapterError('signer_init_only', 'Signer and credential references can only be set by init.', 2);
21
+ const agent = await agentPolicy(o, env, d.agent);
22
+ if (isDiscovery(o))
23
+ return discoverShops(o, env, agent, d.fetch);
24
+ if (o.command === 'init')
25
+ return initAgent(o, env, d.keychain ?? osKeychain);
26
+ const c = await loadConfig(o, env, o.command === 'login', d.agent);
27
+ if (agent || c.agent)
28
+ o = await confinePaths(o, c.workDir);
29
+ if (c.signer) {
30
+ assertSignerInput(o);
31
+ if (o.recovery && identityJson(identity(o.recovery)) !== identityJson(c.signer.identity))
32
+ throw new AdapterError('wrong_identity', 'Recovery must match the configured signer.', 2);
33
+ }
34
+ if ((agent || c.agent) && o.command === 'login')
35
+ throw new AdapterError('agent_policy', 'Agent mode refuses interactive login.', 2);
36
+ let session;
37
+ let terms = null;
38
+ const browser = async () => {
39
+ if (!session) {
40
+ session = await (d.browser ?? startBrowserSession)({ noOpen: o.noOpen === true });
41
+ const port = new URL(session.url).port;
42
+ d.notify?.(`Annon pairing code ${session.pairingCode}${o.noOpen ? `\n${session.url}` : ''}\nFor SSH: forward this port with ssh -L ${port}:127.0.0.1:${port} host. Use --no-open for a manual pairing URL.`);
43
+ }
44
+ return session;
45
+ };
46
+ try {
47
+ if (o.command === 'login') {
48
+ const s = await browser();
49
+ const kind = o.wallet ?? 'evm';
50
+ if (kind === 'solana')
51
+ throw new Error('browser Solana login is not supported');
52
+ const challenge = `Annon login: ${crypto.randomUUID()} on ${c.network}`;
53
+ const q = signingRequest(kind === 'evm' ? 'evm.personalSign' : 'near.nep413', kind === 'evm' ? 'eip155:8453' : c.network, '', kind === 'evm'
54
+ ? { message: challenge }
55
+ : {
56
+ message: challenge,
57
+ recipient: c.contractId || 'annon-cli',
58
+ nonce: [...crypto.getRandomValues(new Uint8Array(32))],
59
+ });
60
+ let identity;
61
+ await s.request(q, async (r) => {
62
+ const selected = parseIdentity({ kind, id: kind === 'evm' ? r.account : r.accountId });
63
+ await verifySigningResult({ ...q, account: selected.id }, r, c.nearRpc);
64
+ identity = selected;
65
+ });
66
+ if (!identity)
67
+ throw new Error('login failed');
68
+ await savePublicConfig(configPath(o, env), {
69
+ signer: { mode: 'browser', identity },
70
+ agent: false,
71
+ network: c.network,
72
+ nearRpc: c.nearRpc,
73
+ ...(c.contractId ? { contractId: c.contractId } : {}),
74
+ ...(c.baseRpc ? { baseRpc: c.baseRpc } : {}),
75
+ });
76
+ return { identity, network: c.network, mode: 'browser' };
77
+ }
78
+ const signer = c.signer;
79
+ return await createCoreAdapter(env, {
80
+ ...d,
81
+ agent: agent || c.agent === true,
82
+ browserApprovals: signer?.mode === 'browser',
83
+ paymentTerms: (value) => {
84
+ terms = value;
85
+ d.paymentTerms?.(value);
86
+ },
87
+ ...(signer && !d.wallet
88
+ ? {
89
+ wallet: (input, environment) => configuredWallet(signer, c, input, environment, d.keychain ?? osKeychain, browser, () => terms),
90
+ }
91
+ : {}),
92
+ })(o);
93
+ }
94
+ finally {
95
+ await session?.close();
96
+ }
97
+ };
98
+ }
@@ -0,0 +1,49 @@
1
+ import { type Identity } from '../settle-near/identity.js';
2
+ import type { Input } from './cli.js';
3
+ import { type Keychain } from './keychain.js';
4
+ import { type Environment } from './wallets.js';
5
+ export type SignerConfig = {
6
+ mode: 'browser';
7
+ identity: Identity;
8
+ } | {
9
+ mode: 'file';
10
+ kind: 'evm' | 'near' | 'solana';
11
+ file: string;
12
+ identity: Identity;
13
+ } | {
14
+ mode: 'env';
15
+ kind: 'evm' | 'near' | 'solana';
16
+ name: string;
17
+ identity: Identity;
18
+ } | {
19
+ mode: 'keychain';
20
+ kind: 'evm' | 'near';
21
+ entry: string;
22
+ identity: Identity;
23
+ publicKey?: string;
24
+ } | {
25
+ mode: 'remote';
26
+ url: string;
27
+ credential: {
28
+ env: string;
29
+ } | {
30
+ keychain: string;
31
+ };
32
+ identity: Identity;
33
+ publicKey?: string;
34
+ };
35
+ export declare const configPath: (o: Partial<Input>, env: Environment) => string;
36
+ /** The launcher environment and its config are the agent policy anchor, never per-call flags. */
37
+ export declare function agentPolicy(o: Input, env: Environment, forced?: boolean): Promise<boolean>;
38
+ export declare function savePublicConfig(path: string, patch: {
39
+ signer: SignerConfig;
40
+ dailyBudget?: string;
41
+ agent: boolean;
42
+ network?: string;
43
+ nearRpc?: string;
44
+ contractId?: string;
45
+ baseRpc?: string;
46
+ }): Promise<void>;
47
+ export declare function initAgent(o: Input, env: Environment, keychain?: Keychain): Promise<Record<string, unknown>>;
48
+ /** Reject plaintext credentials and unknown persisted signer fields at the config boundary. */
49
+ export declare function validateSigner(s: SignerConfig): SignerConfig;
@@ -0,0 +1,266 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { mkdir, readFile, rename, writeFile } from 'node:fs/promises';
3
+ import { homedir } from 'node:os';
4
+ import { dirname, join, resolve } from 'node:path';
5
+ import { secureUrl } from '../download/download.js';
6
+ import { account, network } from '../settle-near/codec.js';
7
+ import { parseIdentity } from '../settle-near/identity.js';
8
+ import { validateDiscoveryConfig } from '../shops/discovery.js';
9
+ import { AdapterError } from './errors.js';
10
+ import { osKeychain } from './keychain.js';
11
+ import { remoteSigner } from './signing.js';
12
+ import { identity } from './wallets.js';
13
+ export const configPath = (o, env) => {
14
+ const home = join(homedir(), '.config', 'annon', 'config.json');
15
+ if (env.ANNON_AGENT !== 'true')
16
+ return o.config ?? env.ANNON_CONFIG ?? home;
17
+ // Agent mode pins the home config; a different ANNON_CONFIG is an operator mistake, not a silent override.
18
+ if (env.ANNON_CONFIG && resolve(env.ANNON_CONFIG) !== resolve(home))
19
+ throw new AdapterError('config_conflict', 'ANNON_AGENT=true always uses ~/.config/annon/config.json; unset ANNON_CONFIG or point it there.', 2);
20
+ return home;
21
+ };
22
+ /** The launcher environment and its config are the agent policy anchor, never per-call flags. */
23
+ export async function agentPolicy(o, env, forced = false) {
24
+ const pinned = configPath({}, env);
25
+ const home = join(homedir(), '.config', 'annon', 'config.json');
26
+ if (resolve(pinned) !== resolve(home)) {
27
+ try {
28
+ if (JSON.parse(await readFile(home, 'utf8')).agent === true)
29
+ throw new AdapterError('config_conflict', 'The home config enables agent mode. Unset ANNON_CONFIG or point it there.', 2);
30
+ }
31
+ catch (error) {
32
+ if (error.code !== 'ENOENT')
33
+ throw error;
34
+ }
35
+ }
36
+ let saved = {};
37
+ try {
38
+ saved = JSON.parse(await readFile(pinned, 'utf8'));
39
+ }
40
+ catch (error) {
41
+ if (error.code !== 'ENOENT')
42
+ throw error;
43
+ }
44
+ const agent = forced || env.ANNON_AGENT === 'true' || saved.agent === true;
45
+ if (agent &&
46
+ (o.command === 'init' ||
47
+ o.command === 'login' ||
48
+ (o.config !== undefined && resolve(o.config) !== resolve(pinned)) ||
49
+ o.stateDir !== undefined ||
50
+ [
51
+ o.contractId,
52
+ o.nearRpc,
53
+ o.baseRpc,
54
+ o.relayer,
55
+ o.network,
56
+ o.trustContract,
57
+ o.moderator,
58
+ o.maxPrice,
59
+ o.storageBudget,
60
+ o.listingStorage,
61
+ o.fundAmount,
62
+ ].some((v) => v !== undefined)))
63
+ throw new AdapterError('agent_policy', 'Agent mode pins deployment, trust, price and state policy; per-call overrides, init and login are refused.', 2);
64
+ return agent;
65
+ }
66
+ export async function savePublicConfig(path, patch) {
67
+ let old = {};
68
+ try {
69
+ old = JSON.parse(await readFile(path, 'utf8'));
70
+ }
71
+ catch (e) {
72
+ if (e.code !== 'ENOENT')
73
+ throw e;
74
+ }
75
+ validateDiscoveryConfig(old);
76
+ const allowed = [
77
+ 'shopDirectoryUrl',
78
+ 'shops',
79
+ 'defaultShop',
80
+ 'contractId',
81
+ 'nearRpc',
82
+ 'baseRpc',
83
+ 'network',
84
+ 'stateDir',
85
+ 'workDir',
86
+ 'relayer',
87
+ 'maxPrice',
88
+ 'trustContract',
89
+ 'mpcKey',
90
+ 'moderators',
91
+ 'origins',
92
+ 'allowedResources',
93
+ 'storageBudget',
94
+ 'listingStorage',
95
+ 'maxProtocolFee',
96
+ ];
97
+ const publicConfig = Object.fromEntries(Object.entries(old).filter(([k]) => allowed.includes(k)));
98
+ await mkdir(dirname(path), { recursive: true, mode: 0o700 });
99
+ const tmp = `${path}.${randomUUID()}.tmp`;
100
+ await writeFile(tmp, JSON.stringify({ network: 'near:mainnet', ...publicConfig, ...patch }, null, 2), {
101
+ mode: 0o600,
102
+ flag: 'wx',
103
+ });
104
+ await rename(tmp, path);
105
+ }
106
+ export async function initAgent(o, env, keychain = osKeychain) {
107
+ await agentPolicy(o, env);
108
+ if ((o.config !== undefined && resolve(o.config) !== resolve(configPath({}, env))) ||
109
+ o.stateDir !== undefined)
110
+ throw new AdapterError('agent_policy', 'Set the trusted ANNON_CONFIG and ANNON_STATE_DIR launcher environment before init; per-call agent paths are refused.', 2);
111
+ if (!o.agent || !o.dailyBudget || !/^(0|[1-9][0-9]*)(\.[0-9]{1,6})?$/.test(o.dailyBudget))
112
+ throw new AdapterError('budget_required', 'init --agent requires --daily-budget in USDC.', 2);
113
+ const kind = o.wallet ??
114
+ (o.recovery
115
+ ? identity(o.recovery).kind
116
+ : o.evmKeyFile
117
+ ? 'evm'
118
+ : o.solanaKeyFile
119
+ ? 'solana'
120
+ : 'near');
121
+ let selected = o.recovery
122
+ ? identity(o.recovery)
123
+ : o.nearAccount
124
+ ? parseIdentity({ kind: 'near', id: o.nearAccount })
125
+ : undefined;
126
+ const choices = [
127
+ o.signer,
128
+ o.newKey,
129
+ o.keyEnv,
130
+ !o.newKey && o.keychainEntry,
131
+ o.evmKeyFile,
132
+ o.nearKeyFile,
133
+ o.solanaKeyFile,
134
+ ].filter(Boolean);
135
+ if (choices.length !== 1)
136
+ throw new AdapterError('signer_required', 'Choose exactly one key reference, new keychain key or remote signer.', 2);
137
+ let signer;
138
+ if (o.signer) {
139
+ remoteSigner({ url: o.signer, credential: async () => '' });
140
+ if (!selected ||
141
+ (!o.credentialEnv && !o.credentialKeychain) ||
142
+ (o.credentialEnv && o.credentialKeychain))
143
+ throw new AdapterError('signer_required', 'Remote signing requires an identity and exactly one credential reference.', 2);
144
+ signer = {
145
+ mode: 'remote',
146
+ url: o.signer,
147
+ identity: selected,
148
+ credential: o.credentialEnv
149
+ ? { env: o.credentialEnv }
150
+ : { keychain: o.credentialKeychain },
151
+ ...(o.nearPublicKey ? { publicKey: o.nearPublicKey } : {}),
152
+ };
153
+ }
154
+ else if (o.newKey) {
155
+ if (kind === 'solana')
156
+ throw new Error('new Solana keys are not supported');
157
+ const entry = o.keychainEntry ?? `annon-agent-${randomUUID()}`;
158
+ const created = await keychain.create(kind, entry, o.nearAccount);
159
+ selected = parseIdentity({ kind: created.kind, id: created.id });
160
+ signer = {
161
+ mode: 'keychain',
162
+ entry,
163
+ kind,
164
+ identity: selected,
165
+ ...(created.publicKey ? { publicKey: created.publicKey } : {}),
166
+ };
167
+ }
168
+ else if (o.keyEnv) {
169
+ if (!/^[A-Z_][A-Z0-9_]*$/.test(o.keyEnv))
170
+ throw new Error('invalid environment reference');
171
+ if (!selected)
172
+ throw new Error('signer identity required');
173
+ signer = { mode: 'env', kind, name: o.keyEnv, identity: selected };
174
+ }
175
+ else if (o.keychainEntry) {
176
+ if (kind === 'solana' || !selected)
177
+ throw new Error('keychain identity required');
178
+ signer = {
179
+ mode: 'keychain',
180
+ entry: o.keychainEntry,
181
+ kind,
182
+ identity: selected,
183
+ ...(o.nearPublicKey ? { publicKey: o.nearPublicKey } : {}),
184
+ };
185
+ }
186
+ else {
187
+ if (!selected)
188
+ throw new Error('signer identity required');
189
+ signer = {
190
+ mode: 'file',
191
+ kind,
192
+ file: resolve((o.evmKeyFile ?? o.nearKeyFile ?? o.solanaKeyFile)),
193
+ identity: selected,
194
+ };
195
+ }
196
+ validateSigner(signer);
197
+ const chain = o.network ?? env.ANNON_NETWORK;
198
+ const endpoints = {
199
+ ...(o.nearRpc ? { nearRpc: o.nearRpc } : {}),
200
+ ...(o.baseRpc ? { baseRpc: o.baseRpc } : {}),
201
+ };
202
+ for (const endpoint of Object.values(endpoints)) {
203
+ const url = secureUrl(endpoint);
204
+ if (url.username || url.password || url.search || url.hash)
205
+ throw new Error('invalid public endpoint');
206
+ }
207
+ if (o.contractId)
208
+ account(o.contractId);
209
+ if (chain)
210
+ network(chain);
211
+ await savePublicConfig(configPath(o, env), {
212
+ signer,
213
+ dailyBudget: o.dailyBudget,
214
+ agent: true,
215
+ ...endpoints,
216
+ ...(chain ? { network: chain } : {}),
217
+ ...(o.contractId ? { contractId: o.contractId } : {}),
218
+ });
219
+ return {
220
+ configured: true,
221
+ mode: signer.mode,
222
+ ...(selected ? { identity: selected } : {}),
223
+ dailyBudget: o.dailyBudget,
224
+ };
225
+ }
226
+ /** Reject plaintext credentials and unknown persisted signer fields at the config boundary. */
227
+ export function validateSigner(s) {
228
+ if (!s || typeof s !== 'object')
229
+ throw new TypeError('invalid signer');
230
+ const fields = {
231
+ browser: ['mode', 'identity'],
232
+ file: ['mode', 'kind', 'file', 'identity'],
233
+ env: ['mode', 'kind', 'name', 'identity'],
234
+ keychain: ['mode', 'kind', 'entry', 'identity', 'publicKey'],
235
+ remote: ['mode', 'url', 'credential', 'identity', 'publicKey'],
236
+ };
237
+ if (!Object.hasOwn(fields, s.mode) || Object.keys(s).some((k) => !fields[s.mode].includes(k)))
238
+ throw new TypeError('unexpected signer fields');
239
+ if (s.identity)
240
+ parseIdentity(s.identity);
241
+ if (!s.identity)
242
+ throw new TypeError('signer identity required');
243
+ if ('kind' in s && s.identity && s.kind !== s.identity.kind)
244
+ throw new TypeError('signer kind/identity mismatch');
245
+ if ('kind' in s && !['near', 'evm', 'solana'].includes(s.kind))
246
+ throw new TypeError('invalid signer kind');
247
+ if (s.mode === 'file' && (typeof s.file !== 'string' || !s.file))
248
+ throw new TypeError('key file reference required');
249
+ if (s.mode === 'env' && !/^[A-Z_][A-Z0-9_]*$/.test(s.name))
250
+ throw new TypeError('environment reference required');
251
+ if (s.mode === 'keychain' && (typeof s.entry !== 'string' || !s.entry))
252
+ throw new TypeError('keychain entry required');
253
+ if (s.mode === 'remote') {
254
+ remoteSigner({ url: s.url, credential: async () => '' });
255
+ if (!s.credential ||
256
+ Object.keys(s.credential).length !== 1 ||
257
+ !('env' in s.credential || 'keychain' in s.credential))
258
+ throw new TypeError('credential reference required');
259
+ if ('env' in s.credential && !/^[A-Z_][A-Z0-9_]*$/.test(s.credential.env))
260
+ throw new TypeError('invalid credential environment reference');
261
+ if ('keychain' in s.credential &&
262
+ (typeof s.credential.keychain !== 'string' || !s.credential.keychain))
263
+ throw new TypeError('invalid credential keychain reference');
264
+ }
265
+ return s;
266
+ }
@@ -0,0 +1,2 @@
1
+ export declare function amount(text: string): string;
2
+ export declare function nearAmount(text: string): bigint;
@@ -0,0 +1,20 @@
1
+ /** Parses decimal USDC and NEAR amounts from command input into base units. */
2
+ import { usage } from './errors.js';
3
+ export function amount(text) {
4
+ if (!/^(0|[1-9][0-9]*)(\.[0-9]{1,6})?$/.test(text))
5
+ throw usage();
6
+ const [whole, fraction = ''] = text.split('.');
7
+ const n = BigInt(whole ?? '') * 1000000n + BigInt(fraction.padEnd(6, '0'));
8
+ if (n <= 0n || n >= 2n ** 128n)
9
+ throw usage();
10
+ return String(n);
11
+ }
12
+ export function nearAmount(text) {
13
+ if (!/^(0|[1-9][0-9]*)(\.[0-9]{1,24})?$/.test(text))
14
+ throw usage();
15
+ const [whole = '0', fraction = ''] = text.split('.');
16
+ const n = BigInt(whole) * 10n ** 24n + BigInt(fraction.padEnd(24, '0'));
17
+ if (n <= 0n || n >= 2n ** 128n)
18
+ throw usage();
19
+ return n;
20
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * The approval page talks only to this loopback server, the NEAR RPC its Wallet Selector uses
3
+ * (fixed per network in browser/login.ts, independent of the CLI's configured RPC) and HOT
4
+ * Wallet's relay; it frames only the HOT widget. EVM wallets are reached through the injected
5
+ * provider, which needs no connection from the page.
6
+ */
7
+ export declare const LOGIN_PAGE_CSP = "default-src 'none'; script-src 'self'; connect-src 'self' https://free.rpc.fastnear.com https://test.rpc.fastnear.com https://h4n.app; frame-src https://hot-labs.org; style-src 'self'; img-src 'self' data:; frame-ancestors 'none'; base-uri 'none'; form-action 'none'";
8
+ export interface BrowserSession {
9
+ url: string;
10
+ pairingCode: string;
11
+ request<T>(action: {
12
+ id: string;
13
+ expiresAt: number;
14
+ exact?: unknown;
15
+ }, verify: (result: T) => Promise<void>): Promise<T>;
16
+ close(): Promise<void>;
17
+ }
18
+ export declare function startBrowserSession(o: {
19
+ noOpen?: boolean;
20
+ open?: (url: string) => void;
21
+ asset?: string;
22
+ timeoutMs?: number;
23
+ }): Promise<BrowserSession>;
24
+ /** Only desktop routing and executable lookup reach the browser opener. */
25
+ export declare function browserOpenerEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv;