@lawrenceliang-btc/atel-sdk 1.2.14 → 1.2.15
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 +8 -10
- package/bin/atel.mjs +79 -78
- package/bin/notification-action-helpers.mjs +53 -16
- package/dist/anchor/index.d.ts +2 -3
- package/dist/anchor/index.js +1 -2
- package/dist/endpoint/index.d.ts +0 -2
- package/dist/handshake/index.d.ts +0 -8
- package/package.json +1 -2
- package/skill/atel-agent/SKILL.md +1 -1
- package/skill/references/commercial.md +3 -14
- package/skill/references/executor.md +1 -1
- package/skill/references/onchain.md +7 -3
- package/skill/references/quickstart.md +1 -5
- package/skill/references/security.md +1 -1
- package/skill/references/workflows.md +1 -1
- package/dist/anchor/solana.d.ts +0 -95
- package/dist/anchor/solana.js +0 -298
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ ATEL provides the cryptographic primitives and protocol building blocks that ena
|
|
|
10
10
|
- **📋 Policy Enforcement** — Scoped consent tokens, call tracking, deterministic hashing
|
|
11
11
|
- **🔍 Execution Tracing** — Tamper-evident, hash-chained audit logs with auto-checkpoints
|
|
12
12
|
- **✅ Proof Generation** — Merkle-tree proof bundles with multi-check verification
|
|
13
|
-
- **⚓ On-Chain Anchoring** — Multi-chain proof anchoring (
|
|
13
|
+
- **⚓ On-Chain Anchoring** — Multi-chain proof anchoring (Base/BSC)
|
|
14
14
|
- **📊 Trust Scoring** — Local trust computation based on execution history
|
|
15
15
|
- **🔔 Notification & Callback Runtime** — Local notify, callback, inbox, and recovery flow
|
|
16
16
|
- **👥 P2P Access Control** — Relationship-based friend system with temporary sessions
|
|
@@ -60,7 +60,7 @@ the `paymentTxHash` on Base, the `auditUrl` pointing at the CompletionProof (Int
|
|
|
60
60
|
### Trust & Verification
|
|
61
61
|
- Tamper-evident execution traces
|
|
62
62
|
- Merkle-tree proof generation
|
|
63
|
-
- On-chain anchoring (
|
|
63
|
+
- On-chain anchoring (Base/BSC)
|
|
64
64
|
- Local trust score computation
|
|
65
65
|
- Callback-driven execution and recovery
|
|
66
66
|
|
|
@@ -359,21 +359,21 @@ const report = ProofVerifier.verify(bundle, { trace });
|
|
|
359
359
|
### On-Chain Anchoring
|
|
360
360
|
|
|
361
361
|
```typescript
|
|
362
|
-
import {
|
|
362
|
+
import { BaseAnchorProvider } from '@lawrenceliang-btc/atel-sdk';
|
|
363
363
|
|
|
364
|
-
const
|
|
365
|
-
rpcUrl: 'https://
|
|
366
|
-
privateKey: process.env.
|
|
364
|
+
const base = new BaseAnchorProvider({
|
|
365
|
+
rpcUrl: process.env.ATEL_BASE_RPC_URL || 'https://mainnet.base.org',
|
|
366
|
+
privateKey: process.env.ATEL_BASE_PRIVATE_KEY
|
|
367
367
|
});
|
|
368
368
|
|
|
369
|
-
const result = await
|
|
369
|
+
const result = await base.anchor(traceRoot, {
|
|
370
370
|
executorDid: 'did:atel:ed25519:...',
|
|
371
371
|
requesterDid: 'did:atel:ed25519:...',
|
|
372
372
|
taskId: 'task-123'
|
|
373
373
|
});
|
|
374
374
|
// result.txHash, result.blockNumber
|
|
375
375
|
|
|
376
|
-
const verified = await
|
|
376
|
+
const verified = await base.verify(traceRoot, txHash);
|
|
377
377
|
// verified.valid, verified.detail
|
|
378
378
|
```
|
|
379
379
|
|
|
@@ -433,8 +433,6 @@ Friend system data is stored in `.atel/`:
|
|
|
433
433
|
## Environment Variables
|
|
434
434
|
|
|
435
435
|
**On-Chain Anchoring:**
|
|
436
|
-
- `ATEL_SOLANA_PRIVATE_KEY` - Solana wallet private key (base58)
|
|
437
|
-
- `ATEL_SOLANA_RPC_URL` - Solana RPC endpoint
|
|
438
436
|
- `ATEL_BASE_PRIVATE_KEY` - Base chain private key (hex)
|
|
439
437
|
- `ATEL_BASE_RPC_URL` - Base RPC endpoint
|
|
440
438
|
- `ATEL_BSC_PRIVATE_KEY` - BSC private key (hex)
|
package/bin/atel.mjs
CHANGED
|
@@ -59,7 +59,7 @@ import crypto from 'node:crypto';
|
|
|
59
59
|
import {
|
|
60
60
|
AgentIdentity, AgentEndpoint, AgentClient, HandshakeManager,
|
|
61
61
|
createMessage, verifyMessage, parseDID, RegistryClient, ExecutionTrace, ProofGenerator,
|
|
62
|
-
|
|
62
|
+
BaseAnchorProvider, BSCAnchorProvider,
|
|
63
63
|
autoNetworkSetup, collectCandidates, connectToAgent,
|
|
64
64
|
discoverPublicIP, checkReachable, verifyPortReachable, ContentAuditor, TrustScoreClient,
|
|
65
65
|
RollbackManager, rotateKey, verifyKeyRotation, ToolGateway, PolicyEngine, mintConsentToken, sign,
|
|
@@ -107,7 +107,6 @@ function getEnvironmentProfile(url = '') {
|
|
|
107
107
|
const host = String(parsed.hostname || '').toLowerCase();
|
|
108
108
|
if (host === 'api.atelai.org') return { name: 'production', label: 'production', url: normalized };
|
|
109
109
|
if (host === '127.0.0.1' || host === 'localhost') return { name: 'test', label: 'local-test', url: normalized };
|
|
110
|
-
if (host === '43.160.230.129' || host === '43.160.211.180') return { name: 'test', label: 'host-test', url: normalized };
|
|
111
110
|
return { name: 'custom', label: host || 'custom', url: normalized };
|
|
112
111
|
} catch {
|
|
113
112
|
return { name: 'custom', label: normalized, url: normalized };
|
|
@@ -2162,7 +2161,6 @@ function getChainPrivateKey(chain) {
|
|
|
2162
2161
|
return config.chains[chain].privateKey;
|
|
2163
2162
|
}
|
|
2164
2163
|
// 2. Fall back to environment variables (backward compatibility)
|
|
2165
|
-
if (chain === 'solana') return process.env.ATEL_SOLANA_PRIVATE_KEY;
|
|
2166
2164
|
if (chain === 'base') return process.env.ATEL_BASE_PRIVATE_KEY;
|
|
2167
2165
|
if (chain === 'bsc') return process.env.ATEL_BSC_PRIVATE_KEY;
|
|
2168
2166
|
return null;
|
|
@@ -2173,19 +2171,6 @@ async function getWalletAddresses() {
|
|
|
2173
2171
|
const wallets = {};
|
|
2174
2172
|
const config = loadAnchorConfig();
|
|
2175
2173
|
|
|
2176
|
-
// Solana: base58 private key → public key
|
|
2177
|
-
const solKey = getChainPrivateKey('solana');
|
|
2178
|
-
if (solKey) {
|
|
2179
|
-
try {
|
|
2180
|
-
const { Keypair } = await import('@solana/web3.js');
|
|
2181
|
-
const bs58 = (await import('bs58')).default;
|
|
2182
|
-
const kp = Keypair.fromSecretKey(bs58.decode(solKey));
|
|
2183
|
-
wallets.solana = kp.publicKey.toBase58();
|
|
2184
|
-
} catch {}
|
|
2185
|
-
} else if (config?.chains?.solana?.address) {
|
|
2186
|
-
wallets.solana = config.chains.solana.address;
|
|
2187
|
-
}
|
|
2188
|
-
|
|
2189
2174
|
// Base: hex private key → address
|
|
2190
2175
|
const baseKey = getChainPrivateKey('base');
|
|
2191
2176
|
if (baseKey) {
|
|
@@ -2961,16 +2946,23 @@ function riskAllowed(maxRisk, requestedRisk) { return (RISK_ORDER[requestedRisk]
|
|
|
2961
2946
|
// Verify anchor_tx list on-chain, return count of valid proofs
|
|
2962
2947
|
async function verifyAnchorTxList(anchorTxList, targetDid) {
|
|
2963
2948
|
if (!anchorTxList || anchorTxList.length === 0) return { verified: 0, total: 0, proofs: [] };
|
|
2964
|
-
const rpcUrl = process.env.ATEL_SOLANA_RPC_URL || 'https://api.mainnet-beta.solana.com';
|
|
2965
|
-
const provider = new SolanaAnchorProvider({ rpcUrl });
|
|
2966
2949
|
let verified = 0;
|
|
2967
2950
|
const proofs = [];
|
|
2968
2951
|
for (const tx of anchorTxList) {
|
|
2969
2952
|
try {
|
|
2953
|
+
const chain = String(tx?.chain || detectPreferredChain() || 'base').toLowerCase();
|
|
2954
|
+
let provider;
|
|
2955
|
+
if (chain === 'base') {
|
|
2956
|
+
provider = new BaseAnchorProvider({ rpcUrl: process.env.ATEL_BASE_RPC_URL || 'https://mainnet.base.org' });
|
|
2957
|
+
} else if (chain === 'bsc') {
|
|
2958
|
+
provider = new BSCAnchorProvider({ rpcUrl: process.env.ATEL_BSC_RPC_URL || 'https://bsc-dataseed.binance.org' });
|
|
2959
|
+
} else {
|
|
2960
|
+
continue;
|
|
2961
|
+
}
|
|
2970
2962
|
const result = await provider.verify(tx.trace_root || '', tx.txHash || tx.anchor_tx || '');
|
|
2971
2963
|
if (result.valid) {
|
|
2972
2964
|
verified++;
|
|
2973
|
-
proofs.push({ proof_id: tx.proof_id || tx.txHash, trace_root: tx.trace_root, verified: true, anchor_tx: tx.txHash || tx.anchor_tx, timestamp: new Date().toISOString() });
|
|
2965
|
+
proofs.push({ proof_id: tx.proof_id || tx.txHash, trace_root: tx.trace_root, verified: true, anchor_tx: tx.txHash || tx.anchor_tx, chain, timestamp: new Date().toISOString() });
|
|
2974
2966
|
}
|
|
2975
2967
|
} catch {}
|
|
2976
2968
|
}
|
|
@@ -3041,12 +3033,7 @@ async function anchorOnChain(traceRoot, metadata, preferredChain) {
|
|
|
3041
3033
|
if (!key) return null;
|
|
3042
3034
|
|
|
3043
3035
|
let provider;
|
|
3044
|
-
if (chain === '
|
|
3045
|
-
provider = new SolanaAnchorProvider({
|
|
3046
|
-
rpcUrl: process.env.ATEL_SOLANA_RPC_URL || 'https://api.mainnet-beta.solana.com',
|
|
3047
|
-
privateKey: key
|
|
3048
|
-
});
|
|
3049
|
-
} else if (chain === 'base') {
|
|
3036
|
+
if (chain === 'base') {
|
|
3050
3037
|
provider = new BaseAnchorProvider({
|
|
3051
3038
|
rpcUrl: process.env.ATEL_BASE_RPC_URL || 'https://mainnet.base.org',
|
|
3052
3039
|
privateKey: key,
|
|
@@ -3161,16 +3148,9 @@ async function configureAnchor() {
|
|
|
3161
3148
|
// 3. Validate private key and derive address
|
|
3162
3149
|
let address;
|
|
3163
3150
|
try {
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
const kp = Keypair.fromSecretKey(bs58.decode(privateKey));
|
|
3168
|
-
address = kp.publicKey.toBase58();
|
|
3169
|
-
} else {
|
|
3170
|
-
const { ethers } = await import('ethers');
|
|
3171
|
-
const wallet = new ethers.Wallet(privateKey);
|
|
3172
|
-
address = wallet.address;
|
|
3173
|
-
}
|
|
3151
|
+
const { ethers } = await import('ethers');
|
|
3152
|
+
const wallet = new ethers.Wallet(privateKey);
|
|
3153
|
+
address = wallet.address;
|
|
3174
3154
|
} catch (e) {
|
|
3175
3155
|
console.error(`❌ Invalid ${chain.toUpperCase()} private key: ${e.message}`);
|
|
3176
3156
|
process.exit(1);
|
|
@@ -3734,14 +3714,6 @@ async function cmdStart(port) {
|
|
|
3734
3714
|
if (!txHash || !traceRoot) return { checked: false, verified: false, reason: 'missing_anchor_or_root' };
|
|
3735
3715
|
const c = (chain || 'base').toLowerCase();
|
|
3736
3716
|
|
|
3737
|
-
if (c === 'solana') {
|
|
3738
|
-
const rpcUrl = process.env.ATEL_SOLANA_RPC_URL || 'https://api.mainnet-beta.solana.com';
|
|
3739
|
-
if (process.env.ATEL_DEBUG) console.error('[DEBUG] Solana RPC URL:', rpcUrl);
|
|
3740
|
-
const provider = new SolanaAnchorProvider({ rpcUrl });
|
|
3741
|
-
const r = await provider.verify(traceRoot, txHash);
|
|
3742
|
-
if (process.env.ATEL_DEBUG) console.error('[DEBUG] Solana verify result:', r);
|
|
3743
|
-
return { checked: true, verified: !!r?.valid, chain: 'solana', detail: r?.detail };
|
|
3744
|
-
}
|
|
3745
3717
|
if (c === 'base') {
|
|
3746
3718
|
const rpcUrl = process.env.ATEL_BASE_RPC_URL || 'https://mainnet.base.org';
|
|
3747
3719
|
if (process.env.ATEL_DEBUG) console.error('[DEBUG] Base RPC URL:', rpcUrl);
|
|
@@ -4303,6 +4275,15 @@ async function cmdStart(port) {
|
|
|
4303
4275
|
'urllib.request.urlopen(req, timeout=10).read()',
|
|
4304
4276
|
'PY',
|
|
4305
4277
|
].join('\n'),
|
|
4278
|
+
executor_milestone: [
|
|
4279
|
+
"python3 - <<'PY'",
|
|
4280
|
+
'import json, urllib.request',
|
|
4281
|
+
`result = """Write the final deliverable here"""`,
|
|
4282
|
+
`body = {"dedupeKey": "${dedupeKey}", "status": "done", "eventType": "${eventType}", "orderId": "${payload?.orderId || ''}", "milestoneIndex": ${Number.isFinite(Number(payload?.currentMilestone)) ? Number(payload.currentMilestone) : Number.isFinite(Number(payload?.milestoneIndex)) ? Number(payload.milestoneIndex) : 0}, "result": result, "summary": result[:120]}`,
|
|
4283
|
+
`req = urllib.request.Request("${callbackUrl}", data=json.dumps(body).encode(), headers={"Content-Type": "application/json"})`,
|
|
4284
|
+
'urllib.request.urlopen(req, timeout=10).read()',
|
|
4285
|
+
'PY',
|
|
4286
|
+
].join('\n'),
|
|
4306
4287
|
default: [
|
|
4307
4288
|
"python3 - <<'PY'",
|
|
4308
4289
|
'import json, urllib.request',
|
|
@@ -4323,7 +4304,11 @@ async function cmdStart(port) {
|
|
|
4323
4304
|
'PY',
|
|
4324
4305
|
].join('\n');
|
|
4325
4306
|
|
|
4326
|
-
const callbackDone = eventType === 'milestone_submitted'
|
|
4307
|
+
const callbackDone = eventType === 'milestone_submitted'
|
|
4308
|
+
? callbackExamples.milestone_submitted
|
|
4309
|
+
: (['milestone_plan_confirmed', 'milestone_verified', 'milestone_rejected'].includes(eventType)
|
|
4310
|
+
? callbackExamples.executor_milestone
|
|
4311
|
+
: callbackExamples.default);
|
|
4327
4312
|
const contextFile = join(cwd, 'ORDER_CONTEXT.md');
|
|
4328
4313
|
const allowRepoAccess = shouldAllowRepoAccess(payload);
|
|
4329
4314
|
const fileAccessRule = allowRepoAccess
|
|
@@ -4371,7 +4356,7 @@ ${callbackFailed}
|
|
|
4371
4356
|
`;
|
|
4372
4357
|
}
|
|
4373
4358
|
|
|
4374
|
-
function buildLocalAgentPrompt(eventType, promptText) {
|
|
4359
|
+
function buildLocalAgentPrompt(eventType, promptText, payload = {}) {
|
|
4375
4360
|
if (eventType === 'milestone_submitted') {
|
|
4376
4361
|
return `${promptText}
|
|
4377
4362
|
|
|
@@ -4387,6 +4372,10 @@ For rejection:
|
|
|
4387
4372
|
}
|
|
4388
4373
|
|
|
4389
4374
|
if (['milestone_plan_confirmed', 'milestone_verified', 'milestone_rejected'].includes(eventType)) {
|
|
4375
|
+
const expectedIndex = eventType === 'milestone_plan_confirmed'
|
|
4376
|
+
? (Number.isFinite(Number(payload?.milestoneIndex)) ? Number(payload.milestoneIndex) : 0)
|
|
4377
|
+
: (Number.isFinite(Number(payload?.currentMilestone)) ? Number(payload.currentMilestone) : Number.isFinite(Number(payload?.milestoneIndex)) ? Number(payload.milestoneIndex) : 0);
|
|
4378
|
+
const expectedOrderId = String(payload?.orderId || '').trim();
|
|
4390
4379
|
return `${promptText}
|
|
4391
4380
|
|
|
4392
4381
|
Important: you are not chatting with the user.
|
|
@@ -4400,9 +4389,11 @@ You must prefer factual evidence:
|
|
|
4400
4389
|
- what concrete output / state / observation you actually got
|
|
4401
4390
|
- whether that evidence satisfies the current milestone
|
|
4402
4391
|
If you could not find evidence, say that explicitly in the JSON result, including what you checked.
|
|
4392
|
+
The JSON must include the exact current order and milestone identity.
|
|
4393
|
+
If you are not certain, fail closed instead of guessing.
|
|
4403
4394
|
|
|
4404
4395
|
Format:
|
|
4405
|
-
{"result":"factual deliverable with concrete evidence and observations only"}`;
|
|
4396
|
+
{"orderId":"${expectedOrderId}","milestoneIndex":${expectedIndex},"result":"factual deliverable with concrete evidence and observations only"}`;
|
|
4406
4397
|
}
|
|
4407
4398
|
|
|
4408
4399
|
return promptText;
|
|
@@ -4531,7 +4522,7 @@ Format:
|
|
|
4531
4522
|
if (isKnownInvalidLocalAgentStdout(cleaned)) {
|
|
4532
4523
|
return { ok: false, error: 'invalid_local_agent_stdout_known_failure' };
|
|
4533
4524
|
}
|
|
4534
|
-
return
|
|
4525
|
+
return { ok: false, error: 'invalid_local_agent_stdout_requires_json' };
|
|
4535
4526
|
}
|
|
4536
4527
|
}
|
|
4537
4528
|
|
|
@@ -5301,15 +5292,15 @@ Advance the current milestone strictly based on these approved results. Do not i
|
|
|
5301
5292
|
return;
|
|
5302
5293
|
}
|
|
5303
5294
|
log({ event: 'agent_session_spawn_error', eventType: hookEvent, dedupeKey: hookKey, error: gatewayResult.error, fallback: 'cli' });
|
|
5304
|
-
spawnArgs[spawnArgs.length - 1] = buildLocalAgentPrompt(hookEvent, promptArg);
|
|
5295
|
+
spawnArgs[spawnArgs.length - 1] = buildLocalAgentPrompt(hookEvent, promptArg, hookPayload);
|
|
5305
5296
|
} else if (spawnArgs.length > 0) {
|
|
5306
5297
|
const promptArg = spawnArgs[spawnArgs.length - 1] || '';
|
|
5307
|
-
spawnArgs[spawnArgs.length - 1] = buildLocalAgentPrompt(hookEvent, promptArg);
|
|
5298
|
+
spawnArgs[spawnArgs.length - 1] = buildLocalAgentPrompt(hookEvent, promptArg, hookPayload);
|
|
5308
5299
|
}
|
|
5309
5300
|
|
|
5310
5301
|
const MAX_ATTEMPTS = 5;
|
|
5311
5302
|
const isMilestoneHook = ['milestone_plan_confirmed', 'milestone_verified', 'milestone_rejected', 'milestone_submitted'].includes(hookEvent);
|
|
5312
|
-
const localHookTimeoutMs = isMilestoneHook ?
|
|
5303
|
+
const localHookTimeoutMs = isMilestoneHook ? 240000 : 600000;
|
|
5313
5304
|
const preparedInvocation = prepareHookInvocation(spawnCmd, spawnArgs, hookKey, Math.ceil(localHookTimeoutMs / 1000));
|
|
5314
5305
|
const runHook = (attempt, invocation = preparedInvocation) => {
|
|
5315
5306
|
const hookStartedAt = Date.now();
|
|
@@ -5607,7 +5598,7 @@ Advance the current milestone strictly based on these approved results. Do not i
|
|
|
5607
5598
|
|
|
5608
5599
|
// ── Anchoring Warning ──
|
|
5609
5600
|
if (!anchor) {
|
|
5610
|
-
log({ event: 'anchor_missing', taskId, warning: 'Proof not anchored on-chain. Set
|
|
5601
|
+
log({ event: 'anchor_missing', taskId, warning: 'Proof not anchored on-chain. Set a supported anchor key for verifiable trust.', timestamp: new Date().toISOString() });
|
|
5611
5602
|
}
|
|
5612
5603
|
|
|
5613
5604
|
|
|
@@ -7193,22 +7184,32 @@ async function cmdVerifyProof(anchorTx, traceRoot) {
|
|
|
7193
7184
|
|
|
7194
7185
|
console.log(JSON.stringify({ event: 'verifying_proof', anchor_tx: anchorTx, trace_root: traceRoot }));
|
|
7195
7186
|
|
|
7196
|
-
const
|
|
7197
|
-
|
|
7198
|
-
|
|
7199
|
-
|
|
7200
|
-
|
|
7201
|
-
|
|
7202
|
-
|
|
7203
|
-
|
|
7204
|
-
|
|
7205
|
-
|
|
7206
|
-
|
|
7207
|
-
|
|
7208
|
-
|
|
7209
|
-
|
|
7210
|
-
|
|
7187
|
+
const providers = [
|
|
7188
|
+
{ chain: 'base', provider: new BaseAnchorProvider({ rpcUrl: process.env.ATEL_BASE_RPC_URL || 'https://mainnet.base.org' }) },
|
|
7189
|
+
{ chain: 'bsc', provider: new BSCAnchorProvider({ rpcUrl: process.env.ATEL_BSC_RPC_URL || 'https://bsc-dataseed.binance.org' }) },
|
|
7190
|
+
];
|
|
7191
|
+
const attempts = [];
|
|
7192
|
+
for (const entry of providers) {
|
|
7193
|
+
try {
|
|
7194
|
+
const result = await entry.provider.verify(traceRoot, anchorTx);
|
|
7195
|
+
attempts.push({ chain: entry.chain, result });
|
|
7196
|
+
if (result.valid) {
|
|
7197
|
+
console.log(JSON.stringify({
|
|
7198
|
+
verified: true,
|
|
7199
|
+
chain: entry.chain,
|
|
7200
|
+
anchor_tx: anchorTx,
|
|
7201
|
+
trace_root: traceRoot,
|
|
7202
|
+
detail: result.detail || 'Anchor matches trace_root',
|
|
7203
|
+
block: result.blockNumber,
|
|
7204
|
+
timestamp: result.timestamp,
|
|
7205
|
+
}, null, 2));
|
|
7206
|
+
return;
|
|
7207
|
+
}
|
|
7208
|
+
} catch (e) {
|
|
7209
|
+
attempts.push({ chain: entry.chain, error: e.message });
|
|
7210
|
+
}
|
|
7211
7211
|
}
|
|
7212
|
+
console.log(JSON.stringify({ verified: false, anchor_tx: anchorTx, trace_root: traceRoot, attempts }, null, 2));
|
|
7212
7213
|
}
|
|
7213
7214
|
|
|
7214
7215
|
async function cmdAudit(targetDidOrUrl, taskId) {
|
|
@@ -7320,13 +7321,16 @@ async function cmdRotate() {
|
|
|
7320
7321
|
|
|
7321
7322
|
// Anchor rotation on-chain if possible
|
|
7322
7323
|
let anchor = null;
|
|
7323
|
-
const
|
|
7324
|
+
const chain = detectPreferredChain() || 'base';
|
|
7325
|
+
const key = getChainPrivateKey(chain);
|
|
7324
7326
|
if (key) {
|
|
7325
7327
|
try {
|
|
7326
|
-
const
|
|
7328
|
+
const provider = chain === 'bsc'
|
|
7329
|
+
? new BSCAnchorProvider({ rpcUrl: process.env.ATEL_BSC_RPC_URL || 'https://bsc-dataseed.binance.org', privateKey: key })
|
|
7330
|
+
: new BaseAnchorProvider({ rpcUrl: process.env.ATEL_BASE_RPC_URL || 'https://mainnet.base.org', privateKey: key, anchorRegistryAddress: process.env.ATEL_ANCHOR_REGISTRY_ADDRESS || undefined });
|
|
7327
7331
|
const { createHash } = await import('node:crypto');
|
|
7328
7332
|
const rotationHash = createHash('sha256').update(JSON.stringify(proof)).digest('hex');
|
|
7329
|
-
anchor = await
|
|
7333
|
+
anchor = await provider.anchor(`rotation:${rotationHash}`, { oldDid, newDid: newIdentity.did, type: 'key_rotation' });
|
|
7330
7334
|
} catch (e) { console.log(JSON.stringify({ warning: 'On-chain anchor failed', error: e.message })); }
|
|
7331
7335
|
}
|
|
7332
7336
|
|
|
@@ -7524,6 +7528,10 @@ async function cmdWithdraw(amount, address, chain) {
|
|
|
7524
7528
|
}
|
|
7525
7529
|
// Smart wallet withdrawal (new flow)
|
|
7526
7530
|
chain = chain || 'base';
|
|
7531
|
+
if (chain !== 'base' && chain !== 'bsc') {
|
|
7532
|
+
console.error('Unsupported withdrawal chain. Supported chains: base, bsc.');
|
|
7533
|
+
process.exit(1);
|
|
7534
|
+
}
|
|
7527
7535
|
|
|
7528
7536
|
// Validate destination address
|
|
7529
7537
|
const channel = 'crypto_' + chain;
|
|
@@ -7532,11 +7540,6 @@ async function cmdWithdraw(amount, address, chain) {
|
|
|
7532
7540
|
console.error('Invalid EVM address. Must be 0x followed by 40 hex characters.');
|
|
7533
7541
|
process.exit(1);
|
|
7534
7542
|
}
|
|
7535
|
-
} else if (channel === 'crypto_solana') {
|
|
7536
|
-
if (!address.match(/^[1-9A-HJ-NP-Za-km-z]{32,44}$/)) {
|
|
7537
|
-
console.error('Invalid Solana address.');
|
|
7538
|
-
process.exit(1);
|
|
7539
|
-
}
|
|
7540
7543
|
}
|
|
7541
7544
|
|
|
7542
7545
|
try {
|
|
@@ -8232,9 +8235,9 @@ async function cmdComplete(orderId, taskId) {
|
|
|
8232
8235
|
console.error('[complete] Anchoring proof on-chain...');
|
|
8233
8236
|
anchor = await anchorOnChain(proof.trace_root, { proof_id: proof.proof_id, executorDid: id.did, requesterDid, taskId: effectiveTaskId, action: 'cli-complete' });
|
|
8234
8237
|
if (anchor) {
|
|
8235
|
-
console.error(`[complete] Anchored on
|
|
8238
|
+
console.error(`[complete] Anchored on-chain: ${anchor.txHash}`);
|
|
8236
8239
|
} else {
|
|
8237
|
-
console.error('[complete] WARNING: On-chain anchoring failed. Set
|
|
8240
|
+
console.error('[complete] WARNING: On-chain anchoring failed. Set a supported anchor key for verifiable trust.');
|
|
8238
8241
|
}
|
|
8239
8242
|
}
|
|
8240
8243
|
|
|
@@ -10790,8 +10793,6 @@ Environment:
|
|
|
10790
10793
|
ATEL_REGISTRY Registry URL (default: https://api.atelai.org)
|
|
10791
10794
|
ATEL_PLATFORM Platform URL (default: ATEL_REGISTRY value)
|
|
10792
10795
|
ATEL_EXECUTOR_URL Local executor HTTP endpoint
|
|
10793
|
-
ATEL_SOLANA_PRIVATE_KEY Solana key for on-chain anchoring
|
|
10794
|
-
ATEL_SOLANA_RPC_URL Solana RPC (default: mainnet-beta)
|
|
10795
10796
|
ATEL_BASE_PRIVATE_KEY Base chain key for on-chain anchoring
|
|
10796
10797
|
ATEL_BSC_PRIVATE_KEY BSC chain key for on-chain anchoring
|
|
10797
10798
|
|
|
@@ -57,11 +57,13 @@ export function shouldSkipAgentHook(eventType, directExecutionSucceeded) {
|
|
|
57
57
|
return eventType === 'order_accepted' && directExecutionSucceeded;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
const EXECUTOR_MILESTONE_EVENTS = new Set(['milestone_plan_confirmed', 'milestone_verified', 'milestone_rejected']);
|
|
61
|
+
|
|
60
62
|
export function shouldUseGatewaySession(eventType) {
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
-
//
|
|
64
|
-
return eventType === 'p2p_task';
|
|
63
|
+
// Use isolated gateway sub-sessions for milestone executor turns so each
|
|
64
|
+
// order+milestone attempt runs in a clean room instead of sharing the main
|
|
65
|
+
// agent runtime context.
|
|
66
|
+
return eventType === 'p2p_task' || EXECUTOR_MILESTONE_EVENTS.has(eventType);
|
|
65
67
|
}
|
|
66
68
|
|
|
67
69
|
export function normalizeGatewayBind(bind) {
|
|
@@ -81,6 +83,44 @@ function normalizeResult(value) {
|
|
|
81
83
|
return value.trim();
|
|
82
84
|
}
|
|
83
85
|
|
|
86
|
+
function normalizeOrderId(value) {
|
|
87
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function extractForeignOrderId(text, expectedOrderId) {
|
|
91
|
+
const current = normalizeOrderId(expectedOrderId);
|
|
92
|
+
if (!current) return '';
|
|
93
|
+
const found = Array.from(String(text || '').matchAll(/ord-[a-f0-9-]+/g)).map((m) => m[0]);
|
|
94
|
+
return found.find((item) => item !== current) || '';
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function validateExecutorMilestoneBody(eventType, payload, body) {
|
|
98
|
+
if (!EXECUTOR_MILESTONE_EVENTS.has(eventType)) return { ok: true };
|
|
99
|
+
const expectedOrderId = normalizeOrderId(payload?.orderId);
|
|
100
|
+
if (!expectedOrderId) return { ok: false, error: 'missing_order_id' };
|
|
101
|
+
const actualOrderId = normalizeOrderId(body?.orderId);
|
|
102
|
+
if (!actualOrderId) return { ok: false, error: 'missing_result_order_id' };
|
|
103
|
+
if (actualOrderId !== expectedOrderId) return { ok: false, error: 'mismatched_result_order_id' };
|
|
104
|
+
|
|
105
|
+
const expectedIndex = eventType === 'milestone_plan_confirmed'
|
|
106
|
+
? normalizeIndex(payload?.milestoneIndex, 0)
|
|
107
|
+
: normalizeIndex(payload?.currentMilestone ?? payload?.milestoneIndex, 0);
|
|
108
|
+
if (!Number.isFinite(Number(body?.milestoneIndex))) return { ok: false, error: 'missing_result_milestone_index' };
|
|
109
|
+
const actualIndex = normalizeIndex(body?.milestoneIndex, -1);
|
|
110
|
+
if (actualIndex !== expectedIndex) return { ok: false, error: 'mismatched_result_milestone_index' };
|
|
111
|
+
|
|
112
|
+
const result = normalizeResult(body?.result || body?.summary);
|
|
113
|
+
if (!result) return { ok: false, error: 'missing_result' };
|
|
114
|
+
const lowered = result.toLowerCase();
|
|
115
|
+
if (lowered.includes('invalid_cross_order_reference')) return { ok: false, error: 'invalid_cross_order_reference' };
|
|
116
|
+
if (lowered.includes('context overflow')) return { ok: false, error: 'context_overflow_output' };
|
|
117
|
+
if (lowered.includes('plugin register() called') || lowered.includes('plugin registration complete')) return { ok: false, error: 'plugin_noise_output' };
|
|
118
|
+
if (lowered.includes('session file locked') || lowered.includes('session locked')) return { ok: false, error: 'session_locked_output' };
|
|
119
|
+
const foreign = extractForeignOrderId(result, expectedOrderId);
|
|
120
|
+
if (foreign) return { ok: false, error: 'foreign_order_reference_detected' };
|
|
121
|
+
return { ok: true, result, orderId: expectedOrderId, milestoneIndex: expectedIndex };
|
|
122
|
+
}
|
|
123
|
+
|
|
84
124
|
export function buildAgentCallbackAction(eventType, payload, body) {
|
|
85
125
|
if (eventType === 'p2p_task') {
|
|
86
126
|
const taskId = payload?.taskId;
|
|
@@ -102,44 +142,41 @@ export function buildAgentCallbackAction(eventType, payload, body) {
|
|
|
102
142
|
if (!orderId) return { ok: false, error: 'missing_order_id' };
|
|
103
143
|
|
|
104
144
|
if (eventType === 'milestone_plan_confirmed') {
|
|
105
|
-
const
|
|
106
|
-
if (!
|
|
107
|
-
const index = normalizeIndex(payload?.milestoneIndex, 0);
|
|
145
|
+
const validated = validateExecutorMilestoneBody(eventType, payload, body);
|
|
146
|
+
if (!validated.ok) return validated;
|
|
108
147
|
return {
|
|
109
148
|
ok: true,
|
|
110
149
|
action: {
|
|
111
150
|
type: 'cli',
|
|
112
151
|
action: 'submit_milestone',
|
|
113
|
-
command: ['atel', 'milestone-submit', orderId, String(
|
|
152
|
+
command: ['atel', 'milestone-submit', orderId, String(validated.milestoneIndex), '--result', validated.result],
|
|
114
153
|
},
|
|
115
154
|
};
|
|
116
155
|
}
|
|
117
156
|
|
|
118
157
|
if (eventType === 'milestone_verified') {
|
|
119
158
|
if (payload?.allComplete) return { ok: false, skipped: true, reason: 'all_complete' };
|
|
120
|
-
const
|
|
121
|
-
if (!
|
|
122
|
-
const index = normalizeIndex(payload?.currentMilestone, 0);
|
|
159
|
+
const validated = validateExecutorMilestoneBody(eventType, payload, body);
|
|
160
|
+
if (!validated.ok) return validated;
|
|
123
161
|
return {
|
|
124
162
|
ok: true,
|
|
125
163
|
action: {
|
|
126
164
|
type: 'cli',
|
|
127
165
|
action: 'submit_milestone',
|
|
128
|
-
command: ['atel', 'milestone-submit', orderId, String(
|
|
166
|
+
command: ['atel', 'milestone-submit', orderId, String(validated.milestoneIndex), '--result', validated.result],
|
|
129
167
|
},
|
|
130
168
|
};
|
|
131
169
|
}
|
|
132
170
|
|
|
133
171
|
if (eventType === 'milestone_rejected') {
|
|
134
|
-
const
|
|
135
|
-
if (!
|
|
136
|
-
const index = normalizeIndex(payload?.milestoneIndex, 0);
|
|
172
|
+
const validated = validateExecutorMilestoneBody(eventType, payload, body);
|
|
173
|
+
if (!validated.ok) return validated;
|
|
137
174
|
return {
|
|
138
175
|
ok: true,
|
|
139
176
|
action: {
|
|
140
177
|
type: 'cli',
|
|
141
178
|
action: 'resubmit',
|
|
142
|
-
command: ['atel', 'milestone-submit', orderId, String(
|
|
179
|
+
command: ['atel', 'milestone-submit', orderId, String(validated.milestoneIndex), '--result', validated.result],
|
|
143
180
|
},
|
|
144
181
|
};
|
|
145
182
|
}
|
package/dist/anchor/index.d.ts
CHANGED
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
* (proof_id, trace_root, etc.) on public blockchains for tamper-evident
|
|
6
6
|
* timestamping and auditability.
|
|
7
7
|
*
|
|
8
|
-
* Supported chains: Base (EVM)
|
|
8
|
+
* Supported chains: Base (EVM) and BSC (EVM).
|
|
9
9
|
* A MockAnchorProvider is included for testing without real chain access.
|
|
10
10
|
*/
|
|
11
11
|
/** Supported chain identifiers */
|
|
12
|
-
export type ChainId = 'base' | '
|
|
12
|
+
export type ChainId = 'base' | 'bsc' | 'mock';
|
|
13
13
|
/**
|
|
14
14
|
* A record of a hash anchored on-chain.
|
|
15
15
|
*/
|
|
@@ -169,5 +169,4 @@ export declare class AnchorManager {
|
|
|
169
169
|
export { EvmAnchorProvider, type EvmAnchorConfig, type EvmAnchorMemoV2 } from './evm.js';
|
|
170
170
|
export { BaseAnchorProvider } from './base.js';
|
|
171
171
|
export { BSCAnchorProvider } from './bsc.js';
|
|
172
|
-
export { SolanaAnchorProvider, type SolanaAnchorConfig, type AnchorMemoV2 } from './solana.js';
|
|
173
172
|
export { MockAnchorProvider } from './mock.js';
|
package/dist/anchor/index.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* (proof_id, trace_root, etc.) on public blockchains for tamper-evident
|
|
6
6
|
* timestamping and auditability.
|
|
7
7
|
*
|
|
8
|
-
* Supported chains: Base (EVM)
|
|
8
|
+
* Supported chains: Base (EVM) and BSC (EVM).
|
|
9
9
|
* A MockAnchorProvider is included for testing without real chain access.
|
|
10
10
|
*/
|
|
11
11
|
// ─── AnchorManager ───────────────────────────────────────────────
|
|
@@ -161,5 +161,4 @@ export class AnchorManager {
|
|
|
161
161
|
export { EvmAnchorProvider } from './evm.js';
|
|
162
162
|
export { BaseAnchorProvider } from './base.js';
|
|
163
163
|
export { BSCAnchorProvider } from './bsc.js';
|
|
164
|
-
export { SolanaAnchorProvider } from './solana.js';
|
|
165
164
|
export { MockAnchorProvider } from './mock.js';
|
package/dist/endpoint/index.d.ts
CHANGED
|
@@ -55,7 +55,6 @@ export interface EndpointConfig {
|
|
|
55
55
|
rateLimit?: RateLimitConfig;
|
|
56
56
|
/** Wallet addresses for on-chain trust verification during handshake */
|
|
57
57
|
wallets?: {
|
|
58
|
-
solana?: string;
|
|
59
58
|
base?: string;
|
|
60
59
|
bsc?: string;
|
|
61
60
|
};
|
|
@@ -124,7 +123,6 @@ export declare class AgentClient {
|
|
|
124
123
|
* Establishes both identity verification and E2E encryption.
|
|
125
124
|
*/
|
|
126
125
|
handshake(remoteEndpoint: string, handshakeManager: HandshakeManager, remoteDid: string, wallets?: {
|
|
127
|
-
solana?: string;
|
|
128
126
|
base?: string;
|
|
129
127
|
bsc?: string;
|
|
130
128
|
}): Promise<import('../handshake/index.js').Session>;
|
|
@@ -16,7 +16,6 @@ import { EncryptionManager } from '../crypto/index.js';
|
|
|
16
16
|
/** Wallet addresses with DID-signed proof of ownership */
|
|
17
17
|
export interface WalletBundle {
|
|
18
18
|
addresses: {
|
|
19
|
-
solana?: string;
|
|
20
19
|
base?: string;
|
|
21
20
|
bsc?: string;
|
|
22
21
|
};
|
|
@@ -32,7 +31,6 @@ export interface HandshakeInitPayload {
|
|
|
32
31
|
capabilities?: string[];
|
|
33
32
|
/** Wallet addresses for on-chain trust verification */
|
|
34
33
|
wallets?: {
|
|
35
|
-
solana?: string;
|
|
36
34
|
base?: string;
|
|
37
35
|
bsc?: string;
|
|
38
36
|
};
|
|
@@ -49,7 +47,6 @@ export interface HandshakeAckPayload {
|
|
|
49
47
|
capabilities?: string[];
|
|
50
48
|
/** Wallet addresses for on-chain trust verification */
|
|
51
49
|
wallets?: {
|
|
52
|
-
solana?: string;
|
|
53
50
|
base?: string;
|
|
54
51
|
bsc?: string;
|
|
55
52
|
};
|
|
@@ -76,7 +73,6 @@ export interface Session {
|
|
|
76
73
|
remoteCapabilities?: string[];
|
|
77
74
|
/** Remote agent's wallet addresses (if provided) */
|
|
78
75
|
remoteWallets?: {
|
|
79
|
-
solana?: string;
|
|
80
76
|
base?: string;
|
|
81
77
|
bsc?: string;
|
|
82
78
|
};
|
|
@@ -103,7 +99,6 @@ export declare class HandshakeError extends Error {
|
|
|
103
99
|
}
|
|
104
100
|
/** Create a signed wallet bundle proving DID ownership of wallet addresses */
|
|
105
101
|
export declare function createWalletBundle(addresses: {
|
|
106
|
-
solana?: string;
|
|
107
102
|
base?: string;
|
|
108
103
|
bsc?: string;
|
|
109
104
|
}, secretKey: Uint8Array): WalletBundle;
|
|
@@ -131,7 +126,6 @@ export declare class HandshakeManager {
|
|
|
131
126
|
* Create a handshake_init message (Step 1).
|
|
132
127
|
*/
|
|
133
128
|
createInit(remoteDid: string, wallets?: {
|
|
134
|
-
solana?: string;
|
|
135
129
|
base?: string;
|
|
136
130
|
bsc?: string;
|
|
137
131
|
}): ATELMessage<HandshakeInitPayload>;
|
|
@@ -146,7 +140,6 @@ export declare class HandshakeManager {
|
|
|
146
140
|
* Process a handshake_init message and create handshake_ack (Step 2).
|
|
147
141
|
*/
|
|
148
142
|
processInit(initMessage: ATELMessage<HandshakeInitPayload>, wallets?: {
|
|
149
|
-
solana?: string;
|
|
150
143
|
base?: string;
|
|
151
144
|
bsc?: string;
|
|
152
145
|
}): ATELMessage<HandshakeAckPayload>;
|
|
@@ -154,7 +147,6 @@ export declare class HandshakeManager {
|
|
|
154
147
|
* Process a handshake_confirm message (Step 3, responder side).
|
|
155
148
|
*/
|
|
156
149
|
processConfirm(confirmMessage: ATELMessage<HandshakeConfirmPayload>, initiatorPublicKey: Uint8Array, initiatorCapabilities?: string[], initiatorWallets?: {
|
|
157
|
-
solana?: string;
|
|
158
150
|
base?: string;
|
|
159
151
|
bsc?: string;
|
|
160
152
|
}, initiatorWalletBundle?: WalletBundle): Session;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lawrenceliang-btc/atel-sdk",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.15",
|
|
4
4
|
"description": "ATEL Protocol SDK - Agent Trust & Exchange Layer",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -49,7 +49,6 @@
|
|
|
49
49
|
"prepublishOnly": "npm run clean && npm run build && npm test"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@solana/web3.js": "^1.98.4",
|
|
53
52
|
"ajv": "^8.17.1",
|
|
54
53
|
"ajv-formats": "^3.0.1",
|
|
55
54
|
"bs58": "^6.0.0",
|
|
@@ -523,7 +523,7 @@ atel withdraw 5 crypto_bsc <外部钱包地址> # 提现到用户的外部钱
|
|
|
523
523
|
atel transactions # 交易记录
|
|
524
524
|
```
|
|
525
525
|
|
|
526
|
-
支持的充值渠道:`
|
|
526
|
+
支持的充值渠道:`crypto_base`、`crypto_bsc`、`stripe`、`alipay`、`manual`
|
|
527
527
|
|
|
528
528
|
### 提现流程
|
|
529
529
|
|
|
@@ -33,27 +33,16 @@ Minimum commission: 0.5%. Free orders (price=0) have no commission.
|
|
|
33
33
|
## Payment Channels
|
|
34
34
|
|
|
35
35
|
- `manual` — Transfer and contact admin for confirmation
|
|
36
|
-
- `crypto_solana` — Solana on-chain deposit (auto-verified)
|
|
37
36
|
- `crypto_base` — Base chain deposit (auto-verified)
|
|
38
37
|
- `crypto_bsc` — BSC deposit (auto-verified)
|
|
39
38
|
|
|
40
|
-
### Solana deposit warning
|
|
41
|
-
|
|
42
|
-
If guiding the owner to deposit USDC on Solana, explicitly tell them the transfer must use **`transferChecked`**.
|
|
43
|
-
Do not describe plain `transfer` as acceptable for Solana USDC deposits.
|
|
44
|
-
|
|
45
|
-
Operational rule:
|
|
46
|
-
- when suggesting or explaining Solana USDC deposit steps, explicitly state that `transferChecked` is required
|
|
47
|
-
- if a Solana deposit is pending but not recognized, check whether the user sent USDC with `transfer` instead of `transferChecked`
|
|
48
|
-
- if the owner cannot ensure `transferChecked`, prefer recommending `crypto_base` or `crypto_bsc` instead of giving risky Solana instructions
|
|
49
|
-
|
|
50
39
|
### Deposit Info API
|
|
51
40
|
|
|
52
41
|
Get platform deposit addresses (no auth required):
|
|
53
42
|
|
|
54
43
|
```bash
|
|
55
44
|
curl https://api.atelai.org/account/v1/deposit-info
|
|
56
|
-
# Returns: { "chains": [{ "chain": "
|
|
45
|
+
# Returns: { "chains": [{ "chain": "base", "address": "...", "minAmount": 5 }, ...] }
|
|
57
46
|
```
|
|
58
47
|
|
|
59
48
|
## Marketplace
|
|
@@ -139,8 +128,8 @@ Agents can withdraw funds from their platform balance:
|
|
|
139
128
|
# Withdraw to Base wallet (instant on-chain transfer)
|
|
140
129
|
atel withdraw 50 crypto_base 0xYOUR_WALLET_ADDRESS
|
|
141
130
|
|
|
142
|
-
# Withdraw to
|
|
143
|
-
atel withdraw 50
|
|
131
|
+
# Withdraw to Base wallet (instant on-chain transfer)
|
|
132
|
+
atel withdraw 50 crypto_base YOUR_BASE_ADDRESS
|
|
144
133
|
|
|
145
134
|
# Withdraw to BSC wallet (instant on-chain transfer)
|
|
146
135
|
atel withdraw 50 crypto_bsc YOUR_BSC_ADDRESS
|
|
@@ -355,7 +355,7 @@ and pay gas). You do not need to configure any chain private key to receive
|
|
|
355
355
|
paid orders.
|
|
356
356
|
|
|
357
357
|
Legacy V1 behaviour (opt-in only, not recommended): if `atel anchor config` was
|
|
358
|
-
run, the SDK will also detect `
|
|
358
|
+
run, the SDK will also detect `ATEL_BASE_PRIVATE_KEY`,
|
|
359
359
|
`ATEL_BSC_PRIVATE_KEY` environment variables and set `preferredChain` in the
|
|
360
360
|
registry metadata as a marketplace hint. This is purely cosmetic — it does not
|
|
361
361
|
affect whether you can execute a paid order.
|
|
@@ -5,9 +5,13 @@ Every completed task generates a cryptographic proof (ExecutionTrace → Merkle
|
|
|
5
5
|
## Setup
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
#
|
|
9
|
-
export
|
|
10
|
-
export
|
|
8
|
+
# Base (primary)
|
|
9
|
+
export ATEL_BASE_PRIVATE_KEY=<hex-private-key>
|
|
10
|
+
export ATEL_BASE_RPC_URL=https://mainnet.base.org
|
|
11
|
+
|
|
12
|
+
# BSC (secondary)
|
|
13
|
+
export ATEL_BSC_PRIVATE_KEY=<hex-private-key>
|
|
14
|
+
export ATEL_BSC_RPC_URL=https://bsc-dataseed.binance.org
|
|
11
15
|
|
|
12
16
|
# Base (optional)
|
|
13
17
|
export ATEL_BASE_PRIVATE_KEY=<hex-key-with-0x>
|
|
@@ -61,7 +61,7 @@ Always ask before deciding any of the following:
|
|
|
61
61
|
anchors on behalf of agents using its own registered executor wallets and pays
|
|
62
62
|
gas. The user's smart wallet (AA) is automatically derived from the ATEL
|
|
63
63
|
identity key — no separate chain key is ever required to send or receive paid
|
|
64
|
-
orders. Any prompt asking for a "Base / BSC
|
|
64
|
+
orders. Any prompt asking for a "Base / BSC private key" is a legacy
|
|
65
65
|
V1 flow and should be declined.
|
|
66
66
|
|
|
67
67
|
Rules:
|
|
@@ -92,10 +92,6 @@ If the owner's preferred language is known, use the owner's language instead.
|
|
|
92
92
|
|
|
93
93
|
Do not spam the owner with every retry or low-level infrastructure event.
|
|
94
94
|
|
|
95
|
-
## Solana deposit caution
|
|
96
|
-
|
|
97
|
-
If guiding the owner to deposit USDC on Solana, tell them the transfer must use `transferChecked`.
|
|
98
|
-
Do not describe plain `transfer` as acceptable for Solana USDC deposits.
|
|
99
95
|
|
|
100
96
|
## Verify after upgrade
|
|
101
97
|
|
|
@@ -80,7 +80,7 @@ Agents below the threshold for a given risk level are rejected. Chain-verified p
|
|
|
80
80
|
# Local-only (default, uses .atel/trust-history.json)
|
|
81
81
|
atel check <did> medium
|
|
82
82
|
|
|
83
|
-
# Chain-verified (queries
|
|
83
|
+
# Chain-verified (queries Base/BSC RPC)
|
|
84
84
|
atel check <did> medium --chain
|
|
85
85
|
```
|
|
86
86
|
|
|
@@ -13,7 +13,7 @@ This includes:
|
|
|
13
13
|
**Do NOT ask the owner for any on-chain private key.** In V2 the Platform
|
|
14
14
|
anchors on behalf of agents using its own registered executor wallets and
|
|
15
15
|
pays gas. The user's smart wallet (AA) is automatically derived from the
|
|
16
|
-
ATEL identity key — no separate Base/BSC
|
|
16
|
+
ATEL identity key — no separate Base/BSC key is ever required to
|
|
17
17
|
send or receive paid orders. Any prompt asking for a raw chain private key
|
|
18
18
|
is a legacy V1 flow and should be declined.
|
|
19
19
|
|
package/dist/anchor/solana.d.ts
DELETED
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Solana Anchor Provider.
|
|
3
|
-
*
|
|
4
|
-
* Anchors hashes on Solana using the official Memo Program
|
|
5
|
-
* (`MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr`).
|
|
6
|
-
*
|
|
7
|
-
* The memo content is formatted as `ATEL_ANCHOR:<hash>` so anchored
|
|
8
|
-
* transactions are easily identifiable.
|
|
9
|
-
*
|
|
10
|
-
* @remarks
|
|
11
|
-
* ⚠️ SECURITY: The `privateKey` (Base58-encoded) is used to sign
|
|
12
|
-
* transactions. Never hard-code it — use environment variables or a vault.
|
|
13
|
-
*/
|
|
14
|
-
import type { AnchorProvider, AnchorRecord, AnchorVerification } from './index.js';
|
|
15
|
-
/** Configuration for the Solana anchor provider */
|
|
16
|
-
export interface SolanaAnchorConfig {
|
|
17
|
-
/** Solana JSON-RPC endpoint URL */
|
|
18
|
-
rpcUrl: string;
|
|
19
|
-
/**
|
|
20
|
-
* Base58-encoded private key for signing transactions.
|
|
21
|
-
* Optional — if omitted the provider can only verify / lookup.
|
|
22
|
-
*
|
|
23
|
-
* ⚠️ SECURITY: Keep this value secret.
|
|
24
|
-
*/
|
|
25
|
-
privateKey?: string;
|
|
26
|
-
}
|
|
27
|
-
/** Structured anchor metadata for v2 memo */
|
|
28
|
-
export interface AnchorMemoV2 {
|
|
29
|
-
version: 1;
|
|
30
|
-
executorDid: string;
|
|
31
|
-
requesterDid: string;
|
|
32
|
-
taskId: string;
|
|
33
|
-
traceRoot: string;
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* Anchor provider for the Solana blockchain.
|
|
37
|
-
*/
|
|
38
|
-
export declare class SolanaAnchorProvider implements AnchorProvider {
|
|
39
|
-
readonly name = "Solana";
|
|
40
|
-
readonly chain = "solana";
|
|
41
|
-
/** Solana RPC connection */
|
|
42
|
-
private readonly connection;
|
|
43
|
-
/** Keypair for signing (undefined when no private key is supplied) */
|
|
44
|
-
private readonly keypair?;
|
|
45
|
-
/** Default Solana mainnet-beta RPC URL */
|
|
46
|
-
static readonly DEFAULT_RPC_URL = "https://api.mainnet-beta.solana.com";
|
|
47
|
-
/**
|
|
48
|
-
* @param config - RPC URL and optional private key.
|
|
49
|
-
* If `rpcUrl` is omitted, the Solana mainnet-beta default is used.
|
|
50
|
-
*/
|
|
51
|
-
constructor(config?: Partial<SolanaAnchorConfig>);
|
|
52
|
-
/**
|
|
53
|
-
* Encode a hash into the memo data buffer (v2 structured format).
|
|
54
|
-
* Falls back to legacy format if no metadata provided.
|
|
55
|
-
*/
|
|
56
|
-
static encodeMemo(hash: string, meta?: {
|
|
57
|
-
executorDid?: string;
|
|
58
|
-
requesterDid?: string;
|
|
59
|
-
taskId?: string;
|
|
60
|
-
}): Buffer;
|
|
61
|
-
/**
|
|
62
|
-
* Decode a hash from memo data. Supports both v2 structured and legacy format.
|
|
63
|
-
*
|
|
64
|
-
* @returns The decoded hash, or `null` if the data doesn't match.
|
|
65
|
-
*/
|
|
66
|
-
static decodeMemo(data: Buffer | Uint8Array | string): string | null;
|
|
67
|
-
/**
|
|
68
|
-
* Decode full structured memo (v2 only).
|
|
69
|
-
* Returns null for legacy format memos.
|
|
70
|
-
*/
|
|
71
|
-
static decodeMemoV2(data: Buffer | Uint8Array | string): AnchorMemoV2 | null;
|
|
72
|
-
/** @inheritdoc */
|
|
73
|
-
anchor(hash: string, metadata?: Record<string, unknown>): Promise<AnchorRecord>;
|
|
74
|
-
/** @inheritdoc */
|
|
75
|
-
verify(hash: string, txHash: string): Promise<AnchorVerification>;
|
|
76
|
-
/** @inheritdoc */
|
|
77
|
-
lookup(hash: string): Promise<AnchorRecord[]>;
|
|
78
|
-
/** @inheritdoc */
|
|
79
|
-
isAvailable(): Promise<boolean>;
|
|
80
|
-
/**
|
|
81
|
-
* Query all ATEL anchor transactions for a given wallet address.
|
|
82
|
-
* Parses v2 structured memos to extract DID and task info.
|
|
83
|
-
*
|
|
84
|
-
* @param walletAddress - Solana wallet public key (base58)
|
|
85
|
-
* @param options - limit (default 100), filterDid (only return records involving this DID)
|
|
86
|
-
* @returns Array of parsed anchor memos with tx info
|
|
87
|
-
*/
|
|
88
|
-
queryByWallet(walletAddress: string, options?: {
|
|
89
|
-
limit?: number;
|
|
90
|
-
filterDid?: string;
|
|
91
|
-
}): Promise<Array<AnchorMemoV2 & {
|
|
92
|
-
txHash: string;
|
|
93
|
-
blockTime?: number;
|
|
94
|
-
}>>;
|
|
95
|
-
}
|
package/dist/anchor/solana.js
DELETED
|
@@ -1,298 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Solana Anchor Provider.
|
|
3
|
-
*
|
|
4
|
-
* Anchors hashes on Solana using the official Memo Program
|
|
5
|
-
* (`MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr`).
|
|
6
|
-
*
|
|
7
|
-
* The memo content is formatted as `ATEL_ANCHOR:<hash>` so anchored
|
|
8
|
-
* transactions are easily identifiable.
|
|
9
|
-
*
|
|
10
|
-
* @remarks
|
|
11
|
-
* ⚠️ SECURITY: The `privateKey` (Base58-encoded) is used to sign
|
|
12
|
-
* transactions. Never hard-code it — use environment variables or a vault.
|
|
13
|
-
*/
|
|
14
|
-
import { Connection, Keypair, PublicKey, Transaction, TransactionInstruction, sendAndConfirmTransaction, } from '@solana/web3.js';
|
|
15
|
-
import bs58 from 'bs58';
|
|
16
|
-
/** Solana Memo Program address */
|
|
17
|
-
const MEMO_PROGRAM_ID = new PublicKey('MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr');
|
|
18
|
-
/** Prefix prepended to the hash in the memo (legacy format) */
|
|
19
|
-
const ANCHOR_PREFIX = 'ATEL_ANCHOR:';
|
|
20
|
-
/** V2 structured memo format: ATEL:1:<executorDID>:<requesterDID>:<taskId>:<trace_root> */
|
|
21
|
-
const ANCHOR_V2_PREFIX = 'ATEL:1:';
|
|
22
|
-
/**
|
|
23
|
-
* Anchor provider for the Solana blockchain.
|
|
24
|
-
*/
|
|
25
|
-
export class SolanaAnchorProvider {
|
|
26
|
-
name = 'Solana';
|
|
27
|
-
chain = 'solana';
|
|
28
|
-
/** Solana RPC connection */
|
|
29
|
-
connection;
|
|
30
|
-
/** Keypair for signing (undefined when no private key is supplied) */
|
|
31
|
-
keypair;
|
|
32
|
-
/** Default Solana mainnet-beta RPC URL */
|
|
33
|
-
static DEFAULT_RPC_URL = 'https://api.mainnet-beta.solana.com';
|
|
34
|
-
/**
|
|
35
|
-
* @param config - RPC URL and optional private key.
|
|
36
|
-
* If `rpcUrl` is omitted, the Solana mainnet-beta default is used.
|
|
37
|
-
*/
|
|
38
|
-
constructor(config) {
|
|
39
|
-
this.connection = new Connection(config?.rpcUrl ?? SolanaAnchorProvider.DEFAULT_RPC_URL, 'confirmed');
|
|
40
|
-
if (config?.privateKey) {
|
|
41
|
-
// ⚠️ SECURITY: The keypair holds the private key in memory.
|
|
42
|
-
const secretKey = bs58.decode(config.privateKey);
|
|
43
|
-
this.keypair = Keypair.fromSecretKey(secretKey);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Encode a hash into the memo data buffer (v2 structured format).
|
|
48
|
-
* Falls back to legacy format if no metadata provided.
|
|
49
|
-
*/
|
|
50
|
-
static encodeMemo(hash, meta) {
|
|
51
|
-
if (meta?.executorDid && meta?.requesterDid && meta?.taskId) {
|
|
52
|
-
return Buffer.from(`${ANCHOR_V2_PREFIX}${meta.executorDid}:${meta.requesterDid}:${meta.taskId}:${hash}`, 'utf-8');
|
|
53
|
-
}
|
|
54
|
-
return Buffer.from(`${ANCHOR_PREFIX}${hash}`, 'utf-8');
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Decode a hash from memo data. Supports both v2 structured and legacy format.
|
|
58
|
-
*
|
|
59
|
-
* @returns The decoded hash, or `null` if the data doesn't match.
|
|
60
|
-
*/
|
|
61
|
-
static decodeMemo(data) {
|
|
62
|
-
const text = typeof data === 'string' ? data : Buffer.from(data).toString('utf-8');
|
|
63
|
-
// V2 structured format
|
|
64
|
-
if (text.startsWith(ANCHOR_V2_PREFIX)) {
|
|
65
|
-
const parts = text.slice(ANCHOR_V2_PREFIX.length).split(':');
|
|
66
|
-
if (parts.length >= 4)
|
|
67
|
-
return parts[parts.length - 1]; // last part is trace_root
|
|
68
|
-
}
|
|
69
|
-
// Legacy format
|
|
70
|
-
if (text.startsWith(ANCHOR_PREFIX)) {
|
|
71
|
-
return text.slice(ANCHOR_PREFIX.length);
|
|
72
|
-
}
|
|
73
|
-
return null;
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* Decode full structured memo (v2 only).
|
|
77
|
-
* Returns null for legacy format memos.
|
|
78
|
-
*/
|
|
79
|
-
static decodeMemoV2(data) {
|
|
80
|
-
const text = typeof data === 'string' ? data : Buffer.from(data).toString('utf-8');
|
|
81
|
-
if (!text.startsWith(ANCHOR_V2_PREFIX))
|
|
82
|
-
return null;
|
|
83
|
-
const rest = text.slice(ANCHOR_V2_PREFIX.length);
|
|
84
|
-
// Format: executorDID:requesterDID:taskId:traceRoot
|
|
85
|
-
// DIDs contain colons (did:atel:ed25519:xxx), so we need smart parsing
|
|
86
|
-
// Split by ':' and reconstruct DIDs
|
|
87
|
-
const parts = rest.split(':');
|
|
88
|
-
// Minimum: did:atel:ed25519:key : did:atel:ed25519:key : taskId : traceRoot = 4+4+1+1 = 10 parts
|
|
89
|
-
if (parts.length < 10)
|
|
90
|
-
return null;
|
|
91
|
-
// Each DID is 4 parts: did:atel:ed25519:base58
|
|
92
|
-
const executorDid = parts.slice(0, 4).join(':');
|
|
93
|
-
const requesterDid = parts.slice(4, 8).join(':');
|
|
94
|
-
const taskId = parts[8];
|
|
95
|
-
const traceRoot = parts.slice(9).join(':');
|
|
96
|
-
if (!executorDid.startsWith('did:atel:') || !requesterDid.startsWith('did:atel:'))
|
|
97
|
-
return null;
|
|
98
|
-
return { version: 1, executorDid, requesterDid, taskId, traceRoot };
|
|
99
|
-
}
|
|
100
|
-
/** @inheritdoc */
|
|
101
|
-
async anchor(hash, metadata) {
|
|
102
|
-
if (!this.keypair) {
|
|
103
|
-
throw new Error('Solana: Cannot anchor without a private key');
|
|
104
|
-
}
|
|
105
|
-
const memoData = SolanaAnchorProvider.encodeMemo(hash, metadata);
|
|
106
|
-
const instruction = new TransactionInstruction({
|
|
107
|
-
keys: [{ pubkey: this.keypair.publicKey, isSigner: true, isWritable: true }],
|
|
108
|
-
programId: MEMO_PROGRAM_ID,
|
|
109
|
-
data: memoData,
|
|
110
|
-
});
|
|
111
|
-
const transaction = new Transaction().add(instruction);
|
|
112
|
-
try {
|
|
113
|
-
const signature = await sendAndConfirmTransaction(this.connection, transaction, [this.keypair], { commitment: 'confirmed' });
|
|
114
|
-
// Fetch the confirmed transaction to get slot/block info
|
|
115
|
-
let blockNumber;
|
|
116
|
-
try {
|
|
117
|
-
const txInfo = await this.connection.getTransaction(signature, {
|
|
118
|
-
commitment: 'confirmed',
|
|
119
|
-
maxSupportedTransactionVersion: 0,
|
|
120
|
-
});
|
|
121
|
-
blockNumber = txInfo?.slot;
|
|
122
|
-
}
|
|
123
|
-
catch {
|
|
124
|
-
// Non-critical
|
|
125
|
-
}
|
|
126
|
-
return {
|
|
127
|
-
hash,
|
|
128
|
-
txHash: signature,
|
|
129
|
-
chain: 'solana',
|
|
130
|
-
timestamp: Date.now(),
|
|
131
|
-
blockNumber,
|
|
132
|
-
metadata,
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
catch (err) {
|
|
136
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
137
|
-
throw new Error(`Solana anchor failed: ${message}`);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
/** @inheritdoc */
|
|
141
|
-
async verify(hash, txHash) {
|
|
142
|
-
try {
|
|
143
|
-
const txInfo = await this.connection.getTransaction(txHash, {
|
|
144
|
-
commitment: 'confirmed',
|
|
145
|
-
maxSupportedTransactionVersion: 0,
|
|
146
|
-
});
|
|
147
|
-
if (!txInfo) {
|
|
148
|
-
return {
|
|
149
|
-
valid: false,
|
|
150
|
-
hash,
|
|
151
|
-
txHash,
|
|
152
|
-
chain: this.chain,
|
|
153
|
-
detail: 'Transaction not found',
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
// Search through instructions for a memo matching our hash
|
|
157
|
-
const message = txInfo.transaction.message;
|
|
158
|
-
let foundHash = null;
|
|
159
|
-
for (let i = 0; i < message.compiledInstructions.length; i++) {
|
|
160
|
-
const ix = message.compiledInstructions[i];
|
|
161
|
-
const keys = message.getAccountKeys();
|
|
162
|
-
const programId = keys.get(ix.programIdIndex);
|
|
163
|
-
if (programId?.equals(MEMO_PROGRAM_ID)) {
|
|
164
|
-
foundHash = SolanaAnchorProvider.decodeMemo(Buffer.from(ix.data));
|
|
165
|
-
if (foundHash)
|
|
166
|
-
break;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
// Fallback: check log messages for memo content
|
|
170
|
-
if (!foundHash && txInfo.meta?.logMessages) {
|
|
171
|
-
for (const log of txInfo.meta.logMessages) {
|
|
172
|
-
if (log.includes(ANCHOR_PREFIX)) {
|
|
173
|
-
const idx = log.indexOf(ANCHOR_PREFIX);
|
|
174
|
-
foundHash = log.slice(idx + ANCHOR_PREFIX.length);
|
|
175
|
-
break;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
if (foundHash === null) {
|
|
180
|
-
return {
|
|
181
|
-
valid: false,
|
|
182
|
-
hash,
|
|
183
|
-
txHash,
|
|
184
|
-
chain: this.chain,
|
|
185
|
-
detail: 'No anchor memo found in transaction',
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
const valid = foundHash === hash;
|
|
189
|
-
const blockTimestamp = txInfo.blockTime ? txInfo.blockTime * 1000 : undefined;
|
|
190
|
-
return {
|
|
191
|
-
valid,
|
|
192
|
-
hash,
|
|
193
|
-
txHash,
|
|
194
|
-
chain: this.chain,
|
|
195
|
-
blockTimestamp,
|
|
196
|
-
detail: valid
|
|
197
|
-
? 'Hash matches on-chain memo'
|
|
198
|
-
: `Hash mismatch: expected "${hash}", found "${foundHash}"`,
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
catch (err) {
|
|
202
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
203
|
-
return {
|
|
204
|
-
valid: false,
|
|
205
|
-
hash,
|
|
206
|
-
txHash,
|
|
207
|
-
chain: this.chain,
|
|
208
|
-
detail: `Verification error: ${message}`,
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
/** @inheritdoc */
|
|
213
|
-
async lookup(hash) {
|
|
214
|
-
// On-chain lookup without an indexer is not feasible for Solana.
|
|
215
|
-
// In production, integrate with a Solana indexer or Helius API.
|
|
216
|
-
return [];
|
|
217
|
-
}
|
|
218
|
-
/** @inheritdoc */
|
|
219
|
-
async isAvailable() {
|
|
220
|
-
try {
|
|
221
|
-
await this.connection.getSlot();
|
|
222
|
-
return true;
|
|
223
|
-
}
|
|
224
|
-
catch {
|
|
225
|
-
return false;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
/**
|
|
229
|
-
* Query all ATEL anchor transactions for a given wallet address.
|
|
230
|
-
* Parses v2 structured memos to extract DID and task info.
|
|
231
|
-
*
|
|
232
|
-
* @param walletAddress - Solana wallet public key (base58)
|
|
233
|
-
* @param options - limit (default 100), filterDid (only return records involving this DID)
|
|
234
|
-
* @returns Array of parsed anchor memos with tx info
|
|
235
|
-
*/
|
|
236
|
-
async queryByWallet(walletAddress, options) {
|
|
237
|
-
const limit = options?.limit ?? 100;
|
|
238
|
-
const pubkey = new PublicKey(walletAddress);
|
|
239
|
-
const results = [];
|
|
240
|
-
try {
|
|
241
|
-
const signatures = await this.connection.getSignaturesForAddress(pubkey, { limit });
|
|
242
|
-
for (const sig of signatures) {
|
|
243
|
-
try {
|
|
244
|
-
const txInfo = await this.connection.getTransaction(sig.signature, {
|
|
245
|
-
commitment: 'confirmed',
|
|
246
|
-
maxSupportedTransactionVersion: 0,
|
|
247
|
-
});
|
|
248
|
-
if (!txInfo)
|
|
249
|
-
continue;
|
|
250
|
-
// Search for ATEL memo in instructions
|
|
251
|
-
const message = txInfo.transaction.message;
|
|
252
|
-
for (let i = 0; i < message.compiledInstructions.length; i++) {
|
|
253
|
-
const ix = message.compiledInstructions[i];
|
|
254
|
-
const keys = message.getAccountKeys();
|
|
255
|
-
const programId = keys.get(ix.programIdIndex);
|
|
256
|
-
if (!programId?.equals(MEMO_PROGRAM_ID))
|
|
257
|
-
continue;
|
|
258
|
-
const memoText = Buffer.from(ix.data).toString('utf-8');
|
|
259
|
-
const parsed = SolanaAnchorProvider.decodeMemoV2(memoText);
|
|
260
|
-
if (!parsed)
|
|
261
|
-
continue;
|
|
262
|
-
// Filter by DID if requested
|
|
263
|
-
if (options?.filterDid && parsed.executorDid !== options.filterDid && parsed.requesterDid !== options.filterDid)
|
|
264
|
-
continue;
|
|
265
|
-
results.push({
|
|
266
|
-
...parsed,
|
|
267
|
-
txHash: sig.signature,
|
|
268
|
-
blockTime: txInfo.blockTime ? txInfo.blockTime * 1000 : undefined,
|
|
269
|
-
});
|
|
270
|
-
}
|
|
271
|
-
// Fallback: check log messages
|
|
272
|
-
if (txInfo.meta?.logMessages) {
|
|
273
|
-
for (const log of txInfo.meta.logMessages) {
|
|
274
|
-
if (!log.includes(ANCHOR_V2_PREFIX))
|
|
275
|
-
continue;
|
|
276
|
-
const idx = log.indexOf(ANCHOR_V2_PREFIX);
|
|
277
|
-
const parsed = SolanaAnchorProvider.decodeMemoV2(log.slice(idx));
|
|
278
|
-
if (!parsed)
|
|
279
|
-
continue;
|
|
280
|
-
if (options?.filterDid && parsed.executorDid !== options.filterDid && parsed.requesterDid !== options.filterDid)
|
|
281
|
-
continue;
|
|
282
|
-
if (!results.some(r => r.txHash === sig.signature)) {
|
|
283
|
-
results.push({ ...parsed, txHash: sig.signature, blockTime: txInfo.blockTime ? txInfo.blockTime * 1000 : undefined });
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
catch {
|
|
289
|
-
// Skip individual tx failures
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
catch {
|
|
294
|
-
// Query failed — return empty
|
|
295
|
-
}
|
|
296
|
-
return results;
|
|
297
|
-
}
|
|
298
|
-
}
|