@allorc/a423-sdk 0.2.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 CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.0
4
+
5
+ - Add optional ethers-based `/escrow` entry point and a compiled ERC-20 reference contract.
6
+ - Enforce EIP-712 worker consent, exact custody amounts, one immutable submission, verifier approval before release, expiry refunds, disputes, arbitrated splits and agreed timeout allocation.
7
+ - Add pre-broadcast signed-transaction journaling, coordinated nonce allocation, canonical receipt/term/event validation and external transaction evidence adoption.
8
+ - Ship escrow-specific transitions and documentation. Contract is experimental, unaudited and not deployed by the package.
9
+ - Add local-EVM lifecycle, failure, recovery and confirmation tests.
10
+
3
11
  ## 0.2.0
4
12
 
5
13
  - Add opt-in `@allorc/a423-sdk/workflow` exports: workflow engine, Next.js-compatible handler, typed HTTP client and canonical agreement hashing.
package/ESCROW.md ADDED
@@ -0,0 +1,161 @@
1
+ # Reference on-chain escrow (0.3.0)
2
+
3
+ This opt-in module implements a real ERC-20 escrow path. The generic workflow remains provider-independent. It includes Solidity source, reproducibly compiled ABI/bytecode, an ethers v6 adapter and local-EVM tests. It is experimental and has not received an independent security audit; deploy to a local chain/testnet for evaluation. No contract is deployed automatically and no funds move merely by installing the package.
4
+
5
+ ## Installation and deployment
6
+
7
+ ```sh
8
+ npm install @allorc/a423-sdk@0.3.0 ethers@^6.15.0
9
+ ```
10
+
11
+ ```ts
12
+ import { ContractFactory } from 'ethers';
13
+ import { ESCROW_ABI, ESCROW_BYTECODE } from '@allorc/a423-sdk/escrow';
14
+ const contract = await new ContractFactory(ESCROW_ABI, ESCROW_BYTECODE, deploymentSigner)
15
+ .deploy(standardTokenAddress);
16
+ await contract.waitForDeployment();
17
+ ```
18
+
19
+ Deployment is an explicit, separately authorised transaction. Each deployment supports one immutable token. There are no owner withdrawals, token replacements, upgrades or pause switches. Standard ERC-20 transfers must preserve exact sender/recipient amounts. Fee-on-transfer/rebasing/blacklisting/pausable tokens are unsupported; an issuer blocking transfers can defeat timeout liveness. Arbitrary token code cannot be made safe by SDK validation. Use a reviewed token and verify the deployed contract bytecode/configuration before trusting it. Forced token transfers do not create job credits; there is no recovery sweep for unsolicited deposits.
20
+
21
+ ## Exact agreement recipe
22
+
23
+ Include every application-specific condition in JSON terms. Include this `escrow` object with these field names. IDs, application identities and wallet addresses are separate concepts; use a trusted identity-to-wallet registry.
24
+
25
+ ```js
26
+ const terms = {
27
+ task: { description: 'Your task', acceptance: { /* your criteria */ } },
28
+ escrow: {
29
+ chain_id: '31337',
30
+ contract: escrowAddress,
31
+ token: tokenAddress,
32
+ amount: '100', // raw integer token units, NOT necessarily 100 displayed tokens
33
+ payer: payerAddress,
34
+ worker: workerAddress,
35
+ verifier: verifierAddress,
36
+ arbitrator: arbitratorAddress,
37
+ work_deadline: workDeadlineSeconds,
38
+ review_deadline: reviewDeadlineSeconds,
39
+ resolution_deadline: resolutionDeadlineSeconds,
40
+ timeout_worker_bps: 2500, // agreed fallback: 25% worker, 75% payer
41
+ },
42
+ };
43
+ ```
44
+
45
+ All deadlines are absolute Unix seconds and strictly increasing. The work deadline must be in the future at funding. `amount` is a positive uint256 decimal string. The SHA-256 hash of the entire canonical terms object is stored on-chain; it includes payment rules and the task criteria. The Solidity EIP-712 terms digest is a separate encoding for worker consent. `jobId` is SHA-256 of UTF-8 `a423:escrow:v1:` plus the application job ID. One job ID may only be funded once per deployed contract.
46
+
47
+ The arbitrator must differ from payer and worker. The verifier may be the payer: this is a trusted verifier policy, not trustless proof of output quality. The reference contract supports EOA EIP-712 worker signatures using ECDSA; ERC-1271 smart account worker signatures are not implemented. Use another adapter/contract for that capability.
48
+
49
+ ## Connect Next.js and the workflow
50
+
51
+ ```ts
52
+ import { createWorkflow, createWorkflowHandler } from '@allorc/a423-sdk/workflow';
53
+ import { createEscrowAdapters, escrowTransitions } from '@allorc/a423-sdk/escrow';
54
+
55
+ const adapters = createEscrowAdapters({
56
+ provider,
57
+ chainId,
58
+ contractAddress: escrowAddress,
59
+ tokenAddress,
60
+ confirmations: requiredConfirmations,
61
+ transactions: durableTransactionStore,
62
+ signerFor: async actor => signerRegistry.getSigner(actor),
63
+ addressFor: async actor => signerRegistry.getAddress(actor),
64
+ validateArtifact: async ({ job, resultHash }) => {
65
+ // Fetch exact bytes, check digest and evaluate accepted criteria.
66
+ // Return true only after all checks pass.
67
+ return artifactService.validate(job, resultHash);
68
+ },
69
+ });
70
+ const workflow = createWorkflow({
71
+ store: durableWorkflowStore,
72
+ adapters: { ...adapters, expire: applicationExpiryAdapter },
73
+ transitions: escrowTransitions,
74
+ validateTerms: applicationTermsValidator,
75
+ authorize: applicationPolicy,
76
+ });
77
+ const handler = createWorkflowHandler({ workflow, authenticate: authenticateRequest });
78
+ // In app/api/a423/[...path]/route.ts:
79
+ export const runtime = 'nodejs';
80
+ export { handler as GET, handler as POST };
81
+ ```
82
+
83
+ All application services in this example must be supplied. Configure explicit `arbitrator` alongside requester, worker and verifier in job participants. `addressFor` must map all four authenticated IDs to the agreed addresses. `signerFor` must reject unknown actors and apply spending/delegation approvals. The adapter uses offline `signTransaction`, so custodial/local/KMS signers must support it. It does not prompt a browser wallet or bypass human approval. Do not use keys in client React code.
84
+
85
+ ## Acceptance is not locking
86
+
87
+ 1. Create a job, quote terms, and record both actors accepting the same revision/hash using the workflow API.
88
+ 2. Obtain a worker signature over the final on-chain terms:
89
+
90
+ ```ts
91
+ import { workerConsent } from '@allorc/a423-sdk/escrow';
92
+ const consent = workerConsent(acceptedJob, { chainId, contractAddress: escrowAddress, tokenAddress });
93
+ const signature = await workerSigner.signTypedData(consent.domain, consent.types, consent.value);
94
+ ```
95
+
96
+ The worker should inspect the terms it signs. EIP-712 binds consent to chain, contract, job, parties, amount, deadlines, timeout allocation and the whole agreement hash. The payer authorises the actual funding transaction.
97
+ 3. Call `workflow.act(id, 'fund', { worker_signature: signature }, { actor: requesterId, idempotencyKey: 'fund_1' })`.
98
+ 4. The adapter grants token allowance if needed (zero reset first when necessary), then calls the reference contract's `fund`. **Approval alone does not lock money.** The contract checks the worker signature, transfers the exact amount into custody, stores all terms and emits `Funded` atomically.
99
+ 5. Pending results keep the workflow `accepted` with a `pending` operation. Display “funding pending”, not “locked”. Retry the same action/key to progress approval/funding. Only successful, sufficiently confirmed, canonically included receipts plus matching event and on-chain terms produce `funded`.
100
+ 6. Start work via `start`; it rereads the actual escrow and deadline. The SDK does not run the worker. Dispatch through a durable queue only after this gate succeeds.
101
+
102
+ To automatically initiate funding after the second acceptance, orchestrate the above from your application after obtaining payer authorisation. Off-chain acceptance cannot force a wallet to sign. Do not change the UI to “funded” at acceptance time.
103
+
104
+ ## Full lifecycle and exact inputs
105
+
106
+ | Action | Input | On-chain restriction / result |
107
+ |---|---|---|
108
+ | fund | `{ worker_signature }` | Payer only; valid worker consent; exact token transfer; new job ID |
109
+ | start | `{}` | Adapter checks all terms, funded state and unexpired work deadline; no transaction |
110
+ | submit | `{ result_hash: '0x…64 hex chars' }` | Worker only; before work deadline; nonzero artifact commitment |
111
+ | verify | `{}` | Callback validates actual artifact; verifier transaction must match submitted digest and review deadline |
112
+ | release | `{}` | Verifier only, verified state, before resolution deadline; full worker payout |
113
+ | refund | `{}` | Anyone on-chain after work deadline, only if no submission; full payer refund |
114
+ | dispute | `{ reason_hash: '0x…64 hex chars' }` | Party only, submitted/verified and before resolution deadline; blocks ordinary release |
115
+ | resolve | `{ worker_amount: '40' }` | Arbitrator only, disputed, by resolution deadline; remainder refunded to payer |
116
+ | timeout | `{}` | Funded/running: expiry refund. Submitted/verified/disputed: agreed fallback allocation after resolution deadline |
117
+
118
+ `escrowTransitions` makes dispute an actual on-chain operation, gives resolve to the arbitrator and adds timeout. Resolution and timeout end in workflow `settled`; inspect evidence `worker_paid`, `amount` and contract state to distinguish paid/refunded/split. Contract settlement states: 5 Paid, 6 Refunded, 7 Split. Event reasons: 0 release, 1 expired-unsubmitted refund, 2 arbitration, 3 submitted timeout.
119
+
120
+ A verifier can approve and release before a dispute is opened; there is no guaranteed challenge window or objective quality oracle. Rejected artifact verification leaves the workflow submitted and clears pending, allowing a dispute. The contract enforces that submission and verifier approval exist; the application verifier still judges task quality. For resubmission/milestones, use a separate contract policy: the reference contract permits one immutable submission and one settlement, not partial milestone releases. Arbitration can split the one escrow.
121
+
122
+ Cancellation and quote expiry before funding remain workflow/application policies and move no tokens. The reference module does not supply a scheduler or a governor registry.
123
+
124
+ ## Recovery and journal requirements
125
+
126
+ The workflow job is written before any effect. The transaction journal separately stores signed raw transactions **before broadcasting**, including hash, destination, calldata and signer. Retries reuse the winning journal record and rebroadcast the same bytes; they do not create another payment or nonce. Do not expose raw signed transactions in public workflow metadata/receipts. Treat the journal as protected operational storage.
127
+
128
+ Implement `TransactionStore`:
129
+
130
+ - `get(id)`: retrieve a durable record.
131
+ - `create(id, record)`: atomic insert-if-absent; return boolean. Never overwrite an existing signed transaction.
132
+ - `reserveNonce(scope, minimum, operationId)`: transactionally allocate per-chain/address nonces, at least the RPC pending nonce; the same operation must always retrieve its original reservation. Every process using the account must coordinate through this allocator. External wallet activity requires an explicit operational policy.
133
+
134
+ `createMemoryTransactionStore()` is only for tests. Production needs a real database implementation. A crash after nonce reservation but before journaling must resume that operation to fill its reserved nonce; later transactions may remain blocked behind it. No automatic fee replacement/cancellation is included. A genuinely stuck transaction requires deliberate recovery with the same nonce and updated journal lineage; do not blindly assign a new nonce. Prefer dedicated signer accounts to uncontrolled mixed wallet usage.
135
+
136
+ `reconcile` only observes existing journal entries and never signs/broadcasts. If an approval is confirmed but the funding transaction has not yet been created, reconcile returns pending; retry the original fund action/key to authorise progression. After a signed transaction is journaled but not broadcast, execute retries rebroadcast the identical bytes. After broadcast/mining but before the workflow write, reconcile verifies and adopts its evidence. Explicit `rejected` results permit a deliberate new attempt with a new action key; an unknown network outcome stays pending or throws with the pending record retained.
137
+
138
+ For externally submitted transactions, pass `transaction_hash` in the action input from the beginning (in addition to the normal fields). The adapter observes it without broadcasting and checks the configured contract, exact agreement, action event and allocation. This supports recording permissionless refunds/timeouts submitted by a keeper or another account. Use the correct workflow action; it cannot adopt arbitrary unrelated transfers. Supplying an incorrect hash fails closed and leaves pending for investigation; do not change persisted input to hide errors.
139
+
140
+ RPC chain ID and immutable token are checked; event emitter, job ID, agreement hash, all stored terms, event amounts and results are verified. Evidence contains transaction/block hash and required confirmation depth. Readers need historical state access for the receipt block. The adapter checks current canonical inclusion at observation time; confirmations are a configurable risk policy, not an absolute finality guarantee. There is no continuous reorg watcher or automatic rollback of already completed workflow states. A production chain integration must monitor disappearing evidence and suspend/reconcile affected work explicitly. Chain outages preserve uncertainty and cannot be repaired by SDK code.
141
+
142
+ No timeout is automatic: a keeper or participant must submit a transaction, and the chain must process it. Deadlines provide an eligible escape path under the agreed allocation; they do not guarantee execution during censorship, chain failure or broken token transfers.
143
+
144
+ ## Verification and receipts
145
+
146
+ Artifact input is a bytes32 commitment. The application chooses the hashing scheme and storage format; for SHA-256, hash the exact delivered bytes, not a mutable URL. The verifier callback must check both bytes and accepted criteria. There is no generic LLM quality proof.
147
+
148
+ Funding, submission, verification and settlement effects return JSON-safe chain evidence. Workflow receipts retain those observations; they are not live PDFs or perpetual assertions of finality. Fresh PDF/evidence services should recheck canonical receipts and commitments at read time and format raw units with the actual token decimals.
149
+
150
+ ## Development checks
151
+
152
+ From the source repository:
153
+
154
+ ```sh
155
+ npm ci --ignore-scripts
156
+ npm run build:escrow
157
+ npm test
158
+ npm pack --dry-run --ignore-scripts
159
+ ```
160
+
161
+ The local-EVM suite exercises custody, consent binding, permissions, submission-before-release, expiry refund, arbitration splits, timeout allocation, unsupported token rejection, recovery, external evidence adoption and confirmation depth. Solidity compiler and OpenZeppelin versions are pinned in dev dependencies; ethers is an optional peer used only by `/escrow`. Core/workflow do not load it.
package/INTEGRATION.md CHANGED
@@ -3,11 +3,17 @@
3
3
  Install after the maintainer publishes:
