Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/acpAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -966,10 +966,11 @@ export class AcpAgent {
chainId,
"SubscriptionHook",
);
const jobPda = await acpClient.resolveJobPda(chainId, jobId);
const terms = await fetchProposedTerms(
acpClient.getProvider().getRpc(chainId),
subHook as SolanaAddress,
jobId,
jobPda,
ACP_COMMITMENT,
);
// Absent PDA mirrors the EVM zero-struct read for "nothing proposed".
Expand Down Expand Up @@ -1381,7 +1382,8 @@ export class AcpAgent {
chainId,
"MultiHookRouter",
);
const pda = await hookRouterPda(routerAddress as SolanaAddress, jobId);
const jobPda = await client.resolveJobPda(chainId, jobId);
const pda = await hookRouterPda(routerAddress as SolanaAddress, jobPda);
const maybe = await fetchMaybeHookRouter(
client.getProvider().getRpc(chainId),
pda,
Expand Down
608 changes: 351 additions & 257 deletions src/clients/solanaAcpClient.ts

Large diffs are not rendered by default.

38 changes: 24 additions & 14 deletions src/core/solana/generated/acp/accounts/acpState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,24 +61,29 @@ export type AcpState = {
pendingAuthority: Option<Address>;
/** SPL mint accepted for job budgets. */
paymentToken: Address;
/** Wallet that receives platform fees and reclaimed vault rent. */
/** Wallet that receives platform fees. Rent goes to `sponsor` instead. */
platformTreasury: Address;
/** Platform fee in basis points (0-10000). Applied to vault balance on completion. */
platformFeeBp: bigint;
/** Evaluator fee in basis points (0-10000). Applied on evaluator-confirmed completions. */
evaluatorFeeBp: bigint;
/**
* Monotonically increasing counter storing the LAST issued job ID
* The next job receives `job_counter + 1`; 0 means no jobs issued yet.
*/
jobCounter: bigint;
/** PDA bump seed. */
bump: number;
/**
* When true, all user-facing lifecycle instructions revert. Admin-toggled.
* Appended after `bump` so the existing on-chain layout only grows by one byte.
*/
paused: boolean;
/**
* Receives reclaimed native rent from every account close. Separate from
* `platform_treasury`, which receives SPL fees: under gas sponsorship the
* rent was never the user's money nor protocol revenue, it is the sponsor's
* float coming back. Appended after `paused` so the layout only grows at
* the tail; `migrate_state` backfills it from an explicit argument when
* zero, since it must differ from `platform_treasury` and so cannot be
* inferred from existing state.
*/
sponsor: Address;
};

export type AcpStateArgs = {
Expand All @@ -88,24 +93,29 @@ export type AcpStateArgs = {
pendingAuthority: OptionOrNullable<Address>;
/** SPL mint accepted for job budgets. */
paymentToken: Address;
/** Wallet that receives platform fees and reclaimed vault rent. */
/** Wallet that receives platform fees. Rent goes to `sponsor` instead. */
platformTreasury: Address;
/** Platform fee in basis points (0-10000). Applied to vault balance on completion. */
platformFeeBp: number | bigint;
/** Evaluator fee in basis points (0-10000). Applied on evaluator-confirmed completions. */
evaluatorFeeBp: number | bigint;
/**
* Monotonically increasing counter storing the LAST issued job ID
* The next job receives `job_counter + 1`; 0 means no jobs issued yet.
*/
jobCounter: number | bigint;
/** PDA bump seed. */
bump: number;
/**
* When true, all user-facing lifecycle instructions revert. Admin-toggled.
* Appended after `bump` so the existing on-chain layout only grows by one byte.
*/
paused: boolean;
/**
* Receives reclaimed native rent from every account close. Separate from
* `platform_treasury`, which receives SPL fees: under gas sponsorship the
* rent was never the user's money nor protocol revenue, it is the sponsor's
* float coming back. Appended after `paused` so the layout only grows at
* the tail; `migrate_state` backfills it from an explicit argument when
* zero, since it must differ from `platform_treasury` and so cannot be
* inferred from existing state.
*/
sponsor: Address;
};

/** Gets the encoder for {@link AcpStateArgs} account data. */
Expand All @@ -119,9 +129,9 @@ export function getAcpStateEncoder(): Encoder<AcpStateArgs> {
["platformTreasury", getAddressEncoder()],
["platformFeeBp", getU64Encoder()],
["evaluatorFeeBp", getU64Encoder()],
["jobCounter", getU64Encoder()],
["bump", getU8Encoder()],
["paused", getBooleanEncoder()],
["sponsor", getAddressEncoder()],
]),
(value) => ({ ...value, discriminator: ACP_STATE_DISCRIMINATOR }),
);
Expand All @@ -137,9 +147,9 @@ export function getAcpStateDecoder(): Decoder<AcpState> {
["platformTreasury", getAddressDecoder()],
["platformFeeBp", getU64Decoder()],
["evaluatorFeeBp", getU64Decoder()],
["jobCounter", getU64Decoder()],
["bump", getU8Decoder()],
["paused", getBooleanDecoder()],
["sponsor", getAddressDecoder()],
]);
}

