Add off-escrow proportional-fee job type#31
Open
DROOdotFOO wants to merge 5 commits into
Open
Conversation
Enable facilitator offerings that charge a proportional fee while the principal settles OUTSIDE ACP escrow (e.g. a cross-chain stablecoin transfer via the buyer's signed intent). ACP escrows only the fee. Additive and fenced-off (priceType "percentage" + requiredFunds:false + settlement_tx_hash deliverable) - it reuses the existing plain-job path (hook = address(0)); no contract change and no impact on existing offerings. - types: optional AcpAgentOffering.feeBasisField + PRICE_TYPE consts - core/fee.ts: computePercentageFee, readFeeBasis, assertNotionalMatches - core/settlement.ts: build/parseSettlementDeliverable + schema - example: off-escrow-percentage/ (stubbed Xochi relay), README sections Fee unit (priceValue bps vs percent) is passed explicitly and never guessed - the backend convention must be confirmed before merge. Refs DROOdotFOO/raxol#373, DROOdotFOO/raxol#371
The settlement tx is mined on the transfer's destination chain (toChainId), not the ACP job chain (Base). - seller: recover the requirement from the job entries at job.funded and stamp the deliverable with toChainId (was session chainId) - buyer: reject a settlement proof whose chainId isn't the destination before completing Both flagged by Cursor Bugbot on Virtual-Protocol#31.
Author
|
Worth noting flagged bugs were both in the example-files, not the core helpers (fee.ts/settlement.ts) lol. |
Review pass on the off-escrow proportional-fee change: - settlement: reject non-positive / non-integer chainId in parseSettlementDeliverable; schema chainId is integer >= 1 - make the trust boundary explicit: the stub notional check reads a plaintext field and is not a real check until the intent signature is verified; note the on-chain tx-existence check the evaluator must do, and that skip-evaluation auto-releases the fee - note the fee is in the notional token's units (example uses USDC) - rewrite comments to drop shouty caps and PR/review meta-narration
Author
|
Self-review pass, two follow-up commits (b5a6300, f8f7ba0).
No change to core helper behavior or existing flows. One open question before merge: is a percentage
|
Second Bugbot pass on f8f7ba0: - buyer: derive expectedDestChainId from the requirement actually sent, not the sample constant, so a customized toChainId stays consistent - seller: on job.funded, if the requirement can't be recovered, notify the room instead of returning silently (job expires, fee refunds)
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit a73dc37. Configure here.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

What this is
A facilitator wants to sell cross-chain stablecoin transfers on ACP and charge a fee that scales with transfer size (say, 8 bps on the notional). Right now that offering isn't listable.
priceType: "percentage"is tied torequiredFunds: true, so the only way to price proportionally is to take custody of the buyer's capital and settle it same-chain on Base. For an off-escrow cross-chain relay neither of those holds, which leaves a flat fee, and a flat fee can't scale with size. That's the blocker. The full write-up is in DROOdotFOO/raxol#373 (spec) and #371 (storefront variants).The reference use case is Raxol's
Xochi.TransferOffering: the buyer signs their own intent (ERC-3009 / Permit2), the principal moves off-escrow straight from their wallet to the destination chain, and ACP only ever escrows the fee.Worth knowing before you review
I went in expecting to relax a percentage-vs-custody check somewhere in the SDK. It isn't here. This SDK never computes a fee.
setBudgetis always caller-supplied, andpriceType/priceValueare metadata it never reads. The coupling that blocks us lives in the registry/backend. On-chain,AgenticCommerceV3has norequiredFundsorpercentageconcept at all: a plain job (hook = address(0)) withbudget = feealready completes and splits the fee 90/5/5. We fork-traced that path end to end.So this PR can't flip the switch on its own. The backend still has to accept
percentage + requiredFunds:falseat offering-creation time. What it does is make the client side of the model first-class and give you a concrete artifact to mirror instead of a prose spec. If you'd rather the fee helper or settlement convention live somewhere else, that's fine; the shapes matter more than the file names.What's in it
All additive. No change to existing offerings, hooks, or the
createJobFromOfferingbranching. The off-escrow path is the plain-job branch that already existed.AcpAgentOffering.feeBasisField(optional): names the requirement field that carries the fee notional, e.g.notionalAtomic. PlusPRICE_TYPEconstants.core/fee.ts:computePercentageFee,readFeeBasis,assertNotionalMatches. bigint math, rounds down.core/settlement.ts:buildSettlementDeliverable/parseSettlementDeliverableand a JSON schema, so the deliverable is a settlement tx hash the evaluator can verify.src/examples/off-escrow-percentage/: a runnable buyer/seller pair. The Xochi quote/sign/settle calls are stubbed (they live in raxol, which is Elixir) and marked TODO. The ACP job lifecycle is real.The two custody concerns
Both came up in review, and both are handled in code rather than in the description.
Faked notional: the buyer declares the notional, but it's also bound in their signed intent. The seller calls
assertNotionalMatches(declared, boundInIntent)and rejects a mismatch before it sets a budget. The buyer independently re-derives the fee from the notional and the rate onbudget.set, and funds only if it equals what the seller proposed. Both sides check, and the buyer sees the exact fee before paying.Disputes: because ACP only holds the fee, a dispute reduces to one question, did the settlement tx land on the destination chain. Yes, release the fee. No, refund it. The principal never sat in anyone's wallet, so the worst case is a fee refund, not lost capital. That's easier to reconcile than the custodial percentage flow, where the buyer's whole principal passes through the seller.
One thing to confirm before merge
Is
priceValueon a percentage offering basis points or percent? Our agent prices in bps; I suspect the frontend shows percent. I didn't want to bake in a guess, socomputePercentageFeetakes the unit explicitly ("bps" | "percent"), and the example flips a singleFEE_UNITconstant. Pin the backend convention and that's settled. It's the only open question on the SDK side.Manual testing
tsc --noEmiton the library: clean.llm/example still needs@anthropic-ai/sdk; untouched here.Refs DROOdotFOO/raxol#373, DROOdotFOO/raxol#371
Note
Low Risk
Additive SDK helpers, types, docs, and an example; no changes to job creation hooks or existing offering flows.
Overview
Introduces off-escrow proportional-fee facilitator jobs: principal settles outside ACP (e.g. signed intent), while escrow holds only a fee derived from a requirement notional (
priceType: "percentage",requiredFunds: false). ExistingcreateJobFromOfferingbehavior is unchanged; this path uses the existing plain job branch (requiredFunds: false).Public API: optional
AcpAgentOffering.feeBasisField,PRICE_TYPE/PriceType, and exports fromcore/fee.ts(computePercentageFee,readFeeBasis,assertNotionalMatches) andcore/settlement.ts(buildSettlementDeliverable,parseSettlementDeliverable, schema).Example:
src/examples/off-escrow-percentage/documents seller notional checks, buyer fee preview beforefund(), and settlement-hash deliverables (Xochi relay stubbed). README and examples index updated accordingly.Reviewed by Cursor Bugbot for commit cc70c37. Bugbot is set up for automated code reviews on this repo. Configure here.