@lawrenceliang-btc/atel-sdk 1.1.38 → 1.1.39

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.
Files changed (2) hide show
  1. package/bin/atel.mjs +139 -0
  2. package/package.json +1 -1
package/bin/atel.mjs CHANGED
@@ -3638,6 +3638,22 @@ Format:
3638
3638
  dedupeKey: body.dedupeKey,
3639
3639
  });
3640
3640
 
3641
+ // ── Fast-coop: auto-submit deliverable when all milestones verified ──
3642
+ // When executor receives the final milestone_verified for a fast-coop order,
3643
+ // sign and submit Escrow::Submit on Fast staging so the Platform can Complete.
3644
+ if (event === 'milestone_verified' && payload.allComplete === true) {
3645
+ const orderChain = payload.chain || body.chain || '';
3646
+ const isExecutor = payload.executorDid === id.did || (payload.orderDescription && payload.requesterDid !== id.did);
3647
+ if (orderChain === 'fast-coop' && isExecutor) {
3648
+ const fastOrderId = body.orderId || payload.orderId || '';
3649
+ log({ event: 'fast_submit_triggered', orderId: fastOrderId, chain: orderChain });
3650
+ // Fire-and-forget: submit deliverable hash to Fast escrow
3651
+ submitFastDeliverable(fastOrderId, id).catch(e => {
3652
+ log({ event: 'fast_submit_error', orderId: fastOrderId, error: e.message });
3653
+ });
3654
+ }
3655
+ }
3656
+
3641
3657
  const dedupeKey = body.dedupeKey || `${event}:${body.orderId || payload.orderId || ''}`;
3642
3658
  const orderIdForCwd = body.orderId || payload.orderId || '';
