mppx 0.17.3 → 0.17.4
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 +37 -0
- package/dist/Errors.d.ts +94 -0
- package/dist/Errors.js +83 -0
- package/dist/Store.d.ts +199 -11
- package/dist/Store.js +527 -48
- package/dist/cli/cli.js +141 -137
- package/dist/cli/internal.js +28 -6
- package/dist/cli/plugins/index.js +16 -12
- package/dist/cli/validate/challenge.d.ts +4 -0
- package/dist/cli/validate/challenge.js +9 -6
- package/dist/cloudflare/ChannelStore.d.ts +97 -0
- package/dist/cloudflare/ChannelStore.js +304 -0
- package/dist/cloudflare/Channels.d.ts +92 -0
- package/dist/cloudflare/Channels.js +325 -0
- package/dist/cloudflare/index.d.ts +5 -0
- package/dist/cloudflare/index.js +5 -0
- package/dist/internal/sse.js +32 -15
- package/dist/internal/version.d.ts +2 -2
- package/dist/internal/version.js +1 -1
- package/dist/stripe/server/Methods.d.ts +2 -0
- package/dist/stripe/server/Methods.js +2 -1
- package/dist/stripe/server/internal/record-payment.d.ts +49 -0
- package/dist/stripe/server/internal/record-payment.js +75 -18
- package/dist/tempo/Methods.d.ts +8 -0
- package/dist/tempo/Methods.js +18 -2
- package/dist/tempo/client/Charge.d.ts +32 -1
- package/dist/tempo/client/Charge.js +201 -3
- package/dist/tempo/client/Methods.d.ts +16 -0
- package/dist/tempo/internal/fee-payer.d.ts +0 -25
- package/dist/tempo/internal/fee-payer.js +50 -3
- package/dist/tempo/internal/zone.d.ts +79 -0
- package/dist/tempo/internal/zone.js +133 -0
- package/dist/tempo/server/Charge.d.ts +7 -3
- package/dist/tempo/server/Charge.js +98 -4
- package/dist/tempo/server/Methods.d.ts +40 -0
- package/dist/tempo/server/internal/html.gen.js +1 -1
- package/dist/tempo/session/server/ChannelStore.d.ts +5 -0
- package/dist/tempo/session/server/ChannelStore.js +2 -2
- package/dist/tempo/session/server/CredentialVerification.js +1 -1
- package/dist/validation/core.js +2 -0
- package/package.json +5 -1
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { type Address, type Client, type Hex, type TransactionReceipt } from 'viem';
|
|
2
|
+
/** Default time a payer waits for a Zone withdrawal to settle on Tempo. */
|
|
3
|
+
export declare const defaultSettlementTimeoutMs = 60000;
|
|
4
|
+
/** A withdrawal as `ZonePortal.processWithdrawals` receives it on Tempo. */
|
|
5
|
+
export type Withdrawal = {
|
|
6
|
+
token: Address;
|
|
7
|
+
senderTag: Hex;
|
|
8
|
+
to: Address;
|
|
9
|
+
amount: bigint;
|
|
10
|
+
memo: Hex;
|
|
11
|
+
gasLimit: bigint;
|
|
12
|
+
fallbackNonce: bigint;
|
|
13
|
+
callbackData: Hex;
|
|
14
|
+
encryptedSender: Hex;
|
|
15
|
+
};
|
|
16
|
+
/** A `ZonePortal.WithdrawalProcessed` event. */
|
|
17
|
+
export type ProcessedEvent = {
|
|
18
|
+
to: Address;
|
|
19
|
+
senderTag: Hex;
|
|
20
|
+
token: Address;
|
|
21
|
+
amount: bigint;
|
|
22
|
+
callbackSuccess: boolean;
|
|
23
|
+
};
|
|
24
|
+
/** Zones a challenge accepts: a list of Zone IDs, or `true` for any Zone. */
|
|
25
|
+
export type Accepted = readonly number[] | true;
|
|
26
|
+
/** Returns whether a value is a valid Zone ID: a positive integer below the Zone chain ID ranges. */
|
|
27
|
+
export declare function isId(zoneId: number): boolean;
|
|
28
|
+
/** Returns whether a challenge's `zones` accepts withdrawals from a Zone. */
|
|
29
|
+
export declare function isAccepted(zones: Accepted | undefined, zoneId: number): boolean;
|
|
30
|
+
/** Returns the Tempo portal address of a Zone. */
|
|
31
|
+
export declare function portalAddress(zoneId: number): Address;
|
|
32
|
+
/**
|
|
33
|
+
* Derives a Zone ID from a Zone chain ID.
|
|
34
|
+
*
|
|
35
|
+
* Zones on Tempo mainnet and Moderato use reserved bases (`421700000` and
|
|
36
|
+
* `1424310000`). Zones on any other parent chain use `(parentChainId << 32) | zoneId`.
|
|
37
|
+
*/
|
|
38
|
+
export declare function idFromChainId(chainId: number): number;
|
|
39
|
+
/**
|
|
40
|
+
* Derives the parent Tempo chain ID of a Zone chain ID, or `undefined` when
|
|
41
|
+
* the chain ID doesn't follow a known Zone scheme.
|
|
42
|
+
*/
|
|
43
|
+
export declare function parentChainIdFromChainId(chainId: number): number | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* Derives a withdrawal's sender tag, which identifies it in the
|
|
46
|
+
* `WithdrawalProcessed` event on Tempo.
|
|
47
|
+
*/
|
|
48
|
+
export declare function senderTag(parameters: {
|
|
49
|
+
fallbackNonce: bigint;
|
|
50
|
+
sender: Address;
|
|
51
|
+
transactionHash: Hex;
|
|
52
|
+
}): Hex;
|
|
53
|
+
/** Returns the `WithdrawalProcessed` event a portal emitted for a sender tag. */
|
|
54
|
+
export declare function findProcessedEvent(receipt: Pick<TransactionReceipt, 'logs'>, parameters: {
|
|
55
|
+
portal: Address;
|
|
56
|
+
senderTag: Hex;
|
|
57
|
+
}): ProcessedEvent | undefined;
|
|
58
|
+
/**
|
|
59
|
+
* Finds the withdrawal for a sender tag in the calldata of a settlement
|
|
60
|
+
* transaction's top-level `processWithdrawals` calls to the portal.
|
|
61
|
+
*/
|
|
62
|
+
export declare function findWithdrawal(calls: readonly {
|
|
63
|
+
to?: Address | null | undefined;
|
|
64
|
+
data?: Hex | undefined;
|
|
65
|
+
}[], parameters: {
|
|
66
|
+
portal: Address;
|
|
67
|
+
senderTag: Hex;
|
|
68
|
+
}): Withdrawal | undefined;
|
|
69
|
+
/**
|
|
70
|
+
* Waits until a portal emits `WithdrawalProcessed` for a sender tag and
|
|
71
|
+
* returns the Tempo transaction hash that settled it.
|
|
72
|
+
*/
|
|
73
|
+
export declare function waitForSettlement(client: Client, parameters: {
|
|
74
|
+
portal: Address;
|
|
75
|
+
senderTag: Hex;
|
|
76
|
+
to?: Address | undefined;
|
|
77
|
+
timeoutMs?: number | undefined;
|
|
78
|
+
}): Promise<Hex>;
|
|
79
|
+
//# sourceMappingURL=zone.d.ts.map
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { AbiParameters, Hash } from 'ox';
|
|
2
|
+
import { decodeFunctionData, getAbiItem, parseEventLogs, } from 'viem';
|
|
3
|
+
import { getBlockNumber, getLogs } from 'viem/actions';
|
|
4
|
+
import { Abis, Addresses } from 'viem/tempo';
|
|
5
|
+
import * as TempoAddress from './address.js';
|
|
6
|
+
/** Default time a payer waits for a Zone withdrawal to settle on Tempo. */
|
|
7
|
+
export const defaultSettlementTimeoutMs = 60_000;
|
|
8
|
+
/** Number of Tempo blocks searched back for a settled Zone withdrawal. */
|
|
9
|
+
const lookbackBlocks = 7200n;
|
|
10
|
+
/** Time between polls for a settled Zone withdrawal. */
|
|
11
|
+
const pollingIntervalMs = 500;
|
|
12
|
+
const withdrawalProcessed = getAbiItem({ abi: Abis.zonePortal, name: 'WithdrawalProcessed' });
|
|
13
|
+
/**
|
|
14
|
+
* Zone IDs from here up read as Zone chain IDs in viem's portal lookup, which
|
|
15
|
+
* would map them to another Zone's portal.
|
|
16
|
+
*/
|
|
17
|
+
const maxIdExclusive = 421_700_000;
|
|
18
|
+
/** Returns whether a value is a valid Zone ID: a positive integer below the Zone chain ID ranges. */
|
|
19
|
+
export function isId(zoneId) {
|
|
20
|
+
return Number.isInteger(zoneId) && zoneId > 0 && zoneId < maxIdExclusive;
|
|
21
|
+
}
|
|
22
|
+
/** Returns whether a challenge's `zones` accepts withdrawals from a Zone. */
|
|
23
|
+
export function isAccepted(zones, zoneId) {
|
|
24
|
+
if (!isId(zoneId))
|
|
25
|
+
return false;
|
|
26
|
+
return zones === true || (zones?.includes(zoneId) ?? false);
|
|
27
|
+
}
|
|
28
|
+
/** Returns the Tempo portal address of a Zone. */
|
|
29
|
+
export function portalAddress(zoneId) {
|
|
30
|
+
return Addresses.zonePortal(zoneId);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Derives a Zone ID from a Zone chain ID.
|
|
34
|
+
*
|
|
35
|
+
* Zones on Tempo mainnet and Moderato use reserved bases (`421700000` and
|
|
36
|
+
* `1424310000`). Zones on any other parent chain use `(parentChainId << 32) | zoneId`.
|
|
37
|
+
*/
|
|
38
|
+
export function idFromChainId(chainId) {
|
|
39
|
+
if (chainId >= 2 ** 32)
|
|
40
|
+
return chainId % 2 ** 32;
|
|
41
|
+
if (chainId > 1_424_310_000 && chainId < 1_424_320_000)
|
|
42
|
+
return chainId - 1_424_310_000;
|
|
43
|
+
if (chainId > 421_700_000 && chainId < 421_710_000)
|
|
44
|
+
return chainId - 421_700_000;
|
|
45
|
+
throw new Error(`Cannot derive a Zone ID from chain ID ${chainId}. Pass \`zoneId\`.`);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Derives the parent Tempo chain ID of a Zone chain ID, or `undefined` when
|
|
49
|
+
* the chain ID doesn't follow a known Zone scheme.
|
|
50
|
+
*/
|
|
51
|
+
export function parentChainIdFromChainId(chainId) {
|
|
52
|
+
if (chainId >= 2 ** 32)
|
|
53
|
+
return Math.floor(chainId / 2 ** 32);
|
|
54
|
+
if (chainId > 1_424_310_000 && chainId < 1_424_320_000)
|
|
55
|
+
return 42_431;
|
|
56
|
+
if (chainId > 421_700_000 && chainId < 421_710_000)
|
|
57
|
+
return 4_217;
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Derives a withdrawal's sender tag, which identifies it in the
|
|
62
|
+
* `WithdrawalProcessed` event on Tempo.
|
|
63
|
+
*/
|
|
64
|
+
export function senderTag(parameters) {
|
|
65
|
+
return Hash.keccak256(AbiParameters.encodePacked(['address', 'bytes32', 'uint64'], [parameters.sender, parameters.transactionHash, parameters.fallbackNonce]));
|
|
66
|
+
}
|
|
67
|
+
/** Returns the `WithdrawalProcessed` event a portal emitted for a sender tag. */
|
|
68
|
+
export function findProcessedEvent(receipt, parameters) {
|
|
69
|
+
const logs = parseEventLogs({ abi: [withdrawalProcessed], logs: receipt.logs });
|
|
70
|
+
const log = logs.find((log) => TempoAddress.isEqual(log.address, parameters.portal) &&
|
|
71
|
+
log.args.senderTag.toLowerCase() === parameters.senderTag.toLowerCase());
|
|
72
|
+
return log?.args;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Finds the withdrawal for a sender tag in the calldata of a settlement
|
|
76
|
+
* transaction's top-level `processWithdrawals` calls to the portal.
|
|
77
|
+
*/
|
|
78
|
+
export function findWithdrawal(calls, parameters) {
|
|
79
|
+
for (const call of calls) {
|
|
80
|
+
if (!call.to || !call.data || !TempoAddress.isEqual(call.to, parameters.portal))
|
|
81
|
+
continue;
|
|
82
|
+
let decoded;
|
|
83
|
+
try {
|
|
84
|
+
decoded = decodeFunctionData({ abi: Abis.zonePortal, data: call.data });
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (decoded.functionName !== 'processWithdrawals')
|
|
90
|
+
continue;
|
|
91
|
+
const [withdrawals] = decoded.args;
|
|
92
|
+
const withdrawal = withdrawals.find((withdrawal) => withdrawal.senderTag.toLowerCase() === parameters.senderTag.toLowerCase());
|
|
93
|
+
if (withdrawal)
|
|
94
|
+
return withdrawal;
|
|
95
|
+
}
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Waits until a portal emits `WithdrawalProcessed` for a sender tag and
|
|
100
|
+
* returns the Tempo transaction hash that settled it.
|
|
101
|
+
*/
|
|
102
|
+
export async function waitForSettlement(client, parameters) {
|
|
103
|
+
const { timeoutMs = defaultSettlementTimeoutMs } = parameters;
|
|
104
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs < 0)
|
|
105
|
+
throw new Error('Zone settlement timeout must be a finite, non-negative number.');
|
|
106
|
+
let latest = await getBlockNumber(client, { cacheTime: 0 });
|
|
107
|
+
// Scan the lookback window once, then only blocks added since the previous poll.
|
|
108
|
+
let fromBlock = latest > lookbackBlocks ? latest - lookbackBlocks : 0n;
|
|
109
|
+
const deadline = Date.now() + timeoutMs;
|
|
110
|
+
while (true) {
|
|
111
|
+
if (fromBlock <= latest) {
|
|
112
|
+
const logs = await getLogs(client, {
|
|
113
|
+
address: parameters.portal,
|
|
114
|
+
event: withdrawalProcessed,
|
|
115
|
+
args: {
|
|
116
|
+
senderTag: parameters.senderTag,
|
|
117
|
+
...(parameters.to ? { to: parameters.to } : {}),
|
|
118
|
+
},
|
|
119
|
+
fromBlock,
|
|
120
|
+
toBlock: latest,
|
|
121
|
+
});
|
|
122
|
+
const hash = logs[0]?.transactionHash;
|
|
123
|
+
if (hash)
|
|
124
|
+
return hash;
|
|
125
|
+
fromBlock = latest + 1n;
|
|
126
|
+
}
|
|
127
|
+
if (Date.now() + pollingIntervalMs > deadline)
|
|
128
|
+
throw new Error(`Zone withdrawal ${parameters.senderTag} did not settle on Tempo within ${timeoutMs}ms.`);
|
|
129
|
+
await new Promise((resolve) => setTimeout(resolve, pollingIntervalMs));
|
|
130
|
+
latest = await getBlockNumber(client, { cacheTime: 0 });
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=zone.js.map
|
|
@@ -61,8 +61,10 @@ export declare namespace charge {
|
|
|
61
61
|
* two thirds of this budget. If it fails with an unknown outcome (e.g. a
|
|
62
62
|
* timeout, a lost connection, or "already known"), mppx polls for the
|
|
63
63
|
* receipt until the deadline, or until 2 seconds after the transaction's
|
|
64
|
-
* `validBefore` followed by one final lookup, before
|
|
65
|
-
*
|
|
64
|
+
* `validBefore` followed by one final lookup, before throwing
|
|
65
|
+
* `Errors.BroadcastOutcomeUnknownError` with the transaction hash and
|
|
66
|
+
* `validBefore`, and the broadcast error as `cause`. Definitive rejections
|
|
67
|
+
* fail immediately with the RPC error.
|
|
66
68
|
* @default 30_000
|
|
67
69
|
*/
|
|
68
70
|
confirmationTimeoutMs?: number | undefined;
|
|
@@ -143,7 +145,9 @@ export declare namespace charge {
|
|
|
143
145
|
* report `status: 'success'` based on simulation alone — if the
|
|
144
146
|
* transaction reverts on-chain after broadcast (e.g. due to a state
|
|
145
147
|
* change between simulation and inclusion), the receipt will not reflect
|
|
146
|
-
* the failure.
|
|
148
|
+
* the failure. An ambiguous broadcast failure (a timeout, a lost
|
|
149
|
+
* connection, a 5xx) throws `Errors.BroadcastOutcomeUnknownError` without
|
|
150
|
+
* polling for a receipt.
|
|
147
151
|
*/
|
|
148
152
|
waitForConfirmation?: boolean | undefined;
|
|
149
153
|
} & Client.getResolver.Parameters & Account.resolve.Parameters & Defaults & Method.ComposableHooks<typeof Methods.charge>;
|
|
@@ -3,7 +3,7 @@ import { decodeFunctionData, formatUnits, keccak256, parseEventLogs, WaitForTran
|
|
|
3
3
|
import { getTransaction, getTransactionReceipt, sendRawTransaction, sendRawTransactionSync, signTransaction, verifyTypedData, waitForTransactionReceipt, call as viem_call, } from 'viem/actions';
|
|
4
4
|
import { Abis, Actions, Transaction } from 'viem/tempo';
|
|
5
5
|
import { tempo as tempo_chain } from 'viem/tempo/chains';
|
|
6
|
-
import { CredentialReplayedError, PaymentError, VerificationFailedError } from '../../Errors.js';
|
|
6
|
+
import { BroadcastOutcomeUnknownError, CredentialReplayedError, PaymentError, VerificationFailedError, } from '../../Errors.js';
|
|
7
7
|
import * as Expires from '../../Expires.js';
|
|
8
8
|
import * as Method from '../../Method.js';
|
|
9
9
|
import * as Store from '../../Store.js';
|
|
@@ -18,6 +18,7 @@ import { resolveFeeToken } from '../internal/fee-token.js';
|
|
|
18
18
|
import * as MachineTokenCharge from '../internal/machine-token-charge.js';
|
|
19
19
|
import * as Proof from '../internal/proof.js';
|
|
20
20
|
import * as Selectors from '../internal/selectors.js';
|
|
21
|
+
import * as Zone from '../internal/zone.js';
|
|
21
22
|
import * as Methods from '../Methods.js';
|
|
22
23
|
import { fallbackStore } from './internal/fallback-store.js';
|
|
23
24
|
import * as Relay from './Relay.js';
|
|
@@ -33,7 +34,7 @@ import * as SponsorBudget from './SponsorBudget.js';
|
|
|
33
34
|
* ```
|
|
34
35
|
*/
|
|
35
36
|
export function charge(parameters = {}) {
|
|
36
|
-
const { machineTokenEnabled, allowedFeeTokens: configuredAllowedFeeTokens, amount, confirmationTimeoutMs = 30_000, currency = defaults.resolveCurrency(parameters), decimals = defaults.decimals, description, externalId, feeToken: configuredFeeToken, feePayerPolicy, html, relay, sponsorBudget, splits, supportedModes, validateSender, waitForConfirmation = true, } = parameters;
|
|
37
|
+
const { machineTokenEnabled, allowedFeeTokens: configuredAllowedFeeTokens, amount, confirmationTimeoutMs = 30_000, currency = defaults.resolveCurrency(parameters), decimals = defaults.decimals, description, externalId, feeToken: configuredFeeToken, feePayerPolicy, html, relay, sponsorBudget, splits, supportedModes, validateSender, waitForConfirmation = true, zones, } = parameters;
|
|
37
38
|
if (configuredAllowedFeeTokens?.length === 0)
|
|
38
39
|
throw new Error('`allowedFeeTokens` must contain at least one token.');
|
|
39
40
|
const storeKeyPrefix = parameters.storeKeyPrefix ?? '';
|
|
@@ -187,6 +188,55 @@ export function charge(parameters = {}) {
|
|
|
187
188
|
const payer = payers.size === 1 ? [...payers][0] : undefined;
|
|
188
189
|
return { payer, receipt, sender, transfers };
|
|
189
190
|
}
|
|
191
|
+
/**
|
|
192
|
+
* Validates a payment that a Zone withdrawal settled on Tempo: the portal's
|
|
193
|
+
* `WithdrawalProcessed` event pays the recipient, and the withdrawal's memo in
|
|
194
|
+
* the `processWithdrawals` calldata binds it to this challenge.
|
|
195
|
+
*/
|
|
196
|
+
async function validateZoneCredential(payload, context) {
|
|
197
|
+
const { amount, challenge, client, currency, methodDetails, recipient, supportedModes } = context;
|
|
198
|
+
if (supportedModes && !supportedModes.includes('push'))
|
|
199
|
+
throw new MismatchError('Zone credentials are not supported for this challenge.', {});
|
|
200
|
+
if (!Zone.isAccepted(methodDetails?.zones, payload.zoneId))
|
|
201
|
+
throw new MismatchError('Zone is not accepted for this challenge.', {
|
|
202
|
+
zoneId: String(payload.zoneId),
|
|
203
|
+
});
|
|
204
|
+
if (methodDetails?.splits)
|
|
205
|
+
throw new MismatchError('Zone credentials do not support split payments.', {});
|
|
206
|
+
const portal = Zone.portalAddress(payload.zoneId);
|
|
207
|
+
const senderTag = payload.senderTag;
|
|
208
|
+
const hash = payload.hash;
|
|
209
|
+
const receipt = await getTransactionReceipt(client, { hash });
|
|
210
|
+
if (receipt.status !== 'success')
|
|
211
|
+
throw new MismatchError('Payment verification failed: settlement transaction reverted.', {
|
|
212
|
+
hash,
|
|
213
|
+
});
|
|
214
|
+
const event = Zone.findProcessedEvent(receipt, { portal, senderTag });
|
|
215
|
+
if (!event)
|
|
216
|
+
throw new MismatchError('Payment verification failed: Zone withdrawal not settled.', {
|
|
217
|
+
senderTag,
|
|
218
|
+
});
|
|
219
|
+
if (!event.callbackSuccess)
|
|
220
|
+
throw new MismatchError('Payment verification failed: Zone withdrawal bounced.', {
|
|
221
|
+
senderTag,
|
|
222
|
+
});
|
|
223
|
+
// The event has no memo, so the calldata's withdrawal shows what was paid and for which challenge.
|
|
224
|
+
const transaction = await getTransaction(client, { hash });
|
|
225
|
+
const calls = transaction.calls ??
|
|
226
|
+
(transaction.to ? [{ to: transaction.to, data: transaction.input }] : []);
|
|
227
|
+
const withdrawal = Zone.findWithdrawal(calls, { portal, senderTag });
|
|
228
|
+
if (!withdrawal ||
|
|
229
|
+
withdrawal.gasLimit !== 0n ||
|
|
230
|
+
!TempoAddress.isEqual(withdrawal.to, recipient) ||
|
|
231
|
+
!TempoAddress.isEqual(withdrawal.token, currency) ||
|
|
232
|
+
withdrawal.amount.toString() !== amount)
|
|
233
|
+
throw new MismatchError('Payment verification failed: Zone withdrawal not found.', {
|
|
234
|
+
senderTag,
|
|
235
|
+
});
|
|
236
|
+
if (!isChallengeBound([withdrawal.memo], { challengeId: challenge.id, realm: challenge.realm }))
|
|
237
|
+
throw new CredentialReplayedError({ reason: 'memo is not bound to this challenge' });
|
|
238
|
+
return { hash, portal, receipt, senderTag };
|
|
239
|
+
}
|
|
190
240
|
async function validateProofCredential(credential, payload, context) {
|
|
191
241
|
const { chainId, challenge, client, isZeroAmount } = context;
|
|
192
242
|
if (!isZeroAmount)
|
|
@@ -282,6 +332,18 @@ export function charge(parameters = {}) {
|
|
|
282
332
|
type: 'proof',
|
|
283
333
|
};
|
|
284
334
|
}
|
|
335
|
+
case 'zone': {
|
|
336
|
+
const { hash, portal, receipt, senderTag } = await validateZoneCredential(context.payload, context);
|
|
337
|
+
return {
|
|
338
|
+
details: { mode: 'push' },
|
|
339
|
+
hash,
|
|
340
|
+
payer: undefined,
|
|
341
|
+
portal,
|
|
342
|
+
receipt,
|
|
343
|
+
senderTag,
|
|
344
|
+
type: 'zone',
|
|
345
|
+
};
|
|
346
|
+
}
|
|
285
347
|
case 'transaction': {
|
|
286
348
|
const { fundingCurrency, isFeePayerTx, serializedTransaction, settlementSenders, transaction, transfers, } = await validateTransactionCredential(credential, context.payload, request, context);
|
|
287
349
|
return {
|
|
@@ -316,6 +378,7 @@ export function charge(parameters = {}) {
|
|
|
316
378
|
recipient,
|
|
317
379
|
splits,
|
|
318
380
|
supportedModes,
|
|
381
|
+
zones,
|
|
319
382
|
},
|
|
320
383
|
stableBinding: chargeBinding,
|
|
321
384
|
html: html
|
|
@@ -450,6 +513,15 @@ export function charge(parameters = {}) {
|
|
|
450
513
|
}
|
|
451
514
|
return toReceipt(receipt.transactionHash, fundingCurrency);
|
|
452
515
|
}
|
|
516
|
+
case 'zone': {
|
|
517
|
+
const { portal, receipt, senderTag } = validated;
|
|
518
|
+
if (!(await tryClaimZoneWithdrawal(store, portal, senderTag, replayExpires))) {
|
|
519
|
+
throw new CredentialReplayedError({
|
|
520
|
+
reason: 'Zone withdrawal has already been used',
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
return toReceipt(receipt.transactionHash);
|
|
524
|
+
}
|
|
453
525
|
case 'proof': {
|
|
454
526
|
if (proofStore && !(await tryClaimProof(proofStore, challenge.id, replayExpires))) {
|
|
455
527
|
throw new CredentialReplayedError({
|
|
@@ -639,7 +711,11 @@ export function charge(parameters = {}) {
|
|
|
639
711
|
if (!recovered && validUntil < deadline)
|
|
640
712
|
recovered = await withDeadline(getTransactionReceipt(client, { hash: finalHash }), Math.max(deadline, Date.now() + client.pollingInterval), broadcastError).catch(() => undefined);
|
|
641
713
|
if (!recovered)
|
|
642
|
-
throw
|
|
714
|
+
throw new BroadcastOutcomeUnknownError({
|
|
715
|
+
cause: broadcastError,
|
|
716
|
+
transactionHash: finalHash,
|
|
717
|
+
validBefore: transaction.validBefore,
|
|
718
|
+
});
|
|
643
719
|
receipt = recovered;
|
|
644
720
|
}
|
|
645
721
|
if (reservation)
|
|
@@ -663,8 +739,18 @@ export function charge(parameters = {}) {
|
|
|
663
739
|
// Optimistic path: broadcast without waiting for confirmation
|
|
664
740
|
// (simulation above already ran). The returned receipt assumes
|
|
665
741
|
// success — callers opt in via waitForConfirmation: false.
|
|
742
|
+
const transactionHash = finalHash;
|
|
666
743
|
const reference = await sendRawTransaction(client, {
|
|
667
744
|
serializedTransaction: serializedTransaction_final,
|
|
745
|
+
}).catch((broadcastError) => {
|
|
746
|
+
// Without a receipt wait, an ambiguous failure leaves the outcome to the caller.
|
|
747
|
+
if (!isAmbiguousBroadcastError(broadcastError))
|
|
748
|
+
throw broadcastError;
|
|
749
|
+
throw new BroadcastOutcomeUnknownError({
|
|
750
|
+
cause: broadcastError,
|
|
751
|
+
transactionHash,
|
|
752
|
+
validBefore: transaction.validBefore,
|
|
753
|
+
});
|
|
668
754
|
});
|
|
669
755
|
if (reference.toLowerCase() !== finalHash.toLowerCase())
|
|
670
756
|
throw new VerificationFailedError({
|
|
@@ -711,7 +797,7 @@ function chargeBinding(request) {
|
|
|
711
797
|
// Exhaustively destructure so new charge request fields require an explicit binding decision.
|
|
712
798
|
const { amount, currency, description, externalId, methodDetails, recipient, ...requestRest } = request;
|
|
713
799
|
requestRest;
|
|
714
|
-
const { chainId, feePayer, machineTokenEnabled, splits, supportedModes, ...methodDetailsRest } = methodDetails ?? {};
|
|
800
|
+
const { chainId, feePayer, machineTokenEnabled, splits, supportedModes, zones, ...methodDetailsRest } = methodDetails ?? {};
|
|
715
801
|
methodDetailsRest;
|
|
716
802
|
void feePayer;
|
|
717
803
|
return {
|
|
@@ -724,6 +810,7 @@ function chargeBinding(request) {
|
|
|
724
810
|
recipient,
|
|
725
811
|
splits,
|
|
726
812
|
supportedModes,
|
|
813
|
+
zones,
|
|
727
814
|
};
|
|
728
815
|
}
|
|
729
816
|
function getExpectedTransfers(parameters) {
|
|
@@ -953,6 +1040,13 @@ function getHashStoreKey(hash) {
|
|
|
953
1040
|
function getProofStoreKey(challengeId) {
|
|
954
1041
|
return `mppx:charge:proof:${challengeId}`;
|
|
955
1042
|
}
|
|
1043
|
+
/** @internal */
|
|
1044
|
+
function getZoneWithdrawalStoreKey(portal, senderTag) {
|
|
1045
|
+
return `mppx:charge:zone:${portal.toLowerCase()}:${senderTag.toLowerCase()}`;
|
|
1046
|
+
}
|
|
1047
|
+
async function tryClaimZoneWithdrawal(store, portal, senderTag, expires) {
|
|
1048
|
+
return Store.tryClaim(store, getZoneWithdrawalStoreKey(portal, senderTag), expires);
|
|
1049
|
+
}
|
|
956
1050
|
async function tryClaimHash(store, hash, expires) {
|
|
957
1051
|
return Store.tryClaim(store, getHashStoreKey(hash), expires);
|
|
958
1052
|
}
|
|
@@ -51,6 +51,11 @@ export declare function tempo<const parameters extends tempo.Parameters>(paramet
|
|
|
51
51
|
}, import("zod/v4/core").$strip>, import("zod/mini").ZodMiniObject<{
|
|
52
52
|
signature: import("zod/mini").ZodMiniString<string>;
|
|
53
53
|
type: import("zod/mini").ZodMiniLiteral<"proof">;
|
|
54
|
+
}, import("zod/v4/core").$strip>, import("zod/mini").ZodMiniObject<{
|
|
55
|
+
hash: import("zod/mini").ZodMiniString<string>;
|
|
56
|
+
senderTag: import("zod/mini").ZodMiniString<string>;
|
|
57
|
+
type: import("zod/mini").ZodMiniLiteral<"zone">;
|
|
58
|
+
zoneId: import("zod/mini").ZodMiniNumber<number>;
|
|
54
59
|
}, import("zod/v4/core").$strip>], "type">;
|
|
55
60
|
};
|
|
56
61
|
request: import("zod/mini").ZodMiniPipe<import("zod/mini").ZodMiniObject<{
|
|
@@ -73,6 +78,7 @@ export declare function tempo<const parameters extends tempo.Parameters>(paramet
|
|
|
73
78
|
pull: "pull";
|
|
74
79
|
push: "push";
|
|
75
80
|
}>>>;
|
|
81
|
+
zones: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniUnion<readonly [import("zod/mini").ZodMiniLiteral<true>, import("zod/mini").ZodMiniArray<import("zod/mini").ZodMiniNumber<number>>]>>;
|
|
76
82
|
}, import("zod/v4/core").$strip>, import("zod/mini").ZodMiniTransform<{
|
|
77
83
|
currency: string;
|
|
78
84
|
description?: string | undefined;
|
|
@@ -89,6 +95,7 @@ export declare function tempo<const parameters extends tempo.Parameters>(paramet
|
|
|
89
95
|
amount: string;
|
|
90
96
|
}[] | undefined;
|
|
91
97
|
supportedModes?: ("pull" | "push")[] | undefined;
|
|
98
|
+
zones?: true | number[] | undefined;
|
|
92
99
|
} | undefined;
|
|
93
100
|
}, {
|
|
94
101
|
machineTokenEnabled?: boolean | undefined;
|
|
@@ -107,6 +114,7 @@ export declare function tempo<const parameters extends tempo.Parameters>(paramet
|
|
|
107
114
|
recipient: `0x${string}`;
|
|
108
115
|
}[] | undefined;
|
|
109
116
|
supportedModes?: ("pull" | "push")[] | undefined;
|
|
117
|
+
zones?: true | number[] | undefined;
|
|
110
118
|
}>>;
|
|
111
119
|
};
|
|
112
120
|
}, charge_.DeriveDefaults<NoExtraKeys<parameters, tempo.Parameters>>, undefined, {}, string | undefined>, import("../../Method.js").Server<{
|
|
@@ -213,6 +221,11 @@ export declare function tempo<const parameters extends tempo.Parameters>(paramet
|
|
|
213
221
|
}, import("zod/v4/core").$strip>, import("zod/mini").ZodMiniObject<{
|
|
214
222
|
signature: import("zod/mini").ZodMiniString<string>;
|
|
215
223
|
type: import("zod/mini").ZodMiniLiteral<"proof">;
|
|
224
|
+
}, import("zod/v4/core").$strip>, import("zod/mini").ZodMiniObject<{
|
|
225
|
+
hash: import("zod/mini").ZodMiniString<string>;
|
|
226
|
+
senderTag: import("zod/mini").ZodMiniString<string>;
|
|
227
|
+
type: import("zod/mini").ZodMiniLiteral<"zone">;
|
|
228
|
+
zoneId: import("zod/mini").ZodMiniNumber<number>;
|
|
216
229
|
}, import("zod/v4/core").$strip>], "type">;
|
|
217
230
|
};
|
|
218
231
|
request: import("zod/mini").ZodMiniPipe<import("zod/mini").ZodMiniObject<{
|
|
@@ -235,6 +248,7 @@ export declare function tempo<const parameters extends tempo.Parameters>(paramet
|
|
|
235
248
|
pull: "pull";
|
|
236
249
|
push: "push";
|
|
237
250
|
}>>>;
|
|
251
|
+
zones: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniUnion<readonly [import("zod/mini").ZodMiniLiteral<true>, import("zod/mini").ZodMiniArray<import("zod/mini").ZodMiniNumber<number>>]>>;
|
|
238
252
|
}, import("zod/v4/core").$strip>, import("zod/mini").ZodMiniTransform<{
|
|
239
253
|
currency: string;
|
|
240
254
|
description?: string | undefined;
|
|
@@ -251,6 +265,7 @@ export declare function tempo<const parameters extends tempo.Parameters>(paramet
|
|
|
251
265
|
amount: string;
|
|
252
266
|
}[] | undefined;
|
|
253
267
|
supportedModes?: ("pull" | "push")[] | undefined;
|
|
268
|
+
zones?: true | number[] | undefined;
|
|
254
269
|
} | undefined;
|
|
255
270
|
}, {
|
|
256
271
|
machineTokenEnabled?: boolean | undefined;
|
|
@@ -269,6 +284,7 @@ export declare function tempo<const parameters extends tempo.Parameters>(paramet
|
|
|
269
284
|
recipient: `0x${string}`;
|
|
270
285
|
}[] | undefined;
|
|
271
286
|
supportedModes?: ("pull" | "push")[] | undefined;
|
|
287
|
+
zones?: true | number[] | undefined;
|
|
272
288
|
}>>;
|
|
273
289
|
};
|
|
274
290
|
}, charge_.DeriveDefaults<Omit<parameters, "chainId" | "currencies" | "currency" | "decimals"> & {
|
|
@@ -2323,6 +2339,11 @@ export declare function tempo<const parameters extends tempo.Parameters>(paramet
|
|
|
2323
2339
|
}, import("zod/v4/core").$strip>, import("zod/mini").ZodMiniObject<{
|
|
2324
2340
|
signature: import("zod/mini").ZodMiniString<string>;
|
|
2325
2341
|
type: import("zod/mini").ZodMiniLiteral<"proof">;
|
|
2342
|
+
}, import("zod/v4/core").$strip>, import("zod/mini").ZodMiniObject<{
|
|
2343
|
+
hash: import("zod/mini").ZodMiniString<string>;
|
|
2344
|
+
senderTag: import("zod/mini").ZodMiniString<string>;
|
|
2345
|
+
type: import("zod/mini").ZodMiniLiteral<"zone">;
|
|
2346
|
+
zoneId: import("zod/mini").ZodMiniNumber<number>;
|
|
2326
2347
|
}, import("zod/v4/core").$strip>], "type">;
|
|
2327
2348
|
};
|
|
2328
2349
|
request: import("zod/mini").ZodMiniPipe<import("zod/mini").ZodMiniObject<{
|
|
@@ -2345,6 +2366,7 @@ export declare function tempo<const parameters extends tempo.Parameters>(paramet
|
|
|
2345
2366
|
pull: "pull";
|
|
2346
2367
|
push: "push";
|
|
2347
2368
|
}>>>;
|
|
2369
|
+
zones: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniUnion<readonly [import("zod/mini").ZodMiniLiteral<true>, import("zod/mini").ZodMiniArray<import("zod/mini").ZodMiniNumber<number>>]>>;
|
|
2348
2370
|
}, import("zod/v4/core").$strip>, import("zod/mini").ZodMiniTransform<{
|
|
2349
2371
|
currency: string;
|
|
2350
2372
|
description?: string | undefined;
|
|
@@ -2361,6 +2383,7 @@ export declare function tempo<const parameters extends tempo.Parameters>(paramet
|
|
|
2361
2383
|
amount: string;
|
|
2362
2384
|
}[] | undefined;
|
|
2363
2385
|
supportedModes?: ("pull" | "push")[] | undefined;
|
|
2386
|
+
zones?: true | number[] | undefined;
|
|
2364
2387
|
} | undefined;
|
|
2365
2388
|
}, {
|
|
2366
2389
|
machineTokenEnabled?: boolean | undefined;
|
|
@@ -2379,6 +2402,7 @@ export declare function tempo<const parameters extends tempo.Parameters>(paramet
|
|
|
2379
2402
|
recipient: `0x${string}`;
|
|
2380
2403
|
}[] | undefined;
|
|
2381
2404
|
supportedModes?: ("pull" | "push")[] | undefined;
|
|
2405
|
+
zones?: true | number[] | undefined;
|
|
2382
2406
|
}>>;
|
|
2383
2407
|
};
|
|
2384
2408
|
}, charge_.DeriveDefaults<Omit<parameters, "chainId" | "currencies" | "currency" | "decimals"> & {
|
|
@@ -2467,6 +2491,11 @@ declare function chargeOffers<const parameters extends CurrencyParameters<charge
|
|
|
2467
2491
|
}, import("zod/v4/core").$strip>, import("zod/mini").ZodMiniObject<{
|
|
2468
2492
|
signature: import("zod/mini").ZodMiniString<string>;
|
|
2469
2493
|
type: import("zod/mini").ZodMiniLiteral<"proof">;
|
|
2494
|
+
}, import("zod/v4/core").$strip>, import("zod/mini").ZodMiniObject<{
|
|
2495
|
+
hash: import("zod/mini").ZodMiniString<string>;
|
|
2496
|
+
senderTag: import("zod/mini").ZodMiniString<string>;
|
|
2497
|
+
type: import("zod/mini").ZodMiniLiteral<"zone">;
|
|
2498
|
+
zoneId: import("zod/mini").ZodMiniNumber<number>;
|
|
2470
2499
|
}, import("zod/v4/core").$strip>], "type">;
|
|
2471
2500
|
};
|
|
2472
2501
|
request: import("zod/mini").ZodMiniPipe<import("zod/mini").ZodMiniObject<{
|
|
@@ -2489,6 +2518,7 @@ declare function chargeOffers<const parameters extends CurrencyParameters<charge
|
|
|
2489
2518
|
pull: "pull";
|
|
2490
2519
|
push: "push";
|
|
2491
2520
|
}>>>;
|
|
2521
|
+
zones: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniUnion<readonly [import("zod/mini").ZodMiniLiteral<true>, import("zod/mini").ZodMiniArray<import("zod/mini").ZodMiniNumber<number>>]>>;
|
|
2492
2522
|
}, import("zod/v4/core").$strip>, import("zod/mini").ZodMiniTransform<{
|
|
2493
2523
|
currency: string;
|
|
2494
2524
|
description?: string | undefined;
|
|
@@ -2505,6 +2535,7 @@ declare function chargeOffers<const parameters extends CurrencyParameters<charge
|
|
|
2505
2535
|
amount: string;
|
|
2506
2536
|
}[] | undefined;
|
|
2507
2537
|
supportedModes?: ("pull" | "push")[] | undefined;
|
|
2538
|
+
zones?: true | number[] | undefined;
|
|
2508
2539
|
} | undefined;
|
|
2509
2540
|
}, {
|
|
2510
2541
|
machineTokenEnabled?: boolean | undefined;
|
|
@@ -2523,6 +2554,7 @@ declare function chargeOffers<const parameters extends CurrencyParameters<charge
|
|
|
2523
2554
|
recipient: `0x${string}`;
|
|
2524
2555
|
}[] | undefined;
|
|
2525
2556
|
supportedModes?: ("pull" | "push")[] | undefined;
|
|
2557
|
+
zones?: true | number[] | undefined;
|
|
2526
2558
|
}>>;
|
|
2527
2559
|
};
|
|
2528
2560
|
}, charge_.DeriveDefaults<Omit<parameters, "chainId" | "currencies" | "currency" | "decimals"> & {
|
|
@@ -2543,6 +2575,11 @@ declare function chargeOffers<const parameters extends CurrencyParameters<charge
|
|
|
2543
2575
|
}, import("zod/v4/core").$strip>, import("zod/mini").ZodMiniObject<{
|
|
2544
2576
|
signature: import("zod/mini").ZodMiniString<string>;
|
|
2545
2577
|
type: import("zod/mini").ZodMiniLiteral<"proof">;
|
|
2578
|
+
}, import("zod/v4/core").$strip>, import("zod/mini").ZodMiniObject<{
|
|
2579
|
+
hash: import("zod/mini").ZodMiniString<string>;
|
|
2580
|
+
senderTag: import("zod/mini").ZodMiniString<string>;
|
|
2581
|
+
type: import("zod/mini").ZodMiniLiteral<"zone">;
|
|
2582
|
+
zoneId: import("zod/mini").ZodMiniNumber<number>;
|
|
2546
2583
|
}, import("zod/v4/core").$strip>], "type">;
|
|
2547
2584
|
};
|
|
2548
2585
|
request: import("zod/mini").ZodMiniPipe<import("zod/mini").ZodMiniObject<{
|
|
@@ -2565,6 +2602,7 @@ declare function chargeOffers<const parameters extends CurrencyParameters<charge
|
|
|
2565
2602
|
pull: "pull";
|
|
2566
2603
|
push: "push";
|
|
2567
2604
|
}>>>;
|
|
2605
|
+
zones: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniUnion<readonly [import("zod/mini").ZodMiniLiteral<true>, import("zod/mini").ZodMiniArray<import("zod/mini").ZodMiniNumber<number>>]>>;
|
|
2568
2606
|
}, import("zod/v4/core").$strip>, import("zod/mini").ZodMiniTransform<{
|
|
2569
2607
|
currency: string;
|
|
2570
2608
|
description?: string | undefined;
|
|
@@ -2581,6 +2619,7 @@ declare function chargeOffers<const parameters extends CurrencyParameters<charge
|
|
|
2581
2619
|
amount: string;
|
|
2582
2620
|
}[] | undefined;
|
|
2583
2621
|
supportedModes?: ("pull" | "push")[] | undefined;
|
|
2622
|
+
zones?: true | number[] | undefined;
|
|
2584
2623
|
} | undefined;
|
|
2585
2624
|
}, {
|
|
2586
2625
|
machineTokenEnabled?: boolean | undefined;
|
|
@@ -2599,6 +2638,7 @@ declare function chargeOffers<const parameters extends CurrencyParameters<charge
|
|
|
2599
2638
|
recipient: `0x${string}`;
|
|
2600
2639
|
}[] | undefined;
|
|
2601
2640
|
supportedModes?: ("pull" | "push")[] | undefined;
|
|
2641
|
+
zones?: true | number[] | undefined;
|
|
2602
2642
|
}>>;
|
|
2603
2643
|
};
|
|
2604
2644
|
}, charge_.DeriveDefaults<Omit<parameters, "chainId" | "currencies" | "currency" | "decimals"> & {
|