@perkos/agent-sdk 0.7.0 → 0.8.0-rc.1

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.
Files changed (83) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/README.md +98 -44
  3. package/dist/builders.d.ts +5 -1
  4. package/dist/builders.d.ts.map +1 -1
  5. package/dist/builders.js +155 -11
  6. package/dist/builders.js.map +1 -1
  7. package/dist/client.d.ts +8 -1
  8. package/dist/client.d.ts.map +1 -1
  9. package/dist/client.js +168 -26
  10. package/dist/client.js.map +1 -1
  11. package/dist/constants.d.ts.map +1 -1
  12. package/dist/constants.js +6 -4
  13. package/dist/constants.js.map +1 -1
  14. package/dist/custody/backend.d.ts +4 -0
  15. package/dist/custody/backend.d.ts.map +1 -0
  16. package/dist/custody/backend.js +149 -0
  17. package/dist/custody/backend.js.map +1 -0
  18. package/dist/custody/cli.d.ts +3 -0
  19. package/dist/custody/cli.d.ts.map +1 -0
  20. package/dist/custody/cli.js +43 -0
  21. package/dist/custody/cli.js.map +1 -0
  22. package/dist/custody/engine.d.ts +54 -0
  23. package/dist/custody/engine.d.ts.map +1 -0
  24. package/dist/custody/engine.js +115 -0
  25. package/dist/custody/engine.js.map +1 -0
  26. package/dist/custody/ledger.d.ts +24 -0
  27. package/dist/custody/ledger.d.ts.map +1 -0
  28. package/dist/custody/ledger.js +122 -0
  29. package/dist/custody/ledger.js.map +1 -0
  30. package/dist/custody/permit.d.ts +42 -0
  31. package/dist/custody/permit.d.ts.map +1 -0
  32. package/dist/custody/permit.js +91 -0
  33. package/dist/custody/permit.js.map +1 -0
  34. package/dist/custody/socket.d.ts +11 -0
  35. package/dist/custody/socket.d.ts.map +1 -0
  36. package/dist/custody/socket.js +118 -0
  37. package/dist/custody/socket.js.map +1 -0
  38. package/dist/evaluation-commitments.d.ts +47 -0
  39. package/dist/evaluation-commitments.d.ts.map +1 -0
  40. package/dist/evaluation-commitments.js +96 -0
  41. package/dist/evaluation-commitments.js.map +1 -0
  42. package/dist/index.d.ts +6 -2
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +3 -1
  45. package/dist/index.js.map +1 -1
  46. package/dist/mcp/cli.d.ts +3 -0
  47. package/dist/mcp/cli.d.ts.map +1 -0
  48. package/dist/mcp/cli.js +36 -0
  49. package/dist/mcp/cli.js.map +1 -0
  50. package/dist/mcp/evaluation.d.ts +33 -0
  51. package/dist/mcp/evaluation.d.ts.map +1 -0
  52. package/dist/mcp/evaluation.js +141 -0
  53. package/dist/mcp/evaluation.js.map +1 -0
  54. package/dist/mcp/server.d.ts +27 -0
  55. package/dist/mcp/server.d.ts.map +1 -0
  56. package/dist/mcp/server.js +207 -0
  57. package/dist/mcp/server.js.map +1 -0
  58. package/dist/policy.d.ts.map +1 -1
  59. package/dist/policy.js +7 -2
  60. package/dist/policy.js.map +1 -1
  61. package/dist/service-fees.d.ts +15 -0
  62. package/dist/service-fees.d.ts.map +1 -0
  63. package/dist/service-fees.js +109 -0
  64. package/dist/service-fees.js.map +1 -0
  65. package/dist/types.d.ts +66 -2
  66. package/dist/types.d.ts.map +1 -1
  67. package/docs/ARCHITECTURE.md +31 -15
  68. package/docs/EXISTING_AGENT.md +123 -0
  69. package/docs/HERMES_BUYER.md +91 -0
  70. package/docs/HERMES_CHECKPOINTS.md +88 -0
  71. package/docs/HERMES_CUSTODY.md +164 -0
  72. package/docs/HERMES_MCP.md +157 -0
  73. package/docs/HERMES_PROVIDER.md +98 -0
  74. package/docs/RELEASE_0.8.0_RC1.md +60 -0
  75. package/docs/SERVICE_FEES.md +108 -0
  76. package/docs/TESTNET_QUICKSTART.md +123 -0
  77. package/docs/VALIDATION_AND_RELEASE.md +82 -0
  78. package/docs/WALLET_SIGNER_SETUP.md +52 -0
  79. package/examples/service-fees.ts +36 -0
  80. package/examples/testnet-journal.ts +57 -0
  81. package/examples/testnet-lifecycle.ts +188 -294
  82. package/examples/testnet.env.example +12 -14
  83. package/package.json +7 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,80 @@ All notable changes to `@perkos/agent-sdk` are documented here.
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 0.8.0-rc.1 — QA release candidate (not published)
8
+
9
+ This candidate packages the QA additions below without changing the default v5/v4 contracts.
10
+ Publication requires separate approval and must use the `next` tag, leaving `latest` at 0.7.1.
11
+ See [candidate release notes](docs/RELEASE_0.8.0_RC1.md) for installation and validation gates.
12
+
13
+ - Record the verified internal Hermes sBTC lifecycle and its public settlement evidence.
14
+ Separate published npm 0.7.1 from the reviewed QA artifact, conversation completion from
15
+ on-chain outcomes, and manifest hashes from versioned commitments. Preserve all release,
16
+ signing and production boundaries; no npm publication or default changes.
17
+
18
+ - Return `null` for absent reputation-sync records on autonomous STX/sBTC contracts
19
+ (`u823`/`u923`), preserving earlier `u623`/`u723` handling. Keep unrelated contract
20
+ errors visible; add regression coverage and readable error descriptions. No changes
21
+ to signing, settlement, contract defaults or npm publication.
22
+
23
+ - Allow 45 seconds for public QA evaluation admission (15 seconds for status reads)
24
+ to accommodate paced eligibility checks. Return sanitized, actionable quota,
25
+ eligibility and transport errors without automatic retries or budget changes.
26
+ Preserve deterministic-ID reconciliation after an ambiguous response.
27
+
28
+ - Document wallet funding versus escrow, per-role QA budgets, six-burn custody confirmation,
29
+ job-bound provider handoff and ambiguous-operation recovery. Align README and buyer/provider
30
+ manuals without changing SDK runtime, production defaults or npm publication.
31
+
32
+ - Start external onboarding with an already running agent and its own LLM; no PerkOS-LLM
33
+ account or model migration required. Add separate operator-owned wallet/signer preparation,
34
+ registration confirmation/read-back, role handoffs and release boundaries. Align the Hermes
35
+ examples and README; no wallet-generation tool, runtime behavior or npm release changes.
36
+
37
+ - Add explicit provider-only QA MCP evaluation admission/status, bound to a custody-authorized
38
+ job and confirmed submission. Validate on-chain identity, budget and commitments; query the
39
+ deterministic evaluation ID before admission. Fixed public endpoint, bounded/sanitized replies,
40
+ no evaluator credentials, new signing, automatic retry, extra fee or x402 purchase. Off by default.
41
+
42
+ - Add opt-in QA custody delegation: Linux signer service, immutable per-job/role permits, fixed gas and escrow caps, durable pre-sign reservation/pre-broadcast txid, nonce/chain guards and fail-closed replay/recovery. Signing stays off by default. No mainnet, npm publication, live Hermes validation or deployment is implied.
43
+ - Add real Unix-socket/MCP protocol tests, offline fixture signing and actual SDK lifecycle planning against mocked testnet reads. Document the separate-UID isolation gate, fee consent and remaining evaluator/x402/video work.
44
+
45
+ - Package a Node-only QA MCP stdio binary with six read/prepare tools per role, fixed testnet contracts, strict inputs and sanitized failures. No wallet, signing, broadcast or x402 capabilities are added to this adapter.
46
+ - Add official MCP protocol/stdio tests and an honest candidate installation guide. Browser exports, published npm package and deployments remain unchanged.
47
+
48
+ - Add QA commitment helpers for client criteria and provider evidence using existing contract fields.
49
+ - Replace the old centralized lifecycle example with role-separated, journaled actions and appeal-aware finalization; offline preview remains default.
50
+ - Document candidate-only onboarding, isolated custody, fee consent and deployment/E2E limitations; npm and deployment defaults are unchanged.
51
+
52
+ - Add explicit opt-in STX v6/sBTC v5 earned-service-fee reads, initialization, waiver and treasury-funded refund methods. Published 0.7.1 and deployed defaults remain unchanged.
53
+ - Require client/provider acceptance of gross budget, 200 bps, treasury and net rejection refund before candidate funding/submission; fail closed on inconsistent live state.
54
+ - Preserve aggregate gross deny-mode settlement constraints and job-pinned sBTC tokens; account for treasury refunds in spending limits.
55
+ - Add a signer-free fee inspection example, candidate integration guide, accounting/consent/role regression tests. No x402 second charge, contract deployment or npm publication is introduced.
56
+
57
+ ## 0.7.1 - 2026-09-02
58
+
59
+ ### Changed
60
+
61
+ - Promote the source-verified `agentic-commerce-v5`, `sbtc-commerce-v4` and
62
+ `reputation-registry-v3` generation to the default mainnet and testnet deployment map.
63
+ - Make autonomous decisions, role-specific appeals, human resolution and permissionless
64
+ finalization available without contract overrides.
65
+
66
+ ### Evidence
67
+
68
+ - Record the exact-source mainnet deployments in blocks `8905872` and `8905874` and configuration
69
+ through block `8905886` with the fixed 12-block review and 144-block appeal policy.
70
+ - Record controlled mainnet STX and canonical-sBTC appeal canaries passing 47/47 and 50/50 checks,
71
+ followed by an independent signer-free 75/75 public-state postcheck. These internal actors do
72
+ not count as external adoption, non-team wallets or revenue.
73
+
74
+ ### Security
75
+
76
+ - Preserve v4/v3 and earlier generations only as explicit same-network historical overrides.
77
+ - Keep the separately pinned appeal authority outside the SDK signer boundary; the SDK derives
78
+ settlement recipients from authoritative on-chain state and retains exact deny-mode outflow
79
+ post-conditions.
80
+
7
81
  ## 0.7.0 - 2026-09-01
