@@ -154,6 +154,7 @@ Challenge expiry is conveyed by the `expires` auth-param in
154154| ` methodDetails.chainId ` | number | OPTIONAL | Tempo chain ID (default: 42431) |
155155| ` methodDetails.feePayer ` | boolean | OPTIONAL | If ` true ` , server pays transaction fees (default: ` false ` ) |
156156| ` methodDetails.memo ` | string | OPTIONAL | A ` bytes32 ` hex value. When present, the client MUST use ` transferWithMemo ` instead of ` transfer ` . |
157+ | ` methodDetails.splits ` | array | OPTIONAL | Additional recipients that receive a portion of ` amount ` . See {{split-payments}}. |
157158
158159** Example:**
159160
@@ -172,7 +173,7 @@ Challenge expiry is conveyed by the `expires` auth-param in
172173The client fulfills this by signing a Tempo Transaction with
173174` transfer(recipient, amount) ` or ` transferWithMemo(recipient, amount, memo) `
174175on the specified ` currency ` (token address),
175- with ` validBefore ` set to the challenge ` expires ` auth-param. The client SHOULD use a dedicated
176+ with ` validBefore ` no later than the challenge ` expires ` auth-param. The client MAY use a dedicated
176177` nonceKey ` (2D nonce lane) for payment transactions to avoid blocking
177178other account activity if the transaction is not immediately settled.
178179
@@ -181,6 +182,108 @@ If `methodDetails.feePayer` is `true`, the client signs with
181182server to sponsor fees. If ` feePayer ` is ` false ` or omitted, the client
182183MUST set ` fee_token ` and pay fees themselves.
183184
185+ ## Split Payments {#split-payments}
186+
187+ The ` splits ` field enables a single charge to distribute payment across
188+ multiple recipients atomically. This is useful for platform fees, revenue
189+ sharing, and marketplace payouts.
190+
191+ ### Semantics
192+
193+ The top-level ` amount ` represents the total amount the client pays. Each
194+ entry in ` splits ` specifies a recipient and the amount they receive. The
195+ primary recipient (the top-level ` recipient ` ) receives the remainder:
196+ ` amount ` minus the sum of all split amounts.
197+
198+ ### Split Entry Schema
199+
200+ Each entry in the ` splits ` array is a JSON object:
201+
202+ | Field | Type | Required | Description |
203+ | -------| ------| ----------| -------------|
204+ | ` amount ` | string | REQUIRED | Amount in base units for this recipient |
205+ | ` memo ` | string | OPTIONAL | A ` bytes32 ` hex value for ` transferWithMemo ` |
206+ | ` recipient ` | string | REQUIRED | Recipient address |
207+
208+ The ` amount ` field in each split entry MUST be a base-10 integer string
209+ with no sign, decimal point, exponent, or surrounding whitespace. Each
210+ ` splits[i].amount ` MUST be greater than zero. The syntax and encoding
211+ requirements for ` splits[i].memo ` are identical to those for
212+ ` methodDetails.memo ` , but apply only to that split transfer. Address
213+ fields are compared by decoded 20-byte value, not by string form.
214+
215+ ### Constraints
216+
217+ Servers MUST NOT generate a request where the sum of ` splits[].amount `
218+ values is greater than or equal to ` amount ` . Clients MUST reject any
219+ request that violates this constraint. This ensures the primary
220+ recipient always receives a non-zero remainder, avoiding the need to
221+ define zero-value transfer semantics.
222+
223+ Additional constraints:
224+
225+ - If present, ` splits ` MUST contain at least 1 entry. Servers
226+ SHOULD limit splits to 10 entries to keep gas usage within a
227+ single block's budget (~ 29,000 gas per additional TIP-20
228+ transfer). Servers MAY reject requests exceeding their supported
229+ split count.
230+ - All transfers MUST target the same ` currency ` token address.
231+
232+ ### Ordering
233+
234+ The order of entries in ` splits ` is not significant for verification.
235+ Clients SHOULD emit calls in array order. Servers MUST verify that the
236+ required payment effects are present regardless of call ordering.
237+
238+ ### Example
239+
240+ ~~~ json
241+ {
242+ "amount" : " 1000000" ,
243+ "currency" : " 0x20c0000000000000000000000000000000000000" ,
244+ "recipient" : " 0x742d35Cc6634C0532925a3b844Bc9e7595f8fE00" ,
245+ "methodDetails" : {
246+ "chainId" : 42431 ,
247+ "feePayer" : true ,
248+ "splits" : [
249+ {
250+ "amount" : " 50000" ,
251+ "recipient" : " 0xA1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2"
252+ },
253+ {
254+ "amount" : " 10000" ,
255+ "memo" : " 0x00000000000000000000000000000000000000000000000000000000deadbeef" ,
256+ "recipient" : " 0xC4D5E6F7A8B9C4D5E6F7A8B9C4D5E6F7A8B9C4D5"
257+ }
258+ ]
259+ }
260+ }
261+ ~~~
262+
263+ This requests a total payment of 1.00 pathUSD (1,000,000 base units).
264+ The platform receives 0.05 pathUSD, the affiliate receives 0.01 pathUSD
265+ (with a memo), and the primary recipient receives the remaining
266+ 0.94 pathUSD (940,000 base units).
267+
268+ ### Client Behavior
269+
270+ When ` splits ` is present, the client MUST produce a transaction whose
271+ on-chain effects include the following ` Transfer ` or ` TransferWithMemo `
272+ events on the ` currency ` token address:
273+
274+ 1 . The primary ` recipient ` receives ` amount - sum(splits[].amount) ` .
275+ 2 . Each ` splits[i].recipient ` receives ` splits[i].amount ` . If
276+ ` splits[i].memo ` is present, the corresponding transfer MUST use
277+ ` transferWithMemo ` .
278+
279+ The top-level ` methodDetails.memo ` , if present, applies to the primary
280+ transfer.
281+
282+ Clients MAY achieve these effects using any valid transaction structure,
283+ including batched calls, smart contract wallet invocations, or
284+ intermediary operations such as token swaps — provided all required
285+ transfer events are emitted atomically.
286+
184287# Credential Schema
185288
186289The credential in the ` Authorization ` header contains a base64url-encoded
@@ -201,8 +304,11 @@ chain ID applicable to the challenge and the payer's Ethereum address.
201304
202305When ` type ` is ` "transaction" ` , ` signature ` contains the complete signed
203306Tempo Transaction (type 0x76) serialized as RLP and hex-encoded with
204- ` 0x ` prefix. The transaction MUST contain a ` transfer(recipient, amount) `
205- or ` transferWithMemo(recipient, amount, memo) ` call on the TIP-20 token.
307+ ` 0x ` prefix. The transaction MUST authorize payment in the requested
308+ TIP-20 token sufficient to satisfy the challenge parameters, using one
309+ or more ` transfer ` and/or ` transferWithMemo ` calls. When ` splits ` are
310+ present, the transaction MUST include transfers for each split entry
311+ (see {{split-payments}}).
206312
207313| Field | Type | Required | Description |
208314| -------| ------| ----------| -------------|
@@ -311,8 +417,10 @@ When acting as fee payer, servers:
311417# Settlement Procedure
312418
313419For ` intent="charge" ` fulfilled via transaction, the client signs a
314- transaction containing a ` transfer ` or ` transferWithMemo ` call. If ` feePayer: true ` , the server
315- adds its fee payer signature before broadcasting:
420+ transaction containing one or more ` transfer ` or ` transferWithMemo ` calls.
421+ When ` splits ` are present, the transaction contains multiple calls (see
422+ {{split-payments}}). If ` feePayer: true ` , the server adds its fee payer
423+ signature before broadcasting:
316424
317425~~~
318426 Client Server Tempo Network
@@ -388,16 +496,33 @@ the transaction. The server verifies the transaction onchain:
388496Before broadcasting a transaction credential, servers MUST verify:
389497
3904981 . Deserialize the RLP-encoded transaction from ` payload.signature `
391- 2 . Verify the transaction contains a ` transfer(recipient, amount) ` or
392- ` transferWithMemo(recipient, amount, memo) ` call matching the challenge request
393- 3 . Verify the call target matches the ` currency ` token address
394- 4 . Verify the ` amount ` matches the challenge request amount
395- 5 . Verify the ` recipient ` matches the challenge request recipient
396- 6 . If ` methodDetails.memo ` is present, verify the transaction uses
499+ 2 . Verify the transaction contains ` transfer ` or ` transferWithMemo `
500+ calls on the ` currency ` token address
501+ 3 . Verify the ` amount ` matches the challenge request amount
502+ 4 . Verify the ` recipient ` matches the challenge request recipient
503+ 5 . If ` methodDetails.memo ` is present, verify the transaction uses
397504 ` transferWithMemo ` with the matching memo value
505+ 6 . If ` methodDetails.splits ` is present, verify the transaction
506+ includes transfers satisfying each split entry: the primary
507+ recipient receives ` amount - sum(splits[].amount) ` , each split
508+ recipient receives its specified amount, and any required memo
509+ values are present
510+
511+ Servers MAY impose additional structural requirements (such as
512+ exact call count or ordering) as local policy before broadcasting.
513+
514+ ## Hash Verification {#hash-verification}
515+
398516For hash credentials, servers MUST fetch the transaction receipt and
399- verify the emitted ` Transfer ` or ` TransferWithMemo ` event logs match
400- the challenge parameters.
517+ verify that it indicates successful execution. Servers MUST verify
518+ that the receipt contains ` Transfer ` and/or ` TransferWithMemo ` event
519+ logs emitted by the ` currency ` token address whose payment effects
520+ satisfy the challenge parameters, including the primary recipient
521+ amount, any split amounts, and any required memo values.
522+
523+ Servers MAY additionally inspect the transaction call data as a
524+ local-policy check, but call-data decoding is not required for
525+ conformance.
401526
402527## Receipt Generation
403528
@@ -434,6 +559,26 @@ Clients MUST parse and verify the `request` payload before signing:
4345591 . Verify ` amount ` is reasonable for the service
4355602 . Verify ` currency ` is the expected token address
4365613 . Verify ` recipient ` is controlled by the expected party
562+ 4 . If ` splits ` is present, verify the sum of split amounts is strictly
563+ less than ` amount ` and that all split recipients are expected
564+
565+ ## Split Payment Risks
566+
567+ When ` splits ` are present, additional risks apply:
568+
569+ ** Recipient Transparency** : Where a human approval step exists, clients
570+ SHOULD present each split recipient and amount so the user can verify
571+ the payment distribution. Clients SHOULD highlight when the primary
572+ recipient receives a small remainder relative to the total ` amount ` .
573+
574+ ** Gas Overhead** : Each additional split adds approximately 29,000 gas
575+ for the TIP-20 precompile transfer execution. A charge with 10 splits
576+ adds approximately 290,000 gas beyond a single-transfer charge. Servers
577+ sponsoring fees via ` feePayer: true ` MUST budget for the increased gas
578+ limit.
579+
580+ ** Split Count Bound** : Servers SHOULD limit ` splits ` to 10 entries.
581+ See {{split-payments}} for rationale.
437582
438583## Server-Paid Fees
439584
@@ -526,6 +671,48 @@ Host: api.example.com
526671Authorization: Payment eyJjaGFsbGVuZ2UiOnsiaWQiOiJrTTl4UHFXdlQybkpySHNZNGFEZkViIn0sInBheWxvYWQiOnsic2lnbmF0dXJlIjoiMHg3NmY5MDEuLi4iLCJ0eXBlIjoidHJhbnNhY3Rpb24ifSwic291cmNlIjoiZGlkOnBraDplaXAxNTU6NDI0MzE6MHgxMjM0NTY3ODkwYWJjZGVmMTIzNDU2Nzg5MGFiY2RlZjEyMzQ1Njc4In0
527672~~~
528673
674+ # Split Payment Example
675+
676+ ** Challenge with splits:**
677+
678+ ~~~ http
679+ HTTP/1.1 402 Payment Required
680+ WWW-Authenticate: Payment id="sP1itPaym3ntEx4mple",
681+ realm="marketplace.example.com",
682+ method="tempo",
683+ intent="charge",
684+ request="eyJhbW91bnQiOiIxMDAwMDAwIiwiY3VycmVuY3kiOiIweDIwYzAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAiLCJtZXRob2REZXRhaWxzIjp7ImNoYWluSWQiOjQyNDMxLCJmZWVQYXllciI6dHJ1ZSwic3BsaXRzIjpbeyJhbW91bnQiOiI1MDAwMCIsInJlY2lwaWVudCI6IjB4QTFCMkMzRDRFNUY2QTFCMkMzRDRFNUY2QTFCMkMzRDRFNUY2QTFCMiJ9XX0sInJlY2lwaWVudCI6IjB4NzQyZDM1Q2M2NjM0QzA1MzI5MjVhM2I4NDRCYzllNzU5NWY4ZkUwMCJ9",
685+ expires="2025-06-01T12:00:00Z"
686+ Cache-Control: no-store
687+ ~~~
688+
689+ The ` request ` decodes to:
690+
691+ ~~~ json
692+ {
693+ "amount" : " 1000000" ,
694+ "currency" : " 0x20c0000000000000000000000000000000000000" ,
695+ "recipient" : " 0x742d35Cc6634C0532925a3b844Bc9e7595f8fE00" ,
696+ "methodDetails" : {
697+ "chainId" : 42431 ,
698+ "feePayer" : true ,
699+ "splits" : [
700+ {
701+ "amount" : " 50000" ,
702+ "recipient" : " 0xA1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2"
703+ }
704+ ]
705+ }
706+ }
707+ ~~~
708+
709+ This requests a total payment of 1.00 pathUSD. The platform receives
710+ 0.05 pathUSD and the merchant receives 0.95 pathUSD. The resulting
711+ transaction must emit the following transfer events:
712+
713+ 1 . 950,000 to ` 0x742d...fE00 ` — merchant receives remainder
714+ 2 . 50,000 to ` 0xA1B2...A1B2 ` — platform fee
715+
529716# Acknowledgements
530717
531718The authors thank the Tempo community for their feedback on this
0 commit comments