@telaro/sacp 1.0.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/INTEGRATION.md +238 -0
- package/README.md +253 -0
- package/dist/codec.d.ts +35 -0
- package/dist/codec.d.ts.map +1 -0
- package/dist/codec.js +63 -0
- package/dist/codec.js.map +1 -0
- package/dist/constants.d.ts +50 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +50 -0
- package/dist/constants.js.map +1 -0
- package/dist/dispatcher.d.ts +87 -0
- package/dist/dispatcher.d.ts.map +1 -0
- package/dist/dispatcher.js +175 -0
- package/dist/dispatcher.js.map +1 -0
- package/dist/events.d.ts +93 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +142 -0
- package/dist/events.js.map +1 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +39 -0
- package/dist/index.js.map +1 -0
- package/dist/job.d.ts +99 -0
- package/dist/job.d.ts.map +1 -0
- package/dist/job.js +297 -0
- package/dist/job.js.map +1 -0
- package/dist/lst.d.ts +52 -0
- package/dist/lst.d.ts.map +1 -0
- package/dist/lst.js +58 -0
- package/dist/lst.js.map +1 -0
- package/dist/negotiation.d.ts +66 -0
- package/dist/negotiation.d.ts.map +1 -0
- package/dist/negotiation.js +184 -0
- package/dist/negotiation.js.map +1 -0
- package/dist/offering.d.ts +65 -0
- package/dist/offering.d.ts.map +1 -0
- package/dist/offering.js +190 -0
- package/dist/offering.js.map +1 -0
- package/dist/pdas.d.ts +44 -0
- package/dist/pdas.d.ts.map +1 -0
- package/dist/pdas.js +95 -0
- package/dist/pdas.js.map +1 -0
- package/dist/runtime.d.ts +244 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +481 -0
- package/dist/runtime.js.map +1 -0
- package/dist/signer.d.ts +107 -0
- package/dist/signer.d.ts.map +1 -0
- package/dist/signer.js +94 -0
- package/dist/signer.js.map +1 -0
- package/dist/take_rate.d.ts +44 -0
- package/dist/take_rate.d.ts.map +1 -0
- package/dist/take_rate.js +141 -0
- package/dist/take_rate.js.map +1 -0
- package/dist/timeout.d.ts +35 -0
- package/dist/timeout.d.ts.map +1 -0
- package/dist/timeout.js +113 -0
- package/dist/timeout.js.map +1 -0
- package/dist/types.d.ts +39 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +13 -0
- package/dist/types.js.map +1 -0
- package/dist/validation.d.ts +133 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +315 -0
- package/dist/validation.js.map +1 -0
- package/dist/watcher.d.ts +67 -0
- package/dist/watcher.d.ts.map +1 -0
- package/dist/watcher.js +176 -0
- package/dist/watcher.js.map +1 -0
- package/dist/wormhole.d.ts +88 -0
- package/dist/wormhole.d.ts.map +1 -0
- package/dist/wormhole.js +152 -0
- package/dist/wormhole.js.map +1 -0
- package/package.json +138 -0
package/INTEGRATION.md
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
# sACP Integration Guide
|
|
2
|
+
|
|
3
|
+
Concrete recipes for the four common ways teams plug into sACP.
|
|
4
|
+
Pick the role you play and follow the matching section.
|
|
5
|
+
|
|
6
|
+
- [Role 1. Agent framework / SDK author](#role-1--agent-framework--sdk-author) (SendAI, Eliza, Crestal, similar)
|
|
7
|
+
- [Role 2. DApp gating on agent commerce](#role-2--dapp-gating-on-agent-commerce) (you accept agent service and want bonded settlement)
|
|
8
|
+
- [Role 3. Validator operator](#role-3--validator-operator) (you stake bond and act as Evaluator across many Jobs)
|
|
9
|
+
- [Role 4. Reputation / indexer integrator](#role-4--reputation--indexer-integrator) (you mirror sACP state for analytics or trust scoring)
|
|
10
|
+
|
|
11
|
+
If your case isn't here, open an issue or DM
|
|
12
|
+
[@telaroai](https://x.com/telaroai). we'll add a recipe.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Role 1. Agent framework / SDK author
|
|
17
|
+
|
|
18
|
+
You ship a TypeScript / Rust SDK that agent operators install. You
|
|
19
|
+
want your agents to be able to **place jobs** (act as Client) and
|
|
20
|
+
**fulfill jobs** (act as Provider) on a standard.
|
|
21
|
+
|
|
22
|
+
### Minimum surface
|
|
23
|
+
|
|
24
|
+
1. **Add `@telaro/sacp` as a peer dependency** of your SDK so users
|
|
25
|
+
already pulling your SDK get sACP for free.
|
|
26
|
+
2. **Expose two methods**: `placeJob(...)` and `claimJob(...)`. Each
|
|
27
|
+
wraps the sACP instruction builders with your SDK's idioms
|
|
28
|
+
(provider client, error handling, retries).
|
|
29
|
+
3. **Surface job state in your agent loop**. Agents poll `fetchJob`
|
|
30
|
+
on PDAs they care about and react to state changes.
|
|
31
|
+
|
|
32
|
+
### Reference pattern
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
// In your SDK
|
|
36
|
+
import {
|
|
37
|
+
buildCreateJobIx,
|
|
38
|
+
buildSubmitWorkIx,
|
|
39
|
+
fetchJob,
|
|
40
|
+
} from "@telaro/sacp/job";
|
|
41
|
+
|
|
42
|
+
export class MyAgent {
|
|
43
|
+
async placeJob(params: { provider, evaluator, workUri, amount }) {
|
|
44
|
+
const jobId = BigInt(Date.now()) * 1000n;
|
|
45
|
+
const { ix, jobPda } = buildCreateJobIx({
|
|
46
|
+
jobId,
|
|
47
|
+
roles: { client: this.wallet.publicKey, ...params },
|
|
48
|
+
bondMint: USDC_MINT,
|
|
49
|
+
amount: params.amount,
|
|
50
|
+
workUri: params.workUri,
|
|
51
|
+
});
|
|
52
|
+
await this.sendTx([ix]);
|
|
53
|
+
return { jobId, jobPda };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async fulfillJob(jobId, submissionUri) {
|
|
57
|
+
const ix = buildSubmitWorkIx(this.wallet.publicKey, jobId, submissionUri);
|
|
58
|
+
return this.sendTx([ix]);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async watchJob(jobId, onStateChange) {
|
|
62
|
+
let last;
|
|
63
|
+
while (true) {
|
|
64
|
+
const job = await fetchJob(this.conn, jobId);
|
|
65
|
+
if (job?.state !== last) {
|
|
66
|
+
last = job?.state;
|
|
67
|
+
onStateChange(job);
|
|
68
|
+
}
|
|
69
|
+
await sleep(2000);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Why integrate
|
|
76
|
+
|
|
77
|
+
- One standard for cross-source agent commerce on Solana
|
|
78
|
+
- Verifiable settlement state (no off-chain agreements)
|
|
79
|
+
- Built-in evaluator economy via Part B
|
|
80
|
+
- Cross-chain compatibility with ERC-8183 (Base, BSC)
|
|
81
|
+
|
|
82
|
+
### Co-marketing path
|
|
83
|
+
|
|
84
|
+
If your framework ships a sACP integration, we'll co-publish:
|
|
85
|
+
the announcement, a worked example in our docs, and a slot on
|
|
86
|
+
the [sACP standard](https://sacp.dev) landing page (coming
|
|
87
|
+
soon). Open a PR or email build@telaro.xyz.
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Role 2. DApp gating on agent commerce
|
|
92
|
+
|
|
93
|
+
You run a DApp that accepts agent-provided services (data
|
|
94
|
+
analysis, content generation, custom workflows) and you want
|
|
95
|
+
settlement that doesn't depend on a centralized escrow.
|
|
96
|
+
|
|
97
|
+
### Minimum surface
|
|
98
|
+
|
|
99
|
+
1. **Use sACP as your escrow layer**. Your DApp creates the Job;
|
|
100
|
+
your selected agent fulfills.
|
|
101
|
+
2. **Pick the Evaluator path**:
|
|
102
|
+
- Single signer (you settle)
|
|
103
|
+
- Multisig (multi-stakeholder DApp)
|
|
104
|
+
- Validation Registry (delegated bonded evaluators)
|
|
105
|
+
3. **Surface Job state in your UI**. Users see the job moving
|
|
106
|
+
through the state machine in real time.
|
|
107
|
+
|
|
108
|
+
### Reference pattern
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
import { buildCreateJobIx, fetchJob } from "@telaro/sacp/job";
|
|
112
|
+
|
|
113
|
+
// User clicks "Hire agent to do X"
|
|
114
|
+
const { ix, jobPda } = buildCreateJobIx({
|
|
115
|
+
jobId,
|
|
116
|
+
roles: {
|
|
117
|
+
client: dappEscrowPda, // your DApp's PDA, signs via CPI
|
|
118
|
+
provider: selectedAgent,
|
|
119
|
+
evaluator: yourMultisigPda,
|
|
120
|
+
},
|
|
121
|
+
bondMint: USDC_MINT,
|
|
122
|
+
amount: jobPrice,
|
|
123
|
+
workUri: `ipfs://${requestSpec.cid}`,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// CPI from your program, or sign with your treasury wallet
|
|
127
|
+
await this.sendTx([ix]);
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Why integrate
|
|
131
|
+
|
|
132
|
+
- No custom escrow contract to audit
|
|
133
|
+
- Standardized dispute flow
|
|
134
|
+
- Optional validator network you don't have to operate yourself
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Role 3. Validator operator
|
|
139
|
+
|
|
140
|
+
You commit USDC bond and earn fees by serving as Evaluator
|
|
141
|
+
across Jobs. Bond is slashed on disputed verdicts.
|
|
142
|
+
|
|
143
|
+
### Onboarding
|
|
144
|
+
|
|
145
|
+
1. Generate a validator authority keypair (cold + hot recommended).
|
|
146
|
+
2. `register_validator` with operator metadata URI.
|
|
147
|
+
3. `stake_bond` (USDC in v0.1; JitoSOL via adapter in v0.2).
|
|
148
|
+
4. Run a verdict-acceptance daemon that watches `JobDisputed`
|
|
149
|
+
events for Jobs that target your validator PDA.
|
|
150
|
+
5. Issue verdicts. Defend against challenges within the window.
|
|
151
|
+
|
|
152
|
+
### Reference pattern
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
import {
|
|
156
|
+
findValidatorPda,
|
|
157
|
+
buildAcceptVerdictIx,
|
|
158
|
+
} from "@telaro/sacp/validation";
|
|
159
|
+
|
|
160
|
+
const [validatorPda] = findValidatorPda(operator.publicKey);
|
|
161
|
+
|
|
162
|
+
// In your daemon, on a Disputed Job that names validatorPda:
|
|
163
|
+
const ix = buildAcceptVerdictIx(jobId, "Provider"); // your decision
|
|
164
|
+
await sendTx([ix]);
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Reward + risk
|
|
168
|
+
|
|
169
|
+
- **Reward**: fee per verdict (set per-Job by Client; defaults TBD).
|
|
170
|
+
- **Slash**: 100% of bond at risk on every challenged verdict the
|
|
171
|
+
challenger wins. Splits 70% to challenger, 30% to public treasury.
|
|
172
|
+
|
|
173
|
+
The full validator runbook (key management, monitoring, dispute
|
|
174
|
+
defense playbook) ships with v0.2.
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Role 4. Reputation / indexer integrator
|
|
179
|
+
|
|
180
|
+
You aggregate on-chain state for analytics, trust scoring,
|
|
181
|
+
discovery (e.g. you build something like 8004scan, or you're
|
|
182
|
+
Telaro's own indexer extending to cover sACP).
|
|
183
|
+
|
|
184
|
+
### Minimum surface
|
|
185
|
+
|
|
186
|
+
1. **Subscribe to the four event types**:
|
|
187
|
+
`JobCreated`, `JobFunded`, `WorkSubmitted`, `JobSettled` (and the
|
|
188
|
+
Validation events `ValidatorRegistered`, `BondStaked`,
|
|
189
|
+
`ValidatorSlashed`).
|
|
190
|
+
2. **Decode account state**: Job, Submission, Verdict, Validator.
|
|
191
|
+
Layouts are documented in
|
|
192
|
+
[`src/job.ts`](src/job.ts) (`fetchJob`) and
|
|
193
|
+
[`src/validation.ts`](src/validation.ts) (`fetchValidator`).
|
|
194
|
+
3. **Materialize derived signals**: settlement rate per Provider,
|
|
195
|
+
slash rate per Validator, dispute frequency, average job value.
|
|
196
|
+
|
|
197
|
+
### Reference pattern
|
|
198
|
+
|
|
199
|
+
```ts
|
|
200
|
+
import {
|
|
201
|
+
SACP_PROGRAM_ID,
|
|
202
|
+
SACP_VALIDATION_PROGRAM_ID,
|
|
203
|
+
} from "@telaro/sacp";
|
|
204
|
+
|
|
205
|
+
// Anchor event signatures = sha256("event:<name>")[..8]
|
|
206
|
+
// or watch via `onLogs(programId, ...)`.
|
|
207
|
+
const sub = conn.onLogs(SACP_PROGRAM_ID, (log) => {
|
|
208
|
+
// parse log.logs for "Program data:" lines, base64-decode, dispatch.
|
|
209
|
+
});
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Why integrate
|
|
213
|
+
|
|
214
|
+
- Single source of truth for agent commerce state on Solana
|
|
215
|
+
- Composable trust signals (combine with Telaro bond, 8004 publishes,
|
|
216
|
+
etc.)
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## Spec change cadence
|
|
221
|
+
|
|
222
|
+
- **v0.1** (now): SDK signatures and PDA seeds frozen. Implementation
|
|
223
|
+
details (escrow CPI, JitoSOL adapter) ship in v0.2 without
|
|
224
|
+
signature changes.
|
|
225
|
+
- **v1.0** (target Q4 2026): governance moves to public RFC process;
|
|
226
|
+
spec changes require RFC + 30-day comment period.
|
|
227
|
+
|
|
228
|
+
If you're worried about a signature change before v1.0, pin
|
|
229
|
+
`@telaro/sacp@~0.1.0`. semver minor bumps will not change PDA seeds
|
|
230
|
+
or instruction argument layout.
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## Need help?
|
|
235
|
+
|
|
236
|
+
- Open a GitHub issue at [Telaro-Protocol](https://github.com/Telaro-Protocol/Telaro-Protocol/issues)
|
|
237
|
+
- Email build@telaro.xyz
|
|
238
|
+
- Cold DM [@telaroai](https://x.com/telaroai) on X
|
package/README.md
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
# @telaro/sacp
|
|
2
|
+
|
|
3
|
+
**Solana Agent Commerce Protocol. TypeScript SDK.**
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@telaro/sacp)
|
|
6
|
+
|
|
7
|
+
sACP is a Solana-native protocol for AI agent commerce: bonded job
|
|
8
|
+
escrow, dispute resolution, and bonded-validator economy. ERC-8183
|
|
9
|
+
compatible at the semantic level.
|
|
10
|
+
|
|
11
|
+
- **Spec**: [`docs/srfc/sacp-v0.1.md`](https://github.com/Telaro-Protocol/Telaro-Protocol/blob/main/docs/srfc/sacp-v0.1.md)
|
|
12
|
+
- **Reference impl**: [Telaro Protocol](https://telaro.xyz)
|
|
13
|
+
- **Status v0.3**: full standard live on devnet. `record_verdict`
|
|
14
|
+
restores the standalone Verdict PDA from SRFC v0.1, and the
|
|
15
|
+
`issue_verdict` cross-program CPI bridge lets a bonded validator
|
|
16
|
+
settle a Job dispute in a single transaction (validator bond is
|
|
17
|
+
real collateral). Verified by 5 devnet smoke tests:
|
|
18
|
+
happy / dispute+record_verdict / reclaim / validation (slash) /
|
|
19
|
+
bonded-evaluator (CPI).
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## What's deployed (devnet)
|
|
24
|
+
|
|
25
|
+
| Program | Program ID | Status |
|
|
26
|
+
|---|---|---|
|
|
27
|
+
| `sacp_job` (Part A. Job Lifecycle) | [`8WAQhJJUnPqiKoJ35dNMSbRWNJsdRqu1f7uVqMwLHZuF`](https://explorer.solana.com/address/8WAQhJJUnPqiKoJ35dNMSbRWNJsdRqu1f7uVqMwLHZuF?cluster=devnet) | live |
|
|
28
|
+
| `sacp_validation` (Part B. Validation Registry) | [`GbWzXwirzzed3WjshJaMat9hiWgVSiPR8tAUj4oJnfMC`](https://explorer.solana.com/address/GbWzXwirzzed3WjshJaMat9hiWgVSiPR8tAUj4oJnfMC?cluster=devnet) | live |
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## What v0.3 ships
|
|
33
|
+
|
|
34
|
+
| Surface | v0.3 | v1.0 plan |
|
|
35
|
+
|---|---|---|
|
|
36
|
+
| Job state machine (Open → Funded → Submitted → Settled / Disputed) | ✅ devnet | mainnet |
|
|
37
|
+
| `create_job`, `submit_work`, `accept_work`, `dispute_work`, `submit_verdict`, `reclaim_escrow` | ✅ full SPL escrow CPI |. |
|
|
38
|
+
| Escrow vault PDA + rent close on settle | ✅ |. |
|
|
39
|
+
| Validation Registry: register / stake / withdraw / challenge / 70-30 slash | ✅ full SPL bond CPI |. |
|
|
40
|
+
| Standalone `Verdict` PDA via `record_verdict` | ✅ |. |
|
|
41
|
+
| `sacp_validation::issue_verdict` → `sacp_job::submit_verdict` CPI bridge | ✅ bonded validator path verified e2e |. |
|
|
42
|
+
| Cross-chain dispatch (Base → Solana via Wormhole) | informative only | normative |
|
|
43
|
+
|
|
44
|
+
The full standard. escrow settlement, bonded validator economy,
|
|
45
|
+
permanent verdict provenance. works on devnet today. The CPI bridge
|
|
46
|
+
in particular makes the validator's bond real collateral: a
|
|
47
|
+
`challenge_verdict` against a verdict the validator issued can slash
|
|
48
|
+
70% of their bond to the challenger.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Install
|
|
53
|
+
|
|
54
|
+
```sh
|
|
55
|
+
pnpm add @telaro/sacp @solana/web3.js
|
|
56
|
+
# or
|
|
57
|
+
npm install @telaro/sacp @solana/web3.js
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Node ≥20.
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Quickstart. drive a Job end-to-end on devnet
|
|
65
|
+
|
|
66
|
+
This is the literal flow our smoke test runs. Copy it, run it, see
|
|
67
|
+
the four real transactions on Solana Explorer.
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
import {
|
|
71
|
+
Connection,
|
|
72
|
+
Keypair,
|
|
73
|
+
PublicKey,
|
|
74
|
+
Transaction,
|
|
75
|
+
sendAndConfirmTransaction,
|
|
76
|
+
} from "@solana/web3.js";
|
|
77
|
+
import {
|
|
78
|
+
buildCreateJobIx,
|
|
79
|
+
buildFundJobIx,
|
|
80
|
+
buildSubmitWorkIx,
|
|
81
|
+
buildAcceptWorkIx,
|
|
82
|
+
fetchJob,
|
|
83
|
+
} from "@telaro/sacp/job";
|
|
84
|
+
|
|
85
|
+
const conn = new Connection("https://api.devnet.solana.com", "confirmed");
|
|
86
|
+
|
|
87
|
+
// You play Client; provider & evaluator are fresh keypairs for the demo.
|
|
88
|
+
const client = Keypair.generate(); // fund this with devnet SOL first
|
|
89
|
+
const provider = Keypair.generate();
|
|
90
|
+
const evaluator = Keypair.generate();
|
|
91
|
+
const jobId = BigInt(Date.now()) * 1000n;
|
|
92
|
+
|
|
93
|
+
const USDC_DEVNET = new PublicKey("4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU");
|
|
94
|
+
|
|
95
|
+
// 1) Open the Job. initializes the escrow vault PDA.
|
|
96
|
+
const { ix: createIx, jobPda } = buildCreateJobIx({
|
|
97
|
+
jobId,
|
|
98
|
+
roles: {
|
|
99
|
+
client: client.publicKey,
|
|
100
|
+
provider: provider.publicKey,
|
|
101
|
+
evaluator: evaluator.publicKey,
|
|
102
|
+
},
|
|
103
|
+
bondMint: USDC_DEVNET,
|
|
104
|
+
amount: 1_000_000n, // 1 USDC (decimals = 6)
|
|
105
|
+
workUri: "ipfs://Qm...your work order...",
|
|
106
|
+
});
|
|
107
|
+
await sendAndConfirmTransaction(conn, new Transaction().add(createIx), [client]);
|
|
108
|
+
|
|
109
|
+
// 2) Fund. Client transfers 1 USDC → escrow vault via SPL CPI.
|
|
110
|
+
// Provide the Client's USDC ATA.
|
|
111
|
+
await sendAndConfirmTransaction(
|
|
112
|
+
conn,
|
|
113
|
+
new Transaction().add(
|
|
114
|
+
buildFundJobIx({
|
|
115
|
+
client: client.publicKey,
|
|
116
|
+
jobId,
|
|
117
|
+
amount: 1_000_000n,
|
|
118
|
+
clientTokenAccount: clientUsdcAta,
|
|
119
|
+
}),
|
|
120
|
+
),
|
|
121
|
+
[client],
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
// 3) Provider submits work.
|
|
125
|
+
await sendAndConfirmTransaction(
|
|
126
|
+
conn,
|
|
127
|
+
new Transaction().add(
|
|
128
|
+
buildSubmitWorkIx(provider.publicKey, jobId, "ipfs://Qm...submission..."),
|
|
129
|
+
),
|
|
130
|
+
[provider],
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
// 4) Client accepts → Settled. Vault drains to Provider's ATA + closes.
|
|
134
|
+
await sendAndConfirmTransaction(
|
|
135
|
+
conn,
|
|
136
|
+
new Transaction().add(
|
|
137
|
+
buildAcceptWorkIx({
|
|
138
|
+
client: client.publicKey,
|
|
139
|
+
provider: provider.publicKey,
|
|
140
|
+
jobId,
|
|
141
|
+
providerTokenAccount: providerUsdcAta,
|
|
142
|
+
}),
|
|
143
|
+
),
|
|
144
|
+
[client],
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
// Verify on-chain state.
|
|
148
|
+
const job = await fetchJob(conn, jobId);
|
|
149
|
+
console.log(job?.state); // "Settled"
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Run our exact smoke test against your wallet:
|
|
153
|
+
|
|
154
|
+
```sh
|
|
155
|
+
git clone https://github.com/Telaro-Protocol/Telaro-Protocol
|
|
156
|
+
cd Telaro-Protocol
|
|
157
|
+
pnpm install
|
|
158
|
+
pnpm --filter @telaro/sacp run smoke
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Output ends with four Solana Explorer links you can click through.
|
|
162
|
+
|
|
163
|
+
### Other verified paths
|
|
164
|
+
|
|
165
|
+
The repository also ships three additional smoke tests covering every
|
|
166
|
+
branch of the state machine. All four pass against the devnet
|
|
167
|
+
deployment.
|
|
168
|
+
|
|
169
|
+
```sh
|
|
170
|
+
pnpm --filter @telaro/sacp run smoke # happy path (4 tx, real SPL escrow)
|
|
171
|
+
pnpm --filter @telaro/sacp run smoke:dispute # dispute → verdict + record_verdict (5 tx)
|
|
172
|
+
pnpm --filter @telaro/sacp run smoke:reclaim # client reclaim after timeout (2 tx + deadline guard)
|
|
173
|
+
pnpm --filter @telaro/sacp run smoke:validation # validator register/stake/challenge/resolve, 70/30 slash (2 scenarios)
|
|
174
|
+
pnpm --filter @telaro/sacp run smoke:bonded # bonded validator → CPI bridge → settlement (7 tx)
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## Disputes
|
|
180
|
+
|
|
181
|
+
When the Client rejects the submitted work:
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
import { buildDisputeWorkIx, buildSubmitVerdictIx } from "@telaro/sacp/job";
|
|
185
|
+
|
|
186
|
+
// Instead of accept_work, Client disputes with a reason hash
|
|
187
|
+
// (e.g. SHA-256 of a public reason document).
|
|
188
|
+
const reasonHash = new Uint8Array(32); // populate with real hash
|
|
189
|
+
await sendAndConfirmTransaction(
|
|
190
|
+
conn,
|
|
191
|
+
new Transaction().add(buildDisputeWorkIx(client.publicKey, jobId, reasonHash)),
|
|
192
|
+
[client],
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
// Evaluator then settles. Winner = "Client" or "Provider".
|
|
196
|
+
await sendAndConfirmTransaction(
|
|
197
|
+
conn,
|
|
198
|
+
new Transaction().add(buildSubmitVerdictIx(evaluator.publicKey, jobId, "Provider")),
|
|
199
|
+
[evaluator],
|
|
200
|
+
);
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Validation Registry (Part B)
|
|
206
|
+
|
|
207
|
+
`@telaro/sacp/validation` exposes the bonded-validator surface. A
|
|
208
|
+
Validator PDA can be wired into a Job's `evaluator` slot, which puts
|
|
209
|
+
the validator's bond at risk every time it issues a verdict.
|
|
210
|
+
|
|
211
|
+
```ts
|
|
212
|
+
import { findValidatorPda } from "@telaro/sacp/validation";
|
|
213
|
+
|
|
214
|
+
const operator = Keypair.generate();
|
|
215
|
+
const [validatorPda] = findValidatorPda(operator.publicKey);
|
|
216
|
+
|
|
217
|
+
// validatorPda can now be used as the `evaluator` in buildCreateJobIx.
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
The full validator-side flow (register, stake, accept verdict,
|
|
221
|
+
respond to challenge) is in
|
|
222
|
+
[`src/validation.ts`](https://github.com/Telaro-Protocol/Telaro-Protocol/blob/main/packages/sacp/src/validation.ts).
|
|
223
|
+
See [INTEGRATION.md](https://github.com/Telaro-Protocol/Telaro-Protocol/blob/main/packages/sacp/INTEGRATION.md)
|
|
224
|
+
for the validator operator runbook.
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## ERC-8183 compatibility
|
|
229
|
+
|
|
230
|
+
The Job state machine, role definitions, and dispute outcomes map 1:1
|
|
231
|
+
to ERC-8183. See the spec's
|
|
232
|
+
[ERC-8183 mapping table](https://github.com/Telaro-Protocol/Telaro-Protocol/blob/main/docs/srfc/sacp-v0.1.md#erc-8183-mapping).
|
|
233
|
+
|
|
234
|
+
Cross-chain dispatch (a Base-side ERC-8183 Job that addresses a
|
|
235
|
+
Solana-side Provider) is informative in v0.1 and normative in v0.2,
|
|
236
|
+
piggybacking the existing Telaro → Base Wormhole bridge.
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## How to verify the SDK matches the on-chain program
|
|
241
|
+
|
|
242
|
+
The SDK does not depend on a generated Anchor IDL. Instead, it builds
|
|
243
|
+
instructions from raw Borsh + the standard Anchor 0.30 discriminator
|
|
244
|
+
(`sha256("global:<name>")[..8]`). The PDAs are derived from the same
|
|
245
|
+
seeds declared in the Rust source. If you want to verify the SDK and
|
|
246
|
+
program agree, the smoke test does exactly that. it confirms every
|
|
247
|
+
state transition by reading the Job account directly after each call.
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
## License
|
|
252
|
+
|
|
253
|
+
MIT (spec) · Apache-2.0 (this SDK).
|
package/dist/codec.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Low-level codec utilities for talking to the sACP Anchor programs
|
|
3
|
+
* without depending on a generated IDL.
|
|
4
|
+
*
|
|
5
|
+
* Anchor 0.30 instruction encoding:
|
|
6
|
+
* 1. 8-byte discriminator = sha256("global:<instruction_name>")[..8]
|
|
7
|
+
* 2. Followed by Borsh-encoded args in declaration order.
|
|
8
|
+
*
|
|
9
|
+
* We hand-roll the encoders here so the SDK has zero coupling to the
|
|
10
|
+
* Anchor build pipeline. That matters because Anchor 0.30 + recent
|
|
11
|
+
* rustc can't always emit an IDL (see anchor-syn issue with
|
|
12
|
+
* proc-macro2 `source_file`), and we don't want our SDK to inherit
|
|
13
|
+
* that fragility.
|
|
14
|
+
*
|
|
15
|
+
* All numeric encoders write little-endian, matching Solana's wire
|
|
16
|
+
* convention.
|
|
17
|
+
*/
|
|
18
|
+
export declare function anchorDiscriminator(instructionName: string): Buffer;
|
|
19
|
+
export declare function encodeU8(value: number): Buffer;
|
|
20
|
+
export declare function encodeU64(value: bigint): Buffer;
|
|
21
|
+
export declare function encodeI64(value: bigint): Buffer;
|
|
22
|
+
/**
|
|
23
|
+
* Borsh string: 4-byte little-endian length followed by UTF-8 bytes.
|
|
24
|
+
* Spec mandates URIs ≤ 256 bytes; we enforce in the high-level builder
|
|
25
|
+
* rather than the codec.
|
|
26
|
+
*/
|
|
27
|
+
export declare function encodeString(value: string): Buffer;
|
|
28
|
+
/**
|
|
29
|
+
* 32-byte fixed array, used for hashes. Throws if input isn't exactly
|
|
30
|
+
* 32 bytes — saner than silently truncating.
|
|
31
|
+
*/
|
|
32
|
+
export declare function encodeBytes32(bytes: Uint8Array): Buffer;
|
|
33
|
+
/** Concatenate the discriminator with serialized args. */
|
|
34
|
+
export declare function encodeInstruction(name: string, ...args: Buffer[]): Buffer;
|
|
35
|
+
//# sourceMappingURL=codec.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codec.d.ts","sourceRoot":"","sources":["../src/codec.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;GAgBG;AAEH,wBAAgB,mBAAmB,CAAC,eAAe,EAAE,MAAM,GAAG,MAAM,CAGnE;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAI9C;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAI/C;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAI/C;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAKlD;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAKvD;AAED,0DAA0D;AAC1D,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,CAEzE"}
|
package/dist/codec.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { sha256 } from "@noble/hashes/sha256";
|
|
2
|
+
/**
|
|
3
|
+
* Low-level codec utilities for talking to the sACP Anchor programs
|
|
4
|
+
* without depending on a generated IDL.
|
|
5
|
+
*
|
|
6
|
+
* Anchor 0.30 instruction encoding:
|
|
7
|
+
* 1. 8-byte discriminator = sha256("global:<instruction_name>")[..8]
|
|
8
|
+
* 2. Followed by Borsh-encoded args in declaration order.
|
|
9
|
+
*
|
|
10
|
+
* We hand-roll the encoders here so the SDK has zero coupling to the
|
|
11
|
+
* Anchor build pipeline. That matters because Anchor 0.30 + recent
|
|
12
|
+
* rustc can't always emit an IDL (see anchor-syn issue with
|
|
13
|
+
* proc-macro2 `source_file`), and we don't want our SDK to inherit
|
|
14
|
+
* that fragility.
|
|
15
|
+
*
|
|
16
|
+
* All numeric encoders write little-endian, matching Solana's wire
|
|
17
|
+
* convention.
|
|
18
|
+
*/
|
|
19
|
+
export function anchorDiscriminator(instructionName) {
|
|
20
|
+
const digest = sha256(new TextEncoder().encode(`global:${instructionName}`));
|
|
21
|
+
return Buffer.from(digest.subarray(0, 8));
|
|
22
|
+
}
|
|
23
|
+
export function encodeU8(value) {
|
|
24
|
+
const buf = Buffer.alloc(1);
|
|
25
|
+
buf.writeUInt8(value & 0xff);
|
|
26
|
+
return buf;
|
|
27
|
+
}
|
|
28
|
+
export function encodeU64(value) {
|
|
29
|
+
const buf = Buffer.alloc(8);
|
|
30
|
+
buf.writeBigUInt64LE(value);
|
|
31
|
+
return buf;
|
|
32
|
+
}
|
|
33
|
+
export function encodeI64(value) {
|
|
34
|
+
const buf = Buffer.alloc(8);
|
|
35
|
+
buf.writeBigInt64LE(value);
|
|
36
|
+
return buf;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Borsh string: 4-byte little-endian length followed by UTF-8 bytes.
|
|
40
|
+
* Spec mandates URIs ≤ 256 bytes; we enforce in the high-level builder
|
|
41
|
+
* rather than the codec.
|
|
42
|
+
*/
|
|
43
|
+
export function encodeString(value) {
|
|
44
|
+
const utf8 = Buffer.from(value, "utf8");
|
|
45
|
+
const len = Buffer.alloc(4);
|
|
46
|
+
len.writeUInt32LE(utf8.length);
|
|
47
|
+
return Buffer.concat([len, utf8]);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* 32-byte fixed array, used for hashes. Throws if input isn't exactly
|
|
51
|
+
* 32 bytes — saner than silently truncating.
|
|
52
|
+
*/
|
|
53
|
+
export function encodeBytes32(bytes) {
|
|
54
|
+
if (bytes.length !== 32) {
|
|
55
|
+
throw new Error(`encodeBytes32: expected 32 bytes, got ${bytes.length}`);
|
|
56
|
+
}
|
|
57
|
+
return Buffer.from(bytes);
|
|
58
|
+
}
|
|
59
|
+
/** Concatenate the discriminator with serialized args. */
|
|
60
|
+
export function encodeInstruction(name, ...args) {
|
|
61
|
+
return Buffer.concat([anchorDiscriminator(name), ...args]);
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=codec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codec.js","sourceRoot":"","sources":["../src/codec.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAE9C;;;;;;;;;;;;;;;;GAgBG;AAEH,MAAM,UAAU,mBAAmB,CAAC,eAAuB;IACzD,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,UAAU,eAAe,EAAE,CAAC,CAAC,CAAC;IAC7E,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;AAC5C,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,KAAa;IACpC,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC5B,GAAG,CAAC,UAAU,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;IAC7B,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,KAAa;IACrC,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC5B,GAAG,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC;IAC5B,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,KAAa;IACrC,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC5B,GAAG,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;IAC3B,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IACxC,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC5B,GAAG,CAAC,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC/B,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC;AACpC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,KAAiB;IAC7C,IAAI,KAAK,CAAC,MAAM,KAAK,EAAE,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CAAC,yCAAyC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;IAC3E,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAC5B,CAAC;AAED,0DAA0D;AAC1D,MAAM,UAAU,iBAAiB,CAAC,IAAY,EAAE,GAAG,IAAc;IAC/D,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,mBAAmB,CAAC,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC;AAC7D,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { PublicKey } from "@solana/web3.js";
|
|
2
|
+
/**
|
|
3
|
+
* sACP program IDs.
|
|
4
|
+
*
|
|
5
|
+
* v0.1 deployments live on Solana devnet. Mainnet program IDs will
|
|
6
|
+
* be added at v1.0; the SDK is version-locked, so a v0.1 SDK only
|
|
7
|
+
* talks to a v0.1 deployment.
|
|
8
|
+
*
|
|
9
|
+
* Deployed: 2026-06-04, slot 467078075 (job) / 467078115 (validation).
|
|
10
|
+
*/
|
|
11
|
+
export declare const SACP_PROGRAM_ID: PublicKey;
|
|
12
|
+
export declare const SACP_VALIDATION_PROGRAM_ID: PublicKey;
|
|
13
|
+
/** Job state machine seed namespace — keeps PDAs cleanly separated. */
|
|
14
|
+
export declare const JOB_SEED_PREFIX = "job";
|
|
15
|
+
export declare const ESCROW_SEED_PREFIX = "escrow";
|
|
16
|
+
export declare const ESCROW_VAULT_SEED_PREFIX = "vault";
|
|
17
|
+
export declare const SUBMISSION_SEED_PREFIX = "submission";
|
|
18
|
+
export declare const VERDICT_SEED_PREFIX = "verdict";
|
|
19
|
+
export declare const CONFIG_SEED_PREFIX = "config";
|
|
20
|
+
export declare const OFFERING_SEED_PREFIX = "offering";
|
|
21
|
+
export declare const NEGOTIATION_SEED_PREFIX = "negotiation";
|
|
22
|
+
/** Validation registry seed namespace. */
|
|
23
|
+
export declare const VALIDATOR_SEED_PREFIX = "validator";
|
|
24
|
+
export declare const BOND_SEED_PREFIX = "bond";
|
|
25
|
+
export declare const BOND_AUTHORITY_SEED_PREFIX = "bond_authority";
|
|
26
|
+
export declare const BOND_VAULT_SEED_PREFIX = "bond_vault";
|
|
27
|
+
export declare const CHALLENGE_SEED_PREFIX = "challenge";
|
|
28
|
+
export declare const TREASURY_AUTHORITY_SEED_PREFIX = "treasury_authority";
|
|
29
|
+
export declare const TREASURY_VAULT_SEED_PREFIX = "treasury_vault";
|
|
30
|
+
export declare const RESOLVER_CONFIG_SEED_PREFIX = "resolver_config";
|
|
31
|
+
/**
|
|
32
|
+
* Default windows (seconds) baked into v0.1. These can be overridden
|
|
33
|
+
* per-job at `create_job` time; the constants are the protocol-level
|
|
34
|
+
* defaults that match the spec.
|
|
35
|
+
*/
|
|
36
|
+
export declare const DEFAULT_SUBMIT_WINDOW_SECS: number;
|
|
37
|
+
export declare const DEFAULT_ACCEPT_WINDOW_SECS: number;
|
|
38
|
+
export declare const DEFAULT_DISPUTE_WINDOW_SECS: number;
|
|
39
|
+
export declare const DEFAULT_CHALLENGE_WINDOW_SECS: number;
|
|
40
|
+
/** Slash distribution constants (basis points). */
|
|
41
|
+
export declare const SLASH_TO_CHALLENGER_BPS = 7000;
|
|
42
|
+
export declare const SLASH_TO_TREASURY_BPS = 3000;
|
|
43
|
+
/** Protocol take-rate constants. Program-enforced cap; the authority
|
|
44
|
+
* can choose any value in [0, MAX_TAKE_RATE_BPS]. */
|
|
45
|
+
export declare const FEE_DENOMINATOR_BPS = 10000;
|
|
46
|
+
export declare const MAX_TAKE_RATE_BPS = 500;
|
|
47
|
+
/** Maximum fraction of the protocol fee routable to the evaluator
|
|
48
|
+
* (v0.7+). 10_000 bps = 100% of the fee. */
|
|
49
|
+
export declare const MAX_VALIDATOR_SHARE_BPS = 10000;
|
|
50
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C;;;;;;;;GAQG;AAEH,eAAO,MAAM,eAAe,WAE3B,CAAC;AAEF,eAAO,MAAM,0BAA0B,WAEtC,CAAC;AAEF,uEAAuE;AACvE,eAAO,MAAM,eAAe,QAAQ,CAAC;AACrC,eAAO,MAAM,kBAAkB,WAAW,CAAC;AAC3C,eAAO,MAAM,wBAAwB,UAAU,CAAC;AAChD,eAAO,MAAM,sBAAsB,eAAe,CAAC;AACnD,eAAO,MAAM,mBAAmB,YAAY,CAAC;AAC7C,eAAO,MAAM,kBAAkB,WAAW,CAAC;AAC3C,eAAO,MAAM,oBAAoB,aAAa,CAAC;AAC/C,eAAO,MAAM,uBAAuB,gBAAgB,CAAC;AAErD,0CAA0C;AAC1C,eAAO,MAAM,qBAAqB,cAAc,CAAC;AACjD,eAAO,MAAM,gBAAgB,SAAS,CAAC;AACvC,eAAO,MAAM,0BAA0B,mBAAmB,CAAC;AAC3D,eAAO,MAAM,sBAAsB,eAAe,CAAC;AACnD,eAAO,MAAM,qBAAqB,cAAc,CAAC;AACjD,eAAO,MAAM,8BAA8B,uBAAuB,CAAC;AACnE,eAAO,MAAM,0BAA0B,mBAAmB,CAAC;AAC3D,eAAO,MAAM,2BAA2B,oBAAoB,CAAC;AAE7D;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,QAAe,CAAC;AACvD,eAAO,MAAM,0BAA0B,QAAe,CAAC;AACvD,eAAO,MAAM,2BAA2B,QAAmB,CAAC;AAC5D,eAAO,MAAM,6BAA6B,QAAe,CAAC;AAE1D,mDAAmD;AACnD,eAAO,MAAM,uBAAuB,OAAQ,CAAC;AAC7C,eAAO,MAAM,qBAAqB,OAAQ,CAAC;AAE3C;qDACqD;AACrD,eAAO,MAAM,mBAAmB,QAAS,CAAC;AAC1C,eAAO,MAAM,iBAAiB,MAAM,CAAC;AACrC;4CAC4C;AAC5C,eAAO,MAAM,uBAAuB,QAAS,CAAC"}
|