3643
3659
  let workspace = getOrderWorkspace(orderIdForCwd, {
@@ -8369,6 +8385,129 @@ async function cmdAliasRemove(args) {
8369
8385
 
8370
8386
  // ─── Main ────────────────────────────────────────────────────────
8371
8387
 
8388
+ // ── Fast-coop: Executor auto-submit deliverable to Fast escrow ──────
8389
+ // Called when executor receives milestone_verified with allComplete=true
8390
+ // for a fast-coop order. Signs Escrow::Submit with the agent's DID
8391
+ // private key (= Fast Ed25519 account key).
8392
+ async function submitFastDeliverable(orderId, identity) {
8393
+ try {
8394
+ // 1. Get order info to find the escrow job_id
8395
+ const orderInfo = await fetchOrderState(orderId);
8396
+ if (!orderInfo) throw new Error('cannot fetch order');
8397
+ const chain = orderInfo.chain || orderInfo.Chain || '';
8398
+ if (chain !== 'fast-coop') return;
8399
+
8400
+ // 2. Find escrow job_id from chain-records
8401
+ const recordsResp = await fetch(`${PLATFORM_URL}/trade/v1/order/${orderId}`, {
8402
+ signal: AbortSignal.timeout(10000),
8403
+ });
8404
+ // The job_id is stored in the on_chain_records by the Platform.
8405
+ // We need to get it from the order or from the chain-records endpoint.
8406
+ // For now, compute it the same way Platform does: keccak256(orderId)
8407
+ const { createHash } = await import('node:crypto');
8408
+
8409
+ // 3. Get the agent's Fast account info
8410
+ const FAST_RPC = process.env.ATEL_FASTCOOP_RPC_URL || 'https://staging.api.fast.xyz/proxy-rest';
8411
+ const pubKeyHex = Buffer.from(identity.publicKey).toString('hex');
8412
+
8413
+ const accountResp = await fetch(`${FAST_RPC}/v1/accounts/${pubKeyHex}`, {
8414
+ signal: AbortSignal.timeout(10000),
8415
+ });
8416
+ if (!accountResp.ok) throw new Error(`Fast account query failed: ${accountResp.status}`);
8417
+ const accountData = (await accountResp.json()).data;
8418
+ const nonce = accountData.next_nonce;
8419
+
8420
+ // 4. Find escrow job for this order (query by provider)
8421
+ const jobsResp = await fetch(`${FAST_RPC}/v1/escrow-jobs?provider=${pubKeyHex}&status=Funded`, {
8422
+ signal: AbortSignal.timeout(10000),
8423
+ });
8424
+ if (!jobsResp.ok) throw new Error(`escrow jobs query failed: ${jobsResp.status}`);
8425
+ const jobs = (await jobsResp.json()).data || [];
8426
+ const job = jobs.find(j => j.description && j.description.includes(orderId));
8427
+ if (!job) {
8428
+ log({ event: 'fast_submit_no_job', orderId, note: 'no Funded escrow job found for this order' });
8429
+ return;
8430
+ }
8431
+
8432
+ // 5. Build Escrow::Submit transaction
8433
+ // deliverable = keccak256(orderId) as 32-byte hash
8434
+ const deliverableHash = createHash('sha3-256').update(orderId).digest('hex');
8435
+
8436
+ // BCS encode: Operation::Escrow(12) → Escrow::Submit(2) → jobId(32) + deliverable(32)
8437
+ const jobIdBytes = Buffer.from(job.job_id, 'hex');
8438
+ const deliverableBytes = Buffer.from(deliverableHash, 'hex');
8439
+
8440
+ // Build minimal BCS by hand (matching Go implementation):
8441
+ // ULEB128(12) = 0x0c, ULEB128(2) = 0x02, then 32+32 bytes
8442
+ const claimBytes = Buffer.concat([
8443
+ Buffer.from([0x0c]), // Operation::Escrow variant 12
8444
+ Buffer.from([0x02]), // Escrow::Submit variant 2
8445
+ jobIdBytes.subarray(0, 32), // job_id 32 bytes
8446
+ deliverableBytes.subarray(0, 32), // deliverable 32 bytes
8447
+ ]);
8448
+
8449
+ // Transaction BCS: VersionedTransaction variant 1 (Release20260407)
8450
+ const networkId = process.env.ATEL_FASTCOOP_NETWORK_ID || 'fast:devnet';
8451
+ const senderBytes = Buffer.from(identity.publicKey);
8452
+ const tsNanos = BigInt(Date.now()) * 1000000n;
8453
+
8454
+ // ULEB128 encoder
8455
+ const uleb128 = (v) => {
8456
+ const buf = [];
8457
+ do { let b = Number(v & 0x7fn); v >>= 7n; if (v > 0n) b |= 0x80; buf.push(b); } while (v > 0n);
8458
+ return Buffer.from(buf.length ? buf : [0]);
8459
+ };
8460
+ const u64le = (v) => { const b = Buffer.alloc(8); b.writeBigUInt64LE(BigInt(v)); return b; };
8461
+ const u128le = (v) => { const b = Buffer.alloc(16); b.writeBigUInt64LE(v & 0xffffffffffffffffn); b.writeBigUInt64LE(v >> 64n, 8); return b; };
8462
+ const bcsString = (s) => { const b = Buffer.from(s, 'utf-8'); return Buffer.concat([uleb128(BigInt(b.length)), b]); };
8463
+
8464
+ // Transaction body
8465
+ const txBody = Buffer.concat([
8466
+ bcsString(networkId),
8467
+ senderBytes.subarray(0, 32),
8468
+ u64le(nonce),
8469
+ u128le(tsNanos),
8470
+ uleb128(1n), // claims vector length = 1
8471
+ claimBytes,
8472
+ Buffer.from([0x00]), // archival = false
8473
+ Buffer.from([0x00]), // fee_token = None
8474
+ ]);
8475
+
8476
+ // VersionedTransaction = enum variant 1
8477
+ const versionedTx = Buffer.concat([uleb128(1n), txBody]);
8478
+
8479
+ // 6. Sign
8480
+ const { default: nacl } = await import('tweetnacl');
8481
+ const sigMessage = Buffer.concat([Buffer.from('VersionedTransaction::'), versionedTx]);
8482
+ const signature = nacl.sign.detached(sigMessage, identity.secretKey);
8483
+
8484
+ // SignatureOrMultiSig::Signature = variant 0 + 64 bytes
8485
+ const bcsSig = Buffer.concat([Buffer.from([0x00]), Buffer.from(signature)]);
8486
+
8487
+ // 7. Submit to Fast
8488
+ const submitResp = await fetch(`${FAST_RPC}/v1/submit-transaction`, {
8489
+ method: 'POST',
8490
+ headers: { 'Content-Type': 'application/json' },
8491
+ body: JSON.stringify({
8492
+ transaction: versionedTx.toString('hex'),
8493
+ signature: bcsSig.toString('hex'),
8494
+ }),
8495
+ signal: AbortSignal.timeout(15000),
8496
+ });
8497
+
8498
+ const submitResult = await submitResp.json();
8499
+ if (submitResult.error) {
8500
+ throw new Error(`Fast submit failed: ${submitResult.error.message || JSON.stringify(submitResult.error)}`);
8501
+ }
8502
+
8503
+ log({ event: 'fast_submit_success', orderId, jobId: job.job_id, nonce });
8504
+ console.log(`⚡ [Fast] Deliverable submitted for ${orderId} (job: ${job.job_id.substring(0, 16)}...)`);
8505
+ } catch (e) {
8506
+ log({ event: 'fast_submit_error', orderId, error: e.message });
8507
+ console.error(`⚠️ [Fast] Submit failed for ${orderId}: ${e.message}`);
8508
+ }
8509
+ }
8510
+
8372
8511
  const [,, cmd, ...rawArgs] = process.argv;
8373
8512
  const args = rawArgs.filter(a => !a.startsWith('--'));
8374
8513
  const commands = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lawrenceliang-btc/atel-sdk",
3
- "version": "1.1.38",
3
+ "version": "1.1.39",
4
4
  "description": "ATEL Protocol SDK - Agent Trust & Exchange Layer",
5
5
  "repository": {
6
6
  "type": "git",