@stableyard/mppx-stableyard 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,607 @@
1
+ ---
2
+ title: Stableyard Charge Intent for HTTP Payment Authentication
3
+ abbrev: Stableyard Charge Intent
4
+ docname: draft-stableyard-charge-00
5
+ version: 00
6
+ category: info
7
+ ipr: noModificationTrust200902
8
+ submissiontype: independent
9
+ consensus: false
10
+
11
+ author:
12
+ - name: Mitesh Metha
13
+ ins: M. Metha
14
+ email: mitesh@stableyard.fi
15
+ org: Stableyard
16
+
17
+ normative:
18
+ RFC2119:
19
+ RFC3339:
20
+ RFC4648:
21
+ RFC8174:
22
+ RFC8259:
23
+ RFC8785:
24
+ RFC9457:
25
+ I-D.payment-intent-charge:
26
+ title: "'charge' Intent for HTTP Payment Authentication"
27
+ target: https://datatracker.ietf.org/doc/draft-payment-intent-charge/
28
+ author:
29
+ - name: Jake Moxey
30
+ - name: Brendan Ryan
31
+ - name: Tom Meagher
32
+ date: 2026
33
+ I-D.httpauth-payment:
34
+ title: "The 'Payment' HTTP Authentication Scheme"
35
+ target: https://datatracker.ietf.org/doc/draft-ietf-httpauth-payment/
36
+ author:
37
+ - name: Jake Moxey
38
+ date: 2026-01
39
+
40
+ informative:
41
+ EIP-712:
42
+ title: "Typed structured data hashing and signing"
43
+ target: https://eips.ethereum.org/EIPS/eip-712
44
+ author:
45
+ - org: Ethereum Foundation
46
+ date: 2017
47
+ W3C-DID:
48
+ title: "Decentralized Identifiers (DIDs) v1.0"
49
+ target: https://www.w3.org/TR/did-core/
50
+ author:
51
+ - org: W3C
52
+ date: 2022
53
+ ---
54
+
55
+ --- abstract
56
+
57
+ This document defines the "charge" intent for the "stableyard"
58
+ payment method within the Payment HTTP Authentication Scheme
59
+ {{I-D.httpauth-payment}}. The server issues a challenge specifying
60
+ amount and destination; the client fulfills it by creating a
61
+ Stableyard payment session from any supported blockchain, then
62
+ presents the session identifier as a credential.
63
+
64
+ Stableyard settles cross-chain: the client pays from whichever chain
65
+ it holds funds on (Base, Arbitrum, Polygon, Ethereum, Solana, or
66
+ Movement), and the merchant receives on their preferred chain, token,
67
+ or fiat currency. The server verifies payment via Stableyard's
68
+ settlement API without needing chain-specific logic.
69
+
70
+ --- middle
71
+
72
+ # Introduction
73
+
74
+ HTTP Payment Authentication {{I-D.httpauth-payment}} defines a
75
+ challenge-response mechanism that gates access to resources behind
76
+ payments. This document registers the "charge" intent for the
77
+ "stableyard" payment method.
78
+
79
+ Existing payment methods in MPP are chain-specific: "tempo" requires
80
+ funds on Tempo chain, "lightning" requires a Lightning wallet. The
81
+ "stableyard" method removes this constraint — an agent pays from
82
+ any supported chain and the merchant receives on any other chain.
83
+
84
+ The flow proceeds as follows:
85
+
86
+ ~~~
87
+ Client Server Stableyard
88
+ | | |
89
+ | (1) GET /resource | |
90
+ |----------------------> | |
91
+ | | |
92
+ | (2) 402 Payment | |
93
+ | Required | |
94
+ | (amount, dest) | |
95
+ |<---------------------- | |
96
+ | | |
97
+ | (3) Create session |
98
+ | {amount, dest, sourceChain} |
99
+ |----------------------------------------------> |
100
+ | (4) Session + deposit address |
101
+ |<---------------------------------------------- |
102
+ | | |
103
+ | (5) Send USDC to |
104
+ | deposit address |
105
+ |----------------------------------------------> |
106
+ | (6) Submit tx hash |
107
+ |----------------------------------------------> |
108
+ | | |
109
+ | | (7) Stableyard routes |
110
+ | | cross-chain |
111
+ | | |
112
+ | (8) GET /resource | |
113
+ | credential: | |
114
+ | {sessionId} | |
115
+ |----------------------> | |
116
+ | | (9) Verify session |
117
+ | |----------------------> |
118
+ | | (10) {verified: true} |
119
+ | |<---------------------- |
120
+ | | |
121
+ | (11) 200 OK | |
122
+ | (resource) | |
123
+ |<---------------------- | |
124
+ | | |
125
+ ~~~
126
+
127
+ ## Relationship to the Charge Intent
128
+
129
+ This document inherits the shared request semantics of the "charge"
130
+ intent from {{I-D.payment-intent-charge}}. It defines only the
131
+ stableyard-specific `methodDetails`, credential payload, and
132
+ verification procedure.
133
+
134
+
135
+ # Requirements Language
136
+
137
+ {::boilerplate bcp14-tagged}
138
+
139
+
140
+ # Terminology
141
+
142
+ Session:
143
+ : A Stableyard payment session representing a single payment intent.
144
+ Sessions track status from creation through settlement.
145
+
146
+ Deposit Address:
147
+ : A blockchain address generated by Stableyard's Intent Engine where
148
+ the client sends funds. May be a direct wallet address (same-chain)
149
+ or a gateway contract address (cross-chain).
150
+
151
+ Settlement:
152
+ : The process by which Stableyard routes funds from the source chain
153
+ to the merchant's preferred destination chain, token, or fiat
154
+ currency.
155
+
156
+ Intent Engine:
157
+ : Stableyard's cross-chain routing system that generates deposit
158
+ addresses, monitors payments, and executes settlement via a
159
+ solver network.
160
+
161
+ Payment Address:
162
+ : A human-readable identifier in the format `name@stableyard` that
163
+ resolves to an account with configured settlement preferences.
164
+
165
+ Vault:
166
+ : An optional Gnosis Safe smart contract wallet enabling gasless
167
+ payments via EIP-712 signed messages.
168
+
169
+
170
+ # Intent Identifier
171
+
172
+ The intent identifier for this specification is "charge".
173
+
174
+
175
+ # Intent: "charge"
176
+
177
+ The "charge" intent represents a one-time payment for resource access.
178
+ The client creates a Stableyard session, funds it from any supported
179
+ chain, and presents the settled session ID as proof of payment.
180
+
181
+
182
+ # Encoding Conventions {#encoding}
183
+
184
+ All JSON values in challenges and credentials MUST be serialized
185
+ using JSON Canonicalization Scheme (JCS) {{RFC8785}} and encoded
186
+ with base64url {{RFC4648}} without padding, consistent with the
187
+ base specification {{I-D.httpauth-payment}}.
188
+
189
+
190
+ # Request Schema
191
+
192
+ The challenge `request` parameter is a base64url-encoded JSON object
193
+ containing the following fields:
194
+
195
+ ## Shared Fields
196
+
197
+ These fields follow the shared charge intent schema from
198
+ {{I-D.payment-intent-charge}}:
199
+
200
+ amount:
201
+ : REQUIRED. String. Amount in base units (smallest denomination).
202
+ For USDC with 6 decimals, "100000" represents $0.10.
203
+
204
+ currency:
205
+ : REQUIRED. String. Currency identifier. MUST be "USDC" or "USDT".
206
+
207
+ decimals:
208
+ : REQUIRED. Number. Decimal places for the currency. MUST be 6 for
209
+ USDC and USDT.
210
+
211
+ destination:
212
+ : REQUIRED. String. Stableyard payment address. Either a
213
+ human-readable address (e.g., "merchant@stableyard") or a wallet
214
+ address (e.g., "0x742d...fA99").
215
+
216
+ description:
217
+ : OPTIONAL. String. Human-readable description of the payment
218
+ purpose. Maximum 500 characters.
219
+
220
+ externalId:
221
+ : OPTIONAL. String. Merchant-defined reference for reconciliation.
222
+
223
+
224
+ ## Method Details
225
+
226
+ No additional `methodDetails` fields are required for the stableyard
227
+ charge intent. The method is chain-agnostic — the client selects its
228
+ source chain when creating the Stableyard session.
229
+
230
+ ## Example Request (Decoded)
231
+
232
+ ~~~json
233
+ {
234
+ "amount": "100000",
235
+ "currency": "USDC",
236
+ "decimals": 6,
237
+ "destination": "merchant@stableyard"
238
+ }
239
+ ~~~
240
+
241
+
242
+ # Credential Schema
243
+
244
+ The credential `payload` field is a JSON object containing:
245
+
246
+ sessionId:
247
+ : REQUIRED. String. The Stableyard session identifier (e.g.,
248
+ "ses_b6afc57b153e2ae1f8fb1025"). This serves as the proof of
249
+ payment — the server verifies it via Stableyard's API.
250
+
251
+ txHash:
252
+ : OPTIONAL. String. The on-chain transaction hash with "0x" prefix.
253
+ Included for transparency and auditability. Not required for
254
+ verification — the session ID is sufficient.
255
+
256
+ ## Example Credential Payload
257
+
258
+ ~~~json
259
+ {
260
+ "sessionId": "ses_b6afc57b153e2ae1f8fb1025",
261
+ "txHash": "0xbeaf32d41f8f7573e02653a79e02bf56a73ae667dca203acb9e5c181116914a9"
262
+ }
263
+ ~~~
264
+
265
+
266
+ # Verification Procedure {#verification}
267
+
268
+ Upon receiving a credential with method "stableyard" and intent
269
+ "charge", the server MUST execute the following steps:
270
+
271
+ 1. Extract `sessionId` from `credential.payload`.
272
+
273
+ 2. Call `POST /v2/sessions/{sessionId}/verify` on the Stableyard
274
+ API with the server's API key.
275
+
276
+ 3. The Stableyard API returns `{verified: true}` if and only if:
277
+ - The session exists and is in "settled" status
278
+ - The session amount matches the challenge amount
279
+ - The session destination matches the challenge destination
280
+ - The session has a `resource` field matching the challenge realm
281
+
282
+ 4. If verification succeeds, the server MUST invalidate the
283
+ challenge and return HTTP 200 with the resource.
284
+
285
+ 5. If verification fails (session not settled, amount mismatch,
286
+ or API error), the server MUST return HTTP 402 with a fresh
287
+ challenge and a Problem Details {{RFC9457}} body.
288
+
289
+ ## Challenge Binding
290
+
291
+ The server SHOULD set the `resource` field when the client creates
292
+ the Stableyard session, binding the session to the specific API
293
+ endpoint being paid for. The Stableyard verify endpoint checks this
294
+ field to prevent session reuse across different resources.
295
+
296
+ ## Delegated Verification
297
+
298
+ Unlike methods where the server independently verifies a
299
+ cryptographic proof (e.g., preimage in Lightning, signed transaction
300
+ in Tempo), the stableyard method delegates verification to
301
+ Stableyard's API. This is analogous to how the "stripe" method
302
+ delegates verification to Stripe's API.
303
+
304
+ This design choice enables:
305
+ - Chain-agnostic verification (server needs no blockchain RPC)
306
+ - Cross-chain settlement (Stableyard handles routing)
307
+ - Unified merchant experience (one API for all chains)
308
+
309
+
310
+ # Settlement Procedure
311
+
312
+ Settlement is handled entirely by Stableyard's Intent Engine:
313
+
314
+ 1. Client creates a session with `sourceChain` parameter.
315
+ 2. Stableyard returns a deposit address on the specified chain.
316
+ 3. Client sends the requested amount to the deposit address.
317
+ 4. Client calls `POST /v2/sessions/{id}/submit-tx` with the
318
+ transaction hash for faster detection.
319
+ 5. Stableyard detects the payment and routes it to the merchant's
320
+ configured settlement destination.
321
+
322
+ Settlement timing depends on the route:
323
+
324
+ | Route | Estimated Time |
325
+ |-------|---------------|
326
+ | Same-chain (e.g., Base to Base) | ~1 second |
327
+ | Cross-chain (e.g., Polygon to Base) | ~15 seconds |
328
+
329
+ ## Deposit Types
330
+
331
+ Stableyard returns one of two deposit types based on the route:
332
+
333
+ direct_transfer:
334
+ : Same-chain payments. Client sends an ERC20 `transfer()` to the
335
+ deposit address.
336
+
337
+ gasyard:
338
+ : Cross-chain payments via Stableyard's gateway contract. Client
339
+ executes `approve()` + `deposit()` transactions. Gateway calldata
340
+ is provided in the session response.
341
+
342
+ ## Gasless Payments
343
+
344
+ If the client has an active Stableyard Vault (Gnosis Safe), it MAY
345
+ pay via `POST /v2/sessions/{id}/pay` with an EIP-712 {{EIP-712}}
346
+ signed message. This enables zero-gas payments — Stableyard executes
347
+ the transaction on behalf of the client.
348
+
349
+ ## Receipt Generation
350
+
351
+ Upon successful verification, the server MUST return a
352
+ `Payment-Receipt` header containing:
353
+
354
+ ~~~json
355
+ {
356
+ "method": "stableyard",
357
+ "status": "success",
358
+ "reference": "ses_b6afc57b153e2ae1f8fb1025",
359
+ "timestamp": "2026-03-19T16:38:34Z"
360
+ }
361
+ ~~~
362
+
363
+ The `reference` field contains the Stableyard session ID, which can
364
+ be used for reconciliation via the Stableyard merchant dashboard.
365
+
366
+
367
+ # Error Responses
368
+
369
+ Failed payment attempts MUST return HTTP 402 with a fresh challenge
370
+ and a Problem Details {{RFC9457}} body. Error types:
371
+
372
+ verification-failed:
373
+ : Session is not in "settled" status, or amount/destination mismatch.
374
+
375
+ invalid-session:
376
+ : Session ID is unknown or expired.
377
+
378
+ malformed-credential:
379
+ : Credential payload is missing `sessionId` or is not valid JSON.
380
+
381
+ settlement-failed:
382
+ : Stableyard detected the payment but settlement routing failed.
383
+ This triggers an automatic refund via Stableyard.
384
+
385
+
386
+ # Security Considerations
387
+
388
+ ## Transport Security
389
+
390
+ All interactions between client, server, and Stableyard API MUST
391
+ use TLS 1.2 or later. Stableyard API endpoints enforce HTTPS.
392
+
393
+ ## Session Uniqueness
394
+
395
+ Each Stableyard session is single-use. Once verified, the session
396
+ transitions to a consumed state and cannot be reused for another
397
+ challenge. The Stableyard API enforces this server-side.
398
+
399
+ ## Amount Verification
400
+
401
+ Clients SHOULD verify that the requested amount, currency, and
402
+ destination are reasonable before creating a session and sending
403
+ funds. The Stableyard API validates that the payment amount matches
404
+ the session amount during settlement.
405
+
406
+ ## API Key Security
407
+
408
+ Server-side API keys (`sy_secret_*`) MUST be treated as secrets.
409
+ They MUST NOT be exposed in client-side code, logs, or error
410
+ responses. Client-side operations SHOULD use public keys
411
+ (`sy_pub_*`) which have restricted permissions.
412
+
413
+ ## Replay Protection
414
+
415
+ Replay attacks are prevented at two levels:
416
+ 1. MPP challenge IDs are HMAC-bound and single-use per the base
417
+ specification {{I-D.httpauth-payment}}.
418
+ 2. Stableyard sessions are single-use — once verified, the session
419
+ cannot be presented again.
420
+
421
+ ## Destination Verification
422
+
423
+ Servers MUST verify that the `destination` in the credential's
424
+ session matches the `destination` in the original challenge. The
425
+ Stableyard verify endpoint performs this check automatically.
426
+
427
+
428
+ # IANA Considerations
429
+
430
+ ## Payment Method Registration
431
+
432
+ This document requests registration of the following payment method
433
+ in the "HTTP Payment Methods" registry:
434
+
435
+ Method: stableyard
436
+
437
+ Description: Cross-chain stablecoin payments via Stableyard
438
+ settlement network. Supports USDC and USDT across multiple EVM
439
+ chains, Solana, and Movement, with fiat settlement available.
440
+
441
+ Reference: This document
442
+
443
+ Contact: Mitesh Metha (mitesh@stableyard.fi)
444
+
445
+
446
+ ## Payment Intent Registration
447
+
448
+ This document requests registration of the following payment intent
449
+ for the "stableyard" method:
450
+
451
+ Intent: charge
452
+
453
+ Method: stableyard
454
+
455
+ Description: One-time cross-chain payment for resource access.
456
+ Client creates a Stableyard session, funds it from any supported
457
+ chain, and presents the session ID as proof.
458
+
459
+ Reference: This document
460
+
461
+ Contact: Mitesh Metha (mitesh@stableyard.fi)
462
+
463
+
464
+ # Examples
465
+
466
+ ## Initial Request and 402 Challenge
467
+
468
+ Client requests a protected resource:
469
+
470
+ ~~~
471
+ GET /api/market-data HTTP/1.1
472
+ Host: api.example.com
473
+ ~~~
474
+
475
+ Server responds with a payment challenge:
476
+
477
+ ~~~
478
+ HTTP/1.1 402 Payment Required
479
+ WWW-Authenticate: Payment id="POudmDHUpiZKtSqrMfFHAdEtLTsc",
480
+ realm="api.example.com",
481
+ method="stableyard",
482
+ intent="charge",
483
+ request="eyJhbW91bnQiOiIxMDAwMDAiLCJjdXJyZW5jeSI6IlVTRE
484
+ MiLCJkZWNpbWFscyI6NiwiZGVzdGluYXRpb24iOiJtZXJjaGFu
485
+ dEBzdGFibGV5YXJkIn0",
486
+ description="Market data feed",
487
+ expires="2026-03-19T16:49:52Z"
488
+ Cache-Control: no-store
489
+ Content-Type: application/problem+json
490
+
491
+ {
492
+ "type": "https://paymentauth.org/problems/payment-required",
493
+ "title": "Payment Required",
494
+ "status": 402,
495
+ "detail": "Payment is required (Market data feed)."
496
+ }
497
+ ~~~
498
+
499
+ The decoded `request` parameter contains:
500
+
501
+ ~~~json
502
+ {
503
+ "amount": "100000",
504
+ "currency": "USDC",
505
+ "decimals": 6,
506
+ "destination": "merchant@stableyard"
507
+ }
508
+ ~~~
509
+
510
+
511
+ ## Client Payment Flow
512
+
513
+ The client creates a Stableyard session:
514
+
515
+ ~~~
516
+ POST /v2/sessions HTTP/1.1
517
+ Host: api.stableyard.fi
518
+ Authorization: Bearer sy_secret_...
519
+ Content-Type: application/json
520
+
521
+ {
522
+ "amount": 100000,
523
+ "destination": "merchant@stableyard",
524
+ "sourceChain": "base",
525
+ "resource": "api.example.com/charge"
526
+ }
527
+ ~~~
528
+
529
+ Stableyard responds with deposit info:
530
+
531
+ ~~~json
532
+ {
533
+ "id": "ses_b6afc57b153e2ae1f8fb1025",
534
+ "status": "open",
535
+ "deposit": {
536
+ "type": "direct_transfer",
537
+ "address": "0xdfd4ab80e163d6864e26f37540563cbf2e52a582",
538
+ "chainId": 8453,
539
+ "token": {
540
+ "address": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
541
+ "symbol": "USDC",
542
+ "decimals": 6
543
+ },
544
+ "amount": {
545
+ "raw": "100000",
546
+ "formatted": "0.10"
547
+ }
548
+ }
549
+ }
550
+ ~~~
551
+
552
+ The client sends 0.10 USDC to the deposit address on Base, then
553
+ submits the transaction hash. After settlement (~1 second for
554
+ same-chain), the client retries with the credential.
555
+
556
+
557
+ ## Retry with Credential
558
+
559
+ ~~~
560
+ GET /api/market-data HTTP/1.1
561
+ Host: api.example.com
562
+ Authorization: Payment eyJjaGFsbGVuZ2UiOnsiY2hhbGxlbmdlSWQi
563
+ OiJQT3VkbURIVXBpWkt0U3FyTWZGSEFkRXRMVHNjIiwibWV0aG9kIj
564
+ oic3RhYmxleWFyZCIsImludGVudCI6ImNoYXJnZSJ9LCJwYXlsb2FkI
565
+ jp7InNlc3Npb25JZCI6InNlc19iNmFmYzU3YjE1M2UyYWUxZjhmYjEw
566
+ MjUifX0
567
+ ~~~
568
+
569
+ Server verifies via Stableyard API:
570
+
571
+ ~~~
572
+ POST /v2/sessions/ses_b6afc57b153e2ae1f8fb1025/verify HTTP/1.1
573
+ Host: api.stableyard.fi
574
+ Authorization: Bearer sy_secret_...
575
+ ~~~
576
+
577
+ ~~~json
578
+ {
579
+ "verified": true,
580
+ "sessionId": "ses_b6afc57b153e2ae1f8fb1025",
581
+ "resource": "api.example.com/charge"
582
+ }
583
+ ~~~
584
+
585
+ Server returns the resource with a receipt:
586
+
587
+ ~~~
588
+ HTTP/1.1 200 OK
589
+ Payment-Receipt: eyJtZXRob2QiOiJzdGFibGV5YXJkIiwic3RhdHVzIj
590
+ oic3VjY2VzcyIsInJlZmVyZW5jZSI6InNlc19iNmFmYzU3YjE1M2Uy
591
+ YWUxZjhmYjEwMjUiLCJ0aW1lc3RhbXAiOiIyMDI2LTAzLTE5VDE2Oj
592
+ Mzjoy...
593
+ Content-Type: application/json
594
+
595
+ {
596
+ "data": {
597
+ "btc": {"price": 98450.23, "change24h": 2.1},
598
+ "eth": {"price": 3842.67, "change24h": -0.5}
599
+ }
600
+ }
601
+ ~~~
602
+
603
+
604
+ # Acknowledgements
605
+
606
+ The Machine Payments Protocol was created by Jake Moxey, Brendan
607
+ Ryan, Tom Meagher, and the teams at Tempo and Stripe.