@avow-finance/sdk 0.0.0-stage → 0.2.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 (267) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +122 -2
  3. package/dist/agent-entry.d.ts +15 -0
  4. package/dist/agent-entry.d.ts.map +1 -0
  5. package/dist/agent-entry.js +11 -0
  6. package/dist/agent-entry.js.map +1 -0
  7. package/dist/agent-key.d.ts +34 -0
  8. package/dist/agent-key.d.ts.map +1 -0
  9. package/dist/agent-key.js +33 -0
  10. package/dist/agent-key.js.map +1 -0
  11. package/dist/agent.d.ts +261 -0
  12. package/dist/agent.d.ts.map +1 -0
  13. package/dist/agent.js +493 -0
  14. package/dist/agent.js.map +1 -0
  15. package/dist/avow-api.d.ts +78 -0
  16. package/dist/avow-api.d.ts.map +1 -0
  17. package/dist/avow-api.js +2 -0
  18. package/dist/avow-api.js.map +1 -0
  19. package/dist/contest.d.ts +135 -0
  20. package/dist/contest.d.ts.map +1 -0
  21. package/dist/contest.js +186 -0
  22. package/dist/contest.js.map +1 -0
  23. package/dist/http.d.ts +161 -0
  24. package/dist/http.d.ts.map +1 -0
  25. package/dist/http.js +311 -0
  26. package/dist/http.js.map +1 -0
  27. package/dist/index.d.ts +38 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +38 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/job/accept.d.ts +84 -0
  32. package/dist/job/accept.d.ts.map +1 -0
  33. package/dist/job/accept.js +136 -0
  34. package/dist/job/accept.js.map +1 -0
  35. package/dist/job/customer.d.ts +43 -0
  36. package/dist/job/customer.d.ts.map +1 -0
  37. package/dist/job/customer.js +83 -0
  38. package/dist/job/customer.js.map +1 -0
  39. package/dist/job/quote.d.ts +34 -0
  40. package/dist/job/quote.d.ts.map +1 -0
  41. package/dist/job/quote.js +37 -0
  42. package/dist/job/quote.js.map +1 -0
  43. package/dist/lock.d.ts +269 -0
  44. package/dist/lock.d.ts.map +1 -0
  45. package/dist/lock.js +283 -0
  46. package/dist/lock.js.map +1 -0
  47. package/dist/moderato.d.ts +27 -0
  48. package/dist/moderato.d.ts.map +1 -0
  49. package/dist/moderato.js +34 -0
  50. package/dist/moderato.js.map +1 -0
  51. package/dist/node/cli.d.ts +3 -0
  52. package/dist/node/cli.d.ts.map +1 -0
  53. package/dist/node/cli.js +346 -0
  54. package/dist/node/cli.js.map +1 -0
  55. package/dist/node/connect.d.ts +15 -0
  56. package/dist/node/connect.d.ts.map +1 -0
  57. package/dist/node/connect.js +91 -0
  58. package/dist/node/connect.js.map +1 -0
  59. package/dist/node/eval.d.ts +3 -0
  60. package/dist/node/eval.d.ts.map +1 -0
  61. package/dist/node/eval.js +67 -0
  62. package/dist/node/eval.js.map +1 -0
  63. package/dist/node/first-run.d.ts +50 -0
  64. package/dist/node/first-run.d.ts.map +1 -0
  65. package/dist/node/first-run.js +149 -0
  66. package/dist/node/first-run.js.map +1 -0
  67. package/dist/node/job.d.ts +50 -0
  68. package/dist/node/job.d.ts.map +1 -0
  69. package/dist/node/job.js +176 -0
  70. package/dist/node/job.js.map +1 -0
  71. package/dist/node/mailbox.d.ts +19 -0
  72. package/dist/node/mailbox.d.ts.map +1 -0
  73. package/dist/node/mailbox.js +81 -0
  74. package/dist/node/mailbox.js.map +1 -0
  75. package/dist/node/mcp.d.ts +74 -0
  76. package/dist/node/mcp.d.ts.map +1 -0
  77. package/dist/node/mcp.js +367 -0
  78. package/dist/node/mcp.js.map +1 -0
  79. package/dist/node/platform.d.ts +35 -0
  80. package/dist/node/platform.d.ts.map +1 -0
  81. package/dist/node/platform.js +102 -0
  82. package/dist/node/platform.js.map +1 -0
  83. package/dist/node/runner-session.d.ts +41 -0
  84. package/dist/node/runner-session.d.ts.map +1 -0
  85. package/dist/node/runner-session.js +100 -0
  86. package/dist/node/runner-session.js.map +1 -0
  87. package/dist/node/runner.d.ts +63 -0
  88. package/dist/node/runner.d.ts.map +1 -0
  89. package/dist/node/runner.js +341 -0
  90. package/dist/node/runner.js.map +1 -0
  91. package/dist/node/service-child.d.ts +2 -0
  92. package/dist/node/service-child.d.ts.map +1 -0
  93. package/dist/node/service-child.js +40 -0
  94. package/dist/node/service-child.js.map +1 -0
  95. package/dist/node/service-excerpt.d.ts +21 -0
  96. package/dist/node/service-excerpt.d.ts.map +1 -0
  97. package/dist/node/service-excerpt.js +32 -0
  98. package/dist/node/service-excerpt.js.map +1 -0
  99. package/dist/node/service-process.d.ts +12 -0
  100. package/dist/node/service-process.d.ts.map +1 -0
  101. package/dist/node/service-process.js +132 -0
  102. package/dist/node/service-process.js.map +1 -0
  103. package/dist/node/session-input.d.ts +12 -0
  104. package/dist/node/session-input.d.ts.map +1 -0
  105. package/dist/node/session-input.js +42 -0
  106. package/dist/node/session-input.js.map +1 -0
  107. package/dist/node/session-view.d.ts +20 -0
  108. package/dist/node/session-view.d.ts.map +1 -0
  109. package/dist/node/session-view.js +198 -0
  110. package/dist/node/session-view.js.map +1 -0
  111. package/dist/node-agent.d.ts +2 -0
  112. package/dist/node-agent.d.ts.map +1 -0
  113. package/dist/node-agent.js +6 -0
  114. package/dist/node-agent.js.map +1 -0
  115. package/dist/node.d.ts +2 -0
  116. package/dist/node.d.ts.map +1 -0
  117. package/dist/node.js +9 -0
  118. package/dist/node.js.map +1 -0
  119. package/dist/sandbox/agent.d.ts +187 -0
  120. package/dist/sandbox/agent.d.ts.map +1 -0
  121. package/dist/sandbox/agent.js +468 -0
  122. package/dist/sandbox/agent.js.map +1 -0
  123. package/dist/sandbox/business-store.d.ts +19 -0
  124. package/dist/sandbox/business-store.d.ts.map +1 -0
  125. package/dist/sandbox/business-store.js +19 -0
  126. package/dist/sandbox/business-store.js.map +1 -0
  127. package/dist/sandbox/business.d.ts +70 -0
  128. package/dist/sandbox/business.d.ts.map +1 -0
  129. package/dist/sandbox/business.js +115 -0
  130. package/dist/sandbox/business.js.map +1 -0
  131. package/dist/sandbox/connect.d.ts +64 -0
  132. package/dist/sandbox/connect.d.ts.map +1 -0
  133. package/dist/sandbox/connect.js +182 -0
  134. package/dist/sandbox/connect.js.map +1 -0
  135. package/dist/sandbox/deployment.d.ts +14 -0
  136. package/dist/sandbox/deployment.d.ts.map +1 -0
  137. package/dist/sandbox/deployment.js +38 -0
  138. package/dist/sandbox/deployment.js.map +1 -0
  139. package/dist/sandbox/keys.d.ts +45 -0
  140. package/dist/sandbox/keys.d.ts.map +1 -0
  141. package/dist/sandbox/keys.js +132 -0
  142. package/dist/sandbox/keys.js.map +1 -0
  143. package/dist/sandbox/mailbox.d.ts +90 -0
  144. package/dist/sandbox/mailbox.d.ts.map +1 -0
  145. package/dist/sandbox/mailbox.js +400 -0
  146. package/dist/sandbox/mailbox.js.map +1 -0
  147. package/dist/sandbox/mandate.d.ts +84 -0
  148. package/dist/sandbox/mandate.d.ts.map +1 -0
  149. package/dist/sandbox/mandate.js +107 -0
  150. package/dist/sandbox/mandate.js.map +1 -0
  151. package/dist/sandbox/namespace.d.ts +16 -0
  152. package/dist/sandbox/namespace.d.ts.map +1 -0
  153. package/dist/sandbox/namespace.js +16 -0
  154. package/dist/sandbox/namespace.js.map +1 -0
  155. package/dist/sandbox/session.d.ts +60 -0
  156. package/dist/sandbox/session.d.ts.map +1 -0
  157. package/dist/sandbox/session.js +94 -0
  158. package/dist/sandbox/session.js.map +1 -0
  159. package/dist/sandbox/setup.d.ts +136 -0
  160. package/dist/sandbox/setup.d.ts.map +1 -0
  161. package/dist/sandbox/setup.js +490 -0
  162. package/dist/sandbox/setup.js.map +1 -0
  163. package/dist/sandbox/store.d.ts +69 -0
  164. package/dist/sandbox/store.d.ts.map +1 -0
  165. package/dist/sandbox/store.js +115 -0
  166. package/dist/sandbox/store.js.map +1 -0
  167. package/dist/sandbox/supplier-key.d.ts +73 -0
  168. package/dist/sandbox/supplier-key.d.ts.map +1 -0
  169. package/dist/sandbox/supplier-key.js +180 -0
  170. package/dist/sandbox/supplier-key.js.map +1 -0
  171. package/dist/sandbox/suppliers.d.ts +7 -0
  172. package/dist/sandbox/suppliers.d.ts.map +1 -0
  173. package/dist/sandbox/suppliers.js +29 -0
  174. package/dist/sandbox/suppliers.js.map +1 -0
  175. package/dist/sandbox/terms.d.ts +32 -0
  176. package/dist/sandbox/terms.d.ts.map +1 -0
  177. package/dist/sandbox/terms.js +58 -0
  178. package/dist/sandbox/terms.js.map +1 -0
  179. package/dist/sandbox/vault.d.ts +24 -0
  180. package/dist/sandbox/vault.d.ts.map +1 -0
  181. package/dist/sandbox/vault.js +54 -0
  182. package/dist/sandbox/vault.js.map +1 -0
  183. package/dist/send.d.ts +122 -0
  184. package/dist/send.d.ts.map +1 -0
  185. package/dist/send.js +328 -0
  186. package/dist/send.js.map +1 -0
  187. package/dist/service-entry.d.ts +6 -0
  188. package/dist/service-entry.d.ts.map +1 -0
  189. package/dist/service-entry.js +6 -0
  190. package/dist/service-entry.js.map +1 -0
  191. package/dist/session/anthropic.d.ts +17 -0
  192. package/dist/session/anthropic.d.ts.map +1 -0
  193. package/dist/session/anthropic.js +215 -0
  194. package/dist/session/anthropic.js.map +1 -0
  195. package/dist/session/charge.d.ts +13 -0
  196. package/dist/session/charge.d.ts.map +1 -0
  197. package/dist/session/charge.js +33 -0
  198. package/dist/session/charge.js.map +1 -0
  199. package/dist/session/data.d.ts +8 -0
  200. package/dist/session/data.d.ts.map +1 -0
  201. package/dist/session/data.js +20 -0
  202. package/dist/session/data.js.map +1 -0
  203. package/dist/session/eval.d.ts +41 -0
  204. package/dist/session/eval.d.ts.map +1 -0
  205. package/dist/session/eval.js +281 -0
  206. package/dist/session/eval.js.map +1 -0
  207. package/dist/session/executor.d.ts +80 -0
  208. package/dist/session/executor.d.ts.map +1 -0
  209. package/dist/session/executor.js +381 -0
  210. package/dist/session/executor.js.map +1 -0
  211. package/dist/session/fixture-world.d.ts +29 -0
  212. package/dist/session/fixture-world.d.ts.map +1 -0
  213. package/dist/session/fixture-world.js +708 -0
  214. package/dist/session/fixture-world.js.map +1 -0
  215. package/dist/session/live-world.d.ts +22 -0
  216. package/dist/session/live-world.d.ts.map +1 -0
  217. package/dist/session/live-world.js +329 -0
  218. package/dist/session/live-world.js.map +1 -0
  219. package/dist/session/ollama.d.ts +29 -0
  220. package/dist/session/ollama.d.ts.map +1 -0
  221. package/dist/session/ollama.js +177 -0
  222. package/dist/session/ollama.js.map +1 -0
  223. package/dist/session/provider.d.ts +79 -0
  224. package/dist/session/provider.d.ts.map +1 -0
  225. package/dist/session/provider.js +19 -0
  226. package/dist/session/provider.js.map +1 -0
  227. package/dist/session/scripted.d.ts +45 -0
  228. package/dist/session/scripted.d.ts.map +1 -0
  229. package/dist/session/scripted.js +236 -0
  230. package/dist/session/scripted.js.map +1 -0
  231. package/dist/session/selection.d.ts +37 -0
  232. package/dist/session/selection.d.ts.map +1 -0
  233. package/dist/session/selection.js +104 -0
  234. package/dist/session/selection.js.map +1 -0
  235. package/dist/session/service-check.d.ts +48 -0
  236. package/dist/session/service-check.d.ts.map +1 -0
  237. package/dist/session/service-check.js +86 -0
  238. package/dist/session/service-check.js.map +1 -0
  239. package/dist/session/service.d.ts +128 -0
  240. package/dist/session/service.d.ts.map +1 -0
  241. package/dist/session/service.js +150 -0
  242. package/dist/session/service.js.map +1 -0
  243. package/dist/session/session.d.ts +95 -0
  244. package/dist/session/session.d.ts.map +1 -0
  245. package/dist/session/session.js +312 -0
  246. package/dist/session/session.js.map +1 -0
  247. package/dist/session/toolbox.d.ts +69 -0
  248. package/dist/session/toolbox.d.ts.map +1 -0
  249. package/dist/session/toolbox.js +391 -0
  250. package/dist/session/toolbox.js.map +1 -0
  251. package/dist/session/tools.d.ts +45 -0
  252. package/dist/session/tools.d.ts.map +1 -0
  253. package/dist/session/tools.js +137 -0
  254. package/dist/session/tools.js.map +1 -0
  255. package/dist/session/world.d.ts +188 -0
  256. package/dist/session/world.d.ts.map +1 -0
  257. package/dist/session/world.js +2 -0
  258. package/dist/session/world.js.map +1 -0
  259. package/dist/sign.d.ts +19 -0
  260. package/dist/sign.d.ts.map +1 -0
  261. package/dist/sign.js +33 -0
  262. package/dist/sign.js.map +1 -0
  263. package/dist/supplier.d.ts +41 -0
  264. package/dist/supplier.d.ts.map +1 -0
  265. package/dist/supplier.js +57 -0
  266. package/dist/supplier.js.map +1 -0
  267. package/package.json +56 -3
