Skip to main content

Cross-Chain Credentials: Multi-Chain Verification

GhostSpeak credentials issued on Solana can be verified on Ethereum, Base, and Polygon via Crossmint’s cross-chain infrastructure. This guide shows how to enable cross-chain sync and verify credentials across multiple blockchains.
Why Cross-Chain? Buyers and platforms on Ethereum/Base/Polygon can verify your Solana-issued credentials without bridging tokens or switching wallets.

How Cross-Chain Works

Architecture Overview

┌─────────────────────────────────────────────────────────────┐
│  SOLANA (Source of Truth)                                   │
│  • Credential issued on-chain                               │
│  • Signed by GhostSpeak DID                                 │
│  • Stored in compressed NFT                                 │
│  • Cost: ~0.0002 SOL (~$0.03)                              │
└─────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────┐
│  CROSSMINT SYNC SERVICE                                     │
│  • Indexes Solana credential                                │
│  • Creates cross-chain verification endpoint                │
│  • No token bridging required                               │
│  • Cost: Free (included in credential issuance)            │
└─────────────────────────────────────────────────────────────┘


┌──────────────┬──────────────┬──────────────┬──────────────┐
│  ETHEREUM    │    BASE      │   POLYGON    │    ARBITRUM  │
│  Verify via  │  Verify via  │  Verify via  │  Verify via  │
│  API call    │  API call    │  API call    │  API call    │
└──────────────┴──────────────┴──────────────┴──────────────┘
Key Points:
  • No bridging: Credentials stay on Solana
  • Universal verification: Any EVM chain can verify
  • Cost-effective: Issue once on Solana, verify everywhere
  • W3C compliant: Same credential format across all chains

Enabling Cross-Chain Sync

Step 1: Get Crossmint API Key

1

Create Crossmint Account

Sign up at crossmint.com
2

Create Project

Navigate to Dashboard → Create New Project → Select “Verifiable Credentials”
3

Get API Key

Copy your API key from Settings → API Keys → Server-Side Key
4

Configure Environment

.env
CROSSMINT_API_KEY=sk_live_...
CROSSMINT_ENVIRONMENT=production # or 'staging'
Devnet Testing: Use Crossmint’s staging environment for Solana devnet:
CROSSMINT_API_KEY=sk_staging_...
CROSSMINT_ENVIRONMENT=staging

Step 2: Issue Cross-Chain Credential

issue-cross-chain.ts
import { GhostSpeakClient } from '@ghostspeak/sdk'
import { createKeyPairSignerFromBytes } from '@solana/signers'
import fs from 'fs'

async function issueCrossChainCredential() {
  console.log('=== Cross-Chain Credential Issuance ===\n')

  // Load wallet and agent
  const walletPath = process.env.WALLET_PATH?.replace('~', process.env.HOME || '')
  const keypairBytes = JSON.parse(fs.readFileSync(walletPath, 'utf-8'))
  const wallet = await createKeyPairSignerFromBytes(new Uint8Array(keypairBytes))

  const agentKeypairBytes = JSON.parse(
    fs.readFileSync('./agent-keypair.json', 'utf-8')
  )
  const agentSigner = await createKeyPairSignerFromBytes(
    new Uint8Array(agentKeypairBytes)
  )

  const client = new GhostSpeakClient({
    cluster: 'devnet',
    commitment: 'confirmed',
  })

  console.log('Issuing credential with cross-chain sync enabled...\n')

  // Issue credential with Crossmint sync
  const result = await client.credentials.issueAgentIdentityCredential({
    agentId: agentSigner.address,
    owner: wallet.address,
    name: 'Code Review AI',
    capabilities: ['code-review', 'security-audit'],
    model: 'gpt-4-turbo',
    serviceEndpoint: 'https://myagent.example.com',
    frameworkOrigin: 'langchain',
    x402Enabled: true,
    registeredAt: Math.floor(Date.now() / 1000),
    verifiedAt: Math.floor(Date.now() / 1000),

    // Enable cross-chain sync
    syncToCrossmint: true,
    crossmintConfig: {
      apiKey: process.env.CROSSMINT_API_KEY!,
      environment: (process.env.CROSSMINT_ENVIRONMENT as 'production' | 'staging') || 'staging',
    },

    // Optional: specify target chains
    targetChains: ['ethereum', 'base', 'polygon'], // default: all supported chains
  })

  console.log('✅ Credential issued successfully!\n')

  console.log('📜 Solana Credential:')
  console.log('  ID:', result.solanaCredential.credentialId)
  console.log('  Issuer:', result.solanaCredential.issuer)
  console.log('  Subject:', result.solanaCredential.subject)

  if (result.crossmintSync) {
    console.log('\n🌉 Cross-Chain Sync:')
    console.log('  Crossmint Credential ID:', result.crossmintSync.credentialId)
    console.log('  Verification URL:', result.crossmintSync.url)
    console.log('  Available on:', result.crossmintSync.chains.join(', '))

    console.log('\n  Chain-Specific Endpoints:')
    result.crossmintSync.chains.forEach((chain: string) => {
      console.log(`    ${chain}: ${result.crossmintSync.endpoints[chain]}`)
    })

    // Save credential data
    fs.writeFileSync(
      './cross-chain-credential.json',
      JSON.stringify({
        solana: result.solanaCredential,
        crossmint: result.crossmintSync,
      }, null, 2)
    )

    console.log('\n📁 Credential data saved to: ./cross-chain-credential.json')
  }

  return result
}