8
82
 
9
83
  ### Added
package/README.md CHANGED
@@ -9,21 +9,68 @@ This repository is the continuation of `PerkOS-xyz/PerkOS-Agent-SDK`, renamed to
9
9
  public SDK with the Nayori product identity. The npm package remains `@perkos/agent-sdk` and the
10
10
  complete Git history, releases, issues, and pull requests are preserved.
11
11
 
12
- > Status: 0.7.0 release candidate; 0.6.0 remains the current public npm release. Read clients,
12
+ > Status: npm 0.7.1 uses Nayori's verified v5/v4 contracts as the
13
+ > default integration. Read clients,
13
14
  > transaction builders, browser and headless signer
14
15
  > adapters, confirmation receipts, safety policies, and a transactional testnet quickstart are
15
16
  > implemented. The x402 v2 client and Stacks facilitator foundations are implemented, with
16
- > wallet-linked OAuth and MCP support for the invite-only testnet pilot. The hosted testnet rollout
17
- > is live; external review and independently attributable adoption evidence remain open. The direct x402
17
+ > wallet-linked OAuth and MCP support for invite-only partners. QA uses Stacks testnet;
18
+ > production uses mainnet. External review and independently attributable adoption evidence remain open. The direct x402
18
19
  > profile includes request-bound pure verification
