@escro/sdk 0.1.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/README.md +65 -0
- package/dist/index.cjs +487 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +170 -0
- package/dist/index.d.ts +170 -0
- package/dist/index.js +455 -0
- package/dist/index.js.map +1 -0
- package/package.json +49 -0
package/README.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# @escro/sdk
|
|
2
|
+
|
|
3
|
+
TypeScript SDK for the [escro.ai](https://escro.ai) on-chain escrow protocol on Solana.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @escro/sdk @solana/web3.js
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { Escro, EscrowState } from "@escro/sdk";
|
|
15
|
+
import { Keypair } from "@solana/web3.js";
|
|
16
|
+
|
|
17
|
+
const client = new Escro({
|
|
18
|
+
rpcUrl: "https://api.devnet.solana.com",
|
|
19
|
+
apiUrl: "https://api.escro.ai",
|
|
20
|
+
wallet: Keypair.fromSecretKey(/* ... */),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// Buyer: create and fund an escrow
|
|
24
|
+
const { escrowPda, signature } = await client.createEscrow({
|
|
25
|
+
taskSpec: { /* ... */ },
|
|
26
|
+
amountUsdc: 10_000_000, // 10 USDC
|
|
27
|
+
deadlineSeconds: Math.floor(Date.now() / 1000) + 3600,
|
|
28
|
+
assignedWorker: "WorkerPubkeyBase58...",
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Worker: discover, claim, and submit
|
|
32
|
+
const { items } = await client.getMyTasks([EscrowState.FUNDED]);
|
|
33
|
+
await client.claimTask(items[0].address);
|
|
34
|
+
await client.submitDeliverable(items[0].address, {
|
|
35
|
+
contentHash: "sha256hex...",
|
|
36
|
+
proofUri: "ipfs://Qm...",
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Buyer: release payment or cancel
|
|
40
|
+
await client.releasePayment(escrowPda);
|
|
41
|
+
await client.cancelEscrow(escrowPda); // only if FUNDED, before worker claims
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## API
|
|
45
|
+
|
|
46
|
+
| Method | Role | Description |
|
|
47
|
+
|---|---|---|
|
|
48
|
+
| `createEscrow(params)` | Buyer | Create + fund escrow atomically |
|
|
49
|
+
| `releasePayment(address)` | Buyer | Approve payment to worker |
|
|
50
|
+
| `cancelEscrow(address)` | Buyer | Cancel funded escrow, full refund |
|
|
51
|
+
| `claimTask(address)` | Worker | Claim an assigned task |
|
|
52
|
+
| `submitDeliverable(address, params)` | Worker | Submit work for review |
|
|
53
|
+
| `getMyTasks(state?, options?)` | Worker | List assigned tasks (paginated) |
|
|
54
|
+
| `getEscrow(address, options?)` | Both | Fetch escrow by PDA address |
|
|
55
|
+
| `raiseDispute(address, params)` | Both | Escalate to dispute |
|
|
56
|
+
|
|
57
|
+
## Docs
|
|
58
|
+
|
|
59
|
+
- [Buyer Bot Guide](https://docs.escro.ai/buyer-bot)
|
|
60
|
+
- [Worker Bot Guide](https://docs.escro.ai/worker-bot)
|
|
61
|
+
- [SDK Reference](https://docs.escro.ai/sdk-reference)
|
|
62
|
+
|
|
63
|
+
## License
|
|
64
|
+
|
|
65
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
9
|
+
var __export = (target, all) => {
|
|
10
|
+
for (var name in all)
|
|
11
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
+
};
|
|
13
|
+
var __copyProps = (to, from, except, desc) => {
|
|
14
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
+
for (let key of __getOwnPropNames(from))
|
|
16
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
17
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
18
|
+
}
|
|
19
|
+
return to;
|
|
20
|
+
};
|
|
21
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
22
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
23
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
24
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
25
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
26
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
27
|
+
mod
|
|
28
|
+
));
|
|
29
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
30
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
31
|
+
|
|
32
|
+
// src/index.ts
|
|
33
|
+
var index_exports = {};
|
|
34
|
+
__export(index_exports, {
|
|
35
|
+
Escro: () => Escro,
|
|
36
|
+
EscroError: () => EscroError,
|
|
37
|
+
EscrowNotFoundError: () => EscrowNotFoundError,
|
|
38
|
+
EscrowStateError: () => EscrowStateError,
|
|
39
|
+
EscrowTimeoutError: () => EscrowTimeoutError,
|
|
40
|
+
UnauthorizedError: () => UnauthorizedError
|
|
41
|
+
});
|
|
42
|
+
module.exports = __toCommonJS(index_exports);
|
|
43
|
+
|
|
44
|
+
// src/client.ts
|
|
45
|
+
var import_web32 = require("@solana/web3.js");
|
|
46
|
+
|
|
47
|
+
// src/api.ts
|
|
48
|
+
var import_tweetnacl = __toESM(require("tweetnacl"), 1);
|
|
49
|
+
var import_bs58 = __toESM(require("bs58"), 1);
|
|
50
|
+
|
|
51
|
+
// src/errors.ts
|
|
52
|
+
var EscroError = class extends Error {
|
|
53
|
+
constructor(message, code = "ESCRO_ERROR") {
|
|
54
|
+
super(message);
|
|
55
|
+
this.code = code;
|
|
56
|
+
this.name = "EscroError";
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
var EscrowNotFoundError = class extends EscroError {
|
|
60
|
+
constructor(address) {
|
|
61
|
+
super(`Escrow not found: ${address}`, "ESCROW_NOT_FOUND");
|
|
62
|
+
this.name = "EscrowNotFoundError";
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
var EscrowStateError = class extends EscroError {
|
|
66
|
+
constructor(message) {
|
|
67
|
+
super(message, "ESCROW_INVALID_STATE");
|
|
68
|
+
this.name = "EscrowStateError";
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
var EscrowTimeoutError = class extends EscroError {
|
|
72
|
+
constructor(address) {
|
|
73
|
+
super(`Polling timed out after 24h for escrow: ${address}`, "ESCROW_TIMEOUT");
|
|
74
|
+
this.name = "EscrowTimeoutError";
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
var UnauthorizedError = class extends EscroError {
|
|
78
|
+
constructor(message = "Unauthorized") {
|
|
79
|
+
super(message, "UNAUTHORIZED");
|
|
80
|
+
this.name = "UnauthorizedError";
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// src/api.ts
|
|
85
|
+
function authHeaders(wallet, method, path) {
|
|
86
|
+
const timestamp = Date.now();
|
|
87
|
+
const message = `escro:${timestamp}:${method}:${path}`;
|
|
88
|
+
const msgBytes = new TextEncoder().encode(message);
|
|
89
|
+
const sigBytes = import_tweetnacl.default.sign.detached(msgBytes, wallet.secretKey);
|
|
90
|
+
return {
|
|
91
|
+
"x-wallet-address": wallet.publicKey.toBase58(),
|
|
92
|
+
"x-signature": import_bs58.default.encode(sigBytes),
|
|
93
|
+
"x-timestamp": String(timestamp),
|
|
94
|
+
"content-type": "application/json"
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
function deserialize(raw) {
|
|
98
|
+
return { ...raw, amount: BigInt(raw.amount) };
|
|
99
|
+
}
|
|
100
|
+
async function handleError(res, address) {
|
|
101
|
+
const body = await res.json().catch(() => ({}));
|
|
102
|
+
const msg = body.error ?? `HTTP ${res.status}`;
|
|
103
|
+
if (res.status === 404) throw new EscrowNotFoundError(address ?? msg);
|
|
104
|
+
if (res.status === 409) throw new EscrowStateError(msg);
|
|
105
|
+
if (res.status === 401 || res.status === 403) throw new UnauthorizedError(msg);
|
|
106
|
+
throw new EscroError(msg, body.code ?? "UNKNOWN");
|
|
107
|
+
}
|
|
108
|
+
var ApiClient = class {
|
|
109
|
+
constructor(baseUrl, wallet) {
|
|
110
|
+
this.baseUrl = baseUrl;
|
|
111
|
+
this.wallet = wallet;
|
|
112
|
+
}
|
|
113
|
+
url(path) {
|
|
114
|
+
return `${this.baseUrl}${path}`;
|
|
115
|
+
}
|
|
116
|
+
/** GET /v1/escrows/:address */
|
|
117
|
+
async getEscrow(address) {
|
|
118
|
+
const path = `/v1/escrows/${address}`;
|
|
119
|
+
const res = await fetch(this.url(path));
|
|
120
|
+
if (!res.ok) await handleError(res, address);
|
|
121
|
+
return deserialize(await res.json());
|
|
122
|
+
}
|
|
123
|
+
/** GET /v1/escrows?assignedTo=...&state=... */
|
|
124
|
+
async listEscrows(params) {
|
|
125
|
+
const qs = new URLSearchParams();
|
|
126
|
+
if (params.assignedTo) qs.set("assignedTo", params.assignedTo);
|
|
127
|
+
if (params.state) qs.set("state", params.state);
|
|
128
|
+
if (params.limit != null) qs.set("limit", String(params.limit));
|
|
129
|
+
if (params.offset != null) qs.set("offset", String(params.offset));
|
|
130
|
+
const path = `/v1/escrows?${qs.toString()}`;
|
|
131
|
+
const res = await fetch(this.url(path));
|
|
132
|
+
if (!res.ok) await handleError(res);
|
|
133
|
+
const body = await res.json();
|
|
134
|
+
return {
|
|
135
|
+
items: body.items.map(deserialize),
|
|
136
|
+
total: body.total,
|
|
137
|
+
limit: body.limit,
|
|
138
|
+
offset: body.offset
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* POST /v1/escrows — create escrow, returns unsigned tx + ids.
|
|
143
|
+
* Requires auth.
|
|
144
|
+
*/
|
|
145
|
+
async createEscrow(params) {
|
|
146
|
+
const path = "/v1/escrows";
|
|
147
|
+
const res = await fetch(this.url(path), {
|
|
148
|
+
method: "POST",
|
|
149
|
+
headers: authHeaders(this.wallet, "POST", path),
|
|
150
|
+
body: JSON.stringify({
|
|
151
|
+
taskSpec: params.taskSpec,
|
|
152
|
+
amountUsdc: params.amountUsdc,
|
|
153
|
+
deadlineSeconds: params.deadlineSeconds,
|
|
154
|
+
assignedWorker: params.assignedWorker
|
|
155
|
+
})
|
|
156
|
+
});
|
|
157
|
+
if (!res.ok) await handleError(res);
|
|
158
|
+
return res.json();
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* POST /v1/escrows/:address/claim — returns unsigned tx.
|
|
162
|
+
* Requires auth (worker).
|
|
163
|
+
*/
|
|
164
|
+
async claimTask(address) {
|
|
165
|
+
const path = `/v1/escrows/${address}/claim`;
|
|
166
|
+
const res = await fetch(this.url(path), {
|
|
167
|
+
method: "POST",
|
|
168
|
+
headers: authHeaders(this.wallet, "POST", path)
|
|
169
|
+
});
|
|
170
|
+
if (!res.ok) await handleError(res, address);
|
|
171
|
+
return res.json();
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* POST /v1/escrows/:address/submit — returns unsigned tx.
|
|
175
|
+
* Requires auth (worker).
|
|
176
|
+
*/
|
|
177
|
+
async submitDeliverable(address, params) {
|
|
178
|
+
const path = `/v1/escrows/${address}/submit`;
|
|
179
|
+
const res = await fetch(this.url(path), {
|
|
180
|
+
method: "POST",
|
|
181
|
+
headers: authHeaders(this.wallet, "POST", path),
|
|
182
|
+
body: JSON.stringify({
|
|
183
|
+
contentHash: params.contentHash,
|
|
184
|
+
proofUri: params.proofUri
|
|
185
|
+
})
|
|
186
|
+
});
|
|
187
|
+
if (!res.ok) await handleError(res, address);
|
|
188
|
+
return res.json();
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* POST /v1/escrows/:address/release — DB-only state update.
|
|
192
|
+
* Requires auth (buyer). No on-chain tx returned.
|
|
193
|
+
*/
|
|
194
|
+
async releasePaymentDb(address) {
|
|
195
|
+
const path = `/v1/escrows/${address}/release`;
|
|
196
|
+
const res = await fetch(this.url(path), {
|
|
197
|
+
method: "POST",
|
|
198
|
+
headers: authHeaders(this.wallet, "POST", path)
|
|
199
|
+
});
|
|
200
|
+
if (!res.ok) await handleError(res, address);
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* POST /v1/escrows/:address/cancel — returns unsigned tx.
|
|
204
|
+
* Requires auth (buyer). Only valid when state is FUNDED.
|
|
205
|
+
*/
|
|
206
|
+
async cancelEscrow(address) {
|
|
207
|
+
const path = `/v1/escrows/${address}/cancel`;
|
|
208
|
+
const res = await fetch(this.url(path), {
|
|
209
|
+
method: "POST",
|
|
210
|
+
headers: authHeaders(this.wallet, "POST", path)
|
|
211
|
+
});
|
|
212
|
+
if (!res.ok) await handleError(res, address);
|
|
213
|
+
return res.json();
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* POST /v1/escrows/:address/dispute — returns unsigned tx.
|
|
217
|
+
* Requires auth (buyer or worker).
|
|
218
|
+
*/
|
|
219
|
+
async raiseDispute(address, params) {
|
|
220
|
+
const path = `/v1/escrows/${address}/dispute`;
|
|
221
|
+
const res = await fetch(this.url(path), {
|
|
222
|
+
method: "POST",
|
|
223
|
+
headers: authHeaders(this.wallet, "POST", path),
|
|
224
|
+
body: JSON.stringify({ reason: params.reason, evidence: params.evidence })
|
|
225
|
+
});
|
|
226
|
+
if (!res.ok) await handleError(res, address);
|
|
227
|
+
return res.json();
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
// src/poll.ts
|
|
232
|
+
var MS_5_MIN = 5 * 60 * 1e3;
|
|
233
|
+
var MS_30_MIN = 30 * 60 * 1e3;
|
|
234
|
+
var MS_24_H = 24 * 60 * 60 * 1e3;
|
|
235
|
+
function backoffInterval(elapsedMs) {
|
|
236
|
+
if (elapsedMs < MS_5_MIN) return 15e3;
|
|
237
|
+
if (elapsedMs < MS_30_MIN) return 3e4;
|
|
238
|
+
return 6e4;
|
|
239
|
+
}
|
|
240
|
+
function sleep(ms) {
|
|
241
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
242
|
+
}
|
|
243
|
+
async function pollUntil(address, fetcher, predicate, timeoutMs = MS_24_H) {
|
|
244
|
+
const deadline = Date.now() + timeoutMs;
|
|
245
|
+
while (true) {
|
|
246
|
+
const result = await fetcher();
|
|
247
|
+
if (predicate(result)) return result;
|
|
248
|
+
const elapsed = Date.now() - (deadline - timeoutMs);
|
|
249
|
+
if (Date.now() >= deadline) {
|
|
250
|
+
throw new EscrowTimeoutError(address);
|
|
251
|
+
}
|
|
252
|
+
const interval = backoffInterval(elapsed);
|
|
253
|
+
const remaining = deadline - Date.now();
|
|
254
|
+
await sleep(Math.min(interval, remaining));
|
|
255
|
+
if (Date.now() >= deadline) {
|
|
256
|
+
throw new EscrowTimeoutError(address);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// src/solana.ts
|
|
262
|
+
var import_node_crypto = require("crypto");
|
|
263
|
+
var import_web3 = require("@solana/web3.js");
|
|
264
|
+
var import_spl_token = require("@solana/spl-token");
|
|
265
|
+
function disc(name) {
|
|
266
|
+
return Buffer.from(
|
|
267
|
+
(0, import_node_crypto.createHash)("sha256").update(`global:${name}`).digest()
|
|
268
|
+
).subarray(0, 8);
|
|
269
|
+
}
|
|
270
|
+
var DISC_RELEASE_PAYMENT = disc("release_payment");
|
|
271
|
+
var PROGRAM_ID = new import_web3.PublicKey("EjLpJ3obUYk1f2KDensU7UdGBZTxSdxufm7CSnMA3H5C");
|
|
272
|
+
async function fetchPlatformConfig(connection) {
|
|
273
|
+
const [pda] = import_web3.PublicKey.findProgramAddressSync([Buffer.from("config")], PROGRAM_ID);
|
|
274
|
+
const info = await connection.getAccountInfo(pda);
|
|
275
|
+
if (!info) throw new Error("PlatformConfig account not found on-chain");
|
|
276
|
+
return {
|
|
277
|
+
mint: new import_web3.PublicKey(info.data.slice(42, 74)),
|
|
278
|
+
treasury: new import_web3.PublicKey(info.data.slice(74, 106))
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
async function signAndSend(base64Tx, wallet, connection) {
|
|
282
|
+
const buf = Buffer.from(base64Tx, "base64");
|
|
283
|
+
const tx = import_web3.Transaction.from(buf);
|
|
284
|
+
tx.partialSign(wallet);
|
|
285
|
+
const raw = tx.serialize();
|
|
286
|
+
const signature = await connection.sendRawTransaction(raw, {
|
|
287
|
+
skipPreflight: false
|
|
288
|
+
});
|
|
289
|
+
await connection.confirmTransaction(signature, "confirmed");
|
|
290
|
+
return signature;
|
|
291
|
+
}
|
|
292
|
+
async function buildReleasePaymentTx(params) {
|
|
293
|
+
const { buyer, escrowPda, worker, mint, treasury, connection } = params;
|
|
294
|
+
const [platformConfig] = import_web3.PublicKey.findProgramAddressSync(
|
|
295
|
+
[Buffer.from("config")],
|
|
296
|
+
PROGRAM_ID
|
|
297
|
+
);
|
|
298
|
+
const vault = (0, import_spl_token.getAssociatedTokenAddressSync)(
|
|
299
|
+
mint,
|
|
300
|
+
escrowPda,
|
|
301
|
+
true,
|
|
302
|
+
import_spl_token.TOKEN_PROGRAM_ID,
|
|
303
|
+
import_spl_token.ASSOCIATED_TOKEN_PROGRAM_ID
|
|
304
|
+
);
|
|
305
|
+
const workerAta = (0, import_spl_token.getAssociatedTokenAddressSync)(
|
|
306
|
+
mint,
|
|
307
|
+
worker,
|
|
308
|
+
false,
|
|
309
|
+
import_spl_token.TOKEN_PROGRAM_ID,
|
|
310
|
+
import_spl_token.ASSOCIATED_TOKEN_PROGRAM_ID
|
|
311
|
+
);
|
|
312
|
+
const treasuryAta = (0, import_spl_token.getAssociatedTokenAddressSync)(
|
|
313
|
+
mint,
|
|
314
|
+
treasury,
|
|
315
|
+
false,
|
|
316
|
+
import_spl_token.TOKEN_PROGRAM_ID,
|
|
317
|
+
import_spl_token.ASSOCIATED_TOKEN_PROGRAM_ID
|
|
318
|
+
);
|
|
319
|
+
const ix = new import_web3.TransactionInstruction({
|
|
320
|
+
programId: PROGRAM_ID,
|
|
321
|
+
keys: [
|
|
322
|
+
{ pubkey: buyer, isSigner: true, isWritable: true },
|
|
323
|
+
{ pubkey: platformConfig, isSigner: false, isWritable: false },
|
|
324
|
+
{ pubkey: mint, isSigner: false, isWritable: false },
|
|
325
|
+
{ pubkey: escrowPda, isSigner: false, isWritable: true },
|
|
326
|
+
{ pubkey: vault, isSigner: false, isWritable: true },
|
|
327
|
+
{ pubkey: workerAta, isSigner: false, isWritable: true },
|
|
328
|
+
{ pubkey: treasuryAta, isSigner: false, isWritable: true },
|
|
329
|
+
{ pubkey: worker, isSigner: false, isWritable: false },
|
|
330
|
+
{ pubkey: treasury, isSigner: false, isWritable: false },
|
|
331
|
+
{ pubkey: import_spl_token.TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
|
|
332
|
+
{ pubkey: import_spl_token.ASSOCIATED_TOKEN_PROGRAM_ID, isSigner: false, isWritable: false }
|
|
333
|
+
],
|
|
334
|
+
data: DISC_RELEASE_PAYMENT
|
|
335
|
+
});
|
|
336
|
+
const { blockhash } = await connection.getLatestBlockhash("confirmed");
|
|
337
|
+
const tx = new import_web3.Transaction();
|
|
338
|
+
tx.recentBlockhash = blockhash;
|
|
339
|
+
tx.feePayer = buyer;
|
|
340
|
+
tx.add(ix);
|
|
341
|
+
return tx.serialize({ requireAllSignatures: false }).toString("base64");
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// src/client.ts
|
|
345
|
+
var Escro = class {
|
|
346
|
+
constructor(options) {
|
|
347
|
+
__publicField(this, "connection");
|
|
348
|
+
__publicField(this, "api");
|
|
349
|
+
__publicField(this, "options");
|
|
350
|
+
this.options = options;
|
|
351
|
+
this.connection = new import_web32.Connection(options.rpcUrl, "confirmed");
|
|
352
|
+
this.api = new ApiClient(options.apiUrl, options.wallet);
|
|
353
|
+
}
|
|
354
|
+
// ── Buyer Bot ────────────────────────────────────────────────────────────────
|
|
355
|
+
/**
|
|
356
|
+
* Create a new escrow: registers with the API, signs and submits the
|
|
357
|
+
* on-chain `create_escrow` transaction.
|
|
358
|
+
*
|
|
359
|
+
* @returns `{ escrowId, escrowPda, signature }` — use `escrowPda` for all subsequent calls.
|
|
360
|
+
*/
|
|
361
|
+
async createEscrow(params) {
|
|
362
|
+
const { escrowId, escrowPda, unsignedTx } = await this.api.createEscrow(params);
|
|
363
|
+
const signature = await signAndSend(unsignedTx, this.options.wallet, this.connection);
|
|
364
|
+
return { escrowId, escrowPda, signature };
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Release payment to the worker. Buyer must be the wallet in the constructor.
|
|
368
|
+
* Updates API DB state, then builds and submits the `release_payment` on-chain tx.
|
|
369
|
+
*
|
|
370
|
+
* @returns Solana transaction signature.
|
|
371
|
+
*/
|
|
372
|
+
async releasePayment(address) {
|
|
373
|
+
const escrow = await this.api.getEscrow(address);
|
|
374
|
+
if (!escrow.taker) {
|
|
375
|
+
throw new EscrowNotFoundError(`Escrow ${address} has no assigned worker`);
|
|
376
|
+
}
|
|
377
|
+
const { treasury, mint } = await fetchPlatformConfig(this.connection);
|
|
378
|
+
await this.api.releasePaymentDb(address);
|
|
379
|
+
const unsignedTx = await buildReleasePaymentTx({
|
|
380
|
+
buyer: this.options.wallet.publicKey,
|
|
381
|
+
escrowPda: new import_web32.PublicKey(address),
|
|
382
|
+
worker: new import_web32.PublicKey(escrow.taker),
|
|
383
|
+
mint,
|
|
384
|
+
treasury,
|
|
385
|
+
connection: this.connection
|
|
386
|
+
});
|
|
387
|
+
return signAndSend(unsignedTx, this.options.wallet, this.connection);
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Raise a dispute for an escrow. Either the buyer or the assigned worker
|
|
391
|
+
* may call this. Signs and submits the `raise_dispute` on-chain tx.
|
|
392
|
+
*
|
|
393
|
+
* @returns Solana transaction signature.
|
|
394
|
+
*/
|
|
395
|
+
async raiseDispute(address, params) {
|
|
396
|
+
const { unsignedTx } = await this.api.raiseDispute(address, params);
|
|
397
|
+
return signAndSend(unsignedTx, this.options.wallet, this.connection);
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Cancel a funded escrow before any worker has claimed it.
|
|
401
|
+
* Buyer must be the wallet in the constructor. Only valid when state is `FUNDED`.
|
|
402
|
+
* Signs and submits the `cancel_escrow` on-chain tx; full USDC refund to buyer.
|
|
403
|
+
*
|
|
404
|
+
* @returns Solana transaction signature.
|
|
405
|
+
*/
|
|
406
|
+
async cancelEscrow(address) {
|
|
407
|
+
const { unsignedTx } = await this.api.cancelEscrow(address);
|
|
408
|
+
return signAndSend(unsignedTx, this.options.wallet, this.connection);
|
|
409
|
+
}
|
|
410
|
+
// ── Worker Bot ───────────────────────────────────────────────────────────────
|
|
411
|
+
/**
|
|
412
|
+
* Claim an assigned task. Worker must be the wallet in the constructor.
|
|
413
|
+
* Signs and submits the `claim_task` on-chain tx.
|
|
414
|
+
*
|
|
415
|
+
* @returns Solana transaction signature.
|
|
416
|
+
*/
|
|
417
|
+
async claimTask(address) {
|
|
418
|
+
const { unsignedTx } = await this.api.claimTask(address);
|
|
419
|
+
return signAndSend(unsignedTx, this.options.wallet, this.connection);
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Submit a deliverable for oracle evaluation. Worker must be the wallet.
|
|
423
|
+
* Signs and submits the `submit_deliverable` on-chain tx.
|
|
424
|
+
*
|
|
425
|
+
* @returns Solana transaction signature.
|
|
426
|
+
*/
|
|
427
|
+
async submitDeliverable(address, params) {
|
|
428
|
+
const { unsignedTx } = await this.api.submitDeliverable(address, params);
|
|
429
|
+
return signAndSend(unsignedTx, this.options.wallet, this.connection);
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* List tasks assigned to the wallet's public key.
|
|
433
|
+
* Optionally filter by state(s) and paginate results.
|
|
434
|
+
*
|
|
435
|
+
* If `options.poll` is true, polls with exponential backoff until at least
|
|
436
|
+
* one task is returned.
|
|
437
|
+
*
|
|
438
|
+
* @param state - Filter by one or more states (first state used as API filter).
|
|
439
|
+
* @param options - Polling, pagination (limit/offset).
|
|
440
|
+
*/
|
|
441
|
+
async getMyTasks(state, options) {
|
|
442
|
+
const assignedTo = this.options.wallet.publicKey.toBase58();
|
|
443
|
+
const params = {
|
|
444
|
+
assignedTo,
|
|
445
|
+
...state?.[0] != null && { state: state[0] },
|
|
446
|
+
...options?.limit != null && { limit: options.limit },
|
|
447
|
+
...options?.offset != null && { offset: options.offset }
|
|
448
|
+
};
|
|
449
|
+
if (!options?.poll) {
|
|
450
|
+
return this.api.listEscrows(params);
|
|
451
|
+
}
|
|
452
|
+
const fetcher = () => this.api.listEscrows(params).then((result) => result.items.length > 0 ? result : null);
|
|
453
|
+
return pollUntil(
|
|
454
|
+
assignedTo,
|
|
455
|
+
fetcher,
|
|
456
|
+
(r) => r !== null,
|
|
457
|
+
options.timeoutMs
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
// ── Shared ───────────────────────────────────────────────────────────────────
|
|
461
|
+
/**
|
|
462
|
+
* Fetch an escrow by its PDA address.
|
|
463
|
+
* If `options.poll` is true, polls with exponential backoff until the escrow
|
|
464
|
+
* appears (useful immediately after a create tx before the API DB syncs).
|
|
465
|
+
*/
|
|
466
|
+
async getEscrow(address, options) {
|
|
467
|
+
if (!options?.poll) {
|
|
468
|
+
return this.api.getEscrow(address);
|
|
469
|
+
}
|
|
470
|
+
return pollUntil(
|
|
471
|
+
address,
|
|
472
|
+
() => this.api.getEscrow(address).catch((e) => e instanceof EscrowNotFoundError ? null : Promise.reject(e)),
|
|
473
|
+
(r) => r !== null,
|
|
474
|
+
options.timeoutMs
|
|
475
|
+
);
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
479
|
+
0 && (module.exports = {
|
|
480
|
+
Escro,
|
|
481
|
+
EscroError,
|
|
482
|
+
EscrowNotFoundError,
|
|
483
|
+
EscrowStateError,
|
|
484
|
+
EscrowTimeoutError,
|
|
485
|
+
UnauthorizedError
|
|
486
|
+
});
|
|
487
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/client.ts","../src/api.ts","../src/errors.ts","../src/poll.ts","../src/solana.ts"],"sourcesContent":["export { Escro } from \"./client.js\";\nexport type { EscroOptions, CreateEscrowParams, CreateEscrowResult, SubmitDeliverableParams, RaiseDisputeParams, PollOptions, ListOptions, PaginatedResult } from \"./types.js\";\nexport { EscroError, EscrowNotFoundError, EscrowStateError, EscrowTimeoutError, UnauthorizedError } from \"./errors.js\";\n\n// Re-export the shared types most commonly needed by SDK consumers.\nexport type {\n EscrowAccount,\n EscrowState,\n Network,\n TaskSpec,\n AcceptanceCriterion,\n DeliverableFormat,\n DeliverableType,\n TaskType,\n EscrowErrorCode,\n} from \"@escro/shared\";\n","import { Connection, PublicKey } from \"@solana/web3.js\";\nimport type { EscrowAccount, EscrowState } from \"@escro/shared\";\nimport type {\n CreateEscrowParams,\n CreateEscrowResult,\n EscroOptions,\n ListOptions,\n PaginatedResult,\n PollOptions,\n RaiseDisputeParams,\n SubmitDeliverableParams,\n} from \"./types.js\";\nimport { ApiClient } from \"./api.js\";\nimport { EscrowNotFoundError } from \"./errors.js\";\nimport { pollUntil } from \"./poll.js\";\nimport { buildReleasePaymentTx, fetchPlatformConfig, signAndSend } from \"./solana.js\";\n\nexport class Escro {\n private readonly connection: Connection;\n private readonly api: ApiClient;\n private readonly options: EscroOptions;\n\n constructor(options: EscroOptions) {\n this.options = options;\n this.connection = new Connection(options.rpcUrl, \"confirmed\");\n this.api = new ApiClient(options.apiUrl, options.wallet);\n }\n\n // ── Buyer Bot ────────────────────────────────────────────────────────────────\n\n /**\n * Create a new escrow: registers with the API, signs and submits the\n * on-chain `create_escrow` transaction.\n *\n * @returns `{ escrowId, escrowPda, signature }` — use `escrowPda` for all subsequent calls.\n */\n async createEscrow(params: CreateEscrowParams): Promise<CreateEscrowResult> {\n const { escrowId, escrowPda, unsignedTx } = await this.api.createEscrow(params);\n const signature = await signAndSend(unsignedTx, this.options.wallet, this.connection);\n return { escrowId, escrowPda, signature };\n }\n\n /**\n * Release payment to the worker. Buyer must be the wallet in the constructor.\n * Updates API DB state, then builds and submits the `release_payment` on-chain tx.\n *\n * @returns Solana transaction signature.\n */\n async releasePayment(address: string): Promise<string> {\n // Fetch escrow metadata for worker pubkey + mint\n const escrow = await this.api.getEscrow(address);\n if (!escrow.taker) {\n throw new EscrowNotFoundError(`Escrow ${address} has no assigned worker`);\n }\n\n // Fetch platform config for treasury pubkey + usdc mint\n const { treasury, mint } = await fetchPlatformConfig(this.connection);\n\n // Update DB state (optimistic — on-chain reconciliation runs asynchronously)\n await this.api.releasePaymentDb(address);\n\n // Build, sign, and submit the on-chain instruction\n const unsignedTx = await buildReleasePaymentTx({\n buyer: this.options.wallet.publicKey,\n escrowPda: new PublicKey(address),\n worker: new PublicKey(escrow.taker),\n mint,\n treasury,\n connection: this.connection,\n });\n\n return signAndSend(unsignedTx, this.options.wallet, this.connection);\n }\n\n /**\n * Raise a dispute for an escrow. Either the buyer or the assigned worker\n * may call this. Signs and submits the `raise_dispute` on-chain tx.\n *\n * @returns Solana transaction signature.\n */\n async raiseDispute(address: string, params: RaiseDisputeParams): Promise<string> {\n const { unsignedTx } = await this.api.raiseDispute(address, params);\n return signAndSend(unsignedTx, this.options.wallet, this.connection);\n }\n\n /**\n * Cancel a funded escrow before any worker has claimed it.\n * Buyer must be the wallet in the constructor. Only valid when state is `FUNDED`.\n * Signs and submits the `cancel_escrow` on-chain tx; full USDC refund to buyer.\n *\n * @returns Solana transaction signature.\n */\n async cancelEscrow(address: string): Promise<string> {\n const { unsignedTx } = await this.api.cancelEscrow(address);\n return signAndSend(unsignedTx, this.options.wallet, this.connection);\n }\n\n // ── Worker Bot ───────────────────────────────────────────────────────────────\n\n /**\n * Claim an assigned task. Worker must be the wallet in the constructor.\n * Signs and submits the `claim_task` on-chain tx.\n *\n * @returns Solana transaction signature.\n */\n async claimTask(address: string): Promise<string> {\n const { unsignedTx } = await this.api.claimTask(address);\n return signAndSend(unsignedTx, this.options.wallet, this.connection);\n }\n\n /**\n * Submit a deliverable for oracle evaluation. Worker must be the wallet.\n * Signs and submits the `submit_deliverable` on-chain tx.\n *\n * @returns Solana transaction signature.\n */\n async submitDeliverable(address: string, params: SubmitDeliverableParams): Promise<string> {\n const { unsignedTx } = await this.api.submitDeliverable(address, params);\n return signAndSend(unsignedTx, this.options.wallet, this.connection);\n }\n\n /**\n * List tasks assigned to the wallet's public key.\n * Optionally filter by state(s) and paginate results.\n *\n * If `options.poll` is true, polls with exponential backoff until at least\n * one task is returned.\n *\n * @param state - Filter by one or more states (first state used as API filter).\n * @param options - Polling, pagination (limit/offset).\n */\n async getMyTasks(\n state?: EscrowState[],\n options?: PollOptions & ListOptions,\n ): Promise<PaginatedResult<EscrowAccount>> {\n const assignedTo = this.options.wallet.publicKey.toBase58();\n const params: Parameters<ApiClient[\"listEscrows\"]>[0] = {\n assignedTo,\n ...(state?.[0] != null && { state: state[0] }),\n ...(options?.limit != null && { limit: options.limit }),\n ...(options?.offset != null && { offset: options.offset }),\n };\n\n if (!options?.poll) {\n return this.api.listEscrows(params);\n }\n\n const fetcher = () =>\n this.api\n .listEscrows(params)\n .then((result) => (result.items.length > 0 ? result : null));\n\n return pollUntil(\n assignedTo,\n fetcher,\n (r): r is PaginatedResult<EscrowAccount> => r !== null,\n options.timeoutMs,\n );\n }\n\n // ── Shared ───────────────────────────────────────────────────────────────────\n\n /**\n * Fetch an escrow by its PDA address.\n * If `options.poll` is true, polls with exponential backoff until the escrow\n * appears (useful immediately after a create tx before the API DB syncs).\n */\n async getEscrow(address: string, options?: PollOptions): Promise<EscrowAccount> {\n if (!options?.poll) {\n return this.api.getEscrow(address);\n }\n\n return pollUntil(\n address,\n () =>\n this.api\n .getEscrow(address)\n .catch((e: unknown) => (e instanceof EscrowNotFoundError ? null : Promise.reject(e))),\n (r): r is EscrowAccount => r !== null,\n options.timeoutMs,\n );\n }\n}\n","import nacl from \"tweetnacl\";\nimport bs58 from \"bs58\";\nimport type { Keypair } from \"@solana/web3.js\";\nimport type { EscrowAccount, EscrowState } from \"@escro/shared\";\nimport type {\n CreateEscrowParams,\n ListOptions,\n PaginatedResult,\n RaiseDisputeParams,\n SerializedEscrow,\n SubmitDeliverableParams,\n} from \"./types.js\";\nimport {\n EscroError,\n EscrowNotFoundError,\n EscrowStateError,\n UnauthorizedError,\n} from \"./errors.js\";\n\n// ── Auth header builder ────────────────────────────────────────────────────────\n\nfunction authHeaders(\n wallet: Keypair,\n method: string,\n path: string,\n): Record<string, string> {\n const timestamp = Date.now();\n const message = `escro:${timestamp}:${method}:${path}`;\n const msgBytes = new TextEncoder().encode(message);\n const sigBytes = nacl.sign.detached(msgBytes, wallet.secretKey);\n return {\n \"x-wallet-address\": wallet.publicKey.toBase58(),\n \"x-signature\": bs58.encode(sigBytes),\n \"x-timestamp\": String(timestamp),\n \"content-type\": \"application/json\",\n };\n}\n\n// ── Deserialization ────────────────────────────────────────────────────────────\n\nfunction deserialize(raw: SerializedEscrow): EscrowAccount {\n return { ...raw, amount: BigInt(raw.amount) };\n}\n\n// ── Error handling ─────────────────────────────────────────────────────────────\n\nasync function handleError(res: Response, address?: string): Promise<never> {\n const body = (await res.json().catch(() => ({}))) as {\n error?: string;\n code?: string;\n };\n const msg = body.error ?? `HTTP ${res.status}`;\n if (res.status === 404) throw new EscrowNotFoundError(address ?? msg);\n if (res.status === 409) throw new EscrowStateError(msg);\n if (res.status === 401 || res.status === 403) throw new UnauthorizedError(msg);\n throw new EscroError(msg, body.code ?? \"UNKNOWN\");\n}\n\n// ── API client ─────────────────────────────────────────────────────────────────\n\nexport class ApiClient {\n constructor(\n private readonly baseUrl: string,\n private readonly wallet: Keypair,\n ) {}\n\n private url(path: string): string {\n return `${this.baseUrl}${path}`;\n }\n\n /** GET /v1/escrows/:address */\n async getEscrow(address: string): Promise<EscrowAccount> {\n const path = `/v1/escrows/${address}`;\n const res = await fetch(this.url(path));\n if (!res.ok) await handleError(res, address);\n return deserialize((await res.json()) as SerializedEscrow);\n }\n\n /** GET /v1/escrows?assignedTo=...&state=... */\n async listEscrows(params: {\n assignedTo?: string;\n state?: EscrowState;\n limit?: number;\n offset?: number;\n }): Promise<PaginatedResult<EscrowAccount>> {\n const qs = new URLSearchParams();\n if (params.assignedTo) qs.set(\"assignedTo\", params.assignedTo);\n if (params.state) qs.set(\"state\", params.state);\n if (params.limit != null) qs.set(\"limit\", String(params.limit));\n if (params.offset != null) qs.set(\"offset\", String(params.offset));\n\n const path = `/v1/escrows?${qs.toString()}`;\n const res = await fetch(this.url(path));\n if (!res.ok) await handleError(res);\n const body = (await res.json()) as {\n items: SerializedEscrow[];\n total: number;\n limit: number;\n offset: number;\n };\n return {\n items: body.items.map(deserialize),\n total: body.total,\n limit: body.limit,\n offset: body.offset,\n };\n }\n\n /**\n * POST /v1/escrows — create escrow, returns unsigned tx + ids.\n * Requires auth.\n */\n async createEscrow(params: CreateEscrowParams): Promise<{\n escrowId: string;\n escrowPda: string;\n unsignedTx: string;\n }> {\n const path = \"/v1/escrows\";\n const res = await fetch(this.url(path), {\n method: \"POST\",\n headers: authHeaders(this.wallet, \"POST\", path),\n body: JSON.stringify({\n taskSpec: params.taskSpec,\n amountUsdc: params.amountUsdc,\n deadlineSeconds: params.deadlineSeconds,\n assignedWorker: params.assignedWorker,\n }),\n });\n if (!res.ok) await handleError(res);\n return res.json() as Promise<{ escrowId: string; escrowPda: string; unsignedTx: string }>;\n }\n\n /**\n * POST /v1/escrows/:address/claim — returns unsigned tx.\n * Requires auth (worker).\n */\n async claimTask(address: string): Promise<{ unsignedTx: string }> {\n const path = `/v1/escrows/${address}/claim`;\n const res = await fetch(this.url(path), {\n method: \"POST\",\n headers: authHeaders(this.wallet, \"POST\", path),\n });\n if (!res.ok) await handleError(res, address);\n return res.json() as Promise<{ unsignedTx: string }>;\n }\n\n /**\n * POST /v1/escrows/:address/submit — returns unsigned tx.\n * Requires auth (worker).\n */\n async submitDeliverable(\n address: string,\n params: SubmitDeliverableParams,\n ): Promise<{ unsignedTx: string }> {\n const path = `/v1/escrows/${address}/submit`;\n const res = await fetch(this.url(path), {\n method: \"POST\",\n headers: authHeaders(this.wallet, \"POST\", path),\n body: JSON.stringify({\n contentHash: params.contentHash,\n proofUri: params.proofUri,\n }),\n });\n if (!res.ok) await handleError(res, address);\n return res.json() as Promise<{ unsignedTx: string }>;\n }\n\n /**\n * POST /v1/escrows/:address/release — DB-only state update.\n * Requires auth (buyer). No on-chain tx returned.\n */\n async releasePaymentDb(address: string): Promise<void> {\n const path = `/v1/escrows/${address}/release`;\n const res = await fetch(this.url(path), {\n method: \"POST\",\n headers: authHeaders(this.wallet, \"POST\", path),\n });\n if (!res.ok) await handleError(res, address);\n }\n\n /**\n * POST /v1/escrows/:address/cancel — returns unsigned tx.\n * Requires auth (buyer). Only valid when state is FUNDED.\n */\n async cancelEscrow(address: string): Promise<{ unsignedTx: string }> {\n const path = `/v1/escrows/${address}/cancel`;\n const res = await fetch(this.url(path), {\n method: \"POST\",\n headers: authHeaders(this.wallet, \"POST\", path),\n });\n if (!res.ok) await handleError(res, address);\n return res.json() as Promise<{ unsignedTx: string }>;\n }\n\n /**\n * POST /v1/escrows/:address/dispute — returns unsigned tx.\n * Requires auth (buyer or worker).\n */\n async raiseDispute(\n address: string,\n params: RaiseDisputeParams,\n ): Promise<{ unsignedTx: string }> {\n const path = `/v1/escrows/${address}/dispute`;\n const res = await fetch(this.url(path), {\n method: \"POST\",\n headers: authHeaders(this.wallet, \"POST\", path),\n body: JSON.stringify({ reason: params.reason, evidence: params.evidence }),\n });\n if (!res.ok) await handleError(res, address);\n return res.json() as Promise<{ unsignedTx: string }>;\n }\n}\n","/** Base class for all errors thrown by the @escro/sdk. */\nexport class EscroError extends Error {\n constructor(\n message: string,\n public readonly code: string = \"ESCRO_ERROR\",\n ) {\n super(message);\n this.name = \"EscroError\";\n }\n}\n\n/** The requested escrow was not found (HTTP 404). */\nexport class EscrowNotFoundError extends EscroError {\n constructor(address: string) {\n super(`Escrow not found: ${address}`, \"ESCROW_NOT_FOUND\");\n this.name = \"EscrowNotFoundError\";\n }\n}\n\n/** The operation is not valid for the escrow's current state (HTTP 409). */\nexport class EscrowStateError extends EscroError {\n constructor(message: string) {\n super(message, \"ESCROW_INVALID_STATE\");\n this.name = \"EscrowStateError\";\n }\n}\n\n/** Polling timed out after 24 hours without reaching the expected state. */\nexport class EscrowTimeoutError extends EscroError {\n constructor(address: string) {\n super(`Polling timed out after 24h for escrow: ${address}`, \"ESCROW_TIMEOUT\");\n this.name = \"EscrowTimeoutError\";\n }\n}\n\n/** The caller is not authorized to perform this action (HTTP 401/403). */\nexport class UnauthorizedError extends EscroError {\n constructor(message = \"Unauthorized\") {\n super(message, \"UNAUTHORIZED\");\n this.name = \"UnauthorizedError\";\n }\n}\n","import { EscrowTimeoutError } from \"./errors.js\";\n\nconst MS_5_MIN = 5 * 60 * 1_000;\nconst MS_30_MIN = 30 * 60 * 1_000;\nconst MS_24_H = 24 * 60 * 60 * 1_000;\n\n/**\n * Returns the next polling interval in ms based on total elapsed time.\n * 0 – 5 min → 15 s\n * 5 – 30 min → 30 s\n * 30 min – ∞ → 60 s\n */\nexport function backoffInterval(elapsedMs: number): number {\n if (elapsedMs < MS_5_MIN) return 15_000;\n if (elapsedMs < MS_30_MIN) return 30_000;\n return 60_000;\n}\n\n/** Resolves after `ms` milliseconds. */\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Polls `fetcher` with exponential backoff until `predicate` returns true,\n * then returns the result. Throws {@link EscrowTimeoutError} after 24 hours.\n *\n * @param address - Escrow address used in the timeout error message.\n * @param fetcher - Async function that returns a value or null.\n * @param predicate - Returns true when polling should stop.\n * @param timeoutMs - Maximum total wait time in ms (default: 24 h).\n */\nexport async function pollUntil<T>(\n address: string,\n fetcher: () => Promise<T | null>,\n predicate: (result: T | null) => result is T,\n timeoutMs = MS_24_H,\n): Promise<T> {\n const deadline = Date.now() + timeoutMs;\n\n while (true) {\n const result = await fetcher();\n if (predicate(result)) return result;\n\n const elapsed = Date.now() - (deadline - timeoutMs);\n if (Date.now() >= deadline) {\n throw new EscrowTimeoutError(address);\n }\n\n const interval = backoffInterval(elapsed);\n // Clamp to remaining time so we don't sleep past the deadline\n const remaining = deadline - Date.now();\n await sleep(Math.min(interval, remaining));\n\n if (Date.now() >= deadline) {\n throw new EscrowTimeoutError(address);\n }\n }\n}\n","import { createHash } from \"node:crypto\";\nimport {\n Connection,\n Keypair,\n PublicKey,\n Transaction,\n TransactionInstruction,\n} from \"@solana/web3.js\";\nimport {\n TOKEN_PROGRAM_ID,\n ASSOCIATED_TOKEN_PROGRAM_ID,\n getAssociatedTokenAddressSync,\n} from \"@solana/spl-token\";\n\n// ── Discriminator helper ───────────────────────────────────────────────────────\n\nfunction disc(name: string): Buffer {\n return Buffer.from(\n createHash(\"sha256\").update(`global:${name}`).digest(),\n ).subarray(0, 8);\n}\n\nconst DISC_RELEASE_PAYMENT = disc(\"release_payment\");\n\n// ── Constants ──────────────────────────────────────────────────────────────────\n\nconst PROGRAM_ID = new PublicKey(\"EjLpJ3obUYk1f2KDensU7UdGBZTxSdxufm7CSnMA3H5C\");\n\n/**\n * PlatformConfig account layout (after 8-byte Anchor discriminator):\n * fee_bps : u16 — offset 8..10\n * oracle_authority : Pubkey — offset 10..42\n * usdc_mint : Pubkey — offset 42..74\n * treasury : Pubkey — offset 74..106\n */\nexport async function fetchPlatformConfig(\n connection: Connection,\n): Promise<{ mint: PublicKey; treasury: PublicKey }> {\n const [pda] = PublicKey.findProgramAddressSync([Buffer.from(\"config\")], PROGRAM_ID);\n const info = await connection.getAccountInfo(pda);\n if (!info) throw new Error(\"PlatformConfig account not found on-chain\");\n return {\n mint: new PublicKey(info.data.slice(42, 74)),\n treasury: new PublicKey(info.data.slice(74, 106)),\n };\n}\n\n// ── Sign and submit ────────────────────────────────────────────────────────────\n\n/**\n * Decode a base64 unsigned transaction, sign it with `wallet`, submit to Solana,\n * and return the transaction signature.\n */\nexport async function signAndSend(\n base64Tx: string,\n wallet: Keypair,\n connection: Connection,\n): Promise<string> {\n const buf = Buffer.from(base64Tx, \"base64\");\n const tx = Transaction.from(buf);\n tx.partialSign(wallet);\n const raw = tx.serialize();\n const signature = await connection.sendRawTransaction(raw, {\n skipPreflight: false,\n });\n await connection.confirmTransaction(signature, \"confirmed\");\n return signature;\n}\n\n// ── release_payment transaction builder ───────────────────────────────────────\n\n/**\n * Build an unsigned `release_payment` transaction.\n * The buyer signs to approve payment to the assigned worker.\n *\n * Account order (matches the on-chain `ReleasePayment` context):\n * 0. buyer — Signer, writable\n * 1. platform_config — read\n * 2. mint — read\n * 3. escrow_account — writable\n * 4. vault — ATA(mint, escrowPda), writable\n * 5. worker_ata — ATA(mint, worker), writable\n * 6. treasury_ata — ATA(mint, treasury), writable\n * 7. worker — read\n * 8. treasury — read\n * 9. token_program — read\n * 10. associated_token_program — read\n */\nexport async function buildReleasePaymentTx(params: {\n buyer: PublicKey;\n escrowPda: PublicKey;\n worker: PublicKey;\n mint: PublicKey;\n treasury: PublicKey;\n connection: Connection;\n}): Promise<string> {\n const { buyer, escrowPda, worker, mint, treasury, connection } = params;\n\n const [platformConfig] = PublicKey.findProgramAddressSync(\n [Buffer.from(\"config\")],\n PROGRAM_ID,\n );\n const vault = getAssociatedTokenAddressSync(\n mint,\n escrowPda,\n true,\n TOKEN_PROGRAM_ID,\n ASSOCIATED_TOKEN_PROGRAM_ID,\n );\n const workerAta = getAssociatedTokenAddressSync(\n mint,\n worker,\n false,\n TOKEN_PROGRAM_ID,\n ASSOCIATED_TOKEN_PROGRAM_ID,\n );\n const treasuryAta = getAssociatedTokenAddressSync(\n mint,\n treasury,\n false,\n TOKEN_PROGRAM_ID,\n ASSOCIATED_TOKEN_PROGRAM_ID,\n );\n\n const ix = new TransactionInstruction({\n programId: PROGRAM_ID,\n keys: [\n { pubkey: buyer, isSigner: true, isWritable: true },\n { pubkey: platformConfig, isSigner: false, isWritable: false },\n { pubkey: mint, isSigner: false, isWritable: false },\n { pubkey: escrowPda, isSigner: false, isWritable: true },\n { pubkey: vault, isSigner: false, isWritable: true },\n { pubkey: workerAta, isSigner: false, isWritable: true },\n { pubkey: treasuryAta, isSigner: false, isWritable: true },\n { pubkey: worker, isSigner: false, isWritable: false },\n { pubkey: treasury, isSigner: false, isWritable: false },\n { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },\n { pubkey: ASSOCIATED_TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },\n ],\n data: DISC_RELEASE_PAYMENT,\n });\n\n const { blockhash } = await connection.getLatestBlockhash(\"confirmed\");\n const tx = new Transaction();\n tx.recentBlockhash = blockhash;\n tx.feePayer = buyer;\n tx.add(ix);\n return tx.serialize({ requireAllSignatures: false }).toString(\"base64\");\n}\n"],"mappings":"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","names":["import_web3","nacl","bs58"]}
|