issueCrossChainCredential().catch(console.error)
Expected Output:
=== Cross-Chain Credential Issuance ===

Issuing credential with cross-chain sync enabled...

✅ Credential issued successfully!

📜 Solana Credential:
  ID: cred_4Hc7mK2pXvF9...
  Issuer: did:sol:devnet:GpvFxus2eecFKcqa2bhxXeRjpstPeCEJNX216TQCcNC9
  Subject: did:sol:devnet:4Hc7mK2pXvF9...

🌉 Cross-Chain Sync:
  Crossmint Credential ID: cm_cred_xyz123
  Verification URL: https://verify.crossmint.com/credentials/cm_cred_xyz123
  Available on: ethereum, base, polygon

  Chain-Specific Endpoints:
    ethereum: https://verify.crossmint.com/ethereum/cm_cred_xyz123
    base: https://verify.crossmint.com/base/cm_cred_xyz123
    polygon: https://verify.crossmint.com/polygon/cm_cred_xyz123

📁 Credential data saved to: ./cross-chain-credential.json

Verifying Cross-Chain Credentials

Ethereum Verification (ethers.js)

verify-ethereum.ts
import { ethers } from 'ethers'

async function verifyOnEthereum() {
  // Load credential data
  const credentialData = JSON.parse(
    fs.readFileSync('./cross-chain-credential.json', 'utf-8')
  )

  const crossmintCredentialId = credentialData.crossmint.credentialId

  console.log('=== Ethereum Verification ===\n')
  console.log('Verifying credential:', crossmintCredentialId)

  // Call Crossmint verification API
  const response = await fetch(
    `https://verify.crossmint.com/api/v1/credentials/${crossmintCredentialId}/verify`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-Chain': 'ethereum', // Specify target chain
      },
      body: JSON.stringify({
        credentialId: crossmintCredentialId,
      }),
    }
  )

  const verification = await response.json()

  console.log('\n✅ Verification Result:\n')
  console.log('Valid:', verification.valid)
  console.log('Issuer:', verification.issuer)
  console.log('Subject:', verification.subject)
  console.log('Issuance Date:', new Date(verification.issuanceDate).toISOString())

  if (verification.valid) {
    console.log('\n📋 Credential Claims:')
    console.log('  Name:', verification.credentialSubject.name)
    console.log('  Capabilities:', verification.credentialSubject.capabilities.join(', '))
    console.log('  Model:', verification.credentialSubject.model)
    console.log('  Ghost Score:', verification.credentialSubject.ghostScore)
    console.log('  Tier:', verification.credentialSubject.tier)
  }

  return verification
}

verifyOnEthereum().catch(console.error)

Base Verification (wagmi)

verify-base.tsx
import { useAccount, useContractRead } from 'wagmi'
import { base } from 'wagmi/chains'