Expand Down
68 changes: 26 additions & 42 deletions src/core/solana/generated/acp/accounts/job.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@
*/

import {
addDecoderSizePrefix,
addEncoderSizePrefix,
assertAccountExists,
assertAccountsExist,
combineCodec,
Expand All @@ -27,14 +25,10 @@ import {
getOptionEncoder,
getStructDecoder,
getStructEncoder,
getU32Decoder,
getU32Encoder,
getU64Decoder,
getU64Encoder,
getU8Decoder,
getU8Encoder,
getUtf8Decoder,
getUtf8Encoder,
transformEncoder,
type Account,
type Address,
Expand Down Expand Up @@ -67,8 +61,18 @@ export function getJobDiscriminatorBytes() {

export type Job = {
discriminator: ReadonlyUint8Array;
/** Unique identifier: `AcpState.job_counter` after its increment at creation. */
jobId: bigint;
/**
* Client-supplied uniquifier, kept only so the PDA can be re-derived
* (`seeds = [b"job", client, seed]`). It is NOT the job's identity and
* carries no ordering: two clients may pick the same value, and `init`
* rejects a reuse by the same client because the address already exists.
*
* **The job's identity is its own address**, `job.key()`. That is what the
* hooks seed their per-job PDAs from and what the CPI payload carries.
* Deriving the address from a shared counter instead made every creation
* contend on one account, capping the protocol at one job per slot.
*/
seed: bigint;
/** The party that created and funds the job. */
client: Address;
/** The agent fulfilling the job. `Pubkey::default()` until set via `set_provider`. */
Expand All @@ -79,29 +83,29 @@ export type Job = {
state: JobState;
/** Token amount escrowed on fund. Zero for zero-budget jobs. */
budgetAmount: bigint;
/** SPL mint for the budget. `None` until `set_budget` is called. */
budgetMint: Option<Address>;
/** Human-readable description (max 256 bytes). */
description: string;
/** Unix timestamp after which the job may be claimed as expired. */
expiredAt: bigint;
/** Unix timestamp of job creation. */
createdAt: bigint;
/** Optional hook program invoked on before/after action callbacks. */
hookAddress: Option<Address>;
/** 32-byte hash/identifier of the work product, set on submit. */
deliverable: ReadonlyUint8Array;
/** 32-byte completion or rejection reason, set on complete/reject. */
completionReason: ReadonlyUint8Array;
/** PDA bump for the per-job vault authority. Set during fund. */
vaultBump: number;
/** PDA bump seed for this job account. */
bump: number;
};

export type JobArgs = {
/** Unique identifier: `AcpState.job_counter` after its increment at creation. */
jobId: number | bigint;
/**
* Client-supplied uniquifier, kept only so the PDA can be re-derived
* (`seeds = [b"job", client, seed]`). It is NOT the job's identity and
* carries no ordering: two clients may pick the same value, and `init`
* rejects a reuse by the same client because the address already exists.
*
* **The job's identity is its own address**, `job.key()`. That is what the
* hooks seed their per-job PDAs from and what the CPI payload carries.
* Deriving the address from a shared counter instead made every creation
* contend on one account, capping the protocol at one job per slot.
*/
seed: number | bigint;
/** The party that created and funds the job. */
client: Address;
/** The agent fulfilling the job. `Pubkey::default()` until set via `set_provider`. */
Expand All @@ -112,20 +116,10 @@ export type JobArgs = {
state: JobStateArgs;
/** Token amount escrowed on fund. Zero for zero-budget jobs. */
budgetAmount: number | bigint;
/** SPL mint for the budget. `None` until `set_budget` is called. */
budgetMint: OptionOrNullable<Address>;
/** Human-readable description (max 256 bytes). */
description: string;
/** Unix timestamp after which the job may be claimed as expired. */
expiredAt: number | bigint;
/** Unix timestamp of job creation. */
createdAt: number | bigint;
/** Optional hook program invoked on before/after action callbacks. */
hookAddress: OptionOrNullable<Address>;
/** 32-byte hash/identifier of the work product, set on submit. */
deliverable: ReadonlyUint8Array;
/** 32-byte completion or rejection reason, set on complete/reject. */
completionReason: ReadonlyUint8Array;
/** PDA bump for the per-job vault authority. Set during fund. */
vaultBump: number;
/** PDA bump seed for this job account. */
Expand All @@ -137,19 +131,14 @@ export function getJobEncoder(): Encoder<JobArgs> {
return transformEncoder(
getStructEncoder([
["discriminator", fixEncoderSize(getBytesEncoder(), 8)],
["jobId", getU64Encoder()],
["seed", getU64Encoder()],
["client", getAddressEncoder()],
["provider", getAddressEncoder()],
["evaluator", getAddressEncoder()],
["state", getJobStateEncoder()],
["budgetAmount", getU64Encoder()],
["budgetMint", getOptionEncoder(getAddressEncoder())],
["description", addEncoderSizePrefix(getUtf8Encoder(), getU32Encoder())],
["expiredAt", getI64Encoder()],
["createdAt", getI64Encoder()],
["hookAddress", getOptionEncoder(getAddressEncoder())],
["deliverable", fixEncoderSize(getBytesEncoder(), 32)],
["completionReason", fixEncoderSize(getBytesEncoder(), 32)],
["vaultBump", getU8Encoder()],
["bump", getU8Encoder()],
]),
Expand All @@ -161,19 +150,14 @@ export function getJobEncoder(): Encoder<JobArgs> {
export function getJobDecoder(): Decoder<Job> {
return getStructDecoder([
["discriminator", fixDecoderSize(getBytesDecoder(), 8)],
["jobId", getU64Decoder()],
["seed", getU64Decoder()],
["client", getAddressDecoder()],
["provider", getAddressDecoder()],
["evaluator", getAddressDecoder()],
["state", getJobStateDecoder()],
["budgetAmount", getU64Decoder()],
["budgetMint", getOptionDecoder(getAddressDecoder())],
["description", addDecoderSizePrefix(getUtf8Decoder(), getU32Decoder())],
["expiredAt", getI64Decoder()],
["createdAt", getI64Decoder()],
["hookAddress", getOptionDecoder(getAddressDecoder())],
["deliverable", fixDecoderSize(getBytesDecoder(), 32)],
["completionReason", fixDecoderSize(getBytesDecoder(), 32)],
["vaultBump", getU8Decoder()],
["bump", getU8Decoder()],
]);
Expand Down
46 changes: 29 additions & 17 deletions src/core/solana/generated/acp/errors/agenticCommerceV3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,38 +56,44 @@ export const AGENTIC_COMMERCE_V3_ERROR__BUDGET_NOT_SET = 0x1782; // 6018
export const AGENTIC_COMMERCE_V3_ERROR__BUDGET_MISMATCH = 0x1783; // 6019
/** ProviderNotSet: Provider not set */
export const AGENTIC_COMMERCE_V3_ERROR__PROVIDER_NOT_SET = 0x1784; // 6020
/** ZeroBudgetVaultNotAllowed: Vault not allowed on a zero-budget job */
export const AGENTIC_COMMERCE_V3_ERROR__ZERO_BUDGET_VAULT_NOT_ALLOWED = 0x1785; // 6021
/** ExpiryTooShort: Expiry too short */
export const AGENTIC_COMMERCE_V3_ERROR__EXPIRY_TOO_SHORT = 0x1785; // 6021
export const AGENTIC_COMMERCE_V3_ERROR__EXPIRY_TOO_SHORT = 0x1786; // 6022
/** ExpiryTooLong: Expiry too long */
export const AGENTIC_COMMERCE_V3_ERROR__EXPIRY_TOO_LONG = 0x1787; // 6023
/** ExpiredAtMustBeInTheFuture: ExpiredAt must be in the future */
export const AGENTIC_COMMERCE_V3_ERROR__EXPIRED_AT_MUST_BE_IN_THE_FUTURE = 0x1786; // 6022
export const AGENTIC_COMMERCE_V3_ERROR__EXPIRED_AT_MUST_BE_IN_THE_FUTURE = 0x1788; // 6024
/** PlatformFeeTooHigh: Platform fee too high */
export const AGENTIC_COMMERCE_V3_ERROR__PLATFORM_FEE_TOO_HIGH = 0x1787; // 6023
export const AGENTIC_COMMERCE_V3_ERROR__PLATFORM_FEE_TOO_HIGH = 0x1789; // 6025
/** FeesTooHigh: Combined fees too high */
export const AGENTIC_COMMERCE_V3_ERROR__FEES_TOO_HIGH = 0x1788; // 6024
export const AGENTIC_COMMERCE_V3_ERROR__FEES_TOO_HIGH = 0x178a; // 6026
/** SponsorCannotBeTreasury: Sponsor and platform treasury must be different addresses */
export const AGENTIC_COMMERCE_V3_ERROR__SPONSOR_CANNOT_BE_TREASURY = 0x178b; // 6027
/** InvalidDestination: Invalid destination account */
export const AGENTIC_COMMERCE_V3_ERROR__INVALID_DESTINATION = 0x1789; // 6025
export const AGENTIC_COMMERCE_V3_ERROR__INVALID_DESTINATION = 0x178c; // 6028
/** InvalidVaultAuthority: Invalid vault authority */
export const AGENTIC_COMMERCE_V3_ERROR__INVALID_VAULT_AUTHORITY = 0x178a; // 6026
export const AGENTIC_COMMERCE_V3_ERROR__INVALID_VAULT_AUTHORITY = 0x178d; // 6029
/** InvalidVaultOwner: Invalid vault owner */
export const AGENTIC_COMMERCE_V3_ERROR__INVALID_VAULT_OWNER = 0x178b; // 6027
export const AGENTIC_COMMERCE_V3_ERROR__INVALID_VAULT_OWNER = 0x178e; // 6030
/** InvalidVaultMint: Invalid vault mint */
export const AGENTIC_COMMERCE_V3_ERROR__INVALID_VAULT_MINT = 0x178c; // 6028
export const AGENTIC_COMMERCE_V3_ERROR__INVALID_VAULT_MINT = 0x178f; // 6031
/** BudgetMintNotSet: Budget mint must be set when budget amount is nonzero */
export const AGENTIC_COMMERCE_V3_ERROR__BUDGET_MINT_NOT_SET = 0x178d; // 6029
export const AGENTIC_COMMERCE_V3_ERROR__BUDGET_MINT_NOT_SET = 0x1790; // 6032
/** InsufficientVaultBalance: Insufficient vault balance */
export const AGENTIC_COMMERCE_V3_ERROR__INSUFFICIENT_VAULT_BALANCE = 0x178e; // 6030
export const AGENTIC_COMMERCE_V3_ERROR__INSUFFICIENT_VAULT_BALANCE = 0x1791; // 6033
/** MissingRequiredAccount: Missing required account for funded job */
export const AGENTIC_COMMERCE_V3_ERROR__MISSING_REQUIRED_ACCOUNT = 0x178f; // 6031
export const AGENTIC_COMMERCE_V3_ERROR__MISSING_REQUIRED_ACCOUNT = 0x1792; // 6034
/** ArithmeticOverflow: Arithmetic overflow */
export const AGENTIC_COMMERCE_V3_ERROR__ARITHMETIC_OVERFLOW = 0x1790; // 6032
export const AGENTIC_COMMERCE_V3_ERROR__ARITHMETIC_OVERFLOW = 0x1793; // 6035
/** UntrustedHookUpgradeAuthority: Hook program upgrade authority must be None or the ACP authority */
export const AGENTIC_COMMERCE_V3_ERROR__UNTRUSTED_HOOK_UPGRADE_AUTHORITY = 0x1791; // 6033
export const AGENTIC_COMMERCE_V3_ERROR__UNTRUSTED_HOOK_UPGRADE_AUTHORITY = 0x1794; // 6036
/** MissingHookProgram: Missing hook program account: job declares a hook but hook_program was not supplied */
export const AGENTIC_COMMERCE_V3_ERROR__MISSING_HOOK_PROGRAM = 0x1792; // 6034
export const AGENTIC_COMMERCE_V3_ERROR__MISSING_HOOK_PROGRAM = 0x1795; // 6037
/** InvalidAutoCompleteParams: Invalid complete_opt_params: must be empty (legacy mode) or [u16 submit_count][...] with submit_count <= remaining_accounts.len() */
export const AGENTIC_COMMERCE_V3_ERROR__INVALID_AUTO_COMPLETE_PARAMS = 0x1793; // 6035
export const AGENTIC_COMMERCE_V3_ERROR__INVALID_AUTO_COMPLETE_PARAMS = 0x1796; // 6038
/** ProtocolPaused: Protocol is paused */
export const AGENTIC_COMMERCE_V3_ERROR__PROTOCOL_PAUSED = 0x1794; // 6036
export const AGENTIC_COMMERCE_V3_ERROR__PROTOCOL_PAUSED = 0x1797; // 6039

export type AgenticCommerceV3Error =
| typeof AGENTIC_COMMERCE_V3_ERROR__AMOUNT_MUST_BE_GREATER_THAN_ZERO
Expand All @@ -99,6 +105,7 @@ export type AgenticCommerceV3Error =
| typeof AGENTIC_COMMERCE_V3_ERROR__EVALUATOR_CANNOT_BE_CLIENT
| typeof AGENTIC_COMMERCE_V3_ERROR__EVALUATOR_CANNOT_BE_PROVIDER
| typeof AGENTIC_COMMERCE_V3_ERROR__EXPIRED_AT_MUST_BE_IN_THE_FUTURE
| typeof AGENTIC_COMMERCE_V3_ERROR__EXPIRY_TOO_LONG
| typeof AGENTIC_COMMERCE_V3_ERROR__EXPIRY_TOO_SHORT
| typeof AGENTIC_COMMERCE_V3_ERROR__FEES_TOO_HIGH
| typeof AGENTIC_COMMERCE_V3_ERROR__HOOK_NOT_WHITELISTED
Expand All @@ -121,12 +128,14 @@ export type AgenticCommerceV3Error =
| typeof AGENTIC_COMMERCE_V3_ERROR__PROTOCOL_PAUSED
| typeof AGENTIC_COMMERCE_V3_ERROR__PROVIDER_CANNOT_BE_CLIENT
| typeof AGENTIC_COMMERCE_V3_ERROR__PROVIDER_NOT_SET
| typeof AGENTIC_COMMERCE_V3_ERROR__SPONSOR_CANNOT_BE_TREASURY
| typeof AGENTIC_COMMERCE_V3_ERROR__UNAUTHORIZED
| typeof AGENTIC_COMMERCE_V3_ERROR__UNTRUSTED_HOOK_UPGRADE_AUTHORITY
| typeof AGENTIC_COMMERCE_V3_ERROR__WRONG_STATUS
| typeof AGENTIC_COMMERCE_V3_ERROR__ZERO_ADDRESS
| typeof AGENTIC_COMMERCE_V3_ERROR__ZERO_ADDRESS_EVALUATOR
| typeof AGENTIC_COMMERCE_V3_ERROR__ZERO_ADDRESS_PROVIDER;
| typeof AGENTIC_COMMERCE_V3_ERROR__ZERO_ADDRESS_PROVIDER
| typeof AGENTIC_COMMERCE_V3_ERROR__ZERO_BUDGET_VAULT_NOT_ALLOWED;

let agenticCommerceV3ErrorMessages:
| Record<AgenticCommerceV3Error, string>
Expand All @@ -142,6 +151,7 @@ if (process.env.NODE_ENV !== "production") {
[AGENTIC_COMMERCE_V3_ERROR__EVALUATOR_CANNOT_BE_CLIENT]: `Evaluator cannot be the client`,
[AGENTIC_COMMERCE_V3_ERROR__EVALUATOR_CANNOT_BE_PROVIDER]: `Evaluator cannot be the provider`,
[AGENTIC_COMMERCE_V3_ERROR__EXPIRED_AT_MUST_BE_IN_THE_FUTURE]: `ExpiredAt must be in the future`,
[AGENTIC_COMMERCE_V3_ERROR__EXPIRY_TOO_LONG]: `Expiry too long`,
[AGENTIC_COMMERCE_V3_ERROR__EXPIRY_TOO_SHORT]: `Expiry too short`,
[AGENTIC_COMMERCE_V3_ERROR__FEES_TOO_HIGH]: `Combined fees too high`,
[AGENTIC_COMMERCE_V3_ERROR__HOOK_NOT_WHITELISTED]: `Hook not whitelisted`,
Expand All @@ -164,12 +174,14 @@ if (process.env.NODE_ENV !== "production") {
[AGENTIC_COMMERCE_V3_ERROR__PROTOCOL_PAUSED]: `Protocol is paused`,
[AGENTIC_COMMERCE_V3_ERROR__PROVIDER_CANNOT_BE_CLIENT]: `Provider cannot be the client`,
[AGENTIC_COMMERCE_V3_ERROR__PROVIDER_NOT_SET]: `Provider not set`,
[AGENTIC_COMMERCE_V3_ERROR__SPONSOR_CANNOT_BE_TREASURY]: `Sponsor and platform treasury must be different addresses`,
[AGENTIC_COMMERCE_V3_ERROR__UNAUTHORIZED]: `Unauthorized`,
[AGENTIC_COMMERCE_V3_ERROR__UNTRUSTED_HOOK_UPGRADE_AUTHORITY]: `Hook program upgrade authority must be None or the ACP authority`,
[AGENTIC_COMMERCE_V3_ERROR__WRONG_STATUS]: `Wrong job status`,
[AGENTIC_COMMERCE_V3_ERROR__ZERO_ADDRESS]: `ZeroAddress`,
[AGENTIC_COMMERCE_V3_ERROR__ZERO_ADDRESS_EVALUATOR]: `ZeroAddressEvaluator`,
[AGENTIC_COMMERCE_V3_ERROR__ZERO_ADDRESS_PROVIDER]: `ZeroAddressProvider`,
[AGENTIC_COMMERCE_V3_ERROR__ZERO_BUDGET_VAULT_NOT_ALLOWED]: `Vault not allowed on a zero-budget job`,
};
}

Expand Down
Loading