@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,184 @@
1
+ import { isTransient } from '../errors.js';
2
+ /** Durable sponsorship admission. A later policy change cannot revoke a checked listing. */
3
+ import { getFile } from '../near/rpc.js';
4
+ import { readSplitState } from '../pay/split.js';
5
+ import { termsHash } from '../settle-near/terms.js';
6
+ import { checkProtocolFee } from './fees.js';
7
+ import { RelayError } from './relay.js';
8
+ export const ZERO_FEE = { fixed: '0', bps: 0, treasuryNear: null, treasuryEvm: null };
9
+ export const relayLimits = (o) => ({
10
+ perDay: 1000,
11
+ unlock: 500,
12
+ unlockReserved: 500,
13
+ unlockListedReserved: 250,
14
+ unlocksPerFile: o.policy.unlocksPerFile ?? 10,
15
+ unlocksPerSeller: o.policy.unlocksPerSeller ?? 20,
16
+ listedUnlocksPerFile: o.policy.listedUnlocksPerFile ?? 100,
17
+ listedUnlocksPerSeller: o.policy.listedUnlocksPerSeller ?? 250,
18
+ files: o.policy.sponsoredFilesPerDay ?? 60,
19
+ filesPerIp: 2,
20
+ registrationsPerSeller: o.policy.registrationsPerSeller ?? 2,
21
+ listedRegistrationsPerSeller: o.policy.listedRegistrationsPerSeller ?? 20,
22
+ ...o.admissionLimits,
23
+ });
24
+ const now = (o) => o.now ?? Math.floor(Date.now() / 1000);
25
+ export const temporary = (paid = false, retryAt = Date.now() + 30000) => {
26
+ const e = new RelayError(paid ? 'settlement_pending' : 'relayer_unavailable', 'Sponsorship is temporarily unavailable.');
27
+ e.retryAt = retryAt;
28
+ throw e;
29
+ };
30
+ export async function admitTerms(terms, o, tier, skipFee = false) {
31
+ const hash = termsHash(terms, {
32
+ baseUsdc: o.usdc,
33
+ ...(o.nearUsdc ? { nearUsdc: o.nearUsdc } : {}),
34
+ });
35
+ if (terms.network !== (o.network ?? 'near:mainnet') ||
36
+ terms.contract !== o.contractId ||
37
+ BigInt(terms.near.price) < o.policy.minPrice ||
38
+ (terms.base &&
39
+ (o.policy.baseSponsorship === false || BigInt(terms.base.price) < o.policy.minPrice)))
40
+ throw new RelayError('not_sponsored');
41
+ try {
42
+ if (!skipFee && o.policy.fee)
43
+ checkProtocolFee(terms, o.policy.fee);
44
+ }
45
+ catch {
46
+ throw new RelayError('not_sponsored');
47
+ }
48
+ if (terms.base) {
49
+ if (terms.near.recipients.some((r) => r.identity.kind !== 'evm'))
50
+ throw new RelayError('not_sponsored');
51
+ try {
52
+ await readSplitState(o.baseRpc, terms.base.pay_to, {
53
+ recipients: terms.near.recipients.map((r) => r.identity.id),
54
+ allocations: terms.near.recipients.map((r) => BigInt(r.bps)),
55
+ totalAllocation: 10000n,
56
+ distributionIncentive: 0,
57
+ });
58
+ }
59
+ catch (e) {
60
+ if (isTransient(e))
61
+ return temporary();
62
+ throw new RelayError('not_sponsored');
63
+ }
64
+ }
65
+ return {
66
+ network: terms.network,
67
+ contract: terms.contract,
68
+ fileId: terms.file_id,
69
+ termsHash: hash,
70
+ terms: structuredClone(terms),
71
+ eligible: true,
72
+ tier,
73
+ listed: tier === 'listed',
74
+ policyVersion: 'v1',
75
+ minPrice: String(o.policy.minPrice),
76
+ fee: structuredClone(skipFee ? ZERO_FEE : (o.policy.fee ?? ZERO_FEE)),
77
+ decidedAt: now(o),
78
+ allocated: null,
79
+ outcome: 'registered',
80
+ };
81
+ }
82
+ export async function checkedDecision(file, o, paid = false) {
83
+ const ledger = o.ledger;
84
+ if (!ledger)
85
+ return temporary(paid);
86
+ const existing = await ledger.decision(file.terms.file_id, file.termsHash);
87
+ if (existing) {
88
+ if (existing.outcome === 'prepared')
89
+ await ledger.registered(file.terms.file_id, file.termsHash);
90
+ return existing;
91
+ }
92
+ const cutover = await ledger.cutover();
93
+ if (!cutover)
94
+ return temporary(paid);
95
+ let historical;
96
+ try {
97
+ if (!o.historicalFile) {
98
+ o.alert?.('historical_unavailable');
99
+ return temporary(paid);
100
+ }
101
+ historical = await o.historicalFile(file.terms.file_id, cutover.blockHash);
102
+ }
103
+ catch {
104
+ o.alert?.('historical_unavailable');
105
+ return temporary(paid);
106
+ }
107
+ if (historical && historical.termsHash === file.termsHash) {
108
+ const inventory = cutover.inventory.includes(file.terms.file_id);
109
+ let decision;
110
+ try {
111
+ decision = await admitTerms(historical.terms, {
112
+ ...o,
113
+ policy: {
114
+ ...o.policy,
115
+ baseSponsorship: true,
116
+ minPrice: BigInt(cutover.minPrice),
117
+ fee: cutover.fee,
118
+ },
119
+ }, 'legacy', inventory);
120
+ }
121
+ catch (e) {
122
+ // Historical paid evidence cannot become a permanent refusal due to a missing policy input.
123
+ if (paid)
124
+ return temporary(true);
125
+ throw e;
126
+ }
127
+ decision.policyVersion = 'legacy-v0';
128
+ decision.listed = inventory;
129
+ return ledger.remember(decision);
130
+ }
131
+ if (o.policy.baseSponsorship === false || o.policy.openPoolEnabled === false)
132
+ return temporary(paid);
133
+ const decision = await admitTerms(file.terms, o, 'external');
134
+ return ledger.remember(decision);
135
+ }
136
+ export async function baseEligibility(fileId, hash, o, paid = false) {
137
+ try {
138
+ if (!paid && o.acceptingNewBasePayments === false)
139
+ return temporary();
140
+ const file = await getFile(o.nearRpc, o.contractId, fileId);
141
+ if (!file ||
142
+ file.termsHash !== hash ||
143
+ !file.terms.base ||
144
+ file.takenDown ||
145
+ (!paid && file.salesStoppedAt !== null))
146
+ throw new RelayError('not_sponsored');
147
+ const d = await checkedDecision(file, o, paid);
148
+ if (!paid && !(await o.ledger?.unlockAvailable(d, relayLimits(o), now(o) * 1000)))
149
+ return temporary(false, (Math.floor(now(o) / 86400) + 1) * 86400000);
150
+ return {
151
+ network: d.network,
152
+ contract: d.contract,
153
+ fileId,
154
+ termsHash: hash,
155
+ eligible: true,
156
+ tier: d.tier,
157
+ policyVersion: d.policyVersion,
158
+ minPrice: d.minPrice,
159
+ fee: d.fee,
160
+ decidedAt: d.decidedAt,
161
+ checkedAt: now(o),
162
+ };
163
+ }
164
+ catch (e) {
165
+ if (e instanceof RelayError && e.code === 'not_sponsored')
166
+ return {
167
+ network: o.network ?? 'near:mainnet',
168
+ contract: o.contractId,
169
+ fileId,
170
+ termsHash: hash,
171
+ eligible: false,
172
+ reason: 'not_sponsored',
173
+ tier: 'external',
174
+ policyVersion: 'v1',
175
+ minPrice: String(o.policy.minPrice),
176
+ fee: o.policy.fee ?? ZERO_FEE,
177
+ decidedAt: now(o),
178
+ checkedAt: now(o),
179
+ };
180
+ if (e instanceof RelayError)
181
+ throw e;
182
+ return temporary(paid);
183
+ }
184
+ }
@@ -0,0 +1,2 @@
1
+ /** Public workflow routes. Protocol Purchase.route retains its wire vocabulary. */
2
+ export type BuyRoute = 'near' | 'base' | '1click';
package/dist/routes.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,17 @@
1
+ /** Small bounded Borsh primitives; all integers here are little-endian. */
2
+ export declare const utf8: TextEncoder;
3
+ export declare const concat: (...parts: Uint8Array[]) => Uint8Array<ArrayBuffer>;
4
+ export declare const hex: (b: Uint8Array) => string;
5
+ export declare function bytes(s: string, length: number): Uint8Array<ArrayBuffer>;
6
+ export declare function uint(s: string | bigint, bits: number): bigint;
7
+ export declare function le(value: string | bigint | number, length: number): Uint8Array<ArrayBuffer>;
8
+ export declare const str: (s: string) => Uint8Array<ArrayBuffer>;
9
+ export declare const hash: (tag: string, data: Uint8Array) => string;
10
+ export declare function account(s: string): string;
11
+ export declare function network(s: string): string;
12
+ export declare function exactKeys(value: unknown, keys: string[]): asserts value is Record<string, unknown>;
13
+ export declare const compare: (a: Uint8Array, b: Uint8Array) => number;
14
+ /** USDC base units in authoritative human-readable text. */
15
+ export declare function usdc(units: string | bigint): string;
16
+ /** Rust text::utc, including every u64 timestamp (no JavaScript Date limit). */
17
+ export declare function utc(secs: number | bigint): string;
@@ -0,0 +1,86 @@
1
+ /** Small bounded Borsh primitives; all integers here are little-endian. */
2
+ import { sha256 } from '@noble/hashes/sha2.js';
3
+ import { concatParts, prefixedHex } from '../bytes.js';
4
+ export const utf8 = new TextEncoder();
5
+ export const concat = (...parts) => concatParts(parts);
6
+ export const hex = (b) => prefixedHex(b);
7
+ export function bytes(s, length) {
8
+ if (typeof s !== 'string' || !new RegExp(`^0x[0-9a-f]{${length * 2}}$`).test(s))
9
+ throw new TypeError(`expected canonical ${length}-byte hex`);
10
+ return Uint8Array.from(s.slice(2).match(/../g) ?? [], (x) => Number.parseInt(x, 16));
11
+ }
12
+ export function uint(s, bits) {
13
+ if (typeof s !== 'bigint' &&
14
+ (typeof s !== 'string' ||
15
+ s.length > Math.ceil(bits * Math.LOG10E * Math.LN2) ||
16
+ !/^(0|[1-9][0-9]*)$/.test(s)))
17
+ throw new TypeError('expected canonical unsigned integer');
18
+ const n = BigInt(s);
19
+ if (n < 0n || n >= 1n << BigInt(bits))
20
+ throw new RangeError(`expected u${bits}`);
21
+ return n;
22
+ }
23
+ export function le(value, length) {
24
+ if (typeof value === 'number' && !Number.isSafeInteger(value))
25
+ throw new RangeError('expected integer');
26
+ let n = uint(typeof value === 'number' ? BigInt(value) : value, length * 8);
27
+ return Uint8Array.from({ length }, () => {
28
+ const b = Number(n & 255n);
29
+ n >>= 8n;
30
+ return b;
31
+ });
32
+ }
33
+ export const str = (s) => {
34
+ const b = utf8.encode(s);
35
+ return concat(le(b.length, 4), b);
36
+ };
37
+ export const hash = (tag, data) => hex(sha256(concat(utf8.encode(tag), new Uint8Array([0]), data)));
38
+ export function account(s) {
39
+ if (typeof s !== 'string' || !/^(?=.{2,64}$)[a-z0-9]+(?:[._-][a-z0-9]+)*$/.test(s))
40
+ throw new TypeError('invalid NEAR account id');
41
+ return s;
42
+ }
43
+ export function network(s) {
44
+ if (!['near:mainnet', 'near:testnet', 'near:sandbox'].includes(s))
45
+ throw new TypeError('invalid NEAR network');
46
+ return s;
47
+ }
48
+ export function exactKeys(value, keys) {
49
+ if (value === null ||
50
+ typeof value !== 'object' ||
51
+ Array.isArray(value) ||
52
+ Object.keys(value).length !== keys.length ||
53
+ !keys.every((k) => Object.hasOwn(value, k)))
54
+ throw new TypeError('unexpected object fields');
55
+ }
56
+ export const compare = (a, b) => {
57
+ for (let i = 0; i < Math.min(a.length, b.length); i++) {
58
+ const d = a[i] - b[i];
59
+ if (d)
60
+ return d;
61
+ }
62
+ return a.length - b.length;
63
+ };
64
+ /** USDC base units in authoritative human-readable text. */
65
+ export function usdc(units) {
66
+ const u = uint(units, 128);
67
+ const part = u % 1000000n;
68
+ return `${u / 1000000n}${part ? `.${part.toString().padStart(6, '0').replace(/0+$/, '')}` : ''}`;
69
+ }
70
+ /** Rust text::utc, including every u64 timestamp (no JavaScript Date limit). */
71
+ export function utc(secs) {
72
+ if (typeof secs === 'number' && !Number.isSafeInteger(secs))
73
+ throw new RangeError('deadline out of range');
74
+ const n = uint(BigInt(secs), 64);
75
+ const z = n / 86400n + 719468n;
76
+ const era = z / 146097n;
77
+ const doe = z % 146097n;
78
+ const yoe = (doe - doe / 1460n + doe / 36524n - doe / 146096n) / 365n;
79
+ const doy = doe - (365n * yoe + yoe / 4n - yoe / 100n);
80
+ const mp = (5n * doy + 2n) / 153n;
81
+ const day = doy - (153n * mp + 2n) / 5n + 1n;
82
+ const month = mp < 10n ? mp + 3n : mp - 9n;
83
+ const year = era * 400n + yoe + (month <= 2n ? 1n : 0n);
84
+ const pad = (v) => v.toString().padStart(2, '0');
85
+ return `${year.toString().padStart(4, '0')}-${pad(month)}-${pad(day)} ${pad((n % 86400n) / 3600n)}:${pad((n / 60n) % 60n)}:${pad(n % 60n)} UTC`;
86
+ }
@@ -0,0 +1,27 @@
1
+ export type Identity = {
2
+ kind: 'evm';
3
+ id: string;
4
+ } | {
5
+ kind: 'solana';
6
+ id: string;
7
+ } | {
8
+ kind: 'near';
9
+ id: string;
10
+ };
11
+ export type Authorization = {
12
+ kind: 'evm' | 'solana';
13
+ sig: string;
14
+ };
15
+ export declare const identityKind: (id: Identity) => number;
16
+ /** Accept base58 Solana keys at the API boundary; store/serialize lowercase hex. */
17
+ export declare function parseIdentity(input: unknown): Identity;
18
+ export declare function encodeIdentity(input: Identity): Uint8Array<ArrayBuffer>;
19
+ export declare const identityJson: (id: Identity) => string;
20
+ export declare function identityText(input: Identity): string;
21
+ /** Malformed signatures and auth of a different kind always fail closed. */
22
+ export declare function verifyAuthorization(input: Identity, text: string, auth?: Authorization, predecessor?: string): boolean;
23
+ /** Wallet Standard results must carry the very same message, before accepting their signature. */
24
+ export declare function solanaWalletAuth(id: Identity, text: string, result: {
25
+ signature: Uint8Array;
26
+ signedMessage: Uint8Array;
27
+ }): Authorization;
@@ -0,0 +1,80 @@
1
+ /** Identity and authoritative-text authorization, near-settlement.md §2. */
2
+ import { ed25519 } from '@noble/curves/ed25519.js';
3
+ import { base58 } from '@scure/base';
4
+ import { signedBy } from '../envelope/evm.js';
5
+ import { AnnonError } from '../errors.js';
6
+ import { account, bytes, compare, concat, exactKeys, hex, str, utf8 } from './codec.js';
7
+ export const identityKind = (id) => ({ evm: 0, solana: 1, near: 2 })[id.kind];
8
+ /** Accept base58 Solana keys at the API boundary; store/serialize lowercase hex. */
9
+ export function parseIdentity(input) {
10
+ exactKeys(input, ['kind', 'id']);
11
+ const { kind, id } = input;
12
+ if (typeof id !== 'string')
13
+ throw new TypeError('invalid identity id');
14
+ if (kind === 'near')
15
+ return { kind, id: account(id) };
16
+ if (kind === 'evm') {
17
+ const normalized = id.toLowerCase();
18
+ bytes(normalized, 20);
19
+ return { kind, id: normalized };
20
+ }
21
+ if (kind === 'solana') {
22
+ let raw;
23
+ if (/^[a-fA-F0-9]{64}$/.test(id))
24
+ raw = bytes(`0x${id.toLowerCase()}`, 32);
25
+ else {
26
+ if (id.length < 32 || id.length > 44)
27
+ throw new TypeError('invalid Solana key');
28
+ raw = base58.decode(id);
29
+ if (raw.length !== 32 || base58.encode(raw) !== id)
30
+ throw new TypeError('invalid Solana key');
31
+ }
32
+ return { kind, id: hex(raw).slice(2) };
33
+ }
34
+ throw new TypeError('unknown identity kind');
35
+ }
36
+ export function encodeIdentity(input) {
37
+ const id = parseIdentity(input);
38
+ return concat(new Uint8Array([identityKind(id)]), id.kind === 'near'
39
+ ? str(id.id)
40
+ : bytes(id.kind === 'evm' ? id.id : `0x${id.id}`, id.kind === 'evm' ? 20 : 32));
41
+ }
42
+ export const identityJson = (id) => JSON.stringify(parseIdentity(id));
43
+ export function identityText(input) {
44
+ const id = parseIdentity(input);
45
+ return id.kind === 'near'
46
+ ? `NEAR ${id.id}`
47
+ : id.kind === 'evm'
48
+ ? `EVM ${id.id}`
49
+ : `Solana ${base58.encode(bytes(`0x${id.id}`, 32))}`;
50
+ }
51
+ /** Malformed signatures and auth of a different kind always fail closed. */
52
+ export function verifyAuthorization(input, text, auth, predecessor) {
53
+ try {
54
+ const id = parseIdentity(input);
55
+ if (typeof text !== 'string' || text.includes('\r') || text.endsWith('\n'))
56
+ return false;
57
+ if (id.kind === 'near')
58
+ return auth === undefined && predecessor === id.id;
59
+ exactKeys(auth, ['kind', 'sig']);
60
+ if (auth.kind !== id.kind)
61
+ return false;
62
+ const sig = bytes(auth.sig, id.kind === 'evm' ? 65 : 64);
63
+ return id.kind === 'evm'
64
+ ? signedBy(text, sig, id.id)
65
+ : ed25519.verify(sig, utf8.encode(text), bytes(`0x${id.id}`, 32), { zip215: false });
66
+ }
67
+ catch {
68
+ return false;
69
+ }
70
+ }
71
+ /** Wallet Standard results must carry the very same message, before accepting their signature. */
72
+ export function solanaWalletAuth(id, text, result) {
73
+ if (!(result.signedMessage instanceof Uint8Array) ||
74
+ compare(result.signedMessage, utf8.encode(text)) !== 0)
75
+ throw new AnnonError('invalid_signature', 'wallet returned different signed bytes');
76
+ const auth = { kind: 'solana', sig: hex(result.signature) };
77
+ if (id.kind !== 'solana' || !verifyAuthorization(id, text, auth))
78
+ throw new AnnonError('invalid_signature', 'the signature is not from the seller');
79
+ return auth;
80
+ }
@@ -0,0 +1,40 @@
1
+ import { type Identity } from './identity.js';
2
+ import { type SettlementAssets } from './terms.js';
3
+ export interface Purchase {
4
+ version: 2;
5
+ refund: Identity;
6
+ network: string;
7
+ contract: string;
8
+ route: 'near-usdc' | 'base-usdc';
9
+ asset: string;
10
+ file_id: string;
11
+ terms_hash: string;
12
+ price: string;
13
+ nonce: string;
14
+ buyer_key: string;
15
+ recovery: Identity;
16
+ expires_at: string;
17
+ }
18
+ export declare function buyerKey(key: string): Uint8Array<ArrayBuffer>;
19
+ export declare function encodePurchase(p: Purchase, assets?: SettlementAssets): Uint8Array<ArrayBuffer>;
20
+ export declare const purchaseId: (p: Purchase, assets?: SettlementAssets) => string;
21
+ export declare const baseNonceV2: (id: string) => string;
22
+ /** The API accepts normalized identities; the wire always has one canonical encoding. */
23
+ export declare function buildPurchaseMessage(p: Purchase, assets?: SettlementAssets): string;
24
+ /** Reads only the first 160 bytes, independently of an invalid/oversized purchase body. */
25
+ export declare function parseRefundPrefix(msg: string): Identity;
26
+ /**
27
+ * Bounds work before JSON.parse, then compares with the canonical serializer. This also rejects
28
+ * duplicate/unknown fields at every level, alternate escapes, key order and whitespace.
29
+ */
30
+ export declare function parsePurchaseMessage(msg: string, assets?: SettlementAssets): Purchase;
31
+ export interface ReopenV2 {
32
+ contract: string;
33
+ purchase_id: string;
34
+ file_id: string;
35
+ recovery: Identity;
36
+ fresh_key: string;
37
+ nonce: string;
38
+ deadline: number;
39
+ }
40
+ export declare function reopenTextV2(a: ReopenV2): string;
@@ -0,0 +1,153 @@
1
+ /** Purchase commitment, canonical wire message and recovery text, spec §§5,9. */
2
+ import { bls12_381 as bls } from '@noble/curves/bls12-381.js';
3
+ import { keccak_256 } from '@noble/hashes/sha3.js';
4
+ import { account, bytes, concat, exactKeys, hash, hex, le, network, str, uint, utc, utf8, } from './codec.js';
5
+ import { encodeIdentity, identityJson, identityText, parseIdentity, } from './identity.js';
6
+ import { BASE_USDC, NEAR_USDC } from './terms.js';
7
+ export function buyerKey(key) {
8
+ const raw = bytes(key, 48);
9
+ const point = bls.G1.Point.fromBytes(raw);
10
+ if (point.is0() || !point.isTorsionFree() || hex(point.toBytes(true)) !== key)
11
+ throw new TypeError('invalid buyer key');
12
+ return raw;
13
+ }
14
+ export function encodePurchase(p, assets = {}) {
15
+ exactKeys(p, [
16
+ 'version',
17
+ 'refund',
18
+ 'network',
19
+ 'contract',
20
+ 'route',
21
+ 'asset',
22
+ 'file_id',
23
+ 'terms_hash',
24
+ 'price',
25
+ 'nonce',
26
+ 'buyer_key',
27
+ 'recovery',
28
+ 'expires_at',
29
+ ]);
30
+ if (p.version !== 2 || (p.route !== 'near-usdc' && p.route !== 'base-usdc'))
31
+ throw new TypeError('invalid purchase version/route');
32
+ if (p.route === 'near-usdc') {
33
+ network(p.network);
34
+ account(p.asset);
35
+ if ((assets.nearUsdc !== undefined || p.network === 'near:mainnet') &&
36
+ p.asset !== (assets.nearUsdc ?? NEAR_USDC))
37
+ throw new TypeError('invalid NEAR asset');
38
+ }
39
+ else {
40
+ if (p.network !== 'eip155:8453' || p.asset !== (assets.baseUsdc ?? BASE_USDC))
41
+ throw new TypeError('invalid Base network/asset');
42
+ bytes(p.asset, 20);
43
+ }
44
+ account(p.contract);
45
+ const amount = uint(p.price, 128);
46
+ if (amount === 0n)
47
+ throw new RangeError('price must be positive');
48
+ return concat(le(2, 1), encodeIdentity(p.refund), str(p.network), str(p.contract), le(p.route === 'near-usdc' ? 0 : 1, 1), str(p.asset), bytes(p.file_id, 32), bytes(p.terms_hash, 32), le(amount, 16), bytes(p.nonce, 32), buyerKey(p.buyer_key), encodeIdentity(p.recovery), le(uint(p.expires_at, 64), 8));
49
+ }
50
+ export const purchaseId = (p, assets) => hash('annon/purchase/v2', encodePurchase(p, assets));
51
+ export const baseNonceV2 = (id) => hex(keccak_256(concat(utf8.encode('annon/base-payment/v2'), le(0, 1), bytes(id, 32))));
52
+ /** The API accepts normalized identities; the wire always has one canonical encoding. */
53
+ export function buildPurchaseMessage(p, assets) {
54
+ const id = purchaseId(p, assets);
55
+ const msg = JSON.stringify({
56
+ annon: 2,
57
+ refund: parseIdentity(p.refund),
58
+ purchase: {
59
+ network: p.network,
60
+ contract: p.contract,
61
+ route: p.route,
62
+ asset: p.asset,
63
+ file_id: p.file_id,
64
+ terms_hash: p.terms_hash,
65
+ price: p.price,
66
+ nonce: p.nonce,
67
+ buyer_key: p.buyer_key,
68
+ recovery: parseIdentity(p.recovery),
69
+ expires_at: p.expires_at,
70
+ purchase_id: id,
71
+ },
72
+ });
73
+ if (utf8.encode(msg).length > 1024)
74
+ throw new RangeError('purchase message exceeds 1024 UTF-8 bytes');
75
+ return msg;
76
+ }
77
+ /** Reads only the first 160 bytes, independently of an invalid/oversized purchase body. */
78
+ export function parseRefundPrefix(msg) {
79
+ if (typeof msg !== 'string')
80
+ throw new TypeError('expected message string');
81
+ const prefix = msg.slice(0, 160);
82
+ const match = /^\{"annon":2,"refund":(\{"kind":"(?:evm|solana|near)","id":"[a-zA-Z0-9._-]+"\}),/.exec(prefix);
83
+ if (!match)
84
+ throw new TypeError('invalid refund prefix');
85
+ const encoded = match[1];
86
+ const id = parseIdentity(JSON.parse(encoded));
87
+ if (identityJson(id) !== encoded || utf8.encode(match[0]).length > 160)
88
+ throw new TypeError('noncanonical refund prefix');
89
+ return id;
90
+ }
91
+ /**
92
+ * Bounds work before JSON.parse, then compares with the canonical serializer. This also rejects
93
+ * duplicate/unknown fields at every level, alternate escapes, key order and whitespace.
94
+ */
95
+ export function parsePurchaseMessage(msg, assets) {
96
+ if (typeof msg !== 'string' || msg.length > 1024 || utf8.encode(msg).length > 1024)
97
+ throw new RangeError('purchase message exceeds 1024 UTF-8 bytes');
98
+ const refund = parseRefundPrefix(msg);
99
+ const obj = JSON.parse(msg);
100
+ exactKeys(obj, ['annon', 'refund', 'purchase']);
101
+ if (obj.annon !== 2)
102
+ throw new TypeError('invalid annon version');
103
+ const p = obj.purchase;
104
+ exactKeys(p, [
105
+ 'network',
106
+ 'contract',
107
+ 'route',
108
+ 'asset',
109
+ 'file_id',
110
+ 'terms_hash',
111
+ 'price',
112
+ 'nonce',
113
+ 'buyer_key',
114
+ 'recovery',
115
+ 'expires_at',
116
+ 'purchase_id',
117
+ ]);
118
+ const decoded = {
119
+ version: 2,
120
+ refund,
121
+ network: p.network,
122
+ contract: p.contract,
123
+ route: p.route,
124
+ asset: p.asset,
125
+ file_id: p.file_id,
126
+ terms_hash: p.terms_hash,
127
+ price: p.price,
128
+ nonce: p.nonce,
129
+ buyer_key: p.buyer_key,
130
+ recovery: parseIdentity(p.recovery),
131
+ expires_at: p.expires_at,
132
+ };
133
+ if (buildPurchaseMessage(decoded, assets) !== msg)
134
+ throw new TypeError('noncanonical purchase message or incorrect purchase_id');
135
+ return decoded;
136
+ }
137
+ export function reopenTextV2(a) {
138
+ account(a.contract);
139
+ bytes(a.purchase_id, 32);
140
+ bytes(a.file_id, 32);
141
+ buyerKey(a.fresh_key);
142
+ bytes(a.nonce, 32);
143
+ return [
144
+ 'Annon: open a purchased file again',
145
+ `contract: ${a.contract}`,
146
+ `purchase: ${a.purchase_id}`,
147
+ `file: ${a.file_id}`,
148
+ `recovery: ${identityText(a.recovery)}`,
149
+ `new key: ${a.fresh_key}`,
150
+ `nonce: ${a.nonce}`,
151
+ `valid until: ${utc(a.deadline)}`,
152
+ ].join('\n');
153
+ }
@@ -0,0 +1,37 @@
1
+ import { type Identity } from './identity.js';
2
+ export declare const BASE_USDC = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913";
3
+ export declare const NEAR_USDC = "17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1";
4
+ export interface NearTerms {
5
+ token: string;
6
+ price: string;
7
+ recipients: {
8
+ identity: Identity;
9
+ bps: number;
10
+ }[];
11
+ remainder: number;
12
+ }
13
+ export interface BaseTerms {
14
+ usdc: string;
15
+ price: string;
16
+ pay_to: string;
17
+ }
18
+ export interface Terms {
19
+ network: string;
20
+ contract: string;
21
+ file_id: string;
22
+ seller: Identity;
23
+ pay2_version: 2;
24
+ cipher_hash: string;
25
+ moderator: string | null;
26
+ near: NearTerms;
27
+ base: BaseTerms | null;
28
+ }
29
+ /** Expected deployed assets; non-mainnet NEAR tokens have no implicit default. */
30
+ export interface SettlementAssets {
31
+ nearUsdc?: string;
32
+ baseUsdc?: string;
33
+ }
34
+ export declare function encodeTerms(t: Terms, assets?: SettlementAssets): Uint8Array<ArrayBuffer>;
35
+ export declare const termsHash: (t: Terms, assets?: SettlementAssets) => string;
36
+ export declare function registerTextV2(t: Terms, sellerKey: string, deadline: number, assets?: SettlementAssets): string;
37
+ export declare function publishTextV2(contract: string, fileId: string, headerHash: string): string;