function VerifyCredentialOnBase({ credentialId }: { credentialId: string }) {
  const { address } = useAccount()

  // Call Crossmint verification endpoint
  const { data, isLoading, error } = useContractRead({
    address: '0x...', // Crossmint verification contract on Base
    abi: CROSSMINT_VERIFIER_ABI,
    functionName: 'verifyCredential',
    args: [credentialId],
    chainId: base.id,
  })

  if (isLoading) return <div>Verifying credential...</div>

  if (error) return <div>Verification failed: {error.message}</div>

  return (
    <div>
      <h3>Credential Verification (Base)</h3>
      <p>Valid: {data.valid ? '✅' : '❌'}</p>
      <p>Issuer: {data.issuer}</p>
      <p>Subject: {data.subject}</p>
      <p>Ghost Score: {data.credentialSubject.ghostScore}</p>
    </div>
  )
}

Polygon Verification (REST API)

verify-polygon.ts
async function verifyOnPolygon(credentialId: string) {
  console.log('=== Polygon Verification ===\n')

  // Use Crossmint REST API
  const response = await fetch(
    `https://verify.crossmint.com/polygon/api/v1/credentials/${credentialId}`,
    {
      headers: {
        'X-API-Key': process.env.CROSSMINT_API_KEY!,
      },
    }
  )

  const credential = await response.json()

  console.log('Credential Details:')
  console.log('  Valid:', credential.valid)
  console.log('  Revoked:', credential.revoked)
  console.log('  Issuer DID:', credential.issuer)
  console.log('  Subject DID:', credential.subject)

  // Check revocation status on Polygon
  const revocationStatus = await fetch(
    `https://verify.crossmint.com/polygon/api/v1/revocation/${credentialId}`,
    {
      headers: {
        'X-API-Key': process.env.CROSSMINT_API_KEY!,
      },
    }
  )

  const revocation = await revocationStatus.json()

  console.log('\nRevocation Status:')
  console.log('  Revoked:', revocation.revoked)
  if (revocation.revoked) {
    console.log('  Reason:', revocation.reason)
    console.log('  Revoked At:', new Date(revocation.revokedAt).toISOString())
  }

  return { credential, revocation }
}

Smart Contract Integration (EVM)

Solidity Contract Example

GhostScoreVerifier.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@crossmint/contracts/interfaces/ICredentialVerifier.sol";

contract GhostScoreVerifier {
    ICredentialVerifier public verifier;

    constructor(address _verifierAddress) {
        verifier = ICredentialVerifier(_verifierAddress);
    }

    // Verify agent has minimum Ghost Score
    function requireMinimumScore(
        string memory credentialId,
        uint256 minimumScore
    ) public view returns (bool) {
        // Verify credential is valid
        (bool valid, , , bytes memory credentialData) = verifier.verifyCredential(credentialId);

        require(valid, "Invalid credential");

        // Decode credential subject
        CredentialSubject memory subject = abi.decode(credentialData, (CredentialSubject));

        // Check Ghost Score
        require(subject.ghostScore >= minimumScore, "Ghost Score too low");

        return true;
    }

    // Verify agent has specific capability
    function requireCapability(
        string memory credentialId,
        string memory capability
    ) public view returns (bool) {
        (bool valid, , , bytes memory credentialData) = verifier.verifyCredential(credentialId);

        require(valid, "Invalid credential");

        CredentialSubject memory subject = abi.decode(credentialData, (CredentialSubject));

        // Check capabilities array
        for (uint i = 0; i < subject.capabilities.length; i++) {
            if (keccak256(bytes(subject.capabilities[i])) == keccak256(bytes(capability))) {
                return true;
            }
        }

        revert("Capability not found");
    }

    struct CredentialSubject {
        string name;
        string[] capabilities;
        string model;
        uint256 ghostScore;
        string tier;
    }
}

Using the Contract

use-verifier-contract.ts
import { ethers } from 'ethers'

async function verifyAgentOnChain() {
  const provider = new ethers.JsonRpcProvider('https://mainnet.base.org')
  const contract = new ethers.Contract(
    '0x...', // GhostScoreVerifier contract address
    GHOST_SCORE_VERIFIER_ABI,
    provider
  )

  const credentialId = 'cm_cred_xyz123'
  const minimumScore = 750 // Gold tier

  try {
    const result = await contract.requireMinimumScore(credentialId, minimumScore)

    console.log('✅ Agent verified on-chain!')
    console.log('  Credential ID:', credentialId)
    console.log('  Minimum Score Required:', minimumScore)
    console.log('  Result:', result)
  } catch (error) {
    console.error('❌ Verification failed:', error.message)
  }
}

Cross-Chain Use Cases

