@lawrenceliang-btc/atel-sdk 1.1.37 → 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.
- package/bin/atel.mjs +143 -2
- package/package.json +1 -1
package/bin/atel.mjs
CHANGED
|
@@ -336,8 +336,10 @@ async function pushTradeNotification(eventType, payload, body) {
|
|
|
336
336
|
return `❌ 里程碑 M${p.milestoneIndex ?? '?'} 被拒绝\n订单: ${p.orderId || body?.orderId || '?'}${desc}\n原因: ${p.rejectReason || '未说明'}`;
|
|
337
337
|
},
|
|
338
338
|
'order_settled': (p) => {
|
|
339
|
-
const
|
|
340
|
-
|
|
339
|
+
const cl = chainLabel(p);
|
|
340
|
+
const amount = p.priceAmount ? `\n金额: $${p.priceAmount} USDC${cl}` : '';
|
|
341
|
+
const dest = cl === ' (Fast)' ? '\n资金已到达执行方 Fast 钱包' : '\nUSDC 已支付';
|
|
342
|
+
return `💰 订单已结算完成\n订单: ${p.orderId || body?.orderId || '?'}${amount}${dest}`;
|
|
341
343
|
},
|
|
342
344
|
};
|
|
343
345
|
const tmpl = templates[eventType];
|
|
@@ -3636,6 +3638,22 @@ Format:
|
|
|
3636
3638
|
dedupeKey: body.dedupeKey,
|
|
3637
3639
|
});
|
|
3638
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
|
+
|
|
3639
3657
|
const dedupeKey = body.dedupeKey || `${event}:${body.orderId || payload.orderId || ''}`;
|
|
3640
3658
|
const orderIdForCwd = body.orderId || payload.orderId || '';
|
|
3641
3659
|
let workspace = getOrderWorkspace(orderIdForCwd, {
|
|
@@ -8367,6 +8385,129 @@ async function cmdAliasRemove(args) {
|
|
|
8367
8385
|
|
|
8368
8386
|
// ─── Main ────────────────────────────────────────────────────────
|
|
8369
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
|
+
|
|
8370
8511
|
const [,, cmd, ...rawArgs] = process.argv;
|
|
8371
8512
|
const args = rawArgs.filter(a => !a.startsWith('--'));
|
|
8372
8513
|
const commands = {
|