19
20
  > and payer-side intent, policy, Leather, and remote-signer foundations for STX, sBTC, and USDCx.
20
21
  > The SDK also implements the MPP PaymentAuth `usdc`/`charge` Stacks profile for direct USDCx,
21
22
  > including canonical challenges, credentials, pure verification and settlement receipts.
22
23
  > Every payment signature remains delegated to the configured wallet or custody boundary. Mainnet
23
- > facilitator settlement remains disabled.
24
+ > facilitator settlement is confirmation-gated and payer-approved; sponsorship remains disabled.
25
+
26
+ ## Connect an existing agent
27
+
28
+ This source prepares **0.8.0-rc.1**, an unpublished QA candidate. Stable npm remains 0.7.1.
29
+ See [candidate release notes and exact installation](docs/RELEASE_0.8.0_RC1.md).
30
+ The candidate preserves default v5/v4 contracts; its Hermes signer pilot is testnet-only.
31
+
32
+ Your agent is already installed and working with your own LLM. Keep that setup: Nayori does not
33
+ require PerkOS-LLM, Hermes or sharing model credentials. Start with the
34
+ [existing-agent onboarding guide](docs/EXISTING_AGENT.md): choose SDK/MCP → prepare your own
35
+ wallet/signer separately → pin testnet and limits → register and verify the agent ID → create
36
+ or fulfill work → verify settlement and reputation. Wallet creation is not registration, and
37
+ OAuth access is not payment authority. The Hermes walkthrough is an unreleased QA example,
38
+ not a prerequisite. One operator-supervised internal sBTC lifecycle has passed; this is not
39
+ certification of external onboarding. See [validation and release boundaries](docs/VALIDATION_AND_RELEASE.md).
40
+
41
+ Use the [QA checkpoints and confirmation guide](docs/HERMES_CHECKPOINTS.md) to distinguish
42
+ wallet funding, registration, funded escrow, submitted work and final settlement. The custody
43
+ pilot requires six subsequent Bitcoin burn blocks before advancing an operation; a txid or
44
+ explorer success alone does not clear that gate. Preserve the journal rather than re-signing.
24
45
 
25
46
  ## Requirements
26
47
 
48
+ ### Unreleased Hermes MCP foundation
49
+
50
+ The QA candidate includes the Node-only `nayori-mcp` stdio binary for public testnet
51
+ reads and offline buyer/provider commitments. It has **no signer, broadcast, x402 purchase
52
+ or wallet-generation tools by default**. Optional operator-configured delegation to the separate
53
+ `nayori-custody` candidate enables bounded QA actions without putting a key in MCP. Neither
54
+ candidate is published. The reviewed QA source passed one funded, operator-supervised Hermes
55
+ buyer/provider lifecycle; npm-only installation, broader autonomy and x402 remain separate gates.
56
+ Provider operators may additionally enable public QA evaluation admission for the permitted,
57
+ confirmed submission; this does not enable x402 purchases or expose evaluator credentials. See
58
+ [MCP setup](docs/HERMES_MCP.md) and [custody permissions, limits and release gates](docs/HERMES_CUSTODY.md).
59
+ Role-specific recording/QA guides: [buyer](docs/HERMES_BUYER.md) and [provider](docs/HERMES_PROVIDER.md).
60
+ Evaluation admission has a bounded 45-second timeout; status reads retain 15 seconds.
61
+ Check evaluator capacity before submission and reconcile the existing evaluation ID after
62
+ an error. Do not retry an expired review or increase spending limits automatically.
63
+
64
+ ### Unreleased earned-service-fee integration
65
+
66
+ This branch adds opt-in STX v6/sBTC v5 support for a 2% **included**, evaluation-earned fee:
67
+ net provider payout on approval or net client refund on evaluated rejection. The full budget
68
+ stays in escrow until final settlement. The new methods are not in npm 0.7.1 and the candidate
69
+ contracts are not deployed defaults. Existing jobs remain unchanged. See the
70
+ [integration and accounting guide](docs/SERVICE_FEES.md) before enabling any signer.
71
+
72
+ ### Runtime
73
+
27
74
  - Node.js 20 or newer
28
75
  - npm 10 or newer
29
76
 
@@ -77,19 +124,20 @@ offline, contains no private key and does not request a wallet signature or broa
77
124
  npm run quickstart:mpp
78
125
  ```
79
126
 
80
- The transactional quickstart is also safe by default: it only prints a seven-step sBTC testnet
81
- lifecycle and its funding-policy decision.
127
+ The candidate transactional quickstart is safe by default: it prints an offline, role-separated
128
+ STX/sBTC testnet workflow with committed criteria, provider evidence and appeal-aware settlement.
82
129
 
83
130
  ```bash
84
131
  npm run quickstart:testnet
85
132
  ```
86
133
 
87
- Live testnet execution requires three distinct funded roles and the exact opt-in documented in
88
- [`examples/testnet.env.example`](examples/testnet.env.example). It confirms every transaction
89
- before moving to the next lifecycle step. The complete flow was verified on testnet with exact
90
- 100-satoshi escrow, provider payout, cleared escrow, and reputation update; the
91
- [completion transaction](https://explorer.hiro.so/txid/0x5cf34295641a9291a2b6785d6db95a5c56b4d3b40d4281c86da194acd4c64248?chain=testnet)
92
- is publicly inspectable.
134
+ Use the [role-separated QA guide](docs/TESTNET_QUICKSTART.md) and
135
+ [`examples/testnet.env.example`](examples/testnet.env.example). Each process uses only its own
136
+ client or provider signer; neither receives the evaluator key. One action runs per invocation.
137
+ An external journal preserves txids and refuses automatic retries after ambiguous signing.
138
+ The new bridge and quickstart are **unreleased QA candidates**, not a completed public Hermes
139
+ integration or a new on-chain E2E result. Existing historical completion receipts concern an
140
+ earlier workflow and do not verify these changes.
93
141
 
94
142
  ## Read on-chain state
95
143
 
@@ -141,7 +189,7 @@ more than the job requires.
141
189
 
142
190
  ## Active versioned escrow
143
191
 
144
- Version `0.7.0` selects Nayori's source-verified v4/v3/v3 generation by default on mainnet and
192
+ Version `0.7.1` selects Nayori's source-verified v5/v4/v3 generation by default on mainnet and
145
193
  testnet. A normal client therefore needs no contract override:
146
194
 
147
195
  ```ts