Use Case 1: Base Marketplace with Solana Agents

Scenario: Marketplace on Base wants to verify Solana-based AI agents.
base-marketplace.ts
// Marketplace contract on Base
async function listAgentOnBase(agentAddress: string, credentialId: string) {
  // 1. Verify credential via Crossmint
  const verification = await fetch(
    `https://verify.crossmint.com/base/api/v1/credentials/${credentialId}`,
    {
      headers: { 'X-API-Key': process.env.CROSSMINT_API_KEY! },
    }
  )

  const credential = await verification.json()

  if (!credential.valid) {
    throw new Error('Invalid credential')
  }

  // 2. Check Ghost Score tier
  if (credential.credentialSubject.tier !== 'Gold' && credential.credentialSubject.tier !== 'Platinum') {
    throw new Error('Agent must be Gold or Platinum tier')
  }

  // 3. List agent on Base marketplace
  const marketplace = new ethers.Contract(
    BASE_MARKETPLACE_ADDRESS,
    MARKETPLACE_ABI,
    signer
  )

  const tx = await marketplace.listAgent({
    agentAddress,
    credentialId,
    ghostScore: credential.credentialSubject.ghostScore,
    tier: credential.credentialSubject.tier,
    capabilities: credential.credentialSubject.capabilities,
  })

  console.log('Agent listed on Base marketplace:', tx.hash)
}

Use Case 2: Ethereum Gated Access

Scenario: Premium Ethereum dApp requires Gold tier agents.
PremiumService.sol
contract PremiumService {
    GhostScoreVerifier public verifier;

    modifier onlyGoldTier(string memory credentialId) {
        require(verifier.requireMinimumScore(credentialId, 750), "Gold tier required");
        _;
    }

    function accessPremiumFeature(string memory credentialId)
        public
        onlyGoldTier(credentialId)
    {
        // Premium feature logic
    }
}

Use Case 3: Polygon Reputation Aggregator

Scenario: Aggregate Ghost Scores from multiple agents on Polygon.
polygon-aggregator.ts
async function aggregateGhostScores(credentialIds: string[]) {
  const scores = await Promise.all(
    credentialIds.map(async (id) => {
      const response = await fetch(
        `https://verify.crossmint.com/polygon/api/v1/credentials/${id}`,
        {
          headers: { 'X-API-Key': process.env.CROSSMINT_API_KEY! },
        }
      )
      const credential = await response.json()
      return {
        id,
        score: credential.credentialSubject.ghostScore,
        tier: credential.credentialSubject.tier,
      }
    })
  )

  const totalScore = scores.reduce((sum, s) => sum + s.score, 0)
  const averageScore = totalScore / scores.length

  console.log('Aggregated Ghost Scores:')
  console.log('  Total agents:', scores.length)
  console.log('  Average score:', averageScore)
  console.log('  Gold+ agents:', scores.filter(s => s.score >= 750).length)

  return { scores, averageScore }
}

Cost Comparison

OperationSolanaEthereumBasePolygon
Credential Issuance~$0.03N/AN/AN/A
Cross-Chain SyncFreeFreeFreeFree
Verification (API)FreeFreeFreeFree
On-Chain Verification~$0.001~$5-20~$0.10~$0.05
Cost Optimization: Issue on Solana (cheapest), verify via API on other chains (free). Only use on-chain verification when you need trustless execution.

Troubleshooting

Error: “Crossmint API returned 401 Unauthorized”Solution: Check API key configuration:
# Verify API key is set
echo $CROSSMINT_API_KEY

# Should start with sk_live_ (production) or sk_staging_ (devnet)
Ensure environment matches:
  • Solana devnet → Crossmint staging
  • Solana mainnet → Crossmint production
Error: “Credential ID not found on Ethereum”Solution: Wait for indexing (can take 30-60 seconds):
await new Promise(resolve => setTimeout(resolve, 60000))

// Then retry verification
const verification = await verifyOnEthereum(credentialId)
Error: “Signature verification failed”Solution: Check credential hasn’t been revoked:
const revocationStatus = await client.credentials.checkRevocation(credentialId)

if (revocationStatus.revoked) {
  console.error('Credential has been revoked:', revocationStatus.reason)
}

Next Steps


Pro Tip: Enable cross-chain sync by default. It costs nothing extra and makes your credentials universally verifiable across all major blockchains.