@officexapp/vidfarm-devcli 0.21.64 → 0.21.65
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/.agents/skills/dollarplatoon-skill/SKILL.md +26 -4
- package/.agents/skills/dollarplatoon-skill/SOURCE.md +27 -1
- package/.agents/skills/dollarplatoon-skill/skill/clients.md +13 -1
- package/.agents/skills/dollarplatoon-skill/skill/gigs.md +153 -6
- package/.agents/skills/dollarplatoon-skill/skill/gigworkers.md +29 -2
- package/.agents/skills/dollarplatoon-skill/skill/orders.md +191 -5
- package/.agents/skills/dollarplatoon-skill/skill/quickstart.md +3 -3
- package/.agents/skills/dollarplatoon-skill/skill/staging.md +28 -20
- package/.agents/skills/vidfarm/SKILL.md +1 -1
- package/SKILL.director.md +1 -1
- package/SKILL.md +1 -1
- package/package.json +3 -1
|
@@ -35,7 +35,7 @@ production's identical copy, so read siblings from the host you started on if th
|
|
|
35
35
|
- **Auth:** an `x-api-key` header on every authenticated call. Get a key at
|
|
36
36
|
[dollarplatoon.com/settings](https://dollarplatoon.com/settings).
|
|
37
37
|
- **Staging:** `https://staging.dollarplatoon.com/api` — same code, Base Sepolia, its own
|
|
38
|
-
accounts, and
|
|
38
|
+
accounts, and test money you cannot lose. **Build here first:**
|
|
39
39
|
[skill/staging.md](https://dollarplatoon.com/skill/staging.md).
|
|
40
40
|
|
|
41
41
|
---
|
|
@@ -65,6 +65,13 @@ approve it. Every "the client sends work and pays for it" sentence in this skill
|
|
|
65
65
|
It is documented on its own page — [skill/orders.md](https://dollarplatoon.com/skill/orders.md) —
|
|
66
66
|
and the pages it contradicts say so where it matters.
|
|
67
67
|
|
|
68
|
+
**An order machine can also be free.** `list_price: 0` opens a shop that takes orders and touches
|
|
69
|
+
no contract at all: no deposit, no Treasury, no gas, and the buyer needs no wallet. It is a
|
|
70
|
+
different machine rather than a cheaper one — approval, not a payout, is what releases the
|
|
71
|
+
vendor's withheld deliverable, and approval is final. Paid shops still have a $0.02 floor, and
|
|
72
|
+
anything between the two is refused. See
|
|
73
|
+
[skill/orders.md](https://dollarplatoon.com/skill/orders.md).
|
|
74
|
+
|
|
68
75
|
**Your role is a property of the MACHINE, not of your session.** There is no client/worker toggle
|
|
69
76
|
and no persona in any URL: for each machine you are its Owner or a Participant in it. One account,
|
|
70
77
|
one set of pages. See [skill/web-pages.md](https://dollarplatoon.com/skill/web-pages.md).
|
|
@@ -135,7 +142,9 @@ deposited — there is no withdrawal.
|
|
|
135
142
|
> **On an order machine every one of these five changes.** The task price comes from the buyer's
|
|
136
143
|
> deposit rather than the gig; the fee comes **out of** that deposit rather than on top, so a
|
|
137
144
|
> $0.50 order pays the vendor $0.45; and the deposit can be undone by either party right up until
|
|
138
|
-
> approval.
|
|
145
|
+
> approval. On a **free** order machine (`list_price: 0`) there is no deposit, no fee and no
|
|
146
|
+
> payout at all — the buyer's approval settles the order and releases the deliverable. Check
|
|
147
|
+
> `gig.distribution` and `gig.list_price` before applying rules 3 and 5, and read
|
|
139
148
|
> [skill/orders.md](https://dollarplatoon.com/skill/orders.md).
|
|
140
149
|
|
|
141
150
|
---
|
|
@@ -151,14 +160,14 @@ Each file below is self-contained and linked directly from here. Open what you n
|
|
|
151
160
|
| [skill/quickstart.md](https://dollarplatoon.com/skill/quickstart.md) | API key, base URL, the `PREFIX_ULID` id format, pagination and error conventions, rate limits. Read once. |
|
|
152
161
|
| [skill/clients.md](https://dollarplatoon.com/skill/clients.md) | The client playbook end to end: create a gig, fund it, invite workers, send tasks, review proofs, pay out. Includes a runnable walkthrough. |
|
|
153
162
|
| [skill/gigworkers.md](https://dollarplatoon.com/skill/gigworkers.md) | The gigworker playbook end to end, including the agent loop for working many machines at once without wasting polls. |
|
|
154
|
-
| [skill/orders.md](https://dollarplatoon.com/skill/orders.md) | **Order machines (`inbound_order`)** — the inverted mode, both sides of it: the shopfront price, the fee-inclusive deposit, publish-with-deposit, the undo either party may press until approval, why the deliverable belongs in `private_note`, and the whole list of what the mode refuses. |
|
|
163
|
+
| [skill/orders.md](https://dollarplatoon.com/skill/orders.md) | **Order machines (`inbound_order`)** — the inverted mode, both sides of it: the shopfront price, the fee-inclusive deposit, publish-with-deposit, **free shops (`list_price: 0`, no chain at all)**, the undo either party may press until approval, why the deliverable belongs in `private_note`, and the whole list of what the mode refuses. |
|
|
155
164
|
| [skill/staging.md](https://dollarplatoon.com/skill/staging.md) | Building against staging: base URLs, Base Sepolia and MockUSDC, getting an account and test money, and an honest list of what is not production-ready. |
|
|
156
165
|
|
|
157
166
|
### API reference, by domain
|
|
158
167
|
|
|
159
168
|
| File | What is in it |
|
|
160
169
|
|---|---|
|
|
161
|
-
| [skill/gigs.md](https://dollarplatoon.com/skill/gigs.md) | Gigs, invite links, mailboxes (joining and leaving), worker rate limits, task expiry, funding, the dashboard
|
|
170
|
+
| [skill/gigs.md](https://dollarplatoon.com/skill/gigs.md) | Gigs, invite links, mailboxes (joining and leaving), worker rate limits, task expiry, funding, the dashboard, and **every webhook event — where to register a URL, which way each event flows, and how to verify the signature**. |
|
|
162
171
|
| [skill/tasks.md](https://dollarplatoon.com/skill/tasks.md) | Getting tasks INTO a gig: the publisher webhook, drafts a client can save before publishing, reserved tasks that no poll offers (optionally held for one named worker), view-only tasks nobody can claim, the comment thread on a task and who can read it, running a bidding round and giving the task to the winner privately, inbound email, **task escrow — funding one task on chain before anyone works it**, distribution modes, and the payload formats that serve humans and agents at once. |
|
|
163
172
|
| [skill/queue.md](https://dollarplatoon.com/skill/queue.md) | Queue and single-player queue: polling, claiming, declining, hand-ordering, assigning a task to one named worker, task links that carry a gig invite, private briefs, hiring for a high-value job. |
|
|
164
173
|
| [skill/pricing-and-tags.md](https://dollarplatoon.com/skill/pricing-and-tags.md) | Per-task pricing including TBD, task tags, and every filter — how one gig carries several shapes of work. |
|
|
@@ -214,3 +223,16 @@ Participant (buys) Vendor (owns the machine, does the
|
|
|
214
223
|
└─ USDC pays out on Base ─────────────────────▶ │ paid_out_at is stamped
|
|
215
224
|
└─ private_note is released to the buyer
|
|
216
225
|
```
|
|
226
|
+
|
|
227
|
+
On a **free** machine (`list_price: 0`) the same picture stops three lines early. There is no
|
|
228
|
+
deposit to send and none to undo — cancelling simply closes the order — and no rollup and no
|
|
229
|
+
payout. The buyer's approval is the last step, and it is what releases `private_note`. Because
|
|
230
|
+
nothing follows it to close the verdict, that approval is **final**:
|
|
231
|
+
|
|
232
|
+
```
|
|
233
|
+
├─ publishes it with NO deposit ──────────────▶ │ receives it, does the work
|
|
234
|
+
│◀──────────────────────────────────────────────┤ delivers a proof, deliverable withheld
|
|
235
|
+
├─ may CANCEL, and so may the vendor, until here
|
|
236
|
+
└─ approves (or the review timeout does) ◀── the point of no return
|
|
237
|
+
└─ private_note is released to the buyer
|
|
238
|
+
```
|
|
@@ -1,6 +1,32 @@
|
|
|
1
1
|
# Provenance of this vendored skill
|
|
2
2
|
|
|
3
|
-
**Synced
|
|
3
|
+
**Synced on 2026-08-29 from the WORKING TREE at
|
|
4
|
+
`ZoomGTM/dollar-platoon/publish-officex/repos/dollarplatoon-skill`, not from a
|
|
5
|
+
host.**
|
|
6
|
+
|
|
7
|
+
## Read this before "correcting" the source
|
|
8
|
+
|
|
9
|
+
The webhook generation is not deployed anywhere yet. Dollar Platoon commit
|
|
10
|
+
`800bdec` ("Send signed webhook events") added the whole *Webhooks and events*
|
|
11
|
+
chapter to `gigs.md` and the order-webhook section to `orders.md`, and neither
|
|
12
|
+
host serves it. Measured the same day:
|
|
13
|
+
|
|
14
|
+
| Source | `gigs.md` | Webhook chapter |
|
|
15
|
+
|---|---|---|
|
|
16
|
+
| working tree `publish-officex/repos/dollarplatoon-skill` | 542 lines | **yes** |
|
|
17
|
+
| `staging.dollarplatoon.com` | 397 lines | no |
|
|
18
|
+
| `dollarplatoon.com` | one generation behind that | no |
|
|
19
|
+
|
|
20
|
+
**Re-running the curl loop below TODAY silently deletes that chapter**, and with
|
|
21
|
+
it the only description of `order_webhook_url`, `events_webhook_url` and
|
|
22
|
+
`order.auto_approved` — the event that names the state
|
|
23
|
+
`orderClosedAt` in `src/services/dollarplatoon-orders.ts` was written to catch.
|
|
24
|
+
Check `grep -c events_webhook_url skill/gigs.md` before and after any re-sync; if
|
|
25
|
+
it drops to zero, the host is still behind and the sync must come from the
|
|
26
|
+
working tree instead.
|
|
27
|
+
|
|
28
|
+
Everything below describes the previous generation and still applies to host
|
|
29
|
+
choice once the hosts catch up.
|
|
4
30
|
|
|
5
31
|
## Read this before "correcting" the source host
|
|
6
32
|
|
|
@@ -169,7 +169,19 @@ PATCH /gigs/:id/proofs/:proof_id
|
|
|
169
169
|
`"resubmitted": true`, and your review window restarts from that moment. Reviewing a proof ends
|
|
170
170
|
this — once you have approved or rejected, the verdict is yours alone.
|
|
171
171
|
- Automate it by setting `proof_webhook_url` on the gig and routing submissions to your own
|
|
172
|
-
validator or agent
|
|
172
|
+
validator or agent:
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
curl -X PATCH "https://dollarplatoon.com/api/gigs/$GIG_ID" \
|
|
176
|
+
-H "x-api-key: $API_KEY" -H "Content-Type: application/json" \
|
|
177
|
+
-d '{ "proof_webhook_url": "https://my-validator.example.com/proofs" }'
|
|
178
|
+
→ { "success": true, "webhook_secret": "whsec_..." } // returned when the gig had none
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Each submission POSTs `{ gig_id, proof_id, task_identifier, locked_price, private_note_locked }`,
|
|
182
|
+
signed with `X-DollarPlatoon-Signature`. Verify it before acting — the snippet, the header
|
|
183
|
+
format and every other event are in [gigs.md](https://dollarplatoon.com/skill/gigs.md). Read
|
|
184
|
+
the secret back at any time with `GET /gigs/:id`, which shows it to the owner only.
|
|
173
185
|
|
|
174
186
|
## Step 6 — pay out
|
|
175
187
|
|
|
@@ -8,6 +8,8 @@ worker's place inside it.
|
|
|
8
8
|
- Gig routes
|
|
9
9
|
- Create a gig
|
|
10
10
|
- Read and update a gig
|
|
11
|
+
- Webhooks and events
|
|
12
|
+
- Verifying a signature
|
|
11
13
|
- Invite links
|
|
12
14
|
- Security token
|
|
13
15
|
- Worker rate limits
|
|
@@ -84,14 +86,21 @@ POST /gigs
|
|
|
84
86
|
"webhook": "https://dollarplatoon.com/api/inbound/webhook/GIG_01HX...?token=abc123",
|
|
85
87
|
"invite_url": "https://dollarplatoon.com/gig/GIG_01HX.../join?invite=a1b2c3d4e5f6",
|
|
86
88
|
"join_policy": "invite", "price": 0.50, "status": "active"
|
|
87
|
-
}
|
|
89
|
+
},
|
|
90
|
+
"webhook_secret": "whsec_..."
|
|
88
91
|
}
|
|
89
92
|
```
|
|
90
93
|
|
|
94
|
+
`webhook_secret` signs every delivery to `proof_webhook_url` and `join_webhook_url`. Store it —
|
|
95
|
+
you can read it back later on `GET /gigs/:id`, but only as the owner. See **Webhooks and
|
|
96
|
+
events** below.
|
|
97
|
+
|
|
91
98
|
Creation runs a compliance check that blocks illegal content and warns on borderline content.
|
|
92
99
|
|
|
93
100
|
**`distribution: "inbound_order"` takes a different body.** Omit `price` — it is pinned to 0 —
|
|
94
|
-
and send `list_price` instead
|
|
101
|
+
and send `list_price` instead: either `0` for a **free shop**, which sends no deposit and needs no
|
|
102
|
+
Treasury, or at least `$0.02` for a paid one. Anything strictly between the two is refused, and a
|
|
103
|
+
shop cannot cross that line later. `review_timeout` must be a positive number of at
|
|
95
104
|
least 3600 seconds; `-1` is refused. `price_tbd`, `allow_price_offers`, a non-null `task_timeout`
|
|
96
105
|
and a non-zero `min_payout` are all rejected at the door rather than quietly ignored. The response
|
|
97
106
|
adds `vendor_mailbox_id` — your own mailbox in your own shop, because there you are the worker
|
|
@@ -126,6 +135,141 @@ there means "not synced", not "zero". On PATCH, `list_price` moves and `price` a
|
|
|
126
135
|
along with `distribution`, `contract_address`, `min_payout`, `task_timeout` and a `review_timeout`
|
|
127
136
|
of `-1`.
|
|
128
137
|
|
|
138
|
+
## Webhooks and events
|
|
139
|
+
|
|
140
|
+
Every event on this platform is delivered to **the party it is news for**, and who that is
|
|
141
|
+
depends on which way the gig points. On a normal gig the owner is the client who pays and the
|
|
142
|
+
mailbox holder is the worker. On an `inbound_order` shop those roles invert: the owner is the
|
|
143
|
+
**vendor** who does the work, and the buyer is a **participant** who funded one order.
|
|
144
|
+
|
|
145
|
+
### Where you register a URL
|
|
146
|
+
|
|
147
|
+
| Field | Lives on | Set by | Receives |
|
|
148
|
+
|---|---|---|---|
|
|
149
|
+
| `proof_webhook_url` | the gig | owner | `proof.submitted` |
|
|
150
|
+
| `join_webhook_url` | the gig | owner | `mailbox.joined` |
|
|
151
|
+
| `webhook` | a mailbox | that member | `task.assigned`, `task.pushed` |
|
|
152
|
+
| `events_webhook_url` | a mailbox | that member | `proof.approved`, `proof.rejected`, `payout.paid`, `order.withdrawn` |
|
|
153
|
+
| `order_webhook_url` | one order | the buyer who funded it | `order.delivered`, `order.auto_approved`, `order.paid_out`, `order.withdrawn` |
|
|
154
|
+
|
|
155
|
+
`webhook` and `events_webhook_url` are **two streams on purpose**. The first carries tasks and
|
|
156
|
+
has since the first release, so an agent parsing it as a task keeps working; the second carries
|
|
157
|
+
everything that happens to work you already did. Set either, both, or neither.
|
|
158
|
+
|
|
159
|
+
### Which way each event flows
|
|
160
|
+
|
|
161
|
+
| Event | On a normal gig it reaches | On an order machine it reaches |
|
|
162
|
+
|---|---|---|
|
|
163
|
+
| `task.assigned` / `task.pushed` | the worker given the task | **the vendor** — this is the new-order notice |
|
|
164
|
+
| `proof.submitted` | the client (gig owner) | the vendor's own endpoint, which is rarely useful |
|
|
165
|
+
| `order.delivered` | — | **the buyer**, when the vendor delivers |
|
|
166
|
+
| `proof.approved` / `proof.rejected` | the worker | the vendor, ruled on by the buyer |
|
|
167
|
+
| `order.auto_approved` | — | the buyer, when their review window ran out |
|
|
168
|
+
| `payout.paid` | the worker | the vendor |
|
|
169
|
+
| `order.paid_out` | — | the buyer; a withheld deliverable is now readable |
|
|
170
|
+
| `order.withdrawn` | — | **both**, with `withdrawn_by` naming who pressed it |
|
|
171
|
+
|
|
172
|
+
Delivery is **best effort**: two attempts, a 3-second timeout each, no durable queue and no
|
|
173
|
+
replay. Treat a webhook as a nudge to go and read the row, never as the only record.
|
|
174
|
+
|
|
175
|
+
### Set a gig's webhooks
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
curl -X PATCH "https://dollarplatoon.com/api/gigs/GIG_01HX..." \
|
|
179
|
+
-H "x-api-key: $API_KEY" -H "Content-Type: application/json" \
|
|
180
|
+
-d '{ "proof_webhook_url": "https://my-agent.example.com/proofs",
|
|
181
|
+
"join_webhook_url": "https://my-agent.example.com/joins" }'
|
|
182
|
+
→ { "success": true, "webhook_secret": "whsec_..." } // only on a gig that had none
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Every gig created from now on is born with a `webhook_secret`. An older gig is given one on the
|
|
186
|
+
first PATCH that touches either URL. Read it back at any time with `GET /gigs/:id` — **owner
|
|
187
|
+
only**, and the one webhook field that route returns.
|
|
188
|
+
|
|
189
|
+
### Set a mailbox's webhooks
|
|
190
|
+
|
|
191
|
+
Member-only. The gig owner can neither set nor read these.
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
curl -X PATCH "https://dollarplatoon.com/api/gigs/GIG_01HX.../mailboxes/MBX_01HX..." \
|
|
195
|
+
-H "x-api-key: $API_KEY" -H "Content-Type: application/json" \
|
|
196
|
+
-d '{ "webhook": "https://my-agent.example.com/tasks",
|
|
197
|
+
"events_webhook_url": "https://my-agent.example.com/events" }'
|
|
198
|
+
→ { "success": true, "webhook": "...", "events_webhook_url": "...", "webhook_secret": "whsec_..." }
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
`null` clears either field. Clearing **both** drops the secret, so setting one again mints a new
|
|
202
|
+
key — that is how you rotate. You can also read your own secret back on `GET /mailboxes/mine`.
|
|
203
|
+
|
|
204
|
+
**Vendors: this is the call that turns your shop on.** Opening an order machine creates your
|
|
205
|
+
mailbox for you with no webhook, so PATCH it once and every order that arrives POSTs to you.
|
|
206
|
+
|
|
207
|
+
### The payloads
|
|
208
|
+
|
|
209
|
+
`task.assigned` — a task was handed to your mailbox. On an order machine this is a new order.
|
|
210
|
+
|
|
211
|
+
```json
|
|
212
|
+
{ "gig_id": "GIG_01HX...", "mailbox_id": "MBX_01HX...", "message_id": "MSG_01HX...",
|
|
213
|
+
"gig_title": "Video edits", "distribution_mode": "assigned",
|
|
214
|
+
"forwarded_at": "2026-08-29T10:00:00.000Z", "payload": { "your": "task body" } }
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
`proof.approved` / `proof.rejected` — a verdict on work you submitted.
|
|
218
|
+
|
|
219
|
+
```json
|
|
220
|
+
{ "event": "proof.approved", "sent_at": "...", "gig_id": "GIG_01HX...",
|
|
221
|
+
"gig_title": "Video edits", "mailbox_id": "MBX_01HX...", "proof_id": "PRF_01HX...",
|
|
222
|
+
"task_identifier": "MSG_01HX...", "amount": 2.5, "feedback": "nice work",
|
|
223
|
+
"revised": false, "auto": false }
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
`amount` is `null` when the client approved without naming one. `revised: true` means this
|
|
227
|
+
overwrites an earlier verdict — always trust the latest. `auto: true` means a clock approved it,
|
|
228
|
+
not a person, and only that kind can still be reported.
|
|
229
|
+
|
|
230
|
+
`payout.paid` — the money moved. Approval is not payment; this is.
|
|
231
|
+
|
|
232
|
+
```json
|
|
233
|
+
{ "event": "payout.paid", "sent_at": "...", "gig_id": "GIG_01HX...", "mailbox_id": "MBX_01HX...",
|
|
234
|
+
"rollup_id": "RLP_01HX...", "gross_amount": 5.0, "net_amount": 4.5,
|
|
235
|
+
"wallet_address": "0x...", "tx_hash": "0x...", "proof_ids": ["PRF_01HX..."] }
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
`tx_hash` is `null` on an off-chain settlement. That is still a payout — do not read a missing
|
|
239
|
+
hash as a failure.
|
|
240
|
+
|
|
241
|
+
The `order.*` payloads are in [orders.md](https://dollarplatoon.com/skill/orders.md).
|
|
242
|
+
|
|
243
|
+
## Verifying a signature
|
|
244
|
+
|
|
245
|
+
Every delivery carries three headers:
|
|
246
|
+
|
|
247
|
+
```
|
|
248
|
+
X-DollarPlatoon-Event: proof.approved
|
|
249
|
+
X-DollarPlatoon-Delivery: 6f2a... # unique per attempt
|
|
250
|
+
X-DollarPlatoon-Signature: t=1756468800,v1=9c1f...
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
`v1` is an HMAC-SHA256 over the exact string `` `${t}.${raw_request_body}` `` keyed with your
|
|
254
|
+
secret. Sign the **raw body**, before any JSON parse — a re-serialised body will not match.
|
|
255
|
+
|
|
256
|
+
```js
|
|
257
|
+
const crypto = require("crypto");
|
|
258
|
+
|
|
259
|
+
function verify(rawBody, header, secret) {
|
|
260
|
+
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
|
|
261
|
+
const expected = crypto.createHmac("sha256", secret)
|
|
262
|
+
.update(`${parts.t}.${rawBody}`).digest("hex");
|
|
263
|
+
const a = Buffer.from(expected), b = Buffer.from(parts.v1 || "");
|
|
264
|
+
// Reject anything older than five minutes, or the signature can be replayed.
|
|
265
|
+
if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false;
|
|
266
|
+
return a.length === b.length && crypto.timingSafeEqual(a, b);
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
A delivery arrives **unsigned** when the destination has no secret — every gig and mailbox
|
|
271
|
+
registered before signing existed. Set the URL again to mint one.
|
|
272
|
+
|
|
129
273
|
## Invite links
|
|
130
274
|
|
|
131
275
|
Invite links gate who joins the private network. Two fields give you every mode:
|
|
@@ -304,11 +448,13 @@ POST /gigs/:id/mailboxes
|
|
|
304
448
|
"email": "john@example.com", // contact address for forwarded tasks
|
|
305
449
|
"invite": "a1b2c3d4e5f6", // required when join_policy is "invite"
|
|
306
450
|
"wallet_address": "0x...", // optional — hot wallet auto-provisioned if omitted
|
|
307
|
-
"webhook": "https://...", // optional —
|
|
451
|
+
"webhook": "https://...", // optional — tasks POST here
|
|
452
|
+
"events_webhook_url": "https://...", // optional — verdicts and payouts POST here
|
|
308
453
|
"notes": "I have experience with Reddit marketing",
|
|
309
454
|
"tags": ["urgent", "linkedin-batch"] // private to the worker
|
|
310
455
|
}
|
|
311
|
-
→ { "mailbox": { "id": "MBX_01HX...", "status": "active" }
|
|
456
|
+
→ { "mailbox": { "id": "MBX_01HX...", "status": "active" },
|
|
457
|
+
"webhook_secret": "whsec_..." } // only when you registered a URL. Keep it.
|
|
312
458
|
```
|
|
313
459
|
|
|
314
460
|
The invite is the gate. An invite gig rejects a join without a valid token (`403`); an
|
|
@@ -332,8 +478,9 @@ Useful for auto-provisioning a workspace or syncing a roster the moment somebody
|
|
|
332
478
|
pending mailbox, `"inactive"` disables it), and the rate-limit override.
|
|
333
479
|
|
|
334
480
|
**The worker** sets `tags`, the standing `filter_*` preferences (see
|
|
335
|
-
[pricing-and-tags.md](https://dollarplatoon.com/skill/pricing-and-tags.md)),
|
|
336
|
-
`wallet_address
|
|
481
|
+
[pricing-and-tags.md](https://dollarplatoon.com/skill/pricing-and-tags.md)),
|
|
482
|
+
`wallet_address`, and both webhook URLs. The owner cannot set or read the URLs, and never sees
|
|
483
|
+
`webhook_secret` at all.
|
|
337
484
|
|
|
338
485
|
Worker `tags` are **never returned to the gig owner**. They are for organising your own inbox.
|
|
339
486
|
|
|
@@ -52,12 +52,18 @@ POST /gigs/:id/mailboxes
|
|
|
52
52
|
{
|
|
53
53
|
"name": "my-agent",
|
|
54
54
|
"invite": "a1b2c3d4e5f6",
|
|
55
|
-
"webhook": "https://my-agent.example.com/tasks", // optional —
|
|
55
|
+
"webhook": "https://my-agent.example.com/tasks", // optional — tasks POST here
|
|
56
|
+
"events_webhook_url": "https://my-agent.example.com/events", // optional — verdicts, payouts
|
|
56
57
|
"tags": ["video", "urgent"] // optional — YOUR private labels
|
|
57
58
|
}
|
|
58
|
-
→ { "mailbox": { "id": "MBX_01HX...", "gig_id": "GIG_01HX...", "status": "active" }
|
|
59
|
+
→ { "mailbox": { "id": "MBX_01HX...", "gig_id": "GIG_01HX...", "status": "active" },
|
|
60
|
+
"webhook_secret": "whsec_..." } // save it — it signs your deliveries
|
|
59
61
|
```
|
|
60
62
|
|
|
63
|
+
Both URLs are yours alone: the gig owner can neither set nor read them, and never sees the
|
|
64
|
+
secret. You can add or change them later with
|
|
65
|
+
`PATCH /gigs/:id/mailboxes/:mbx_id`, and `null` clears either one.
|
|
66
|
+
|
|
61
67
|
- `status: "pending_approval"` means the gig requires the owner to let you in. You will receive
|
|
62
68
|
nothing until they do.
|
|
63
69
|
- Omit `wallet_address` and a hot wallet is created for you. Supply one to be paid at your own
|
|
@@ -248,6 +254,27 @@ licence key, the password, the download link. The client sees only `private_note
|
|
|
248
254
|
until `paid_out_at` is stamped on that proof; approving it is not enough. You can always read
|
|
249
255
|
your own note back. See [proofs.md](https://dollarplatoon.com/skill/proofs.md).
|
|
250
256
|
|
|
257
|
+
**Or be told, instead of polling.** Set `events_webhook_url` on your mailbox and the platform
|
|
258
|
+
POSTs you `proof.approved`, `proof.rejected` and `payout.paid` as they happen:
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
curl -X PATCH "https://dollarplatoon.com/api/gigs/$GIG_ID/mailboxes/$MBX_ID" \
|
|
262
|
+
-H "x-api-key: $API_KEY" -H "Content-Type: application/json" \
|
|
263
|
+
-d '{ "events_webhook_url": "https://my-agent.example.com/events" }'
|
|
264
|
+
→ { "success": true, "events_webhook_url": "...", "webhook_secret": "whsec_..." }
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
```json
|
|
268
|
+
{ "event": "payout.paid", "sent_at": "...", "gig_id": "GIG_01HX...",
|
|
269
|
+
"mailbox_id": "MBX_01HX...", "rollup_id": "RLP_01HX...", "gross_amount": 5.0,
|
|
270
|
+
"net_amount": 4.5, "wallet_address": "0x...", "tx_hash": "0x...",
|
|
271
|
+
"proof_ids": ["PRF_01HX..."] }
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Verify the `X-DollarPlatoon-Signature` header before you trust one, and still reconcile against
|
|
275
|
+
the API — delivery is best effort, with no durable retry. The verification snippet and every
|
|
276
|
+
payload are in [gigs.md](https://dollarplatoon.com/skill/gigs.md).
|
|
277
|
+
|
|
251
278
|
---
|
|
252
279
|
|
|
253
280
|
## Running as an autonomous agent
|
|
@@ -11,6 +11,8 @@ here in both halves.
|
|
|
11
11
|
- Routes
|
|
12
12
|
- Open a shop — the vendor's side
|
|
13
13
|
- `list_price`, and why `gig.price` is pinned to 0
|
|
14
|
+
- Free shops (`list_price: 0`)
|
|
15
|
+
- Closing a PAID order machine
|
|
14
16
|
- Fee-INCLUSIVE pricing: what the deposit actually buys
|
|
15
17
|
- The order lifecycle
|
|
16
18
|
- Place an order — the participant's side, end to end
|
|
@@ -19,6 +21,7 @@ here in both halves.
|
|
|
19
21
|
- `private_note` is the vendor's only protection
|
|
20
22
|
- Approve, then settle, then reveal
|
|
21
23
|
- Report and comments both work in both directions
|
|
24
|
+
- Webhooks: run the shop without polling
|
|
22
25
|
- What this mode refuses, and why a `409` is an answer
|
|
23
26
|
- Fields you will see on an order
|
|
24
27
|
- What is still missing
|
|
@@ -137,9 +140,91 @@ settled on chain, `paid_out_at` is never stamped, the withheld deliverable is ne
|
|
|
137
140
|
and the undo is already closed by the approval. The buyer pays, the vendor works, and the escrow
|
|
138
141
|
is stranded with no exit. $0.02 is the smallest deposit that leaves the vendor $0.01.
|
|
139
142
|
|
|
143
|
+
**`list_price: 0` is the one value below that floor which is allowed, and it is a different shop
|
|
144
|
+
rather than a cheaper one.** See [Free shops](#free-shops-list_price-0). Anything strictly between
|
|
145
|
+
`0` and `$0.02` is refused.
|
|
146
|
+
|
|
140
147
|
`PATCH /gigs/:id` moves `list_price` and refuses `price` (`409`, `reason: "inbound_order"`).
|
|
141
148
|
Repricing the shop **does not touch orders already funded** — their amounts were fixed by the
|
|
142
|
-
money that was actually sent, and are bounded on chain by it.
|
|
149
|
+
money that was actually sent, and are bounded on chain by it. A PATCH that would cross the
|
|
150
|
+
free/paid line in either direction is refused with `409 list_price_mode_change`.
|
|
151
|
+
|
|
152
|
+
## Free shops (`list_price: 0`)
|
|
153
|
+
|
|
154
|
+
A free order machine takes orders and **touches no contract at all**. No deposit, no Treasury, no
|
|
155
|
+
`DEPOSIT_ID_SECRET`, no gas, and the buyer needs no wallet.
|
|
156
|
+
|
|
157
|
+
That is the point of it rather than a side effect: a paid shop needs a Treasury carrying the
|
|
158
|
+
escrow surface, and a deployment whose Treasury has been drained cannot run one. A free shop runs
|
|
159
|
+
anywhere.
|
|
160
|
+
|
|
161
|
+
Everything the trap above describes is a property of escrow. A free order has no deposit, so
|
|
162
|
+
there is no escrow to strand, no rollup to settle, no `paid_out_at` to lose and no undo to close.
|
|
163
|
+
|
|
164
|
+
**Open one** exactly as above, with `list_price: 0`.
|
|
165
|
+
|
|
166
|
+
**Place an order** with the same two calls as a paid one — draft through the webhook, then
|
|
167
|
+
`POST /gigs/:id/tasks/:msgId/publish` — with **no body**. An `amount` other than `0` is refused
|
|
168
|
+
(`409`, `reason: "free_order"`), never silently dropped, so a buyer is never told they tipped when
|
|
169
|
+
they did not. The response carries `free: true`, `price: 0`, and no `deposit_id` or `tx_hash`.
|
|
170
|
+
|
|
171
|
+
What changes downstream:
|
|
172
|
+
|
|
173
|
+
| | Paid shop | Free shop |
|
|
174
|
+
|---|---|---|
|
|
175
|
+
| Marker on the sent order | `deposit_id` | `order_free: true` |
|
|
176
|
+
| Ledger row `state` | `pending` → `open` → `settled` | `free` (terminal) |
|
|
177
|
+
| Releases the withheld `private_note` | `paid_out_at`, after the payout | the buyer's **approval** |
|
|
178
|
+
| Rollups | one per settled order | none, ever (`409 free_order`) |
|
|
179
|
+
| Approval | revisable until a rollup carries it | **final** (`409 free_order_approved`) |
|
|
180
|
+
| Undo | returns the deposit on chain | closes the order, returns nothing |
|
|
181
|
+
| Closing the shop | allowed once nothing is unsettled | always allowed |
|
|
182
|
+
|
|
183
|
+
The approval being final is the one asymmetry worth reading twice. On a paid order the payout
|
|
184
|
+
stamp is what closes the verdict; a free order never gets one, and approval is what *releases the
|
|
185
|
+
deliverable* — so if approval stayed revisable, a buyer could read a withheld licence key and then
|
|
186
|
+
withdraw the verdict that unlocked it, repeatedly. Rejections stay revisable in both modes,
|
|
187
|
+
because a rejection releases nothing.
|
|
188
|
+
|
|
189
|
+
A free cancel writes **no reputation event**. Those are keyed on the depositor's wallet address,
|
|
190
|
+
and a free order has no depositor.
|
|
191
|
+
|
|
192
|
+
**A free shop can always be closed** — `PATCH /gigs/:id {"status":"closed"}`, or `DELETE /gigs/:id`,
|
|
193
|
+
which writes the same field. It has no deposits and no sweep.
|
|
194
|
+
|
|
195
|
+
Closing stops **new** orders only (`409`, "This shop is not open"). Orders already in flight run to
|
|
196
|
+
their end: auto-approval is a query over proof rows and never reads a gig, and delivery, the
|
|
197
|
+
verdict and the undo have no gig-status check either. Reopen with `{"status":"active"}`.
|
|
198
|
+
|
|
199
|
+
## Closing a PAID order machine
|
|
200
|
+
|
|
201
|
+
Allowed too, but only while no order is unsettled. Closing takes a gig out of `scanActiveGigs()`,
|
|
202
|
+
and two cron sweeps read that scan: `processApprovedProofs` (the payout backstop for a buyer who
|
|
203
|
+
approved and never pressed the button) and `reconcilePendingDeposits`. So the refusal asks about
|
|
204
|
+
the money, not about the mode:
|
|
205
|
+
|
|
206
|
+
| Deposit state | Blocks the close? | Why |
|
|
207
|
+
|---|---|---|
|
|
208
|
+
| `pending` | **yes** | money may be on chain with nothing recorded; only the reconciler resolves it |
|
|
209
|
+
| `open` | **yes** | in escrow, and the review timeout can still auto-approve it into a payout |
|
|
210
|
+
| `settled` | no | the chain already moved it |
|
|
211
|
+
| `withdrawn` | no | the buyer has it back |
|
|
212
|
+
| `free` | no | there never was any |
|
|
213
|
+
|
|
214
|
+
An **abandoned** row never blocks, whatever its state — the id was given up on and the salt
|
|
215
|
+
stepped. A ledger read that fails refuses the close (`409 ledger_unreadable`): "we could not tell"
|
|
216
|
+
is not "there is nothing there".
|
|
217
|
+
|
|
218
|
+
The refusal names the offending deposits so you know what to chase:
|
|
219
|
+
|
|
220
|
+
```json
|
|
221
|
+
{ "error": "This shop cannot be closed while 2 orders are unsettled. …",
|
|
222
|
+
"reason": "unsettled_orders", "unsettled": 2,
|
|
223
|
+
"deposits": [ { "deposit_id": "0x…", "state": "open", "amount": 0.5, "task_id": "TASK_…" } ] }
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
An `open` deposit clears by the buyer approving the delivery (or undoing the order); a `pending`
|
|
227
|
+
one clears when the reconciler catches up on the next cron run.
|
|
143
228
|
|
|
144
229
|
## Fee-INCLUSIVE pricing: what the deposit actually buys
|
|
145
230
|
|
|
@@ -252,7 +337,9 @@ outright (`403`) and refuse everybody once the draft is funded (`409`): at that
|
|
|
252
337
|
|
|
253
338
|
```json
|
|
254
339
|
POST /gigs/:id/tasks/:msgId/publish
|
|
255
|
-
{ "amount": 0.50, "wallet_alias_id": "..."
|
|
340
|
+
{ "amount": 0.50, "wallet_alias_id": "...",
|
|
341
|
+
"order_webhook_url": "https://my-app.example.com/callback", // optional, per order
|
|
342
|
+
"order_webhook_secret": "a-key-i-chose" } // optional, signs it
|
|
256
343
|
|
|
257
344
|
→ { "status": "assigned", "message_id": "TASK_01KX...", "published": true,
|
|
258
345
|
"deposit_id": "0x…", "tx_hash": "0x…",
|
|
@@ -497,6 +584,99 @@ work the same way. Read the role note at the top of this page before you render
|
|
|
497
584
|
buyer's comments are stored `client` and the vendor's are stored `gigworker`, which is the
|
|
498
585
|
inverse of what "owns the gig" would tell you.
|
|
499
586
|
|
|
587
|
+
## Webhooks: run the shop without polling
|
|
588
|
+
|
|
589
|
+
Both sides of an order machine can be driven by events. Full field-by-field reference, including
|
|
590
|
+
the signature scheme, is in [gigs.md](https://dollarplatoon.com/skill/gigs.md); this section is
|
|
591
|
+
the order-specific half.
|
|
592
|
+
|
|
593
|
+
**The direction rule.** `proof_webhook_url` on the gig belongs to the **gig owner**, who here is
|
|
594
|
+
the vendor. It is the wrong channel for a buyer, and pointing it at one would tell a seller about
|
|
595
|
+
their own delivery. The buyer's channel is on the **order**, and the vendor's is on their
|
|
596
|
+
**mailbox**.
|
|
597
|
+
|
|
598
|
+
### Vendor: be told when an order arrives
|
|
599
|
+
|
|
600
|
+
Opening a shop creates your own mailbox (`vendor_mailbox_id` in the create response) with no
|
|
601
|
+
webhook on it. One PATCH turns the shop on:
|
|
602
|
+
|
|
603
|
+
```bash
|
|
604
|
+
curl -X PATCH "https://dollarplatoon.com/api/gigs/$GIG_ID/mailboxes/$VENDOR_MAILBOX_ID" \
|
|
605
|
+
-H "x-api-key: $VENDOR_KEY" -H "Content-Type: application/json" \
|
|
606
|
+
-d '{ "webhook": "https://my-shop.example.com/orders",
|
|
607
|
+
"events_webhook_url": "https://my-shop.example.com/order-events" }'
|
|
608
|
+
→ { "success": true, "webhook_secret": "whsec_..." }
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
Every funded order now POSTs to `/orders` as `task.assigned`, carrying `message_id` — the order
|
|
612
|
+
you deliver a proof against. Verdicts (`proof.approved`, `proof.rejected`), payouts
|
|
613
|
+
(`payout.paid`) and cancellations (`order.withdrawn`) arrive on `/order-events`.
|
|
614
|
+
|
|
615
|
+
### Buyer: be told what happens to your order
|
|
616
|
+
|
|
617
|
+
Name a callback in the **publish** call — the one that funds the order. It is per order, because
|
|
618
|
+
you may hold more than one mailbox in a shop and "your webhook" would otherwise be ambiguous.
|
|
619
|
+
|
|
620
|
+
```bash
|
|
621
|
+
curl -X POST "https://dollarplatoon.com/api/gigs/$GIG_ID/tasks/$DRAFT_ID/publish" \
|
|
622
|
+
-H "x-api-key: $BUYER_KEY" -H "Content-Type: application/json" \
|
|
623
|
+
-d '{ "amount": 5.00,
|
|
624
|
+
"order_webhook_url": "https://my-app.example.com/orders/callback",
|
|
625
|
+
"order_webhook_secret": "a-key-i-chose" }'
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
`order_webhook_secret` is **yours to pick**, unlike every other secret on the platform, and it
|
|
629
|
+
signs every `order.*` event for this order. Omit it and the deliveries go out unsigned. Both
|
|
630
|
+
fields work identically on a free shop.
|
|
631
|
+
|
|
632
|
+
### The `order.*` payloads
|
|
633
|
+
|
|
634
|
+
`order.delivered` — the vendor submitted work. Review it, or let the clock do it for you.
|
|
635
|
+
|
|
636
|
+
```json
|
|
637
|
+
{ "event": "order.delivered", "sent_at": "...", "gig_id": "GIG_01HX...",
|
|
638
|
+
"gig_title": "Thumbnail shop", "task_id": "MSG_01HX...", "proof_id": "PRF_01HX...",
|
|
639
|
+
"price": 4.54, "deliverable_locked": true, "resubmitted": false }
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
`deliverable_locked: true` means the vendor withheld the file itself in `private_note`.
|
|
643
|
+
Approving does **not** open it on a paid shop — payment does. Wait for `order.paid_out`.
|
|
644
|
+
|
|
645
|
+
`order.auto_approved` — your review window ran out and the clock ruled for the vendor.
|
|
646
|
+
|
|
647
|
+
```json
|
|
648
|
+
{ "event": "order.auto_approved", "sent_at": "...", "gig_id": "GIG_01HX...",
|
|
649
|
+
"task_id": "MSG_01HX...", "proof_id": "PRF_01HX...", "price": 4.54, "reportable": true }
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
`reportable: true` is your remedy, and it is the only event that carries it: `POST
|
|
653
|
+
/gigs/:id/proofs/:proof_id/report` takes the delivery out of the payable set — but only until a
|
|
654
|
+
rollup carries it.
|
|
655
|
+
|
|
656
|
+
`order.paid_out` — the vendor was paid, and a withheld deliverable is now readable.
|
|
657
|
+
|
|
658
|
+
```json
|
|
659
|
+
{ "event": "order.paid_out", "sent_at": "...", "gig_id": "GIG_01HX...",
|
|
660
|
+
"task_id": "MSG_01HX...", "proof_id": "PRF_01HX...", "paid_to_vendor": 4.54,
|
|
661
|
+
"tx_hash": "0x...", "deliverable_unlocked": true }
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
On `deliverable_unlocked: true`, fetch `GET /gigs/:id/proofs/:proof_id` to read the note. A free
|
|
665
|
+
shop never sends this event — nothing settles, and approval is what releases the note there.
|
|
666
|
+
|
|
667
|
+
`order.withdrawn` — the order was cancelled and the money went back. **The one event both sides
|
|
668
|
+
receive.** Read `withdrawn_by` before you react, or you will handle your own cancellation as if
|
|
669
|
+
the other party had sent it.
|
|
670
|
+
|
|
671
|
+
```json
|
|
672
|
+
{ "event": "order.withdrawn", "sent_at": "...", "gig_id": "GIG_01HX...",
|
|
673
|
+
"task_id": "MSG_01HX...", "withdrawn_by": "participant", "withdrawn_at": "..." }
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
`withdrawn_by` is `"participant"` (the buyer cancelled), `"vendor"` (the shop declined the job),
|
|
677
|
+
or `"reconciler"` (a publish that never completed, cleaned up by the cron). The vendor's copy
|
|
678
|
+
also carries `mailbox_id`.
|
|
679
|
+
|
|
500
680
|
## What this mode refuses, and why a `409` is an answer
|
|
501
681
|
|
|
502
682
|
Roughly twenty owner-only levers are closed on an order machine. Every one of them answers
|
|
@@ -543,6 +723,10 @@ order's value is not readable by everyone who joined.
|
|
|
543
723
|
| `accepted_at` | When the vendor took delivery. There is no claim step in this mode. |
|
|
544
724
|
| `order_withdrawn_at` | Set on an undone order. Absent everywhere else. |
|
|
545
725
|
|
|
726
|
+
`order_webhook_url` and `order_webhook_secret` are **write-only**. They are stored on the order
|
|
727
|
+
and no route ever reads them back — not to the vendor, and not to the buyer who set them. Keep
|
|
728
|
+
your own record of what you registered.
|
|
729
|
+
|
|
546
730
|
On `GET /orders`, `state` is one of `pending | open | settled | withdrawn`, `vendor_payout` is
|
|
547
731
|
derived from the deposit's own `fee_bps`, and `tip_estimate` is explicitly nullable and
|
|
548
732
|
best-effort — it is measured against the shop's price **today**. The exact figure is on the task
|
|
@@ -557,9 +741,11 @@ lands somewhere unrelated.
|
|
|
557
741
|
|
|
558
742
|
Honest gaps, so you do not design around something that is not there.
|
|
559
743
|
|
|
560
|
-
- **
|
|
561
|
-
|
|
562
|
-
|
|
744
|
+
- **On production, only gigs created since 2026-08-29 can be shops.** `inbound_order` shipped to
|
|
745
|
+
production that day on a new Treasury. Gigs older than that are on the retired contract, which
|
|
746
|
+
has no escrow in its bytecode, and a publish-with-deposit there is refused `contract_no_escrow`.
|
|
747
|
+
This cannot be configured away and escrow cannot be moved between contracts — create a new gig.
|
|
748
|
+
See [staging.md](https://dollarplatoon.com/skill/staging.md).
|
|
563
749
|
- **No route lists a gig's deposits.** A vendor cannot enumerate who owes what; they see the
|
|
564
750
|
aggregate `escrowed_funds` and their own order inbox.
|
|
565
751
|
- **No vendor display name on the gig read.** `GET /gigs/:id` gives `owner_wallet_alias` only, so
|
|
@@ -44,9 +44,9 @@ curl -H "x-api-key: $DOLLAR_PLATOON_API_KEY" https://dollarplatoon.com/api/auth/
|
|
|
44
44
|
```
|
|
45
45
|
|
|
46
46
|
Staging is the same API at `https://staging.dollarplatoon.com/api`, with its own accounts and
|
|
47
|
-
its own testnet USDC on Base Sepolia. Use it for anything you would not want to pay for
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
its own testnet USDC on Base Sepolia. Use it for anything you would not want to pay for. Every
|
|
48
|
+
path in this skill is identical there; substitute the host.
|
|
49
|
+
See [staging.md](https://dollarplatoon.com/skill/staging.md).
|
|
50
50
|
|
|
51
51
|
## Identifier format
|
|
52
52
|
|
|
@@ -25,14 +25,16 @@ be wrong with.
|
|
|
25
25
|
| API | `https://staging.dollarplatoon.com/api` | `https://dollarplatoon.com/api` |
|
|
26
26
|
| Chain | **Base Sepolia** (chain id `84532`) | Base mainnet (chain id `8453`) |
|
|
27
27
|
| USDC | **MockUSDC** `0xE4E5…c6a8` — worthless by design | Real USDC `0x8335…2913` |
|
|
28
|
-
| Treasury | `0x932B9D4CA0e11D7859C43F7e58492F2C6206D485` | `
|
|
28
|
+
| Treasury | `0x932B9D4CA0e11D7859C43F7e58492F2C6206D485` | `0x42118E4F0c0E326c5c09d2aC5d764957025214A9` |
|
|
29
29
|
| Accounts | Entirely separate. Your production key does **not** work here. | |
|
|
30
|
-
| `inbound_order` | **Available** |
|
|
30
|
+
| `inbound_order` | **Available** | **Available** |
|
|
31
31
|
| Inbound email | `…@fwd.zoomgtm.com`, with a `staging.` infix in the address | same domain, no infix |
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
different chains. Nothing is shared between them.
|
|
33
|
+
Staging's MockUSDC address is `0xE4E5…c6a8`, which was also production's Treasury address until
|
|
34
|
+
2026-08-29. That was never a copy-paste error: it is the same deployer account at the same nonce
|
|
35
|
+
on two different chains. Nothing is shared between them. Production has since moved to
|
|
36
|
+
`0x42118E4F…14A9`, and `0xE4E5…c6a8` is retired there — so if you see that address in an older
|
|
37
|
+
document, check which chain and which role is meant.
|
|
36
38
|
|
|
37
39
|
Everything in the rest of this skill is written with production URLs. To read it as a staging
|
|
38
40
|
integrator, substitute the host — every path is identical. `POST /gigs` means
|
|
@@ -109,29 +111,35 @@ history. Compose it with `?hide_navbar=true&hide_logo=true` for an embed. See
|
|
|
109
111
|
|
|
110
112
|
## What staging has that production does not
|
|
111
113
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
of it, and would refuse: its Treasury migration has not run and its deposit-id secret is unset,
|
|
115
|
-
so a publish-with-deposit would `500` by design rather than take money it could not settle.
|
|
114
|
+
**Money that costs nothing to be wrong with.** That is now the main difference. `inbound_order`
|
|
115
|
+
shipped to production on 2026-08-29, so both stages carry it.
|
|
116
116
|
|
|
117
|
-
|
|
118
|
-
|
|
117
|
+
One thing to know if you read older notes: production runs **two Treasuries at once**. Gigs
|
|
118
|
+
created before 2026-08-29 live on the retired `0xE4E5…c6a8` and can never do escrow — that
|
|
119
|
+
contract predates the feature, and an order against one of those gigs is refused
|
|
120
|
+
`contract_no_escrow`. Gigs created since then are on `0x42118E4F…14A9` and have the full set. A
|
|
121
|
+
gig keeps the Treasury it was born on for life, so this is a property of the GIG, not of the
|
|
122
|
+
stage. `GET /gigs/:id` tells you which one you are on.
|
|
123
|
+
|
|
124
|
+
See [orders.md](https://dollarplatoon.com/skill/orders.md).
|
|
119
125
|
|
|
120
126
|
## What is NOT production-ready — read before you ship
|
|
121
127
|
|
|
122
128
|
Stated plainly, because the difference between the stages is not only "one has fake money".
|
|
123
129
|
|
|
124
|
-
- **The wallet encryption key rotation has not run
|
|
125
|
-
are encrypted with a key that is the published development default
|
|
126
|
-
|
|
130
|
+
- **The wallet encryption key rotation has not run on production.** Hot wallet private keys there
|
|
131
|
+
are encrypted with a key that is the published development default — 782 real wallets. The
|
|
132
|
+
dual-key read path that makes rotation possible is now deployed to both stages, but the rotation
|
|
133
|
+
itself has not been done on production. Staging has been rotated.
|
|
127
134
|
**Do not treat a Dollar Platoon hot wallet as cold storage on either stage.** Withdraw earnings
|
|
128
135
|
to a wallet you control (`POST /wallets/:alias_id/transfer`).
|
|
129
|
-
- **
|
|
130
|
-
which has no per-task deposits, no undo
|
|
131
|
-
|
|
132
|
-
- **
|
|
133
|
-
|
|
134
|
-
|
|
136
|
+
- **Gigs created before 2026-08-29 cannot do escrow, on production.** They live on the retired
|
|
137
|
+
Treasury, which has no per-task deposits, no undo and no reserved-balance guard. This is
|
|
138
|
+
permanent for those gigs — escrow cannot be migrated between contracts. Create a new gig.
|
|
139
|
+
- **Those older production gigs settle by hand, not on chain.** Their Treasury was drained on
|
|
140
|
+
2026-08-29, so they mint no payouts and refuse deposits; approved work there is paid directly to
|
|
141
|
+
worker wallets. `POST /gigs/:id/rollups` on one answers `409 settled_offchain`. New gigs are
|
|
142
|
+
unaffected and settle on chain as normal.
|
|
135
143
|
- **`GET /public/read-url` signs any S3 key for any share-token holder.** Unfixed on both stages.
|
|
136
144
|
Anything offloaded to S3 — a large task body, an uploaded file — should be treated as readable
|
|
137
145
|
by anyone holding any share token on the platform. Keep secrets in `private_details` and
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: vidfarm
|
|
3
|
-
skill_version: 0.21.
|
|
3
|
+
skill_version: 0.21.65
|
|
4
4
|
description: Use Vidfarm as a director. Run a strategy **consultation** (the `brainstorm/*` chain — cold-start interview, awareness stages, persuasive angles, hooks, product placement). Answer "give me content ideas" / "what should I post" / "I need 30 videos this month" from the bundled idea banks (50 content frames x 5 awareness stages x 44 problem angles). Browse/add inspiration videos, browse the free public raws catalog BY CATEGORY (curated shelves like scroll-stoppers/greenscreen/reaction — the cheapest way to source footage for one video, and a ready-made clip pool for bulk scripting N variants), fork a template into a composition, edit it in the Trackpad Editor (timeline-based like Premiere/DaVinci), auto-decompose source video into scenes, render to MP4, approve into a shareable post, and schedule it. Includes login, provider keys, discovery, versioning, uploads/downloads, and billing. Every step is available as raw REST; `vidfarm-devcli` wraps those routes and composes the file-backed scripting flows.
|
|
5
5
|
---
|
|
6
6
|
|
package/SKILL.director.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: vidfarm
|
|
3
|
-
skill_version: 0.21.
|
|
3
|
+
skill_version: 0.21.65
|
|
4
4
|
description: Use Vidfarm as a director. Run a strategy **consultation** (the `brainstorm/*` chain — cold-start interview, awareness stages, persuasive angles, hooks, product placement). Answer "give me content ideas" / "what should I post" / "I need 30 videos this month" from the bundled idea banks (50 content frames x 5 awareness stages x 44 problem angles). Browse/add inspiration videos, browse the free public raws catalog BY CATEGORY (curated shelves like scroll-stoppers/greenscreen/reaction — the cheapest way to source footage for one video, and a ready-made clip pool for bulk scripting N variants), fork a template into a composition, edit it in the Trackpad Editor (timeline-based like Premiere/DaVinci), auto-decompose source video into scenes, render to MP4, approve into a shareable post, and schedule it. Includes login, provider keys, discovery, versioning, uploads/downloads, and billing. Every step is available as raw REST; `vidfarm-devcli` wraps those routes and composes the file-backed scripting flows.
|
|
5
5
|
---
|
|
6
6
|
|
package/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: vidfarm
|
|
3
|
-
skill_version: 0.21.
|
|
3
|
+
skill_version: 0.21.65
|
|
4
4
|
description: Entry point for making, editing, and rendering short-form video with Vidfarm. Use when the task is to create a video, ad, explainer, or social clip; to edit or re-theme an existing composition; to source footage or audio; or to run a batch of video variants. Routes to the full director skill and the `vidfarm` CLI.
|
|
5
5
|
---
|
|
6
6
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@officexapp/vidfarm-devcli",
|
|
3
|
-
"version": "0.21.
|
|
3
|
+
"version": "0.21.65",
|
|
4
4
|
"description": "Local bridge for the Vidfarm Trackpad Editor. `vidfarm serve <template_id>` boots the FULL editor on localhost (disk-backed records/storage, free in-process render); edit composition.html on disk (Claude Code, Codex, etc.) and the browser live-morphs it.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -161,6 +161,8 @@
|
|
|
161
161
|
"test:order-decision": "node --import tsx --test test/order-decision.test.ts",
|
|
162
162
|
"test:reviews": "node --import tsx --test test/shop-reviews.test.ts",
|
|
163
163
|
"test:marketplace-api": "node --import tsx --test test/marketplace-api.test.ts",
|
|
164
|
+
"test:marketplace-regressions": "node --import tsx --test test/marketplace-prod-regressions.test.ts",
|
|
165
|
+
"test:new-shop-free-order": "node --import tsx --test test/new-shop-free-order.test.ts",
|
|
164
166
|
"test:notifications": "node --import tsx --test test/marketplace-notifications.test.ts",
|
|
165
167
|
"test:delivery": "node --import tsx --test test/delivery-items.test.ts",
|
|
166
168
|
"test:dporders": "node --import tsx --test test/dp-order-fee.test.ts",
|