@@ -155,33 +203,35 @@ const perkos = new PerkOSClient({
155
203
  const job = await perkos.getJob("sbtc", 7n);
156
204
  console.log(job?.submittedAtBurn, job?.reviewDeadline, job?.status);
157
205
 
158
- // After the on-chain Bitcoin review deadline, any signer may trigger the
159
- // exact provider payout. The plan remains deny-mode with exact escrow outflow.
160
- await perkos.settleReviewTimeout("sbtc", 7n);
206
+ // The evaluator records an explainable decision without moving escrow.
207
+ await perkos.recordDecision({
208
+ asset: "sbtc",
209
+ jobId: 7n,
210
+ decision: "approve",
211
+ evidenceHash: evidenceSha256,
212
+ explanationHash: explanationSha256,
213
+ });
161
214
 
162
215
  const sync = await perkos.getReputationSync("sbtc", 7n);
163
216
  if (sync?.pending) await perkos.retryReputationSync("sbtc", 7n);
164
217
  ```
165
218
 
166
- The fixed 12-burn-block review window is readable through `getReviewWindow(asset)`.
167
- Evaluator completion/rejection is available through the exact deadline; timeout settlement is
168
- available only after it. Timeout payout is returned as the distinct `timeout-paid` (`u6`) status
169
- and must not be counted as a completed job or reputation success. A failed reputation write never
170
- rolls back economic settlement and can be retried permissionlessly.
219
+ The fixed 12-burn-block review window is readable through `getReviewWindow(asset)`. A decision
220
+ opens the appeal period: three burn blocks in isolated QA and 144 on mainnet. The affected client
221
+ or provider may appeal, a separately pinned human authority may resolve, and permissionless
222
+ finalizers preserve liveness after either deadline. A review timeout is returned as the distinct
223
+ `timeout-paid` (`u6`) status and must not be counted as a completed job or reputation success. A
224
+ failed reputation write never rolls back economic settlement and can be retried permissionlessly.
171
225
 
172
- ## Autonomous evaluator candidate
226
+ ## Autonomous evaluation and appeals
173
227
 
174
- The SDK supports the source-reviewed `agentic-commerce-v5` and `sbtc-commerce-v4` candidate
175
- through explicit contract overrides. This does not change the active deployment defaults:
228
+ The SDK uses the active source-reviewed `agentic-commerce-v5` and `sbtc-commerce-v4` deployment by
229
+ default:
176
230
 
177
231
  ```ts
178
232
  const autonomous = new PerkOSClient({
179
- network: "testnet",
233
+ network: "mainnet",
180
234
  signer,
181
- contracts: {
182
- stxCommerce: "ST...agentic-commerce-v5",
183
- sbtcCommerce: "ST...sbtc-commerce-v4",
184
- },
185
235
  });
186
236
 
187
237
  await autonomous.recordDecision({
@@ -213,8 +263,8 @@ pinned when the job was funded. Both the trait argument and exact fungible-token
213
263
  that historical token, so rotating the contract's future funding default cannot strand an existing
214
264
  escrow.
215
265
 
216
- The previous v3/v2 testnet generation remains supported through explicit overrides and immutable
217
- at 144 blocks. The active v4/v3/v3 generation first passed on Stacks testnet. Controlled STX and
266
+ The previous v4/v3 and v3/v2 generations remain supported through explicit same-network overrides.
267
+ The historical v4/v3/v3 generation first passed on Stacks testnet. Controlled STX and
218
268
  official PoX-5 sBTC complete paths pass 27/27 and 30/30. The real timeout path passes preparation
219
269
  20/20, settlement 12/12 and
220
270
  separate public-state verification 10/10. Job `u2` settled at burn `11290` in
@@ -223,11 +273,12 @@ ending in `timeout-paid` (`u6`) with zero escrow, one exact 1,000-atomic-unit sB
223
273
  completion, reputation or rating credit. The frozen evidence is documented in the
224
274
  [contracts/Web repository](https://github.com/PerkOS-xyz/PerkOS-Nayori/blob/main/docs/TESTNET_SECURITY_EVIDENCE.md).
225
275
 
226
- The same exact sources are live on mainnet under
227
- `SP2K7PV5NXBNRV510S6DCA6RFMTFHAF3ZPK6ZSXPH`. Deploy/configuration confirmed in blocks
228
- 8885885–8885898. A guarded internal mainnet sBTC job then passed 26/26 checks for exactly 100
229
- atomic units, ending in `completed` (`u3`) with escrow zero, exact payout, synchronized reputation
230
- and persisted rating. Those actors are team-operated release evidence, not external adoption,
276
+ The active v5/v4 sources are live on mainnet under
277
+ `SP2K7PV5NXBNRV510S6DCA6RFMTFHAF3ZPK6ZSXPH`. Deployment confirmed in blocks `8905872` and
278
+ `8905874`, with configuration through `8905886`. Guarded STX and canonical-sBTC appeal canaries
279
+ passed 47/47 and 50/50 checks, followed by an independent 75/75 public-state postcheck. Both jobs
280
+ settled exactly once with zero escrow and synchronized reputation. Those actors are team-operated
281
+ release evidence, not external adoption,
231
282
  non-team wallet activity or revenue. The independent external security review remains open.
232
283
 
233
284
  ## Browser signer (Leather and other Stacks wallets)
@@ -583,8 +634,8 @@ or facilitator-submitted signed transaction.
583
634
  - OAuth client secrets and access tokens are application secrets. The SDK returns them to the
584
635
  caller but does not persist, log or refresh them automatically.
585
636
 
586
- The independent external security review remains open. Do not treat this release candidate as
587
- audited software.
637
+ The independent external security review remains open. Do not treat this release as audited
638
+ software.
588
639
 
589
640
  See [Architecture](docs/ARCHITECTURE.md), [x402 payments](docs/X402_PAYMENTS.md),
590
641
  [MPP payments](docs/MPP_PAYMENTS.md), [Partner pilot](docs/PARTNER_PILOT.md) and
@@ -593,12 +644,15 @@ responsible disclosure process.
593
644
 
594
645
  ## QA promotion
595
646
 
596
- `main` is the canonical integration branch. The `Promote exact main commit to QA` workflow accepts
597
- only a full commit SHA already contained in `main`, requires a fast-forward from the current `qa`
598
- branch, reruns verification, package inspection and dependency audit, and then pushes that exact
599
- SHA without force. The protected branch must grant bypass only to the official GitHub Actions app
600
- before the first promotion; administrator enforcement must remain enabled. QA consumes an exact
601
- commit or tarball and never publishes npm as a side effect.
647
+ New integration work is reviewed in pull requests targeting `qa`; promotion to `main` is a
648
+ separate reviewed release after QA verification. QA consumes an exact commit or tarball and
649
+ never publishes npm as a side effect. Operator deployments are initiated from the approved Mac,
650
+ with image builds on the VPS; do not assume GitHub-hosted runners can reach that server.
651
+
652
+ The repository also retains the historical `Promote exact main commit to QA` workflow. It accepts
653
+ only a full SHA already in `main` and a fast-forward from `qa`. It is not the forward path for
654
+ new QA-only changes: do not force or rewind branches to make it run. Branch protection and
655
+ restricted GitHub Actions bypass remain required where that legacy workflow is used.
602
656
 
603
657
  ## Development
604
658
 
@@ -1,4 +1,4 @@
1
- import type { AppealDecisionInput, AssignProviderInput, ContractCallPlan, CreateJobInput, DecisionSettlementInput, FundJobInput, JobAmountInput, PaymentAsset, RateProviderInput, RecordDecisionInput, RegisterAgentInput, ResolvedPerkOSConfig, ResolveAppealPlanInput, SettleJobInput, SubmitWorkInput, UpdateAgentInput } from "./types.js";
1
+ import type { AppealDecisionInput, AssignProviderInput, ContractCallPlan, CreateJobInput, DecisionSettlementInput, FundJobInput, InitializeServiceFeeProtocolInput, RefundServiceFeePlanInput, WaiveServiceFeePlanInput, JobAmountInput, PaymentAsset, RateProviderInput, RecordDecisionInput, RegisterAgentInput, ResolvedPerkOSConfig, ResolveAppealPlanInput, SettleJobInput, SubmitWorkInput, UpdateAgentInput } from "./types.js";
2
2
  export declare class PerkOSTransactionBuilder {
3
3
  readonly config: ResolvedPerkOSConfig;
4
4
  constructor(config: ResolvedPerkOSConfig);
@@ -21,6 +21,10 @@ export declare class PerkOSTransactionBuilder {
21
21
  settleAppealTimeout(input: DecisionSettlementInput): ContractCallPlan;
22
22
  retryReputationSync(asset: PaymentAsset, jobIdInput: bigint | number | string): ContractCallPlan;
23
23
  rateProvider(input: RateProviderInput): ContractCallPlan;
24
+ initializeServiceFeeProtocol(input: InitializeServiceFeeProtocolInput): ContractCallPlan;
25
+ waiveServiceFee(input: WaiveServiceFeePlanInput): ContractCallPlan;
26
+ refundServiceFee(input: RefundServiceFeePlanInput): ContractCallPlan;
27
+ private requireServiceFees;
24
28
  private decisionSettlement;
25
29
  private settlement;
26
30
  }
@@ -1 +1 @@
1
- {"version":3,"file":"builders.d.ts","sourceRoot":"","sources":["../src/builders.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,mBAAmB,EACnB,mBAAmB,EACnB,gBAAgB,EAEhB,cAAc,EACd,uBAAuB,EACvB,YAAY,EACZ,cAAc,EAEd,YAAY,EACZ,iBAAiB,EACjB,mBAAmB,EACnB,kBAAkB,EAClB,oBAAoB,EACpB,sBAAsB,EACtB,cAAc,EACd,eAAe,EACf,gBAAgB,EACjB,MAAM,YAAY,CAAC;AAuFpB,qBAAa,wBAAwB;IACnC,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAC;gBAE1B,MAAM,EAAE,oBAAoB;IAIxC,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,gBAAgB;IA8B1D,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,gBAAgB;IAuBtD,eAAe,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,gBAAgB;IAWzE,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,gBAAgB;IAsBlD,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,gBAAgB;IAYlD,OAAO,CAAC,KAAK,EAAE,YAAY,GAAG,gBAAgB;IA2B9C,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,gBAAgB;IAiB5D,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,gBAAgB;IAWpD,WAAW,CAAC,KAAK,EAAE,cAAc,GAAG,gBAAgB;IAIpD,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,gBAAgB;IAIlD,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,gBAAgB;IAIlD,mBAAmB,CAAC,KAAK,EAAE,cAAc,GAAG,gBAAgB;IAI5D,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,gBAAgB;IAgB5D,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,gBAAgB;IAc5D,gBAAgB,CAAC,KAAK,EAAE,uBAAuB,GAAG,gBAAgB;IAMlE,aAAa,CAAC,KAAK,EAAE,sBAAsB,GAAG,gBAAgB;IAQ9D,mBAAmB,CAAC,KAAK,EAAE,uBAAuB,GAAG,gBAAgB;IAMrE,mBAAmB,CACjB,KAAK,EAAE,YAAY,EACnB,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GACnC,gBAAgB;IAWnB,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,gBAAgB;IAgBxD,OAAO,CAAC,kBAAkB;IAkC1B,OAAO,CAAC,UAAU;CAoCnB"}
1
+ {"version":3,"file":"builders.d.ts","sourceRoot":"","sources":["../src/builders.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EACV,mBAAmB,EACnB,mBAAmB,EACnB,gBAAgB,EAEhB,cAAc,EACd,uBAAuB,EACvB,YAAY,EACZ,iCAAiC,EACjC,yBAAyB,EACzB,wBAAwB,EACxB,cAAc,EAEd,YAAY,EACZ,iBAAiB,EACjB,mBAAmB,EACnB,kBAAkB,EAClB,oBAAoB,EACpB,sBAAsB,EACtB,cAAc,EACd,eAAe,EACf,gBAAgB,EACjB,MAAM,YAAY,CAAC;AAiGpB,qBAAa,wBAAwB;IACnC,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAC;gBAE1B,MAAM,EAAE,oBAAoB;IAIxC,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,gBAAgB;IAiC1D,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,gBAAgB;IA4BtD,eAAe,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,gBAAgB;IAWzE,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,gBAAgB;IA0BlD,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,gBAAgB;IAYlD,OAAO,CAAC,KAAK,EAAE,YAAY,GAAG,gBAAgB;IAkE9C,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,gBAAgB;IAiB5D,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,gBAAgB;IAsDpD,WAAW,CAAC,KAAK,EAAE,cAAc,GAAG,gBAAgB;IAIpD,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,gBAAgB;IAIlD,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,gBAAgB;IAIlD,mBAAmB,CAAC,KAAK,EAAE,cAAc,GAAG,gBAAgB;IAI5D,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,gBAAgB;IAgB5D,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,gBAAgB;IAW5D,gBAAgB,CAAC,KAAK,EAAE,uBAAuB,GAAG,gBAAgB;IAMlE,aAAa,CAAC,KAAK,EAAE,sBAAsB,GAAG,gBAAgB;IAQ9D,mBAAmB,CAAC,KAAK,EAAE,uBAAuB,GAAG,gBAAgB;IAMrE,mBAAmB,CACjB,KAAK,EAAE,YAAY,EACnB,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GACnC,gBAAgB;IAWnB,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,gBAAgB;IAgBxD,4BAA4B,CAC1B,KAAK,EAAE,iCAAiC,GACvC,gBAAgB;IAsCnB,eAAe,CAAC,KAAK,EAAE,wBAAwB,GAAG,gBAAgB;IAkBlE,gBAAgB,CAAC,KAAK,EAAE,yBAAyB,GAAG,gBAAgB;IAyCpE,OAAO,CAAC,kBAAkB;IAU1B,OAAO,CAAC,kBAAkB;IA0D1B,OAAO,CAAC,UAAU;CA6CnB"}
package/dist/builders.js CHANGED
@@ -1,8 +1,11 @@
1
1
  import { Cl, Pc } from "@stacks/transactions";
2
2
  import { PerkOSError } from "./errors.js";
3
+ import { quoteServiceFee, supportsServiceFees, validateServiceFeeSplit, } from "./service-fees.js";
3
4
  import { assertAscii, assertPrincipal, parseContractId, toHash32, toUint, } from "./validation.js";
4
5
  function commerceContract(config, asset) {
5
- return asset === "sbtc" ? config.contracts.sbtcCommerce : config.contracts.stxCommerce;
6
+ return asset === "sbtc"
7
+ ? config.contracts.sbtcCommerce
8
+ : config.contracts.stxCommerce;
6
9
  }
7
10
  function plan(config, contract, functionName, functionArgs, intent, postConditions = []) {
8
11
  return {
@@ -21,7 +24,9 @@ function tokenArg(config, tokenContract = config.contracts.sbtcToken) {
21
24
  return Cl.contractPrincipal(token.address, token.name);
22
25
  }
23
26
  function escrowArgs(config, asset, baseArgs, sbtcToken) {
24
- return asset === "sbtc" ? [...baseArgs, tokenArg(config, sbtcToken)] : baseArgs;
27
+ return asset === "sbtc"
28
+ ? [...baseArgs, tokenArg(config, sbtcToken)]
29
+ : baseArgs;
25
30
  }
26
31
  function exactTransfer(config, asset, sender, amount, sbtcToken) {
27
32
  if (asset === "stx") {
@@ -85,11 +90,15 @@ export class PerkOSTransactionBuilder {
85
90
  }
86
91
  return plan(this.config, this.config.contracts.agentRegistry, "update-agent", [
87
92
  Cl.uint(agentId),
88
- input.name === undefined ? Cl.none() : Cl.some(Cl.stringAscii(input.name)),
93
+ input.name === undefined
94
+ ? Cl.none()
95
+ : Cl.some(Cl.stringAscii(input.name)),
89
96
  input.description === undefined
90
97
  ? Cl.none()
91
98
  : Cl.some(Cl.stringAscii(input.description)),
92
- input.wallet === undefined ? Cl.none() : Cl.some(Cl.principal(input.wallet)),
99
+ input.wallet === undefined
100
+ ? Cl.none()
101
+ : Cl.some(Cl.principal(input.wallet)),
93
102
  ], { operation: "update-agent" });
94
103
  }
95
104
  deactivateAgent(agentIdInput) {
@@ -125,12 +134,40 @@ export class PerkOSTransactionBuilder {
125
134
  }
126
135
  assertPrincipal(input.sender, "sender", this.config.network);
127
136
  const contract = commerceContract(this.config, input.asset);
137
+ if (supportsServiceFees(contract, input.asset)) {
138
+ const acceptance = input.serviceFeeAcceptance;
139
+ if (!acceptance ||
140
+ acceptance.gross !== amount ||
141
+ acceptance.basisPoints !== 200 ||
142
+ acceptance.rejectionRefund !== "net-after-evaluation") {
143
+ throw new PerkOSError("INPUT_INVALID", "Explicit 2% earned-fee acceptance, including net rejection refund, is required.");
144
+ }
145
+ validateServiceFeeSplit({
146
+ ...quoteServiceFee(amount),
147
+ treasury: acceptance.treasury,
148
+ waived: false,
149
+ }, amount, this.config.network);
150
+ if (acceptance.treasury === input.sender)
151
+ throw new PerkOSError("INPUT_INVALID", "Treasury cannot fund a job.");
152
+ }
153
+ else if (input.serviceFeeAcceptance) {
154
+ throw new PerkOSError("INPUT_INVALID", "This contract generation has no earned service fee.");
155
+ }
128
156
  return plan(this.config, contract, "fund-job", escrowArgs(this.config, input.asset, [Cl.uint(jobId)]), {
129
157
  operation: "fund-job",
130
158
  asset: input.asset,
131
159
  amount,
132
160
  jobId,
133
161
  sender: input.sender,
162
+ ...(input.serviceFeeAcceptance
163
+ ? {
164
+ serviceFee: {
165
+ ...quoteServiceFee(amount),
166
+ treasury: input.serviceFeeAcceptance.treasury,
167
+ waived: false,
168
+ },
169
+ }
170
+ : {}),
134
171
  }, [exactTransfer(this.config, input.asset, input.sender, amount)]);
135
172
  }
136
173
  assignProvider(input) {
@@ -145,7 +182,36 @@ export class PerkOSTransactionBuilder {
145
182
  }
146
183
  submitWork(input) {
147
184
  const jobId = toUint(input.jobId, "jobId");
148
- return plan(this.config, commerceContract(this.config, input.asset), "submit-work", [Cl.uint(jobId), Cl.buffer(deliverableBuffer(input.deliverable))], { operation: "submit-work", asset: input.asset, jobId });
185
+ const acceptance = input.serviceFeeAcceptance;
186
+ const feeContract = supportsServiceFees(commerceContract(this.config, input.asset), input.asset);
187
+ if (feeContract) {
188
+ if (!acceptance ||
189
+ acceptance.gross <= 0n ||
190
+ acceptance.basisPoints !== 200 ||
191
+ acceptance.rejectionRefund !== "net-after-evaluation")
192
+ throw new PerkOSError("INPUT_INVALID", "Provider must accept the gross budget and earned fee before submission.");
193
+ validateServiceFeeSplit({
194
+ ...quoteServiceFee(acceptance.gross),
195
+ treasury: acceptance.treasury,
196
+ waived: false,
197
+ }, acceptance.gross, this.config.network);
198
+ }
199
+ else if (acceptance)
200
+ throw new PerkOSError("INPUT_INVALID", "Unexpected service fee acceptance for this generation.");
201
+ return plan(this.config, commerceContract(this.config, input.asset), "submit-work", [Cl.uint(jobId), Cl.buffer(deliverableBuffer(input.deliverable))], {
202
+ operation: "submit-work",
203
+ asset: input.asset,
204
+ jobId,
205
+ ...(acceptance
206
+ ? {
207
+ serviceFee: {
208
+ ...quoteServiceFee(acceptance.gross),
209
+ treasury: acceptance.treasury,
210
+ waived: false,
211
+ },
212
+ }
213
+ : {}),
214
+ });
149
215
  }
150
216
  completeJob(input) {
151
217
  return this.settlement("complete-job", input);
@@ -170,10 +236,7 @@ export class PerkOSTransactionBuilder {
170
236
  }
171
237
  appealDecision(input) {
172
238
  const jobId = toUint(input.jobId, "jobId");
173
- return plan(this.config, commerceContract(this.config, input.asset), "appeal-decision", [
174
- Cl.uint(jobId),
175
- Cl.buffer(toHash32(input.evidenceHash, "evidenceHash")),
176
- ], { operation: "appeal-decision", asset: input.asset, jobId });
239
+ return plan(this.config, commerceContract(this.config, input.asset), "appeal-decision", [Cl.uint(jobId), Cl.buffer(toHash32(input.evidenceHash, "evidenceHash"))], { operation: "appeal-decision", asset: input.asset, jobId });
177
240
  }
178
241
  finalizeDecision(input) {
179
242
  return this.decisionSettlement("finalize-decision", input, [
@@ -205,13 +268,89 @@ export class PerkOSTransactionBuilder {
205
268
  assertAscii(input.comment, "comment", 256, true);
206
269
  return plan(this.config, commerceContract(this.config, input.asset), "rate-provider", [Cl.uint(jobId), Cl.uint(score), Cl.stringAscii(input.comment)], { operation: "rate-provider", asset: input.asset, jobId });
207
270
  }
271
+ initializeServiceFeeProtocol(input) {
272
+ const contract = this.requireServiceFees(input.asset);
273
+ const appealWindow = toUint(input.appealWindow, "appealWindow");
274
+ const expectedWindow = this.config.network === "mainnet" ? 144n : 3n;
275
+ for (const [field, value] of Object.entries({
276
+ owner: input.owner,
277
+ treasury: input.treasury,
278
+ appealAuthority: input.appealAuthority,
279
+ })) {
280
+ assertPrincipal(value, field, this.config.network);
281
+ }
282
+ if (appealWindow !== expectedWindow ||
283
+ input.treasury === input.owner ||
284
+ input.treasury === input.appealAuthority) {
285
+ throw new PerkOSError("INPUT_INVALID", "Initialization requires a separate treasury and the network-specific appeal window (testnet 3, mainnet 144).");
286
+ }
287
+ return plan(this.config, contract, "initialize-protocol", [
288
+ Cl.uint(appealWindow),
289
+ Cl.principal(input.appealAuthority),
290
+ Cl.principal(input.treasury),
291
+ ], {
292
+ operation: "initialize-protocol",
293
+ asset: input.asset,
294
+ sender: input.owner,
295
+ });
296
+ }
297
+ waiveServiceFee(input) {
298
+ const contract = this.requireServiceFees(input.asset);
299
+ const jobId = toUint(input.jobId, "jobId");
300
+ assertPrincipal(input.authority, "authority", this.config.network);
301
+ return plan(this.config, contract, "waive-service-fee", [Cl.uint(jobId), Cl.buffer(toHash32(input.evidenceHash, "evidenceHash"))], {
302
+ operation: "waive-service-fee",
303
+ asset: input.asset,
304
+ jobId,
305
+ sender: input.authority,
306
+ });
307
+ }
308
+ refundServiceFee(input) {
309
+ const contract = this.requireServiceFees(input.asset);
310
+ const jobId = toUint(input.jobId, "jobId");
311
+ const amount = toUint(input.amount, "amount");
312
+ assertPrincipal(input.treasury, "treasury", this.config.network);
313
+ assertPrincipal(input.recipient, "recipient", this.config.network);
314
+ if (input.treasury === input.recipient)
315
+ throw new PerkOSError("INPUT_INVALID", "Refund recipient cannot be treasury.");
316
+ if (input.asset === "sbtc" && !input.sbtcToken)
317
+ throw new PerkOSError("INPUT_INVALID", "Refund requires the job-pinned sBTC token.");
318
+ return plan(this.config, contract, "refund-service-fee", escrowArgs(this.config, input.asset, [Cl.uint(jobId)], input.sbtcToken), {
319
+ operation: "refund-service-fee",
320
+ asset: input.asset,
321
+ jobId,
322
+ amount,
323
+ sender: input.treasury,
324
+ recipient: input.recipient,
325
+ }, [
326
+ exactTransfer(this.config, input.asset, input.treasury, amount, input.sbtcToken),
327
+ ]);
328
+ }
329
+ requireServiceFees(asset) {
330
+ const contract = commerceContract(this.config, asset);
331
+ if (!supportsServiceFees(contract, asset))
332
+ throw new PerkOSError("INPUT_INVALID", "Operation requires STX v6 or sBTC v5 explicitly configured.");
333
+ return contract;
334
+ }
208
335
  decisionSettlement(operation, input, functionArgs) {
209
336
  const jobId = toUint(input.jobId, "jobId");
210
337
  const amount = toUint(input.amount, "amount", true);
211
338
  assertPrincipal(input.recipient, "recipient", this.config.network);
212
339
  const contract = commerceContract(this.config, input.asset);
340
+ if (supportsServiceFees(contract, input.asset)) {
341
+ if (!input.serviceFee || amount === 0n)
342
+ throw new PerkOSError("INPUT_INVALID", "Fee settlement requires a verified split and positive gross escrow.");
343
+ validateServiceFeeSplit(input.serviceFee, amount, this.config.network);
344
+ if (input.serviceFee.treasury === input.recipient)
345
+ throw new PerkOSError("INPUT_INVALID", "Economic recipient cannot be treasury.");
346
+ if (input.asset === "sbtc" && !input.sbtcToken)
347
+ throw new PerkOSError("INPUT_INVALID", "Settlement requires the job-pinned sBTC token.");
348
+ }
349
+ else if (input.serviceFee) {
350
+ throw new PerkOSError("INPUT_INVALID", "Unexpected fee metadata for a no-fee contract.");
351
+ }
213
352
  const sbtcToken = input.asset === "sbtc"
214
- ? input.sbtcToken ?? this.config.contracts.sbtcToken
353
+ ? (input.sbtcToken ?? this.config.contracts.sbtcToken)
215
354
  : undefined;
216
355
  if (sbtcToken)
217
356
  parseContractId(sbtcToken, "sbtcToken", this.config.network);
@@ -224,6 +363,7 @@ export class PerkOSTransactionBuilder {
224
363
  amount,
225
364
  jobId,
226
365
  recipient: input.recipient,
366
+ ...(input.serviceFee ? { serviceFee: input.serviceFee } : {}),
227
367
  }, postConditions);
228
368
  }
229
369
  settlement(operation, input) {
@@ -231,8 +371,12 @@ export class PerkOSTransactionBuilder {
231
371
  const amount = toUint(input.amount, "amount", true);
232
372
  assertPrincipal(input.recipient, "recipient", this.config.network);
233
373
  const contract = commerceContract(this.config, input.asset);
374
+ if (supportsServiceFees(contract, input.asset) &&
375
+ (operation === "complete-job" || operation === "reject-job")) {
376
+ throw new PerkOSError("INPUT_INVALID", "Use recordDecision and the autonomous decision settlement flow for this generation.");
377
+ }
234
378
  const sbtcToken = input.asset === "sbtc"
235
- ? input.sbtcToken ?? this.config.contracts.sbtcToken
379
+ ? (input.sbtcToken ?? this.config.contracts.sbtcToken)
236
380
  : undefined;
237
381
  if (sbtcToken)
238
382
  parseContractId(sbtcToken, "sbtcToken", this.config.network);