@perkos/agent-sdk 0.1.0 → 0.3.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 +73 -0
- package/README.md +191 -5
- package/SECURITY.md +12 -1
- 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 +8 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/x402-direct.d.ts +116 -0
- package/dist/x402-direct.d.ts.map +1 -0
- package/dist/x402-direct.js +593 -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-paying.d.ts +193 -0
- package/dist/x402-paying.d.ts.map +1 -0
- package/dist/x402-paying.js +587 -0
- package/dist/x402-paying.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/X402_PAYMENTS.md +143 -0
- 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/docs/plans/2026-08-27-x402-paying-flow-design.md +125 -0
- package/examples/x402-facilitator.ts +69 -0
- package/examples/x402-foundation.ts +30 -0
- package/examples/x402-payment-intent.ts +81 -0
- package/package.json +12 -5
|
@@ -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.
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# Stacks x402 v2 foundation
|
|
2
|
+
|
|
3
|
+
Date: 2026-08-25
|
|
4
|
+
Status: approved foundation for implementation
|
|
5
|
+
|
|
6
|
+
## Milestone 2 alignment
|
|
7
|
+
|
|
8
|
+
Milestone 1 is approved and closed. This work must not redeploy, replace, or add fee behavior to
|
|
9
|
+
the M1 mainnet contracts. It is an additive SDK adapter intended to make the existing STX and sBTC
|
|
10
|
+
job lifecycle easier for external developers and agent frameworks to adopt during Milestone 2.
|
|
11
|
+
|
|
12
|
+
The authoritative M2 acceptance work remains the public SDK, working documentation and example,
|
|
13
|
+
external security review, recorded SDK demo, mainnet adoption, non-team participation, and external
|
|
14
|
+
technical feedback. This x402 foundation can support the SDK/example/adoption portions, but does
|
|
15
|
+
not by itself complete M2 and must not delay the security review or adoption outreach.
|
|
16
|
+
|
|
17
|
+
## Decision
|
|
18
|
+
|
|
19
|
+
Use the official `@x402/core` v2 envelope and HTTP header codecs, then implement a narrow Stacks
|
|
20
|
+
escrow adapter around the existing `PerkOSClient`. The first version represents a confirmed
|
|
21
|
+
`fund-job` transaction as the scheme-specific payment proof. It does not implement a hosted
|
|
22
|
+
facilitator, protocol revenue, metering, or a new smart contract.
|
|
23
|
+
|
|
24
|
+
The wire contract uses:
|
|
25
|
+
|
|
26
|
+
- x402 version `2`;
|
|
27
|
+
- `PAYMENT-REQUIRED`, `PAYMENT-SIGNATURE`, and `PAYMENT-RESPONSE` header formats from
|
|
28
|
+
`@x402/core/http`;
|
|
29
|
+
- CAIP-2 network identifiers `stacks:1` for mainnet and `stacks:2147483648` for testnet;
|
|
30
|
+
- scheme `exact` because the job budget is an exact atomic amount;
|
|
31
|
+
- asset transfer method `perkos-escrow-v1` to prevent a direct-transfer facilitator from
|
|
32
|
+
misinterpreting an escrow requirement;
|
|
33
|
+
- payment flow `upfront` for the HTTP resource because the escrow must be confirmed before the
|
|
34
|
+
protected endpoint proceeds. The later job completion remains governed by the existing neutral
|
|
35
|
+
evaluator and escrow contract.
|
|
36
|
+
|
|
37
|
+
## Alternatives considered
|
|
38
|
+
|
|
39
|
+
### Keep the existing custom `X-X402-*` headers
|
|
40
|
+
|
|
41
|
+
This is the smallest code change, but it is not the current x402 v2 transport and makes ecosystem
|
|
42
|
+
integration harder. It is rejected for new SDK work. The application migration will happen only
|
|
43
|
+
after this SDK foundation is merged and tested.
|
|
44
|
+
|
|
45
|
+
### Adopt a third-party Stacks x402 package directly
|
|
46
|
+
|
|
47
|
+
`@t402/stacks` and `x402-stacks` provide useful reference implementations. Their transfer and
|
|
48
|
+
signer assumptions do not match the PerkOS job escrow lifecycle or the SDK's injected signer and
|
|
49
|
+
fail-closed spending policy. A direct dependency would create two signing/policy stacks. They are
|
|
50
|
+
not used in the foundation.
|
|
51
|
+
|
|
52
|
+
### Official x402 core plus a PerkOS Stacks adapter
|
|
53
|
+
|
|
54
|
+
This keeps the rapidly evolving transport and schemas with the x402 Foundation while retaining
|
|
55
|
+
PerkOS-specific escrow validation, exact post-conditions, Leather support, confirmation tracking,
|
|
56
|
+
and spending limits. This is the selected approach.
|
|
57
|
+
|
|
58
|
+
## Components
|
|
59
|
+
|
|
60
|
+
`src/x402.ts` will provide:
|
|
61
|
+
|
|
62
|
+
1. Network conversion between the SDK network names and Stacks CAIP-2 identifiers.
|
|
63
|
+
2. A payment-requirement builder derived from a resolved `PerkOSClient` configuration.
|
|
64
|
+
3. Strict parsing of PerkOS escrow requirements and confirmed-funding proofs.
|
|
65
|
+
4. `PerkOSX402SchemeClient`, an `@x402/core` scheme client that uses the existing SDK to inspect
|
|
66
|
+
the job, enforce an exact budget, fund it through the configured signer, wait for terminal
|
|
67
|
+
confirmation, and return a proof containing the transaction ID.
|
|
68
|
+
5. Re-exported x402 v2 header codecs so consumers do not recreate base64 JSON handling.
|
|
69
|
+
|
|
70
|
+
The adapter accepts a small structural client interface rather than a private key or wallet
|
|
71
|
+
implementation. The production `PerkOSClient` satisfies the interface; tests can provide a
|
|
72
|
+
deterministic in-memory client.
|
|
73
|
+
|
|
74
|
+
## Data flow
|
|
75
|
+
|
|
76
|
+
```text
|
|
77
|
+
Buyer agent Resource server Stacks
|
|
78
|
+
| GET/POST resource | |
|
|
79
|
+
|--------------------------->| |
|
|
80
|
+
| 402 + PAYMENT-REQUIRED | |
|
|
81
|
+
|<---------------------------| |
|
|
82
|
+
| validate requirement | |
|
|
83
|
+
| inspect job/budget | |
|
|
84
|
+
| fundJob via SDK signer --------------------------->|
|
|
85
|
+
| wait for confirmation ---------------------------->|
|
|
86
|
+
| PAYMENT-SIGNATURE | |
|
|
87
|
+
| (confirmed tx proof) | |
|
|
88
|
+
|--------------------------->| |
|
|
89
|
+
| | facilitator/verification is a later PR
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
The `PAYMENT-SIGNATURE` name is the x402 transport field for a scheme-specific payment payload.
|
|
93
|
+
For this foundation, its Stacks payload is a confirmed transaction proof rather than an EVM
|
|
94
|
+
EIP-3009 signature.
|
|
95
|
+
|
|
96
|
+
## Validation and failure handling
|
|
97
|
+
|
|
98
|
+
- Reject protocol versions other than v2.
|
|
99
|
+
- Reject non-Stacks or cross-network requirements.
|
|
100
|
+
- Require positive decimal atomic amounts and job IDs.
|
|
101
|
+
- Require the configured commerce contract as `payTo`.
|
|
102
|
+
- Require the configured sBTC asset identifier for sBTC and `STX` for STX.
|
|
103
|
+
- Require the on-chain job to exist, remain open, and have exactly the quoted budget.
|
|
104
|
+
- Reuse the SDK spending policy, signer/network checks, post-conditions, and confirmation tracker.
|
|
105
|
+
- Return a payload only after a `success` confirmation. Pending, abort, dropped, and timeout states
|
|
106
|
+
are payment failures.
|
|
107
|
+
- Never accept a transaction ID as proof of payment without a later facilitator/server verifying
|
|
108
|
+
the transaction, contract call, sender, amount, job, and replay status.
|
|
109
|
+
|
|
110
|
+
## Security boundary and known limitation
|
|
111
|
+
|
|
112
|
+
This PR creates and serializes payment proofs; it does not make an HTTP resource safe to monetize.
|
|
113
|
+
A production resource server still needs an independent Stacks facilitator/verifier and replay
|
|
114
|
+
store. The verifier must hydrate the transaction, match it to the accepted requirement, confirm
|
|
115
|
+
successful escrow funding, and prevent the same transaction from authorizing multiple purchases.
|
|
116
|
+
|
|
117
|
+
No secret, private key, treasury address, protocol fee, or production endpoint is introduced.
|
|
118
|
+
|
|
119
|
+
## Testing
|
|
120
|
+
|
|
121
|
+
The implementation will cover:
|
|
122
|
+
|
|
123
|
+
- CAIP-2 network mapping;
|
|
124
|
+
- STX and sBTC payment-requirement construction;
|
|
125
|
+
- official x402 header encode/decode round trips;
|
|
126
|
+
- invalid version, network, amount, asset, payee, metadata, and proof rejection;
|
|
127
|
+
- exact job-budget validation;
|
|
128
|
+
- successful confirmed funding through a fake client;
|
|
129
|
+
- failure on non-success terminal confirmations;
|
|
130
|
+
- compatibility with `@x402/core`'s `x402Client` registration and payload creation.
|
|
131
|
+
|
|
132
|
+
## Follow-up sequence
|
|
133
|
+
|
|
134
|
+
1. Merge this SDK foundation.
|
|
135
|
+
2. Build a read-only Stacks verifier/facilitator with replay protection and testnet evidence.
|
|
136
|
+
3. Replace the application's custom `X-X402-*` middleware with the SDK v2 adapter.
|
|
137
|
+
4. Add an M2 example/demo and use it for external developer feedback and adoption outreach.
|
|
138
|
+
5. Consider metering and protocol revenue only as a separate, reviewed product phase that does
|
|
139
|
+
not alter the approved M1 contracts or block M2 acceptance.
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# Nayori x402 paying flow design
|
|
2
|
+
|
|
3
|
+
Date: 2026-08-27
|
|
4
|
+
|
|
5
|
+
Status: approved for implementation
|
|
6
|
+
|
|
7
|
+
## Objective
|
|
8
|
+
|
|
9
|
+
Add a payer-side TypeScript flow for Nayori's direct Stacks x402 profile. The same immutable
|
|
10
|
+
payment intent must support an interactive Leather wallet and an automated agent whose key stays
|
|
11
|
+
inside an application-owned KMS, HSM, secret manager, or wallet service. The SDK prepares and
|
|
12
|
+
validates the payment; it does not broadcast. The Nayori facilitator remains the only component
|
|
13
|
+
authorized to reserve and broadcast a submitted payment.
|
|
14
|
+
|
|
15
|
+
This increment does not modify or redeploy the approved Milestone 1 contracts, enable Platform
|
|
16
|
+
settlement, or claim Milestone 2 transaction/adoption evidence.
|
|
17
|
+
|
|
18
|
+
## Constraints and external behavior
|
|
19
|
+
|
|
20
|
+
- Direct payments use the existing `stacks-signed-tx-v1` profile for STX, sBTC, and USDCx.
|
|
21
|
+
- `PaymentIntent` is deterministic, serializable, request-bound, and contains no private material.
|
|
22
|
+
- The SDK must validate the trusted quote, accepted x402 requirement, request digest, payer,
|
|
23
|
+
recipient, asset, atomic amount, fee, nonce, signature, memo, and post-conditions before returning
|
|
24
|
+
settlement input.
|
|
25
|
+
- A signer receives only an immutable intent and the canonical unsigned transaction.
|
|
26
|
+
- Leather integration uses the current Stacks Connect `stx_signTransaction` method with
|
|
27
|
+
`broadcast: false`; a missing signed transaction is a failure even if a wallet returns a txid.
|
|
28
|
+
- Headless integration delegates signing to an application callback. The SDK never accepts,
|
|
29
|
+
requests, stores, or logs the agent's private key.
|
|
30
|
+
- Spending policy is mandatory and fail-closed. An LLM or agent cannot override its decision.
|
|
31
|
+
- Session limits count signed authorizations, not confirmed settlements. This conservative rule
|
|
32
|
+
prevents repeated signing after an ambiguous network result.
|
|
33
|
+
|
|
34
|
+
## Public API
|
|
35
|
+
|
|
36
|
+
The new module exports:
|
|
37
|
+
|
|
38
|
+
- `NayoriX402PaymentIntent` and `createNayoriX402PaymentIntent`;
|
|
39
|
+
- `NayoriX402PaymentPolicy`, its explicit configuration, and read-only session usage;
|
|
40
|
+
- `NayoriX402PaymentSigner` as the common signer contract;
|
|
41
|
+
- `LeatherSigner`, a Stacks Connect callback adapter;
|
|
42
|
+
- `PolicySigner`, a remote/headless signing callback adapter;
|
|
43
|
+
- `NayoriX402PaymentClient`, which builds, authorizes, signs, verifies, and returns the exact input
|
|
44
|
+
accepted by the hosted `/v1/x402/settle` route.
|
|
45
|
+
|
|
46
|
+
The client input contains the signed quote bundle returned by Nayori, the protected request, fee,
|
|
47
|
+
and nonce. Address and compressed public key come from the signer. The output contains
|
|
48
|
+
`signedQuote`, `paymentRequirements`, `paymentPayload`, `request`, and locally verified payment
|
|
49
|
+
metadata. It never broadcasts.
|
|
50
|
+
|
|
51
|
+
## Intent and transaction construction
|
|
52
|
+
|
|
53
|
+
Intent construction first normalizes the quote and proves that:
|
|
54
|
+
|
|
55
|
+
1. the protected method, canonical URL, and body digest match the quote;
|
|
56
|
+
2. the accepted x402 requirement is exactly the requirement derived from that quote;
|
|
57
|
+
3. the quote is currently valid with the configured clock-skew allowance;
|
|
58
|
+
4. the signer's compressed public key derives the declared Stacks payer address;
|
|
59
|
+
5. fee and nonce are canonical unsigned integers.
|
|
60
|
+
|
|
61
|
+
The intent ID is a SHA-256 digest over a domain-separated canonical representation containing the
|
|
62
|
+
quote fingerprint, payer, public key, fee, and nonce. The signed transaction is constructed as:
|
|
63
|
+
|
|
64
|
+
- STX: a native token transfer with the quote fingerprint in the memo;
|
|
65
|
+
- sBTC/USDCx: the canonical SIP-010 `transfer` call with the payer and recipient principals, the
|
|
66
|
+
fingerprint in the optional memo, deny mode, and one exact fungible-token post-condition.
|
|
67
|
+
|
|
68
|
+
All three paths are standard, non-sponsored transactions. Fee and nonce are provided explicitly so
|
|
69
|
+
transaction preparation is deterministic and performs no hidden network request.
|
|
70
|
+
|
|
71
|
+
## Policy and concurrency
|
|
72
|
+
|
|
73
|
+
Policy configuration explicitly lists allowed networks, assets, recipients, and HTTPS origins,
|
|
74
|
+
plus per-transaction and per-session atomic limits for every allowed asset and a maximum fee in
|
|
75
|
+
micro-STX. Optional merchant IDs and a minimum remaining quote lifetime further narrow authority.
|
|
76
|
+
|
|
77
|
+
Authorization reserves both payment amount and fee synchronously before asynchronous signing. The
|
|
78
|
+
reservation is committed after a valid signed transaction is produced, or released if building,
|
|
79
|
+
signing, or local verification fails. Active reservations are included in session-limit checks, so
|
|
80
|
+
concurrent agent calls cannot oversubscribe the budget. A committed intent cannot be signed again
|
|
81
|
+
by the same policy instance.
|
|
82
|
+
|
|
83
|
+
## Signer boundaries
|
|
84
|
+
|
|
85
|
+
`LeatherSigner` is configured with the connected address, compressed public key, and an injected
|
|
86
|
+
Stacks Connect-compatible request function. It calls only:
|
|
87
|
+
|
|
88
|
+
```text
|
|
89
|
+
stx_signTransaction({ transaction: unsignedHex, broadcast: false })
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
`PolicySigner` is configured with the same public identity and a remote callback. That callback may
|
|
93
|
+
talk to a KMS/HSM/wallet service and must return a fully signed serialized transaction. No example
|
|
94
|
+
introduced for this payer flow uses an environment variable or local raw private key. The older,
|
|
95
|
+
explicitly opt-in escrow lifecycle example remains separate from this x402 custody boundary.
|
|
96
|
+
|
|
97
|
+
Neither signer result is trusted. The client runs the existing pure x402 verifier and additionally
|
|
98
|
+
checks payer, nonce, fee, non-sponsored authorization, and the canonical intent fields before
|
|
99
|
+
committing policy usage.
|
|
100
|
+
|
|
101
|
+
## Errors and observability
|
|
102
|
+
|
|
103
|
+
Invalid quote context returns `X402_INVALID`; policy rejection returns the existing `POLICY_DENIED`
|
|
104
|
+
or `POLICY_LIMIT_REQUIRED`; signer cancellation or malformed output returns `SIGNING_FAILED`.
|
|
105
|
+
Errors include stable, non-secret context such as intent ID, asset, or quote ID. Signed quote tokens,
|
|
106
|
+
request bodies, unsigned/signed transaction bytes, and callbacks are not included in error details.
|
|
107
|
+
|
|
108
|
+
## Tests and release gate
|
|
109
|
+
|
|
110
|
+
Regression coverage must include all three assets, Leather `broadcast: false`, remote signing,
|
|
111
|
+
request/requirement mismatch, public-key mismatch, fee and amount limits, recipient/origin denial,
|
|
112
|
+
quote expiry, signer mutation, invalid signatures, cancellation, concurrent reservation, release on
|
|
113
|
+
failure, and duplicate-intent prevention. `npm run verify`, `npm audit --audit-level=high`, package
|
|
114
|
+
contents inspection, and a clean-room consumer import are required before the PR is opened.
|
|
115
|
+
|
|
116
|
+
## Primary references
|
|
117
|
+
|
|
118
|
+
- Stacks Connect `request` API and normalized `stx_signTransaction` result:
|
|
119
|
+
https://docs.stacks.co/reference/stacks.js/stacks-connect/request/request
|
|
120
|
+
- Stacks Connect wallet compatibility, including Leather support:
|
|
121
|
+
https://docs.stacks.co/stacks-connect/wallet-support
|
|
122
|
+
- Stacks transaction construction and unsigned transaction primitives:
|
|
123
|
+
https://docs.stacks.co/stacks.js/build-transactions
|
|
124
|
+
- Stacks node raw transaction broadcast boundary:
|
|
125
|
+
https://docs.stacks.co/reference/api/stacks-node-rpc/transactions
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import {
|
|
2
|
+
HiroX402TransactionSource,
|
|
3
|
+
InMemoryX402ReplayStore,
|
|
4
|
+
PerkOSX402Facilitator,
|
|
5
|
+
createPerkOSX402PaymentRequired,
|
|
6
|
+
resolveConfig,
|
|
7
|
+
type PaymentPayload,
|
|
8
|
+
} from "@perkos/agent-sdk";
|
|
9
|
+
|
|
10
|
+
const transaction =
|
|
11
|
+
"0xaf4129fe46fc913fda7b9fa87543f05fc5f4430b9b5f26a46f9c3032ea0fcbd4";
|
|
12
|
+
const payer = "SP2K7PV5NXBNRV510S6DCA6RFMTFHAF3ZPK6ZSXPH";
|
|
13
|
+
const blockHeight = 8_650_821;
|
|
14
|
+
const blockHash =
|
|
15
|
+
"0x46786964695d632ce40d54e5a94f5e74bcaf4fc995ff45c9ae913be0020e402d";
|
|
16
|
+
const config = resolveConfig({ network: "mainnet" });
|
|
17
|
+
const paymentRequired = createPerkOSX402PaymentRequired(config, {
|
|
18
|
+
resource: {
|
|
19
|
+
url: "https://nayori.example/evidence/m1-job-1",
|
|
20
|
+
description: "Read-only x402 verifier evidence",
|
|
21
|
+
mimeType: "application/json",
|
|
22
|
+
serviceName: "Nayori",
|
|
23
|
+
},
|
|
24
|
+
asset: "sbtc",
|
|
25
|
+
jobId: 1n,
|
|
26
|
+
amount: 10_000n,
|
|
27
|
+
maxTimeoutSeconds: 3_600,
|
|
28
|
+
});
|
|
29
|
+
const paymentPayload: PaymentPayload = {
|
|
30
|
+
x402Version: 2,
|
|
31
|
+
resource: paymentRequired.resource,
|
|
32
|
+
accepted: paymentRequired.accepts[0]!,
|
|
33
|
+
payload: {
|
|
34
|
+
transaction,
|
|
35
|
+
payer,
|
|
36
|
+
jobId: "1",
|
|
37
|
+
amount: "10000",
|
|
38
|
+
asset: "sbtc",
|
|
39
|
+
commerceContract: config.contracts.sbtcCommerce,
|
|
40
|
+
blockHeight,
|
|
41
|
+
blockHash,
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
const transactionSource = new HiroX402TransactionSource({ network: "mainnet" });
|
|
45
|
+
const facilitator = new PerkOSX402Facilitator({
|
|
46
|
+
config,
|
|
47
|
+
transactionSource,
|
|
48
|
+
replayStore: new InMemoryX402ReplayStore(),
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const verification = await facilitator.verify(
|
|
52
|
+
paymentPayload,
|
|
53
|
+
paymentRequired.accepts[0]!
|
|
54
|
+
);
|
|
55
|
+
if (verification.isValid || verification.invalidReason !== "payment_expired") {
|
|
56
|
+
throw new Error(
|
|
57
|
+
`Expected the historical proof to be rejected as expired, received ${JSON.stringify(verification)}`
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
console.log({
|
|
62
|
+
transaction,
|
|
63
|
+
observedContract: config.contracts.sbtcCommerce,
|
|
64
|
+
observedAsset: paymentRequired.accepts[0]!.asset,
|
|
65
|
+
observedAmount: paymentRequired.accepts[0]!.amount,
|
|
66
|
+
verification,
|
|
67
|
+
note:
|
|
68
|
+
"The facilitator matched the public funding call and transfer events, then failed closed because this historical transaction is outside the x402 payment window. No state was changed.",
|
|
69
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createPerkOSX402PaymentRequired,
|
|
3
|
+
decodePaymentRequiredHeader,
|
|
4
|
+
encodePaymentRequiredHeader,
|
|
5
|
+
resolveConfig,
|
|
6
|
+
} from "@perkos/agent-sdk";
|
|
7
|
+
|
|
8
|
+
const config = resolveConfig({ network: "mainnet" });
|
|
9
|
+
const paymentRequired = createPerkOSX402PaymentRequired(config, {
|
|
10
|
+
resource: {
|
|
11
|
+
url: "https://agent.example/jobs/7/fund",
|
|
12
|
+
description: "Fund an existing PerkOS agent job escrow",
|
|
13
|
+
mimeType: "application/json",
|
|
14
|
+
serviceName: "Nayori",
|
|
15
|
+
tags: ["agents", "escrow"],
|
|
16
|
+
},
|
|
17
|
+
asset: "sbtc",
|
|
18
|
+
jobId: 7n,
|
|
19
|
+
amount: 25_000n,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const header = encodePaymentRequiredHeader(paymentRequired);
|
|
23
|
+
const decoded = decodePaymentRequiredHeader(header);
|
|
24
|
+
|
|
25
|
+
console.log({
|
|
26
|
+
headerName: "PAYMENT-REQUIRED",
|
|
27
|
+
header,
|
|
28
|
+
decoded,
|
|
29
|
+
note: "Envelope-only example: no wallet, transaction, or facilitator is used.",
|
|
30
|
+
});
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import {
|
|
2
|
+
NayoriX402PaymentPolicy,
|
|
3
|
+
buildNayoriX402UnsignedPaymentTransaction,
|
|
4
|
+
createNayoriX402PaymentIntent,
|
|
5
|
+
createNayoriX402PaymentRequirements,
|
|
6
|
+
createNayoriX402Quote,
|
|
7
|
+
} from "@perkos/agent-sdk";
|
|
8
|
+
|
|
9
|
+
const payer = "ST1THWXQ8368SDN2MJGE4BMDKMCHZ2GSVTSQDA7QF";
|
|
10
|
+
const payerPublicKey =
|
|
11
|
+
"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798";
|
|
12
|
+
const payTo = "ST3AZN3BSQYJ5VWMNG92N88Z4G9498VYSHDZD9EK";
|
|
13
|
+
const now = Math.floor(Date.now() / 1_000);
|
|
14
|
+
const request = {
|
|
15
|
+
method: "POST",
|
|
16
|
+
url: "https://api.example.com/v1/weather",
|
|
17
|
+
body: JSON.stringify({ city: "Miami" }),
|
|
18
|
+
} as const;
|
|
19
|
+
|
|
20
|
+
const quote = await createNayoriX402Quote({
|
|
21
|
+
quoteId: "quote-payer-quickstart",
|
|
22
|
+
merchantId: "merchant-weather",
|
|
23
|
+
network: "testnet",
|
|
24
|
+
asset: "usdcx",
|
|
25
|
+
amount: 100_000n,
|
|
26
|
+
payTo,
|
|
27
|
+
...request,
|
|
28
|
+
issuedAt: now,
|
|
29
|
+
expiresAt: now + 300,
|
|
30
|
+
});
|
|
31
|
+
const paymentRequirements = await createNayoriX402PaymentRequirements(quote);
|
|
32
|
+
const intent = await createNayoriX402PaymentIntent({
|
|
33
|
+
quote,
|
|
34
|
+
paymentRequirements,
|
|
35
|
+
request,
|
|
36
|
+
payer,
|
|
37
|
+
publicKey: payerPublicKey,
|
|
38
|
+
fee: 300n,
|
|
39
|
+
nonce: 7n,
|
|
40
|
+
nowSeconds: now,
|
|
41
|
+
});
|
|
42
|
+
const policy = new NayoriX402PaymentPolicy(
|
|
43
|
+
{
|
|
44
|
+
allowedNetworks: ["testnet"],
|
|
45
|
+
allowedAssets: ["usdcx"],
|
|
46
|
+
allowedRecipients: [payTo],
|
|
47
|
+
allowedOrigins: ["https://api.example.com"],
|
|
48
|
+
allowedMerchantIds: ["merchant-weather"],
|
|
49
|
+
maxPerTransaction: { usdcx: 100_000n },
|
|
50
|
+
maxPerSession: { usdcx: 500_000n },
|
|
51
|
+
maxFeePerTransaction: 500n,
|
|
52
|
+
maxFeePerSession: 2_500n,
|
|
53
|
+
},
|
|
54
|
+
() => now
|
|
55
|
+
);
|
|
56
|
+
const authorization = policy.reserve(intent);
|
|
57
|
+
const unsignedTransaction = await buildNayoriX402UnsignedPaymentTransaction(intent);
|
|
58
|
+
|
|
59
|
+
console.log(
|
|
60
|
+
JSON.stringify(
|
|
61
|
+
{
|
|
62
|
+
status: "ready-for-signature",
|
|
63
|
+
intentId: intent.intentId,
|
|
64
|
+
quoteId: intent.quoteId,
|
|
65
|
+
asset: intent.asset,
|
|
66
|
+
amount: intent.amount,
|
|
67
|
+
payer: intent.payer,
|
|
68
|
+
payTo: intent.payTo,
|
|
69
|
+
fee: intent.fee,
|
|
70
|
+
nonce: intent.nonce,
|
|
71
|
+
quoteFingerprint: intent.quoteFingerprint,
|
|
72
|
+
unsignedTransactionBytes: unsignedTransaction.length / 2,
|
|
73
|
+
policyRemaining: authorization.remainingThisSession.toString(),
|
|
74
|
+
broadcast: false,
|
|
75
|
+
},
|
|
76
|
+
null,
|
|
77
|
+
2
|
|
78
|
+
)
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
authorization.release();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@perkos/agent-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "TypeScript SDK for agent identity, escrow settlement, and reputation on Stacks.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -29,17 +29,23 @@
|
|
|
29
29
|
"prepublishOnly": "npm run verify",
|
|
30
30
|
"prequickstart": "npm run build",
|
|
31
31
|
"quickstart": "node --import tsx examples/quickstart.ts",
|
|
32
|
+
"prequickstart:x402": "npm run build",
|
|
33
|
+
"quickstart:x402": "node --import tsx examples/x402-foundation.ts",
|
|
34
|
+
"prequickstart:x402:facilitator": "npm run build",
|
|
35
|
+
"quickstart:x402:facilitator": "node --import tsx examples/x402-facilitator.ts",
|
|
36
|
+
"prequickstart:x402:payer": "npm run build",
|
|
37
|
+
"quickstart:x402:payer": "node --import tsx examples/x402-payment-intent.ts",
|
|
32
38
|
"prequickstart:testnet": "npm run build",
|
|
33
39
|
"quickstart:testnet": "node --import tsx examples/testnet-lifecycle.ts"
|
|
34
40
|
},
|
|
35
41
|
"repository": {
|
|
36
42
|
"type": "git",
|
|
37
|
-
"url": "git+https://github.com/PerkOS-xyz/PerkOS-Agent-SDK.git"
|
|
43
|
+
"url": "git+https://github.com/PerkOS-xyz/PerkOS-Nayori-Agent-SDK.git"
|
|
38
44
|
},
|
|
39
45
|
"bugs": {
|
|
40
|
-
"url": "https://github.com/PerkOS-xyz/PerkOS-Agent-SDK/issues"
|
|
46
|
+
"url": "https://github.com/PerkOS-xyz/PerkOS-Nayori-Agent-SDK/issues"
|
|
41
47
|
},
|
|
42
|
-
"homepage": "https://
|
|
48
|
+
"homepage": "https://nayori.ai",
|
|
43
49
|
"keywords": [
|
|
44
50
|
"ai-agents",
|
|
45
51
|
"agentic-commerce",
|
|
@@ -58,7 +64,8 @@
|
|
|
58
64
|
"node": ">=20"
|
|
59
65
|
},
|
|
60
66
|
"dependencies": {
|
|
61
|
-
"@stacks/transactions": "^7.6.0"
|
|
67
|
+
"@stacks/transactions": "^7.6.0",
|
|
68
|
+
"@x402/core": "^2.23.0"
|
|
62
69
|
},
|
|
63
70
|
"devDependencies": {
|
|
64
71
|
"@types/node": "^22.15.0",
|