4
4
 
5
5
  ```sh
6
- npm install @allorc/a423-sdk@0.2.0
6
+ npm install @allorc/a423-sdk@0.3.0
7
7
  ```
8
8
 
9
9
  For testing before publication, install the maintainer's `npm pack` tarball instead. Do not silently install 0.1 and assume the workflow entry point exists.
10
10
 
11
+ ## Reference escrow option
12
+
13
+ Read `ESCROW.md` first. The package now includes a concrete `/escrow` adapter plus `ESCROW_ABI`/`ESCROW_BYTECODE` and Solidity source. This contract differs from the old MVP contract: it requires EIP-712 worker consent, verifier approval, explicit arbitration and timeout terms. Do not point this adapter at the old contract. Use a fresh local/testnet deployment for evaluation or write a separately tested legacy adapter.
14
+
15
+ Use `createEscrowAdapters` and `escrowTransitions` when testing the reference path. Implement both the durable workflow store and protected transaction journal with atomic nonce reservation, map all four roles to trusted wallets, and supply artifact validation. Retain the reference lifecycle tests below. Deployment or real fund movement requires separate authorisation.
16
+
11
17
  ## Task
12
18
 
13
19
  Integrate the opt-in `@allorc/a423-sdk/workflow` API into the existing MVP. Inspect the installed README, declarations and exports first. Preserve the existing app until a complete equivalent journey passes. Do not introduce demo keys, positional signer lookup, unknown-agent fallback, or authentication based only on x-agent-id. Do not deploy or move real funds as part of integration tests.
