Skip to content
This repository was archived by the owner on Jun 1, 2026. It is now read-only.

Commit 1d60323

Browse files
committed
fix(subscription): fix compile errors, simplify createPayableRequirement, and update tests
1 parent a2ff6c0 commit 1d60323

8 files changed

Lines changed: 129 additions & 37 deletions

File tree

examples/acp-base/subscription/seller.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,8 +148,9 @@ async function handleSubscriptionCheck(acpClient: AcpClient, job: AcpJob) {
148148
subscriptionMetadata,
149149
MemoType.PAYABLE_REQUEST_SUBSCRIPTION,
150150
fareAmount,
151-
undefined,
152-
{ duration: durationSeconds },
151+
job.providerAddress,
152+
undefined, // expiredAt — defaults to 5 minutes from now
153+
durationSeconds,
153154
);
154155
console.log(
155156
`Seller: Job ${job.id} — Subscription payment request created (tx: ${subReqTx})`,

src/acpAccount.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,6 @@ export class AcpAccount {
1111
public expiry?: number
1212
) {}
1313

14-
isSubscriptionValid(): boolean {
15-
if (!this.expiry || this.expiry === 0) {
16-
return false;
17-
}
18-
return this.expiry > Math.floor(Date.now() / 1000);
19-
}
20-
2114
async updateMetadata(metadata: Record<string, any>) {
2215
const hash = await this.contractClient.updateAccountMetadata(
2316
this.id,

src/acpClient.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ interface IAcpBrowseAgentsOptions {
5454
cluster?: string;
5555
sortBy?: AcpAgentSort[];
5656
topK?: number;
57+
sort_by?: AcpAgentSort[]; // deprecated
58+
top_k?: number; // deprecated
5759
graduationStatus?: AcpGraduationStatus;
5860
onlineStatus?: AcpOnlineStatus;
5961
showHiddenOfferings?: boolean;
@@ -463,11 +465,13 @@ class AcpClient {
463465
async browseAgents(
464466
keyword: string,
465467
options: IAcpBrowseAgentsOptions = {},
466-
): Promise<AcpAgent[] | undefined> {
468+
): Promise<AcpAgent[]> {
467469
const {
468470
cluster,
469471
sortBy,
470472
topK = 5,
473+
sort_by,
474+
top_k = 5,
471475
graduationStatus,
472476
onlineStatus,
473477
showHiddenOfferings,
@@ -477,11 +481,12 @@ class AcpClient {
477481
search: keyword,
478482
};
479483

480-
params.top_k = topK;
484+
params.top_k = topK || top_k;
481485
params.walletAddressesToExclude = this.walletAddress;
482486

483-
if (sortBy && sortBy.length > 0) {
484-
params.sortBy = sortBy.join(",");
487+
const sortByArray = sortBy || sort_by;
488+
if (sortByArray && sortByArray.length > 0) {
489+
params.sortBy = sortByArray.join(",");
485490
}
486491

487492
if (cluster) {

src/acpJob.ts

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -178,19 +178,12 @@ class AcpJob {
178178
| MemoType.PAYABLE_TRANSFER
179179
| MemoType.PAYABLE_REQUEST_SUBSCRIPTION,
180180
amount: FareAmountBase,
181-
recipient?: Address,
182-
options?: {
183-
expiredAt?: Date;
184-
duration?: number; // Required for PAYABLE_REQUEST_SUBSCRIPTION
185-
nextPhase?: AcpJobPhases;
186-
}
181+
recipient: Address,
182+
expiredAt: Date = new Date(Date.now() + 1000 * 60 * 5), // 5 minutes
183+
duration?: number // Required for PAYABLE_REQUEST_SUBSCRIPTION
187184
) {
188-
const expiredAt = options?.expiredAt ?? new Date(Date.now() + 1000 * 60 * 5); // 5 minutes
189-
const nextPhase = options?.nextPhase ?? AcpJobPhases.TRANSACTION;
190-
const finalRecipient = recipient ?? this.providerAddress;
191-
192185
// Validate subscription-specific requirements
193-
if (type === MemoType.PAYABLE_REQUEST_SUBSCRIPTION && !options?.duration) {
186+
if (type === MemoType.PAYABLE_REQUEST_SUBSCRIPTION && !duration) {
194187
throw new AcpError("Duration is required for subscription payment requests");
195188
}
196189

@@ -247,13 +240,13 @@ class AcpJob {
247240
this.id,
248241
content,
249242
amount.amount,
250-
finalRecipient,
243+
recipient,
251244
isPercentagePricing
252245
? BigInt(Math.round(this.priceValue * 10000))
253246
: feeAmount.amount,
254247
isPercentagePricing ? FeeType.PERCENTAGE_FEE : FeeType.NO_FEE,
255-
options?.duration!,
256-
nextPhase,
248+
duration!,
249+
AcpJobPhases.TRANSACTION,
257250
expiredAt,
258251
amount.fare.contractAddress
259252
)
@@ -270,14 +263,14 @@ class AcpJob {
270263
content,
271264
amount.fare.contractAddress,
272265
amount.amount,
273-
finalRecipient,
266+
recipient,
274267
isPercentagePricing
275268
? BigInt(Math.round(this.priceValue * 10000)) // convert to basis points
276269
: feeAmount.amount,
277270
isPercentagePricing ? FeeType.PERCENTAGE_FEE : FeeType.NO_FEE,
278271
type as MemoType.PAYABLE_REQUEST,
279272
expiredAt,
280-
nextPhase,
273+
AcpJobPhases.TRANSACTION,
281274
getDestinationEndpointId(amount.fare.chainId as number)
282275
)
283276
);
@@ -289,12 +282,12 @@ class AcpJob {
289282
this.id,
290283
content,
291284
amount.amount,
292-
finalRecipient,
285+
recipient,
293286
isPercentagePricing
294287
? BigInt(Math.round(this.priceValue * 10000)) // convert to basis points
295288
: feeAmount.amount,
296289
isPercentagePricing ? FeeType.PERCENTAGE_FEE : FeeType.NO_FEE,
297-
nextPhase,
290+
AcpJobPhases.TRANSACTION,
298291
type,
299292
expiredAt,
300293
amount.fare.contractAddress

src/interfaces.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ export interface IAcpClientOptions {
114114
}
115115

116116
export interface IAcpAgent {
117-
id: string | number;
117+
id: number;
118118
name: string;
119119
description: string;
120120
walletAddress: Address;

test/integration/acpClient.integration.test.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,8 +132,6 @@ describe("AcpClient Integration Testing", () => {
132132
if (result.length > 0) {
133133
const firstAgent = result[0];
134134

135-
console.log("firstAgent: ", firstAgent);
136-
137135
expect(firstAgent).toHaveProperty("id");
138136
expect(firstAgent).toHaveProperty("name");
139137
expect(firstAgent).toHaveProperty("description");

test/unit/acpJob.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -430,7 +430,7 @@ describe("AcpJob Unit Testing", () => {
430430
MemoType.PAYABLE_REQUEST,
431431
mockFareAmount,
432432
recipient,
433-
{ expiredAt },
433+
expiredAt,
434434
);
435435

436436
expect(mockContractClient.approveAllowance).not.toHaveBeenCalled();
@@ -472,7 +472,7 @@ describe("AcpJob Unit Testing", () => {
472472
MemoType.PAYABLE_TRANSFER_ESCROW,
473473
mockFareAmount,
474474
recipient,
475-
{ expiredAt },
475+
expiredAt,
476476
);
477477

478478
expect(mockContractClient.approveAllowance).toHaveBeenCalledWith(

test/unit/acpJobOffering.test.ts

Lines changed: 103 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import AcpJobOffering, { PriceType } from "../../src/acpJobOffering";
44
import { BaseAcpContractClient } from "../../src";
55
import AcpClient from "../../src/acpClient";
66
import AcpError from "../../src/acpError";
7+
import { AcpAccount } from "../../src/acpAccount";
78

89
jest.mock("../../src/configs/acpConfigs", () => ({
910
baseSepoliaAcpConfig: {
@@ -77,10 +78,11 @@ describe("AcpJobOffering Unit Testing", () => {
7778
expect(offering.requiredFunds).toBe(true);
7879
expect(offering.slaMinutes).toBe(1440);
7980
expect(offering.requirement).toBe(undefined);
81+
expect(offering.deliverable).toBe(undefined);
8082
expect(offering.subscriptionTiers).toEqual([]);
8183
});
8284

83-
it("should use priceType FIXED", () => {
85+
it("should use priceType FIXED and requiredFunds", () => {
8486
const offering = new AcpJobOffering(
8587
mockAcpClient,
8688
mockContractClient,
@@ -94,6 +96,7 @@ describe("AcpJobOffering Unit Testing", () => {
9496

9597
expect(offering).toBeInstanceOf(AcpJobOffering);
9698
expect(offering.priceType).toBe(PriceType.FIXED);
99+
expect(offering.requiredFunds).toBe(false);
97100
});
98101

99102
it("should accept custom priceType", () => {
@@ -150,6 +153,44 @@ describe("AcpJobOffering Unit Testing", () => {
150153
expect(offering.requirement).toBe(requirementObject);
151154
});
152155

156+
it("should accept deliverable as string", () => {
157+
const offering = new AcpJobOffering(
158+
mockAcpClient,
159+
mockContractClient,
160+
"0xProvider" as Address,
161+
"MockJob",
162+
100,
163+
PriceType.FIXED,
164+
true,
165+
1440,
166+
undefined,
167+
"custom deliverable",
168+
);
169+
170+
expect(offering).toBeInstanceOf(AcpJobOffering);
171+
expect(offering.deliverable).toBe("custom deliverable");
172+
});
173+
174+
it("should accept deliverable as object", () => {
175+
const deliverableObject = { type: "image", format: "png" };
176+
177+
const offering = new AcpJobOffering(
178+
mockAcpClient,
179+
mockContractClient,
180+
"0xProvider" as Address,
181+
"MockJob",
182+
100,
183+
PriceType.FIXED,
184+
false,
185+
1440,
186+
undefined,
187+
deliverableObject,
188+
);
189+
190+
expect(offering).toBeInstanceOf(AcpJobOffering);
191+
expect(offering.deliverable).toEqual(deliverableObject);
192+
});
193+
153194
it("should accept subscription tiers", () => {
154195
const offering = new AcpJobOffering(
155196
mockAcpClient,
@@ -401,6 +442,67 @@ describe("AcpJobOffering Unit Testing", () => {
401442
});
402443

403444
it("should use createJobWithAccount for V2 contracts when account exists", async () => {
445+
const mockUserOpHash = "0xmockUserOpHash";
446+
const mockJobId = 12345;
447+
const mockCreateJobPayload = { data: "createJobWithAccountPayload" };
448+
const mockSetBudgetPayload = { data: "setBudgetPayload" };
449+
const mockMemoPayload = { data: "memoPayload" };
450+
const mockAccount = new AcpAccount(
451+
mockContractClient as any,
452+
999,
453+
"0xClient" as Address,
454+
"0xProvider" as Address,
455+
{},
456+
);
457+
458+
// Mock getByClientAndProvider to return an account (V2 behavior)
459+
jest
460+
.spyOn(mockAcpClient, "getByClientAndProvider")
461+
.mockResolvedValue(mockAccount as any);
462+
463+
mockContractClient.createJobWithAccount.mockReturnValue(
464+
mockCreateJobPayload as any,
465+
);
466+
mockContractClient.handleOperation.mockResolvedValue({
467+
userOpHash: mockUserOpHash,
468+
} as any);
469+
mockContractClient.getJobId.mockResolvedValue(mockJobId);
470+
mockContractClient.setBudgetWithPaymentToken.mockReturnValue(
471+
mockSetBudgetPayload as any,
472+
);
473+
mockContractClient.createMemo.mockReturnValue(mockMemoPayload as any);
474+
475+
// Use a non-V1 contract address
476+
mockContractClient.config.contractAddress =
477+
"0xV2ContractAddress" as Address;
478+
479+
const offering = new AcpJobOffering(
480+
mockAcpClient,
481+
mockContractClient,
482+
"0xProvider" as Address,
483+
"Generate Image",
484+
100,
485+
PriceType.FIXED,
486+
true,
487+
1440,
488+
);
489+
490+
const result = await offering.initiateJob(
491+
"generate an image about Virtuals",
492+
);
493+
494+
expect(result).toBe(mockJobId);
495+
expect(mockContractClient.createJobWithAccount).toHaveBeenCalledTimes(1);
496+
expect(mockContractClient.createJob).not.toHaveBeenCalled();
497+
498+
// Verify that createJobWithAccount was called with account.id (1st parameter)
499+
const createJobCall =
500+
mockContractClient.createJobWithAccount.mock.calls[0];
501+
const accountIdParam = createJobCall[0];
502+
expect(accountIdParam).toBe(mockAccount.id);
503+
});
504+
505+
it("should use createJobWithAccount for V2 contracts when subscription account exists", async () => {
404506
const mockUserOpHash = "0xmockUserOpHash";
405507
const mockJobId = 12345;
406508
const mockCreateJobPayload = { data: "createJobWithAccountPayload" };

0 commit comments

Comments
 (0)