@perkos/agent-sdk 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +48 -0
- package/README.md +175 -5
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/x402-direct.d.ts +100 -0
- package/dist/x402-direct.d.ts.map +1 -0
- package/dist/x402-direct.js +576 -0
- package/dist/x402-direct.js.map +1 -0
- package/dist/x402-facilitator.d.ts +86 -0
- package/dist/x402-facilitator.d.ts.map +1 -0
- package/dist/x402-facilitator.js +480 -0
- package/dist/x402-facilitator.js.map +1 -0
- package/dist/x402.d.ts +60 -0
- package/dist/x402.d.ts.map +1 -0
- package/dist/x402.js +306 -0
- package/dist/x402.js.map +1 -0
- package/docs/ARCHITECTURE.md +25 -1
- package/docs/plans/2026-08-25-stacks-x402-direct-profile-design.md +68 -0
- package/docs/plans/2026-08-25-stacks-x402-facilitator-design.md +143 -0
- package/docs/plans/2026-08-25-stacks-x402-v2-design.md +139 -0
- package/examples/x402-facilitator.ts +69 -0
- package/examples/x402-foundation.ts +30 -0
- package/package.json +10 -5
package/dist/x402.d.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { decodePaymentRequiredHeader, decodePaymentResponseHeader, decodePaymentSignatureHeader, encodePaymentRequiredHeader, encodePaymentResponseHeader, encodePaymentSignatureHeader } from "@x402/core/http";
|
|
2
|
+
import type { PaymentPayload, PaymentPayloadResult, PaymentRequired, PaymentRequirements, ResourceInfo, SchemeNetworkClient, SettleResponse } from "@x402/core/types";
|
|
3
|
+
import type { AmountLike, ConfirmationOptions, ContractId, FundJobInput, JobRecord, PaymentAsset, PerkOSNetwork, ResolvedPerkOSConfig, TransactionConfirmation, TransactionReceipt } from "./types.js";
|
|
4
|
+
export declare const X402_VERSION = 2;
|
|
5
|
+
export declare const PERKOS_X402_SCHEME = "exact";
|
|
6
|
+
export declare const PERKOS_X402_ASSET_TRANSFER_METHOD = "perkos-escrow-v1";
|
|
7
|
+
export declare const PERKOS_X402_PAYMENT_FLOW = "upfront";
|
|
8
|
+
export declare const STACKS_X402_NETWORKS: {
|
|
9
|
+
readonly mainnet: "stacks:1";
|
|
10
|
+
readonly testnet: "stacks:2147483648";
|
|
11
|
+
};
|
|
12
|
+
export interface PerkOSX402ClientLike {
|
|
13
|
+
readonly config: ResolvedPerkOSConfig;
|
|
14
|
+
getJob(asset: PaymentAsset, jobId: AmountLike): Promise<JobRecord | null>;
|
|
15
|
+
fundJob(input: FundJobInput): Promise<TransactionReceipt>;
|
|
16
|
+
confirm(receiptOrTxid: TransactionReceipt | string, options?: ConfirmationOptions): Promise<TransactionConfirmation>;
|
|
17
|
+
}
|
|
18
|
+
export interface PerkOSX402PaymentRequiredInput {
|
|
19
|
+
readonly resource: ResourceInfo;
|
|
20
|
+
readonly asset: PaymentAsset;
|
|
21
|
+
readonly jobId: AmountLike;
|
|
22
|
+
readonly amount: AmountLike;
|
|
23
|
+
readonly maxTimeoutSeconds?: number;
|
|
24
|
+
readonly error?: string;
|
|
25
|
+
}
|
|
26
|
+
export interface ParsedPerkOSX402Requirement {
|
|
27
|
+
readonly network: PerkOSNetwork;
|
|
28
|
+
readonly asset: PaymentAsset;
|
|
29
|
+
readonly jobId: bigint;
|
|
30
|
+
readonly amount: bigint;
|
|
31
|
+
readonly commerceContract: ContractId;
|
|
32
|
+
readonly assetIdentifier: string;
|
|
33
|
+
readonly maxTimeoutSeconds: number;
|
|
34
|
+
}
|
|
35
|
+
export interface PerkOSX402PaymentProof extends ParsedPerkOSX402Requirement {
|
|
36
|
+
readonly transaction: string;
|
|
37
|
+
readonly payer: string;
|
|
38
|
+
readonly blockHeight?: number;
|
|
39
|
+
readonly blockHash?: string;
|
|
40
|
+
}
|
|
41
|
+
export interface PerkOSX402SchemeClientOptions {
|
|
42
|
+
readonly client: PerkOSX402ClientLike;
|
|
43
|
+
readonly confirmation?: ConfirmationOptions;
|
|
44
|
+
}
|
|
45
|
+
export declare function toStacksX402Network(network: PerkOSNetwork): (typeof STACKS_X402_NETWORKS)[PerkOSNetwork];
|
|
46
|
+
export declare function fromStacksX402Network(network: string): PerkOSNetwork;
|
|
47
|
+
export declare function createPerkOSX402PaymentRequired(config: ResolvedPerkOSConfig, input: PerkOSX402PaymentRequiredInput): PaymentRequired;
|
|
48
|
+
export declare function parsePerkOSX402Requirement(config: ResolvedPerkOSConfig, paymentRequirements: PaymentRequirements): ParsedPerkOSX402Requirement;
|
|
49
|
+
export declare function parsePerkOSX402PaymentPayload(config: ResolvedPerkOSConfig, paymentPayload: PaymentPayload): PerkOSX402PaymentProof;
|
|
50
|
+
export declare class PerkOSX402SchemeClient implements SchemeNetworkClient {
|
|
51
|
+
readonly scheme = "exact";
|
|
52
|
+
private readonly client;
|
|
53
|
+
private readonly confirmation;
|
|
54
|
+
constructor(options: PerkOSX402SchemeClientOptions);
|
|
55
|
+
createPaymentPayload(x402Version: number, paymentRequirements: PaymentRequirements): Promise<PaymentPayloadResult>;
|
|
56
|
+
private assertReceipt;
|
|
57
|
+
}
|
|
58
|
+
export { decodePaymentRequiredHeader, decodePaymentResponseHeader, decodePaymentSignatureHeader, encodePaymentRequiredHeader, encodePaymentResponseHeader, encodePaymentSignatureHeader, };
|
|
59
|
+
export type { PaymentPayload, PaymentRequired, PaymentRequirements, ResourceInfo, SettleResponse, };
|
|
60
|
+
//# sourceMappingURL=x402.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"x402.d.ts","sourceRoot":"","sources":["../src/x402.ts"],"names":[],"mappings":"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"}
|
package/dist/x402.js
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import { decodePaymentRequiredHeader, decodePaymentResponseHeader, decodePaymentSignatureHeader, encodePaymentRequiredHeader, encodePaymentResponseHeader, encodePaymentSignatureHeader, } from "@x402/core/http";
|
|
2
|
+
import { validatePaymentPayload, validatePaymentRequired, validatePaymentRequirements, } from "@x402/core/schemas";
|
|
3
|
+
import { PerkOSError } from "./errors.js";
|
|
4
|
+
import { normalizeTxid } from "./txid.js";
|
|
5
|
+
import { assertPrincipal, toUint } from "./validation.js";
|
|
6
|
+
export const X402_VERSION = 2;
|
|
7
|
+
export const PERKOS_X402_SCHEME = "exact";
|
|
8
|
+
export const PERKOS_X402_ASSET_TRANSFER_METHOD = "perkos-escrow-v1";
|
|
9
|
+
export const PERKOS_X402_PAYMENT_FLOW = "upfront";
|
|
10
|
+
export const STACKS_X402_NETWORKS = {
|
|
11
|
+
mainnet: "stacks:1",
|
|
12
|
+
testnet: "stacks:2147483648",
|
|
13
|
+
};
|
|
14
|
+
const X402_NETWORK_TO_PERKOS = {
|
|
15
|
+
"stacks:1": "mainnet",
|
|
16
|
+
"stacks:2147483648": "testnet",
|
|
17
|
+
};
|
|
18
|
+
const DEFAULT_TIMEOUT_SECONDS = 600;
|
|
19
|
+
const MAX_TIMEOUT_SECONDS = 3_600;
|
|
20
|
+
function x402Error(message, details) {
|
|
21
|
+
return new PerkOSError("X402_INVALID", message, details);
|
|
22
|
+
}
|
|
23
|
+
function commerceContract(config, asset) {
|
|
24
|
+
return asset === "sbtc" ? config.contracts.sbtcCommerce : config.contracts.stxCommerce;
|
|
25
|
+
}
|
|
26
|
+
function assetIdentifier(config, asset) {
|
|
27
|
+
return asset === "sbtc"
|
|
28
|
+
? `${config.contracts.sbtcToken}::${config.contracts.sbtcAssetName}`
|
|
29
|
+
: "STX";
|
|
30
|
+
}
|
|
31
|
+
function parseProtocolObject(label, parser) {
|
|
32
|
+
try {
|
|
33
|
+
return parser();
|
|
34
|
+
}
|
|
35
|
+
catch (cause) {
|
|
36
|
+
throw x402Error(`${label} is not a valid x402 v2 object.`, { cause });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function requireExtraString(extra, field) {
|
|
40
|
+
const value = extra[field];
|
|
41
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
42
|
+
throw x402Error(`paymentRequirements.extra.${field} must be a non-empty string.`, {
|
|
43
|
+
field,
|
|
44
|
+
value,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
return value;
|
|
48
|
+
}
|
|
49
|
+
function parseTimeout(value) {
|
|
50
|
+
if (!Number.isSafeInteger(value) || value <= 0 || value > MAX_TIMEOUT_SECONDS) {
|
|
51
|
+
throw x402Error(`maxTimeoutSeconds must be an integer between 1 and ${MAX_TIMEOUT_SECONDS}.`, { value });
|
|
52
|
+
}
|
|
53
|
+
return value;
|
|
54
|
+
}
|
|
55
|
+
function parseResource(resource) {
|
|
56
|
+
let url;
|
|
57
|
+
try {
|
|
58
|
+
url = new URL(resource.url);
|
|
59
|
+
}
|
|
60
|
+
catch (cause) {
|
|
61
|
+
throw x402Error("resource.url must be a valid HTTP or HTTPS URL.", { cause });
|
|
62
|
+
}
|
|
63
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") {
|
|
64
|
+
throw x402Error("resource.url must use HTTP or HTTPS.", { protocol: url.protocol });
|
|
65
|
+
}
|
|
66
|
+
return resource;
|
|
67
|
+
}
|
|
68
|
+
export function toStacksX402Network(network) {
|
|
69
|
+
return STACKS_X402_NETWORKS[network];
|
|
70
|
+
}
|
|
71
|
+
export function fromStacksX402Network(network) {
|
|
72
|
+
const resolved = X402_NETWORK_TO_PERKOS[network];
|
|
73
|
+
if (!resolved) {
|
|
74
|
+
throw x402Error("network must be a supported Stacks CAIP-2 identifier.", { network });
|
|
75
|
+
}
|
|
76
|
+
return resolved;
|
|
77
|
+
}
|
|
78
|
+
export function createPerkOSX402PaymentRequired(config, input) {
|
|
79
|
+
const jobId = toUint(input.jobId, "jobId");
|
|
80
|
+
const amount = toUint(input.amount, "amount");
|
|
81
|
+
const contract = commerceContract(config, input.asset);
|
|
82
|
+
const timeout = parseTimeout(input.maxTimeoutSeconds ?? DEFAULT_TIMEOUT_SECONDS);
|
|
83
|
+
const paymentRequired = {
|
|
84
|
+
x402Version: X402_VERSION,
|
|
85
|
+
...(input.error ? { error: input.error } : {}),
|
|
86
|
+
resource: parseResource(input.resource),
|
|
87
|
+
accepts: [
|
|
88
|
+
{
|
|
89
|
+
scheme: PERKOS_X402_SCHEME,
|
|
90
|
+
network: toStacksX402Network(config.network),
|
|
91
|
+
amount: amount.toString(),
|
|
92
|
+
asset: assetIdentifier(config, input.asset),
|
|
93
|
+
payTo: contract,
|
|
94
|
+
maxTimeoutSeconds: timeout,
|
|
95
|
+
extra: {
|
|
96
|
+
assetTransferMethod: PERKOS_X402_ASSET_TRANSFER_METHOD,
|
|
97
|
+
paymentFlow: PERKOS_X402_PAYMENT_FLOW,
|
|
98
|
+
paymentAsset: input.asset,
|
|
99
|
+
jobId: jobId.toString(),
|
|
100
|
+
commerceContract: contract,
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
extensions: {},
|
|
105
|
+
};
|
|
106
|
+
return parseProtocolObject("paymentRequired", () => {
|
|
107
|
+
validatePaymentRequired(paymentRequired);
|
|
108
|
+
return paymentRequired;
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
export function parsePerkOSX402Requirement(config, paymentRequirements) {
|
|
112
|
+
const requirements = parseProtocolObject("paymentRequirements", () => {
|
|
113
|
+
validatePaymentRequirements(paymentRequirements);
|
|
114
|
+
return paymentRequirements;
|
|
115
|
+
});
|
|
116
|
+
if (requirements.scheme !== PERKOS_X402_SCHEME) {
|
|
117
|
+
throw x402Error(`scheme must be ${PERKOS_X402_SCHEME}.`, {
|
|
118
|
+
scheme: requirements.scheme,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
const network = fromStacksX402Network(requirements.network);
|
|
122
|
+
if (network !== config.network) {
|
|
123
|
+
throw x402Error("The x402 requirement network does not match the SDK client network.", {
|
|
124
|
+
requirementNetwork: network,
|
|
125
|
+
clientNetwork: config.network,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
const extra = requirements.extra ?? {};
|
|
129
|
+
if (requireExtraString(extra, "assetTransferMethod") !== PERKOS_X402_ASSET_TRANSFER_METHOD) {
|
|
130
|
+
throw x402Error(`assetTransferMethod must be ${PERKOS_X402_ASSET_TRANSFER_METHOD}.`);
|
|
131
|
+
}
|
|
132
|
+
if (requireExtraString(extra, "paymentFlow") !== PERKOS_X402_PAYMENT_FLOW) {
|
|
133
|
+
throw x402Error(`paymentFlow must be ${PERKOS_X402_PAYMENT_FLOW}.`);
|
|
134
|
+
}
|
|
135
|
+
const paymentAsset = requireExtraString(extra, "paymentAsset");
|
|
136
|
+
if (paymentAsset !== "sbtc" && paymentAsset !== "stx") {
|
|
137
|
+
throw x402Error('paymentAsset must be "sbtc" or "stx".', { paymentAsset });
|
|
138
|
+
}
|
|
139
|
+
const asset = paymentAsset;
|
|
140
|
+
const expectedContract = commerceContract(config, asset);
|
|
141
|
+
const declaredContract = requireExtraString(extra, "commerceContract");
|
|
142
|
+
if (requirements.payTo !== expectedContract || declaredContract !== expectedContract) {
|
|
143
|
+
throw x402Error("payTo and commerceContract must match the configured escrow contract.", {
|
|
144
|
+
payTo: requirements.payTo,
|
|
145
|
+
declaredContract,
|
|
146
|
+
expectedContract,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
const expectedAsset = assetIdentifier(config, asset);
|
|
150
|
+
if (requirements.asset !== expectedAsset) {
|
|
151
|
+
throw x402Error("The x402 asset does not match the configured settlement asset.", {
|
|
152
|
+
asset: requirements.asset,
|
|
153
|
+
expectedAsset,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
const jobId = toUint(requireExtraString(extra, "jobId"), "jobId");
|
|
157
|
+
const amount = toUint(requirements.amount, "amount");
|
|
158
|
+
return {
|
|
159
|
+
network,
|
|
160
|
+
asset,
|
|
161
|
+
jobId,
|
|
162
|
+
amount,
|
|
163
|
+
commerceContract: expectedContract,
|
|
164
|
+
assetIdentifier: expectedAsset,
|
|
165
|
+
maxTimeoutSeconds: parseTimeout(requirements.maxTimeoutSeconds),
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
export function parsePerkOSX402PaymentPayload(config, paymentPayload) {
|
|
169
|
+
const parsed = parseProtocolObject("paymentPayload", () => {
|
|
170
|
+
validatePaymentPayload(paymentPayload);
|
|
171
|
+
return paymentPayload;
|
|
172
|
+
});
|
|
173
|
+
if (parsed.x402Version !== X402_VERSION) {
|
|
174
|
+
throw x402Error(`x402Version must be ${X402_VERSION}.`, {
|
|
175
|
+
x402Version: parsed.x402Version,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
const requirement = parsePerkOSX402Requirement(config, parsed.accepted);
|
|
179
|
+
const transactionValue = parsed.payload.transaction;
|
|
180
|
+
const payer = parsed.payload.payer;
|
|
181
|
+
const jobId = parsed.payload.jobId;
|
|
182
|
+
const amount = parsed.payload.amount;
|
|
183
|
+
const paymentAsset = parsed.payload.asset;
|
|
184
|
+
const contract = parsed.payload.commerceContract;
|
|
185
|
+
if (typeof transactionValue !== "string") {
|
|
186
|
+
throw x402Error("payload.transaction must be a Stacks transaction ID.");
|
|
187
|
+
}
|
|
188
|
+
const transaction = normalizeTxid(transactionValue);
|
|
189
|
+
if (typeof payer !== "string") {
|
|
190
|
+
throw x402Error("payload.payer must be a Stacks principal.");
|
|
191
|
+
}
|
|
192
|
+
assertPrincipal(payer, "payload.payer", config.network);
|
|
193
|
+
if (jobId !== requirement.jobId.toString()) {
|
|
194
|
+
throw x402Error("payload.jobId does not match the accepted requirement.", {
|
|
195
|
+
jobId,
|
|
196
|
+
expectedJobId: requirement.jobId.toString(),
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
if (amount !== requirement.amount.toString()) {
|
|
200
|
+
throw x402Error("payload.amount does not match the accepted requirement.", {
|
|
201
|
+
amount,
|
|
202
|
+
expectedAmount: requirement.amount.toString(),
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
if (paymentAsset !== requirement.asset || contract !== requirement.commerceContract) {
|
|
206
|
+
throw x402Error("The payment proof asset or contract does not match the requirement.", {
|
|
207
|
+
paymentAsset,
|
|
208
|
+
contract,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
const blockHeight = parsed.payload.blockHeight;
|
|
212
|
+
const blockHash = parsed.payload.blockHash;
|
|
213
|
+
if (blockHeight !== undefined &&
|
|
214
|
+
(typeof blockHeight !== "number" ||
|
|
215
|
+
!Number.isSafeInteger(blockHeight) ||
|
|
216
|
+
blockHeight < 0)) {
|
|
217
|
+
throw x402Error("payload.blockHeight must be a non-negative safe integer.");
|
|
218
|
+
}
|
|
219
|
+
if (blockHash !== undefined && typeof blockHash !== "string") {
|
|
220
|
+
throw x402Error("payload.blockHash must be a string.");
|
|
221
|
+
}
|
|
222
|
+
return {
|
|
223
|
+
...requirement,
|
|
224
|
+
transaction,
|
|
225
|
+
payer,
|
|
226
|
+
...(typeof blockHeight === "number" ? { blockHeight } : {}),
|
|
227
|
+
...(typeof blockHash === "string" ? { blockHash } : {}),
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
export class PerkOSX402SchemeClient {
|
|
231
|
+
scheme = PERKOS_X402_SCHEME;
|
|
232
|
+
client;
|
|
233
|
+
confirmation;
|
|
234
|
+
constructor(options) {
|
|
235
|
+
this.client = options.client;
|
|
236
|
+
this.confirmation = options.confirmation ?? {};
|
|
237
|
+
}
|
|
238
|
+
async createPaymentPayload(x402Version, paymentRequirements) {
|
|
239
|
+
if (x402Version !== X402_VERSION) {
|
|
240
|
+
throw x402Error(`x402Version must be ${X402_VERSION}.`, { x402Version });
|
|
241
|
+
}
|
|
242
|
+
const requirement = parsePerkOSX402Requirement(this.client.config, paymentRequirements);
|
|
243
|
+
const job = await this.client.getJob(requirement.asset, requirement.jobId);
|
|
244
|
+
if (!job) {
|
|
245
|
+
throw x402Error(`Job ${requirement.jobId} does not exist.`, {
|
|
246
|
+
jobId: requirement.jobId,
|
|
247
|
+
asset: requirement.asset,
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
if (job.status !== "open") {
|
|
251
|
+
throw x402Error(`Job ${requirement.jobId} must be open before x402 funding.`, {
|
|
252
|
+
status: job.status,
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
if (job.budget !== requirement.amount) {
|
|
256
|
+
throw x402Error("The on-chain job budget does not match the x402 amount.", {
|
|
257
|
+
jobBudget: job.budget,
|
|
258
|
+
requirementAmount: requirement.amount,
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
const receipt = await this.client.fundJob({
|
|
262
|
+
asset: requirement.asset,
|
|
263
|
+
jobId: requirement.jobId,
|
|
264
|
+
amount: requirement.amount,
|
|
265
|
+
sender: job.client,
|
|
266
|
+
});
|
|
267
|
+
this.assertReceipt(receipt, requirement);
|
|
268
|
+
const confirmation = await this.client.confirm(receipt, this.confirmation);
|
|
269
|
+
if (confirmation.txid !== receipt.txid || confirmation.status !== "success") {
|
|
270
|
+
throw new PerkOSError("X402_PAYMENT_FAILED", "The Stacks escrow funding transaction did not confirm successfully.", {
|
|
271
|
+
receiptTxid: receipt.txid,
|
|
272
|
+
confirmationTxid: confirmation.txid,
|
|
273
|
+
status: confirmation.status,
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
return {
|
|
277
|
+
x402Version: X402_VERSION,
|
|
278
|
+
payload: {
|
|
279
|
+
transaction: receipt.txid,
|
|
280
|
+
payer: job.client,
|
|
281
|
+
jobId: requirement.jobId.toString(),
|
|
282
|
+
amount: requirement.amount.toString(),
|
|
283
|
+
asset: requirement.asset,
|
|
284
|
+
commerceContract: requirement.commerceContract,
|
|
285
|
+
...(confirmation.blockHeight !== undefined
|
|
286
|
+
? { blockHeight: confirmation.blockHeight }
|
|
287
|
+
: {}),
|
|
288
|
+
...(confirmation.blockHash ? { blockHash: confirmation.blockHash } : {}),
|
|
289
|
+
},
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
assertReceipt(receipt, requirement) {
|
|
293
|
+
const valid = receipt.operation === "fund-job" &&
|
|
294
|
+
receipt.network === requirement.network &&
|
|
295
|
+
receipt.contract === requirement.commerceContract &&
|
|
296
|
+
receipt.asset === requirement.asset &&
|
|
297
|
+
receipt.amount === requirement.amount &&
|
|
298
|
+
receipt.jobId === requirement.jobId;
|
|
299
|
+
if (!valid) {
|
|
300
|
+
throw new PerkOSError("X402_PAYMENT_FAILED", "The SDK funding receipt does not match the accepted x402 requirement.", { receipt, requirement });
|
|
301
|
+
}
|
|
302
|
+
normalizeTxid(receipt.txid);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
export { decodePaymentRequiredHeader, decodePaymentResponseHeader, decodePaymentSignatureHeader, encodePaymentRequiredHeader, encodePaymentResponseHeader, encodePaymentSignatureHeader, };
|
|
306
|
+
//# sourceMappingURL=x402.js.map
|
package/dist/x402.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"x402.js","sourceRoot":"","sources":["../src/x402.ts"],"names":[],"mappings":"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"}
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -97,6 +97,29 @@ post-conditions:
|
|
|
97
97
|
This difference stays inside the builder so frameworks do not duplicate asset-specific Clarity
|
|
98
98
|
logic.
|
|
99
99
|
|
|
100
|
+
## x402 v2 client adapter
|
|
101
|
+
|
|
102
|
+
The x402 adapter uses the official v2 schemas and HTTP header codecs. It maps mainnet and testnet
|
|
103
|
+
to their Stacks CAIP-2 identifiers and binds every payment requirement to the configured commerce
|
|
104
|
+
contract, settlement asset, job ID, and exact atomic budget. `PerkOSX402SchemeClient` then reuses
|
|
105
|
+
the normal read path, spending policy, signer, exact post-conditions, and confirmation tracker to
|
|
106
|
+
produce a confirmed escrow-funding proof.
|
|
107
|
+
|
|
108
|
+
`PerkOSX402Facilitator` closes the server-side observation boundary. It uses current Hiro v3
|
|
109
|
+
transaction and event responses plus the Stacks chain tip, decodes the commerce contract's
|
|
110
|
+
`job-funded` Clarity print event, matches the exact asset transfer, and atomically consumes the
|
|
111
|
+
network-prefixed transaction ID through an injected replay store. The facilitator broadcasts no
|
|
112
|
+
second transaction because the declared x402 payment flow is upfront.
|
|
113
|
+
|
|
114
|
+
The included in-memory replay store is process-local and not production durability. A deployed
|
|
115
|
+
resource server must inject shared atomic storage. The existing custom application headers are
|
|
116
|
+
outside the SDK and are not implicitly treated as x402 v2.
|
|
117
|
+
|
|
118
|
+
The confirmed funding transaction is public before its proof reaches the resource server. Replay
|
|
119
|
+
consumption and a short freshness window do not cryptographically bind that bearer proof to one
|
|
120
|
+
HTTP request, so a later request-bound signature/challenge or facilitator-submitted transaction is
|
|
121
|
+
required before a high-value production paywall migration.
|
|
122
|
+
|
|
100
123
|
## Settlement safety
|
|
101
124
|
|
|
102
125
|
Completion, rejection, and expiry may transfer funds held by the escrow contract rather than by
|
|
@@ -106,6 +129,7 @@ exact amount expected to leave it.
|
|
|
106
129
|
|
|
107
130
|
## Future adapters
|
|
108
131
|
|
|
109
|
-
-
|
|
132
|
+
- Durable replay-store and hosted HTTP adapters for the x402 Stacks facilitator.
|
|
133
|
+
- Request-bound payer authorization for front-running resistance.
|
|
110
134
|
- MCP tools with typed inputs, allowlists, and the same spending policy.
|
|
111
135
|
- Optional framework-specific integrations and higher-confirmation settlement policies.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Nayori direct x402 Stacks profile
|
|
2
|
+
|
|
3
|
+
Date: 2026-08-25
|
|
4
|
+
Status: approved for implementation
|
|
5
|
+
|
|
6
|
+
## Boundary
|
|
7
|
+
|
|
8
|
+
This is an additive Milestone 2 SDK capability. It does not change the approved M1 contracts,
|
|
9
|
+
broadcast transactions, host a facilitator, persist replay state, sponsor fees, or replace the
|
|
10
|
+
existing `perkos-escrow-v1` job-funding mechanism. The direct mechanism is for an immediate paid
|
|
11
|
+
resource; the existing mechanism remains for jobs with escrow, delivery, evaluation, payout, and
|
|
12
|
+
reputation.
|
|
13
|
+
|
|
14
|
+
## Decision
|
|
15
|
+
|
|
16
|
+
Add a versioned `stacks-signed-tx-v1` mechanism for direct STX, sBTC, and USDCx payments using the
|
|
17
|
+
official x402 v2 `exact` scheme and explicit `upfront` flow. The client signs the real Stacks
|
|
18
|
+
transaction, and a later hosted facilitator validates and broadcasts it before the resource runs.
|
|
19
|
+
|
|
20
|
+
The approved compatibility-first wire profile emits:
|
|
21
|
+
|
|
22
|
+
- `STX` for native STX;
|
|
23
|
+
- the canonical `address.contract` principal for sBTC and USDCx;
|
|
24
|
+
- a full internal CAIP-19 identifier in `extra.nayoriAssetId` for every asset;
|
|
25
|
+
- `extra.assetTransferMethod: "stacks-signed-tx-v1"`;
|
|
26
|
+
- `extra.paymentFlow: "upfront"`.
|
|
27
|
+
|
|
28
|
+
Unknown networks, assets, contracts, or token names fail closed. There is no fallback to STX.
|
|
29
|
+
|
|
30
|
+
## Request binding
|
|
31
|
+
|
|
32
|
+
A normalized trusted quote covers quote and merchant IDs, HTTP method, canonical absolute URL,
|
|
33
|
+
SHA-256 body digest, network, canonical asset, exact atomic amount, recipient, issue time, and
|
|
34
|
+
expiry. The canonical representation has fixed keys and version 1. Its SHA-256 digest is truncated
|
|
35
|
+
to 20 bytes and encoded as `ny1_<base64url>`: 31 ASCII characters that fit both the 34-byte native
|
|
36
|
+
STX memo and SIP-010 optional memo.
|
|
37
|
+
|
|
38
|
+
The pure verifier receives the trusted quote and actual request, recomputes the fingerprint, and
|
|
39
|
+
requires the signed transaction memo to match. The future hosted layer must authenticate the
|
|
40
|
+
merchant and verify the quote signature before treating the quote as trusted.
|
|
41
|
+
|
|
42
|
+
## Pure verifier
|
|
43
|
+
|
|
44
|
+
The verifier has no network, database, replay, or broadcast side effects. It:
|
|
45
|
+
|
|
46
|
+
1. validates the official x402 payload and requirement;
|
|
47
|
+
2. requires payload `accepted` to equal the server requirement;
|
|
48
|
+
3. matches requirement, trusted quote, and actual request;
|
|
49
|
+
4. deserializes one canonical transaction with bounded size and no trailing bytes;
|
|
50
|
+
5. verifies chain ID, transaction version, authorization type, and origin signature;
|
|
51
|
+
6. derives the payer from the origin spending condition;
|
|
52
|
+
7. permits only an exact STX token transfer or canonical SIP-010 `transfer` call;
|
|
53
|
+
8. checks amount, sender, recipient, memo, contract, function, arguments, and asset;
|
|
54
|
+
9. requires `PostConditionMode.Deny`; SIP-010 requires exactly one equal FT post-condition from
|
|
55
|
+
the payer for the canonical token and amount;
|
|
56
|
+
10. returns a typed verified plan for a later settlement layer.
|
|
57
|
+
|
|
58
|
+
Standard and origin-signed sponsored transactions are accepted. Sponsor signing, fee policy,
|
|
59
|
+
nonce queues, balance checks, simulation, replay, idempotency, broadcast, confirmation, and
|
|
60
|
+
delivery remain required hosted-service controls.
|
|
61
|
+
|
|
62
|
+
## Testing
|
|
63
|
+
|
|
64
|
+
Deterministic signed fixtures cover STX, sBTC, USDCx, mainnet/testnet registry values, canonical
|
|
65
|
+
fingerprints, and sponsored origin signatures. Negative tests mutate the request, quote,
|
|
66
|
+
requirement, network, signature, transaction template, recipient, amount, memo, contract,
|
|
67
|
+
function arguments, post-condition mode, post-condition asset, and serialized bytes. No test calls
|
|
68
|
+
a live RPC or broadcasts a transaction.
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# Stacks x402 verifier and facilitator
|
|
2
|
+
|
|
3
|
+
Date: 2026-08-25
|
|
4
|
+
Status: approved for implementation by the M2 continuation
|
|
5
|
+
|
|
6
|
+
## Milestone boundary
|
|
7
|
+
|
|
8
|
+
Milestone 1 is approved and closed. This work is an additive `@perkos/agent-sdk` capability for
|
|
9
|
+
Milestone 2 developer adoption and demo readiness. It does not change, redeploy, or charge a fee
|
|
10
|
+
through the STX or sBTC contracts. It also does not replace the external security review or any
|
|
11
|
+
mainnet/non-team adoption requirement.
|
|
12
|
+
|
|
13
|
+
The deliverable is a reusable facilitator mechanism, not a hosted production service. It can be
|
|
14
|
+
registered with the official `@x402/core` facilitator and can later power a Next.js route, a
|
|
15
|
+
standalone facilitator, or an agent framework without duplicating Stacks verification logic.
|
|
16
|
+
|
|
17
|
+
## Approaches considered
|
|
18
|
+
|
|
19
|
+
### Deploy a hosted facilitator immediately
|
|
20
|
+
|
|
21
|
+
This would create an immediately callable endpoint, but it also introduces hosting, rate limits,
|
|
22
|
+
availability, durable storage, observability, and incident-response obligations before the core
|
|
23
|
+
verification rules are reviewed. It is deferred.
|
|
24
|
+
|
|
25
|
+
### Migrate the application middleware first
|
|
26
|
+
|
|
27
|
+
The current application middleware uses custom `X-X402-*` headers and escrow-balance checks. A
|
|
28
|
+
direct migration before independent transaction verification would make the surface look standard
|
|
29
|
+
without closing its security boundary. It is deferred until the SDK mechanism is merged.
|
|
30
|
+
|
|
31
|
+
### Add a reusable SDK facilitator with injected state
|
|
32
|
+
|
|
33
|
+
This provides strict, testable verification and lets each host supply its own fetch implementation
|
|
34
|
+
and atomic replay store. It aligns directly with the public SDK, example, and external-feedback
|
|
35
|
+
parts of M2. This is the selected approach.
|
|
36
|
+
|
|
37
|
+
## Protocol model
|
|
38
|
+
|
|
39
|
+
The existing client uses `paymentFlow: "upfront"`: the buyer broadcasts and confirms `fund-job`
|
|
40
|
+
before it asks for the protected resource again. Under the official x402 v2 flow table, upfront
|
|
41
|
+
payments call facilitator settlement before the resource handler; read-only `/verify` is not part
|
|
42
|
+
of the normal resource-server ordering. For this Stacks mechanism, settlement does not broadcast a
|
|
43
|
+
second transaction. It independently observes the already confirmed escrow funding and atomically
|
|
44
|
+
consumes its transaction ID as the one-time payment proof.
|
|
45
|
+
|
|
46
|
+
The facilitator still implements `verify` because it is part of the standard facilitator API and
|
|
47
|
+
is useful for diagnostics. `verify` never writes replay state. `settle` repeats verification and
|
|
48
|
+
then performs the atomic replay-state transition before returning success.
|
|
49
|
+
|
|
50
|
+
## Components
|
|
51
|
+
|
|
52
|
+
`src/x402-facilitator.ts` will contain:
|
|
53
|
+
|
|
54
|
+
1. `HiroX402TransactionSource`, a read-only client for current v3 transaction/event detail and
|
|
55
|
+
the chain tip.
|
|
56
|
+
2. Strict parsers for the minimum Hiro response fields needed by the mechanism.
|
|
57
|
+
3. Verification of requirement/payload equality, confirmation depth, freshness, payer, contract
|
|
58
|
+
call, function arguments, asset transfer event, amount, and client proof block metadata.
|
|
59
|
+
4. `PerkOSX402Facilitator`, implementing the official `SchemeNetworkFacilitator` interface.
|
|
60
|
+
5. `PerkOSX402ReplayStore`, an injected atomic-consume interface.
|
|
61
|
+
6. `InMemoryX402ReplayStore`, safe for examples and one-process tests but explicitly unsuitable
|
|
62
|
+
for horizontally scaled production.
|
|
63
|
+
|
|
64
|
+
The default transaction source uses the API URL already resolved by the SDK configuration. No API
|
|
65
|
+
key, private key, signer, fee recipient, or treasury is needed.
|
|
66
|
+
|
|
67
|
+
## Verification rules
|
|
68
|
+
|
|
69
|
+
A payment is valid only when all checks pass:
|
|
70
|
+
|
|
71
|
+
- The payload and supplied requirement are deeply equal and both pass the existing PerkOS x402
|
|
72
|
+
parser.
|
|
73
|
+
- The transaction ID and claimed payer are valid for the configured Stacks network.
|
|
74
|
+
- Hiro v3 returns a successful, indexed `contract_call` with an anchored block record.
|
|
75
|
+
- The transaction sender equals the payload payer.
|
|
76
|
+
- The contract and function are exactly the configured asset-specific commerce contract and
|
|
77
|
+
`fund-job`.
|
|
78
|
+
- The decoded contract `job-funded` print event contains the exact job ID and amount. For sBTC, its
|
|
79
|
+
token principal is the configured canonical token.
|
|
80
|
+
- A matching on-chain transfer event moves the exact requirement amount from the payer to the
|
|
81
|
+
commerce contract: canonical SIP-010 sBTC for sBTC, or micro-STX for STX.
|
|
82
|
+
- The API block height/hash match any block metadata included in the client proof.
|
|
83
|
+
- The transaction has the configured minimum confirmation count and is no older than the quote's
|
|
84
|
+
`maxTimeoutSeconds`, with a small configurable clock-skew allowance.
|
|
85
|
+
- The network-prefixed transaction key has not already been consumed.
|
|
86
|
+
|
|
87
|
+
The event check is mandatory even when a transaction result is `(ok true)`: successful execution
|
|
88
|
+
alone does not prove that the quoted amount and asset moved into escrow.
|
|
89
|
+
|
|
90
|
+
## Replay and concurrency
|
|
91
|
+
|
|
92
|
+
The replay key is `${network}:${normalizedTxid}`. `settle` calls an atomic
|
|
93
|
+
`consume(key, record): Promise<boolean>`. Exactly one concurrent caller can receive `true`; every
|
|
94
|
+
later caller receives a replay failure. A production adapter must implement this with a unique
|
|
95
|
+
database constraint or an atomic primitive such as Redis `SET NX`, and should retain records at
|
|
96
|
+
least as long as any protected resource can be delivered.
|
|
97
|
+
|
|
98
|
+
The in-memory implementation uses a process-local map and is only for tests, examples, local
|
|
99
|
+
development, and a single-process demo. It must not be represented as durable or distributed
|
|
100
|
+
replay protection.
|
|
101
|
+
|
|
102
|
+
## Security limitation
|
|
103
|
+
|
|
104
|
+
The current Stacks payload is proof of an already public transaction, not a private off-chain
|
|
105
|
+
authorization bound cryptographically to an HTTP request. Confirmation freshness and first-writer
|
|
106
|
+
replay consumption reduce stale reuse, but a party that obtains the proof before settlement could
|
|
107
|
+
attempt to front-run it. The application must not migrate a high-value production paywall until a
|
|
108
|
+
later design either gives the facilitator the signed transaction before broadcast or adds a
|
|
109
|
+
request-bound payer signature/challenge.
|
|
110
|
+
|
|
111
|
+
This limitation will be visible in the README and example. The mechanism is appropriate for M2
|
|
112
|
+
integration evidence and controlled pilots, not an unaudited high-value production gateway.
|
|
113
|
+
|
|
114
|
+
## Errors
|
|
115
|
+
|
|
116
|
+
Malformed requirements, mismatches, failed transactions, insufficient confirmations, expired
|
|
117
|
+
proofs, missing transfer events, and replay attempts return stable invalid-reason strings. Network
|
|
118
|
+
or malformed API responses fail closed. Settlement never marks a proof consumed until every
|
|
119
|
+
read-only verification check succeeds.
|
|
120
|
+
|
|
121
|
+
If consumption succeeds, settlement returns the existing funding transaction, payer, Stacks
|
|
122
|
+
CAIP-2 network, and exact atomic amount. It never claims to have broadcast another transaction.
|
|
123
|
+
|
|
124
|
+
## Testing and evidence
|
|
125
|
+
|
|
126
|
+
Unit tests will cover sBTC and STX success, official facilitator registration, malformed payloads,
|
|
127
|
+
cross-network/requirement mismatch, wrong sender/contract/function/job/token/amount, missing or
|
|
128
|
+
incorrect print/transfer events, failed or incomplete transaction evidence, insufficient
|
|
129
|
+
confirmation, expiry, block metadata mismatch, API failure, and concurrent replay consumption.
|
|
130
|
+
|
|
131
|
+
A wallet-free quickstart will inspect the public M1 mainnet sBTC funding transaction through Hiro
|
|
132
|
+
and demonstrate that its contract call and transfer event match the expected escrow fields while
|
|
133
|
+
the facilitator correctly refuses it as an expired x402 proof. It changes no M1 state and consumes
|
|
134
|
+
no replay record. This is additional M2 SDK evidence, not a new M1 claim or adoption credit.
|
|
135
|
+
|
|
136
|
+
## Follow-up
|
|
137
|
+
|
|
138
|
+
1. Merge and publish the SDK verifier/facilitator.
|
|
139
|
+
2. Obtain external technical feedback and include the module in the recorded M2 SDK demo.
|
|
140
|
+
3. Add a durable replay-store adapter and thin hosted HTTP facilitator.
|
|
141
|
+
4. Replace the application's custom x402 middleware only after the request-binding limitation has
|
|
142
|
+
an accepted mitigation.
|
|
143
|
+
5. Continue the external security review and mainnet/non-team adoption work in parallel.
|