Skip to content

Commit 241fa10

Browse files
committed
docs(subscription-example): align buyer/seller docs with scenario-based flow
1 parent 4562980 commit 241fa10

File tree

3 files changed

+92
-56
lines changed

3 files changed

+92
-56
lines changed

examples/acp-base/subscription/README.md

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,21 @@ This example demonstrates how to test subscription-backed jobs with ACP v2 using
66

77
The flow covers:
88

9-
- Buyer initiates a job with subscription metadata.
10-
- Seller checks the account subscription status.
9+
- Assumes the selected agent has:
10+
- subscription offering at `jobOfferings[0]`
11+
- fixed-price offering at `jobOfferings[1]`
12+
- Buyer runs one of two scenarios:
13+
- Scenario 1: subscription offering
14+
- Scenario 2: fixed-price offering
15+
- Seller handles incoming jobs by price type.
16+
- For subscription jobs, seller checks account subscription status.
1117
- If no valid subscription exists, seller requests subscription payment.
12-
- Buyer pays the subscription and continues the normal job flow.
13-
- Subsequent jobs reuse the active subscription account (no additional subscription payment).
18+
- If subscription is active, seller proceeds without requesting subscription payment.
1419

1520
## Files
1621

17-
- buyer.ts: Initiates the subscription job and handles subscription payment.
18-
- seller.ts: Validates subscription status and requests subscription payment when needed.
22+
- buyer.ts: Runs scenario-based job initiation and handles subscription/fixed-price memo flows.
23+
- seller.ts: Handles fixed-price and subscription paths, including subscription payment requirements.
1924
- env.ts: Loads environment variables from .env.
2025

2126
## Setup
@@ -32,9 +37,9 @@ The flow covers:
3237
2. Install dependencies (from repo root):
3338
- npm install
3439

35-
3. Update buyer.ts placeholders:
36-
- Replace <your-filter-agent-keyword>
37-
- Replace <your-schema-key> and <your-schema-value>
40+
3. Ensure selected agent has at least:
41+
- One subscription offering at index `jobOfferings[0]`
42+
- One fixed-price offering at index `jobOfferings[1]`
3843

3944
## Run
4045

@@ -44,23 +49,27 @@ The flow covers:
4449

4550
2. Start the buyer in another terminal:
4651
- cd examples/acp-base/subscription
47-
- npx ts-node buyer.ts
52+
- npx ts-node buyer.ts --scenario 1 # Subscription offering
53+
- npx ts-node buyer.ts --scenario 2 # Fixed-price offering
4854

4955
## Expected Flow
5056

51-
- Job 1:
52-
- Buyer creates a job with subscription metadata (sub_premium).
53-
- Seller checks subscription validity and requests payment if expired/missing.
54-
- Buyer pays the subscription and then pays the job requirement.
55-
- Seller delivers, job completes.
57+
- Scenario 1 (Subscription offering):
58+
- Buyer initiates a subscription job with tier metadata (for example `sub_premium`).
59+
- Seller checks subscription validity.
60+
- If missing/expired, seller creates `PAYABLE_REQUEST_SUBSCRIPTION`.
61+
- Buyer calls `paySubscription(...)`.
62+
- Seller moves forward and eventually delivers in `TRANSACTION` phase.
63+
- If you run scenario 1 again while subscription is active, seller skips subscription payment and sends a plain requirement.
5664

57-
- Job 2:
58-
- Buyer creates a second job with the same metadata.
59-
- Subscription account is reused, no subscription payment requested.
60-
- Normal job flow continues.
65+
- Scenario 2 (Fixed-price offering):
66+
- Buyer initiates a non-subscription job.
67+
- Seller accepts and creates `PAYABLE_REQUEST`.
68+
- Buyer pays with `payAndAcceptRequirement(...)`.
69+
- Seller delivers in `TRANSACTION` phase.
6170

6271
## Notes
6372

6473
- Both agents must be registered and whitelisted on ACP.
65-
- The seller config uses a local price table for subscription tiers.
74+
- Subscription tier name in buyer defaults to `sub_premium`; adjust to match seller offering config.
6675
- If the buyer does not see the seller, make sure the seller has at least one job offering and is searchable by the buyer's keyword.

examples/acp-base/subscription/buyer.ts

