@catena/sdk 0.1.0 → 0.4.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.
- package/README.md +789 -358
- package/dist/{client-CHZMO00P.mjs → client-BSYVYJGm.mjs} +196 -21
- package/dist/{client-Cm2wNUuB.d.mts → client-B_Gi0aqH.d.mts} +270 -13
- package/dist/client.d.mts +2 -2
- package/dist/client.mjs +2 -2
- package/dist/movements.d.mts +139 -0
- package/dist/movements.mjs +334 -0
- package/dist/mpp.d.mts +42 -30
- package/dist/mpp.mjs +13 -14
- package/dist/{settlement-report-CT3EbtLL.mjs → settlement-report-DTxaAxE3.mjs} +22 -1
- package/dist/viem.d.mts +18 -18
- package/dist/viem.mjs +10 -15
- package/dist/x402.d.mts +21 -15
- package/dist/x402.mjs +8 -14
- package/package.json +9 -9
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import * as v from "valibot";
|
|
2
|
+
import { getAddress, hashAuthorization, hashTypedData, keccak256, serializeTransaction } from "viem/utils";
|
|
3
|
+
//#region src/movements.ts
|
|
4
|
+
const addressSchema = v.pipe(v.string(), v.transform(getAddress));
|
|
5
|
+
const digitsSchema = v.pipe(v.string(), v.regex(/^\d+$/));
|
|
6
|
+
const hexBytesSchema = v.custom((value) => typeof value === "string" && /^0x([0-9a-fA-F]{2})*$/.test(value), "expected hex bytes");
|
|
7
|
+
const bytes32Schema = v.custom((value) => typeof value === "string" && /^0x[0-9a-fA-F]{64}$/.test(value), "expected a 32-byte value");
|
|
8
|
+
const USDC_EIP3009_NETWORKS = ["base", "base-sepolia"];
|
|
9
|
+
const USDC_EIP3009_DOMAINS = {
|
|
10
|
+
base: {
|
|
11
|
+
name: "USD Coin",
|
|
12
|
+
version: "2",
|
|
13
|
+
chainId: 8453,
|
|
14
|
+
verifyingContract: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
|
|
15
|
+
},
|
|
16
|
+
"base-sepolia": {
|
|
17
|
+
name: "USDC",
|
|
18
|
+
version: "2",
|
|
19
|
+
chainId: 84532,
|
|
20
|
+
verifyingContract: "0x036CbD53842c5426634e7929541eC2318f3dCF7e"
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
const GAS_DELEGATE_DOMAIN = {
|
|
24
|
+
name: "TKGasDelegate",
|
|
25
|
+
version: "1.1"
|
|
26
|
+
};
|
|
27
|
+
const GAS_DELEGATE_ADDRESS = "0x955d84139e7621bc571b117d8eb5d28a4a222c6f";
|
|
28
|
+
const NETWORK_CHAIN_IDS = {
|
|
29
|
+
...Object.fromEntries(Object.entries(USDC_EIP3009_DOMAINS).map(([network, domain]) => [network, domain.chainId])),
|
|
30
|
+
arc: 5042
|
|
31
|
+
};
|
|
32
|
+
const rawTransactionSchema = v.looseObject({ transaction: v.object({
|
|
33
|
+
chainId: v.pipe(v.number(), v.integer(), v.minValue(1)),
|
|
34
|
+
nonce: v.pipe(v.number(), v.integer(), v.minValue(0)),
|
|
35
|
+
to: addressSchema,
|
|
36
|
+
value: digitsSchema,
|
|
37
|
+
data: hexBytesSchema,
|
|
38
|
+
gas: digitsSchema,
|
|
39
|
+
maxFeePerGasWei: digitsSchema,
|
|
40
|
+
maxPriorityFeePerGasWei: digitsSchema
|
|
41
|
+
}) });
|
|
42
|
+
const transferAuthorizationSchema = v.looseObject({
|
|
43
|
+
network: v.picklist(USDC_EIP3009_NETWORKS),
|
|
44
|
+
authorization: v.object({
|
|
45
|
+
from: addressSchema,
|
|
46
|
+
to: addressSchema,
|
|
47
|
+
value: digitsSchema,
|
|
48
|
+
validAfter: v.union([v.number(), digitsSchema]),
|
|
49
|
+
validBefore: v.union([v.number(), digitsSchema]),
|
|
50
|
+
nonce: bytes32Schema
|
|
51
|
+
})
|
|
52
|
+
});
|
|
53
|
+
const delegationSchema = v.looseObject({ authorization: v.object({
|
|
54
|
+
address: addressSchema,
|
|
55
|
+
chainId: v.pipe(v.number(), v.integer(), v.minValue(1)),
|
|
56
|
+
nonce: v.pipe(v.number(), v.integer(), v.minValue(0))
|
|
57
|
+
}) });
|
|
58
|
+
const batchExecutionSchema = v.looseObject({ batch: v.object({
|
|
59
|
+
from: addressSchema,
|
|
60
|
+
chainId: v.pipe(v.number(), v.integer(), v.minValue(1)),
|
|
61
|
+
nonce: digitsSchema,
|
|
62
|
+
deadline: v.pipe(v.number(), v.integer(), v.minValue(1)),
|
|
63
|
+
calls: v.pipe(v.array(v.object({
|
|
64
|
+
to: addressSchema,
|
|
65
|
+
value: digitsSchema,
|
|
66
|
+
data: hexBytesSchema
|
|
67
|
+
})), v.minLength(1))
|
|
68
|
+
}) });
|
|
69
|
+
const preparedBodySchema = v.looseObject({
|
|
70
|
+
type: v.literal("ACTIVITY_TYPE_SIGN_RAW_PAYLOADS"),
|
|
71
|
+
parameters: v.looseObject({
|
|
72
|
+
signWith: addressSchema,
|
|
73
|
+
payloads: v.pipe(v.array(bytes32Schema), v.minLength(1)),
|
|
74
|
+
encoding: v.literal("PAYLOAD_ENCODING_HEXADECIMAL"),
|
|
75
|
+
hashFunction: v.literal("HASH_FUNCTION_NO_OP")
|
|
76
|
+
})
|
|
77
|
+
});
|
|
78
|
+
function usdcBaseUnits(amount) {
|
|
79
|
+
const match = /^(\d+)(?:\.(\d{1,6}))?$/.exec(amount.trim());
|
|
80
|
+
if (match === null) return null;
|
|
81
|
+
const [, whole = "0", fraction = ""] = match;
|
|
82
|
+
return BigInt(whole) * 1000000n + BigInt(fraction.padEnd(6, "0"));
|
|
83
|
+
}
|
|
84
|
+
function instructionFacts(instruction) {
|
|
85
|
+
try {
|
|
86
|
+
switch (instruction.kind) {
|
|
87
|
+
case "evm_raw_transaction": {
|
|
88
|
+
const { transaction } = v.parse(rawTransactionSchema, instruction.payload);
|
|
89
|
+
return {
|
|
90
|
+
digest: keccak256(serializeTransaction({
|
|
91
|
+
type: "eip1559",
|
|
92
|
+
chainId: transaction.chainId,
|
|
93
|
+
nonce: transaction.nonce,
|
|
94
|
+
to: transaction.to,
|
|
95
|
+
value: BigInt(transaction.value),
|
|
96
|
+
data: transaction.data,
|
|
97
|
+
gas: BigInt(transaction.gas),
|
|
98
|
+
maxFeePerGas: BigInt(transaction.maxFeePerGasWei),
|
|
99
|
+
maxPriorityFeePerGas: BigInt(transaction.maxPriorityFeePerGasWei),
|
|
100
|
+
accessList: []
|
|
101
|
+
})),
|
|
102
|
+
chainId: transaction.chainId,
|
|
103
|
+
movesNative: BigInt(transaction.value) !== 0n
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
case "eip3009_transfer_authorization": {
|
|
107
|
+
const { network, authorization } = v.parse(transferAuthorizationSchema, instruction.payload);
|
|
108
|
+
return {
|
|
109
|
+
digest: hashTypedData({
|
|
110
|
+
domain: USDC_EIP3009_DOMAINS[network],
|
|
111
|
+
types: { TransferWithAuthorization: [
|
|
112
|
+
{
|
|
113
|
+
name: "from",
|
|
114
|
+
type: "address"
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
name: "to",
|
|
118
|
+
type: "address"
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
name: "value",
|
|
122
|
+
type: "uint256"
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
name: "validAfter",
|
|
126
|
+
type: "uint256"
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
name: "validBefore",
|
|
130
|
+
type: "uint256"
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
name: "nonce",
|
|
134
|
+
type: "bytes32"
|
|
135
|
+
}
|
|
136
|
+
] },
|
|
137
|
+
primaryType: "TransferWithAuthorization",
|
|
138
|
+
message: {
|
|
139
|
+
from: authorization.from,
|
|
140
|
+
to: authorization.to,
|
|
141
|
+
value: BigInt(authorization.value),
|
|
142
|
+
validAfter: BigInt(authorization.validAfter),
|
|
143
|
+
validBefore: BigInt(authorization.validBefore),
|
|
144
|
+
nonce: authorization.nonce
|
|
145
|
+
}
|
|
146
|
+
}),
|
|
147
|
+
chainId: USDC_EIP3009_DOMAINS[network].chainId,
|
|
148
|
+
from: authorization.from
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
case "eip7702_delegation": {
|
|
152
|
+
const { authorization } = v.parse(delegationSchema, instruction.payload);
|
|
153
|
+
return {
|
|
154
|
+
digest: hashAuthorization({
|
|
155
|
+
address: authorization.address,
|
|
156
|
+
chainId: authorization.chainId,
|
|
157
|
+
nonce: authorization.nonce
|
|
158
|
+
}),
|
|
159
|
+
chainId: authorization.chainId,
|
|
160
|
+
delegate: authorization.address
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
case "eip712_execution": {
|
|
164
|
+
const { batch } = v.parse(batchExecutionSchema, instruction.payload);
|
|
165
|
+
return {
|
|
166
|
+
digest: hashTypedData({
|
|
167
|
+
domain: {
|
|
168
|
+
...GAS_DELEGATE_DOMAIN,
|
|
169
|
+
chainId: batch.chainId,
|
|
170
|
+
verifyingContract: batch.from
|
|
171
|
+
},
|
|
172
|
+
types: {
|
|
173
|
+
BatchExecution: [
|
|
174
|
+
{
|
|
175
|
+
name: "nonce",
|
|
176
|
+
type: "uint128"
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
name: "deadline",
|
|
180
|
+
type: "uint32"
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
name: "calls",
|
|
184
|
+
type: "Call[]"
|
|
185
|
+
}
|
|
186
|
+
],
|
|
187
|
+
Call: [
|
|
188
|
+
{
|
|
189
|
+
name: "to",
|
|
190
|
+
type: "address"
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
name: "value",
|
|
194
|
+
type: "uint256"
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
name: "data",
|
|
198
|
+
type: "bytes"
|
|
199
|
+
}
|
|
200
|
+
]
|
|
201
|
+
},
|
|
202
|
+
primaryType: "BatchExecution",
|
|
203
|
+
message: {
|
|
204
|
+
nonce: BigInt(batch.nonce),
|
|
205
|
+
deadline: batch.deadline,
|
|
206
|
+
calls: batch.calls.map((call) => ({
|
|
207
|
+
to: call.to,
|
|
208
|
+
value: BigInt(call.value),
|
|
209
|
+
data: call.data
|
|
210
|
+
}))
|
|
211
|
+
}
|
|
212
|
+
}),
|
|
213
|
+
from: batch.from,
|
|
214
|
+
chainId: batch.chainId,
|
|
215
|
+
movesNative: batch.calls.some((call) => BigInt(call.value) !== 0n)
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
} catch {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
function rebuildInstructionDigest(instruction) {
|
|
225
|
+
return instructionFacts(instruction)?.digest ?? null;
|
|
226
|
+
}
|
|
227
|
+
function sameList(left, right) {
|
|
228
|
+
return left.length === right.length && left.every((value, index) => value === right[index]);
|
|
229
|
+
}
|
|
230
|
+
function verifyPreparedMovement(prepared, requested) {
|
|
231
|
+
if (prepared.instructions.length === 0) return {
|
|
232
|
+
ok: false,
|
|
233
|
+
reason: "The approval carries nothing to sign."
|
|
234
|
+
};
|
|
235
|
+
const signer = prepared.signerAddress.toLowerCase();
|
|
236
|
+
for (const instruction of prepared.instructions) {
|
|
237
|
+
const facts = instructionFacts(instruction);
|
|
238
|
+
if (facts === null || facts.digest.toLowerCase() !== instruction.digest.toLowerCase()) return {
|
|
239
|
+
ok: false,
|
|
240
|
+
reason: "An instruction does not match what it says it signs."
|
|
241
|
+
};
|
|
242
|
+
if (instruction.signerAddress.toLowerCase() !== signer) return {
|
|
243
|
+
ok: false,
|
|
244
|
+
reason: "An instruction names a different wallet than the approval."
|
|
245
|
+
};
|
|
246
|
+
const named = NETWORK_CHAIN_IDS[instruction.network];
|
|
247
|
+
if (named === void 0) return {
|
|
248
|
+
ok: false,
|
|
249
|
+
reason: `An instruction names a network this client cannot place: ${instruction.network}. If the payment looks right, this client may be out of date.`
|
|
250
|
+
};
|
|
251
|
+
if (facts.chainId !== named) return {
|
|
252
|
+
ok: false,
|
|
253
|
+
reason: "An instruction names a different network than it signs on."
|
|
254
|
+
};
|
|
255
|
+
if (facts.delegate !== void 0 && facts.delegate.toLowerCase() !== GAS_DELEGATE_ADDRESS) return {
|
|
256
|
+
ok: false,
|
|
257
|
+
reason: "An instruction would hand the wallet over to an unrecognised contract."
|
|
258
|
+
};
|
|
259
|
+
if (facts.from !== void 0 && facts.from.toLowerCase() !== signer) return {
|
|
260
|
+
ok: false,
|
|
261
|
+
reason: "An instruction would spend from a different wallet."
|
|
262
|
+
};
|
|
263
|
+
if (facts.movesNative === true) return {
|
|
264
|
+
ok: false,
|
|
265
|
+
reason: "An instruction would move the network's own token."
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
const rebuiltDigests = prepared.instructions.map((instruction) => instruction.digest.toLowerCase());
|
|
269
|
+
if (new Set(rebuiltDigests).size !== rebuiltDigests.length) return {
|
|
270
|
+
ok: false,
|
|
271
|
+
reason: "The approval lists the same digest more than once."
|
|
272
|
+
};
|
|
273
|
+
const spentNonces = new Set();
|
|
274
|
+
for (const instruction of prepared.instructions) {
|
|
275
|
+
if (instruction.kind !== "eip3009_transfer_authorization") continue;
|
|
276
|
+
const parsed = v.safeParse(transferAuthorizationSchema, instruction.payload);
|
|
277
|
+
if (!parsed.success) continue;
|
|
278
|
+
const { network, authorization } = parsed.output;
|
|
279
|
+
const spent = `${network}:${authorization.from.toLowerCase()}:${authorization.nonce.toLowerCase()}`;
|
|
280
|
+
if (spentNonces.has(spent)) return {
|
|
281
|
+
ok: false,
|
|
282
|
+
reason: "The approval spends one authorization nonce twice."
|
|
283
|
+
};
|
|
284
|
+
spentNonces.add(spent);
|
|
285
|
+
}
|
|
286
|
+
if (!sameList(prepared.digests.map((digest) => digest.toLowerCase()), rebuiltDigests)) return {
|
|
287
|
+
ok: false,
|
|
288
|
+
reason: "The approval lists different digests than its instructions."
|
|
289
|
+
};
|
|
290
|
+
let body;
|
|
291
|
+
try {
|
|
292
|
+
body = v.parse(preparedBodySchema, JSON.parse(prepared.body));
|
|
293
|
+
} catch {
|
|
294
|
+
return {
|
|
295
|
+
ok: false,
|
|
296
|
+
reason: "The prepared request is not in the shape expected."
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
if (!sameList(body.parameters.payloads.map((payload) => payload.toLowerCase()), rebuiltDigests)) return {
|
|
300
|
+
ok: false,
|
|
301
|
+
reason: "The prepared request would sign different digests."
|
|
302
|
+
};
|
|
303
|
+
if (body.parameters.signWith.toLowerCase() !== signer) return {
|
|
304
|
+
ok: false,
|
|
305
|
+
reason: "The prepared request names a different wallet than the approval."
|
|
306
|
+
};
|
|
307
|
+
return bindsRequestedAmount(prepared, signer, requested);
|
|
308
|
+
}
|
|
309
|
+
function bindsRequestedAmount(prepared, signer, requested) {
|
|
310
|
+
if (requested === void 0) return { ok: true };
|
|
311
|
+
const wanted = usdcBaseUnits(requested.amount);
|
|
312
|
+
if (wanted === null) return {
|
|
313
|
+
ok: false,
|
|
314
|
+
reason: "The requested amount is not an amount this client can check."
|
|
315
|
+
};
|
|
316
|
+
let delivered = 0n;
|
|
317
|
+
for (const instruction of prepared.instructions) {
|
|
318
|
+
if (instruction.kind === "evm_raw_transaction" || instruction.kind === "eip712_execution") return { ok: true };
|
|
319
|
+
if (instruction.kind !== "eip3009_transfer_authorization") continue;
|
|
320
|
+
const parsed = v.safeParse(transferAuthorizationSchema, instruction.payload);
|
|
321
|
+
if (!parsed.success) return {
|
|
322
|
+
ok: false,
|
|
323
|
+
reason: "An instruction does not match what it says it signs."
|
|
324
|
+
};
|
|
325
|
+
if (parsed.output.authorization.to.toLowerCase() !== signer) delivered += BigInt(parsed.output.authorization.value);
|
|
326
|
+
}
|
|
327
|
+
if (delivered === wanted) return { ok: true };
|
|
328
|
+
return {
|
|
329
|
+
ok: false,
|
|
330
|
+
reason: "This approval does not pay the amount that was requested."
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
//#endregion
|
|
334
|
+
export { rebuildInstructionDigest, verifyPreparedMovement };
|
package/dist/mpp.d.mts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { J as WalletCounterpartyRail, N as IntentResult, i as FetchLike, n as CatenaClient } from "./client-B_Gi0aqH.mjs";
|
|
2
2
|
import { Method, z } from "mppx";
|
|
3
3
|
//#region src/mpp.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* CAIP-2 network carried by an EVM MPP charge. Known values provide editor
|
|
6
|
+
* hints, while other strings remain representable because the server decides
|
|
7
|
+
* which networks are payable.
|
|
8
|
+
*/
|
|
9
|
+
export type MppNetwork = "eip155:8453" | "eip155:84532" | "eip155:5042" | (string & {});
|
|
4
10
|
declare const evmCharge: {
|
|
5
11
|
readonly name: "evm";
|
|
6
12
|
readonly intent: "charge";
|
|
@@ -71,7 +77,7 @@ declare const usdcCharge: {
|
|
|
71
77
|
/**
|
|
72
78
|
* Parameters shared by both Catena MPP charge methods.
|
|
73
79
|
*/
|
|
74
|
-
interface CatenaMppParameters {
|
|
80
|
+
export interface CatenaMppParameters {
|
|
75
81
|
/**
|
|
76
82
|
* A linked Catena SDK client used to submit the payment intent.
|
|
77
83
|
*/
|
|
@@ -89,32 +95,32 @@ interface CatenaMppParameters {
|
|
|
89
95
|
/**
|
|
90
96
|
* Catena-backed `evm/charge` mppx client method.
|
|
91
97
|
*/
|
|
92
|
-
type CatenaEvmChargeMethod = Method.Client<typeof evmCharge>;
|
|
98
|
+
export type CatenaEvmChargeMethod = Method.Client<typeof evmCharge>;
|
|
93
99
|
/**
|
|
94
100
|
* Catena-backed `usdc/charge` mppx client method.
|
|
95
101
|
*/
|
|
96
|
-
type CatenaUsdcChargeMethod = Method.Client<typeof usdcCharge>;
|
|
102
|
+
export type CatenaUsdcChargeMethod = Method.Client<typeof usdcCharge>;
|
|
97
103
|
/**
|
|
98
104
|
* Both Catena methods registered by the factory.
|
|
99
105
|
*/
|
|
100
|
-
type CatenaMppMethods = readonly [CatenaEvmChargeMethod, CatenaUsdcChargeMethod];
|
|
106
|
+
export type CatenaMppMethods = readonly [CatenaEvmChargeMethod, CatenaUsdcChargeMethod];
|
|
101
107
|
/**
|
|
102
108
|
* Callable Catena MPP factory, with the conventional charge alias.
|
|
103
109
|
*/
|
|
104
|
-
interface CatenaMppFactory {
|
|
110
|
+
export interface CatenaMppFactory {
|
|
105
111
|
(parameters: CatenaMppParameters): CatenaMppMethods;
|
|
106
112
|
charge(parameters: CatenaMppParameters): CatenaMppMethods;
|
|
107
113
|
}
|
|
108
114
|
/**
|
|
109
115
|
* Status of a Catena MPP intent that did not yield a usable credential.
|
|
110
116
|
*/
|
|
111
|
-
type MppPaymentStatus = IntentResult["status"];
|
|
117
|
+
export type MppPaymentStatus = IntentResult["status"];
|
|
112
118
|
/**
|
|
113
119
|
* A Catena MPP intent completed without a usable credential or stopped in a
|
|
114
120
|
* non-completed state. Inspect `status` to choose a recovery path and
|
|
115
121
|
* `intentId` to reconcile the payment with `client.getIntent(intentId)`.
|
|
116
122
|
*/
|
|
117
|
-
declare class MppPaymentError extends Error {
|
|
123
|
+
export declare class MppPaymentError extends Error {
|
|
118
124
|
readonly intentId: string;
|
|
119
125
|
readonly status: MppPaymentStatus;
|
|
120
126
|
readonly reasons: readonly string[];
|
|
@@ -130,7 +136,7 @@ declare class MppPaymentError extends Error {
|
|
|
130
136
|
* Credential-free record of an MPP payment completed by the managed fetch
|
|
131
137
|
* wrapper.
|
|
132
138
|
*/
|
|
133
|
-
interface MppPaymentReceipt {
|
|
139
|
+
export interface MppPaymentReceipt {
|
|
134
140
|
/**
|
|
135
141
|
* Catena intent that completed the payment.
|
|
136
142
|
*/
|
|
@@ -146,7 +152,7 @@ interface MppPaymentReceipt {
|
|
|
146
152
|
/**
|
|
147
153
|
* CAIP-2 network on which the payment was authorized.
|
|
148
154
|
*/
|
|
149
|
-
readonly network:
|
|
155
|
+
readonly network: MppNetwork;
|
|
150
156
|
/**
|
|
151
157
|
* EVM address that received the payment authorization.
|
|
152
158
|
*/
|
|
@@ -161,7 +167,7 @@ interface MppPaymentReceipt {
|
|
|
161
167
|
* after Catena created an intent; `reasons` carries server recovery detail
|
|
162
168
|
* without exposing the payment credential.
|
|
163
169
|
*/
|
|
164
|
-
declare class MppFetchPaymentError extends Error {
|
|
170
|
+
export declare class MppFetchPaymentError extends Error {
|
|
165
171
|
/**
|
|
166
172
|
* Catena intent to reconcile, when one was created.
|
|
167
173
|
*/
|
|
@@ -179,7 +185,7 @@ declare class MppFetchPaymentError extends Error {
|
|
|
179
185
|
/**
|
|
180
186
|
* None of the offered MPP networks can be funded by the selected account.
|
|
181
187
|
*/
|
|
182
|
-
declare class MppNetworkMismatchError extends MppFetchPaymentError {
|
|
188
|
+
export declare class MppNetworkMismatchError extends MppFetchPaymentError {
|
|
183
189
|
/**
|
|
184
190
|
* Account that needs a wallet on one of the required networks.
|
|
185
191
|
*/
|
|
@@ -187,14 +193,14 @@ declare class MppNetworkMismatchError extends MppFetchPaymentError {
|
|
|
187
193
|
/**
|
|
188
194
|
* Offered networks that Catena reported as mismatches.
|
|
189
195
|
*/
|
|
190
|
-
readonly requiredNetworks: readonly
|
|
191
|
-
constructor(accountId: string, requiredNetworks: readonly
|
|
196
|
+
readonly requiredNetworks: readonly MppNetwork[];
|
|
197
|
+
constructor(accountId: string, requiredNetworks: readonly MppNetwork[], reasons: readonly string[]);
|
|
192
198
|
}
|
|
193
199
|
/**
|
|
194
200
|
* The selected MPP recipient is not saved as a usable Catena counterparty
|
|
195
201
|
* rail.
|
|
196
202
|
*/
|
|
197
|
-
declare class MppCounterpartyNotFoundError extends MppFetchPaymentError {
|
|
203
|
+
export declare class MppCounterpartyNotFoundError extends MppFetchPaymentError {
|
|
198
204
|
/**
|
|
199
205
|
* Recipient address that must be added.
|
|
200
206
|
*/
|
|
@@ -202,14 +208,20 @@ declare class MppCounterpartyNotFoundError extends MppFetchPaymentError {
|
|
|
202
208
|
/**
|
|
203
209
|
* Network on which the recipient was offered.
|
|
204
210
|
*/
|
|
205
|
-
readonly network:
|
|
206
|
-
|
|
211
|
+
readonly network: MppNetwork;
|
|
212
|
+
/**
|
|
213
|
+
* Server-resolved Catena rail that can be passed to a
|
|
214
|
+
* `create_counterparty` intent. Absent when the server sends no valid rail,
|
|
215
|
+
* or when the rail names a different recipient than the challenge.
|
|
216
|
+
*/
|
|
217
|
+
readonly requiredRail: WalletCounterpartyRail | undefined;
|
|
218
|
+
constructor(recipient: string, network: MppNetwork, reasons: readonly string[], requiredRail?: WalletCounterpartyRail);
|
|
207
219
|
}
|
|
208
220
|
/**
|
|
209
221
|
* The MPP payment is parked for human approval. Retry the original request
|
|
210
222
|
* after approval so a fresh challenge can consume the grant.
|
|
211
223
|
*/
|
|
212
|
-
declare class MppApprovalPendingError extends MppFetchPaymentError {
|
|
224
|
+
export declare class MppApprovalPendingError extends MppFetchPaymentError {
|
|
213
225
|
/**
|
|
214
226
|
* Time at which the pending approval expires, when supplied by Catena.
|
|
215
227
|
*/
|
|
@@ -219,7 +231,7 @@ declare class MppApprovalPendingError extends MppFetchPaymentError {
|
|
|
219
231
|
/**
|
|
220
232
|
* Catena terminally blocked or failed the selected MPP payment.
|
|
221
233
|
*/
|
|
222
|
-
declare class MppPaymentDeclinedError extends MppFetchPaymentError {
|
|
234
|
+
export declare class MppPaymentDeclinedError extends MppFetchPaymentError {
|
|
223
235
|
/**
|
|
224
236
|
* Terminal intent disposition.
|
|
225
237
|
*/
|
|
@@ -230,7 +242,7 @@ declare class MppPaymentDeclinedError extends MppFetchPaymentError {
|
|
|
230
242
|
* Submission failed where Catena may already have acted. Reconcile the intent
|
|
231
243
|
* before authorizing another payment.
|
|
232
244
|
*/
|
|
233
|
-
declare class MppSubmitInterruptedError extends MppFetchPaymentError {
|
|
245
|
+
export declare class MppSubmitInterruptedError extends MppFetchPaymentError {
|
|
234
246
|
/**
|
|
235
247
|
* Validated MPP method whose submission became ambiguous.
|
|
236
248
|
*/
|
|
@@ -242,7 +254,7 @@ declare class MppSubmitInterruptedError extends MppFetchPaymentError {
|
|
|
242
254
|
/**
|
|
243
255
|
* Network on which submission may have occurred.
|
|
244
256
|
*/
|
|
245
|
-
readonly network:
|
|
257
|
+
readonly network: MppNetwork;
|
|
246
258
|
/**
|
|
247
259
|
* Recipient whose payment may have been submitted.
|
|
248
260
|
*/
|
|
@@ -254,7 +266,7 @@ declare class MppSubmitInterruptedError extends MppFetchPaymentError {
|
|
|
254
266
|
constructor(intentId: string, terms: {
|
|
255
267
|
readonly method: "evm" | "usdc";
|
|
256
268
|
readonly atomicAmount: string;
|
|
257
|
-
readonly network:
|
|
269
|
+
readonly network: MppNetwork;
|
|
258
270
|
readonly recipient: string;
|
|
259
271
|
}, resourceUrl: string, cause: unknown);
|
|
260
272
|
}
|
|
@@ -262,7 +274,7 @@ declare class MppSubmitInterruptedError extends MppFetchPaymentError {
|
|
|
262
274
|
* Payment completed but `onPayment` or the single paid retry failed. The
|
|
263
275
|
* credential-free receipt identifies the settled intent for reconciliation.
|
|
264
276
|
*/
|
|
265
|
-
declare class MppRetryFailedError extends MppFetchPaymentError {
|
|
277
|
+
export declare class MppRetryFailedError extends MppFetchPaymentError {
|
|
266
278
|
/**
|
|
267
279
|
* Completed payment that must not be paid a second time.
|
|
268
280
|
*/
|
|
@@ -272,7 +284,7 @@ declare class MppRetryFailedError extends MppFetchPaymentError {
|
|
|
272
284
|
/**
|
|
273
285
|
* Options for the managed MPP fetch wrapper.
|
|
274
286
|
*/
|
|
275
|
-
interface MppFetchOptions {
|
|
287
|
+
export interface MppFetchOptions {
|
|
276
288
|
/**
|
|
277
289
|
* Catena account whose wallet authorizes payments.
|
|
278
290
|
*/
|
|
@@ -292,11 +304,11 @@ interface MppFetchOptions {
|
|
|
292
304
|
readonly onPayment?: (receipt: MppPaymentReceipt) => void | Promise<void>;
|
|
293
305
|
}
|
|
294
306
|
/**
|
|
295
|
-
* Create Catena-backed mppx charge methods
|
|
296
|
-
*
|
|
297
|
-
* })`.
|
|
307
|
+
* Create Catena-backed mppx EVM charge methods. The Catena server decides
|
|
308
|
+
* which networks and token contracts are payable. Pass the returned tuple
|
|
309
|
+
* directly in `Mppx.create({ methods: [...] })`.
|
|
298
310
|
*/
|
|
299
|
-
declare const catena: CatenaMppFactory;
|
|
311
|
+
export declare const catena: CatenaMppFactory;
|
|
300
312
|
/**
|
|
301
313
|
* Wrap `fetch` with one managed MPP payment attempt. Ordinary responses and
|
|
302
314
|
* 402s without a decodable MPP challenge pass through unchanged. For a
|
|
@@ -310,6 +322,6 @@ declare const catena: CatenaMppFactory;
|
|
|
310
322
|
* failures throw `MppRetryFailedError`; do not run the payment loop again
|
|
311
323
|
* without first reconciling that receipt's intent.
|
|
312
324
|
*/
|
|
313
|
-
declare function wrapFetchWithMppPayment(client: CatenaClient, options: MppFetchOptions): FetchLike;
|
|
325
|
+
export declare function wrapFetchWithMppPayment(client: CatenaClient, options: MppFetchOptions): FetchLike;
|
|
314
326
|
//#endregion
|
|
315
|
-
export {
|
|
327
|
+
export type { WalletCounterpartyRail };
|
package/dist/mpp.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { a as
|
|
1
|
+
import { k as paymentCredentialFromIntentData, n as IntentSubmitError, t as ApiError } from "./client-BSYVYJGm.mjs";
|
|
2
|
+
import { a as isReplayableFetchRequest, i as fetchRetryHeaders, n as reportSettlementHint, o as requiredWalletRailFromDetails, r as paymentNetworkLabel, s as assertNever, t as EVM_TRANSACTION_HASH_PATTERN } from "./settlement-report-DTxaAxE3.mjs";
|
|
3
3
|
import { Credential, Method, PaymentRequest, Receipt, z } from "mppx";
|
|
4
4
|
import { Mppx } from "mppx/client";
|
|
5
5
|
import { isAddress } from "viem";
|
|
@@ -39,8 +39,6 @@ function isLoopbackHostname(hostname) {
|
|
|
39
39
|
}
|
|
40
40
|
//#endregion
|
|
41
41
|
//#region src/mpp.ts
|
|
42
|
-
const BASE_USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
43
|
-
const BASE_SEPOLIA_USDC = "0x036CbD53842c5426634e7929541eC2318f3dCF7e";
|
|
44
42
|
const MAX_REQUEST_LENGTH = 16384;
|
|
45
43
|
const MAX_CHALLENGE_BYTES = 32768;
|
|
46
44
|
const MIN_REMAINING_MS = 5e3;
|
|
@@ -143,7 +141,7 @@ var MppNetworkMismatchError = class extends MppFetchPaymentError {
|
|
|
143
141
|
accountId;
|
|
144
142
|
requiredNetworks;
|
|
145
143
|
constructor(accountId, requiredNetworks, reasons) {
|
|
146
|
-
super(`Account ${accountId} cannot pay on the MPP challenge's required network${requiredNetworks.length === 1 ? "" : "s"}: ${requiredNetworks.join(", ")}`, { reasons });
|
|
144
|
+
super(`Account ${accountId} cannot pay on the MPP challenge's required network${requiredNetworks.length === 1 ? "" : "s"}: ${requiredNetworks.map(paymentNetworkLabel).join(", ")}`, { reasons });
|
|
147
145
|
this.name = "MppNetworkMismatchError";
|
|
148
146
|
this.accountId = accountId;
|
|
149
147
|
this.requiredNetworks = requiredNetworks;
|
|
@@ -152,11 +150,13 @@ var MppNetworkMismatchError = class extends MppFetchPaymentError {
|
|
|
152
150
|
var MppCounterpartyNotFoundError = class extends MppFetchPaymentError {
|
|
153
151
|
recipient;
|
|
154
152
|
network;
|
|
155
|
-
|
|
153
|
+
requiredRail;
|
|
154
|
+
constructor(recipient, network, reasons, requiredRail) {
|
|
156
155
|
super(`No saved Catena counterparty rail can pay ${recipient} on ${network}`, { reasons });
|
|
157
156
|
this.name = "MppCounterpartyNotFoundError";
|
|
158
157
|
this.recipient = recipient;
|
|
159
158
|
this.network = network;
|
|
159
|
+
this.requiredRail = requiredRail;
|
|
160
160
|
}
|
|
161
161
|
};
|
|
162
162
|
var MppApprovalPendingError = class extends MppFetchPaymentError {
|
|
@@ -340,7 +340,7 @@ function terminalMppCandidateError(accountId, skips) {
|
|
|
340
340
|
...otherSkips
|
|
341
341
|
].map((skip) => skip.error.message))];
|
|
342
342
|
const unsaved = counterpartySkips.find((skip) => skip.error.status === 404);
|
|
343
|
-
if (unsaved) return new MppCounterpartyNotFoundError(unsaved.terms.recipient, unsaved.terms.network, reasons);
|
|
343
|
+
if (unsaved) return new MppCounterpartyNotFoundError(unsaved.terms.recipient, unsaved.terms.network, reasons, requiredWalletRailFromDetails(unsaved.error.details, unsaved.terms.recipient));
|
|
344
344
|
if (mismatchSkips.length > 0 && counterpartySkips.length === 0) return new MppNetworkMismatchError(accountId, [...new Set(mismatchSkips.map((skip) => skip.terms.network))], reasons);
|
|
345
345
|
return new MppFetchPaymentError("No supported candidate in the MPP challenge is payable from this account", { reasons });
|
|
346
346
|
}
|
|
@@ -450,8 +450,8 @@ function isCanonicalizableText(value, maximumLength) {
|
|
|
450
450
|
function supportedEvmRequest(request, maxAtomicAmount) {
|
|
451
451
|
const result = evmRequestSchema.safeParse(request);
|
|
452
452
|
if (!result.success || !hasSupportedCommonRequest(result.data, maxAtomicAmount)) return;
|
|
453
|
-
const { amount,
|
|
454
|
-
const network =
|
|
453
|
+
const { amount, methodDetails, recipient } = result.data;
|
|
454
|
+
const network = evmNetwork(methodDetails.chainId);
|
|
455
455
|
return isSupportedMethodDetails(methodDetails) && methodDetails.credentialTypes.length === 1 && network !== void 0 ? {
|
|
456
456
|
method: "evm",
|
|
457
457
|
atomicAmount: amount,
|
|
@@ -462,9 +462,9 @@ function supportedEvmRequest(request, maxAtomicAmount) {
|
|
|
462
462
|
function supportedUsdcRequest(request, maxAtomicAmount) {
|
|
463
463
|
const result = usdcRequestSchema.safeParse(request);
|
|
464
464
|
if (!result.success || !hasSupportedCommonRequest(result.data, maxAtomicAmount)) return;
|
|
465
|
-
const { amount,
|
|
465
|
+
const { amount, methodDetails, recipient } = result.data;
|
|
466
466
|
const details = methodDetails.evm;
|
|
467
|
-
const network =
|
|
467
|
+
const network = evmNetwork(details.chainId);
|
|
468
468
|
return isSupportedMethodDetails(details) && (details.credentialTypes === void 0 || details.credentialTypes.length === 1) && network !== void 0 ? {
|
|
469
469
|
method: "usdc",
|
|
470
470
|
atomicAmount: amount,
|
|
@@ -475,9 +475,8 @@ function supportedUsdcRequest(request, maxAtomicAmount) {
|
|
|
475
475
|
function isSupportedMethodDetails(details) {
|
|
476
476
|
return Number.isSafeInteger(details.chainId) && details.chainId >= 1 && (details.permit2Address === void 0 || isAddress$1(details.permit2Address));
|
|
477
477
|
}
|
|
478
|
-
function
|
|
479
|
-
|
|
480
|
-
if (chainId === 84532) return currency.toLowerCase() === BASE_SEPOLIA_USDC.toLowerCase() ? "eip155:84532" : void 0;
|
|
478
|
+
function evmNetwork(chainId) {
|
|
479
|
+
return Number.isSafeInteger(chainId) && chainId >= 1 ? `eip155:${chainId}` : void 0;
|
|
481
480
|
}
|
|
482
481
|
function supportedEnvelope(challenge) {
|
|
483
482
|
if (!hasSupportedChallengeStrings(challenge)) return;
|
|
@@ -1,8 +1,18 @@
|
|
|
1
|
+
import { C as walletCounterpartyRailSchema } from "./client-BSYVYJGm.mjs";
|
|
2
|
+
import * as v from "valibot";
|
|
1
3
|
//#region src/lib/assert-never.ts
|
|
2
4
|
function assertNever(value) {
|
|
3
5
|
throw new Error(`Unhandled case: ${String(value)}`);
|
|
4
6
|
}
|
|
5
7
|
//#endregion
|
|
8
|
+
//#region src/lib/counterparty-recovery.ts
|
|
9
|
+
const requiredRailDetailsSchema = v.object({ requiredRail: walletCounterpartyRailSchema });
|
|
10
|
+
function requiredWalletRailFromDetails(details, expectedAddress) {
|
|
11
|
+
const parsed = v.safeParse(requiredRailDetailsSchema, details);
|
|
12
|
+
if (!parsed.success || parsed.output.requiredRail.walletAddress.toLowerCase() !== expectedAddress.toLowerCase()) return;
|
|
13
|
+
return parsed.output.requiredRail;
|
|
14
|
+
}
|
|
15
|
+
//#endregion
|
|
6
16
|
//#region src/lib/fetch-replay.ts
|
|
7
17
|
function isReplayableFetchRequest(input, init) {
|
|
8
18
|
if (init?.body !== void 0 && init.body !== null) {
|
|
@@ -19,6 +29,17 @@ function fetchRetryHeaders(input, init) {
|
|
|
19
29
|
return headers;
|
|
20
30
|
}
|
|
21
31
|
//#endregion
|
|
32
|
+
//#region src/lib/payment-network.ts
|
|
33
|
+
const PAYMENT_NETWORK_LABELS = {
|
|
34
|
+
"eip155:8453": "Base",
|
|
35
|
+
"eip155:84532": "Base Sepolia",
|
|
36
|
+
"eip155:5042": "Arc"
|
|
37
|
+
};
|
|
38
|
+
function paymentNetworkLabel(network) {
|
|
39
|
+
const label = Object.hasOwn(PAYMENT_NETWORK_LABELS, network) ? PAYMENT_NETWORK_LABELS[network] : void 0;
|
|
40
|
+
return label ? `${label} (${network})` : network;
|
|
41
|
+
}
|
|
42
|
+
//#endregion
|
|
22
43
|
//#region src/lib/settlement-report.ts
|
|
23
44
|
const EVM_TRANSACTION_HASH_PATTERN = /^0x[0-9a-fA-F]{64}$/;
|
|
24
45
|
async function reportSettlementHint(client, intentId, txHash, receipt) {
|
|
@@ -33,4 +54,4 @@ async function reportSettlementHint(client, intentId, txHash, receipt) {
|
|
|
33
54
|
}
|
|
34
55
|
}
|
|
35
56
|
//#endregion
|
|
36
|
-
export {
|
|
57
|
+
export { isReplayableFetchRequest as a, fetchRetryHeaders as i, reportSettlementHint as n, requiredWalletRailFromDetails as o, paymentNetworkLabel as r, assertNever as s, EVM_TRANSACTION_HASH_PATTERN as t };
|