@@ -0,0 +1,708 @@
1
+ import { approvalDigest, checkMandate, computeOfferCost, computeOfferTerms, DEMO_GPU_SUPPLIER, DEMO_JOB_TIMING, demoComputeOffers, dollars, GPU_RESERVATION, isChainedTerms, lockTerms, mandateHash, newSalt, TEST_CUSTOMER_LABEL, TESTNET_DEMO_PROFILE, termsHash, testnetDemoJobTerms, } from '@avow-finance/spec';
2
+ import { verifyDependencies, } from '@avow-finance/verifier';
3
+ import { isAddressEqual, keccak256, stringToHex } from 'viem';
4
+ import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts';
5
+ import { quoteJob } from '../job/quote.js';
6
+ import { sandboxMandate } from '../sandbox/mandate.js';
7
+ import { SendError } from '../send.js';
8
+ import { chargeStanding } from './charge.js';
9
+ /**
10
+ * The fixture world: the sandbox in memory, for tests and the eval. Its customer verifies a quote
11
+ * with the real `verifyDependencies`, its Avow approves a booking with the real `checkMandate`, and
12
+ * its chain keeps every lock's records with blocks that advance with its clock, which only runs when
13
+ * the session waits. Its keys are made for each run. It never stands for testnet, and whatever
14
+ * draws it says it's the fixture world.
15
+ */
16
+ const CHAIN_ID = 42431;
17
+ const FACTORY = '0x00000000000000000000000000000000000fac01';
18
+ const ARBITER = '0x000000000000000000000000000000000000a4b1';
19
+ const TOKEN = {
20
+ rail: 'tempo',
21
+ chainId: CHAIN_ID,
22
+ token: '0x20c0000000000000000000000000000000000001',
23
+ currency: 'USD',
24
+ decimals: 6,
25
+ };
26
+ /** Where the fixture's clock starts, in unix seconds, so every run reads the same. */
27
+ export const FIXTURE_START = 1800000000n;
28
+ /**
29
+ * When Avow records a job's cancellation: from its start, when free cancellation ends, until its
30
+ * no-show charge can be reported.
31
+ */
32
+ function cancellationWindow(offer) {
33
+ const r = offer.terms.remedies;
34
+ const noShow = r.charges.find((c) => c.kind === 'noShow');
35
+ return { opens: r.freeCancelBy, closes: noShow?.earliest ?? r.chargeDeadline };
36
+ }
37
+ const key = (lock, commitmentId, side) => `${lock.toLowerCase()}:${commitmentId.toLowerCase()}:${side}`;
38
+ const bytes32 = (label) => keccak256(stringToHex(label));
39
+ const orderIdOf = (n) => keccak256(stringToHex(`order ${n}`)).slice(2, 34);
40
+ /** How long Avow has to answer a report in the fixture world, in seconds. */
41
+ export const FIXTURE_CONFIRM_WINDOW = 600n;
42
+ /** How long after a report the fixture world's Avow answers it, in seconds. */
43
+ const AVOW_ANSWERS_AFTER = 6n;
44
+ /**
45
+ * How far a cancellation has to sit from the report and from each end of the time Avow records
46
+ * one in, in seconds, before the fixture world's Avow confirms a charge from it. Closer than that,
47
+ * Avow's clock and the chain's could disagree about the order, so the report is referred.
48
+ */
49
+ const AVOW_CLOCK_MARGIN = 30n;
50
+ export function fixtureWorld(options = {}) {
51
+ let now = FIXTURE_START;
52
+ let block = 1000n;
53
+ const head = () => ({
54
+ number: block,
55
+ hash: bytes32(`block ${block}`),
56
+ timestamp: now,
57
+ });
58
+ const records = new Map();
59
+ const locks = new Map();
60
+ const rootKey = generatePrivateKey();
61
+ const root = privateKeyToAccount(rootKey);
62
+ const agentKey = generatePrivateKey();
63
+ const agent = privateKeyToAccount(agentKey);
64
+ const signingSecret = generatePrivateKey();
65
+ const signing = privateKeyToAccount(signingSecret);
66
+ const customerRootKey = generatePrivateKey();
67
+ const customerRoot = privateKeyToAccount(customerRootKey);
68
+ const customerAgent = privateKeyToAccount(generatePrivateKey()).address;
69
+ const gpuCloud = privateKeyToAccount(generatePrivateKey()).address;
70
+ const businessLock = '0x00000000000000000000000000000000000010c6';
71
+ const customerLock = '0x00000000000000000000000000000000000010c4';
72
+ locks.set(businessLock.toLowerCase(), { account: root.address });
73
+ locks.set(customerLock.toLowerCase(), { account: customerRoot.address });
74
+ const authority = dollars(options.authority ?? 500);
75
+ const mandate = sandboxMandate({
76
+ businessId: 'fixture-business',
77
+ agentId: 'agt_fixture',
78
+ account: root.address,
79
+ agentKey: agent.address,
80
+ suppliers: [DEMO_GPU_SUPPLIER],
81
+ token: TOKEN.token,
82
+ capPerCommitment: authority,
83
+ capOpenReserves: authority,
84
+ expiry: FIXTURE_START + 86400n,
85
+ });
86
+ const businessMandate = mandateHash(mandate, {
87
+ chainId: CHAIN_ID,
88
+ verifyingContract: businessLock,
89
+ });
90
+ const customer = {
91
+ label: TEST_CUSTOMER_LABEL,
92
+ businessId: 'fixture-test-customer',
93
+ account: customerRoot.address,
94
+ lock: customerLock,
95
+ chainId: CHAIN_ID,
96
+ mandateHash: bytes32('the fixture customer mandate'),
97
+ limits: { reserve: dollars(25), price: dollars(100), openOrders: 2 },
98
+ };
99
+ const supplier = { account: root.address, signingKey: signing.address, payee: root.address };
100
+ const signingKey = {
101
+ address: signing.address,
102
+ signDigest: (digest) => signing.sign({ hash: digest }),
103
+ };
104
+ const orders = new Map();
105
+ const bookings = [];
106
+ /** When each report landed, by the commitment it's on. */
107
+ const reports = new Map();
108
+ let acceptFails = 0;
109
+ const acceptances = [];
110
+ let orderCount = 0;
111
+ let txCount = 0;
112
+ const tx = (label) => bytes32(`${label} ${++txCount}`);
113
+ const reader = {
114
+ chainId: CHAIN_ID,
115
+ factory: FACTORY,
116
+ head: async () => head(),
117
+ blockHash: async (n) => (n <= block ? bytes32(`block ${n}`) : null),
118
+ isLock: async (lock) => locks.has(lock.toLowerCase()),
119
+ lockFields: async (lock) => {
120
+ const found = locks.get(lock.toLowerCase());
121
+ if (!found)
122
+ throw new Error(`No contract at ${lock}`);
123
+ return { factory: FACTORY, account: found.account };
124
+ },
125
+ commitment: async (lock, commitmentId, side) => {
126
+ const r = records.get(key(lock, commitmentId, side));
127
+ return r
128
+ ? {
129
+ state: r.state,
130
+ hasReport: r.hasReport,
131
+ termsHash: r.termsHash,
132
+ supplier: r.supplier,
133
+ freeCancelBy: r.freeCancelBy,
134
+ }
135
+ : null;
136
+ },
137
+ };
138
+ /** What a lock records for a reserve funded under an approval built from the terms. */
139
+ function record(a, state) {
140
+ return {
141
+ state,
142
+ token: a.token,
143
+ amount: a.amount,
144
+ held: a.amount,
145
+ payee: a.payee,
146
+ supplier: a.supplier,
147
+ supplierSigner: a.supplierSigner,
148
+ freeCancelBy: a.freeCancelBy,
149
+ acceptBy: a.acceptBy,
150
+ chargeDeadline: a.chargeDeadline,
151
+ contestWindow: a.contestWindow,
152
+ releaseDate: a.releaseDate,
153
+ charges: a.charges,
154
+ day90SupplierShareBps: a.day90SupplierShareBps,
155
+ arbiter: a.arbiter,
156
+ termsHash: a.termsHash,
157
+ mandateHash: a.mandateHash,
158
+ approvalHash: approvalDigest(a, { chainId: CHAIN_ID, verifyingContract: customerLock }),
159
+ fundingKey: a.fundingKey,
160
+ reportCount: 0,
161
+ reportKind: null,
162
+ reportAmount: 0n,
163
+ hasReport: false,
164
+ contested: false,
165
+ chargeStart: 0n,
166
+ pausedAt: 0n,
167
+ hasRuling: false,
168
+ };
169
+ }
170
+ function approval(commitmentId, terms, salt, parties, funding) {
171
+ return {
172
+ commitmentId,
173
+ token: TOKEN.token,
174
+ ...lockTerms(terms, TOKEN.decimals),
175
+ supplier: parties.supplier,
176
+ supplierSigner: parties.signer,
177
+ payee: parties.payee,
178
+ termsHash: termsHash(terms, salt),
179
+ mandateHash: funding.mandateHash,
180
+ capPerCommitment: authority,
181
+ capOpenReserves: authority,
182
+ fundingKey: funding.key,
183
+ nonce: 1n,
184
+ expiry: now + 120n,
185
+ };
186
+ }
187
+ const openReserves = () => bookings.reduce((sum, b) => {
188
+ const r = records.get(key(b.reference.lock, b.reference.commitmentId, 'buyer'));
189
+ return r && (r.state === 'reserved' || r.state === 'accepted') ? sum + b.reserve : sum;
190
+ }, 0n);
191
+ const readOf = (r) => ({
192
+ state: r.state,
193
+ block: head(),
194
+ freeCancelBy: r.freeCancelBy,
195
+ releaseDate: r.releaseDate,
196
+ });
197
+ /** The customer's check of a quote, as its verifier runs it. */
198
+ async function verify(order) {
199
+ const quote = order.quote;
200
+ if (!quote)
201
+ return;
202
+ const refuse = (refusal, line) => {
203
+ order.status = 'refused';
204
+ order.refusal = refusal;
205
+ order.said.push({ index: order.said.length, text: line });
206
+ };
207
+ if (!isChainedTerms(quote.terms)) {
208
+ refuse({ code: 'dependency.required', layer: 'customer', block: null, index: null }, "Your quote names no compute booking, so I can't rely on it to start my job.");
209
+ return;
210
+ }
211
+ const checked = await verifyDependencies({
212
+ terms: quote.terms,
213
+ approval: { side: 'buyer', supplier: supplier.account },
214
+ disclosed: quote.disclosed,
215
+ now,
216
+ }, { readers: [reader] });
217
+ if (!checked.ok) {
218
+ refuse({ code: checked.reason, layer: 'customer', block: checked.block, index: checked.index }, "The compute booking your quote names doesn't hold up, so I won't commit.");
219
+ return;
220
+ }
221
+ order.checks = [...checked.checked];
222
+ for (const c of checked.checked) {
223
+ order.facts.push({
224
+ index: order.facts.length,
225
+ text: 'Your compute booking is accepted, past its free cancellation, and holds the GPUs for the whole job.',
226
+ check: 'dependency',
227
+ verifiedBy: 'customer',
228
+ reference: c.on,
229
+ block: c.block,
230
+ transactionHash: null,
231
+ });
232
+ }
233
+ // The customer reserves for the job in its own lock, under an approval naming this supplier.
234
+ const terms = quote.terms;
235
+ const a = approval(order.offer.commitmentId, terms, quote.salt, { supplier: supplier.account, signer: supplier.signingKey, payee: supplier.payee }, { mandateHash: customer.mandateHash, key: customerAgent });
236
+ records.set(key(customerLock, order.offer.commitmentId, 'buyer'), record(a, 'reserved'));
237
+ const reference = {
238
+ kind: 'commitment',
239
+ chainId: CHAIN_ID,
240
+ lock: customerLock,
241
+ commitmentId: order.offer.commitmentId,
242
+ side: 'buyer',
243
+ termsHash: quote.agreement.termsHash,
244
+ };
245
+ const fundedIn = tx('the customer reserves');
246
+ order.reserve = { reference, amount: a.amount, fundedIn };
247
+ order.status = 'reserved';
248
+ order.said.push({
249
+ index: order.said.length,
250
+ text: 'Verified. I reserved for the job in my lock.',
251
+ });
252
+ order.facts.push({
253
+ index: order.facts.length,
254
+ text: 'The customer reserved for the job in its own lock.',
255
+ check: 'reserve',
256
+ verifiedBy: 'customer',
257
+ reference,
258
+ block: head(),
259
+ transactionHash: fundedIn,
260
+ });
261
+ }
262
+ const world = {
263
+ kind: 'fixture',
264
+ customer,
265
+ computeSupplier: DEMO_GPU_SUPPLIER,
266
+ supplier,
267
+ token: { address: TOKEN.token, decimals: TOKEN.decimals },
268
+ secrets: [rootKey, agentKey, signingSecret, customerRootKey],
269
+ acceptances,
270
+ failAcceptances(count) {
271
+ acceptFails = count;
272
+ },
273
+ async pullOrder(scenario) {
274
+ const n = ++orderCount;
275
+ const orderId = orderIdOf(n);
276
+ // The test customer's own clock: open for a quote, then the acceptance window, then work.
277
+ const startsAt = now + DEMO_JOB_TIMING.quote + DEMO_JOB_TIMING.accept;
278
+ const offer = {
279
+ orderId,
280
+ commitmentId: bytes32(`job ${n}`),
281
+ customer: { label: TEST_CUSTOMER_LABEL, businessId: customer.businessId },
282
+ supplierBusinessId: 'fixture-business',
283
+ terms: testnetDemoJobTerms(startsAt, ARBITER),
284
+ requirement: { template: GPU_RESERVATION, condition: 'accepted', disclosed: 'subject' },
285
+ expiry: now + DEMO_JOB_TIMING.quote,
286
+ };
287
+ orders.set(orderId, {
288
+ offer,
289
+ quote: null,
290
+ status: 'offered',
291
+ said: [],
292
+ facts: [],
293
+ refusal: null,
294
+ checks: [],
295
+ reserve: null,
296
+ payment: null,
297
+ scenario: scenario ?? null,
298
+ cancelled: null,
299
+ reads: 0,
300
+ });
301
+ return offer;
302
+ },
303
+ async offers(order) {
304
+ const s = order.terms.subject;
305
+ return demoComputeOffers({ startsAt: s.startsAt, deliverBy: s.deliverBy, listedAt: now });
306
+ },
307
+ async book(offer, gpus) {
308
+ const terms = computeOfferTerms(offer, gpus, {
309
+ arbiter: ARBITER,
310
+ day90SupplierShareBps: 5000,
311
+ });
312
+ const { reserve, price } = computeOfferCost(offer, gpus);
313
+ const open = openReserves();
314
+ const amounts = {
315
+ needed: reserve,
316
+ left: authority - open,
317
+ authority,
318
+ capPerCommitment: authority,
319
+ dailyLimit: dollars(1000),
320
+ dailyLeft: dollars(1000),
321
+ };
322
+ const request = {
323
+ commitmentId: bytes32(`compute ${bookings.length + 1}`),
324
+ agentKey: agent.address,
325
+ supplier: offer.supplier,
326
+ payee: gpuCloud,
327
+ category: 'compute',
328
+ terms,
329
+ };
330
+ // The SDK runs the mandate check first, counting nothing open, as Avow's check does after it.
331
+ const sdk = checkMandate({
332
+ mandate,
333
+ request,
334
+ token: TOKEN,
335
+ openReserves: 0n,
336
+ revoked: false,
337
+ now,
338
+ profile: TESTNET_DEMO_PROFILE,
339
+ });
340
+ if (sdk.result !== 'approved')
341
+ return { ok: false, code: sdk.reason, layer: 'sdk', amounts };
342
+ const avow = checkMandate({
343
+ mandate,
344
+ request,
345
+ token: TOKEN,
346
+ openReserves: open,
347
+ revoked: false,
348
+ now,
349
+ profile: TESTNET_DEMO_PROFILE,
350
+ });
351
+ if (avow.result !== 'approved')
352
+ return { ok: false, code: avow.reason, layer: 'avow', amounts };
353
+ const salt = newSalt();
354
+ const a = approval(request.commitmentId, terms, salt, { supplier: gpuCloud, signer: gpuCloud, payee: gpuCloud }, { mandateHash: businessMandate, key: agent.address });
355
+ const reference = {
356
+ kind: 'commitment',
357
+ chainId: CHAIN_ID,
358
+ lock: businessLock,
359
+ commitmentId: request.commitmentId,
360
+ side: 'buyer',
361
+ termsHash: a.termsHash,
362
+ };
363
+ now += 4n;
364
+ block += 4n;
365
+ // The GPU supplier accepts at once.
366
+ records.set(key(businessLock, request.commitmentId, 'buyer'), record(a, 'accepted'));
367
+ bookings.push({ reference, reserve });
368
+ const held = records.get(key(businessLock, request.commitmentId, 'buyer'));
369
+ return {
370
+ ok: true,
371
+ booking: {
372
+ offer,
373
+ gpus,
374
+ reference,
375
+ terms,
376
+ salt,
377
+ reserve,
378
+ price,
379
+ reservedIn: tx('the compute reserve'),
380
+ read: readOf(held),
381
+ },
382
+ };
383
+ },
384
+ async authority() {
385
+ return {
386
+ capPerCommitment: authority,
387
+ capOpenReserves: authority,
388
+ openReservesLeft: authority - openReserves(),
389
+ dailyLimit: dollars(1000),
390
+ dailyLeft: dollars(1000),
391
+ suppliers: [DEMO_GPU_SUPPLIER],
392
+ };
393
+ },
394
+ async read(reference) {
395
+ const r = records.get(key(reference.lock, reference.commitmentId, reference.side));
396
+ return r ? readOf(r) : null;
397
+ },
398
+ async quote(order, booking) {
399
+ const held = orders.get(order.orderId);
400
+ if (!held)
401
+ return { ok: false, code: 'order.unknown', layer: 'customer' };
402
+ if (held.quote)
403
+ return { ok: false, code: 'order.out_of_step', layer: 'customer' };
404
+ const quote = await quoteJob({
405
+ offer: order,
406
+ customer,
407
+ supplier: { businessId: 'fixture-business', ...supplier },
408
+ signingKey,
409
+ compute: booking
410
+ ? { reference: booking.reference, terms: booking.terms, salt: booking.salt }
411
+ : null,
412
+ expiry: now + 120n,
413
+ });
414
+ held.quote = quote;
415
+ held.status = 'verifying';
416
+ held.said.push({
417
+ index: held.said.length,
418
+ text: 'Before I accept, I need to verify your compute.',
419
+ });
420
+ return { ok: true, quote };
421
+ },
422
+ async orderState(orderId) {
423
+ const o = orders.get(orderId);
424
+ if (!o)
425
+ throw new Error(`No order ${orderId}`);
426
+ o.reads++;
427
+ if (o.status === 'verifying' && o.reads > 1)
428
+ await verify(o);
429
+ // A customer asked to cancel late does, shortly after the start and before the no-show charge.
430
+ // It cancels halfway through the time Avow records a cancellation in, clear of both ends.
431
+ const window = cancellationWindow(o.offer);
432
+ const cancelsAt = window.opens + (window.closes - window.opens) / 2n;
433
+ if (o.status === 'accepted' && o.scenario === 'cancels_late' && now >= cancelsAt) {
434
+ o.status = 'cancelled';
435
+ o.cancelled = { at: now, recorded: options.charge !== 'referred' };
436
+ o.said.push({
437
+ index: o.said.length,
438
+ text: "I'm cancelling this job after its start. My reserve backs the late-cancellation charge our terms set.",
439
+ });
440
+ }
441
+ if (o.status === 'delivered' && o.reads > 1 && o.reserve) {
442
+ o.status = 'paid';
443
+ const price = o.offer.terms.consideration.kind === 'fixed' ? o.offer.terms.consideration.amount : 0n;
444
+ o.payment = {
445
+ kind: 'payment',
446
+ chainId: CHAIN_ID,
447
+ transactionHash: tx('the payment'),
448
+ logIndex: 0,
449
+ token: TOKEN.token,
450
+ from: customer.account,
451
+ to: supplier.payee,
452
+ amount: price,
453
+ memo: o.offer.commitmentId,
454
+ };
455
+ o.said.push({ index: o.said.length, text: 'Received the result. I paid the price.' });
456
+ }
457
+ const common = {
458
+ orderId,
459
+ commitmentId: o.offer.commitmentId,
460
+ customer: o.offer.customer,
461
+ supplierBusinessId: o.offer.supplierBusinessId,
462
+ said: o.said.map((s) => ({ ...s })),
463
+ facts: o.facts.map((f) => ({ ...f })),
464
+ };
465
+ if (o.status === 'offered' || o.status === 'verifying')
466
+ return { ...common, status: o.status };
467
+ if (o.status === 'refused' && o.refusal) {
468
+ return { ...common, status: 'refused', refusal: o.refusal, checks: o.checks };
469
+ }
470
+ if (!o.reserve)
471
+ throw new Error(`Order ${orderId} has no reserve`);
472
+ if (o.status === 'cancelled' && o.cancelled) {
473
+ return {
474
+ ...common,
475
+ status: 'cancelled',
476
+ checks: o.checks,
477
+ reserve: o.reserve,
478
+ cancellation: { cancelledAt: o.cancelled.at },
479
+ };
480
+ }
481
+ if (o.status === 'paid' && o.payment) {
482
+ return {
483
+ ...common,
484
+ status: 'paid',
485
+ checks: o.checks,
486
+ reserve: o.reserve,
487
+ payment: o.payment,
488
+ };
489
+ }
490
+ return {
491
+ ...common,
492
+ status: o.status,
493
+ checks: o.checks,
494
+ reserve: o.reserve,
495
+ };
496
+ },
497
+ async accept(order, quote) {
498
+ const k = key(customerLock, order.commitmentId, 'buyer');
499
+ const r = records.get(k);
500
+ if (!r)
501
+ return { ok: false, code: 'chain.reserve_missing', layer: 'avow', block: null, index: null };
502
+ if (!isAddressEqual(r.supplierSigner, supplier.signingKey)) {
503
+ return {
504
+ ok: false,
505
+ code: 'approval.parties_mismatch',
506
+ layer: 'service',
507
+ block: null,
508
+ index: null,
509
+ };
510
+ }
511
+ const relied = await verifyDependencies({
512
+ terms: quote.terms,
513
+ approval: { side: 'buyer', supplier: supplier.account },
514
+ disclosed: quote.disclosed,
515
+ now,
516
+ }, { readers: [reader] });
517
+ if (!relied.ok) {
518
+ return {
519
+ ok: false,
520
+ code: relied.reason,
521
+ layer: 'service',
522
+ block: relied.block,
523
+ index: relied.index,
524
+ };
525
+ }
526
+ if (r.state === 'accepted')
527
+ return { ok: true, sent: null, checks: relied.checked };
528
+ const hash = tx('the acceptance');
529
+ acceptances.push(hash);
530
+ now += 2n;
531
+ block += 2n;
532
+ records.set(k, { ...r, state: 'accepted' });
533
+ const o = orders.get(order.orderId);
534
+ if (o)
535
+ o.status = 'accepted';
536
+ if (acceptFails > 0) {
537
+ acceptFails--;
538
+ throw new SendError(`No receipt for ${hash} after waiting`, 'unknown', { hashes: [hash], signings: 1 }, { stage: 'landed' });
539
+ }
540
+ return {
541
+ ok: true,
542
+ sent: {
543
+ acceptance: {
544
+ type: 'Acceptance',
545
+ schemaVersion: '1',
546
+ chainId: CHAIN_ID,
547
+ lock: customerLock,
548
+ message: {
549
+ commitmentId: order.commitmentId,
550
+ side: 'buyer',
551
+ approvalHash: r.approvalHash,
552
+ termsHash: r.termsHash,
553
+ amount: r.amount,
554
+ token: r.token,
555
+ acceptBy: r.acceptBy,
556
+ nonce: 1n,
557
+ expiry: now + 600n,
558
+ },
559
+ signature: '0x',
560
+ },
561
+ disclosureId: bytes32(`disclosure ${hash}`),
562
+ landed: { transactionHash: hash, blockNumber: block },
563
+ },
564
+ checks: relied.checked,
565
+ };
566
+ },
567
+ async landed(hashes) {
568
+ const found = hashes.find((h) => acceptances.includes(h));
569
+ return found ? { transactionHash: found, blockNumber: block } : null;
570
+ },
571
+ async reportCharge(order, kind) {
572
+ // The lock's own rules for a report, as the verifier checks them before it builds one.
573
+ const k = key(customerLock, order.commitmentId, 'buyer');
574
+ const r = records.get(k);
575
+ const refuse = (code) => ({
576
+ ok: false,
577
+ code,
578
+ layer: 'service',
579
+ });
580
+ if (!r)
581
+ return refuse('chain.reserve_missing');
582
+ if (r.state !== 'accepted')
583
+ return refuse('report.not_reportable');
584
+ if (r.hasReport)
585
+ return refuse('report.open');
586
+ const charge = r.charges.find((c) => c.kind === kind);
587
+ if (!charge)
588
+ return refuse('report.kind_not_allowed');
589
+ if (now < charge.earliest)
590
+ return refuse('report.too_early');
591
+ if (now >= r.chargeDeadline)
592
+ return refuse('commitment.ended');
593
+ now += 2n;
594
+ block += 2n;
595
+ const reportNumber = r.reportCount + 1;
596
+ records.set(k, {
597
+ ...r,
598
+ hasReport: true,
599
+ reportCount: reportNumber,
600
+ reportKind: kind,
601
+ reportAmount: charge.most,
602
+ });
603
+ reports.set(k, now);
604
+ return {
605
+ ok: true,
606
+ amount: charge.most,
607
+ reportNumber,
608
+ transactionHash: tx('the report'),
609
+ reportedAt: now,
610
+ };
611
+ },
612
+ async charge(order, report) {
613
+ // What Avow, the customer and the lock's own clock do to a report, each step once.
614
+ const k = key(customerLock, order.commitmentId, 'buyer');
615
+ const o = orders.get(order.orderId);
616
+ const reportedAt = reports.get(k);
617
+ let r = records.get(k);
618
+ const how = options.charge ?? 'pays';
619
+ if (r && reportedAt !== undefined) {
620
+ const answerDue = now >= reportedAt + AVOW_ANSWERS_AFTER;
621
+ if (r.state === 'accepted' && r.hasReport && how !== 'unanswered' && answerDue) {
622
+ // Avow confirms only from its own record of the customer's signed cancellation, and only
623
+ // one clear of the report and of both ends of the time it records cancellations in.
624
+ const at = o?.cancelled?.recorded ? o.cancelled.at : null;
625
+ const window = o ? cancellationWindow(o.offer) : null;
626
+ const clear = at !== null &&
627
+ window !== null &&
628
+ at <= reportedAt - AVOW_CLOCK_MARGIN &&
629
+ at >= window.opens + AVOW_CLOCK_MARGIN &&
630
+ at <= window.closes - AVOW_CLOCK_MARGIN;
631
+ r = clear
632
+ ? { ...r, state: 'charge_pending', chargeStart: now, held: r.reportAmount }
633
+ : { ...r, state: 'paused', pausedAt: now };
634
+ }
635
+ else if (r.state === 'accepted' &&
636
+ r.hasReport &&
637
+ now >= reportedAt + FIXTURE_CONFIRM_WINDOW) {
638
+ r = { ...r, state: 'paused', pausedAt: reportedAt + FIXTURE_CONFIRM_WINDOW };
639
+ }
640
+ else if (r.state === 'charge_pending' && how === 'contested' && now > r.chargeStart) {
641
+ r = { ...r, state: 'paused', contested: true, pausedAt: now };
642
+ }
643
+ else if (r.state === 'charge_pending' && now >= r.chargeStart + r.contestWindow) {
644
+ r = { ...r, state: 'charged', held: 0n };
645
+ }
646
+ records.set(k, r);
647
+ }
648
+ return chargeStanding(r ?? null, report, FIXTURE_CONFIRM_WINDOW);
649
+ },
650
+ async deliver(orderId, _result) {
651
+ const o = orders.get(orderId);
652
+ if (!o)
653
+ throw new Error(`No order ${orderId}`);
654
+ if (o.status === 'cancelled')
655
+ throw new Error(`Order ${orderId} was cancelled`);
656
+ o.status = 'delivered';
657
+ o.reads = 0;
658
+ },
659
+ async cancel(booking) {
660
+ const k = key(booking.reference.lock, booking.reference.commitmentId, 'buyer');
661
+ const r = records.get(k);
662
+ if (!r)
663
+ return {
664
+ ok: false,
665
+ code: 'chain.reserve_missing',
666
+ layer: 'lock',
667
+ why: 'The lock holds no such booking.',
668
+ };
669
+ records.set(k, { ...r, state: 'released' });
670
+ return { ok: true, transactionHash: tx('the cancellation') };
671
+ },
672
+ async chainFor(job) {
673
+ const wanted = [job.commitment, ...job.verification.dependencies.map((d) => d.on)];
674
+ const byLock = new Map();
675
+ for (const ref of wanted) {
676
+ const r = records.get(key(ref.lock, ref.commitmentId, ref.side));
677
+ const lock = locks.get(ref.lock.toLowerCase());
678
+ if (!lock)
679
+ continue;
680
+ const entry = byLock.get(ref.lock.toLowerCase()) ?? {
681
+ address: ref.lock,
682
+ account: lock.account,
683
+ records: [],
684
+ };
685
+ if (r)
686
+ entry.records.push({ commitmentId: ref.commitmentId, side: ref.side, record: { ...r } });
687
+ byLock.set(ref.lock.toLowerCase(), entry);
688
+ }
689
+ return {
690
+ kind: 'snapshot',
691
+ snapshot: {
692
+ chainId: CHAIN_ID,
693
+ factory: FACTORY,
694
+ block: head(),
695
+ locks: [...byLock.values()],
696
+ },
697
+ };
698
+ },
699
+ now: () => now,
700
+ async sleep(ms) {
701
+ const seconds = BigInt(Math.max(1, Math.ceil(ms / 1000)));
702
+ now += seconds;
703
+ block += seconds;
704
+ },
705
+ };
706
+ return world;
707
+ }
708
+ //# sourceMappingURL=fixture-world.js.map