@tabai/sdk 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 (204) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +401 -0
  3. package/bin/tab.mjs +23 -0
  4. package/dist/_shared/abi.d.ts +150 -0
  5. package/dist/_shared/abi.d.ts.map +1 -0
  6. package/dist/_shared/abi.js +197 -0
  7. package/dist/_shared/abi.js.map +1 -0
  8. package/dist/_shared/chains.d.ts +118 -0
  9. package/dist/_shared/chains.d.ts.map +1 -0
  10. package/dist/_shared/chains.js +89 -0
  11. package/dist/_shared/chains.js.map +1 -0
  12. package/dist/_shared/hex.d.ts +35 -0
  13. package/dist/_shared/hex.d.ts.map +1 -0
  14. package/dist/_shared/hex.js +40 -0
  15. package/dist/_shared/hex.js.map +1 -0
  16. package/dist/_shared/index.d.ts +14 -0
  17. package/dist/_shared/index.d.ts.map +1 -0
  18. package/dist/_shared/index.js +14 -0
  19. package/dist/_shared/index.js.map +1 -0
  20. package/dist/_shared/keccak256.d.ts +29 -0
  21. package/dist/_shared/keccak256.d.ts.map +1 -0
  22. package/dist/_shared/keccak256.js +145 -0
  23. package/dist/_shared/keccak256.js.map +1 -0
  24. package/dist/_shared/result.d.ts +78 -0
  25. package/dist/_shared/result.d.ts.map +1 -0
  26. package/dist/_shared/result.js +61 -0
  27. package/dist/_shared/result.js.map +1 -0
  28. package/dist/cli/client-config.d.ts +155 -0
  29. package/dist/cli/client-config.d.ts.map +1 -0
  30. package/dist/cli/client-config.js +382 -0
  31. package/dist/cli/client-config.js.map +1 -0
  32. package/dist/cli/connect.d.ts +76 -0
  33. package/dist/cli/connect.d.ts.map +1 -0
  34. package/dist/cli/connect.js +158 -0
  35. package/dist/cli/connect.js.map +1 -0
  36. package/dist/cli/doctor.d.ts +57 -0
  37. package/dist/cli/doctor.d.ts.map +1 -0
  38. package/dist/cli/doctor.js +253 -0
  39. package/dist/cli/doctor.js.map +1 -0
  40. package/dist/cli/index.d.ts +13 -0
  41. package/dist/cli/index.d.ts.map +1 -0
  42. package/dist/cli/index.js +13 -0
  43. package/dist/cli/index.js.map +1 -0
  44. package/dist/cli/main.d.ts +45 -0
  45. package/dist/cli/main.d.ts.map +1 -0
  46. package/dist/cli/main.js +371 -0
  47. package/dist/cli/main.js.map +1 -0
  48. package/dist/errors.d.ts +29 -0
  49. package/dist/errors.d.ts.map +1 -0
  50. package/dist/errors.js +37 -0
  51. package/dist/errors.js.map +1 -0
  52. package/dist/http/client-402.d.ts +243 -0
  53. package/dist/http/client-402.d.ts.map +1 -0
  54. package/dist/http/client-402.js +515 -0
  55. package/dist/http/client-402.js.map +1 -0
  56. package/dist/http/headers.d.ts +173 -0
  57. package/dist/http/headers.d.ts.map +1 -0
  58. package/dist/http/headers.js +284 -0
  59. package/dist/http/headers.js.map +1 -0
  60. package/dist/http/index.d.ts +15 -0
  61. package/dist/http/index.d.ts.map +1 -0
  62. package/dist/http/index.js +15 -0
  63. package/dist/http/index.js.map +1 -0
  64. package/dist/http/metering-claim.d.ts +82 -0
  65. package/dist/http/metering-claim.d.ts.map +1 -0
  66. package/dist/http/metering-claim.js +99 -0
  67. package/dist/http/metering-claim.js.map +1 -0
  68. package/dist/index.d.ts +48 -0
  69. package/dist/index.d.ts.map +1 -0
  70. package/dist/index.js +51 -0
  71. package/dist/index.js.map +1 -0
  72. package/dist/logger.d.ts +40 -0
  73. package/dist/logger.d.ts.map +1 -0
  74. package/dist/logger.js +50 -0
  75. package/dist/logger.js.map +1 -0
  76. package/dist/mcp/assets.d.ts +31 -0
  77. package/dist/mcp/assets.d.ts.map +1 -0
  78. package/dist/mcp/assets.js +78 -0
  79. package/dist/mcp/assets.js.map +1 -0
  80. package/dist/mcp/index.d.ts +19 -0
  81. package/dist/mcp/index.d.ts.map +1 -0
  82. package/dist/mcp/index.js +19 -0
  83. package/dist/mcp/index.js.map +1 -0
  84. package/dist/mcp/json-schema.d.ts +86 -0
  85. package/dist/mcp/json-schema.d.ts.map +1 -0
  86. package/dist/mcp/json-schema.js +215 -0
  87. package/dist/mcp/json-schema.js.map +1 -0
  88. package/dist/mcp/json.d.ts +43 -0
  89. package/dist/mcp/json.d.ts.map +1 -0
  90. package/dist/mcp/json.js +69 -0
  91. package/dist/mcp/json.js.map +1 -0
  92. package/dist/mcp/registry-client.d.ts +88 -0
  93. package/dist/mcp/registry-client.d.ts.map +1 -0
  94. package/dist/mcp/registry-client.js +158 -0
  95. package/dist/mcp/registry-client.js.map +1 -0
  96. package/dist/mcp/schemas.d.ts +82 -0
  97. package/dist/mcp/schemas.d.ts.map +1 -0
  98. package/dist/mcp/schemas.js +493 -0
  99. package/dist/mcp/schemas.js.map +1 -0
  100. package/dist/mcp/server.d.ts +97 -0
  101. package/dist/mcp/server.d.ts.map +1 -0
  102. package/dist/mcp/server.js +285 -0
  103. package/dist/mcp/server.js.map +1 -0
  104. package/dist/mcp/settings.d.ts +90 -0
  105. package/dist/mcp/settings.d.ts.map +1 -0
  106. package/dist/mcp/settings.js +160 -0
  107. package/dist/mcp/settings.js.map +1 -0
  108. package/dist/mcp/toolset.d.ts +231 -0
  109. package/dist/mcp/toolset.d.ts.map +1 -0
  110. package/dist/mcp/toolset.js +760 -0
  111. package/dist/mcp/toolset.js.map +1 -0
  112. package/dist/payments/abi.d.ts +9 -0
  113. package/dist/payments/abi.d.ts.map +1 -0
  114. package/dist/payments/abi.js +17 -0
  115. package/dist/payments/abi.js.map +1 -0
  116. package/dist/payments/config.d.ts +199 -0
  117. package/dist/payments/config.d.ts.map +1 -0
  118. package/dist/payments/config.js +259 -0
  119. package/dist/payments/config.js.map +1 -0
  120. package/dist/payments/index.d.ts +13 -0
  121. package/dist/payments/index.d.ts.map +1 -0
  122. package/dist/payments/index.js +13 -0
  123. package/dist/payments/index.js.map +1 -0
  124. package/dist/payments/kuru.d.ts +191 -0
  125. package/dist/payments/kuru.d.ts.map +1 -0
  126. package/dist/payments/kuru.js +377 -0
  127. package/dist/payments/kuru.js.map +1 -0
  128. package/dist/payments/monad.d.ts +69 -0
  129. package/dist/payments/monad.d.ts.map +1 -0
  130. package/dist/payments/monad.js +306 -0
  131. package/dist/payments/monad.js.map +1 -0
  132. package/dist/payments/permit2.d.ts +118 -0
  133. package/dist/payments/permit2.d.ts.map +1 -0
  134. package/dist/payments/permit2.js +366 -0
  135. package/dist/payments/permit2.js.map +1 -0
  136. package/dist/payments/registry.d.ts +119 -0
  137. package/dist/payments/registry.d.ts.map +1 -0
  138. package/dist/payments/registry.js +199 -0
  139. package/dist/payments/registry.js.map +1 -0
  140. package/dist/payments/strategy.d.ts +80 -0
  141. package/dist/payments/strategy.d.ts.map +1 -0
  142. package/dist/payments/strategy.js +103 -0
  143. package/dist/payments/strategy.js.map +1 -0
  144. package/dist/proxy/hooks.d.ts +90 -0
  145. package/dist/proxy/hooks.d.ts.map +1 -0
  146. package/dist/proxy/hooks.js +35 -0
  147. package/dist/proxy/hooks.js.map +1 -0
  148. package/dist/proxy/index.d.ts +9 -0
  149. package/dist/proxy/index.d.ts.map +1 -0
  150. package/dist/proxy/index.js +9 -0
  151. package/dist/proxy/index.js.map +1 -0
  152. package/dist/proxy/proxy.d.ts +156 -0
  153. package/dist/proxy/proxy.d.ts.map +1 -0
  154. package/dist/proxy/proxy.js +366 -0
  155. package/dist/proxy/proxy.js.map +1 -0
  156. package/dist/server/adapters/express.d.ts +89 -0
  157. package/dist/server/adapters/express.d.ts.map +1 -0
  158. package/dist/server/adapters/express.js +215 -0
  159. package/dist/server/adapters/express.js.map +1 -0
  160. package/dist/server/adapters/hono.d.ts +52 -0
  161. package/dist/server/adapters/hono.d.ts.map +1 -0
  162. package/dist/server/adapters/hono.js +61 -0
  163. package/dist/server/adapters/hono.js.map +1 -0
  164. package/dist/server/adapters/next.d.ts +52 -0
  165. package/dist/server/adapters/next.d.ts.map +1 -0
  166. package/dist/server/adapters/next.js +56 -0
  167. package/dist/server/adapters/next.js.map +1 -0
  168. package/dist/server/index.d.ts +30 -0
  169. package/dist/server/index.d.ts.map +1 -0
  170. package/dist/server/index.js +30 -0
  171. package/dist/server/index.js.map +1 -0
  172. package/dist/server/metering.d.ts +209 -0
  173. package/dist/server/metering.d.ts.map +1 -0
  174. package/dist/server/metering.js +365 -0
  175. package/dist/server/metering.js.map +1 -0
  176. package/dist/server/post-paid.d.ts +355 -0
  177. package/dist/server/post-paid.d.ts.map +1 -0
  178. package/dist/server/post-paid.js +512 -0
  179. package/dist/server/post-paid.js.map +1 -0
  180. package/dist/x402/client.d.ts +203 -0
  181. package/dist/x402/client.d.ts.map +1 -0
  182. package/dist/x402/client.js +337 -0
  183. package/dist/x402/client.js.map +1 -0
  184. package/dist/x402/hub.d.ts +79 -0
  185. package/dist/x402/hub.d.ts.map +1 -0
  186. package/dist/x402/hub.js +164 -0
  187. package/dist/x402/hub.js.map +1 -0
  188. package/dist/x402/index.d.ts +27 -0
  189. package/dist/x402/index.d.ts.map +1 -0
  190. package/dist/x402/index.js +27 -0
  191. package/dist/x402/index.js.map +1 -0
  192. package/dist/x402/proxy.d.ts +162 -0
  193. package/dist/x402/proxy.d.ts.map +1 -0
  194. package/dist/x402/proxy.js +198 -0
  195. package/dist/x402/proxy.js.map +1 -0
  196. package/dist/x402/server.d.ts +162 -0
  197. package/dist/x402/server.d.ts.map +1 -0
  198. package/dist/x402/server.js +306 -0
  199. package/dist/x402/server.js.map +1 -0
  200. package/dist/x402/wire.d.ts +104 -0
  201. package/dist/x402/wire.d.ts.map +1 -0
  202. package/dist/x402/wire.js +265 -0
  203. package/dist/x402/wire.js.map +1 -0
  204. package/package.json +61 -0
