@allorc/a423-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 +18 -0
- package/ESCROW.md +161 -0
- package/INTEGRATION.md +69 -0
- package/NOTICE +24 -0
- package/README.md +110 -0
- package/SECURITY.md +5 -0
- package/contracts/A423Escrow.sol +131 -0
- package/package.json +46 -4
- package/src/escrow/artifact.json +661 -0
- package/src/escrow/index.cjs +137 -0
- package/src/escrow/index.d.mts +36 -0
- package/src/escrow/index.d.ts +36 -0
- package/src/escrow/index.mjs +2 -0
- package/src/workflow.cjs +215 -0
- package/src/workflow.d.ts +57 -0
- package/src/workflow.mjs +2 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Changelog
|
|
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
|
+
|
|
11
|
+
## 0.2.0
|
|
12
|
+
|
|
13
|
+
- Add opt-in `@allorc/a423-sdk/workflow` exports: workflow engine, Next.js-compatible handler, typed HTTP client and canonical agreement hashing.
|
|
14
|
+
- Add versioned quotes, bilateral acceptance, role checks, application policies and configurable transitions.
|
|
15
|
+
- Cover funding, execution gate, submission, verification, release, refund, cancellation, expiry, disputes and resolution through explicit adapters.
|
|
16
|
+
- Persist pending operations before effects, stable provider idempotency keys, reconciliation observations and structured evidence.
|
|
17
|
+
- Preserve the 0.1 root API unchanged. Its approval/settlement behaviour is distinct from the new workflow.
|
|
18
|
+
- Add migration instructions and lifecycle/recovery/HTTP tests. No chain adapter, database driver, signer, queue or PDF renderer is bundled.
|
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
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Integration instructions for the MVP coding agent
|
|
2
|
+
|
|
3
|
+
Install after the maintainer publishes:
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
npm install @allorc/a423-sdk@0.3.0
|
|
7
|
+
```
|
|
8
|
+
|
|
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
|
+
|
|
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
|
+
|
|
17
|
+
## Task
|
|
18
|
+
|
|
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.
|
|
20
|
+
|
|
21
|
+
1. Implement a durable `WorkflowStore` in the app's database using atomic create and version-checked replacement. Store the whole workflow record together. Keep legacy data separate; explicitly migrate records only after reviewing the mapping. Use stable application-assigned job IDs; recover duplicate creates by loading and comparing the record.
|
|
22
|
+
2. Implement authentication and bind authenticated agent IDs to the application's wallet/signer registry. Keep `@allorc/identity` as public identity derivation only. Configure participants as requester, worker and verifier. Reject unknown agents before signing anything.
|
|
23
|
+
3. Define the application's JSON agreement terms and `validateTerms`: task specification, price, raw amount string, token/chain/contract, participant wallets, deadline, artifact requirements and verification/refund/dispute policy. Preserve decimal metadata. Use the returned SHA-256 agreement hash and revision for bilateral acceptance. Do not substitute this for the existing contract's keccak spec hash: explicitly map and verify both formats where needed.
|
|
24
|
+
4. Implement `fund.execute/reconcile`. Use `operation.id` for persistent deduplication; save provider transaction identifiers before waiting. Validate successful funding event plus actual escrow fields against the agreed terms and trusted chain/contract configuration. Return pending while unconfirmed, confirmed with structured observations only when policy passes, and rejected only on definitive failure. Confirm how the actual ABI exposes specHash; do not skip the comparison silently.
|
|
25
|
+
5. Implement `start` as a fresh eligibility gate. Fail closed if escrow configuration, evidence or chain access is missing. Match funding and assigned worker before scheduling execution. Dispatch actual work through the app's durable queue using a deduplicated run key. The SDK itself does not execute an AI agent.
|
|
26
|
+
6. Implement one submission path: store the actual artifact bytes, hash them, persist the manifest/reference, submit the required on-chain commitment, and wait/reconcile before confirming. If durable execution starts asynchronously, it must not report submitted just because the queue accepted it.
|
|
27
|
+
7. Implement `verify`: validate the exact committed artifact against accepted criteria. Only return confirmed after checks pass. A definitive failure returns rejected, then a separately authorized dispute action records the dispute. Record check results and validator version. If revisions are needed, add explicit transitions and define how they interact with already submitted commitments.
|
|
28
|
+
8. Implement `release.execute/reconcile`, checking caller role, recorded verification and actual payment evidence. Verify exact amount/recipient and relevant event. The SDK state guard does not change contract rules; document whether the contract itself requires submission. Never infer successful payment from a broadcast hash alone.
|
|
29
|
+
9. Implement `refund.execute/reconcile` based on actual contract eligibility. The default rule allows requesting a refund while funded/running; the adapter must enforce the deadline and absence of submission. Submitted disputes require an explicit supported resolution policy. Implement `resolve` only when that policy exists; confirmed resolution evidence must state whether money was released, refunded or remains locked. `resolved` alone does not imply payment.
|
|
30
|
+
10. Add application deadline/expiry checks to authorization and the `expire` adapter. Schedule sweeps with authorized roles. Cancellation only applies before funding in the default workflow. Do not claim automatic sweeps or a dispute timeout exist if they are not implemented.
|
|
31
|
+
11. Configure each adapter's `reconcile` to inspect the provider using durable operation records. Unknown outcomes remain pending. Poll/reconcile in a durable worker. Handle configured confirmations, disappearing observations and reorgs according to the app's policy; the SDK does not include a chain watcher. Add explicit corrective transitions/policies if required rather than editing records outside CAS.
|
|
32
|
+
12. Expose `createWorkflowHandler` through a Node-runtime Next.js catch-all route. Use `createWorkflowClient` or documented HTTP endpoints externally. The old root API and new workflow API use distinct models; migrate the client deliberately.
|
|
33
|
+
13. Build machine-readable receipts through `workflow.receipts`. Revalidate chain evidence before generating chain-backed PDFs; derive amounts/participants from validated observations and use actual token decimals. Distinguish cached observations from freshly checked evidence.
|
|
34
|
+
|
|
35
|
+
## Adapter shape
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
const adapters = {
|
|
39
|
+
fund: {
|
|
40
|
+
async execute({ job, input, operation }) {
|
|
41
|
+
// Deduplicate operation.id durably, enforce agreed terms and submit/observe.
|
|
42
|
+
return { status: 'pending', reference: 'your-provider-operation-id' };
|
|
43
|
+
},
|
|
44
|
+
async reconcile({ job, operation }) {
|
|
45
|
+
// Query your saved provider operation. Do not blindly resend.
|
|
46
|
+
return { status: 'confirmed', evidence: { /* independently validated fields */ } };
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
// start, submit, verify, release, refund, expire, resolve as applicable.
|
|
50
|
+
};
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
This is a structural example, not a working escrow adapter. Never return confirmed unconditionally in an actual integration.
|
|
54
|
+
|
|
55
|
+
## Required validation before replacing MVP routes
|
|
56
|
+
|
|
57
|
+
- Happy path: create → quote → both accept same revision/hash → fund confirmed → start gate → submit confirmed → verify → release confirmed → JSON/PDF evidence.
|
|
58
|
+
- Quotes: revised terms clear acceptances; stale revision rejected; funding prevents edits; deadline policy rejects expired quotes.
|
|
59
|
+
- Permissions: anonymous/unknown/unrelated actors blocked; each role enforced; identity hash alone cannot authenticate.
|
|
60
|
+
- Funding: wrong amount, token, chain, contract, payer, worker, verifier, deadline or spec commitment blocks work; missing escrow configuration fails closed.
|
|
61
|
+
- Effects: duplicate requests and simultaneous retries produce one provider operation; differing input with reused key rejected; crashes before/after broadcast and before final DB save recover by reconciliation.
|
|
62
|
+
- Submission: asynchronous execution cannot prematurely submit; bytes match committed digest; missing/failed chain submission stays pending/rejected.
|
|
63
|
+
- Verification: bad output never enables release; dispute recorded; unsupported resolution leaves explicit unresolved state.
|
|
64
|
+
- Refund: before deadline rejected; after deadline/unsubmitted succeeds; submitted work obeys contract restrictions.
|
|
65
|
+
- Exceptions: pre-funding cancellation, unfunded expiry, definitive provider rejection and long pending operations.
|
|
66
|
+
- Receipts: correct raw units/decimals, matching funding/payment/refund events and artifact linkage; provider outage is distinguished from absent evidence.
|
|
67
|
+
- Custom use case: prove a second task type or non-chain provider works without modifying SDK internals.
|
|
68
|
+
|
|
69
|
+
Report changed files, adapter capabilities, test results and unsupported contract guarantees. Keep real-chain tests opt-in. Do not claim every possible workflow or payment provider is implemented merely because adapter hooks exist.
|
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,113 @@
|
|
|
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
|
+
|
|
7
|
+
# Configurable workflows in 0.2.0
|
|
8
|
+
|
|
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.
|
|
10
|
+
|
|
11
|
+
The original root exports remain compatible. Choose the workflow entry point for the full lifecycle; do not mix its records with the original root API. See [integration and migration instructions](./INTEGRATION.md).
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { createWorkflow, createWorkflowHandler } from '@allorc/a423-sdk/workflow';
|
|
15
|
+
|
|
16
|
+
const workflow = createWorkflow({
|
|
17
|
+
store: durableWorkflowStore,
|
|
18
|
+
adapters: applicationAdapters,
|
|
19
|
+
validateTerms: validateYourTerms,
|
|
20
|
+
authorize: checkYourPolicy,
|
|
21
|
+
});
|
|
22
|
+
const handler = createWorkflowHandler({ workflow, authenticate: authenticateRequest });
|
|
23
|
+
// app/api/a423/[...path]/route.ts
|
|
24
|
+
export const runtime = 'nodejs';
|
|
25
|
+
export { handler as GET, handler as POST };
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The names `durableWorkflowStore`, `applicationAdapters`, `validateYourTerms`, `checkYourPolicy` and `authenticateRequest` are application implementations, not SDK exports. Configure them once in a server module and import the handler into your route.
|
|
29
|
+
|
|
30
|
+
## Workflow coverage
|
|
31
|
+
|
|
32
|
+
| Action | From | Default role | Result | Required adapter |
|
|
33
|
+
|---|---|---|---|---|
|
|
34
|
+
| create | — | requester | draft | none |
|
|
35
|
+
| quote | draft / quoted / accepted | requester or worker | quoted, acceptances reset | none |
|
|
36
|
+
| accept | quoted | requester and worker separately | accepted after both | none |
|
|
37
|
+
| fund | accepted | requester | funded | fund |
|
|
38
|
+
| start | funded | worker | running | start (funding/work gate) |
|
|
39
|
+
| submit | running | worker | submitted | submit |
|
|
40
|
+
| verify | submitted | verifier | verified | verify |
|
|
41
|
+
| release | verified | verifier | paid | release |
|
|
42
|
+
| refund | funded / running | requester | refunded | refund |
|
|
43
|
+
| cancel | draft / quoted / accepted | requester | cancelled | none |
|
|
44
|
+
| expire | draft / quoted / accepted | requester or worker | expired | expire |
|
|
45
|
+
| dispute | submitted / verified | any of the three parties | disputed | none |
|
|
46
|
+
| resolve | disputed | verifier | resolved | resolve |
|
|
47
|
+
|
|
48
|
+
`resolved` records a completed resolution process; it does not mean paid or refunded. The resolution adapter must record its actual outcome in evidence. Refund eligibility, quote expiry, deadlines, spending limits, asset rules and verification semantics belong in application policies/adapters. There is no automatic timer. A scheduler calls the API using an authenticated participant or an explicitly configured scheduler role.
|
|
49
|
+
|
|
50
|
+
### Terms and acceptance
|
|
51
|
+
|
|
52
|
+
Use JSON terms for your domain: task specification, price, asset, raw amount, deadline, wallet addresses, verifier policy and artifact expectations. Put every term you want committed into this object, including parties and settlement configuration where relevant. The SDK does not invent or validate those domain fields; `validateTerms` should throw on invalid terms. Use decimal strings for raw token amounts, not floating point or JSON bigint.
|
|
53
|
+
|
|
54
|
+
The SDK hashes sorted-key canonical JSON with SHA-256 (array order preserved, no whitespace). Hash inputs must be plain JSON; avoid cyclic structures and sparse arrays. This is the SDK's own format, not a claim of RFC 8785 compatibility. Names and IDs are case-sensitive. A quote stores revision, terms, hash and acceptances. Accept requires both `agreement_hash` and `revision`; editing a quote clears acceptance. Terms cannot be revised after funding begins under the default rules. Hashes establish integrity, not caller identity or legal consent.
|
|
55
|
+
|
|
56
|
+
### Adapter contract and recovery
|
|
57
|
+
|
|
58
|
+
Each effect adapter is either an async function or `{ execute, reconcile }`. It receives `{ job, input, operation }` and returns:
|
|
59
|
+
|
|
60
|
+
```js
|
|
61
|
+
{ status: 'pending', reference: 'provider-operation-reference' }
|
|
62
|
+
{ status: 'confirmed', evidence: { /* domain-specific verified observations */ } }
|
|
63
|
+
{ status: 'rejected', reason: 'definitive rejection' }
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Confirmed results require an evidence object. The SDK trusts the adapter's verdict; it cannot independently establish payment, artifact quality or chain finality. An arbitrary reference is not payment proof. Chain adapters should compare configured chain/contract, job ID, amount, token, participants, deadline and commitments against the accepted terms, plus relevant successful transaction events and the chosen finality policy. `start` must recheck eligibility before work is dispatched.
|
|
67
|
+
|
|
68
|
+
The SDK atomically stores a pending operation before invoking an effect. Its `operation.id` is stable across retries: use it as the provider idempotency key. **Adapters must durably deduplicate effects, including concurrent calls.** Compare-and-set does not make an external payment exactly-once. If execution throws or returns an invalid result, pending remains; inspect/reconcile it. A pending result retains its observation. A rejected result clears pending without advancing state; repeat the same action key returns the stored job, so use a new key only after a definitive rejection and a deliberate new attempt.
|
|
69
|
+
|
|
70
|
+
`reconcile(id, actor)` calls only the pending adapter's `reconcile`, never its execute method. That implementation should inspect known provider operations, not blindly repeat a transaction. Supply reconciliation for every external effect. Reuse the same adapter configuration and transitions across restarts. Reconciliation can record additional pending observations. No other action runs while an operation remains pending. If a final database save fails, recovery relies on the same persisted operation and provider key.
|
|
71
|
+
|
|
72
|
+
The SDK does not run agents or background queues. Persist intent and schedule work in adapters; use a durable worker to submit actual output. Do not mark work submitted merely because a worker was queued. Custom transitions can add milestones, rejection/revision loops, manual review, nonpayment completion, or alternative arbitration. Override whole rules with `{ from, roles, to, effect }`; applications assume responsibility for the resulting invariants. Effect adapters must be trusted server code.
|
|
73
|
+
|
|
74
|
+
### Storage, permissions and receipts
|
|
75
|
+
|
|
76
|
+
A workflow store implements `get`, atomic insert-if-absent `create`, and atomic version-checked `compareAndSet`. Persist the entire record (state, pending operation, evidence, events, idempotency history) atomically. Use a separate table/namespace from legacy records. The root `createMemoryStore()` also works structurally at runtime for development, but is not durable or suitable for distributed deployment; a typed workflow store should implement the interface from the workflow entry point.
|
|
77
|
+
|
|
78
|
+
The handler requires authentication. It obtains actor identity from your callback, never from action bodies. Default role checks always run; `authorize` adds application policy and must return exactly true. Creation requires requester authentication, and requester must differ from worker. The verifier may be the requester or a separate participant. Authentication, wallet/key custody, identity-to-wallet binding and proof of ownership remain application responsibilities. `@allorc/identity` only derives public identifiers.
|
|
79
|
+
|
|
80
|
+
Receipts are JSON snapshots of agreement, submission, stored adapter evidence, events and pending state. They are not freshly queried chain proofs or signed attestations. Render PDFs separately, and revalidate provider evidence if fresh verification is required. Protect credentials and never put secrets in terms, evidence, metadata or events: all participants can read these fields. Records/events grow over time; production storage should enforce application quotas. The HTTP client limits responses to 1 MiB.
|
|
81
|
+
|
|
82
|
+
### HTTP and client
|
|
83
|
+
|
|
84
|
+
| Method | Path beneath `/api/a423` | Body |
|
|
85
|
+
|---|---|---|
|
|
86
|
+
| POST | `/jobs` | `{ id, participants, metadata? }` |
|
|
87
|
+
| GET | `/jobs/:id` | — |
|
|
88
|
+
| POST | `/jobs/:id/actions` | `{ action, input }` plus `Idempotency-Key` |
|
|
89
|
+
| POST | `/jobs/:id/reconcile` | `{}` |
|
|
90
|
+
| GET | `/jobs/:id/receipts` | — |
|
|
91
|
+
|
|
92
|
+
The handler uses standard Web Request/Response, bounded streaming JSON (64 KiB default), explicit errors, and no background HTTP work. IDs and idempotency keys use 1–128 ASCII letters/digits/underscores/hyphens. Create with a stable application-assigned ID; duplicate creation returns conflict, then retrieve and compare the existing record. Every action needs an actor-scoped key; reusing it with different input fails. Transient conflicts should reload and retry using the same key. Authentication callbacks must inspect headers/cookies or a cloned request if they need the body.
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
import { createWorkflowClient } from '@allorc/a423-sdk/workflow';
|
|
96
|
+
const client = createWorkflowClient({
|
|
97
|
+
baseUrl: 'https://your-app.example/api/a423',
|
|
98
|
+
headers: () => ({ Authorization: `Bearer ${serverCredential}` }),
|
|
99
|
+
});
|
|
100
|
+
const quote = await client.act(jobId, 'quote', { terms }, { idempotencyKey: 'quote_1' });
|
|
101
|
+
await client.act(jobId, 'accept', {
|
|
102
|
+
agreement_hash: quote.agreement!.hash,
|
|
103
|
+
revision: quote.agreement!.revision,
|
|
104
|
+
}, { idempotencyKey: 'accept_1' });
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Each participant needs its own credentials. The client does not automatically retry. Default timeout is 30 seconds; adapters should return pending quickly for long operations. Use TLS and appropriate CSRF protection for cookie-authenticated mutations.
|
|
108
|
+
|
|
109
|
+
## Original 0.1 API documentation
|
|
110
|
+
|
|
1
111
|
# @allorc/a423-sdk
|
|
2
112
|
|
|
3
113
|
One experimental package for exposing an agent job API from a Next.js App Router application. Configure a handler, export it from a catch-all route, and use the typed client to offer, accept, submit and approve work.
|
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.
|
|
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",
|
|
@@ -11,6 +11,21 @@
|
|
|
11
11
|
"types": "./src/index.d.ts",
|
|
12
12
|
"import": "./src/index.mjs",
|
|
13
13
|
"require": "./src/index.cjs"
|
|
14
|
+
},
|
|
15
|
+
"./workflow": {
|
|
16
|
+
"types": "./src/workflow.d.ts",
|
|
17
|
+
"import": "./src/workflow.mjs",
|
|
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
|
+
}
|
|
14
29
|
}
|
|
15
30
|
},
|
|
16
31
|
"files": [
|
|
@@ -22,15 +37,27 @@
|
|
|
22
37
|
"examples/nextjs/app/api/a423/*/route.ts",
|
|
23
38
|
"README.md",
|
|
24
39
|
"LICENSE",
|
|
25
|
-
"SECURITY.md"
|
|
40
|
+
"SECURITY.md",
|
|
41
|
+
"src/workflow.cjs",
|
|
42
|
+
"src/workflow.mjs",
|
|
43
|
+
"src/workflow.d.ts",
|
|
44
|
+
"INTEGRATION.md",
|
|
45
|
+
"CHANGELOG.md",
|
|
46
|
+
"src/escrow",
|
|
47
|
+
"contracts/A423Escrow.sol",
|
|
48
|
+
"ESCROW.md",
|
|
49
|
+
"NOTICE"
|
|
26
50
|
],
|
|
27
51
|
"engines": {
|
|
28
52
|
"node": ">=22"
|
|
29
53
|
},
|
|
30
54
|
"scripts": {
|
|
31
|
-
"test": "node --test",
|
|
55
|
+
"test": "node --test test/*.test.cjs",
|
|
32
56
|
"pack:check": "npm pack --dry-run --ignore-scripts",
|
|
33
|
-
"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"
|
|
34
61
|
},
|
|
35
62
|
"publishConfig": {
|
|
36
63
|
"access": "public"
|
|
@@ -49,5 +76,20 @@
|
|
|
49
76
|
"homepage": "https://github.com/Allorc-official/A423-SDK#readme",
|
|
50
77
|
"bugs": {
|
|
51
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"
|
|
52
94
|
}
|
|
53
95
|
}
|