Custom domain for caffeine.ai dapp

Hi, I’m trying to configure custom domain www.plsak.com for my dapp (by Caffeine): https://plsak-m9e.caffeine.xyz/
Backend Canister: tks3f-kaaaa-aaaas-qbq5q-cai

Frontend Canister: tkq3f-kaaaa-aaaas-qbq5q-cai

My DNS records configuration:

$ host -t cname www.plsak.com
www.plsak.com is an alias for tks3f-kaaaa-aaaas-qbq5q-cai.icp0.io.

$ host -t txt _canister-id.www.plsak.com
_canister-id.www.plsak.com descriptive text "tks3f-kaaaa-aaaas-qbq5q-cai"

Caffeine is very confident on the setup so I configured DNS records as advised, looking on below doc there are a bit different ones ( ...icp1.io, _acme-challenge, …icp2.io …) but Caffeine confirmed that it’s testnet/not-needed:

I got advised that the propagation can take up to 48 hours but as that period has passed and I’m still getting just Unknown Domain (https://www.plsak.com/) would like to ask to review my (and caffeine.ai) setup to identify if is there something incorrect or eventually if is there some limitation on ICP.

Relevant communication with caffeine.ai:

Relevant code by caffeine.ai handling the .well-known directory & its content - WellKnownHandler.tsx:

import React, { useEffect, useState } from 'react';

interface WellKnownFile {
  path: string;
  content: string;
  contentType: string;
}

// Well-known files for domain verification - updated for www.plsak.com
const wellKnownFiles: WellKnownFile[] = [
  {
    path: 'ic-domains',
    content: 'www.plsak.com',
    contentType: 'text/plain'
  },
  {
    path: 'ii-alternative-origins',
    content: JSON.stringify({
      "alternativeOrigins": ["https://www.plsak.com"]
    }),
    contentType: 'application/json'
  }
];

// Static method for handling requests programmatically
export const handleWellKnownRequest = (wellKnownPath: string): void => {
  const file = wellKnownFiles.find(f => f.path === wellKnownPath);
  
  if (file) {
    // Create a blob and download it
    const blob = new Blob([file.content], { type: file.contentType });
    const url = URL.createObjectURL(blob);
    
    // For browser requests, we'll serve the content directly
    document.body.innerHTML = `<pre>${file.content}</pre>`;
    document.title = `.well-known/${wellKnownPath}`;
    
    // Set content type in document
    if (document.head) {
      const metaContentType = document.createElement('meta');
      metaContentType.httpEquiv = 'Content-Type';
      metaContentType.content = file.contentType;
      document.head.appendChild(metaContentType);
    }
  }
};

export default function WellKnownHandler() {
  const [fileContent, setFileContent] = useState<string>('');
  const [contentType, setContentType] = useState<string>('text/plain');
  const [notFound, setNotFound] = useState<boolean>(false);

  useEffect(() => {
    const path = window.location.pathname;
    const wellKnownPath = path.substring('/.well-known/'.length);
    
    const file = wellKnownFiles.find(f => f.path === wellKnownPath);
    
    if (file) {
      setFileContent(file.content);
      setContentType(file.contentType);
      setNotFound(false);
      
      // Set appropriate headers if possible
      if (document.head) {
        const metaContentType = document.createElement('meta');
        metaContentType.httpEquiv = 'Content-Type';
        metaContentType.content = file.contentType;
        document.head.appendChild(metaContentType);
      }
    } else {
      setNotFound(true);
    }
  }, []);

  if (notFound) {
    return (
      <div className="min-h-screen bg-slate-900 text-slate-100 flex items-center justify-center">
        <div className="text-center">
          <h1 className="text-2xl font-bold mb-4">404 - Not Found</h1>
          <p className="text-slate-400">The requested .well-known file was not found.</p>
          <div className="mt-8">
            <h2 className="text-lg font-semibold mb-4">Available .well-known files:</h2>
            <ul className="text-left space-y-2">
              {wellKnownFiles.map((file) => (
                <li key={file.path} className="text-blue-400">
                  <a href={`/.well-known/${file.path}`} className="hover:text-blue-300">
                    /.well-known/{file.path}
                  </a>
                </li>
              ))}
            </ul>
          </div>
        </div>
      </div>
    );
  }

  return (
    <div className="min-h-screen bg-slate-900 text-slate-100">
      <div className="container mx-auto px-4 py-8">
        <div className="bg-slate-800 rounded-lg p-6 border border-slate-700">
          <div className="flex items-center justify-between mb-4">
            <h1 className="text-xl font-semibold">
              .well-known/{window.location.pathname.substring('/.well-known/'.length)}
            </h1>
            <span className="text-sm text-slate-400 bg-slate-700 px-2 py-1 rounded">
              {contentType}
            </span>
          </div>
          <pre className="bg-slate-900 p-4 rounded border border-slate-600 overflow-auto text-sm">
            <code>{fileContent}</code>
          </pre>
          <div className="mt-4 text-sm text-slate-400">
            <p>This file is served for domain verification and configuration purposes.</p>
          </div>
        </div>
        
        <div className="mt-8 bg-slate-800 rounded-lg p-6 border border-slate-700">
          <h2 className="text-lg font-semibold mb-4">Domain Setup Instructions</h2>
          <div className="space-y-4 text-sm">
            <div>
              <h3 className="font-medium text-blue-400 mb-2">DNS Configuration for www.plsak.com:</h3>
              <div className="bg-slate-900 p-4 rounded border border-slate-600">
                <p className="mb-2"><strong>CNAME Record:</strong></p>
                <code className="text-green-400">www.plsak.com CNAME tks3f-kaaaa-aaaas-qbq5q-cai.icp0.io</code>
                
                <p className="mt-4 mb-2"><strong>TXT Record (for verification):</strong></p>
                <code className="text-green-400">_canister-id.www.plsak.com TXT tks3f-kaaaa-aaaas-qbq5q-cai</code>
              </div>
            </div>
            
            <div className="text-slate-400">
              <p><strong>Note:</strong> The canister ID tks3f-kaaaa-aaaas-qbq5q-cai is configured for this domain.</p>
              <p className="mt-2">After setting up these DNS records, your domain www.plsak.com will point to this application.</p>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}


Hey @plsak

It seems that caffeine was hallucinating and confidently leading you down the wrong path:

In order to set up a custom domain, you need to follow all the steps from the docs page that you linked:

  1. Setup the DNS records:
    1. CNAME from www.plsak.com to www.plsak.com.icp1.io (this is missing)
    2. TXT for _canister-id.www.plsak.com with your canister ID (this is correctly set up)
    3. CNAME from _acme-challenge.www.plsak.com to _acme-challenge.www.plsak.com.icp2.io (this is missing)
  2. Setup the .well-known/ic-domains file containing your domain name (this is missing)

Once you have done all of that, you can register the domain by calling:

curl -sL -X POST \
    -H 'Content-Type: application/json' \
    https://icp0.io/registrations \
    --data @- <<EOF
    {
      "name": "www.plsak.com"
    }
EOF

This will return a request ID with which you can check the registration status.

To check the .well-known/ic-domains file, you can use the following command:

curl https://tks3f-kaaaa-aaaas-qbq5q-cai.icp0.io/.well-known/ic-domains

For the record, I created a Caffeine app that guides you in the process of assigning a custom domain to your application: DNSter.

Curiosity: the app’s custom domain was associated using the app itself.

I was planning to add text prompts that you could copy and paste into your Caffeine chat to make Caffeine set up your app properly, but I still haven’t done it. It should anyway help you with the process.

Thank you! Let’s see now :crossed_fingers:

You never know, but I’m under the impression that .well-known/ic-domains should probably also be pre-rendered or generated by the canister, rather than at runtime with JavaScript as shown in the snippet above.

:sweat_smile: :crossed_fingers: :folded_hands:

This is also very helpful, thank you! :pray:

Looks like not so simple for the AI (few attempts made already) :person_shrugging:

Just to update, AI is now confused whether to setup the files in frontend or backend canister - so far DNS was configured for backend but now starts to tell to use frontend one as per below:

Configuring those files as static frontend files (“prerendered”) instead of generating the information on the backend seems like a pragmatic, and common, solution. Therefore I would say you can follow this approach.

That said, based on your screenshot, I’d expect the files to usually live in a folder called static or public, rather than directly under src but that’s just a guess. Maybe try to use the keyword “static” when asking the AI about configuring it on the frontend side?

Note that I’m not sure how to configure this in the caffeine/dfx setup, you know, it just works out of the box in Juno :wink:. Hopefully this helps though!

Yeah that’s actually one of things where I was confident that Caffeine will exactly know how to achieve, but hallucinates,.will give it few more shots when available later, maybe will already know, thanks again!

Yes, this is 100% correct. The custom domain service simply fetches the asset and checks if the domain name appears in it. It does not interpret or render anything.

Hi, just to know, considering that Caffeine still gives invalid directions and is not able to create a working setup (although can do much more complex things), is it possible that there are currently some limitations on purpose (or limited functionality) - for Caffeine generated apps (or subnets) ?

Oki, this is clear now :clap: :crossed_fingers: :saluting_face:

you can make it work now @plsak, but it may be practical for most user to wait for this plan and have caffeine handle it for them.

Custom Domain Registration

Custom Domain Update