Lines changed: 54 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,9 @@
77
*
88
* Default: scenario 1
99
*
10-
* Scenarios:
11-
* 1. Subscription Offering
12-
* - Seller creates PAYABLE_REQUEST_SUBSCRIPTION memo
13-
* - Buyer pays subscription; job proceeds to delivery
14-
*
15-
* 2. Non-Subscription Offering (Fixed-Price)
16-
* - Uses a non-subscription offering (jobOfferings[1])
17-
* - Seller accepts and creates a payable requirement
18-
* - Buyer pays and advances to delivery
10+
* Assumption:
11+
* - chosenAgent.jobOfferings[0] is a subscription offering
12+
* - chosenAgent.jobOfferings[1] is a non-subscription (fixed-price) offering
1913
*/
2014
import AcpClient, {
2115
AcpContractClientV2,
@@ -39,7 +33,8 @@ const SUBSCRIPTION_TIER = "sub_premium";
3933

4034
// Parse --scenario N from argv
4135
const scenarioArg = process.argv.indexOf("--scenario");
42-
const SCENARIO = scenarioArg !== -1 ? parseInt(process.argv[scenarioArg + 1], 10) : 1;
36+
const SCENARIO =
37+
scenarioArg !== -1 ? parseInt(process.argv[scenarioArg + 1], 10) : 1;
4338

4439
async function buyer() {
4540
console.log(`=== Subscription Example - Buyer (Scenario ${SCENARIO}) ===\n`);
@@ -66,37 +61,55 @@ async function buyer() {
6661
console.log(
6762
`Buyer: Job ${job.id} — Subscription payment requested: ${memoToSign.content}`,
6863
);
69-
console.log(`Buyer: Job ${job.id} — Amount: ${memoToSign.payableDetails?.amount}`);
64+
console.log(
65+
`Buyer: Job ${job.id} — Amount: ${memoToSign.payableDetails?.amount}`,
66+
);
7067
const { txnHash: subPayTx } = await job.paySubscription(
7168
`Subscription payment for ${SUBSCRIPTION_TIER}`,
7269
);
73-
console.log(`Buyer: Job ${job.id} — Subscription paid (tx: ${subPayTx})`);
70+
console.log(
71+
`Buyer: Job ${job.id} — Subscription paid (tx: ${subPayTx})`,
72+
);
7473

75-
// Fixed-price requirement — pay and advance to delivery (Scenario 3)
74+
// Fixed-price requirement — pay and advance to delivery (Scenario 2)
7675
} else if (
7776
job.phase === AcpJobPhases.NEGOTIATION &&
7877
memoToSign?.type === MemoType.PAYABLE_REQUEST
7978
) {
80-
console.log(`Buyer: Job ${job.id} — Fixed-price requirement, paying now`);
79+
console.log(
80+
`Buyer: Job ${job.id} — Fixed-price requirement, paying now`,
81+
);
8182
const payResult = await job.payAndAcceptRequirement("Payment for job");
82-
console.log(`Buyer: Job ${job.id} — Paid and advanced to TRANSACTION phase (tx: ${payResult?.txnHash})`);
83+
console.log(
84+
`Buyer: Job ${job.id} — Paid and advanced to TRANSACTION phase (tx: ${payResult?.txnHash})`,
85+
);
8386

84-
// Valid subscription — accept requirement without payment (Scenario 2)
87+
// Active subscription path — accept requirement without payment
8588
} else if (
8689
job.phase === AcpJobPhases.NEGOTIATION &&
8790
memoToSign?.type === MemoType.MESSAGE &&
8891
memoToSign?.nextPhase === AcpJobPhases.TRANSACTION
8992
) {
90-
console.log(`Buyer: Job ${job.id} — Subscription active, accepting without payment`);
93+
console.log(
94+
`Buyer: Job ${job.id} — Subscription active, accepting without payment`,
95+
);
9196
const { txnHash: signMemoTx } = await job.acceptRequirement(
9297
memoToSign,
9398
"Subscription verified, proceeding to delivery",
9499
);
95-
console.log(`Buyer: Job ${job.id} — Advanced to TRANSACTION phase (tx: ${signMemoTx})`);
100+
console.log(
101+
`Buyer: Job ${job.id} — Advanced to TRANSACTION phase (tx: ${signMemoTx})`,
102+
);
96103
} else if (job.phase === AcpJobPhases.COMPLETED) {
97-
console.log(`Buyer: Job ${job.id} — Completed! Deliverable:`, job.deliverable);
104+
console.log(
105+
`Buyer: Job ${job.id} — Completed! Deliverable:`,
106+
job.deliverable,
107+
);
98108
} else if (job.phase === AcpJobPhases.REJECTED) {
99-
console.log(`Buyer: Job ${job.id} — Rejected. Reason:`, job.rejectionReason);
109+
console.log(
110+
`Buyer: Job ${job.id} — Rejected. Reason:`,
111+
job.rejectionReason,
112+
);
100113
} else {
101114
console.log(
102115
`Buyer: Job ${job.id} — Unhandled event (phase: ${AcpJobPhases[job.phase]}, ` +
@@ -116,36 +129,47 @@ async function buyer() {
116129
showHiddenOfferings: true,
117130
});
118131

132+
console.log("Relevant agents:", relevantAgents);
133+
119134
if (!relevantAgents || relevantAgents.length === 0) {
120135
console.error("No agents found");
121136
return;
122137
}
123138

139+
// Pick one of the agents based on your criteria (in this example we just pick the first one)
124140
const chosenAgent = relevantAgents[0];
141+
142+
// Pick one of the service offerings based on your criteria:
143+
// - index 0: subscription offering
144+
// - index 1: non-subscription (fixed-price) offering
125145
const subscriptionOffering = chosenAgent.jobOfferings[0];
126146
const fixedOffering = chosenAgent.jobOfferings[1];
127147

128148
switch (SCENARIO) {
129149
case 1: {
130-
console.log("--- Scenario 1: Subscription Offering ---\n");
131-
const jobId1 = await subscriptionOffering.initiateJob(
150+
const chosenJobOffering = subscriptionOffering;
151+
const jobId = await chosenJobOffering.initiateJob(
152+
// Requirement payload schema depends on your ACP service configuration.
153+
// If your service requires fields, replace {} with the expected schema payload.
132154
{},
133-
undefined,
134-
new Date(Date.now() + 1000 * 60 * 15), // 15 min job expiry
155+
undefined, // evaluator address, undefined fallback to empty address
156+
new Date(Date.now() + 1000 * 60 * 15), // job expiry duration, minimum 5 minutes
135157
SUBSCRIPTION_TIER,
136158
);
137-
console.log(`\nBuyer: [Scenario 1 — Subscription Offering] Job ${jobId1} initiated`);
159+
console.log(`Buyer: [Scenario 1 — Subscription Offering] Job ${jobId} initiated`);
138160
break;
139161
}
140162

141163
case 2: {
142-
console.log("--- Scenario 2: Non-Subscription Offering (Fixed-Price) ---\n");
143-
const jobId2 = await fixedOffering.initiateJob(
164+
const chosenJobOffering = fixedOffering;
165+
const jobId = await chosenJobOffering.initiateJob(
166+
// Requirement payload schema depends on your ACP service configuration.
167+
// If your service requires fields, replace {} with the expected schema payload.
144168
{},
145-
undefined,
146-
new Date(Date.now() + 1000 * 60 * 15), // 15 min job expiry
169+
undefined, // evaluator address, undefined fallback to empty address
170+
new Date(Date.now() + 1000 * 60 * 15), // job expiry duration, minimum 5 minutes
147171
);
148-
console.log(`\nBuyer: [Scenario 2 — Fixed-Price Job] Job ${jobId2} initiated`);
172+
console.log(`Buyer: [Scenario 2 — Fixed-Price Job] Job ${jobId} initiated`);
149173
break;
150174
}
151175

examples/acp-base/subscription/seller.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
/**
22
* Subscription Example - Seller (Provider)
33
*
4-
* Demonstrates the subscription lifecycle from the provider side:
4+
* Demonstrates provider-side handling for both subscription and fixed-price jobs:
55
* 1. Listen for new jobs
6-
* 2. Check if the job's account has an active subscription (expiry > now)
7-
* 3. If no subscription, create a PAYABLE_REQUEST_SUBSCRIPTION memo
8-
* 4. Once subscription is paid, proceed with normal job flow
6+
* 2. If job is fixed-price, accept and create PAYABLE_REQUEST
7+
* 3. If job is subscription, check account status via getSubscriptionPaymentRequirement
8+
* 4. If no active subscription, create PAYABLE_REQUEST_SUBSCRIPTION
9+
* 5. Deliver once job reaches TRANSACTION phase
910
*/
1011
import AcpClient, {
1112
AcpContractClientV2,
@@ -67,8 +68,10 @@ async function seller() {
6768
}
6869

6970
/**
70-
* Uses AcpClient.getSubscriptionPaymentRequirement to decide whether to
71-
* create a subscription payment request memo or just accept and create a requirement.
71+
* Handles pricing logic for incoming jobs:
72+
* - Fixed-price jobs: accept + create PAYABLE_REQUEST
73+
* - Subscription jobs with active subscription: accept + create plain requirement
74+
* - Subscription jobs without active subscription: accept + create PAYABLE_REQUEST_SUBSCRIPTION
7275
*/
7376
async function handleSubscriptionCheck(acpClient: AcpClient, job: AcpJob) {
7477
const offeringName = job.name;

0 commit comments

Comments
 (0)