mppx 0.5.13 → 0.5.16
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/CHANGELOG.md +23 -0
- package/dist/Method.d.ts +5 -2
- package/dist/Method.d.ts.map +1 -1
- package/dist/Method.js.map +1 -1
- package/dist/mcp-sdk/server/Transport.d.ts.map +1 -1
- package/dist/mcp-sdk/server/Transport.js +8 -2
- package/dist/mcp-sdk/server/Transport.js.map +1 -1
- package/dist/server/Mppx.d.ts.map +1 -1
- package/dist/server/Mppx.js +17 -10
- package/dist/server/Mppx.js.map +1 -1
- package/dist/server/Request.js +5 -1
- package/dist/server/Request.js.map +1 -1
- package/dist/server/Transport.d.ts.map +1 -1
- package/dist/server/Transport.js +4 -0
- package/dist/server/Transport.js.map +1 -1
- package/dist/stripe/server/internal/html.gen.d.ts +1 -1
- package/dist/stripe/server/internal/html.gen.d.ts.map +1 -1
- package/dist/stripe/server/internal/html.gen.js +1 -1
- package/dist/stripe/server/internal/html.gen.js.map +1 -1
- package/dist/tempo/Methods.d.ts.map +1 -1
- package/dist/tempo/Methods.js +4 -2
- package/dist/tempo/Methods.js.map +1 -1
- package/dist/tempo/client/SessionManager.d.ts.map +1 -1
- package/dist/tempo/client/SessionManager.js +20 -10
- package/dist/tempo/client/SessionManager.js.map +1 -1
- package/dist/tempo/internal/fee-payer.d.ts +4 -1
- package/dist/tempo/internal/fee-payer.d.ts.map +1 -1
- package/dist/tempo/internal/fee-payer.js +99 -23
- package/dist/tempo/internal/fee-payer.js.map +1 -1
- package/dist/tempo/server/Charge.d.ts.map +1 -1
- package/dist/tempo/server/Charge.js +6 -0
- package/dist/tempo/server/Charge.js.map +1 -1
- package/dist/tempo/server/Session.d.ts +4 -0
- package/dist/tempo/server/Session.d.ts.map +1 -1
- package/dist/tempo/server/Session.js +79 -48
- package/dist/tempo/server/Session.js.map +1 -1
- package/dist/tempo/server/internal/html.gen.d.ts +1 -1
- package/dist/tempo/server/internal/html.gen.d.ts.map +1 -1
- package/dist/tempo/server/internal/html.gen.js +1 -1
- package/dist/tempo/server/internal/html.gen.js.map +1 -1
- package/dist/tempo/server/internal/transport.d.ts +0 -7
- package/dist/tempo/server/internal/transport.d.ts.map +1 -1
- package/dist/tempo/server/internal/transport.js +84 -13
- package/dist/tempo/server/internal/transport.js.map +1 -1
- package/dist/tempo/session/Chain.d.ts +5 -0
- package/dist/tempo/session/Chain.d.ts.map +1 -1
- package/dist/tempo/session/Chain.js +202 -63
- package/dist/tempo/session/Chain.js.map +1 -1
- package/dist/tempo/session/ChannelStore.d.ts +1 -0
- package/dist/tempo/session/ChannelStore.d.ts.map +1 -1
- package/dist/tempo/session/ChannelStore.js +38 -15
- package/dist/tempo/session/ChannelStore.js.map +1 -1
- package/package.json +2 -2
- package/src/Method.ts +5 -2
- package/src/internal/changeset.test.ts +106 -0
- package/src/mcp-sdk/client/McpClient.integration.test.ts +634 -0
- package/src/mcp-sdk/server/Transport.test.ts +1 -0
- package/src/mcp-sdk/server/Transport.ts +10 -2
- package/src/proxy/Proxy.test.ts +149 -1
- package/src/server/Mppx.test.ts +120 -0
- package/src/server/Mppx.ts +27 -11
- package/src/server/Request.test.ts +46 -1
- package/src/server/Request.ts +6 -1
- package/src/server/Transport.test.ts +2 -0
- package/src/server/Transport.ts +4 -0
- package/src/stripe/server/internal/html.gen.ts +1 -1
- package/src/tempo/Methods.test.ts +13 -0
- package/src/tempo/Methods.ts +23 -16
- package/src/tempo/client/SessionManager.ts +32 -9
- package/src/tempo/internal/fee-payer.test.ts +88 -16
- package/src/tempo/internal/fee-payer.ts +118 -23
- package/src/tempo/server/Charge.test.ts +73 -0
- package/src/tempo/server/Charge.ts +6 -0
- package/src/tempo/server/Session.test.ts +934 -47
- package/src/tempo/server/Session.ts +100 -52
- package/src/tempo/server/internal/html.gen.ts +1 -1
- package/src/tempo/server/internal/transport.test.ts +321 -10
- package/src/tempo/server/internal/transport.ts +101 -14
- package/src/tempo/session/Chain.test.ts +225 -2
- package/src/tempo/session/Chain.ts +250 -65
- package/src/tempo/session/ChannelStore.test.ts +23 -0
- package/src/tempo/session/ChannelStore.ts +46 -13
- package/src/viem/Client.test.ts +52 -1
|
@@ -37,6 +37,7 @@ import * as Store from '../../Store.js'
|
|
|
37
37
|
import * as Client from '../../viem/Client.js'
|
|
38
38
|
import * as Account from '../internal/account.js'
|
|
39
39
|
import * as defaults from '../internal/defaults.js'
|
|
40
|
+
import * as FeePayer from '../internal/fee-payer.js'
|
|
40
41
|
import type * as types from '../internal/types.js'
|
|
41
42
|
import * as Methods from '../Methods.js'
|
|
42
43
|
import {
|
|
@@ -92,6 +93,7 @@ export function session<const parameters extends session.Parameters>(
|
|
|
92
93
|
channelStateTtl = 5_000,
|
|
93
94
|
currency = defaults.resolveCurrency(parameters),
|
|
94
95
|
decimals = defaults.decimals,
|
|
96
|
+
feePayerPolicy,
|
|
95
97
|
store: rawStore = Store.memory(),
|
|
96
98
|
suggestedDeposit,
|
|
97
99
|
unitType,
|
|
@@ -179,7 +181,7 @@ export function session<const parameters extends session.Parameters>(
|
|
|
179
181
|
}
|
|
180
182
|
},
|
|
181
183
|
|
|
182
|
-
async verify({ credential, request }) {
|
|
184
|
+
async verify({ credential, envelope, request }) {
|
|
183
185
|
const { challenge, payload } = credential as Credential.Credential<SessionCredentialPayload>
|
|
184
186
|
|
|
185
187
|
const resolvedRequest = Methods.session.schema.request.parse(request)
|
|
@@ -203,9 +205,10 @@ export function session<const parameters extends session.Parameters>(
|
|
|
203
205
|
payload,
|
|
204
206
|
methodDetails,
|
|
205
207
|
resolvedFeePayer,
|
|
208
|
+
feePayerPolicy,
|
|
206
209
|
waitForConfirmation,
|
|
207
210
|
)
|
|
208
|
-
lastOnChainVerified.set(
|
|
211
|
+
lastOnChainVerified.set(sessionReceipt.channelId, Date.now())
|
|
209
212
|
break
|
|
210
213
|
|
|
211
214
|
case 'topUp':
|
|
@@ -216,8 +219,9 @@ export function session<const parameters extends session.Parameters>(
|
|
|
216
219
|
payload,
|
|
217
220
|
methodDetails,
|
|
218
221
|
resolvedFeePayer,
|
|
222
|
+
feePayerPolicy,
|
|
219
223
|
)
|
|
220
|
-
lastOnChainVerified.set(
|
|
224
|
+
lastOnChainVerified.set(sessionReceipt.channelId, Date.now())
|
|
221
225
|
break
|
|
222
226
|
|
|
223
227
|
case 'voucher':
|
|
@@ -251,6 +255,28 @@ export function session<const parameters extends session.Parameters>(
|
|
|
251
255
|
})
|
|
252
256
|
}
|
|
253
257
|
|
|
258
|
+
// In the default HTTP request/response mode, each successful content
|
|
259
|
+
// request consumes one unit immediately after the credential is accepted.
|
|
260
|
+
// This keeps equal-voucher replays bounded by the voucher's remaining
|
|
261
|
+
// balance instead of serving repeated responses for free.
|
|
262
|
+
if (
|
|
263
|
+
!parameters.sse &&
|
|
264
|
+
envelope &&
|
|
265
|
+
isBillableContentRequest(envelope.capturedRequest) &&
|
|
266
|
+
(payload.action === 'open' || payload.action === 'voucher')
|
|
267
|
+
) {
|
|
268
|
+
const charged = await charge(
|
|
269
|
+
store,
|
|
270
|
+
sessionReceipt.channelId,
|
|
271
|
+
BigInt(resolvedRequest.amount),
|
|
272
|
+
)
|
|
273
|
+
sessionReceipt = {
|
|
274
|
+
...sessionReceipt,
|
|
275
|
+
spent: charged.spent.toString(),
|
|
276
|
+
units: charged.units,
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
254
280
|
return sessionReceipt
|
|
255
281
|
},
|
|
256
282
|
|
|
@@ -261,28 +287,24 @@ export function session<const parameters extends session.Parameters>(
|
|
|
261
287
|
//
|
|
262
288
|
// close and topUp are always gated (204) — they are pure management.
|
|
263
289
|
//
|
|
264
|
-
// open and voucher
|
|
265
|
-
//
|
|
266
|
-
//
|
|
267
|
-
|
|
268
|
-
// clients (whose fetch wrapper bundles open+voucher into a single
|
|
269
|
-
// GET retry) receive content as expected.
|
|
270
|
-
respond({ credential, input }) {
|
|
290
|
+
// open and voucher share the same captured-request classifier used
|
|
291
|
+
// during verification. Non-billable requests are treated as management
|
|
292
|
+
// updates; billable requests fall through to the application handler.
|
|
293
|
+
respond({ credential, envelope, input }) {
|
|
271
294
|
const { payload } = credential as Credential.Credential<SessionCredentialPayload>
|
|
272
295
|
|
|
273
296
|
if (payload.action === 'close') return new Response(null, { status: 204 })
|
|
274
297
|
if (payload.action === 'topUp') return new Response(null, { status: 204 })
|
|
275
298
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
if (contentLength !== null && contentLength !== '0') return undefined
|
|
282
|
-
if (input.headers.has('transfer-encoding')) return undefined
|
|
283
|
-
return new Response(null, { status: 204 })
|
|
299
|
+
const capturedRequest = envelope?.capturedRequest ?? {
|
|
300
|
+
hasBody: input.body !== null,
|
|
301
|
+
headers: input.headers,
|
|
302
|
+
method: input.method,
|
|
303
|
+
url: new URL(input.url),
|
|
284
304
|
}
|
|
285
|
-
|
|
305
|
+
|
|
306
|
+
if (isBillableContentRequest(capturedRequest)) return undefined
|
|
307
|
+
return new Response(null, { status: 204 })
|
|
286
308
|
},
|
|
287
309
|
})
|
|
288
310
|
}
|
|
@@ -293,9 +315,13 @@ export declare namespace session {
|
|
|
293
315
|
'feePayer' | 'recipient'
|
|
294
316
|
>
|
|
295
317
|
|
|
318
|
+
type FeePayerPolicy = Partial<FeePayer.Policy>
|
|
319
|
+
|
|
296
320
|
type Parameters = {
|
|
297
321
|
/** TTL in milliseconds for cached on-chain channel state. After this duration, the server re-queries on-chain state during voucher handling to detect forced close requests. @default 5_000 */
|
|
298
322
|
channelStateTtl?: number | undefined
|
|
323
|
+
/** Override the fee-sponsor policy used for sponsored open/topUp transactions. */
|
|
324
|
+
feePayerPolicy?: FeePayerPolicy | undefined
|
|
299
325
|
/** Minimum voucher delta to accept (numeric string, default: "0"). */
|
|
300
326
|
minVoucherDelta?: string | undefined
|
|
301
327
|
/**
|
|
@@ -444,6 +470,30 @@ function validateOnChainChannel(
|
|
|
444
470
|
}
|
|
445
471
|
}
|
|
446
472
|
|
|
473
|
+
function isBillableContentRequest(input: {
|
|
474
|
+
hasBody?: boolean | undefined
|
|
475
|
+
headers: Headers
|
|
476
|
+
method: string
|
|
477
|
+
}): boolean {
|
|
478
|
+
if (input.method === 'POST') return hasCapturedRequestBody(input)
|
|
479
|
+
|
|
480
|
+
if (input.method === 'HEAD') return false
|
|
481
|
+
|
|
482
|
+
return true
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function hasCapturedRequestBody(input: {
|
|
486
|
+
hasBody?: boolean | undefined
|
|
487
|
+
headers: Headers
|
|
488
|
+
}): boolean {
|
|
489
|
+
const contentLength = input.headers.get('content-length')
|
|
490
|
+
const headerIndicatesBody =
|
|
491
|
+
(contentLength !== null && contentLength !== '0') || input.headers.has('transfer-encoding')
|
|
492
|
+
|
|
493
|
+
if (input.hasBody === true) return true
|
|
494
|
+
return headerIndicatesBody
|
|
495
|
+
}
|
|
496
|
+
|
|
447
497
|
/**
|
|
448
498
|
* Shared logic for verifying an incremental voucher and updating channel state.
|
|
449
499
|
* Used by both handleVoucher and (indirectly) handleOpen.
|
|
@@ -561,13 +611,11 @@ async function handleOpen(
|
|
|
561
611
|
payload: SessionCredentialPayload & { action: 'open' },
|
|
562
612
|
methodDetails: SessionMethodDetails,
|
|
563
613
|
feePayer: viem_Account | undefined,
|
|
614
|
+
feePayerPolicy: session.FeePayerPolicy | undefined,
|
|
564
615
|
waitForConfirmation: boolean,
|
|
565
616
|
): Promise<SessionReceipt> {
|
|
566
|
-
const
|
|
567
|
-
|
|
568
|
-
payload.cumulativeAmount,
|
|
569
|
-
payload.signature,
|
|
570
|
-
)
|
|
617
|
+
const channelId = ChannelStore.normalizeChannelId(payload.channelId)
|
|
618
|
+
const voucher = parseVoucherFromPayload(channelId, payload.cumulativeAmount, payload.signature)
|
|
571
619
|
|
|
572
620
|
const recipient = challenge.request.recipient as Address
|
|
573
621
|
const currency = challenge.request.currency as Address
|
|
@@ -577,9 +625,11 @@ async function handleOpen(
|
|
|
577
625
|
client,
|
|
578
626
|
serializedTransaction: payload.transaction,
|
|
579
627
|
escrowContract: methodDetails.escrowContract,
|
|
580
|
-
channelId
|
|
628
|
+
channelId,
|
|
581
629
|
recipient,
|
|
582
630
|
currency,
|
|
631
|
+
challengeExpires: challenge.expires,
|
|
632
|
+
feePayerPolicy,
|
|
583
633
|
feePayer,
|
|
584
634
|
waitForConfirmation,
|
|
585
635
|
})
|
|
@@ -612,7 +662,7 @@ async function handleOpen(
|
|
|
612
662
|
throw new InvalidSignatureError({ reason: 'invalid voucher signature' })
|
|
613
663
|
}
|
|
614
664
|
|
|
615
|
-
const updated = await store.updateChannel(
|
|
665
|
+
const updated = await store.updateChannel(channelId, (existing) => {
|
|
616
666
|
if (existing) {
|
|
617
667
|
if (voucher.cumulativeAmount <= existing.settledOnChain) {
|
|
618
668
|
throw new VerificationFailedError({
|
|
@@ -644,7 +694,7 @@ async function handleOpen(
|
|
|
644
694
|
}
|
|
645
695
|
}
|
|
646
696
|
return {
|
|
647
|
-
channelId
|
|
697
|
+
channelId,
|
|
648
698
|
chainId: methodDetails.chainId,
|
|
649
699
|
escrowContract: methodDetails.escrowContract,
|
|
650
700
|
closeRequestedAt: onChain.closeRequestedAt,
|
|
@@ -667,7 +717,7 @@ async function handleOpen(
|
|
|
667
717
|
|
|
668
718
|
return createSessionReceipt({
|
|
669
719
|
challengeId: challenge.id,
|
|
670
|
-
channelId:
|
|
720
|
+
channelId: updated.channelId,
|
|
671
721
|
acceptedCumulative: updated.highestVoucherAmount,
|
|
672
722
|
spent: updated.spent,
|
|
673
723
|
units: updated.units,
|
|
@@ -689,8 +739,10 @@ async function handleTopUp(
|
|
|
689
739
|
payload: SessionCredentialPayload & { action: 'topUp' },
|
|
690
740
|
methodDetails: SessionMethodDetails,
|
|
691
741
|
feePayer: viem_Account | undefined,
|
|
742
|
+
feePayerPolicy: session.FeePayerPolicy | undefined,
|
|
692
743
|
): Promise<SessionReceipt> {
|
|
693
|
-
const
|
|
744
|
+
const channelId = ChannelStore.normalizeChannelId(payload.channelId)
|
|
745
|
+
const channel = await store.getChannel(channelId)
|
|
694
746
|
if (!channel) {
|
|
695
747
|
throw new ChannelNotFoundError({ reason: 'channel not found' })
|
|
696
748
|
}
|
|
@@ -701,21 +753,23 @@ async function handleTopUp(
|
|
|
701
753
|
client,
|
|
702
754
|
serializedTransaction: payload.transaction,
|
|
703
755
|
escrowContract: methodDetails.escrowContract,
|
|
704
|
-
channelId
|
|
756
|
+
channelId,
|
|
705
757
|
currency: challenge.request.currency as Address,
|
|
706
758
|
declaredDeposit,
|
|
707
759
|
previousDeposit: channel.deposit,
|
|
760
|
+
challengeExpires: challenge.expires,
|
|
761
|
+
feePayerPolicy,
|
|
708
762
|
feePayer,
|
|
709
763
|
})
|
|
710
764
|
|
|
711
|
-
const updated = await store.updateChannel(
|
|
765
|
+
const updated = await store.updateChannel(channelId, (current) => {
|
|
712
766
|
if (!current) throw new ChannelNotFoundError({ reason: 'channel not found' })
|
|
713
767
|
return { ...current, deposit: onChainDeposit }
|
|
714
768
|
})
|
|
715
769
|
|
|
716
770
|
return createSessionReceipt({
|
|
717
771
|
challengeId: challenge.id,
|
|
718
|
-
channelId:
|
|
772
|
+
channelId: updated?.channelId ?? channel.channelId,
|
|
719
773
|
acceptedCumulative: updated?.highestVoucherAmount ?? channel.highestVoucherAmount,
|
|
720
774
|
spent: updated?.spent ?? channel.spent,
|
|
721
775
|
units: updated?.units ?? channel.units,
|
|
@@ -735,7 +789,8 @@ async function handleVoucher(
|
|
|
735
789
|
channelStateTtl: number,
|
|
736
790
|
lastOnChainVerified: Map<Hex, number>,
|
|
737
791
|
): Promise<SessionReceipt> {
|
|
738
|
-
const
|
|
792
|
+
const channelId = ChannelStore.normalizeChannelId(payload.channelId)
|
|
793
|
+
const channel = await store.getChannel(channelId)
|
|
739
794
|
if (!channel) {
|
|
740
795
|
throw new ChannelNotFoundError({ reason: 'channel not found' })
|
|
741
796
|
}
|
|
@@ -743,11 +798,7 @@ async function handleVoucher(
|
|
|
743
798
|
throw new ChannelClosedError({ reason: 'channel is finalized' })
|
|
744
799
|
}
|
|
745
800
|
|
|
746
|
-
const voucher = parseVoucherFromPayload(
|
|
747
|
-
payload.channelId,
|
|
748
|
-
payload.cumulativeAmount,
|
|
749
|
-
payload.signature,
|
|
750
|
-
)
|
|
801
|
+
const voucher = parseVoucherFromPayload(channelId, payload.cumulativeAmount, payload.signature)
|
|
751
802
|
|
|
752
803
|
// Use locally-stored channel state as a trusted cache instead of
|
|
753
804
|
// reading on-chain for every voucher. The on-chain state is verified
|
|
@@ -759,7 +810,7 @@ async function handleVoucher(
|
|
|
759
810
|
// To guard against the payer initiating a forced close while vouchers
|
|
760
811
|
// are still being accepted, re-query on-chain state when the cache
|
|
761
812
|
// exceeds the configured staleness TTL (default: 5s).
|
|
762
|
-
const lastVerified = lastOnChainVerified.get(
|
|
813
|
+
const lastVerified = lastOnChainVerified.get(channelId) ?? 0
|
|
763
814
|
const isStale = Date.now() - lastVerified > channelStateTtl
|
|
764
815
|
|
|
765
816
|
const onChain = await (async () => {
|
|
@@ -767,13 +818,13 @@ async function handleVoucher(
|
|
|
767
818
|
const onChainChannel = await getOnChainChannel(
|
|
768
819
|
client,
|
|
769
820
|
methodDetails.escrowContract,
|
|
770
|
-
|
|
821
|
+
channelId,
|
|
771
822
|
)
|
|
772
|
-
lastOnChainVerified.set(
|
|
823
|
+
lastOnChainVerified.set(channelId, Date.now())
|
|
773
824
|
// Persist closeRequestedAt so the cached path detects force-close
|
|
774
825
|
// between re-queries.
|
|
775
826
|
if (onChainChannel.closeRequestedAt !== 0n) {
|
|
776
|
-
await store.updateChannel(
|
|
827
|
+
await store.updateChannel(channelId, (current) =>
|
|
777
828
|
current ? { ...current, closeRequestedAt: onChainChannel.closeRequestedAt } : current,
|
|
778
829
|
)
|
|
779
830
|
}
|
|
@@ -796,7 +847,7 @@ async function handleVoucher(
|
|
|
796
847
|
minVoucherDelta,
|
|
797
848
|
challenge,
|
|
798
849
|
channel,
|
|
799
|
-
channelId
|
|
850
|
+
channelId,
|
|
800
851
|
voucher,
|
|
801
852
|
onChain,
|
|
802
853
|
methodDetails,
|
|
@@ -815,7 +866,8 @@ async function handleClose(
|
|
|
815
866
|
account?: viem_Account,
|
|
816
867
|
feePayer?: viem_Account,
|
|
817
868
|
): Promise<SessionReceipt> {
|
|
818
|
-
const
|
|
869
|
+
const channelId = ChannelStore.normalizeChannelId(payload.channelId)
|
|
870
|
+
const channel = await store.getChannel(channelId)
|
|
819
871
|
if (!channel) {
|
|
820
872
|
throw new ChannelNotFoundError({ reason: 'channel not found' })
|
|
821
873
|
}
|
|
@@ -823,13 +875,9 @@ async function handleClose(
|
|
|
823
875
|
throw new ChannelClosedError({ reason: 'channel is already finalized' })
|
|
824
876
|
}
|
|
825
877
|
|
|
826
|
-
const voucher = parseVoucherFromPayload(
|
|
827
|
-
payload.channelId,
|
|
828
|
-
payload.cumulativeAmount,
|
|
829
|
-
payload.signature,
|
|
830
|
-
)
|
|
878
|
+
const voucher = parseVoucherFromPayload(channelId, payload.cumulativeAmount, payload.signature)
|
|
831
879
|
|
|
832
|
-
const onChain = await getOnChainChannel(client, methodDetails.escrowContract,
|
|
880
|
+
const onChain = await getOnChainChannel(client, methodDetails.escrowContract, channelId)
|
|
833
881
|
|
|
834
882
|
if (onChain.finalized) {
|
|
835
883
|
throw new ChannelClosedError({ reason: 'channel is finalized on-chain' })
|
|
@@ -867,7 +915,7 @@ async function handleClose(
|
|
|
867
915
|
...(feePayer && account ? { feePayer, account } : { account }),
|
|
868
916
|
})
|
|
869
917
|
|
|
870
|
-
const updated = await store.updateChannel(
|
|
918
|
+
const updated = await store.updateChannel(channelId, (current) => {
|
|
871
919
|
if (!current) return null
|
|
872
920
|
const updateVoucher = voucher.cumulativeAmount > current.highestVoucherAmount
|
|
873
921
|
return {
|
|
@@ -883,7 +931,7 @@ async function handleClose(
|
|
|
883
931
|
|
|
884
932
|
return createSessionReceipt({
|
|
885
933
|
challengeId: challenge.id,
|
|
886
|
-
channelId:
|
|
934
|
+
channelId: updated?.channelId ?? channel.channelId,
|
|
887
935
|
acceptedCumulative: voucher.cumulativeAmount,
|
|
888
936
|
spent: updated?.spent ?? channel.spent,
|
|
889
937
|
units: updated?.units ?? channel.units,
|