Generate 10 verifiable agents.
+
+
+ Only ENS identity is checked. Claim does not scan token balances or unrelated wallet assets.
@@ -341,10 +344,10 @@
Bring one ENS name.
Generate 10 verifiable agents.
-
+
Please enter a valid .eth name
- Must end in .eth · The name you own or control
+ Must end in .eth · The name you own or control
@@ -587,7 +590,8 @@
Payment and provisioning are coming next.
pubKeyB64:'', privKeyB64:'', kid:'',
keyGenerated:false, keyDownloaded:false, keyAcked:false,
_cardJson:'',
- authenticatedAddress:'', authStatus:'NOT_AUTHENTICATED', authChainId:null, siweAuthenticated:false
+ authenticatedAddress:'', authStatus:'NOT_AUTHENTICATED', authChainId:null, siweAuthenticated:false,
+ primaryEnsName:null
};
const SIWE_STATEMENT = 'Authenticate with CommandLayer Claim activation.';
@@ -654,10 +658,11 @@ Payment and provisioning are coming next.
// ── STEP 2: MODE ──────────────────────────────────────────────────────────────
function updateModeExamples() {
- const base = state.ens.replace('.eth','');
+ const currentName = state.ens || 'acme.eth';
+ const base = currentName.replace('.eth','');
document.getElementById('eg-cl').textContent = base + '.approveagent.eth';
- document.getElementById('eg-own').textContent = 'approve.' + state.ens;
- document.getElementById('eg-single').textContent = state.ens;
+ document.getElementById('eg-own').textContent = 'approve.' + currentName;
+ document.getElementById('eg-single').textContent = currentName;
}
function selectMode(m) {
@@ -667,6 +672,7 @@ Payment and provisioning are coming next.
});
updateNamePreview();
updateSiweModeHint();
+ updateEnsFieldByMode();
}
function updateNamePreview() {
@@ -689,11 +695,13 @@ Payment and provisioning are coming next.
if (!document.querySelector('.mode-card.selected')) selectMode('cl');
updateSiweModeHint();
goToStep(3);
+ updateEnsFieldByMode();
}
// Auto-select CL mode on load
selectMode('cl');
updateSiweModeHint();
+updateEnsFieldByMode();
// ── STEP 3: CAPABILITIES ──────────────────────────────────────────────────────
function setCapMode(m) {
@@ -750,7 +758,8 @@ Payment and provisioning are coming next.
}
function goStep3() {
- const val = document.getElementById('ensInput').value.trim().toLowerCase();
+ const raw = document.getElementById('ensInput').value.trim().toLowerCase();
+ const val = state.activationMode==='cl' && raw && !raw.endsWith('.eth') ? `${raw}.eth` : raw;
const err = document.getElementById('ensError');
if (!val.endsWith('.eth') || val.length < 6) {
err.classList.add('show');
@@ -801,6 +810,63 @@ Payment and provisioning are coming next.
}
}
+function labelFromEnsName(name){
+ return String(name || '').toLowerCase().replace(/\.eth$/,'').replace(/[^a-z0-9-]/g,'');
+}
+
+function updateEnsFieldByMode() {
+ const labelEl = document.getElementById('ensFieldLabel');
+ const inputEl = document.getElementById('ensInput');
+ const hintEl = document.getElementById('ensFieldHint');
+ if(state.activationMode==='cl'){
+ labelEl.textContent='Tenant label';
+ hintEl.textContent='This creates names like hydroseal.approveagent.eth';
+ inputEl.placeholder='hydroseal';
+ if(state.primaryEnsName && !inputEl.value.trim()){ inputEl.value=labelFromEnsName(state.primaryEnsName); }
+ } else if (state.activationMode==='own'){
+ labelEl.textContent='ENS Name';
+ hintEl.textContent='This creates names like approve.hydroseal.eth';
+ inputEl.placeholder='hydroseal.eth';
+ if(state.primaryEnsName && !inputEl.value.trim()){ inputEl.value=state.primaryEnsName; }
+ } else {
+ labelEl.textContent='ENS Name';
+ hintEl.textContent='Single agent mode uses your ENS name directly';
+ inputEl.placeholder='hydroseal.eth';
+ if(state.primaryEnsName && !inputEl.value.trim()){ inputEl.value=state.primaryEnsName; }
+ }
+}
+
+async function lookupPrimaryEns(address){
+ const statusEl=document.getElementById('ensLookupStatus');
+ const btn=document.getElementById('usePrimaryEnsBtn');
+ statusEl.textContent='Checking primary ENS name...';
+ btn.style.display='none';
+ const res=await fetch(`/api/ens/lookup?address=${encodeURIComponent(address)}`,{headers:{Accept:'application/json'}});
+ const data=await res.json();
+ if(!res.ok || !data.ok){
+ statusEl.textContent=(data && data.status==='PROVIDER_UNAVAILABLE')
+ ? 'Primary ENS lookup unavailable: ETH_RPC_URL is not configured.'
+ : 'No primary ENS name found. You can still enter a tenant label or ENS name manually.';
+ state.primaryEnsName=null;
+ return;
+ }
+ if(data.primaryName){
+ state.primaryEnsName=data.primaryName.toLowerCase();
+ statusEl.textContent=`Primary ENS detected: ${state.primaryEnsName}`;
+ btn.style.display='inline-flex';
+ }else{
+ state.primaryEnsName=null;
+ statusEl.textContent='No primary ENS name found. You can still enter a tenant label or ENS name manually.';
+ }
+}
+
+window.usePrimaryEnsName = function usePrimaryEnsName() {
+ if(!state.primaryEnsName) return;
+ const inputEl=document.getElementById('ensInput');
+ inputEl.value = state.activationMode==='cl' ? labelFromEnsName(state.primaryEnsName) : state.primaryEnsName;
+ inputEl.focus();
+};
+
window.connectWallet = async function connectWallet(){
const statusEl=document.getElementById('siweStatus');
const walletEl=document.getElementById('siweWallet');
@@ -888,6 +954,8 @@ Payment and provisioning are coming next.
walletEl.textContent=`Connected wallet: ${shortAddr(verify.address)}`;
btn.textContent='Authenticated';
nextBtn.disabled=false;
+ await lookupPrimaryEns(verify.address);
+ updateEnsFieldByMode();
} catch (err) {
const msg=((err && err.message)||'SIWE verification failed.').trim();
errorEl.textContent=err && err.code===4001 ? 'Signature rejected.' : msg;
diff --git a/tests/api-ens-lookup.test.js b/tests/api-ens-lookup.test.js
new file mode 100644
index 0000000..5efe2d8
--- /dev/null
+++ b/tests/api-ens-lookup.test.js
@@ -0,0 +1,51 @@
+'use strict';
+
+const test = require('node:test');
+const assert = require('node:assert/strict');
+
+const lookupHandler = require('../api/ens/lookup');
+
+function makeRes() {
+ return {
+ statusCode: 200,
+ headers: {},
+ body: null,
+ setHeader(name, value) { this.headers[name.toLowerCase()] = value; },
+ status(code) { this.statusCode = code; return this; },
+ json(payload) { this.body = payload; return this; }
+ };
+}
+
+test('GET /api/ens/lookup rejects missing address', async () => {
+ const res = makeRes();
+ await lookupHandler({ method: 'GET', query: {}, headers: {} }, res);
+ assert.equal(res.statusCode, 400);
+ assert.equal(res.body.ok, false);
+ assert.equal(res.body.status, 'INVALID_REQUEST');
+});
+
+test('GET /api/ens/lookup rejects invalid address', async () => {
+ const res = makeRes();
+ await lookupHandler({ method: 'GET', query: { address: 'not-an-address' }, headers: {} }, res);
+ assert.equal(res.statusCode, 400);
+ assert.equal(res.body.ok, false);
+ assert.equal(res.body.status, 'INVALID_ADDRESS');
+});
+
+test('GET /api/ens/lookup returns provider unavailable when ETH_RPC_URL is missing', async () => {
+ const prevEth = process.env.ETH_RPC_URL;
+ const prevBase = process.env.BASE_RPC_URL;
+ delete process.env.ETH_RPC_URL;
+ delete process.env.BASE_RPC_URL;
+
+ const res = makeRes();
+ await lookupHandler({ method: 'GET', query: { address: '0x0000000000000000000000000000000000000001' }, headers: {} }, res);
+
+ assert.equal(res.statusCode, 503);
+ assert.equal(res.body.ok, false);
+ assert.equal(res.body.status, 'PROVIDER_UNAVAILABLE');
+ assert.equal(res.body.error, 'ETH_RPC_URL is not configured');
+
+ if (typeof prevEth === 'undefined') delete process.env.ETH_RPC_URL; else process.env.ETH_RPC_URL = prevEth;
+ if (typeof prevBase === 'undefined') delete process.env.BASE_RPC_URL; else process.env.BASE_RPC_URL = prevBase;
+});