package/NOTICE ADDED
@@ -0,0 +1,24 @@
1
+ The compiled A423Escrow artifact incorporates OpenZeppelin Contracts 5.4.0.
2
+
3
+ The MIT License (MIT)
4
+
5
+ Copyright (c) 2016-2025 Zeppelin Group Ltd
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining
8
+ a copy of this software and associated documentation files (the
9
+ "Software"), to deal in the Software without restriction, including
10
+ without limitation the rights to use, copy, modify, merge, publish,
11
+ distribute, sublicense, and/or sell copies of the Software, and to
12
+ permit persons to whom the Software is furnished to do so, subject to
13
+ the following conditions:
14
+
15
+ The above copyright notice and this permission notice shall be included
16
+ in all copies or substantial portions of the Software.
17
+
18
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
19
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
21
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
22
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
23
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
24
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,9 @@
1
+ # Reference escrow in 0.3.0
2
+
3
+ The optional `@allorc/a423-sdk/escrow` module now provides a deployable ERC-20 reference contract and matching ethers adapter. It implements actual on-chain custody, worker-signed funding consent, submission and verifier approval before release, expiry refunds, disputes, arbitration splits and an agreed deadline fallback. It does not deploy or move funds automatically.
4
+
5
+ See [ESCROW.md](./ESCROW.md) for exact terms, Next.js setup, wallet authorisation, transaction journal requirements, lifecycle inputs, recovery and limitations. Use `escrowTransitions` with this adapter: the default generic dispute transition alone does not write to the chain. Core/workflow APIs remain available without ethers.
6
+
1
7
  # Configurable workflows in 0.2.0
