@oracle-agent/oracle 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/CONTRIBUTING.md +98 -0
- package/LICENSE +202 -0
- package/README.md +384 -0
- package/SECURITY.md +89 -0
- package/SETUP.md +235 -0
- package/artifacts/inscription/oracle-was-here-preview.png +0 -0
- package/artifacts/inscription/oracle-was-here.svg +1 -0
- package/artifacts/specialist-packs/oracle-full-crypto.json +273 -0
- package/bin/desk-server.mjs +438 -0
- package/bin/oracle-data-mcp.mjs +603 -0
- package/bin/oracle-init.mjs +423 -0
- package/bin/oracle-public-server.mjs +36 -0
- package/bin/oracle-route.mjs +254 -0
- package/bin/oracle-scan.mjs +192 -0
- package/docs/adding-a-chain.md +229 -0
- package/docs/architecture.md +135 -0
- package/docs/profiles.md +132 -0
- package/examples/add-a-chain.mjs +65 -0
- package/examples/research-a-token.mjs +70 -0
- package/package.json +90 -0
- package/profiles/_template/SOUL.md +47 -0
- package/profiles/_template/profile.json +22 -0
- package/profiles/bitcoin-agent/SOUL.md +31 -0
- package/profiles/bitcoin-agent/profile.json +32 -0
- package/profiles/hyperliquid-agent/SOUL.md +34 -0
- package/profiles/hyperliquid-agent/profile.json +37 -0
- package/profiles/oracle/SOUL.md +65 -0
- package/profiles/oracle/profile.json +35 -0
- package/profiles/polymarket-agent/SOUL.md +35 -0
- package/profiles/polymarket-agent/profile.json +34 -0
- package/profiles/profile.schema.json +90 -0
- package/profiles/protocol-builder/SOUL.md +50 -0
- package/profiles/protocol-builder/profile.json +37 -0
- package/profiles/robinhood-agent/SOUL.md +45 -0
- package/profiles/robinhood-agent/profile.json +39 -0
- package/profiles/solana-agent/SOUL.md +37 -0
- package/profiles/solana-agent/profile.json +37 -0
- package/profiles/stable-agent/SOUL.md +43 -0
- package/profiles/stable-agent/profile.json +37 -0
- package/public/oracle-console/app.js +272 -0
- package/public/oracle-console/bitcoin-wallets.js +206 -0
- package/public/oracle-console/index.html +91 -0
- package/public/oracle-console/styles.css +239 -0
- package/public/oracle-splash/index.html +931 -0
- package/scripts/build-inscription.py +230 -0
- package/scripts/check-test-count.mjs +105 -0
- package/scripts/e2e-hl-markets.mjs +21 -0
- package/scripts/e2e-hl-perps.mjs +48 -0
- package/scripts/e2e-hypercore-staking.mjs +128 -0
- package/scripts/e2e-solana-bitcoin.mjs +183 -0
- package/scripts/public-api-scan.mjs +23 -0
- package/scripts/secret-scan.mjs +181 -0
- package/scripts/verify-v3-venues.mjs +192 -0
- package/skills/oracle-best-execution/SKILL.md +127 -0
- package/skills/oracle-bitcoin/SKILL.md +53 -0
- package/skills/oracle-chain-graphs-telegram-cards/SKILL.md +59 -0
- package/skills/oracle-circuit-breaker/SKILL.md +51 -0
- package/skills/oracle-contract-research/SKILL.md +55 -0
- package/skills/oracle-desk/SKILL.md +58 -0
- package/skills/oracle-dex-launch/SKILL.md +38 -0
- package/skills/oracle-grants/SKILL.md +69 -0
- package/skills/oracle-hypercore-staking/SKILL.md +57 -0
- package/skills/oracle-hyperliquid/SKILL.md +56 -0
- package/skills/oracle-meme-token-sniper/SKILL.md +73 -0
- package/skills/oracle-nft-gacha-launch/SKILL.md +48 -0
- package/skills/oracle-nft-mint-gas-war/SKILL.md +63 -0
- package/skills/oracle-polymarket/SKILL.md +60 -0
- package/skills/oracle-protocol-builder/SKILL.md +38 -0
- package/skills/oracle-protocol-security/SKILL.md +60 -0
- package/skills/oracle-public-product/SKILL.md +44 -0
- package/skills/oracle-receipts/SKILL.md +52 -0
- package/skills/oracle-rfq-tokenized-assets/SKILL.md +69 -0
- package/skills/oracle-smart-wallet-scanner/SKILL.md +49 -0
- package/skills/oracle-solana/SKILL.md +65 -0
- package/skills/oracle-solana-nft/SKILL.md +54 -0
- package/skills/oracle-token-research/SKILL.md +67 -0
- package/src/agent-auth.mjs +191 -0
- package/src/approval-guard.mjs +282 -0
- package/src/attestation-secret.mjs +88 -0
- package/src/audit-log.mjs +196 -0
- package/src/auto-slippage.mjs +378 -0
- package/src/capability-posture.mjs +125 -0
- package/src/chains.mjs +62 -0
- package/src/data/catalog.mjs +495 -0
- package/src/data/desk-data.mjs +623 -0
- package/src/data/http.mjs +200 -0
- package/src/data/provider-endpoint.mjs +94 -0
- package/src/data/providers/aerodrome.mjs +244 -0
- package/src/data/providers/balancer.mjs +208 -0
- package/src/data/providers/bitcoin-esplora.mjs +230 -0
- package/src/data/providers/bitcoin-meta.mjs +378 -0
- package/src/data/providers/blockscout.mjs +14 -0
- package/src/data/providers/bridges.mjs +241 -0
- package/src/data/providers/cowswap.mjs +501 -0
- package/src/data/providers/curve.mjs +200 -0
- package/src/data/providers/defillama.mjs +88 -0
- package/src/data/providers/dexscreener.mjs +43 -0
- package/src/data/providers/evm-rpc.mjs +203 -0
- package/src/data/providers/geckoterminal.mjs +34 -0
- package/src/data/providers/gmx.mjs +495 -0
- package/src/data/providers/hl-info.mjs +102 -0
- package/src/data/providers/hl-markets.mjs +210 -0
- package/src/data/providers/hl-perps.mjs +382 -0
- package/src/data/providers/hl-staking.mjs +352 -0
- package/src/data/providers/hl-ws.mjs +119 -0
- package/src/data/providers/hyperevm-dex.mjs +49 -0
- package/src/data/providers/jupiter.mjs +182 -0
- package/src/data/providers/lifi.mjs +150 -0
- package/src/data/providers/magiceden-sol.mjs +355 -0
- package/src/data/providers/morpho.mjs +173 -0
- package/src/data/providers/odos.mjs +155 -0
- package/src/data/providers/oneinch.mjs +173 -0
- package/src/data/providers/opensea-multichain.mjs +136 -0
- package/src/data/providers/opensea-nft.mjs +99 -0
- package/src/data/providers/paraswap.mjs +117 -0
- package/src/data/providers/pendle.mjs +187 -0
- package/src/data/providers/poly-public.mjs +96 -0
- package/src/data/providers/poly-ws.mjs +103 -0
- package/src/data/providers/rh-agent.mjs +59 -0
- package/src/data/providers/satflow.mjs +336 -0
- package/src/data/providers/solana-rpc.mjs +186 -0
- package/src/data/providers/uniswap-v3.mjs +303 -0
- package/src/data/providers/zerox.mjs +166 -0
- package/src/data/public-api-scan.mjs +61 -0
- package/src/data/quote-placeholder.mjs +31 -0
- package/src/exact-integer.mjs +72 -0
- package/src/exec-policy.mjs +444 -0
- package/src/flags.mjs +15 -0
- package/src/fresh-window.mjs +76 -0
- package/src/gmx-attestation.mjs +175 -0
- package/src/index.mjs +50 -0
- package/src/nft-gas-war-guard.mjs +139 -0
- package/src/onboarding/agent-keys.mjs +157 -0
- package/src/onboarding/index.mjs +18 -0
- package/src/onboarding/tiers.mjs +139 -0
- package/src/oracle-env.mjs +38 -0
- package/src/protocol-execution.mjs +84 -0
- package/src/public-api/buzz-integration.mjs +256 -0
- package/src/public-api/connect-agent.mjs +397 -0
- package/src/public-api/grants.mjs +142 -0
- package/src/public-api/http.mjs +374 -0
- package/src/public-control/aa-adapter.mjs +402 -0
- package/src/public-control/build-registry.mjs +227 -0
- package/src/public-control/bundler-client.mjs +372 -0
- package/src/public-control/grant-indexer.mjs +296 -0
- package/src/public-control/policy-render.mjs +69 -0
- package/src/public-control/policy-schema.mjs +318 -0
- package/src/public-control/runtime-config.mjs +265 -0
- package/src/public-control/session-key-model.mjs +374 -0
- package/src/public-control/session-orchestrator.mjs +412 -0
- package/src/route-attestation.mjs +132 -0
- package/src/router/best-execution.mjs +221 -0
- package/src/router/index.mjs +185 -0
- package/src/router/prepare-bridge.mjs +288 -0
- package/src/router/prepare-route.mjs +341 -0
- package/src/router/proposal.mjs +311 -0
- package/src/router/risk-classifier.mjs +119 -0
- package/src/router/route-sources.mjs +292 -0
- package/src/scanner/chains.config.mjs +381 -0
- package/src/scanner/contract.mjs +270 -0
- package/src/scanner/evm-scanner.mjs +394 -0
- package/src/scanner/index.mjs +9 -0
- package/src/scanner/v2-venue.mjs +335 -0
- package/src/scanner/v3-venue.mjs +290 -0
- package/src/scopes.mjs +44 -0
- package/src/sell-simulation.mjs +167 -0
- package/src/token-transfer-guard.mjs +188 -0
- package/src/vault-attestation.mjs +145 -0
- package/src/venues.mjs +206 -0
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
// Oracle Console. Public frontend, Slice I.
|
|
2
|
+
//
|
|
3
|
+
// Single action UI: connect a wallet, then preview a proposed permission set.
|
|
4
|
+
// This file never installs a grant or session key and never claims on-chain
|
|
5
|
+
// enforcement. It never stores, logs, or transmits a private key, session
|
|
6
|
+
// secret, bearer token, or signer URL.
|
|
7
|
+
//
|
|
8
|
+
// Talks to two BFF routes only:
|
|
9
|
+
// POST /public/connect/assemble -> assembleUnsignedGrant(body)
|
|
10
|
+
// POST /public/grants/active -> listActiveGrants(body.store, body.opts)
|
|
11
|
+
|
|
12
|
+
(function () {
|
|
13
|
+
"use strict";
|
|
14
|
+
|
|
15
|
+
const CONNECT_ASSEMBLE_URL = "/public/connect/assemble";
|
|
16
|
+
const GRANTS_ACTIVE_URL = "/public/grants/active";
|
|
17
|
+
|
|
18
|
+
// Demo policy for the connected agent. Read only scopes, bounded caps,
|
|
19
|
+
// one hour expiry. Nothing here is authorization.
|
|
20
|
+
const DEMO_AGENT_ADDRESS = "0x1111111111111111111111111111111111111111";
|
|
21
|
+
const DEMO_CHAIN_ID = 8453;
|
|
22
|
+
const DEMO_ACTIONS = ["read:balance", "read:positions"];
|
|
23
|
+
const DEMO_TARGETS = [];
|
|
24
|
+
const DEMO_MAX_VALUE_WEI = "0";
|
|
25
|
+
const DEMO_MAX_GAS_WEI = "0";
|
|
26
|
+
const DEMO_MAX_SLIPPAGE_BPS = 0;
|
|
27
|
+
const DEMO_TTL_SECONDS = 3600;
|
|
28
|
+
|
|
29
|
+
const connectBtn = document.getElementById("connect-btn");
|
|
30
|
+
const walletState = document.getElementById("wallet-state");
|
|
31
|
+
const revokeBtn = document.getElementById("revoke-btn");
|
|
32
|
+
const permissionPanel = document.getElementById("permission-panel");
|
|
33
|
+
const panelStatus = document.getElementById("panel-status");
|
|
34
|
+
|
|
35
|
+
const stepConnect = document.getElementById("step-connect");
|
|
36
|
+
const stepBound = document.getElementById("step-bound");
|
|
37
|
+
const stepEnforced = document.getElementById("step-enforced");
|
|
38
|
+
|
|
39
|
+
const permChain = document.getElementById("perm-chain");
|
|
40
|
+
const permActions = document.getElementById("perm-actions");
|
|
41
|
+
const permTargets = document.getElementById("perm-targets");
|
|
42
|
+
const permMaxValue = document.getElementById("perm-max-value");
|
|
43
|
+
const permExpiry = document.getElementById("perm-expiry");
|
|
44
|
+
|
|
45
|
+
// In-memory session state only. Never persisted, never logged.
|
|
46
|
+
let session = {
|
|
47
|
+
accountAddress: null,
|
|
48
|
+
grant: null,
|
|
49
|
+
grantId: null,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
function setStep(stepEl, state) {
|
|
53
|
+
stepEl.setAttribute("data-state", state);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function resetSteps() {
|
|
57
|
+
setStep(stepConnect, "pending");
|
|
58
|
+
setStep(stepBound, "pending");
|
|
59
|
+
setStep(stepEnforced, "pending");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function hasInjectedWallet() {
|
|
63
|
+
return typeof window !== "undefined" && typeof window.ethereum !== "undefined";
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function postJson(url, body) {
|
|
67
|
+
const res = await fetch(url, {
|
|
68
|
+
method: "POST",
|
|
69
|
+
headers: { "content-type": "application/json" },
|
|
70
|
+
body: JSON.stringify(body),
|
|
71
|
+
});
|
|
72
|
+
const data = await res.json().catch(() => null);
|
|
73
|
+
if (!res.ok) {
|
|
74
|
+
const message = (data && data.error) || `request to ${url} failed with status ${res.status}`;
|
|
75
|
+
throw new Error(message);
|
|
76
|
+
}
|
|
77
|
+
return data;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function nowUnixSeconds() {
|
|
81
|
+
return Math.floor(Date.now() / 1000);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function formatExpiry(unixSeconds) {
|
|
85
|
+
return new Date(unixSeconds * 1000).toISOString();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function renderPermissionPanel(grant) {
|
|
89
|
+
permChain.textContent = String(grant.chainId);
|
|
90
|
+
permActions.textContent = grant.actions.join(", ");
|
|
91
|
+
permTargets.textContent = grant.targets.length ? grant.targets.join(", ") : "none, read and simulate only";
|
|
92
|
+
permMaxValue.textContent = `${grant.maxValueWei} wei`;
|
|
93
|
+
permExpiry.textContent = formatExpiry(grant.expiresAt);
|
|
94
|
+
permissionPanel.hidden = false;
|
|
95
|
+
revokeBtn.disabled = false;
|
|
96
|
+
panelStatus.textContent = "";
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function requestAccount() {
|
|
100
|
+
const accounts = await window.ethereum.request({ method: "eth_requestAccounts" });
|
|
101
|
+
if (!accounts || !accounts[0]) {
|
|
102
|
+
throw new Error("no account returned by wallet");
|
|
103
|
+
}
|
|
104
|
+
return accounts[0];
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function connectFlow() {
|
|
108
|
+
if (!hasInjectedWallet()) {
|
|
109
|
+
walletState.textContent = "Connect a wallet to continue.";
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
connectBtn.disabled = true;
|
|
114
|
+
resetSteps();
|
|
115
|
+
permissionPanel.hidden = true;
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
walletState.textContent = "Requesting account.";
|
|
119
|
+
const accountAddress = await requestAccount();
|
|
120
|
+
session.accountAddress = accountAddress;
|
|
121
|
+
setStep(stepConnect, "done");
|
|
122
|
+
walletState.textContent = `Connected as ${accountAddress}.`;
|
|
123
|
+
|
|
124
|
+
setStep(stepBound, "active");
|
|
125
|
+
const assembled = await postJson(CONNECT_ASSEMBLE_URL, {
|
|
126
|
+
chainId: DEMO_CHAIN_ID,
|
|
127
|
+
agentAddress: DEMO_AGENT_ADDRESS,
|
|
128
|
+
accountAddress: accountAddress,
|
|
129
|
+
actions: DEMO_ACTIONS,
|
|
130
|
+
targets: DEMO_TARGETS,
|
|
131
|
+
maxValueWei: DEMO_MAX_VALUE_WEI,
|
|
132
|
+
maxGasWei: DEMO_MAX_GAS_WEI,
|
|
133
|
+
maxSlippageBps: DEMO_MAX_SLIPPAGE_BPS,
|
|
134
|
+
ttlSeconds: DEMO_TTL_SECONDS,
|
|
135
|
+
nonce: `console.${nowUnixSeconds()}`,
|
|
136
|
+
revocationKey: `revoke.console.${nowUnixSeconds()}`,
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
const grant = assembled.payload.grant;
|
|
140
|
+
const grantId = assembled.payload.grantId;
|
|
141
|
+
session.grant = grant;
|
|
142
|
+
session.grantId = grantId;
|
|
143
|
+
setStep(stepBound, "done");
|
|
144
|
+
walletState.textContent = "Permission set reviewed. Building local preview.";
|
|
145
|
+
|
|
146
|
+
setStep(stepEnforced, "active");
|
|
147
|
+
await refreshActiveGrants();
|
|
148
|
+
setStep(stepEnforced, "done");
|
|
149
|
+
walletState.textContent = "Permission preview ready. No session was installed.";
|
|
150
|
+
} catch (err) {
|
|
151
|
+
walletState.textContent = `Connect failed: ${err.message}`;
|
|
152
|
+
} finally {
|
|
153
|
+
connectBtn.disabled = false;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async function refreshActiveGrants() {
|
|
158
|
+
if (!session.grant || !session.accountAddress) return;
|
|
159
|
+
|
|
160
|
+
const store = [
|
|
161
|
+
{
|
|
162
|
+
grant: session.grant,
|
|
163
|
+
revoked: false,
|
|
164
|
+
},
|
|
165
|
+
];
|
|
166
|
+
|
|
167
|
+
const result = await postJson(GRANTS_ACTIVE_URL, {
|
|
168
|
+
store: store,
|
|
169
|
+
opts: {
|
|
170
|
+
now: nowUnixSeconds(),
|
|
171
|
+
accountAddress: session.accountAddress,
|
|
172
|
+
agentAddress: DEMO_AGENT_ADDRESS,
|
|
173
|
+
chainId: DEMO_CHAIN_ID,
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
const active = Array.isArray(result) ? result : result.active || [];
|
|
178
|
+
const match = active.find((entry) => entry.id === session.grantId);
|
|
179
|
+
|
|
180
|
+
if (match) {
|
|
181
|
+
renderPermissionPanel(match.grant);
|
|
182
|
+
} else {
|
|
183
|
+
permissionPanel.hidden = false;
|
|
184
|
+
revokeBtn.disabled = true;
|
|
185
|
+
panelStatus.textContent = "This permission preview is no longer active.";
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function clearPreviewFlow() {
|
|
190
|
+
if (!session.grant) return;
|
|
191
|
+
revokeBtn.disabled = true;
|
|
192
|
+
session.grant = null;
|
|
193
|
+
session.grantId = null;
|
|
194
|
+
permissionPanel.hidden = true;
|
|
195
|
+
resetSteps();
|
|
196
|
+
setStep(stepConnect, "done");
|
|
197
|
+
walletState.textContent = "Permission preview cleared. No on-chain revocation was required.";
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
connectBtn.addEventListener("click", () => {
|
|
201
|
+
connectFlow();
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
revokeBtn.addEventListener("click", () => {
|
|
205
|
+
clearPreviewFlow();
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// ── Bitcoin multi-wallet lane (actual BTC L1) ───────────────────────────
|
|
209
|
+
const btcConnectBtn = document.getElementById("btc-connect-btn");
|
|
210
|
+
const btcWalletState = document.getElementById("btc-wallet-state");
|
|
211
|
+
const btcPanel = document.getElementById("btc-panel");
|
|
212
|
+
const btcWalletLabel = document.getElementById("btc-wallet-label");
|
|
213
|
+
const btcAddressEl = document.getElementById("btc-address");
|
|
214
|
+
const btcDetectedEl = document.getElementById("btc-detected");
|
|
215
|
+
const btcPanelStatus = document.getElementById("btc-panel-status");
|
|
216
|
+
|
|
217
|
+
let btcSession = null;
|
|
218
|
+
|
|
219
|
+
function refreshBtcDetected() {
|
|
220
|
+
const api = window.OracleBitcoinWallets;
|
|
221
|
+
if (!api) {
|
|
222
|
+
if (btcDetectedEl) btcDetectedEl.textContent = "wallet adapter missing";
|
|
223
|
+
return [];
|
|
224
|
+
}
|
|
225
|
+
const detected = api.listDetected();
|
|
226
|
+
if (btcDetectedEl) {
|
|
227
|
+
btcDetectedEl.textContent = detected.length
|
|
228
|
+
? detected.map((d) => d.label).join(", ")
|
|
229
|
+
: "none - install Xverse / UniSat / Leather / OKX / Phantom / Magic Eden";
|
|
230
|
+
}
|
|
231
|
+
return detected;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async function btcConnectFlow() {
|
|
235
|
+
const api = window.OracleBitcoinWallets;
|
|
236
|
+
if (!api) {
|
|
237
|
+
btcWalletState.textContent = "Bitcoin wallet adapter failed to load.";
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
btcConnectBtn.disabled = true;
|
|
241
|
+
btcWalletState.textContent = "Requesting Bitcoin wallet.";
|
|
242
|
+
try {
|
|
243
|
+
btcSession = await api.connect();
|
|
244
|
+
btcWalletState.textContent = `BTC connected: ${btcSession.walletLabel}`;
|
|
245
|
+
btcWalletLabel.textContent = btcSession.walletLabel;
|
|
246
|
+
btcAddressEl.textContent = btcSession.address;
|
|
247
|
+
btcPanel.hidden = false;
|
|
248
|
+
btcPanelStatus.textContent =
|
|
249
|
+
"Connected. Signing stays in your wallet (signPsbt). Oracle only reads + broadcasts user-signed hex.";
|
|
250
|
+
refreshBtcDetected();
|
|
251
|
+
} catch (err) {
|
|
252
|
+
btcWalletState.textContent = `BTC connect failed: ${err.message}`;
|
|
253
|
+
btcPanelStatus.textContent = err.message;
|
|
254
|
+
} finally {
|
|
255
|
+
btcConnectBtn.disabled = false;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (btcConnectBtn) {
|
|
260
|
+
btcConnectBtn.addEventListener("click", () => {
|
|
261
|
+
btcConnectFlow();
|
|
262
|
+
});
|
|
263
|
+
refreshBtcDetected();
|
|
264
|
+
if (!refreshBtcDetected().length) {
|
|
265
|
+
btcWalletState.textContent = "No Bitcoin wallet detected yet.";
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (!hasInjectedWallet()) {
|
|
270
|
+
walletState.textContent = "Connect an EVM wallet to continue.";
|
|
271
|
+
}
|
|
272
|
+
})();
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
// Multi-wallet Bitcoin connector for Oracle Console.
|
|
2
|
+
// Injected providers only - no npm wallet SDK required in the static console.
|
|
3
|
+
// Supported: Xverse, UniSat, Leather, OKX, Phantom, Magic Eden, generic.
|
|
4
|
+
// Signing: PSBT stays in the wallet. Oracle never sees private keys.
|
|
5
|
+
|
|
6
|
+
(function (root) {
|
|
7
|
+
"use strict";
|
|
8
|
+
|
|
9
|
+
const WALLETS = [
|
|
10
|
+
{
|
|
11
|
+
id: "unisat",
|
|
12
|
+
label: "UniSat",
|
|
13
|
+
detect: () => window.unisat,
|
|
14
|
+
async connect(provider) {
|
|
15
|
+
const accounts = await provider.requestAccounts();
|
|
16
|
+
const address = accounts && accounts[0];
|
|
17
|
+
if (!address) throw new Error("UniSat returned no address");
|
|
18
|
+
const network = (await provider.getNetwork?.()) || "livenet";
|
|
19
|
+
return { address, network, provider };
|
|
20
|
+
},
|
|
21
|
+
async signPsbt(provider, psbtHex, options = {}) {
|
|
22
|
+
return provider.signPsbt(psbtHex, options);
|
|
23
|
+
},
|
|
24
|
+
async pushPsbt(provider, psbtHex) {
|
|
25
|
+
if (typeof provider.pushPsbt === "function") return provider.pushPsbt(psbtHex);
|
|
26
|
+
throw new Error("UniSat pushPsbt unavailable - use Oracle broadcast with raw hex");
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: "okx",
|
|
31
|
+
label: "OKX",
|
|
32
|
+
detect: () => window.okxwallet && window.okxwallet.bitcoin,
|
|
33
|
+
async connect(provider) {
|
|
34
|
+
const result = await provider.connect();
|
|
35
|
+
const address = result?.address || result?.addresses?.[0] || (Array.isArray(result) && result[0]);
|
|
36
|
+
if (!address) throw new Error("OKX returned no address");
|
|
37
|
+
return { address, network: result?.network || "livenet", provider };
|
|
38
|
+
},
|
|
39
|
+
async signPsbt(provider, psbtHex, options = {}) {
|
|
40
|
+
return provider.signPsbt(psbtHex, options);
|
|
41
|
+
},
|
|
42
|
+
async pushPsbt(provider, psbtHex) {
|
|
43
|
+
if (typeof provider.pushPsbt === "function") return provider.pushPsbt(psbtHex);
|
|
44
|
+
throw new Error("OKX pushPsbt unavailable - use Oracle broadcast with raw hex");
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
id: "xverse",
|
|
49
|
+
label: "Xverse",
|
|
50
|
+
detect: () =>
|
|
51
|
+
window.XverseProviders?.BitcoinProvider ||
|
|
52
|
+
window.BitcoinProvider ||
|
|
53
|
+
(window.xverse && window.xverse.bitcoin),
|
|
54
|
+
async connect(provider) {
|
|
55
|
+
// Sats Connect request shape when provider.request exists
|
|
56
|
+
if (typeof provider.request === "function") {
|
|
57
|
+
const res = await provider.request("getAccounts", {
|
|
58
|
+
purposes: ["payment", "ordinals"],
|
|
59
|
+
message: "Connect to Oracle",
|
|
60
|
+
});
|
|
61
|
+
const arr = res?.result || res;
|
|
62
|
+
const payment = Array.isArray(arr) ? arr.find((a) => a.purpose === "payment") || arr[0] : null;
|
|
63
|
+
const address = payment?.address || payment;
|
|
64
|
+
if (!address) throw new Error("Xverse returned no address");
|
|
65
|
+
return { address, network: payment?.network || "Mainnet", provider };
|
|
66
|
+
}
|
|
67
|
+
if (typeof provider.connect === "function") {
|
|
68
|
+
const res = await provider.connect("Oracle");
|
|
69
|
+
const address = res?.addresses?.payment?.address || res?.paymentAddress || res?.address;
|
|
70
|
+
if (!address) throw new Error("Xverse returned no address");
|
|
71
|
+
return { address, network: res?.network || "Mainnet", provider };
|
|
72
|
+
}
|
|
73
|
+
throw new Error("Xverse provider has no connect/request method");
|
|
74
|
+
},
|
|
75
|
+
async signPsbt(provider, psbtBase64, options = {}) {
|
|
76
|
+
if (typeof provider.request === "function") {
|
|
77
|
+
const res = await provider.request("signPsbt", {
|
|
78
|
+
psbt: psbtBase64,
|
|
79
|
+
allowedSignHash: options.allowedSignHash,
|
|
80
|
+
signInputs: options.signInputs,
|
|
81
|
+
broadcast: false,
|
|
82
|
+
});
|
|
83
|
+
return res?.result?.psbt || res?.psbt || res;
|
|
84
|
+
}
|
|
85
|
+
if (typeof provider.signPsbt === "function") {
|
|
86
|
+
return provider.signPsbt(psbtBase64, options);
|
|
87
|
+
}
|
|
88
|
+
throw new Error("Xverse signPsbt unavailable");
|
|
89
|
+
},
|
|
90
|
+
async pushPsbt() {
|
|
91
|
+
throw new Error("Xverse: finalize in wallet or broadcast raw hex via Oracle");
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
id: "leather",
|
|
96
|
+
label: "Leather",
|
|
97
|
+
detect: () => window.LeatherProvider || window.HiroWalletProvider || window.btc,
|
|
98
|
+
async connect(provider) {
|
|
99
|
+
if (typeof provider.request === "function") {
|
|
100
|
+
const res = await provider.request("getAddresses");
|
|
101
|
+
const addrs = res?.result?.addresses || res?.addresses || [];
|
|
102
|
+
const payment = addrs.find((a) => a.type === "p2wpkh" || a.symbol === "BTC") || addrs[0];
|
|
103
|
+
const address = payment?.address;
|
|
104
|
+
if (!address) throw new Error("Leather returned no address");
|
|
105
|
+
return { address, network: payment?.network || "mainnet", provider };
|
|
106
|
+
}
|
|
107
|
+
throw new Error("Leather provider has no request method");
|
|
108
|
+
},
|
|
109
|
+
async signPsbt(provider, psbtBase64, options = {}) {
|
|
110
|
+
const res = await provider.request("signPsbt", {
|
|
111
|
+
hex: options.hex || undefined,
|
|
112
|
+
base64: psbtBase64,
|
|
113
|
+
broadcast: false,
|
|
114
|
+
...options,
|
|
115
|
+
});
|
|
116
|
+
return res?.result?.hex || res?.result?.base64 || res?.hex || res;
|
|
117
|
+
},
|
|
118
|
+
async pushPsbt() {
|
|
119
|
+
throw new Error("Leather: broadcast raw hex via Oracle");
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
id: "phantom",
|
|
124
|
+
label: "Phantom",
|
|
125
|
+
detect: () => window.phantom && window.phantom.bitcoin,
|
|
126
|
+
async connect(provider) {
|
|
127
|
+
const res = await provider.requestAccounts();
|
|
128
|
+
const address = res?.[0]?.address || res?.[0];
|
|
129
|
+
if (!address) throw new Error("Phantom returned no BTC address");
|
|
130
|
+
return { address, network: "mainnet", provider };
|
|
131
|
+
},
|
|
132
|
+
async signPsbt(provider, psbtHex, options = {}) {
|
|
133
|
+
const res = await provider.signPSBT(Buffer.from(psbtHex, "hex"), options);
|
|
134
|
+
return res;
|
|
135
|
+
},
|
|
136
|
+
async pushPsbt() {
|
|
137
|
+
throw new Error("Phantom: broadcast raw hex via Oracle");
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
id: "magic-eden",
|
|
142
|
+
label: "Magic Eden",
|
|
143
|
+
detect: () => window.magicEden && window.magicEden.bitcoin,
|
|
144
|
+
async connect(provider) {
|
|
145
|
+
const res = await provider.connect();
|
|
146
|
+
const address = res?.address || res?.ordinals?.address || res?.payment?.address;
|
|
147
|
+
if (!address) throw new Error("Magic Eden returned no address");
|
|
148
|
+
return { address, network: "mainnet", provider };
|
|
149
|
+
},
|
|
150
|
+
async signPsbt(provider, psbt, options = {}) {
|
|
151
|
+
return provider.signPsbt(psbt, options);
|
|
152
|
+
},
|
|
153
|
+
async pushPsbt() {
|
|
154
|
+
throw new Error("Magic Eden: broadcast raw hex via Oracle");
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
];
|
|
158
|
+
|
|
159
|
+
function listDetected() {
|
|
160
|
+
return WALLETS.filter((w) => {
|
|
161
|
+
try {
|
|
162
|
+
return !!w.detect();
|
|
163
|
+
} catch {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
}).map((w) => ({ id: w.id, label: w.label }));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async function connect(preferredId) {
|
|
170
|
+
const detected = WALLETS.filter((w) => {
|
|
171
|
+
try {
|
|
172
|
+
return !!w.detect();
|
|
173
|
+
} catch {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
if (!detected.length) {
|
|
178
|
+
throw new Error(
|
|
179
|
+
"No Bitcoin wallet detected. Install Xverse, UniSat, Leather, OKX, Phantom, or Magic Eden."
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
const wallet = (preferredId && detected.find((w) => w.id === preferredId)) || detected[0];
|
|
183
|
+
const provider = wallet.detect();
|
|
184
|
+
const session = await wallet.connect(provider);
|
|
185
|
+
return {
|
|
186
|
+
walletId: wallet.id,
|
|
187
|
+
walletLabel: wallet.label,
|
|
188
|
+
address: session.address,
|
|
189
|
+
network: session.network,
|
|
190
|
+
provider: session.provider,
|
|
191
|
+
wallet,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async function signPsbt(session, psbt, options = {}) {
|
|
196
|
+
if (!session?.wallet || !session?.provider) throw new Error("Bitcoin wallet not connected");
|
|
197
|
+
return session.wallet.signPsbt(session.provider, psbt, options);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
root.OracleBitcoinWallets = {
|
|
201
|
+
listDetected,
|
|
202
|
+
connect,
|
|
203
|
+
signPsbt,
|
|
204
|
+
supported: WALLETS.map((w) => ({ id: w.id, label: w.label })),
|
|
205
|
+
};
|
|
206
|
+
})(typeof window !== "undefined" ? window : globalThis);
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>Oracle Console</title>
|
|
7
|
+
<link rel="stylesheet" href="styles.css" />
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<main class="page">
|
|
11
|
+
<header class="hero">
|
|
12
|
+
<p class="hero-line">Preview agent permissions before you install a real wallet-enforced session.</p>
|
|
13
|
+
<p class="hero-sub">
|
|
14
|
+
Full crypto pack: multichain reads, quotes, simulation, and unsigned
|
|
15
|
+
transaction prep across 11 EVM chains plus Solana and Bitcoin L1 lanes.
|
|
16
|
+
Protocol builder included — design and prepare a deploy, sign it yourself.
|
|
17
|
+
</p>
|
|
18
|
+
<p class="hero-posture">
|
|
19
|
+
Every lane starts <strong>DISARMED</strong>. This console previews policy only and does not authorize an agent.
|
|
20
|
+
</p>
|
|
21
|
+
</header>
|
|
22
|
+
|
|
23
|
+
<section class="cta-row">
|
|
24
|
+
<button id="connect-btn" class="cta" type="button">Preview agent permissions</button>
|
|
25
|
+
<button id="btc-connect-btn" class="cta cta-secondary" type="button">Connect Bitcoin wallet</button>
|
|
26
|
+
<p id="wallet-state" class="wallet-state" aria-live="polite"></p>
|
|
27
|
+
<p id="btc-wallet-state" class="wallet-state" aria-live="polite"></p>
|
|
28
|
+
</section>
|
|
29
|
+
|
|
30
|
+
<section id="btc-panel" class="permission-panel btc-panel" hidden>
|
|
31
|
+
<h2 class="panel-title">Bitcoin lane</h2>
|
|
32
|
+
<p class="panel-note">Actual BTC L1 + ordinals/runes via Satflow. Sign in wallet (Xverse / UniSat / Leather / OKX / Phantom). Magic Eden BTC marketplace is dead. Oracle never holds keys.</p>
|
|
33
|
+
<dl class="panel-grid">
|
|
34
|
+
<dt>Wallet</dt>
|
|
35
|
+
<dd id="btc-wallet-label">.</dd>
|
|
36
|
+
<dt>Address</dt>
|
|
37
|
+
<dd id="btc-address">.</dd>
|
|
38
|
+
<dt>Detected</dt>
|
|
39
|
+
<dd id="btc-detected">.</dd>
|
|
40
|
+
</dl>
|
|
41
|
+
<p id="btc-panel-status" class="panel-status" aria-live="polite"></p>
|
|
42
|
+
</section>
|
|
43
|
+
|
|
44
|
+
<section class="sequence" aria-label="connection sequence">
|
|
45
|
+
<div class="seq-step" id="step-connect" data-state="pending">
|
|
46
|
+
<span class="seq-index">1</span>
|
|
47
|
+
<span class="seq-label">Connect</span>
|
|
48
|
+
</div>
|
|
49
|
+
<div class="seq-arrow">-></div>
|
|
50
|
+
<div class="seq-step" id="step-bound" data-state="pending">
|
|
51
|
+
<span class="seq-index">2</span>
|
|
52
|
+
<span class="seq-label">Review</span>
|
|
53
|
+
</div>
|
|
54
|
+
<div class="seq-arrow">-></div>
|
|
55
|
+
<div class="seq-step" id="step-enforced" data-state="pending">
|
|
56
|
+
<span class="seq-index">3</span>
|
|
57
|
+
<span class="seq-label">Preview</span>
|
|
58
|
+
</div>
|
|
59
|
+
</section>
|
|
60
|
+
|
|
61
|
+
<section id="permission-panel" class="permission-panel" hidden>
|
|
62
|
+
<h2 class="panel-title">What this agent can do</h2>
|
|
63
|
+
<dl class="panel-grid">
|
|
64
|
+
<dt>Chain</dt>
|
|
65
|
+
<dd id="perm-chain">.</dd>
|
|
66
|
+
|
|
67
|
+
<dt>Actions</dt>
|
|
68
|
+
<dd id="perm-actions">.</dd>
|
|
69
|
+
|
|
70
|
+
<dt>Targets</dt>
|
|
71
|
+
<dd id="perm-targets">.</dd>
|
|
72
|
+
|
|
73
|
+
<dt>Max value</dt>
|
|
74
|
+
<dd id="perm-max-value">.</dd>
|
|
75
|
+
|
|
76
|
+
<dt>Expiry</dt>
|
|
77
|
+
<dd id="perm-expiry">.</dd>
|
|
78
|
+
</dl>
|
|
79
|
+
<button id="revoke-btn" class="revoke" type="button">Clear preview</button>
|
|
80
|
+
<p id="panel-status" class="panel-status" aria-live="polite"></p>
|
|
81
|
+
</section>
|
|
82
|
+
|
|
83
|
+
<footer class="custody-line">
|
|
84
|
+
<p>You keep custody. A preview is not an installed grant, session key, or revocation.</p>
|
|
85
|
+
</footer>
|
|
86
|
+
</main>
|
|
87
|
+
|
|
88
|
+
<script src="bitcoin-wallets.js"></script>
|
|
89
|
+
<script src="app.js"></script>
|
|
90
|
+
</body>
|
|
91
|
+
</html>
|