@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,43 @@
1
+ import '../api/annon.js';
2
+ import '../download/download.js';
3
+ import '../download/proof.js';
4
+ import '../envelope/pay2.js';
5
+ import '../errors.js';
6
+ import '../near/rpc.js';
7
+ import '../oneclick/oneclick.js';
8
+ import '../pay/backup.js';
9
+ import '../pay/purchase.js';
10
+ import '../pay/x402.js';
11
+ import '../relay/capabilities.js';
12
+ import '../relay/fees.js';
13
+ import '../settle-near/codec.js';
14
+ import '../settle-near/identity.js';
15
+ import '../wallet/uncertain.js';
16
+ import '../wallet/wallet.js';
17
+ import './agent.js';
18
+ import './browser-login.js';
19
+ import './budget.js';
20
+ import type { Input } from './cli.js';
21
+ import './errors.js';
22
+ import './io.js';
23
+ import './keychain.js';
24
+ import './paths.js';
25
+ import './payment-terms.js';
26
+ import './purchase-guard.js';
27
+ import './resources.js';
28
+ import './retry-gate.js';
29
+ import './signer-wallet.js';
30
+ import './signing.js';
31
+ import { type Environment } from './wallets.js';
32
+ import './adapter-checks.js';
33
+ import './amounts.js';
34
+ import './offers.js';
35
+ import './origin.js';
36
+ import { type Dependencies } from './command-context.js';
37
+ export type { Dependencies } from './command-context.js';
38
+ /**
39
+ * Steps run in this order for every command: config and contract trust, the checks that need no
40
+ * backup secret, the secret, the local stores and the SDK, then the command itself.
41
+ */
42
+ declare function createCoreAdapter(env?: Environment, d?: Dependencies): (o: Input) => Promise<Record<string, unknown>>;
43
+ export { createCoreAdapter };
@@ -0,0 +1,122 @@
1
+ /** Runs one CLI command against the SDK once config, trust and signer are resolved. */
2
+ // The imports keep node/adapter.ts's original order, bare ones included, so modules initialize as before.
3
+ import { mkdir } from 'node:fs/promises';
4
+ import { join } from 'node:path';
5
+ import '../api/annon.js';
6
+ import '../download/download.js';
7
+ import '../download/proof.js';
8
+ import '../envelope/pay2.js';
9
+ import '../errors.js';
10
+ import '../near/rpc.js';
11
+ import { sleep } from '../net/retry.js';
12
+ import '../oneclick/oneclick.js';
13
+ import '../pay/backup.js';
14
+ import '../pay/purchase.js';
15
+ import { fileStore } from '../pay/store-node.js';
16
+ import '../pay/x402.js';
17
+ import '../relay/capabilities.js';
18
+ import '../relay/fees.js';
19
+ import '../settle-near/codec.js';
20
+ import '../settle-near/identity.js';
21
+ import '../wallet/uncertain.js';
22
+ import '../wallet/wallet.js';
23
+ import './agent.js';
24
+ import './browser-login.js';
25
+ import './budget.js';
26
+ import { loadConfig, trustContract } from './config.js';
27
+ import './errors.js';
28
+ import './io.js';
29
+ import './keychain.js';
30
+ import './paths.js';
31
+ import './payment-terms.js';
32
+ import './purchase-guard.js';
33
+ import './resources.js';
34
+ import './retry-gate.js';
35
+ import { shareHost } from './sharing.js';
36
+ import { selectedShopRules } from './shops.js';
37
+ import './signer-wallet.js';
38
+ import './signing.js';
39
+ import { readBackupSecret } from './wallets.js';
40
+ // Modules split out of the adapter load after its original imports, so initialization order is unchanged.
41
+ import './adapter-checks.js';
42
+ import './amounts.js';
43
+ import './offers.js';
44
+ import './origin.js';
45
+ // The command modules declare functions only; every module they import has loaded by now.
46
+ import { buy, prepareBuy, verifyFile } from './buy-commands.js';
47
+ import { requestContext, } from './command-context.js';
48
+ import { open, recover, status, statusOnChain } from './purchase-commands.js';
49
+ import { createSdk } from './relay-transport.js';
50
+ import { sell, share, stop } from './seller-commands.js';
51
+ import { fileBudget, fund, fundWithNearWallet } from './storage-commands.js';
52
+ /**
53
+ * Steps run in this order for every command: config and contract trust, the checks that need no
54
+ * backup secret, the secret, the local stores and the SDK, then the command itself.
55
+ */
56
+ function createCoreAdapter(env = process.env, d = {}) {
57
+ const fetcher = d.fetch ?? globalThis.fetch;
58
+ const wait = d.wait ?? sleep;
59
+ return async (o) => {
60
+ const c = await loadConfig(o, env, false, d.agent);
61
+ await trustContract(c, o.trustContract === true || c.trustContract === true, o.json || d.agent ? undefined : d.prompt);
62
+ const ctx = requestContext(env, d, fetcher, wait, o, c);
63
+ if (o.command === 'budget')
64
+ return fileBudget(ctx);
65
+ if (o.command === 'verify')
66
+ return verifyFile(ctx);
67
+ if (o.command === 'status') {
68
+ const settled = await statusOnChain(ctx);
69
+ if (settled)
70
+ return settled;
71
+ }
72
+ const preparedBuy = o.command === 'buy' ? await prepareBuy(ctx) : undefined;
73
+ const hostedOrigin = o.command === 'share' ? await shareHost(c, o.shop, fetcher) : undefined;
74
+ const shopRules = o.command === 'share' ? await selectedShopRules(c, o.shop, fetcher) : {};
75
+ if (o.command === 'fund') {
76
+ const funded = await fundWithNearWallet(ctx);
77
+ if (funded)
78
+ return funded;
79
+ }
80
+ let secret;
81
+ try {
82
+ secret = await readBackupSecret(o, env);
83
+ }
84
+ catch (error) {
85
+ if (o.command === 'status' && o.target)
86
+ return ctx.pendingSetup(o.target);
87
+ throw error;
88
+ }
89
+ const storeDir = join(c.stateDir, 'records');
90
+ const resourceDir = join(c.stateDir, 'resources');
91
+ if (o.command !== 'status')
92
+ for (const dir of [storeDir, resourceDir, join(c.stateDir, 'backups')])
93
+ await mkdir(dir, { recursive: true, mode: 0o700 });
94
+ const rawStore = fileStore(storeDir);
95
+ const resources = fileStore(resourceDir);
96
+ const sdk = createSdk(ctx, rawStore, secret);
97
+ // The same object as ctx, so the wallet selected so far stays visible to every step.
98
+ const session = Object.assign(ctx, {
99
+ secret,
100
+ storeDir,
101
+ rawStore,
102
+ resources,
103
+ sdk,
104
+ });
105
+ if (o.command === 'fund')
106
+ return fund(session);
107
+ if (o.command === 'status')
108
+ return status(session);
109
+ if (o.command === 'share')
110
+ return share(session, hostedOrigin, shopRules);
111
+ if (o.command === 'sell')
112
+ return sell(session);
113
+ if (o.command === 'buy')
114
+ return buy(session, preparedBuy);
115
+ if (o.command === 'recover')
116
+ return recover(session);
117
+ if (o.command === 'stop')
118
+ return stop(session);
119
+ return open(session);
120
+ };
121
+ }
122
+ export { createCoreAdapter };
@@ -0,0 +1,8 @@
1
+ import { AnnonError, type AnnonErrorCode, type PublicError, toPublicError } from '../errors.js';
2
+ /** Adapter-authored messages and exit codes are safe to display to the caller. */
3
+ export declare class AdapterError extends AnnonError {
4
+ readonly exitCode: PublicError['exitCode'];
5
+ constructor(code: AnnonErrorCode, message: string, exitCode?: PublicError['exitCode']);
6
+ }
7
+ export declare const usage: () => AdapterError;
8
+ export declare const publicError: typeof toPublicError;
@@ -0,0 +1,12 @@
1
+ import { AnnonError, toPublicError } from '../errors.js';
2
+ /** Adapter-authored messages and exit codes are safe to display to the caller. */
3
+ export class AdapterError extends AnnonError {
4
+ exitCode;
5
+ constructor(code, message, exitCode = 1) {
6
+ super(code, message);
7
+ this.exitCode = exitCode;
8
+ this.name = 'AdapterError';
9
+ }
10
+ }
11
+ export const usage = () => new AdapterError('invalid_arguments', 'Invalid arguments. Run annon --help.', 2);
12
+ export const publicError = toPublicError;
@@ -0,0 +1,7 @@
1
+ /** Node-only entry: the adapter behind the annon CLI and the MCP server. */
2
+ export { type AnnonErrorCode, annonErrorCodes, type PublicError } from '../errors.js';
3
+ export { createAdapter } from './adapter.js';
4
+ export type { BudgetRecord } from './budget.js';
5
+ export { BudgetError } from './budget.js';
6
+ export type { Input } from './cli.js';
7
+ export { AdapterError, publicError } from './errors.js';
@@ -0,0 +1,5 @@
1
+ /** Node-only entry: the adapter behind the annon CLI and the MCP server. */
2
+ export { annonErrorCodes } from '../errors.js';
3
+ export { createAdapter } from './adapter.js';
4
+ export { BudgetError } from './budget.js';
5
+ export { AdapterError, publicError } from './errors.js';
@@ -0,0 +1,13 @@
1
+ import type { ReplayableSource } from '../envelope/replay.js';
2
+ /** Also supplies serveFile's size/read(start) interface without buffering the disk file. */
3
+ export declare function fileSource(path: string): Promise<ReplayableSource & {
4
+ read(start?: number): ReadableStream<Uint8Array>;
5
+ }>;
6
+ /** No requested output name is created until the producer and final authentication succeed. */
7
+ export declare function withOutput<T>(path: string, write: (sink: WritableStream<Uint8Array>) => Promise<T>): Promise<T>;
8
+ export declare function writeStream(path: string, stream: ReadableStream<Uint8Array>): Promise<void>;
9
+ /** Encrypted disk spool for a non-replayable download; always removed on success or failure. */
10
+ export declare function withSpool<T>(output: string, stream: ReadableStream<Uint8Array>, use: (source: ReplayableSource) => Promise<T>): Promise<T>;
11
+ export declare function newOutput(path: string): Promise<void>;
12
+ /** Portable backups are complete and synced before the SDK proceeds to payment or key release. */
13
+ export declare function writeBackup(path: string, content: string): Promise<void>;
@@ -0,0 +1,112 @@
1
+ import { randomBytes } from 'node:crypto';
2
+ import { createReadStream } from 'node:fs';
3
+ import { link, lstat, mkdtemp, open, rename, rm, stat, unlink } from 'node:fs/promises';
4
+ import { dirname, join } from 'node:path';
5
+ import { Readable } from 'node:stream';
6
+ import { AdapterError } from './errors.js';
7
+ /** Also supplies serveFile's size/read(start) interface without buffering the disk file. */
8
+ export async function fileSource(path) {
9
+ const info = await stat(path);
10
+ if (!info.isFile())
11
+ throw new TypeError('expected a regular file');
12
+ return {
13
+ size: info.size,
14
+ read: (start = 0) => Readable.toWeb(createReadStream(path, { start, highWaterMark: 256 * 1024 }), {
15
+ strategy: { highWaterMark: 256 * 1024, size: (chunk) => chunk.length },
16
+ }),
17
+ };
18
+ }
19
+ /** No requested output name is created until the producer and final authentication succeed. */
20
+ export async function withOutput(path, write) {
21
+ await newOutput(path);
22
+ const temp = `${path}.${randomBytes(8).toString('hex')}.tmp`;
23
+ const handle = await open(temp, 'wx', 0o600);
24
+ try {
25
+ let result;
26
+ try {
27
+ result = await write(new WritableStream({ write: (chunk) => handle.writeFile(chunk) }));
28
+ await handle.sync();
29
+ }
30
+ finally {
31
+ await handle.close();
32
+ }
33
+ // Hard-link the complete inode exclusively: the requested name is never empty.
34
+ try {
35
+ await link(temp, path);
36
+ }
37
+ catch (error) {
38
+ if (error.code === 'EEXIST')
39
+ await newOutput(path);
40
+ throw error;
41
+ }
42
+ await unlink(temp);
43
+ const dir = await open(dirname(path), 'r');
44
+ try {
45
+ await dir.sync();
46
+ }
47
+ finally {
48
+ await dir.close();
49
+ }
50
+ return result;
51
+ }
52
+ finally {
53
+ await unlink(temp).catch(() => { });
54
+ }
55
+ }
56
+ export async function writeStream(path, stream) {
57
+ try {
58
+ await withOutput(path, (sink) => stream.pipeTo(sink));
59
+ }
60
+ catch (error) {
61
+ await stream.cancel(error).catch(() => { });
62
+ throw error;
63
+ }
64
+ }
65
+ /** Encrypted disk spool for a non-replayable download; always removed on success or failure. */
66
+ export async function withSpool(output, stream, use) {
67
+ const dir = await mkdtemp(join(dirname(output), '.annon-spool-'));
68
+ try {
69
+ const path = join(dir, 'encrypted');
70
+ await writeStream(path, stream);
71
+ return await use(await fileSource(path));
72
+ }
73
+ finally {
74
+ await rm(dir, { recursive: true, force: true });
75
+ }
76
+ }
77
+ export async function newOutput(path) {
78
+ try {
79
+ await lstat(path);
80
+ }
81
+ catch (error) {
82
+ if (error.code === 'ENOENT')
83
+ return;
84
+ throw error;
85
+ }
86
+ throw new AdapterError('output_exists', 'Choose a new output path; the existing file will not be overwritten.', 2);
87
+ }
88
+ /** Portable backups are complete and synced before the SDK proceeds to payment or key release. */
89
+ export async function writeBackup(path, content) {
90
+ const temp = `${path}.${randomBytes(8).toString('hex')}.tmp`;
91
+ const handle = await open(temp, 'wx', 0o600);
92
+ try {
93
+ try {
94
+ await handle.writeFile(content);
95
+ await handle.sync();
96
+ }
97
+ finally {
98
+ await handle.close();
99
+ }
100
+ await rename(temp, path);
101
+ const dir = await open(dirname(path), 'r');
102
+ try {
103
+ await dir.sync();
104
+ }
105
+ finally {
106
+ await dir.close();
107
+ }
108
+ }
109
+ finally {
110
+ await unlink(temp).catch(() => { });
111
+ }
112
+ }
@@ -0,0 +1,10 @@
1
+ import type { Identity } from '../settle-near/identity.js';
2
+ /** Inject this boundary in every test. Entry is a public service/account reference, never a secret. */
3
+ export interface Keychain {
4
+ read(entry: string): Promise<string>;
5
+ create(kind: 'evm' | 'near', entry: string, nearAccount?: string): Promise<Identity & {
6
+ publicKey?: string;
7
+ }>;
8
+ }
9
+ export declare const osKeychain: Keychain;
10
+ export declare function decodeKeychain(value: string): string;
@@ -0,0 +1,67 @@
1
+ import { execFile, spawn } from 'node:child_process';
2
+ import { promisify } from 'node:util';
3
+ import { base58 } from '@scure/base';
4
+ import { KeyPair } from 'near-api-js';
5
+ import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts';
6
+ const entryParts = (entry) => {
7
+ const [service, account = service] = entry.split('/');
8
+ if (!service ||
9
+ !account ||
10
+ entry.split('/').length > 2 ||
11
+ !/^[a-zA-Z0-9_.:@-]+$/.test(service + account))
12
+ throw new TypeError('invalid keychain reference');
13
+ return { service, account };
14
+ };
15
+ export const osKeychain = {
16
+ async read(entry) {
17
+ if (process.platform !== 'darwin')
18
+ throw new Error('OS keychain adapter currently requires macOS; use an env/file/remote signer');
19
+ const { service, account } = entryParts(entry);
20
+ const { stdout } = await promisify(execFile)('/usr/bin/security', ['find-generic-password', '-s', service, '-a', account, '-w'], { timeout: 10000, maxBuffer: 16384 });
21
+ return stdout.trim();
22
+ },
23
+ async create(kind, entry, nearAccount) {
24
+ if (process.platform !== 'darwin')
25
+ throw new Error('OS keychain adapter currently requires macOS');
26
+ const { service, account } = entryParts(entry);
27
+ let secret;
28
+ let identity;
29
+ if (kind === 'near') {
30
+ if (!nearAccount)
31
+ throw new Error('NEAR account required');
32
+ const key = KeyPair.fromRandom('ed25519');
33
+ secret = JSON.stringify({
34
+ account_id: nearAccount,
35
+ public_key: key.getPublicKey().toString(),
36
+ private_key: key.toString(),
37
+ });
38
+ identity = { kind, id: nearAccount, publicKey: key.getPublicKey().toString() };
39
+ }
40
+ else {
41
+ secret = generatePrivateKey();
42
+ identity = { kind, id: privateKeyToAccount(secret).address.toLowerCase() };
43
+ }
44
+ // An empty trusted-app ACL removes the creator (security) from automatic access.
45
+ // Reads require macOS approval; never trust the generic security executable.
46
+ // Feed security's command interpreter via stdin, keeping secrets out of process arguments.
47
+ // Base58 is an encoding inside the keychain, not a config-file credential.
48
+ const encoded = base58.encode(new TextEncoder().encode(secret));
49
+ await new Promise((resolve, reject) => {
50
+ const child = spawn('/usr/bin/security', ['-i'], { stdio: ['pipe', 'ignore', 'pipe'] });
51
+ let failed = false;
52
+ child.stderr.on('data', (b) => {
53
+ if (/SecKeychain|Error|error|already exists/.test(String(b)))
54
+ failed = true;
55
+ });
56
+ child.on('error', reject);
57
+ child.on('exit', (code) => code === 0 && !failed ? resolve() : reject(new Error('keychain storage failed')));
58
+ child.stdin.end(`add-generic-password -s ${service} -a ${account} -T "" -w ${encoded}\n`);
59
+ });
60
+ return identity;
61
+ },
62
+ };
63
+ export function decodeKeychain(value) {
64
+ if (value.startsWith('{') || value.startsWith('0x'))
65
+ return value;
66
+ return new TextDecoder().decode(base58.decode(value));
67
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env node
2
+ import { createInterface } from 'node:readline/promises';
3
+ import { createAdapter } from './adapter.js';
4
+ import { runCli } from './cli.js';
5
+ const adapter = createAdapter(process.env, {
6
+ ...(process.stdin.isTTY && process.stderr.isTTY
7
+ ? {
8
+ prompt: async (message) => {
9
+ const reader = createInterface({ input: process.stdin, output: process.stderr });
10
+ try {
11
+ return (await reader.question(message)).trim().toLowerCase() === 'yes';
12
+ }
13
+ finally {
14
+ reader.close();
15
+ }
16
+ },
17
+ }
18
+ : {}),
19
+ notify: (message) => process.stderr.write(`${message}\n`),
20
+ });
21
+ process.exitCode = await runCli(process.argv.slice(2), adapter, (text) => process.stdout.write(`${text}\n`));
@@ -0,0 +1,16 @@
1
+ import { type PaymentRequired } from '../pay/x402.js';
2
+ import type { Config } from './config.js';
3
+ declare const requestOptions: {
4
+ readonly redirect: "error";
5
+ readonly credentials: "omit";
6
+ readonly cache: "no-store";
7
+ };
8
+ declare function response(url: string, fetcher: typeof fetch, headers?: Record<string, string>): Promise<Response>;
9
+ declare function offer(target: string, c: Config, fetcher: typeof fetch, provided?: PaymentRequired, expectedUrl?: string): Promise<{
10
+ pr: PaymentRequired;
11
+ url: string;
12
+ header: Uint8Array<ArrayBufferLike>;
13
+ fileId: string;
14
+ }>;
15
+ declare function resourceHeader(url: string, c: Config, fetcher: typeof fetch): Promise<Uint8Array<ArrayBufferLike>>;
16
+ export { offer, requestOptions, resourceHeader, response };
@@ -0,0 +1,91 @@
1
+ /** Reads x402 offers and PAY2 headers over HTTPS for the CLI. */
2
+ import { readFile } from 'node:fs/promises';
3
+ import { secureUrl } from '../download/download.js';
4
+ import { sameResource } from '../download/proof.js';
5
+ import { MAX_HEADER, parseHeader } from '../envelope/pay2.js';
6
+ import { discover, readPaymentRequired } from '../pay/x402.js';
7
+ import { hex } from '../settle-near/codec.js';
8
+ import { fail } from './adapter-checks.js';
9
+ import { usage } from './errors.js';
10
+ const requestOptions = { redirect: 'error', credentials: 'omit', cache: 'no-store' };
11
+ async function response(url, fetcher, headers) {
12
+ const u = secureUrl(url);
13
+ if (u.username || u.password)
14
+ throw usage();
15
+ return fetcher(u, {
16
+ ...requestOptions,
17
+ signal: AbortSignal.timeout(20000),
18
+ ...(headers ? { headers } : {}),
19
+ });
20
+ }
21
+ async function headerAt(url, fetcher, initial) {
22
+ const r = initial ?? (await response(url, fetcher, { Range: `bytes=0-${MAX_HEADER - 1}` }));
23
+ if (![200, 206].includes(r.status) || !r.body)
24
+ throw new Error('header unavailable');
25
+ const reader = r.body.getReader();
26
+ const parts = [];
27
+ let size = 0;
28
+ try {
29
+ while (size < MAX_HEADER) {
30
+ const next = await reader.read();
31
+ if (next.done)
32
+ break;
33
+ const part = next.value.subarray(0, MAX_HEADER - size);
34
+ parts.push(part);
35
+ size += part.length;
36
+ }
37
+ }
38
+ finally {
39
+ await reader.cancel();
40
+ }
41
+ const b = Buffer.concat(parts);
42
+ if (b.length < 12)
43
+ throw new Error('short header');
44
+ const length = b.readUInt32BE(8);
45
+ if (length > MAX_HEADER || length > b.length)
46
+ throw new Error('bad header length');
47
+ return new Uint8Array(b.subarray(0, length));
48
+ }
49
+ async function offer(target, c, fetcher, provided, expectedUrl) {
50
+ let pr;
51
+ if (provided)
52
+ pr = provided;
53
+ else if (/^https?:/.test(target)) {
54
+ const r = await response(target, fetcher);
55
+ await r.body?.cancel();
56
+ const required = r.headers.get('PAYMENT-REQUIRED');
57
+ if (!required)
58
+ return fail('offer_required', 'The URL must advertise an Annon PAYMENT-REQUIRED offer.');
59
+ pr = readPaymentRequired(required);
60
+ }
61
+ else {
62
+ // A named file contains the decoded x402 JSON; SDK parsing remains authoritative.
63
+ const text = await readFile(target, 'utf8');
64
+ pr = readPaymentRequired(Buffer.from(text).toString('base64'));
65
+ }
66
+ const found = discover(pr, [c.contractId]);
67
+ const url = pr.resource?.url;
68
+ if (typeof url !== 'string' ||
69
+ !sameResource(url, /^https?:/.test(target) ? target : (expectedUrl ?? '')))
70
+ throw new Error('resource mismatch');
71
+ secureUrl(url);
72
+ const header = pr.extensions?.annon?.info.header;
73
+ if (typeof header !== 'string')
74
+ throw new Error('header missing');
75
+ if (secureUrl(header).origin !== new URL(url).origin)
76
+ throw new Error('header origin mismatch');
77
+ return { pr, url, header: await headerAt(header, fetcher), fileId: found.fileId };
78
+ }
79
+ async function resourceHeader(url, c, fetcher) {
80
+ const r = await response(url, fetcher, { Range: `bytes=0-${MAX_HEADER - 1}` });
81
+ const required = r.headers.get('PAYMENT-REQUIRED');
82
+ if (r.status === 402 && required) {
83
+ await r.body?.cancel();
84
+ const found = await offer(url, c, fetcher, readPaymentRequired(required));
85
+ if (hex(parseHeader(new Uint8Array(found.header)).fileId) !== found.fileId)
86
+ throw new Error('offer header mismatch');
87
+ return found.header;
88
+ }
89
+ return headerAt(url, fetcher, r);
90
+ }
91
+ export { offer, requestOptions, resourceHeader, response };
@@ -0,0 +1,11 @@
1
+ /** Resolves the 1Click source chain, asset and refund address for a cross-chain purchase. */
2
+ import { type Origin } from '../oneclick/oneclick.js';
3
+ import type { Input } from './cli.js';
4
+ import type { Config } from './config.js';
5
+ import type { SourceAsset } from './payment-terms.js';
6
+ import type { Environment, LocalWallet } from './wallets.js';
7
+ declare function originFor(o: Input, env: Environment, wallet: LocalWallet, fetcher: typeof fetch, c: Config): Promise<Origin & {
8
+ sourceAsset: SourceAsset;
9
+ maxSourceAmount?: string;
10
+ }>;
11
+ export { originFor };
@@ -0,0 +1,80 @@
1
+ /** Resolves the 1Click source chain, asset and refund address for a cross-chain purchase. */
2
+ import { originFamily } from '../oneclick/oneclick.js';
3
+ import { bytes } from '../settle-near/codec.js';
4
+ import { fail } from './adapter-checks.js';
5
+ import { response } from './offers.js';
6
+ const tokenIdentifier = (value) => typeof value === 'string' &&
7
+ value.length >= 1 &&
8
+ value.length <= 128 &&
9
+ !/[^A-Za-z0-9._:-]/.test(value);
10
+ const chainAliases = new Map([
11
+ ['solana', 'sol'],
12
+ ['ethereum', 'eth'],
13
+ ['arbitrum', 'arb'],
14
+ ['polygon', 'pol'],
15
+ ['optimism', 'op'],
16
+ ['avalanche', 'avax'],
17
+ ['berachain', 'bera'],
18
+ ]);
19
+ async function originFor(o, env, wallet, fetcher, c) {
20
+ const sourcePolicy = o.origin ? c.origins?.[o.origin] : undefined;
21
+ if (o.origin) {
22
+ if (!c.origins || !Object.hasOwn(c.origins, o.origin))
23
+ return fail('origin_not_allowed', 'Choose a human-configured origin name.', 2);
24
+ o = { ...o, ...c.origins[o.origin] };
25
+ }
26
+ const inputChain = o.fromChain ?? env.ANNON_FROM_CHAIN;
27
+ const chain = inputChain ? (chainAliases.get(inputChain) ?? inputChain) : undefined;
28
+ if (!chain)
29
+ return fail('origin_required', 'Choose --from-chain and optionally --from-asset for 1Click.', 2);
30
+ let asset = o.fromAsset ?? env.ANNON_FROM_ASSET;
31
+ let token;
32
+ if (!asset) {
33
+ const r = await response('https://1click.chaindefuser.com/v0/tokens', fetcher);
34
+ if (!r.ok)
35
+ throw new Error('tokens unavailable');
36
+ const tokens = (await r.json());
37
+ const matches = tokens.filter((t) => t.blockchain === chain && t.symbol === 'USDC');
38
+ if (matches.length !== 1)
39
+ return fail('origin_asset_required', 'Specify --from-asset with the exact 1Click nep141 asset ID for this chain.', 2);
40
+ token = matches[0];
41
+ asset = matches[0]?.assetId;
42
+ }
43
+ if (!tokenIdentifier(asset))
44
+ return fail('origin_asset_required', 'Specify --from-asset with a valid 1Click asset ID.', 2);
45
+ const kind = originFamily(chain);
46
+ let refundTo = o.refundTo ?? env.ANNON_REFUND_TO;
47
+ if (!refundTo && kind === wallet.identity.kind) {
48
+ refundTo = wallet.identity.id;
49
+ if (kind === 'solana') {
50
+ const { base58 } = await import('@scure/base');
51
+ refundTo = base58.encode(bytes(`0x${wallet.identity.id}`, 32));
52
+ }
53
+ }
54
+ if (!asset || !refundTo)
55
+ return fail('origin_required', 'Provide --from-asset and --refund-to for the source chain.', 2);
56
+ const decimals = token?.decimals;
57
+ const contractAddress = token?.contractAddress;
58
+ return {
59
+ asset,
60
+ chain: kind,
61
+ refundTo,
62
+ ...(sourcePolicy?.maxSourceAmount ? { maxSourceAmount: sourcePolicy.maxSourceAmount } : {}),
63
+ sourceAsset: {
64
+ ...(sourcePolicy?.usdcDecimals !== undefined
65
+ ? { symbol: 'USDC', decimals: sourcePolicy.usdcDecimals }
66
+ : {}),
67
+ chain,
68
+ asset,
69
+ ...(token ? { symbol: token.symbol } : {}),
70
+ ...(typeof decimals === 'number' &&
71
+ Number.isInteger(decimals) &&
72
+ decimals >= 0 &&
73
+ decimals <= 255
74
+ ? { decimals }
75
+ : {}),
76
+ ...(tokenIdentifier(contractAddress) ? { contractAddress } : {}),
77
+ },
78
+ };
79
+ }
80
+ export { originFor };
@@ -0,0 +1,3 @@
1
+ import type { Input } from './cli.js';
2
+ /** Canonical paths are passed to I/O; tool-relative paths start at the operator directory. */
3
+ export declare function confinePaths(input: Input, workDir: string): Promise<Input>;