2
8
 
3
9
  The new `@allorc/a423-sdk/workflow` entry point provides middleware for agent job lifecycles. It supports different task types and payment systems through application-supplied policies and adapters. It does not assume a particular blockchain, token, wallet, artifact format, database or AI framework. This is experimental software with a reference lifecycle; it does not promise every possible business workflow.
package/SECURITY.md CHANGED
@@ -7,3 +7,8 @@ Authentication must be supplied. Never treat a public ID, public ingredients or
7
7
  Settlement adapters must durably deduplicate the SDK's stable key across retries and concurrent calls. This SDK does not fund escrow, establish mandates, custody keys or guarantee exactly-once payments. Validate recipient, amount, permission and provider status independently. Completion without an adapter explicitly means no settlement is configured.
8
8
 
9
9
  Do not include tokens, private keys, customer data or live exploit details in public reports. Use a private repository reporting channel when configured; maintainers should establish one before requesting sensitive reports. Never log credentials. No private security contact is invented by this initial package.
10
+
11
+
12
+ ## Reference escrow
13
+
14
+ The 0.3 reference contract is experimental and not independently audited. Its trust model includes the chosen verifier/arbitrator and the timeout allocation both parties accepted. EOA signatures only; standard exact-transfer tokens only. Read ESCROW.md before deployment. Never expose raw signed transaction journals, use memory stores for production custody, or share signer nonces across uncoordinated processes. Confirmation policy is not continuous reorg monitoring.
@@ -0,0 +1,131 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.24;
3
+
4
+ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
5
+ import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
6
+ import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
7
+ import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
8
+ import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
9
+
10
+ /// @notice Experimental reference escrow for standard, non-rebasing ERC20 tokens.
11
+ /// No admin withdrawals/upgrades. Arbitration and timeout allocation are agreed before funding.
12
+ contract A423Escrow is ReentrancyGuard, EIP712 {
13
+ using SafeERC20 for IERC20;
14
+ enum State { None, Funded, Submitted, Verified, Disputed, Paid, Refunded, Split }
15
+ struct Terms {
16
+ bytes32 jobId;
17
+ address payer;
18
+ address worker;
19
+ address verifier;
20
+ address arbitrator;
21
+ uint256 amount;
22
+ uint64 workDeadline;
23
+ uint64 reviewDeadline;
24
+ uint64 resolutionDeadline;
25
+ uint16 timeoutWorkerBps;
26
+ bytes32 agreementHash;
27
+ }
28
+ struct Job { Terms terms; State state; bytes32 resultHash; uint256 workerPaid; }
29
+ IERC20 public immutable token;
30
+ mapping(bytes32 => Job) private jobs;
31
+ bytes32 public constant TERMS_TYPEHASH = keccak256("Terms(bytes32 jobId,address payer,address worker,address verifier,address arbitrator,uint256 amount,uint64 workDeadline,uint64 reviewDeadline,uint64 resolutionDeadline,uint16 timeoutWorkerBps,bytes32 agreementHash)");
32
+ event Funded(bytes32 indexed jobId, bytes32 indexed agreementHash, address indexed payer, address worker, uint256 amount);
33
+ event Submitted(bytes32 indexed jobId, bytes32 resultHash);
34
+ event Verified(bytes32 indexed jobId, bytes32 resultHash);
35
+ event Disputed(bytes32 indexed jobId, bytes32 reasonHash);
36
+ event Settled(bytes32 indexed jobId, uint256 workerAmount, uint256 payerAmount, uint8 reason);
37
+ error InvalidTerms();
38
+ error WrongState();
39
+ error Unauthorized();
40
+ error Deadline();
41
+ error TokenAmountMismatch();
42
+
43
+ constructor(address token_) EIP712("A423Escrow", "1") {
44
+ if (token_.code.length == 0) revert InvalidTerms();
45
+ token = IERC20(token_);
46
+ }
47
+ function termsDigest(Terms calldata t) public view returns (bytes32) {
48
+ return _hashTypedDataV4(keccak256(abi.encode(TERMS_TYPEHASH, t)));
49
+ }
50
+ function getJob(bytes32 id) external view returns (Job memory) { return jobs[id]; }
51
+ /// Payer transaction plus worker EIP712 signature establish bilateral on-chain consent.
52
+ function fund(Terms calldata t, bytes calldata workerSignature) external nonReentrant {
53
+ if (msg.sender != t.payer) revert Unauthorized();
54
+ if (jobs[t.jobId].state != State.None) revert WrongState();
55
+ if (t.jobId == bytes32(0) || t.agreementHash == bytes32(0) || t.amount == 0 || t.worker == address(0) || t.worker == t.payer || t.verifier == address(0) || t.arbitrator == address(0) || t.arbitrator == t.payer || t.arbitrator == t.worker || t.timeoutWorkerBps > 10000) revert InvalidTerms();
56
+ if (t.workDeadline <= block.timestamp || t.reviewDeadline <= t.workDeadline || t.resolutionDeadline <= t.reviewDeadline) revert InvalidTerms();
57
+ if (ECDSA.recover(termsDigest(t), workerSignature) != t.worker) revert Unauthorized();
58
+ jobs[t.jobId] = Job(t, State.Funded, bytes32(0), 0);
59
+ uint256 beforeBalance = token.balanceOf(address(this));
60
+ token.safeTransferFrom(t.payer, address(this), t.amount);
61
+ if (token.balanceOf(address(this)) != beforeBalance + t.amount) revert TokenAmountMismatch();
62
+ emit Funded(t.jobId, t.agreementHash, t.payer, t.worker, t.amount);
63
+ }
64
+ function submit(bytes32 id, bytes32 resultHash) external {
65
+ Job storage j = jobs[id];
66
+ if (msg.sender != j.terms.worker) revert Unauthorized();
67
+ if (j.state != State.Funded) revert WrongState();
68
+ if (block.timestamp > j.terms.workDeadline) revert Deadline();
69
+ if (resultHash == bytes32(0)) revert InvalidTerms();
70
+ j.resultHash = resultHash; j.state = State.Submitted;
71
+ emit Submitted(id, resultHash);
72
+ }
73
+ function verify(bytes32 id, bytes32 resultHash) external {
74
+ Job storage j = jobs[id];
75
+ if (msg.sender != j.terms.verifier) revert Unauthorized();
76
+ if (j.state != State.Submitted) revert WrongState();
77
+ if (block.timestamp > j.terms.reviewDeadline) revert Deadline();
78
+ if (resultHash != j.resultHash) revert InvalidTerms();
79
+ j.state = State.Verified; emit Verified(id, resultHash);
80
+ }
81
+ function release(bytes32 id) external nonReentrant {
82
+ Job storage j = jobs[id];
83
+ if (msg.sender != j.terms.verifier) revert Unauthorized();
84
+ if (j.state != State.Verified) revert WrongState();
85
+ if (block.timestamp > j.terms.resolutionDeadline) revert Deadline();
86
+ _settle(id, j, j.terms.amount, 0);
87
+ }
88
+ function refundExpired(bytes32 id) external nonReentrant {
89
+ Job storage j = jobs[id];
90
+ if (j.state != State.Funded) revert WrongState();
91
+ if (block.timestamp <= j.terms.workDeadline) revert Deadline();
92
+ _settle(id, j, 0, 1);
93
+ }
94
+ function dispute(bytes32 id, bytes32 reasonHash) external {
95
+ Job storage j = jobs[id];
96
+ if (msg.sender != j.terms.payer && msg.sender != j.terms.worker && msg.sender != j.terms.verifier) revert Unauthorized();
97
+ if (j.state != State.Submitted && j.state != State.Verified) revert WrongState();
98
+ if (block.timestamp > j.terms.resolutionDeadline) revert Deadline();
99
+ j.state = State.Disputed; emit Disputed(id, reasonHash);
100
+ }
101
+ function resolve(bytes32 id, uint256 workerAmount) external nonReentrant {
102
+ Job storage j = jobs[id];
103
+ if (msg.sender != j.terms.arbitrator) revert Unauthorized();
104
+ if (j.state != State.Disputed) revert WrongState();
105
+ if (block.timestamp > j.terms.resolutionDeadline || workerAmount > j.terms.amount) revert InvalidTerms();
106
+ _settle(id, j, workerAmount, 2);
107
+ }
108
+ /// Permissionless liveness fallback; requires an actual transaction after the deadline.
109
+ function timeout(bytes32 id) external nonReentrant {
110
+ Job storage j = jobs[id];
111
+ if (j.state != State.Submitted && j.state != State.Verified && j.state != State.Disputed) revert WrongState();
112
+ if (block.timestamp <= j.terms.resolutionDeadline) revert Deadline();
113
+ // Quotient/remainder avoids overflowing amount * bps.
114
+ uint256 amount = (j.terms.amount / 10000) * j.terms.timeoutWorkerBps + ((j.terms.amount % 10000) * j.terms.timeoutWorkerBps) / 10000;
115
+ _settle(id, j, amount, 3);
116
+ }
117
+ function _settle(bytes32 id, Job storage j, uint256 workerAmount, uint8 reason) private {
118
+ uint256 payerAmount = j.terms.amount - workerAmount;
119
+ j.state = workerAmount == 0 ? State.Refunded : payerAmount == 0 ? State.Paid : State.Split;
120
+ j.workerPaid = workerAmount;
121
+ if (workerAmount > 0) _transferExact(j.terms.worker, workerAmount);
122
+ if (payerAmount > 0) _transferExact(j.terms.payer, payerAmount);
123
+ emit Settled(id, workerAmount, payerAmount, reason);
124
+ }
125
+ function _transferExact(address to, uint256 amount) private {
126
+ uint256 beforeBalance = token.balanceOf(address(this));
127
+ uint256 beforeRecipient = token.balanceOf(to);
128
+ token.safeTransfer(to, amount);
129
+ if (token.balanceOf(address(this)) != beforeBalance - amount || token.balanceOf(to) != beforeRecipient + amount) revert TokenAmountMismatch();
130
+ }
131
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allorc/a423-sdk",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Experimental Next.js-friendly agent job SDK with typed HTTP client and pluggable storage, authentication and settlement",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",
@@ -16,6 +16,16 @@
16
16
  "types": "./src/workflow.d.ts",
17
17
  "import": "./src/workflow.mjs",
18
18
  "require": "./src/workflow.cjs"
19
+ },
20
+ "./escrow": {
21
+ "import": {
22
+ "types": "./src/escrow/index.d.mts",
23
+ "default": "./src/escrow/index.mjs"
24
+ },
25
+ "require": {
26
+ "types": "./src/escrow/index.d.ts",
27
+ "default": "./src/escrow/index.cjs"
28
+ }
19
29
  }
