mppx 0.13.4 → 0.13.6
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 +48 -0
- package/dist/Credential.d.ts +3 -0
- package/dist/Credential.js +9 -0
- package/dist/Errors.d.ts +63 -2
- package/dist/Errors.js +52 -2
- package/dist/Method.d.ts +10 -0
- package/dist/cli/cli.js +92 -92
- package/dist/cli/plugins/evm.js +2 -2
- package/dist/cli/plugins/index.js +12 -12
- package/dist/client/Mppx.d.ts +2 -0
- package/dist/client/Mppx.js +8 -10
- package/dist/client/internal/Fetch.js +107 -8
- package/dist/client/internal/MethodChallenge.d.ts +2 -0
- package/dist/client/internal/MethodChallenge.js +4 -0
- package/dist/client/internal/MethodResponse.d.ts +6 -0
- package/dist/client/internal/MethodResponse.js +16 -0
- package/dist/client/internal/protocols/Mcp.js +36 -5
- package/dist/evm/server/Charge.js +3 -1
- package/dist/internal/version.d.ts +2 -2
- package/dist/internal/version.js +1 -1
- package/dist/internal/warnOnce.d.ts +3 -0
- package/dist/internal/warnOnce.js +9 -0
- package/dist/server/Mppx.d.ts +5 -0
- package/dist/server/Mppx.js +45 -18
- package/dist/server/Transport.js +10 -0
- package/dist/stripe/server/Methods.js +4 -1
- package/dist/tempo/Attribution.d.ts +24 -9
- package/dist/tempo/Attribution.js +25 -5
- package/dist/tempo/client/Charge.d.ts +116 -10
- package/dist/tempo/client/Charge.js +117 -22
- package/dist/tempo/client/Methods.d.ts +88 -9
- package/dist/tempo/index.d.ts +1 -0
- package/dist/tempo/index.js +1 -0
- package/dist/tempo/internal/auto-swap.d.ts +10 -0
- package/dist/tempo/internal/auto-swap.js +35 -8
- package/dist/tempo/internal/fee-payer.d.ts +8 -0
- package/dist/tempo/internal/fee-payer.js +20 -8
- package/dist/tempo/legacy/session/Chain.js +8 -2
- package/dist/tempo/server/Charge.d.ts +11 -0
- package/dist/tempo/server/Charge.js +160 -29
- package/dist/tempo/server/Methods.js +28 -5
- package/dist/tempo/server/Relay.js +6 -22
- package/dist/tempo/server/Subscription.js +5 -1
- package/dist/tempo/server/internal/fallback-store.d.ts +9 -0
- package/dist/tempo/server/internal/fallback-store.js +13 -0
- package/dist/tempo/server/internal/html.gen.js +1 -1
- package/dist/tempo/server/internal/transport.d.ts +3 -0
- package/dist/tempo/server/internal/transport.js +5 -1
- package/dist/tempo/session/client/ChannelOps.d.ts +2 -0
- package/dist/tempo/session/client/ChannelStore.d.ts +12 -1
- package/dist/tempo/session/client/ChannelStore.js +47 -0
- package/dist/tempo/session/client/CredentialState.js +8 -3
- package/dist/tempo/session/client/Session.js +50 -33
- package/dist/tempo/session/client/SessionManager.js +186 -65
- package/dist/tempo/session/client/Transports.d.ts +6 -0
- package/dist/tempo/session/client/Transports.js +52 -7
- package/dist/tempo/session/precompile/Chain.d.ts +2 -0
- package/dist/tempo/session/precompile/Chain.js +8 -3
- package/dist/tempo/session/server/ChannelStore.d.ts +34 -0
- package/dist/tempo/session/server/ChannelStore.js +69 -9
- package/dist/tempo/session/server/CredentialVerification.d.ts +7 -1
- package/dist/tempo/session/server/CredentialVerification.js +220 -48
- package/dist/tempo/session/server/MeteredStream.d.ts +6 -5
- package/dist/tempo/session/server/MeteredStream.js +10 -26
- package/dist/tempo/session/server/Session.d.ts +29 -2
- package/dist/tempo/session/server/Session.js +124 -44
- package/dist/tempo/session/server/Settlement.d.ts +9 -0
- package/dist/tempo/session/server/Settlement.js +23 -3
- package/dist/tempo/session/server/Sse.d.ts +4 -3
- package/dist/tempo/session/server/Sse.js +3 -3
- package/dist/tempo/session/server/Ws.d.ts +9 -1
- package/dist/tempo/session/server/Ws.js +1 -0
- package/package.json +1 -1
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
*
|
|
17
17
|
* The TAG prefix makes MPP transactions trivially distinguishable
|
|
18
18
|
* from arbitrary memos via `TransferWithMemo` event topic filtering.
|
|
19
|
+
* The Tempo charge client uses the challenge `realm` as `serverId`, so
|
|
20
|
+
* `verifyServer(memo, realm)` matches on-chain transfers to a server.
|
|
19
21
|
*
|
|
20
22
|
* @module
|
|
21
23
|
*/
|
|
@@ -26,12 +28,12 @@ export declare const anonymous: '0x00000000000000000000';
|
|
|
26
28
|
/**
|
|
27
29
|
* Encodes an MPP attribution memo as a `bytes32` hex string.
|
|
28
30
|
*
|
|
29
|
-
* @param parameters - The serverId (server identity) and optional clientId.
|
|
31
|
+
* @param parameters - The challengeId, serverId (server identity), and optional clientId.
|
|
30
32
|
* @returns A `0x`-prefixed 64-char hex string (32 bytes).
|
|
31
33
|
*
|
|
32
34
|
* @example
|
|
33
35
|
* ```ts
|
|
34
|
-
* import
|
|
36
|
+
* import { Attribution } from 'mppx/tempo'
|
|
35
37
|
*
|
|
36
38
|
* const memo = Attribution.encode({ challengeId: 'challenge-123', clientId: 'my-app', serverId: 'api.example.com' })
|
|
37
39
|
* ```
|
|
@@ -55,7 +57,7 @@ export declare namespace encode {
|
|
|
55
57
|
*
|
|
56
58
|
* @example
|
|
57
59
|
* ```ts
|
|
58
|
-
* import
|
|
60
|
+
* import { Attribution } from 'mppx/tempo'
|
|
59
61
|
*
|
|
60
62
|
* Attribution.isMppMemo(Attribution.encode({ challengeId: 'challenge-123', serverId: 'api.example.com' })) // true
|
|
61
63
|
* Attribution.isMppMemo('0x0000...0000') // false
|
|
@@ -70,6 +72,15 @@ export declare function isMppMemo(memo: `0x${string}`): boolean;
|
|
|
70
72
|
* @param memo - A `0x`-prefixed hex string (bytes32).
|
|
71
73
|
* @param serverId - The expected server identity.
|
|
72
74
|
* @returns `true` if the memo has a valid MPP tag and matching server fingerprint.
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```ts
|
|
78
|
+
* import { Attribution } from 'mppx/tempo'
|
|
79
|
+
*
|
|
80
|
+
* const memo = Attribution.encode({ challengeId: 'challenge-123', serverId: 'api.example.com' })
|
|
81
|
+
* Attribution.verifyServer(memo, 'api.example.com') // true
|
|
82
|
+
* Attribution.verifyServer(memo, 'other.example.com') // false
|
|
83
|
+
* ```
|
|
73
84
|
*/
|
|
74
85
|
export declare function verifyServer(memo: `0x${string}`, serverId: string): boolean;
|
|
75
86
|
/**
|
|
@@ -80,7 +91,7 @@ export declare function verifyServer(memo: `0x${string}`, serverId: string): boo
|
|
|
80
91
|
*
|
|
81
92
|
* @example
|
|
82
93
|
* ```ts
|
|
83
|
-
* import
|
|
94
|
+
* import { Attribution } from 'mppx/tempo'
|
|
84
95
|
*
|
|
85
96
|
* const memo = Attribution.encode({ challengeId: 'challenge-123', clientId: 'my-app', serverId: 'api.example.com' })
|
|
86
97
|
* const decoded = Attribution.decode(memo)
|
|
@@ -100,11 +111,6 @@ export declare namespace decode {
|
|
|
100
111
|
nonce: `0x${string}`;
|
|
101
112
|
};
|
|
102
113
|
}
|
|
103
|
-
/**
|
|
104
|
-
* Computes the 7-byte challenge-bound nonce: keccak256(challengeId)[0..6].
|
|
105
|
-
* @internal
|
|
106
|
-
*/
|
|
107
|
-
export declare function challengeNonce(challengeId: string): Uint8Array;
|
|
108
114
|
/**
|
|
109
115
|
* Verifies that a memo's nonce is bound to the given challengeId.
|
|
110
116
|
*
|
|
@@ -113,6 +119,15 @@ export declare function challengeNonce(challengeId: string): Uint8Array;
|
|
|
113
119
|
* @param memo - A `0x`-prefixed hex string (bytes32).
|
|
114
120
|
* @param challengeId - The expected challenge ID.
|
|
115
121
|
* @returns `true` if the memo has a valid MPP tag and matching challenge nonce.
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
* ```ts
|
|
125
|
+
* import { Attribution } from 'mppx/tempo'
|
|
126
|
+
*
|
|
127
|
+
* const memo = Attribution.encode({ challengeId: 'challenge-123', serverId: 'api.example.com' })
|
|
128
|
+
* Attribution.verifyChallengeBinding(memo, 'challenge-123') // true
|
|
129
|
+
* Attribution.verifyChallengeBinding(memo, 'challenge-456') // false
|
|
130
|
+
* ```
|
|
116
131
|
*/
|
|
117
132
|
export declare function verifyChallengeBinding(memo: `0x${string}`, challengeId: string): boolean;
|
|
118
133
|
//# sourceMappingURL=Attribution.d.ts.map
|
|
@@ -17,6 +17,8 @@ import { Bytes, Hash, Hex } from 'ox';
|
|
|
17
17
|
*
|
|
18
18
|
* The TAG prefix makes MPP transactions trivially distinguishable
|
|
19
19
|
* from arbitrary memos via `TransferWithMemo` event topic filtering.
|
|
20
|
+
* The Tempo charge client uses the challenge `realm` as `serverId`, so
|
|
21
|
+
* `verifyServer(memo, realm)` matches on-chain transfers to a server.
|
|
20
22
|
*
|
|
21
23
|
* @module
|
|
22
24
|
*/
|
|
@@ -37,12 +39,12 @@ function fingerprint(value) {
|
|
|
37
39
|
/**
|
|
38
40
|
* Encodes an MPP attribution memo as a `bytes32` hex string.
|
|
39
41
|
*
|
|
40
|
-
* @param parameters - The serverId (server identity) and optional clientId.
|
|
42
|
+
* @param parameters - The challengeId, serverId (server identity), and optional clientId.
|
|
41
43
|
* @returns A `0x`-prefixed 64-char hex string (32 bytes).
|
|
42
44
|
*
|
|
43
45
|
* @example
|
|
44
46
|
* ```ts
|
|
45
|
-
* import
|
|
47
|
+
* import { Attribution } from 'mppx/tempo'
|
|
46
48
|
*
|
|
47
49
|
* const memo = Attribution.encode({ challengeId: 'challenge-123', clientId: 'my-app', serverId: 'api.example.com' })
|
|
48
50
|
* ```
|
|
@@ -69,7 +71,7 @@ export function encode(parameters) {
|
|
|
69
71
|
*
|
|
70
72
|
* @example
|
|
71
73
|
* ```ts
|
|
72
|
-
* import
|
|
74
|
+
* import { Attribution } from 'mppx/tempo'
|
|
73
75
|
*
|
|
74
76
|
* Attribution.isMppMemo(Attribution.encode({ challengeId: 'challenge-123', serverId: 'api.example.com' })) // true
|
|
75
77
|
* Attribution.isMppMemo('0x0000...0000') // false
|
|
@@ -90,6 +92,15 @@ export function isMppMemo(memo) {
|
|
|
90
92
|
* @param memo - A `0x`-prefixed hex string (bytes32).
|
|
91
93
|
* @param serverId - The expected server identity.
|
|
92
94
|
* @returns `true` if the memo has a valid MPP tag and matching server fingerprint.
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* ```ts
|
|
98
|
+
* import { Attribution } from 'mppx/tempo'
|
|
99
|
+
*
|
|
100
|
+
* const memo = Attribution.encode({ challengeId: 'challenge-123', serverId: 'api.example.com' })
|
|
101
|
+
* Attribution.verifyServer(memo, 'api.example.com') // true
|
|
102
|
+
* Attribution.verifyServer(memo, 'other.example.com') // false
|
|
103
|
+
* ```
|
|
93
104
|
*/
|
|
94
105
|
export function verifyServer(memo, serverId) {
|
|
95
106
|
if (!isMppMemo(memo))
|
|
@@ -106,7 +117,7 @@ export function verifyServer(memo, serverId) {
|
|
|
106
117
|
*
|
|
107
118
|
* @example
|
|
108
119
|
* ```ts
|
|
109
|
-
* import
|
|
120
|
+
* import { Attribution } from 'mppx/tempo'
|
|
110
121
|
*
|
|
111
122
|
* const memo = Attribution.encode({ challengeId: 'challenge-123', clientId: 'my-app', serverId: 'api.example.com' })
|
|
112
123
|
* const decoded = Attribution.decode(memo)
|
|
@@ -127,7 +138,7 @@ export function decode(memo) {
|
|
|
127
138
|
* Computes the 7-byte challenge-bound nonce: keccak256(challengeId)[0..6].
|
|
128
139
|
* @internal
|
|
129
140
|
*/
|
|
130
|
-
|
|
141
|
+
function challengeNonce(challengeId) {
|
|
131
142
|
const hash = Hash.keccak256(Bytes.fromString(challengeId), { as: 'Hex' });
|
|
132
143
|
return Hex.toBytes(Hex.slice(hash, 0, 7));
|
|
133
144
|
}
|
|
@@ -139,6 +150,15 @@ export function challengeNonce(challengeId) {
|
|
|
139
150
|
* @param memo - A `0x`-prefixed hex string (bytes32).
|
|
140
151
|
* @param challengeId - The expected challenge ID.
|
|
141
152
|
* @returns `true` if the memo has a valid MPP tag and matching challenge nonce.
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* ```ts
|
|
156
|
+
* import { Attribution } from 'mppx/tempo'
|
|
157
|
+
*
|
|
158
|
+
* const memo = Attribution.encode({ challengeId: 'challenge-123', serverId: 'api.example.com' })
|
|
159
|
+
* Attribution.verifyChallengeBinding(memo, 'challenge-123') // true
|
|
160
|
+
* Attribution.verifyChallengeBinding(memo, 'challenge-456') // false
|
|
161
|
+
* ```
|
|
142
162
|
*/
|
|
143
163
|
export function verifyChallengeBinding(memo, challengeId) {
|
|
144
164
|
const decoded = decode(memo);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type Address } from 'viem';
|
|
2
2
|
import * as Method from '../../Method.js';
|
|
3
3
|
import * as Account from '../../viem/Account.js';
|
|
4
4
|
import * as Client from '../../viem/Client.js';
|
|
@@ -25,7 +25,16 @@ export type ChargeContext = {
|
|
|
25
25
|
* })
|
|
26
26
|
* ```
|
|
27
27
|
*/
|
|
28
|
-
export declare function charge(parameters?: charge.Parameters):
|
|
28
|
+
export declare function charge(parameters?: charge.Parameters): {
|
|
29
|
+
readonly canHandleChallenge?: Method.CanHandleChallengeFn | undefined;
|
|
30
|
+
readonly context?: z.ZodMiniObject<{
|
|
31
|
+
account: z.ZodMiniOptional<z.ZodMiniCustom<`0x${string}` | Account.Account | undefined, `0x${string}` | Account.Account | undefined>>;
|
|
32
|
+
autoSwap: z.ZodMiniOptional<z.ZodMiniCustom<AutoSwap.resolve.Value | undefined, AutoSwap.resolve.Value | undefined>>;
|
|
33
|
+
mode: z.ZodMiniOptional<z.ZodMiniEnum<{
|
|
34
|
+
pull: "pull";
|
|
35
|
+
push: "push";
|
|
36
|
+
}>>;
|
|
37
|
+
}, z.core.$strip> | undefined;
|
|
29
38
|
readonly name: "tempo";
|
|
30
39
|
readonly intent: "charge";
|
|
31
40
|
readonly schema: {
|
|
@@ -97,14 +106,84 @@ export declare function charge(parameters?: charge.Parameters): Method.Client<{
|
|
|
97
106
|
supportedModes?: ("pull" | "push")[] | undefined;
|
|
98
107
|
}>>;
|
|
99
108
|
};
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
109
|
+
readonly createCredential: Method.CreateCredentialFn<{
|
|
110
|
+
readonly name: "tempo";
|
|
111
|
+
readonly intent: "charge";
|
|
112
|
+
readonly schema: {
|
|
113
|
+
credential: {
|
|
114
|
+
payload: z.ZodMiniDiscriminatedUnion<[z.ZodMiniObject<{
|
|
115
|
+
hash: z.ZodMiniString<string>;
|
|
116
|
+
type: z.ZodMiniLiteral<"hash">;
|
|
117
|
+
}, z.core.$strip>, z.ZodMiniObject<{
|
|
118
|
+
signature: z.ZodMiniString<string>;
|
|
119
|
+
type: z.ZodMiniLiteral<"transaction">;
|
|
120
|
+
}, z.core.$strip>, z.ZodMiniObject<{
|
|
121
|
+
signature: z.ZodMiniString<string>;
|
|
122
|
+
type: z.ZodMiniLiteral<"proof">;
|
|
123
|
+
}, z.core.$strip>], "type">;
|
|
124
|
+
};
|
|
125
|
+
request: z.ZodMiniPipe<z.ZodMiniObject<{
|
|
126
|
+
machineTokenEnabled: z.ZodMiniOptional<z.ZodMiniBoolean<boolean>>;
|
|
127
|
+
amount: z.ZodMiniString<string>;
|
|
128
|
+
chainId: z.ZodMiniOptional<z.ZodMiniNumber<number>>;
|
|
129
|
+
currency: z.ZodMiniString<string>;
|
|
130
|
+
decimals: z.ZodMiniNumber<number>;
|
|
131
|
+
description: z.ZodMiniOptional<z.ZodMiniString<string>>;
|
|
132
|
+
externalId: z.ZodMiniOptional<z.ZodMiniString<string>>;
|
|
133
|
+
feePayer: z.ZodMiniOptional<z.ZodMiniPipe<z.ZodMiniUnion<readonly [z.ZodMiniBoolean<boolean>, z.ZodMiniCustom<import("viem").Account, import("viem").Account>]>, z.ZodMiniTransform<boolean, boolean | import("viem").Account>>>;
|
|
134
|
+
memo: z.ZodMiniOptional<z.ZodMiniNever>;
|
|
135
|
+
recipient: z.ZodMiniOptional<z.ZodMiniString<string>>;
|
|
136
|
+
splits: z.ZodMiniOptional<z.ZodMiniArray<z.ZodMiniObject<{
|
|
137
|
+
amount: z.ZodMiniString<string>;
|
|
138
|
+
memo: z.ZodMiniOptional<z.ZodMiniString<string>>;
|
|
139
|
+
recipient: z.ZodMiniPipe<z.ZodMiniString<string>, z.ZodMiniTransform<`0x${string}`, string>>;
|
|
140
|
+
}, z.core.$strip>>>;
|
|
141
|
+
supportedModes: z.ZodMiniOptional<z.ZodMiniArray<z.ZodMiniEnum<{
|
|
142
|
+
pull: "pull";
|
|
143
|
+
push: "push";
|
|
144
|
+
}>>>;
|
|
145
|
+
}, z.core.$strip>, z.ZodMiniTransform<{
|
|
146
|
+
currency: string;
|
|
147
|
+
description?: string | undefined;
|
|
148
|
+
externalId?: string | undefined;
|
|
149
|
+
recipient?: string | undefined;
|
|
150
|
+
amount: string;
|
|
151
|
+
methodDetails?: {
|
|
152
|
+
machineTokenEnabled?: boolean | undefined;
|
|
153
|
+
chainId?: number | undefined;
|
|
154
|
+
feePayer?: boolean | undefined;
|
|
155
|
+
splits?: {
|
|
156
|
+
memo?: string | undefined;
|
|
157
|
+
recipient: `0x${string}`;
|
|
158
|
+
amount: string;
|
|
159
|
+
}[] | undefined;
|
|
160
|
+
supportedModes?: ("pull" | "push")[] | undefined;
|
|
161
|
+
} | undefined;
|
|
162
|
+
}, {
|
|
163
|
+
machineTokenEnabled?: boolean | undefined;
|
|
164
|
+
amount: string;
|
|
165
|
+
chainId?: number | undefined;
|
|
166
|
+
currency: string;
|
|
167
|
+
decimals: number;
|
|
168
|
+
description?: string | undefined;
|
|
169
|
+
externalId?: string | undefined;
|
|
170
|
+
feePayer?: boolean | undefined;
|
|
171
|
+
memo?: undefined;
|
|
172
|
+
recipient?: string | undefined;
|
|
173
|
+
splits?: {
|
|
174
|
+
amount: string;
|
|
175
|
+
memo?: string | undefined;
|
|
176
|
+
recipient: `0x${string}`;
|
|
177
|
+
}[] | undefined;
|
|
178
|
+
supportedModes?: ("pull" | "push")[] | undefined;
|
|
179
|
+
}>>;
|
|
180
|
+
};
|
|
181
|
+
}, {
|
|
182
|
+
account?: `0x${string}` | Account.Account | undefined;
|
|
183
|
+
autoSwap?: AutoSwap.resolve.Value | undefined;
|
|
184
|
+
mode?: "pull" | "push" | undefined;
|
|
185
|
+
}>;
|
|
186
|
+
};
|
|
108
187
|
export declare namespace charge {
|
|
109
188
|
type AutoSwap = AutoSwap.resolve.Value;
|
|
110
189
|
type Context = ChargeContext;
|
|
@@ -137,6 +216,33 @@ export declare namespace charge {
|
|
|
137
216
|
* recipient and every split recipient must be included in this list.
|
|
138
217
|
*/
|
|
139
218
|
expectedRecipients?: readonly Address[] | undefined;
|
|
219
|
+
/**
|
|
220
|
+
* Allowlist of TIP-20 currency addresses this client will pay in, compared
|
|
221
|
+
* case-insensitively. Offers in other currencies are skipped during selection
|
|
222
|
+
* and rejected before signing; an empty list rejects all. Required with
|
|
223
|
+
* `maxAmount` or `maxAtomicAmount`, which are denominated in the challenge currency.
|
|
224
|
+
*/
|
|
225
|
+
currencies?: readonly Address[] | undefined;
|
|
226
|
+
/**
|
|
227
|
+
* Decimals of the `currencies`, used to parse `maxAmount`. Required with
|
|
228
|
+
* `maxAmount` because challenges do not carry currency decimals.
|
|
229
|
+
*/
|
|
230
|
+
decimals?: number | undefined;
|
|
231
|
+
/**
|
|
232
|
+
* Maximum display-unit amount (e.g. `'1.5'`) the client is willing to pay per
|
|
233
|
+
* charge, parsed with `decimals`. Values with more fractional digits than
|
|
234
|
+
* `decimals` are rejected rather than rounded. Compared against the challenge
|
|
235
|
+
* amount, which includes splits; larger offers are skipped during selection and
|
|
236
|
+
* rejected before signing. Applies per credential, not cumulatively across
|
|
237
|
+
* payment retries. Requires `currencies`.
|
|
238
|
+
*/
|
|
239
|
+
maxAmount?: string | undefined;
|
|
240
|
+
/**
|
|
241
|
+
* Maximum amount the client is willing to pay per charge, in raw base units of
|
|
242
|
+
* the challenge currency (compared directly with the challenge `amount`).
|
|
243
|
+
* Checked like `maxAmount`. Requires `currencies`.
|
|
244
|
+
*/
|
|
245
|
+
maxAtomicAmount?: string | undefined;
|
|
140
246
|
/**
|
|
141
247
|
* Controls how the charge transaction is submitted.
|
|
142
248
|
*
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import { TxEnvelopeTempo } from 'ox/tempo';
|
|
2
|
+
import { keccak256, parseUnits } from 'viem';
|
|
1
3
|
import { prepareTransactionRequest, sendCallsSync, sendTransactionSync, signTypedData, signTransaction, } from 'viem/actions';
|
|
2
4
|
import { Actions } from 'viem/tempo';
|
|
3
5
|
import { tempo as tempo_chain } from 'viem/tempo/chains';
|
|
6
|
+
import * as MethodResponse from '../../client/internal/MethodResponse.js';
|
|
4
7
|
import * as Credential from '../../Credential.js';
|
|
5
8
|
import * as Method from '../../Method.js';
|
|
6
9
|
import * as Account from '../../viem/Account.js';
|
|
@@ -34,24 +37,36 @@ const chargeContextSchema = z.object({
|
|
|
34
37
|
*/
|
|
35
38
|
export function charge(parameters = {}) {
|
|
36
39
|
const { clientId } = parameters;
|
|
40
|
+
// Caps are denominated in the challenge currency, so they require a currency pin.
|
|
41
|
+
if ((parameters.maxAmount !== undefined || parameters.maxAtomicAmount !== undefined) &&
|
|
42
|
+
parameters.currencies === undefined)
|
|
43
|
+
throw new Error('Tempo charge maxAmount and maxAtomicAmount require currencies.');
|
|
44
|
+
const caps = {
|
|
45
|
+
maxAmount: parseMaxAmount(parameters.maxAmount, parameters.decimals),
|
|
46
|
+
maxAtomicAmount: parameters.maxAtomicAmount === undefined ? undefined : BigInt(parameters.maxAtomicAmount),
|
|
47
|
+
};
|
|
37
48
|
const getClient = Client.getResolver({
|
|
38
49
|
chain: tempo_chain,
|
|
39
50
|
getClient: parameters.getClient,
|
|
40
51
|
rpcUrl: defaults.rpcUrl,
|
|
41
52
|
});
|
|
42
53
|
const getAccount = Account.getResolver({ account: parameters.account });
|
|
43
|
-
|
|
54
|
+
const method = Method.toClient(Methods.charge, {
|
|
55
|
+
// Skip offers that violate client policy so an acceptable offer can be chosen.
|
|
56
|
+
canHandleChallenge({ challenge }) {
|
|
57
|
+
try {
|
|
58
|
+
assertChallengePolicy(parameters, caps, challenge.request);
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
},
|
|
44
65
|
context: chargeContextSchema,
|
|
45
66
|
async createCredential({ challenge, context }) {
|
|
46
|
-
|
|
47
|
-
//
|
|
67
|
+
assertChallengePolicy(parameters, caps, challenge.request);
|
|
68
|
+
// Sign on the pinned chain when the challenge omits a chain ID.
|
|
48
69
|
const challengeChainId = challenge.request.methodDetails?.chainId;
|
|
49
|
-
if (parameters.expectedChainId !== undefined &&
|
|
50
|
-
challengeChainId !== undefined &&
|
|
51
|
-
challengeChainId !== parameters.expectedChainId)
|
|
52
|
-
throw new Error(`Chain ID mismatch: expected ${parameters.expectedChainId}, got ${challengeChainId}.`);
|
|
53
|
-
if (challengeChainId !== undefined || parameters.allowedChainIds?.length === 0)
|
|
54
|
-
Client.assertAllowedChainId(parameters.allowedChainIds, challengeChainId);
|
|
55
70
|
const resolvedChainId = challengeChainId ??
|
|
56
71
|
parameters.expectedChainId ??
|
|
57
72
|
(parameters.allowedChainIds?.length === 1 ? parameters.allowedChainIds[0] : undefined);
|
|
@@ -70,18 +85,6 @@ export function charge(parameters = {}) {
|
|
|
70
85
|
chainId,
|
|
71
86
|
operation,
|
|
72
87
|
})) ?? defaultAccount;
|
|
73
|
-
if (parameters.expectedRecipients) {
|
|
74
|
-
const allowed = new Set(parameters.expectedRecipients.map((a) => a.toLowerCase()));
|
|
75
|
-
if (!request.recipient || !allowed.has(request.recipient.toLowerCase()))
|
|
76
|
-
throw new Error(`Unexpected primary recipient: ${request.recipient}`);
|
|
77
|
-
const splits = methodDetails?.splits;
|
|
78
|
-
if (splits) {
|
|
79
|
-
for (const split of splits) {
|
|
80
|
-
if (!allowed.has(split.recipient.toLowerCase()))
|
|
81
|
-
throw new Error(`Unexpected split recipient: ${split.recipient}`);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
88
|
// Zero-amount: sign EIP-712 typed data instead of creating a transaction.
|
|
86
89
|
if (BigInt(amount) === 0n) {
|
|
87
90
|
const account = await resolveAccount({ kind: 'signPaymentProof' });
|
|
@@ -168,6 +171,7 @@ export function charge(parameters = {}) {
|
|
|
168
171
|
tokenOut: currency,
|
|
169
172
|
tokenIn: autoSwap.tokenIn,
|
|
170
173
|
slippage: autoSwap.slippage,
|
|
174
|
+
maxAmountIn: autoSwap.maxAmountIn,
|
|
171
175
|
})
|
|
172
176
|
: undefined;
|
|
173
177
|
}
|
|
@@ -198,7 +202,9 @@ export function charge(parameters = {}) {
|
|
|
198
202
|
const defaultExpiry = Math.floor(Date.now() / 1000) + 25;
|
|
199
203
|
if (!challenge.expires)
|
|
200
204
|
return defaultExpiry;
|
|
201
|
-
|
|
205
|
+
// `validBefore` is an exclusive whole-second bound, so cap at the second
|
|
206
|
+
// after a (possibly fractional) expiry to stay valid as long as the challenge.
|
|
207
|
+
const challengeExpiry = Math.floor(new Date(challenge.expires).getTime() / 1000) + 1;
|
|
202
208
|
return Math.min(defaultExpiry, challengeExpiry);
|
|
203
209
|
})();
|
|
204
210
|
if (mode === 'push') {
|
|
@@ -254,5 +260,94 @@ export function charge(parameters = {}) {
|
|
|
254
260
|
});
|
|
255
261
|
},
|
|
256
262
|
});
|
|
263
|
+
// Share an in-flight signing so concurrent calls for one challenge cannot yield
|
|
264
|
+
// two independently settleable transactions. Settled signings are not reused.
|
|
265
|
+
const signing = new Map();
|
|
266
|
+
const createCredential = async (options) => {
|
|
267
|
+
// Enforce client policy on every call, including ones joining a signing.
|
|
268
|
+
assertChallengePolicy(parameters, caps, options.challenge.request);
|
|
269
|
+
const account = options.context?.account ?? parameters.account;
|
|
270
|
+
const signer = (typeof account === 'string' ? account : account?.address)?.toLowerCase();
|
|
271
|
+
const key = `${signer ?? ''}:${options.challenge.id}`;
|
|
272
|
+
let promise = signing.get(key);
|
|
273
|
+
if (!promise) {
|
|
274
|
+
promise = method.createCredential(options).finally(() => signing.delete(key));
|
|
275
|
+
signing.set(key, promise);
|
|
276
|
+
}
|
|
277
|
+
return promise;
|
|
278
|
+
};
|
|
279
|
+
return MethodResponse.registerTransactionHash({ ...method, createCredential }, transactionHash);
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Returns the final transaction hash of a credential, when known before settlement.
|
|
283
|
+
*
|
|
284
|
+
* Push credentials carry the broadcast hash; pull credentials hash a canonical,
|
|
285
|
+
* sender-signed 0x76 transaction. Sponsored transactions (0x78 envelopes or 0x76
|
|
286
|
+
* awaiting a fee payer) return `undefined` because the fee payer's co-signature
|
|
287
|
+
* changes the final hash.
|
|
288
|
+
*/
|
|
289
|
+
function transactionHash(credential) {
|
|
290
|
+
const parsed = Methods.charge.schema.credential.payload.safeParse(Credential.deserialize(credential).payload);
|
|
291
|
+
if (!parsed.success)
|
|
292
|
+
return undefined;
|
|
293
|
+
const payload = parsed.data;
|
|
294
|
+
if (payload.type === 'hash')
|
|
295
|
+
return payload.hash;
|
|
296
|
+
const serialized = payload.signature;
|
|
297
|
+
if (payload.type !== 'transaction' || !serialized.startsWith(TxEnvelopeTempo.serializedType))
|
|
298
|
+
return undefined;
|
|
299
|
+
const transaction = TxEnvelopeTempo.deserialize(serialized);
|
|
300
|
+
if (!transaction.signature || transaction.feePayerSignature === null)
|
|
301
|
+
return undefined;
|
|
302
|
+
// Reject trailing or non-canonical bytes, which never identify an on-chain transaction.
|
|
303
|
+
if (TxEnvelopeTempo.serialize(transaction) !== serialized)
|
|
304
|
+
return undefined;
|
|
305
|
+
return keccak256(serialized);
|
|
306
|
+
}
|
|
307
|
+
/** Parses `maxAmount` to base units, rejecting values that cannot be represented exactly. */
|
|
308
|
+
function parseMaxAmount(maxAmount, decimals) {
|
|
309
|
+
if (maxAmount === undefined)
|
|
310
|
+
return undefined;
|
|
311
|
+
if (decimals === undefined)
|
|
312
|
+
throw new Error('Tempo charge maxAmount requires currency decimals.');
|
|
313
|
+
// `parseUnits` rounds excess fractional digits, which could loosen the cap.
|
|
314
|
+
const fraction = maxAmount.split('.')[1]?.replace(/0+$/, '') ?? '';
|
|
315
|
+
if (fraction.length > decimals)
|
|
316
|
+
throw new Error(`Tempo charge maxAmount ${maxAmount} has more fractional digits than ${decimals} decimals allow.`);
|
|
317
|
+
return parseUnits(maxAmount, decimals);
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Rejects a challenge whose chain, currency, recipients, or amount violate the client policy.
|
|
321
|
+
* Runs during challenge selection and again before any client, account, or signing work.
|
|
322
|
+
*/
|
|
323
|
+
function assertChallengePolicy(parameters, caps, request) {
|
|
324
|
+
const { amount, methodDetails, recipient } = request;
|
|
325
|
+
// Chain pinning: reject a challenge whose chain ID conflicts with the pinned one.
|
|
326
|
+
const challengeChainId = methodDetails?.chainId;
|
|
327
|
+
if (parameters.expectedChainId !== undefined &&
|
|
328
|
+
challengeChainId !== undefined &&
|
|
329
|
+
challengeChainId !== parameters.expectedChainId)
|
|
330
|
+
throw new Error(`Chain ID mismatch: expected ${parameters.expectedChainId}, got ${challengeChainId}.`);
|
|
331
|
+
if (challengeChainId !== undefined || parameters.allowedChainIds?.length === 0)
|
|
332
|
+
Client.assertAllowedChainId(parameters.allowedChainIds, challengeChainId);
|
|
333
|
+
if (parameters.currencies &&
|
|
334
|
+
!parameters.currencies.some((c) => c.toLowerCase() === request.currency.toLowerCase()))
|
|
335
|
+
throw new Error(`Tempo charge currency is not allowed: ${request.currency}.`);
|
|
336
|
+
if (parameters.expectedRecipients) {
|
|
337
|
+
const allowed = new Set(parameters.expectedRecipients.map((a) => a.toLowerCase()));
|
|
338
|
+
if (!recipient || !allowed.has(recipient.toLowerCase()))
|
|
339
|
+
throw new Error(`Unexpected primary recipient: ${recipient}`);
|
|
340
|
+
const splits = methodDetails?.splits;
|
|
341
|
+
if (splits) {
|
|
342
|
+
for (const split of splits) {
|
|
343
|
+
if (!allowed.has(split.recipient.toLowerCase()))
|
|
344
|
+
throw new Error(`Unexpected split recipient: ${split.recipient}`);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
if (caps.maxAtomicAmount !== undefined && BigInt(amount) > caps.maxAtomicAmount)
|
|
349
|
+
throw new Error('Tempo charge amount exceeds maxAtomicAmount.');
|
|
350
|
+
if (caps.maxAmount !== undefined && BigInt(amount) > caps.maxAmount)
|
|
351
|
+
throw new Error('Tempo charge amount exceeds maxAmount.');
|
|
257
352
|
}
|
|
258
353
|
//# sourceMappingURL=Charge.js.map
|
|
@@ -19,7 +19,16 @@ export { sessionClient as session };
|
|
|
19
19
|
* })
|
|
20
20
|
* ```
|
|
21
21
|
*/
|
|
22
|
-
export declare function tempo(parameters?: tempo.Parameters): readonly [
|
|
22
|
+
export declare function tempo(parameters?: tempo.Parameters): readonly [{
|
|
23
|
+
readonly canHandleChallenge?: import("../../Method.js").CanHandleChallengeFn | undefined;
|
|
24
|
+
readonly context?: import("zod/mini").ZodMiniObject<{
|
|
25
|
+
account: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniCustom<`0x${string}` | import("../../viem/Account.js").Account | undefined, `0x${string}` | import("../../viem/Account.js").Account | undefined>>;
|
|
26
|
+
autoSwap: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniCustom<import("../internal/auto-swap.js").resolve.Value | undefined, import("../internal/auto-swap.js").resolve.Value | undefined>>;
|
|
27
|
+
mode: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniEnum<{
|
|
28
|
+
pull: "pull";
|
|
29
|
+
push: "push";
|
|
30
|
+
}>>;
|
|
31
|
+
}, import("zod/v4/core").$strip> | undefined;
|
|
23
32
|
readonly name: "tempo";
|
|
24
33
|
readonly intent: "charge";
|
|
25
34
|
readonly schema: {
|
|
@@ -91,14 +100,84 @@ export declare function tempo(parameters?: tempo.Parameters): readonly [import("
|
|
|
91
100
|
supportedModes?: ("pull" | "push")[] | undefined;
|
|
92
101
|
}>>;
|
|
93
102
|
};
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
103
|
+
readonly createCredential: import("../../Method.js").CreateCredentialFn<{
|
|
104
|
+
readonly name: "tempo";
|
|
105
|
+
readonly intent: "charge";
|
|
106
|
+
readonly schema: {
|
|
107
|
+
credential: {
|
|
108
|
+
payload: import("zod/mini").ZodMiniDiscriminatedUnion<[import("zod/mini").ZodMiniObject<{
|
|
109
|
+
hash: import("zod/mini").ZodMiniString<string>;
|
|
110
|
+
type: import("zod/mini").ZodMiniLiteral<"hash">;
|
|
111
|
+
}, import("zod/v4/core").$strip>, import("zod/mini").ZodMiniObject<{
|
|
112
|
+
signature: import("zod/mini").ZodMiniString<string>;
|
|
113
|
+
type: import("zod/mini").ZodMiniLiteral<"transaction">;
|
|
114
|
+
}, import("zod/v4/core").$strip>, import("zod/mini").ZodMiniObject<{
|
|
115
|
+
signature: import("zod/mini").ZodMiniString<string>;
|
|
116
|
+
type: import("zod/mini").ZodMiniLiteral<"proof">;
|
|
117
|
+
}, import("zod/v4/core").$strip>], "type">;
|
|
118
|
+
};
|
|
119
|
+
request: import("zod/mini").ZodMiniPipe<import("zod/mini").ZodMiniObject<{
|
|
120
|
+
machineTokenEnabled: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniBoolean<boolean>>;
|
|
121
|
+
amount: import("zod/mini").ZodMiniString<string>;
|
|
122
|
+
chainId: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniNumber<number>>;
|
|
123
|
+
currency: import("zod/mini").ZodMiniString<string>;
|
|
124
|
+
decimals: import("zod/mini").ZodMiniNumber<number>;
|
|
125
|
+
description: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniString<string>>;
|
|
126
|
+
externalId: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniString<string>>;
|
|
127
|
+
feePayer: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniPipe<import("zod/mini").ZodMiniUnion<readonly [import("zod/mini").ZodMiniBoolean<boolean>, import("zod/mini").ZodMiniCustom<import("viem").Account, import("viem").Account>]>, import("zod/mini").ZodMiniTransform<boolean, boolean | import("viem").Account>>>;
|
|
128
|
+
memo: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniNever>;
|
|
129
|
+
recipient: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniString<string>>;
|
|
130
|
+
splits: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniArray<import("zod/mini").ZodMiniObject<{
|
|
131
|
+
amount: import("zod/mini").ZodMiniString<string>;
|
|
132
|
+
memo: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniString<string>>;
|
|
133
|
+
recipient: import("zod/mini").ZodMiniPipe<import("zod/mini").ZodMiniString<string>, import("zod/mini").ZodMiniTransform<`0x${string}`, string>>;
|
|
134
|
+
}, import("zod/v4/core").$strip>>>;
|
|
135
|
+
supportedModes: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniArray<import("zod/mini").ZodMiniEnum<{
|
|
136
|
+
pull: "pull";
|
|
137
|
+
push: "push";
|
|
138
|
+
}>>>;
|
|
139
|
+
}, import("zod/v4/core").$strip>, import("zod/mini").ZodMiniTransform<{
|
|
140
|
+
currency: string;
|
|
141
|
+
description?: string | undefined;
|
|
142
|
+
externalId?: string | undefined;
|
|
143
|
+
recipient?: string | undefined;
|
|
144
|
+
amount: string;
|
|
145
|
+
methodDetails?: {
|
|
146
|
+
machineTokenEnabled?: boolean | undefined;
|
|
147
|
+
chainId?: number | undefined;
|
|
148
|
+
feePayer?: boolean | undefined;
|
|
149
|
+
splits?: {
|
|
150
|
+
memo?: string | undefined;
|
|
151
|
+
recipient: `0x${string}`;
|
|
152
|
+
amount: string;
|
|
153
|
+
}[] | undefined;
|
|
154
|
+
supportedModes?: ("pull" | "push")[] | undefined;
|
|
155
|
+
} | undefined;
|
|
156
|
+
}, {
|
|
157
|
+
machineTokenEnabled?: boolean | undefined;
|
|
158
|
+
amount: string;
|
|
159
|
+
chainId?: number | undefined;
|
|
160
|
+
currency: string;
|
|
161
|
+
decimals: number;
|
|
162
|
+
description?: string | undefined;
|
|
163
|
+
externalId?: string | undefined;
|
|
164
|
+
feePayer?: boolean | undefined;
|
|
165
|
+
memo?: undefined;
|
|
166
|
+
recipient?: string | undefined;
|
|
167
|
+
splits?: {
|
|
168
|
+
amount: string;
|
|
169
|
+
memo?: string | undefined;
|
|
170
|
+
recipient: `0x${string}`;
|
|
171
|
+
}[] | undefined;
|
|
172
|
+
supportedModes?: ("pull" | "push")[] | undefined;
|
|
173
|
+
}>>;
|
|
174
|
+
};
|
|
175
|
+
}, {
|
|
176
|
+
account?: `0x${string}` | import("../../viem/Account.js").Account | undefined;
|
|
177
|
+
autoSwap?: import("../internal/auto-swap.js").resolve.Value | undefined;
|
|
178
|
+
mode?: "pull" | "push" | undefined;
|
|
179
|
+
}>;
|
|
180
|
+
}, import("../../Method.js").Client<{
|
|
102
181
|
readonly name: "tempo";
|
|
103
182
|
readonly intent: "session";
|
|
104
183
|
readonly schema: {
|
package/dist/tempo/index.d.ts
CHANGED
package/dist/tempo/index.js
CHANGED
|
@@ -17,6 +17,8 @@ export declare namespace findCalls {
|
|
|
17
17
|
amountOut: bigint;
|
|
18
18
|
/** Candidate input tokens to swap from, in priority order. */
|
|
19
19
|
tokenIn: readonly Address[];
|
|
20
|
+
/** Maximum input-token units to spend. Defaults to the missing output amount plus slippage (1:1 units). */
|
|
21
|
+
maxAmountIn?: bigint | undefined;
|
|
20
22
|
/** Max slippage tolerance as a percentage (e.g. `1` = 1%). */
|
|
21
23
|
slippage: number;
|
|
22
24
|
/** Address of the target token to acquire. */
|
|
@@ -29,8 +31,15 @@ export declare namespace findCalls {
|
|
|
29
31
|
export declare function resolve(value: resolve.Value | undefined, defaultCurrencies: readonly Address[]): resolve.Resolved | false;
|
|
30
32
|
export declare namespace resolve {
|
|
31
33
|
type Options = {
|
|
34
|
+
/** Maximum input-token units per swap. Defaults to parity with the missing output amount plus slippage; set explicitly for non-parity pairs. */
|
|
35
|
+
maxAmountIn?: bigint | undefined;
|
|
32
36
|
/** Fallback tokens to try swapping from, in priority order. */
|
|
33
37
|
tokenIn?: Address[] | undefined;
|
|
38
|
+
/**
|
|
39
|
+
* Whether to also try the default currencies after `tokenIn`. Set `false` to swap only from
|
|
40
|
+
* `tokenIn`, as an allowlist. Ignored without `tokenIn`. @default true
|
|
41
|
+
*/
|
|
42
|
+
fallback?: boolean | undefined;
|
|
34
43
|
/** Max slippage tolerance as a percentage (e.g. `1` = 1%). @default 1 */
|
|
35
44
|
slippage?: number | undefined;
|
|
36
45
|
};
|
|
@@ -38,6 +47,7 @@ export declare namespace resolve {
|
|
|
38
47
|
type Resolved = {
|
|
39
48
|
tokenIn: readonly Address[];
|
|
40
49
|
slippage: number;
|
|
50
|
+
maxAmountIn?: bigint | undefined;
|
|
41
51
|
};
|
|
42
52
|
}
|
|
43
53
|
export declare class InsufficientFundsError extends Error {
|