@@ -0,0 +1,306 @@
1
+ /**
2
+ * The Monad payment strategy: approve the Asset, call `TabSettlement.settle`, and
3
+ * read the Settlement back off the receipt.
4
+ *
5
+ * The transaction is signed by the Agent's own key. Nothing is escrowed and no
6
+ * facilitator is asked: the Asset moves straight to the Service's Collection
7
+ * address and `TabBook` applies the Settlement in the same transaction, so the
8
+ * transaction hash is the whole proof of payment.
9
+ *
10
+ * ## The signer
11
+ *
12
+ * A structural `EthersV6Signer` rather than the `ethers` class, so a test double
13
+ * or any wallet that can sign a transaction satisfies it. An `ethers.Signer`
14
+ * satisfies it as is; the `Assert` below fails to compile if that ever stops
15
+ * being true.
16
+ */
17
+ import { Interface, MaxUint256, getAddress, isAddress as isEthersAddress } from "ethers";
18
+ import { causeOf, ok, wrap } from "../_shared/index.js";
19
+ import { defaultLogger } from "../logger.js";
20
+ import { chainError, upstreamError, validationError } from "../errors.js";
21
+ import { ERC20_ABI, TAB_SETTLEMENT_ABI } from "./abi.js";
22
+ import { assetKey, validateAssetRef, validateSettleRequest, } from "./strategy.js";
23
+ export const MONAD_STRATEGY_ID = "monad";
24
+ const erc20 = new Interface(ERC20_ABI);
25
+ const tabSettlement = new Interface(TAB_SETTLEMENT_ABI);
26
+ const settledTopic = tabSettlement.getEvent("Settled")?.topicHash.toLowerCase() ?? "";
27
+ /** Reads one `Settled` log emitted by the surface, or null for any other log. */
28
+ export function decodeSettled(log, surface) {
29
+ if (log.address.toLowerCase() !== surface.toLowerCase())
30
+ return null;
31
+ if ((log.topics[0] ?? "").toLowerCase() !== settledTopic)
32
+ return null;
33
+ const fragment = tabSettlement.getEvent("Settled");
34
+ if (fragment === null)
35
+ return null;
36
+ const decoded = tabSettlement.decodeEventLog(fragment, log.data, [...log.topics]);
37
+ return {
38
+ settlementId: String(decoded.settlementId).toLowerCase(),
39
+ agent: getAddress(String(decoded.agent)),
40
+ serviceId: String(decoded.serviceId).toLowerCase(),
41
+ applied: BigInt(decoded.applied),
42
+ toPrepaid: BigInt(decoded.toPrepaid),
43
+ };
44
+ }
45
+ export function createMonadStrategy(config) {
46
+ const logger = config.logger ?? defaultLogger;
47
+ const id = config.id ?? MONAD_STRATEGY_ID;
48
+ const now = config.now ?? (() => Date.now());
49
+ const approval = config.approval ?? "exact";
50
+ const allowanceCheck = config.allowanceCheck ?? "read";
51
+ const receiptMode = config.receipt ?? "wait";
52
+ const assets = indexAssets(config.assets, id, logger);
53
+ const chainIds = [...new Set([...assets.values()].map((asset) => asset.chainId))].sort((left, right) => left < right ? -1 : left > right ? 1 : 0);
54
+ const known = (asset) => assets.get(assetKey(asset));
55
+ const requireKnown = (asset) => {
56
+ const shape = validateAssetRef(asset, "request.asset");
57
+ if (!shape.ok)
58
+ return shape;
59
+ const found = known(shape.value);
60
+ if (found === undefined) {
61
+ return validationError("ASSET_NOT_CONFIGURED", `strategy \`${id}\` is not configured for asset ${assetKey(shape.value)}`, {
62
+ details: { asset: assetKey(shape.value), configured: [...assets.keys()].join(", ") },
63
+ });
64
+ }
65
+ if (found.decimals !== shape.value.decimals) {
66
+ return validationError("ASSET_DECIMALS_MISMATCH", `asset ${assetKey(found)} is configured with ${found.decimals} decimals and the request names ${shape.value.decimals}; amounts are base units and the two must agree`, { details: { asset: assetKey(found), configured: found.decimals, requested: shape.value.decimals } });
67
+ }
68
+ return ok(found);
69
+ };
70
+ const requireSurface = () => {
71
+ const address = config.tabSettlement;
72
+ if (typeof address !== "string" || !isEthersAddress(address)) {
73
+ return validationError("SETTLEMENT_CONTRACT_INVALID", `strategy \`${id}\` was given \`${String(address)}\` as its tabSettlement, which is not an address`);
74
+ }
75
+ return ok(getAddress(address));
76
+ };
77
+ const payer = async () => wrap(async () => getAddress(await config.signer.getAddress()), (error) => ({
78
+ category: "UPSTREAM",
79
+ code: "SIGNER_ADDRESS_UNAVAILABLE",
80
+ message: `strategy \`${id}\` could not read the payer address from its signer`,
81
+ retryable: true,
82
+ cause: causeOf(error),
83
+ }));
84
+ const send = async (to, data, what) => {
85
+ const response = await wrap(async () => config.signer.sendTransaction({ to, data }), (error) => ({
86
+ category: "CHAIN",
87
+ code: "SETTLEMENT_SUBMISSION_FAILED",
88
+ message: `strategy \`${id}\` could not submit ${what}`,
89
+ retryable: true,
90
+ details: { to },
91
+ cause: causeOf(error),
92
+ }));
93
+ if (!response.ok)
94
+ return response;
95
+ const hash = response.value.hash;
96
+ if (typeof hash !== "string" || !hash.startsWith("0x")) {
97
+ return chainError("SETTLEMENT_HASH_MISSING", `strategy \`${id}\` submitted ${what} but the signer returned no transaction hash`);
98
+ }
99
+ return ok(response.value);
100
+ };
101
+ /** Waits for the receipt and returns every `Settled` log the surface emitted in it. */
102
+ const settledLogs = async (response, surface) => {
103
+ if (receiptMode === "skip" || typeof response.wait !== "function")
104
+ return ok(null);
105
+ const receipt = await wrap(async () => response.wait?.() ?? null, (error) => ({
106
+ category: "CHAIN",
107
+ code: "SETTLEMENT_RECEIPT_UNAVAILABLE",
108
+ message: `strategy \`${id}\` submitted ${response.hash} but could not read its receipt`,
109
+ retryable: true,
110
+ details: { txHash: response.hash },
111
+ cause: causeOf(error),
112
+ }));
113
+ if (!receipt.ok)
114
+ return receipt;
115
+ if (receipt.value === null)
116
+ return ok(null);
117
+ if (receipt.value.status === 0) {
118
+ return chainError("SETTLEMENT_REVERTED", `settlement ${response.hash} was mined and reverted`, {
119
+ details: { txHash: response.hash },
120
+ });
121
+ }
122
+ const logs = [];
123
+ for (const log of receipt.value.logs) {
124
+ const settled = decodeSettled(log, surface);
125
+ if (settled !== null)
126
+ logs.push(settled);
127
+ }
128
+ return ok(logs);
129
+ };
130
+ const ensureAllowance = async (asset, owner, spender, needed) => {
131
+ if (allowanceCheck === "skip")
132
+ return ok(undefined);
133
+ const provider = config.signer.provider;
134
+ if (provider === undefined || provider === null) {
135
+ return upstreamError("PROVIDER_REQUIRED", `strategy \`${id}\` needs a connected signer to read the allowance before settling; connect one or set allowanceCheck to "skip"`);
136
+ }
137
+ const current = await wrap(async () => {
138
+ const raw = await provider.call({ to: asset.address, data: erc20.encodeFunctionData("allowance", [owner, spender]) });
139
+ return erc20.decodeFunctionResult("allowance", raw)[0];
140
+ }, (error) => ({
141
+ category: "UPSTREAM",
142
+ code: "ALLOWANCE_READ_FAILED",
143
+ message: `strategy \`${id}\` could not read the allowance of ${owner} for ${spender} on ${assetKey(asset)}`,
144
+ retryable: true,
145
+ cause: causeOf(error),
146
+ }));
147
+ if (!current.ok)
148
+ return current;
149
+ if (current.value >= needed)
150
+ return ok(undefined);
151
+ const amount = approval === "unlimited" ? MaxUint256 : needed;
152
+ const approved = await send(asset.address, erc20.encodeFunctionData("approve", [spender, amount]), `the allowance top-up for ${spender} on ${assetKey(asset)}`);
153
+ if (!approved.ok)
154
+ return approved;
155
+ // The approval has to land before the settlement is sent, or the settle reverts
156
+ // on allowance. Waiting here costs one block and saves a wasted transaction.
157
+ if (typeof approved.value.wait === "function") {
158
+ const landed = await wrap(async () => approved.value.wait?.() ?? null, (error) => ({
159
+ category: "CHAIN",
160
+ code: "APPROVAL_RECEIPT_UNAVAILABLE",
161
+ message: `strategy \`${id}\` could not confirm the allowance top-up ${approved.value.hash}`,
162
+ retryable: true,
163
+ cause: causeOf(error),
164
+ }));
165
+ if (!landed.ok)
166
+ return landed;
167
+ }
168
+ logger.debug("allowance topped up", { strategyId: id, asset: assetKey(asset), spender, amount: amount.toString(10), txHash: approved.value.hash });
169
+ return ok(undefined);
170
+ };
171
+ const receiptFor = (request, asset, payerAddress, txHash, settled, batchIndex) => ({
172
+ strategyId: id,
173
+ chainId: asset.chainId,
174
+ txHash,
175
+ asset,
176
+ amount: request.amount,
177
+ payer: payerAddress,
178
+ serviceId: request.serviceId,
179
+ settlementId: settled?.settlementId ?? null,
180
+ applied: settled?.applied ?? null,
181
+ toPrepaid: settled?.toPrepaid ?? null,
182
+ submittedAt: now(),
183
+ ...(batchIndex === undefined ? {} : { batchIndex }),
184
+ });
185
+ const strategy = {
186
+ id,
187
+ chainIds,
188
+ supports(asset) {
189
+ return known(asset) !== undefined;
190
+ },
191
+ async quote(request) {
192
+ const asset = requireKnown(request.asset);
193
+ if (!asset.ok)
194
+ return asset;
195
+ if (typeof request.amount !== "bigint") {
196
+ return validationError("CHARGE_INVALID", "request.amount must be a bigint count of Asset base units, never a number");
197
+ }
198
+ if (request.amount <= 0n) {
199
+ return validationError("AMOUNT_NOT_POSITIVE", "request.amount must be greater than zero base units");
200
+ }
201
+ return ok({
202
+ amount: request.amount,
203
+ asset: asset.value,
204
+ feeNote: "A stablecoin settles one-for-one in base units: the amount charged is the amount transferred, with no rate and no protocol fee. Gas is paid separately in MON and is never deducted from the Settlement amount.",
205
+ });
206
+ },
207
+ async settle(request) {
208
+ const validated = validateSettleRequest(request);
209
+ if (!validated.ok)
210
+ return validated;
211
+ const asset = requireKnown(request.asset);
212
+ if (!asset.ok)
213
+ return asset;
214
+ const surface = requireSurface();
215
+ if (!surface.ok)
216
+ return surface;
217
+ const from = await payer();
218
+ if (!from.ok)
219
+ return from;
220
+ const allowance = await ensureAllowance(asset.value, from.value, surface.value, request.amount);
221
+ if (!allowance.ok)
222
+ return allowance;
223
+ const sent = await send(surface.value, tabSettlement.encodeFunctionData("settle", [request.serviceId, asset.value.address, request.amount]), `a settlement of ${request.amount.toString(10)} ${asset.value.symbol} base units`);
224
+ if (!sent.ok)
225
+ return sent;
226
+ const logs = await settledLogs(sent.value, surface.value);
227
+ if (!logs.ok)
228
+ return logs;
229
+ return ok(receiptFor(request, asset.value, from.value, sent.value.hash, logs.value?.[0] ?? null));
230
+ },
231
+ async settleBatch(requests) {
232
+ if (requests.length === 0) {
233
+ return validationError("EMPTY_BATCH", "settleBatch was given no settlements");
234
+ }
235
+ const validatedAssets = [];
236
+ for (const request of requests) {
237
+ const validated = validateSettleRequest(request);
238
+ if (!validated.ok)
239
+ return validated;
240
+ const asset = requireKnown(request.asset);
241
+ if (!asset.ok)
242
+ return asset;
243
+ validatedAssets.push(asset.value);
244
+ }
245
+ const first = validatedAssets[0];
246
+ if (first === undefined) {
247
+ return validationError("EMPTY_BATCH", "settleBatch was given no settlements");
248
+ }
249
+ for (const [index, asset] of validatedAssets.entries()) {
250
+ if (asset.chainId !== first.chainId) {
251
+ return validationError("BATCH_CHAIN_MIXED", `settleBatch entry ${index} settles on chain ${asset.chainId.toString(10)} and entry 0 on ${first.chainId.toString(10)}; one transaction settles on one chain`, { details: { index } });
252
+ }
253
+ }
254
+ const surface = requireSurface();
255
+ if (!surface.ok)
256
+ return surface;
257
+ const from = await payer();
258
+ if (!from.ok)
259
+ return from;
260
+ const owed = new Map();
261
+ for (const [index, asset] of validatedAssets.entries()) {
262
+ const request = requests[index];
263
+ if (request === undefined)
264
+ continue;
265
+ const key = assetKey(asset);
266
+ const running = owed.get(key);
267
+ owed.set(key, { asset, total: (running?.total ?? 0n) + request.amount });
268
+ }
269
+ for (const { asset, total } of owed.values()) {
270
+ const allowance = await ensureAllowance(asset, from.value, surface.value, total);
271
+ if (!allowance.ok)
272
+ return allowance;
273
+ }
274
+ const instructions = requests.map((request, index) => [
275
+ request.serviceId,
276
+ (validatedAssets[index] ?? request.asset).address,
277
+ request.amount,
278
+ ]);
279
+ const sent = await send(surface.value, tabSettlement.encodeFunctionData("settleBatch", [instructions]), `a batch of ${requests.length} settlements`);
280
+ if (!sent.ok)
281
+ return sent;
282
+ const logs = await settledLogs(sent.value, surface.value);
283
+ if (!logs.ok)
284
+ return logs;
285
+ return ok(requests.map((request, index) => receiptFor(request, validatedAssets[index] ?? request.asset, from.value, sent.value.hash, logs.value?.[index] ?? null, index)));
286
+ },
287
+ };
288
+ return strategy;
289
+ }
290
+ export function indexAssets(assets, id, logger) {
291
+ const indexed = new Map();
292
+ for (const [declaredKey, value] of Object.entries(assets)) {
293
+ const validated = validateAssetRef(value, `assets["${declaredKey}"]`);
294
+ if (!validated.ok) {
295
+ logger.error("payment strategy asset entry ignored", { strategyId: id, key: declaredKey, message: validated.error.message });
296
+ continue;
297
+ }
298
+ const derived = assetKey(validated.value);
299
+ if (declaredKey.toLowerCase() !== derived) {
300
+ logger.warn("payment strategy asset key disagrees with its value and the value wins", { strategyId: id, declaredKey, derivedKey: derived });
301
+ }
302
+ indexed.set(derived, validated.value);
303
+ }
304
+ return indexed;
305
+ }
306
+ //# sourceMappingURL=monad.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"monad.js","sourceRoot":"","sources":["../../src/payments/monad.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,UAAU,EAAE,SAAS,IAAI,eAAe,EAAE,MAAM,QAAQ,CAAC;AAEzF,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAqD,MAAM,eAAe,CAAC;AACrG,OAAO,EAAE,aAAa,EAAe,MAAM,cAAc,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC1E,OAAO,EAAE,SAAS,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AACzD,OAAO,EACL,QAAQ,EACR,gBAAgB,EAChB,qBAAqB,GAOtB,MAAM,eAAe,CAAC;AAEvB,MAAM,CAAC,MAAM,iBAAiB,GAAG,OAAO,CAAC;AAyDzC,MAAM,KAAK,GAAG,IAAI,SAAS,CAAC,SAAS,CAAC,CAAC;AACvC,MAAM,aAAa,GAAG,IAAI,SAAS,CAAC,kBAAkB,CAAC,CAAC;AACxD,MAAM,YAAY,GAAG,aAAa,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC,WAAW,EAAE,IAAI,EAAE,CAAC;AAWtF,iFAAiF;AACjF,MAAM,UAAU,aAAa,CAAC,GAAgB,EAAE,OAAgB;IAC9D,IAAI,GAAG,CAAC,OAAO,CAAC,WAAW,EAAE,KAAK,OAAO,CAAC,WAAW,EAAE;QAAE,OAAO,IAAI,CAAC;IACrE,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,KAAK,YAAY;QAAE,OAAO,IAAI,CAAC;IACtE,MAAM,QAAQ,GAAG,aAAa,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;IACnD,IAAI,QAAQ,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IACnC,MAAM,OAAO,GAAG,aAAa,CAAC,cAAc,CAAC,QAAQ,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;IAClF,OAAO;QACL,YAAY,EAAE,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,WAAW,EAAa;QACnE,KAAK,EAAE,UAAU,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAY;QACnD,SAAS,EAAE,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,WAAW,EAAa;QAC7D,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,OAAiB,CAAC;QAC1C,SAAS,EAAE,MAAM,CAAC,OAAO,CAAC,SAAmB,CAAC;KAC/C,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,MAA2B;IAC7D,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,IAAI,aAAa,CAAC;IAC9C,MAAM,EAAE,GAAG,MAAM,CAAC,EAAE,IAAI,iBAAiB,CAAC;IAC1C,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;IAC7C,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,IAAI,OAAO,CAAC;IAC5C,MAAM,cAAc,GAAG,MAAM,CAAC,cAAc,IAAI,MAAM,CAAC;IACvD,MAAM,WAAW,GAAG,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC;IAC7C,MAAM,MAAM,GAAG,WAAW,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,EAAE,MAAM,CAAC,CAAC;IACtD,MAAM,QAAQ,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CACrG,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CACzC,CAAC;IAEF,MAAM,KAAK,GAAG,CAAC,KAAe,EAAwB,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;IAErF,MAAM,YAAY,GAAG,CAAC,KAAe,EAAoB,EAAE;QACzD,MAAM,KAAK,GAAG,gBAAgB,CAAC,KAAK,EAAE,eAAe,CAAC,CAAC;QACvD,IAAI,CAAC,KAAK,CAAC,EAAE;YAAE,OAAO,KAAK,CAAC;QAC5B,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACjC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,OAAO,eAAe,CAAC,sBAAsB,EAAE,cAAc,EAAE,kCAAkC,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,EAAE;gBACxH,OAAO,EAAE,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,UAAU,EAAE,CAAC,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;aACrF,CAAC,CAAC;QACL,CAAC;QACD,IAAI,KAAK,CAAC,QAAQ,KAAK,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC;YAC5C,OAAO,eAAe,CACpB,yBAAyB,EACzB,SAAS,QAAQ,CAAC,KAAK,CAAC,uBAAuB,KAAK,CAAC,QAAQ,mCAAmC,KAAK,CAAC,KAAK,CAAC,QAAQ,iDAAiD,EACrK,EAAE,OAAO,EAAE,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,UAAU,EAAE,KAAK,CAAC,QAAQ,EAAE,SAAS,EAAE,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE,CACrG,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC;IACnB,CAAC,CAAC;IAEF,MAAM,cAAc,GAAG,GAAoB,EAAE;QAC3C,MAAM,OAAO,GAAG,MAAM,CAAC,aAAa,CAAC;QACrC,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC,EAAE,CAAC;YAC7D,OAAO,eAAe,CACpB,6BAA6B,EAC7B,cAAc,EAAE,kBAAkB,MAAM,CAAC,OAAO,CAAC,kDAAkD,CACpG,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,CAAC,UAAU,CAAC,OAAO,CAAY,CAAC,CAAC;IAC5C,CAAC,CAAC;IAEF,MAAM,KAAK,GAAG,KAAK,IAA8B,EAAE,CACjD,IAAI,CACF,KAAK,IAAI,EAAE,CAAC,UAAU,CAAC,MAAM,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,CAAY,EACnE,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACV,QAAQ,EAAE,UAAU;QACpB,IAAI,EAAE,4BAA4B;QAClC,OAAO,EAAE,cAAc,EAAE,qDAAqD;QAC9E,SAAS,EAAE,IAAI;QACf,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC;KACtB,CAAC,CACH,CAAC;IAEJ,MAAM,IAAI,GAAG,KAAK,EAAE,EAAW,EAAE,IAAY,EAAE,IAAY,EAAgD,EAAE;QAC3G,MAAM,QAAQ,GAAG,MAAM,IAAI,CACzB,KAAK,IAAI,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,eAAe,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,EACvD,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YACV,QAAQ,EAAE,OAAO;YACjB,IAAI,EAAE,8BAA8B;YACpC,OAAO,EAAE,cAAc,EAAE,uBAAuB,IAAI,EAAE;YACtD,SAAS,EAAE,IAAI;YACf,OAAO,EAAE,EAAE,EAAE,EAAE;YACf,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC;SACtB,CAAC,CACH,CAAC;QACF,IAAI,CAAC,QAAQ,CAAC,EAAE;YAAE,OAAO,QAAQ,CAAC;QAClC,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC;QACjC,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YACvD,OAAO,UAAU,CAAC,yBAAyB,EAAE,cAAc,EAAE,gBAAgB,IAAI,8CAA8C,CAAC,CAAC;QACnI,CAAC;QACD,OAAO,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC5B,CAAC,CAAC;IAEF,uFAAuF;IACvF,MAAM,WAAW,GAAG,KAAK,EACvB,QAAqC,EACrC,OAAgB,EAC+B,EAAE;QACjD,IAAI,WAAW,KAAK,MAAM,IAAI,OAAO,QAAQ,CAAC,IAAI,KAAK,UAAU;YAAE,OAAO,EAAE,CAAC,IAAI,CAAC,CAAC;QACnF,MAAM,OAAO,GAAG,MAAM,IAAI,CACxB,KAAK,IAAI,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,IAAI,IAAI,EACrC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YACV,QAAQ,EAAE,OAAO;YACjB,IAAI,EAAE,gCAAgC;YACtC,OAAO,EAAE,cAAc,EAAE,gBAAgB,QAAQ,CAAC,IAAI,iCAAiC;YACvF,SAAS,EAAE,IAAI;YACf,OAAO,EAAE,EAAE,MAAM,EAAE,QAAQ,CAAC,IAAI,EAAE;YAClC,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC;SACtB,CAAC,CACH,CAAC;QACF,IAAI,CAAC,OAAO,CAAC,EAAE;YAAE,OAAO,OAAO,CAAC;QAChC,IAAI,OAAO,CAAC,KAAK,KAAK,IAAI;YAAE,OAAO,EAAE,CAAC,IAAI,CAAC,CAAC;QAC5C,IAAI,OAAO,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC/B,OAAO,UAAU,CAAC,qBAAqB,EAAE,cAAc,QAAQ,CAAC,IAAI,yBAAyB,EAAE;gBAC7F,OAAO,EAAE,EAAE,MAAM,EAAE,QAAQ,CAAC,IAAI,EAAE;aACnC,CAAC,CAAC;QACL,CAAC;QACD,MAAM,IAAI,GAAiB,EAAE,CAAC;QAC9B,KAAK,MAAM,GAAG,IAAI,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;YACrC,MAAM,OAAO,GAAG,aAAa,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;YAC5C,IAAI,OAAO,KAAK,IAAI;gBAAE,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC3C,CAAC;QACD,OAAO,EAAE,CAAC,IAAI,CAAC,CAAC;IAClB,CAAC,CAAC;IAEF,MAAM,eAAe,GAAG,KAAK,EAAE,KAAe,EAAE,KAAc,EAAE,OAAgB,EAAE,MAAc,EAAyB,EAAE;QACzH,IAAI,cAAc,KAAK,MAAM;YAAE,OAAO,EAAE,CAAC,SAAS,CAAC,CAAC;QACpD,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC;QACxC,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;YAChD,OAAO,aAAa,CAClB,mBAAmB,EACnB,cAAc,EAAE,gHAAgH,CACjI,CAAC;QACJ,CAAC;QACD,MAAM,OAAO,GAAG,MAAM,IAAI,CACxB,KAAK,IAAI,EAAE;YACT,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,kBAAkB,CAAC,WAAW,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;YACtH,OAAO,KAAK,CAAC,oBAAoB,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC,CAAC,CAAW,CAAC;QACnE,CAAC,EACD,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YACV,QAAQ,EAAE,UAAU;YACpB,IAAI,EAAE,uBAAuB;YAC7B,OAAO,EAAE,cAAc,EAAE,sCAAsC,KAAK,QAAQ,OAAO,OAAO,QAAQ,CAAC,KAAK,CAAC,EAAE;YAC3G,SAAS,EAAE,IAAI;YACf,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC;SACtB,CAAC,CACH,CAAC;QACF,IAAI,CAAC,OAAO,CAAC,EAAE;YAAE,OAAO,OAAO,CAAC;QAChC,IAAI,OAAO,CAAC,KAAK,IAAI,MAAM;YAAE,OAAO,EAAE,CAAC,SAAS,CAAC,CAAC;QAClD,MAAM,MAAM,GAAG,QAAQ,KAAK,WAAW,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC;QAC9D,MAAM,QAAQ,GAAG,MAAM,IAAI,CACzB,KAAK,CAAC,OAAO,EACb,KAAK,CAAC,kBAAkB,CAAC,SAAS,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,EACtD,4BAA4B,OAAO,OAAO,QAAQ,CAAC,KAAK,CAAC,EAAE,CAC5D,CAAC;QACF,IAAI,CAAC,QAAQ,CAAC,EAAE;YAAE,OAAO,QAAQ,CAAC;QAClC,gFAAgF;QAChF,6EAA6E;QAC7E,IAAI,OAAO,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;YAC9C,MAAM,MAAM,GAAG,MAAM,IAAI,CACvB,KAAK,IAAI,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,IAAI,IAAI,EAC3C,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;gBACV,QAAQ,EAAE,OAAO;gBACjB,IAAI,EAAE,8BAA8B;gBACpC,OAAO,EAAE,cAAc,EAAE,6CAA6C,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE;gBAC3F,SAAS,EAAE,IAAI;gBACf,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC;aACtB,CAAC,CACH,CAAC;YACF,IAAI,CAAC,MAAM,CAAC,EAAE;gBAAE,OAAO,MAAM,CAAC;QAChC,CAAC;QACD,MAAM,CAAC,KAAK,CAAC,qBAAqB,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;QACnJ,OAAO,EAAE,CAAC,SAAS,CAAC,CAAC;IACvB,CAAC,CAAC;IAEF,MAAM,UAAU,GAAG,CACjB,OAAsB,EACtB,KAAe,EACf,YAAqB,EACrB,MAAW,EACX,OAA0B,EAC1B,UAAmB,EACA,EAAE,CAAC,CAAC;QACvB,UAAU,EAAE,EAAE;QACd,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,MAAM;QACN,KAAK;QACL,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,KAAK,EAAE,YAAY;QACnB,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,YAAY,EAAE,OAAO,EAAE,YAAY,IAAI,IAAI;QAC3C,OAAO,EAAE,OAAO,EAAE,OAAO,IAAI,IAAI;QACjC,SAAS,EAAE,OAAO,EAAE,SAAS,IAAI,IAAI;QACrC,WAAW,EAAE,GAAG,EAAE;QAClB,GAAG,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC;KACpD,CAAC,CAAC;IAEH,MAAM,QAAQ,GAAoB;QAChC,EAAE;QACF,QAAQ;QACR,QAAQ,CAAC,KAAK;YACZ,OAAO,KAAK,CAAC,KAAK,CAAC,KAAK,SAAS,CAAC;QACpC,CAAC;QACD,KAAK,CAAC,KAAK,CAAC,OAAsB;YAChC,MAAM,KAAK,GAAG,YAAY,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;YAC1C,IAAI,CAAC,KAAK,CAAC,EAAE;gBAAE,OAAO,KAAK,CAAC;YAC5B,IAAI,OAAO,OAAO,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;gBACvC,OAAO,eAAe,CAAC,gBAAgB,EAAE,2EAA2E,CAAC,CAAC;YACxH,CAAC;YACD,IAAI,OAAO,CAAC,MAAM,IAAI,EAAE,EAAE,CAAC;gBACzB,OAAO,eAAe,CAAC,qBAAqB,EAAE,qDAAqD,CAAC,CAAC;YACvG,CAAC;YACD,OAAO,EAAE,CAAC;gBACR,MAAM,EAAE,OAAO,CAAC,MAAM;gBACtB,KAAK,EAAE,KAAK,CAAC,KAAK;gBAClB,OAAO,EACL,iNAAiN;aACpN,CAAC,CAAC;QACL,CAAC;QACD,KAAK,CAAC,MAAM,CAAC,OAAsB;YACjC,MAAM,SAAS,GAAG,qBAAqB,CAAC,OAAO,CAAC,CAAC;YACjD,IAAI,CAAC,SAAS,CAAC,EAAE;gBAAE,OAAO,SAAS,CAAC;YACpC,MAAM,KAAK,GAAG,YAAY,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;YAC1C,IAAI,CAAC,KAAK,CAAC,EAAE;gBAAE,OAAO,KAAK,CAAC;YAC5B,MAAM,OAAO,GAAG,cAAc,EAAE,CAAC;YACjC,IAAI,CAAC,OAAO,CAAC,EAAE;gBAAE,OAAO,OAAO,CAAC;YAChC,MAAM,IAAI,GAAG,MAAM,KAAK,EAAE,CAAC;YAC3B,IAAI,CAAC,IAAI,CAAC,EAAE;gBAAE,OAAO,IAAI,CAAC;YAC1B,MAAM,SAAS,GAAG,MAAM,eAAe,CAAC,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;YAChG,IAAI,CAAC,SAAS,CAAC,EAAE;gBAAE,OAAO,SAAS,CAAC;YACpC,MAAM,IAAI,GAAG,MAAM,IAAI,CACrB,OAAO,CAAC,KAAK,EACb,aAAa,CAAC,kBAAkB,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,SAAS,EAAE,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,EACpG,mBAAmB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,aAAa,CAClF,CAAC;YACF,IAAI,CAAC,IAAI,CAAC,EAAE;gBAAE,OAAO,IAAI,CAAC;YAC1B,MAAM,IAAI,GAAG,MAAM,WAAW,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;YAC1D,IAAI,CAAC,IAAI,CAAC,EAAE;gBAAE,OAAO,IAAI,CAAC;YAC1B,OAAO,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,IAAW,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC;QAC3G,CAAC;QACD,KAAK,CAAC,WAAW,CAAC,QAAkC;YAClD,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC1B,OAAO,eAAe,CAAC,aAAa,EAAE,sCAAsC,CAAC,CAAC;YAChF,CAAC;YACD,MAAM,eAAe,GAAe,EAAE,CAAC;YACvC,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;gBAC/B,MAAM,SAAS,GAAG,qBAAqB,CAAC,OAAO,CAAC,CAAC;gBACjD,IAAI,CAAC,SAAS,CAAC,EAAE;oBAAE,OAAO,SAAS,CAAC;gBACpC,MAAM,KAAK,GAAG,YAAY,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;gBAC1C,IAAI,CAAC,KAAK,CAAC,EAAE;oBAAE,OAAO,KAAK,CAAC;gBAC5B,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YACpC,CAAC;YACD,MAAM,KAAK,GAAG,eAAe,CAAC,CAAC,CAAC,CAAC;YACjC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;gBACxB,OAAO,eAAe,CAAC,aAAa,EAAE,sCAAsC,CAAC,CAAC;YAChF,CAAC;YACD,KAAK,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,eAAe,CAAC,OAAO,EAAE,EAAE,CAAC;gBACvD,IAAI,KAAK,CAAC,OAAO,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC;oBACpC,OAAO,eAAe,CACpB,mBAAmB,EACnB,qBAAqB,KAAK,qBAAqB,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC,mBAAmB,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC,wCAAwC,EAC9J,EAAE,OAAO,EAAE,EAAE,KAAK,EAAE,EAAE,CACvB,CAAC;gBACJ,CAAC;YACH,CAAC;YACD,MAAM,OAAO,GAAG,cAAc,EAAE,CAAC;YACjC,IAAI,CAAC,OAAO,CAAC,EAAE;gBAAE,OAAO,OAAO,CAAC;YAChC,MAAM,IAAI,GAAG,MAAM,KAAK,EAAE,CAAC;YAC3B,IAAI,CAAC,IAAI,CAAC,EAAE;gBAAE,OAAO,IAAI,CAAC;YAC1B,MAAM,IAAI,GAAG,IAAI,GAAG,EAA8C,CAAC;YACnE,KAAK,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,eAAe,CAAC,OAAO,EAAE,EAAE,CAAC;gBACvD,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;gBAChC,IAAI,OAAO,KAAK,SAAS;oBAAE,SAAS;gBACpC,MAAM,GAAG,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;gBAC5B,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;gBAC9B,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,OAAO,EAAE,KAAK,IAAI,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;YAC3E,CAAC;YACD,KAAK,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;gBAC7C,MAAM,SAAS,GAAG,MAAM,eAAe,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;gBACjF,IAAI,CAAC,SAAS,CAAC,EAAE;oBAAE,OAAO,SAAS,CAAC;YACtC,CAAC;YACD,MAAM,YAAY,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,CAAC;gBACpD,OAAO,CAAC,SAAS;gBACjB,CAAC,eAAe,CAAC,KAAK,CAAC,IAAI,OAAO,CAAC,KAAK,CAAC,CAAC,OAAO;gBACjD,OAAO,CAAC,MAAM;aACf,CAAC,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,IAAI,CACrB,OAAO,CAAC,KAAK,EACb,aAAa,CAAC,kBAAkB,CAAC,aAAa,EAAE,CAAC,YAAY,CAAC,CAAC,EAC/D,cAAc,QAAQ,CAAC,MAAM,cAAc,CAC5C,CAAC;YACF,IAAI,CAAC,IAAI,CAAC,EAAE;gBAAE,OAAO,IAAI,CAAC;YAC1B,MAAM,IAAI,GAAG,MAAM,WAAW,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;YAC1D,IAAI,CAAC,IAAI,CAAC,EAAE;gBAAE,OAAO,IAAI,CAAC;YAC1B,OAAO,EAAE,CACP,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,CAC9B,UAAU,CAAC,OAAO,EAAE,eAAe,CAAC,KAAK,CAAC,IAAI,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,IAAW,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,IAAI,IAAI,EAAE,KAAK,CAAC,CACrI,CACF,CAAC;QACJ,CAAC;KACF,CAAC;IACF,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,MAA0C,EAAE,EAAU,EAAE,MAAc;IAChG,MAAM,OAAO,GAAG,IAAI,GAAG,EAAoB,CAAC;IAC5C,KAAK,MAAM,CAAC,WAAW,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAC1D,MAAM,SAAS,GAAG,gBAAgB,CAAC,KAAK,EAAE,WAAW,WAAW,IAAI,CAAC,CAAC;QACtE,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,CAAC;YAClB,MAAM,CAAC,KAAK,CAAC,sCAAsC,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,GAAG,EAAE,WAAW,EAAE,OAAO,EAAE,SAAS,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;YAC7H,SAAS;QACX,CAAC;QACD,MAAM,OAAO,GAAG,QAAQ,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QAC1C,IAAI,WAAW,CAAC,WAAW,EAAE,KAAK,OAAO,EAAE,CAAC;YAC1C,MAAM,CAAC,IAAI,CAAC,wEAAwE,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,WAAW,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC,CAAC;QAC9I,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,SAAS,CAAC,KAAK,CAAC,CAAC;IACxC,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC","sourcesContent":["/**\n * The Monad payment strategy: approve the Asset, call `TabSettlement.settle`, and\n * read the Settlement back off the receipt.\n *\n * The transaction is signed by the Agent's own key. Nothing is escrowed and no\n * facilitator is asked: the Asset moves straight to the Service's Collection\n * address and `TabBook` applies the Settlement in the same transaction, so the\n * transaction hash is the whole proof of payment.\n *\n * ## The signer\n *\n * A structural `EthersV6Signer` rather than the `ethers` class, so a test double\n * or any wallet that can sign a transaction satisfies it. An `ethers.Signer`\n * satisfies it as is; the `Assert` below fails to compile if that ever stops\n * being true.\n */\nimport { Interface, MaxUint256, getAddress, isAddress as isEthersAddress } from \"ethers\";\nimport type { Signer } from \"ethers\";\nimport { causeOf, ok, wrap, type Address, type Bytes32, type Hex, type Result } from \"../_shared/index.js\";\nimport { defaultLogger, type Logger } from \"../logger.js\";\nimport { chainError, upstreamError, validationError } from \"../errors.js\";\nimport { ERC20_ABI, TAB_SETTLEMENT_ABI } from \"./abi.js\";\nimport {\n assetKey,\n validateAssetRef,\n validateSettleRequest,\n type AssetRef,\n type ChargeQuote,\n type ChargeRequest,\n type PaymentStrategy,\n type SettleRequest,\n type SettlementReceipt,\n} from \"./strategy.js\";\n\nexport const MONAD_STRATEGY_ID = \"monad\";\n\nexport interface EthersV6TransactionRequest {\n readonly to: string;\n readonly data: string;\n readonly value?: bigint;\n}\n\n/** One log as the receipt carries it. */\nexport interface EthersV6Log {\n readonly address: string;\n readonly topics: readonly string[];\n readonly data: string;\n}\n\nexport interface EthersV6TransactionReceipt {\n readonly hash: string;\n readonly status: number | null;\n readonly logs: readonly EthersV6Log[];\n}\n\nexport interface EthersV6TransactionResponse {\n readonly hash: string;\n wait?(confirmations?: number): Promise<EthersV6TransactionReceipt | null>;\n}\n\nexport interface EthersV6CallProvider {\n call(transaction: { readonly to: string; readonly data: string }): Promise<string>;\n}\n\nexport interface EthersV6Signer {\n getAddress(): Promise<string>;\n sendTransaction(transaction: EthersV6TransactionRequest): Promise<EthersV6TransactionResponse>;\n readonly provider?: EthersV6CallProvider | null;\n}\n\ntype Assert<T extends true> = T;\ntype SignerIsAccepted = Assert<Signer extends EthersV6Signer ? true : false>;\nexport type EthersV6SignerAcceptsEthersSigner = SignerIsAccepted;\n\nexport interface MonadStrategyConfig {\n readonly signer: EthersV6Signer;\n /** The deployed `TabSettlement`. */\n readonly tabSettlement: Address;\n /** The Assets this strategy will settle in, keyed by `<chainId>:<address>`. */\n readonly assets: Readonly<Record<string, AssetRef>>;\n readonly id?: string;\n /** `exact` approves each amount; `unlimited` approves once. Default `exact`. */\n readonly approval?: \"exact\" | \"unlimited\";\n /** `skip` sends without reading the allowance first, for a signer with no provider. */\n readonly allowanceCheck?: \"read\" | \"skip\";\n /** Whether to wait for the receipt and decode the Settlement from it. Default `wait`. */\n readonly receipt?: \"wait\" | \"skip\";\n readonly logger?: Logger;\n readonly now?: () => number;\n}\n\nconst erc20 = new Interface(ERC20_ABI);\nconst tabSettlement = new Interface(TAB_SETTLEMENT_ABI);\nconst settledTopic = tabSettlement.getEvent(\"Settled\")?.topicHash.toLowerCase() ?? \"\";\n\n/** What one `Settled` log says. */\nexport interface SettledLog {\n readonly settlementId: Bytes32;\n readonly agent: Address;\n readonly serviceId: Bytes32;\n readonly applied: bigint;\n readonly toPrepaid: bigint;\n}\n\n/** Reads one `Settled` log emitted by the surface, or null for any other log. */\nexport function decodeSettled(log: EthersV6Log, surface: Address): SettledLog | null {\n if (log.address.toLowerCase() !== surface.toLowerCase()) return null;\n if ((log.topics[0] ?? \"\").toLowerCase() !== settledTopic) return null;\n const fragment = tabSettlement.getEvent(\"Settled\");\n if (fragment === null) return null;\n const decoded = tabSettlement.decodeEventLog(fragment, log.data, [...log.topics]);\n return {\n settlementId: String(decoded.settlementId).toLowerCase() as Bytes32,\n agent: getAddress(String(decoded.agent)) as Address,\n serviceId: String(decoded.serviceId).toLowerCase() as Bytes32,\n applied: BigInt(decoded.applied as bigint),\n toPrepaid: BigInt(decoded.toPrepaid as bigint),\n };\n}\n\nexport function createMonadStrategy(config: MonadStrategyConfig): PaymentStrategy {\n const logger = config.logger ?? defaultLogger;\n const id = config.id ?? MONAD_STRATEGY_ID;\n const now = config.now ?? (() => Date.now());\n const approval = config.approval ?? \"exact\";\n const allowanceCheck = config.allowanceCheck ?? \"read\";\n const receiptMode = config.receipt ?? \"wait\";\n const assets = indexAssets(config.assets, id, logger);\n const chainIds = [...new Set([...assets.values()].map((asset) => asset.chainId))].sort((left, right) =>\n left < right ? -1 : left > right ? 1 : 0,\n );\n\n const known = (asset: AssetRef): AssetRef | undefined => assets.get(assetKey(asset));\n\n const requireKnown = (asset: AssetRef): Result<AssetRef> => {\n const shape = validateAssetRef(asset, \"request.asset\");\n if (!shape.ok) return shape;\n const found = known(shape.value);\n if (found === undefined) {\n return validationError(\"ASSET_NOT_CONFIGURED\", `strategy \\`${id}\\` is not configured for asset ${assetKey(shape.value)}`, {\n details: { asset: assetKey(shape.value), configured: [...assets.keys()].join(\", \") },\n });\n }\n if (found.decimals !== shape.value.decimals) {\n return validationError(\n \"ASSET_DECIMALS_MISMATCH\",\n `asset ${assetKey(found)} is configured with ${found.decimals} decimals and the request names ${shape.value.decimals}; amounts are base units and the two must agree`,\n { details: { asset: assetKey(found), configured: found.decimals, requested: shape.value.decimals } },\n );\n }\n return ok(found);\n };\n\n const requireSurface = (): Result<Address> => {\n const address = config.tabSettlement;\n if (typeof address !== \"string\" || !isEthersAddress(address)) {\n return validationError(\n \"SETTLEMENT_CONTRACT_INVALID\",\n `strategy \\`${id}\\` was given \\`${String(address)}\\` as its tabSettlement, which is not an address`,\n );\n }\n return ok(getAddress(address) as Address);\n };\n\n const payer = async (): Promise<Result<Address>> =>\n wrap(\n async () => getAddress(await config.signer.getAddress()) as Address,\n (error) => ({\n category: \"UPSTREAM\",\n code: \"SIGNER_ADDRESS_UNAVAILABLE\",\n message: `strategy \\`${id}\\` could not read the payer address from its signer`,\n retryable: true,\n cause: causeOf(error),\n }),\n );\n\n const send = async (to: Address, data: string, what: string): Promise<Result<EthersV6TransactionResponse>> => {\n const response = await wrap(\n async () => config.signer.sendTransaction({ to, data }),\n (error) => ({\n category: \"CHAIN\",\n code: \"SETTLEMENT_SUBMISSION_FAILED\",\n message: `strategy \\`${id}\\` could not submit ${what}`,\n retryable: true,\n details: { to },\n cause: causeOf(error),\n }),\n );\n if (!response.ok) return response;\n const hash = response.value.hash;\n if (typeof hash !== \"string\" || !hash.startsWith(\"0x\")) {\n return chainError(\"SETTLEMENT_HASH_MISSING\", `strategy \\`${id}\\` submitted ${what} but the signer returned no transaction hash`);\n }\n return ok(response.value);\n };\n\n /** Waits for the receipt and returns every `Settled` log the surface emitted in it. */\n const settledLogs = async (\n response: EthersV6TransactionResponse,\n surface: Address,\n ): Promise<Result<readonly SettledLog[] | null>> => {\n if (receiptMode === \"skip\" || typeof response.wait !== \"function\") return ok(null);\n const receipt = await wrap(\n async () => response.wait?.() ?? null,\n (error) => ({\n category: \"CHAIN\",\n code: \"SETTLEMENT_RECEIPT_UNAVAILABLE\",\n message: `strategy \\`${id}\\` submitted ${response.hash} but could not read its receipt`,\n retryable: true,\n details: { txHash: response.hash },\n cause: causeOf(error),\n }),\n );\n if (!receipt.ok) return receipt;\n if (receipt.value === null) return ok(null);\n if (receipt.value.status === 0) {\n return chainError(\"SETTLEMENT_REVERTED\", `settlement ${response.hash} was mined and reverted`, {\n details: { txHash: response.hash },\n });\n }\n const logs: SettledLog[] = [];\n for (const log of receipt.value.logs) {\n const settled = decodeSettled(log, surface);\n if (settled !== null) logs.push(settled);\n }\n return ok(logs);\n };\n\n const ensureAllowance = async (asset: AssetRef, owner: Address, spender: Address, needed: bigint): Promise<Result<void>> => {\n if (allowanceCheck === \"skip\") return ok(undefined);\n const provider = config.signer.provider;\n if (provider === undefined || provider === null) {\n return upstreamError(\n \"PROVIDER_REQUIRED\",\n `strategy \\`${id}\\` needs a connected signer to read the allowance before settling; connect one or set allowanceCheck to \"skip\"`,\n );\n }\n const current = await wrap(\n async () => {\n const raw = await provider.call({ to: asset.address, data: erc20.encodeFunctionData(\"allowance\", [owner, spender]) });\n return erc20.decodeFunctionResult(\"allowance\", raw)[0] as bigint;\n },\n (error) => ({\n category: \"UPSTREAM\",\n code: \"ALLOWANCE_READ_FAILED\",\n message: `strategy \\`${id}\\` could not read the allowance of ${owner} for ${spender} on ${assetKey(asset)}`,\n retryable: true,\n cause: causeOf(error),\n }),\n );\n if (!current.ok) return current;\n if (current.value >= needed) return ok(undefined);\n const amount = approval === \"unlimited\" ? MaxUint256 : needed;\n const approved = await send(\n asset.address,\n erc20.encodeFunctionData(\"approve\", [spender, amount]),\n `the allowance top-up for ${spender} on ${assetKey(asset)}`,\n );\n if (!approved.ok) return approved;\n // The approval has to land before the settlement is sent, or the settle reverts\n // on allowance. Waiting here costs one block and saves a wasted transaction.\n if (typeof approved.value.wait === \"function\") {\n const landed = await wrap(\n async () => approved.value.wait?.() ?? null,\n (error) => ({\n category: \"CHAIN\",\n code: \"APPROVAL_RECEIPT_UNAVAILABLE\",\n message: `strategy \\`${id}\\` could not confirm the allowance top-up ${approved.value.hash}`,\n retryable: true,\n cause: causeOf(error),\n }),\n );\n if (!landed.ok) return landed;\n }\n logger.debug(\"allowance topped up\", { strategyId: id, asset: assetKey(asset), spender, amount: amount.toString(10), txHash: approved.value.hash });\n return ok(undefined);\n };\n\n const receiptFor = (\n request: SettleRequest,\n asset: AssetRef,\n payerAddress: Address,\n txHash: Hex,\n settled: SettledLog | null,\n batchIndex?: number,\n ): SettlementReceipt => ({\n strategyId: id,\n chainId: asset.chainId,\n txHash,\n asset,\n amount: request.amount,\n payer: payerAddress,\n serviceId: request.serviceId,\n settlementId: settled?.settlementId ?? null,\n applied: settled?.applied ?? null,\n toPrepaid: settled?.toPrepaid ?? null,\n submittedAt: now(),\n ...(batchIndex === undefined ? {} : { batchIndex }),\n });\n\n const strategy: PaymentStrategy = {\n id,\n chainIds,\n supports(asset) {\n return known(asset) !== undefined;\n },\n async quote(request: ChargeRequest): Promise<Result<ChargeQuote>> {\n const asset = requireKnown(request.asset);\n if (!asset.ok) return asset;\n if (typeof request.amount !== \"bigint\") {\n return validationError(\"CHARGE_INVALID\", \"request.amount must be a bigint count of Asset base units, never a number\");\n }\n if (request.amount <= 0n) {\n return validationError(\"AMOUNT_NOT_POSITIVE\", \"request.amount must be greater than zero base units\");\n }\n return ok({\n amount: request.amount,\n asset: asset.value,\n feeNote:\n \"A stablecoin settles one-for-one in base units: the amount charged is the amount transferred, with no rate and no protocol fee. Gas is paid separately in MON and is never deducted from the Settlement amount.\",\n });\n },\n async settle(request: SettleRequest): Promise<Result<SettlementReceipt>> {\n const validated = validateSettleRequest(request);\n if (!validated.ok) return validated;\n const asset = requireKnown(request.asset);\n if (!asset.ok) return asset;\n const surface = requireSurface();\n if (!surface.ok) return surface;\n const from = await payer();\n if (!from.ok) return from;\n const allowance = await ensureAllowance(asset.value, from.value, surface.value, request.amount);\n if (!allowance.ok) return allowance;\n const sent = await send(\n surface.value,\n tabSettlement.encodeFunctionData(\"settle\", [request.serviceId, asset.value.address, request.amount]),\n `a settlement of ${request.amount.toString(10)} ${asset.value.symbol} base units`,\n );\n if (!sent.ok) return sent;\n const logs = await settledLogs(sent.value, surface.value);\n if (!logs.ok) return logs;\n return ok(receiptFor(request, asset.value, from.value, sent.value.hash as Hex, logs.value?.[0] ?? null));\n },\n async settleBatch(requests: readonly SettleRequest[]): Promise<Result<readonly SettlementReceipt[]>> {\n if (requests.length === 0) {\n return validationError(\"EMPTY_BATCH\", \"settleBatch was given no settlements\");\n }\n const validatedAssets: AssetRef[] = [];\n for (const request of requests) {\n const validated = validateSettleRequest(request);\n if (!validated.ok) return validated;\n const asset = requireKnown(request.asset);\n if (!asset.ok) return asset;\n validatedAssets.push(asset.value);\n }\n const first = validatedAssets[0];\n if (first === undefined) {\n return validationError(\"EMPTY_BATCH\", \"settleBatch was given no settlements\");\n }\n for (const [index, asset] of validatedAssets.entries()) {\n if (asset.chainId !== first.chainId) {\n return validationError(\n \"BATCH_CHAIN_MIXED\",\n `settleBatch entry ${index} settles on chain ${asset.chainId.toString(10)} and entry 0 on ${first.chainId.toString(10)}; one transaction settles on one chain`,\n { details: { index } },\n );\n }\n }\n const surface = requireSurface();\n if (!surface.ok) return surface;\n const from = await payer();\n if (!from.ok) return from;\n const owed = new Map<string, { asset: AssetRef; total: bigint }>();\n for (const [index, asset] of validatedAssets.entries()) {\n const request = requests[index];\n if (request === undefined) continue;\n const key = assetKey(asset);\n const running = owed.get(key);\n owed.set(key, { asset, total: (running?.total ?? 0n) + request.amount });\n }\n for (const { asset, total } of owed.values()) {\n const allowance = await ensureAllowance(asset, from.value, surface.value, total);\n if (!allowance.ok) return allowance;\n }\n const instructions = requests.map((request, index) => [\n request.serviceId,\n (validatedAssets[index] ?? request.asset).address,\n request.amount,\n ]);\n const sent = await send(\n surface.value,\n tabSettlement.encodeFunctionData(\"settleBatch\", [instructions]),\n `a batch of ${requests.length} settlements`,\n );\n if (!sent.ok) return sent;\n const logs = await settledLogs(sent.value, surface.value);\n if (!logs.ok) return logs;\n return ok(\n requests.map((request, index) =>\n receiptFor(request, validatedAssets[index] ?? request.asset, from.value, sent.value.hash as Hex, logs.value?.[index] ?? null, index),\n ),\n );\n },\n };\n return strategy;\n}\n\nexport function indexAssets(assets: Readonly<Record<string, AssetRef>>, id: string, logger: Logger): Map<string, AssetRef> {\n const indexed = new Map<string, AssetRef>();\n for (const [declaredKey, value] of Object.entries(assets)) {\n const validated = validateAssetRef(value, `assets[\"${declaredKey}\"]`);\n if (!validated.ok) {\n logger.error(\"payment strategy asset entry ignored\", { strategyId: id, key: declaredKey, message: validated.error.message });\n continue;\n }\n const derived = assetKey(validated.value);\n if (declaredKey.toLowerCase() !== derived) {\n logger.warn(\"payment strategy asset key disagrees with its value and the value wins\", { strategyId: id, declaredKey, derivedKey: derived });\n }\n indexed.set(derived, validated.value);\n }\n return indexed;\n}\n"]}
@@ -0,0 +1,118 @@
1
+ import { type Address, type Bytes32, type Hex, type Result } from "../_shared/index.js";
2
+ import { type Logger } from "../logger.js";
3
+ import { type EthersV6CallProvider, type EthersV6TransactionRequest, type EthersV6TransactionResponse } from "./monad.js";
4
+ import { type AssetRef, type PaymentStrategy } from "./strategy.js";
5
+ export declare const RELAYED_MONAD_STRATEGY_ID = "monad-relayed";
6
+ /** How long a signature stays valid when the caller names no deadline: ten minutes. */
7
+ export declare const DEFAULT_PERMIT_TTL_SECONDS = 600;
8
+ /** A signer that can produce EIP-712 signatures, which an ethers `Signer` can. */
9
+ export interface Permit2Signer {
10
+ getAddress(): Promise<string>;
11
+ signTypedData(domain: {
12
+ name: string;
13
+ chainId: bigint;
14
+ verifyingContract: string;
15
+ }, types: Record<string, readonly {
16
+ name: string;
17
+ type: string;
18
+ }[]>, value: Record<string, unknown>): Promise<string>;
19
+ readonly provider?: EthersV6CallProvider | null;
20
+ sendTransaction?(transaction: EthersV6TransactionRequest): Promise<EthersV6TransactionResponse>;
21
+ }
22
+ export interface SettlementPermitInput {
23
+ readonly signer: Permit2Signer;
24
+ readonly chainId: bigint | number;
25
+ /** The deployed `TabSettlement`, which is the `spender` Permit2 sees. */
26
+ readonly tabSettlement: Address;
27
+ readonly serviceId: Bytes32;
28
+ readonly asset: Address;
29
+ readonly amount: bigint;
30
+ /** The canonical Permit2 unless a deployment overrides it. */
31
+ readonly permit2?: Address;
32
+ /** A Permit2 unordered nonce. Random when omitted, which is what Permit2 is built for. */
33
+ readonly nonce?: bigint;
34
+ /** Unix seconds. Defaults to now plus {@link DEFAULT_PERMIT_TTL_SECONDS}. */
35
+ readonly deadline?: bigint;
36
+ readonly now?: () => number;
37
+ }
38
+ /** Exactly what `settleWithPermit2` takes, plus what was signed, for the record. */
39
+ export interface SettlementPermit {
40
+ readonly agent: Address;
41
+ readonly serviceId: Bytes32;
42
+ readonly asset: Address;
43
+ readonly amount: bigint;
44
+ readonly nonce: bigint;
45
+ readonly deadline: bigint;
46
+ readonly signature: Hex;
47
+ readonly chainId: bigint;
48
+ readonly tabSettlement: Address;
49
+ readonly permit2: Address;
50
+ }
51
+ /** A random 256-bit unordered nonce. Permit2 keys them per owner in a bitmap. */
52
+ export declare const randomPermitNonce: () => bigint;
53
+ /**
54
+ * Signs one Settlement under Permit2's domain.
55
+ *
56
+ * The typed data is `PermitWitnessTransferFrom` with `TabSettlement` as the
57
+ * witness struct, field for field what the contract rebuilds before it hands
58
+ * Permit2 the signature. Nothing is sent.
59
+ */
60
+ export declare function signSettlementPermit(input: SettlementPermitInput): Promise<Result<SettlementPermit>>;
61
+ /** Calldata for `settleWithPermit2`, for a relayer that submits it itself. */
62
+ export declare function encodeSettleWithPermit2(permit: SettlementPermit): Hex;
63
+ /** The JSON body a relay endpoint takes: the permit with every integer as decimal text. */
64
+ export interface RelaySettleBody {
65
+ readonly agent: string;
66
+ readonly serviceId: string;
67
+ readonly asset: string;
68
+ readonly amount: string;
69
+ readonly nonce: string;
70
+ readonly deadline: string;
71
+ readonly signature: string;
72
+ readonly chainId: string;
73
+ readonly tabSettlement: string;
74
+ }
75
+ export declare function toRelayBody(permit: SettlementPermit): RelaySettleBody;
76
+ /** Reads a relay body back into a permit, refusing anything that is not one. */
77
+ export declare function fromRelayBody(value: unknown, permit2?: Address): Result<SettlementPermit>;
78
+ /** What a relay answers once the Settlement is mined. */
79
+ export interface RelaySettleReply {
80
+ readonly ok: true;
81
+ readonly txHash: string;
82
+ readonly settlementId: string | null;
83
+ readonly applied: string | null;
84
+ readonly toPrepaid: string | null;
85
+ }
86
+ /** The little of `fetch` the strategy uses. */
87
+ export type RelayFetch = (url: string, init: {
88
+ method: string;
89
+ headers: Record<string, string>;
90
+ body: string;
91
+ }) => Promise<{
92
+ readonly status: number;
93
+ json(): Promise<unknown>;
94
+ }>;
95
+ export interface RelayedMonadStrategyConfig {
96
+ readonly signer: Permit2Signer;
97
+ readonly tabSettlement: Address;
98
+ /** The relay endpoint, for example `https://gateway.example/relay/settle`. */
99
+ readonly relayUrl: string;
100
+ readonly assets: Readonly<Record<string, AssetRef>>;
101
+ readonly permit2?: Address;
102
+ readonly id?: string;
103
+ /**
104
+ * What to do when the Agent has not approved Permit2 on the Asset. `require`
105
+ * refuses with `PERMIT2_ALLOWANCE_MISSING`; `approve` sends the one-time
106
+ * unlimited approval first, which needs MON once; `skip` does not read it.
107
+ */
108
+ readonly allowance?: "require" | "approve" | "skip";
109
+ readonly fetchImpl?: RelayFetch;
110
+ readonly logger?: Logger;
111
+ readonly now?: () => number;
112
+ }
113
+ /**
114
+ * A strategy that settles by signature and a relay, never by a transaction of
115
+ * the Agent's own.
116
+ */
117
+ export declare function createRelayedMonadStrategy(config: RelayedMonadStrategyConfig): PaymentStrategy;
118
+ //# sourceMappingURL=permit2.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permit2.d.ts","sourceRoot":"","sources":["../../src/payments/permit2.ts"],"names":[],"mappings":"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"}