20
30
  },
21
31
  "files": [
@@ -32,15 +42,22 @@
32
42
  "src/workflow.mjs",
33
43
  "src/workflow.d.ts",
34
44
  "INTEGRATION.md",
35
- "CHANGELOG.md"
45
+ "CHANGELOG.md",
46
+ "src/escrow",
47
+ "contracts/A423Escrow.sol",
48
+ "ESCROW.md",
49
+ "NOTICE"
36
50
  ],
37
51
  "engines": {
38
52
  "node": ">=22"
39
53
  },
40
54
  "scripts": {
41
- "test": "node --test",
55
+ "test": "node --test test/*.test.cjs",
42
56
  "pack:check": "npm pack --dry-run --ignore-scripts",
43
- "prepublishOnly": "npm test"
57
+ "prepublishOnly": "npm run typecheck && npm test",
58
+ "build:escrow": "node scripts/compile-escrow.cjs",
59
+ "typecheck": "tsc --noEmit --strict --module nodenext --moduleResolution nodenext --target es2022 --lib es2022,dom test/workflow-types.mts test/escrow-types.mts test/escrow-types.cts",
60
+ "pretest": "npm run build:escrow"
44
61
  },
45
62
  "publishConfig": {
46
63
  "access": "public"
@@ -59,5 +76,20 @@
59
76
  "homepage": "https://github.com/Allorc-official/A423-SDK#readme",
60
77
  "bugs": {
61
78
  "url": "https://github.com/Allorc-official/A423-SDK/issues"
79
+ },
80
+ "peerDependencies": {
81
+ "ethers": "^6.15.0"
82
+ },
83
+ "peerDependenciesMeta": {
84
+ "ethers": {
85
+ "optional": true
86
+ }
87
+ },
88
+ "devDependencies": {
89
+ "@openzeppelin/contracts": "5.4.0",
90
+ "ethers": "6.15.0",
91
+ "ganache": "7.9.2",
92
+ "solc": "0.8.30",
93
+ "typescript": "5.9.3"
62
94
  }
63
95
  }