Most RWA applications end at issuance: create a token, display its address, and call it a product. The harder question begins afterward: what does this asset need, who is economically compatible, and can the proposed relationship actually transact?
AssetCupid is an answer to that question. It is a matching workflow built around live Cleanverse-issued A-Tokens on Monad Testnet. The app separates economic compatibility from transaction eligibility, then uses Cleanverse as a real trust gate rather than a decorative badge.
The product model
![The product model(https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/nzb7990bypojr1p9vo5r.png)
This model avoids an easy but misleading shortcut: treating every promising mandate as a Cleanverse-verified counterparty. AssetCupid’s mandate records are curated marketplace content. They only become a transaction candidate when a real representative wallet is supplied and checked.
Live assets first, not demo fixtures
The catalog route queries the configured Cleanverse account for issued Monad assets at runtime. The asset address then becomes the anchor for the profile, matching exercise, and eligibility checks.
// Route handlers call server-only services; the browser does not see credentials.
const assets = await listIssuedMonadAssets();
return Response.json({ ok: true, data: assets });
The practical effect is important: the demo evolves as the Cleanverse sandbox account evolves. It does not rely on a hardcoded collection of fictional RWAs.
Keeping Cleanverse credentials off the client
Cleanverse credentials live in server environment variables. The browser calls same-origin Next.js routes; those routes use a shared server client that adds the API ID, creates a request ID, encrypts sensitive endpoints, and normalizes failures.
private async request<T>(path: string, init: RequestInit): Promise<T> {
this.assertConfigured();
const response = await fetch(`${this.url}${path}`, {
...init,
headers: { "api-id": this.id!, "X-Request-ID": randomUUID(), ...init.headers },
cache: "no-store",
});
if (!response.ok) throw new CleanverseError(String(response.status));
const envelope = await response.json() as CleanverseEnvelope<T>;
if (envelope.code !== "0000") throw new CleanverseError(envelope.code, envelope.message);
return envelope.data;
}
That final envelope.code check matters. API integrations often assume an HTTP 200 means success; Cleanverse can return a business failure inside a successful HTTP response. AssetCupid tests that behavior directly.
Why matching is deterministic
The term “AI matching” can hide a major product risk: an opaque system whose rationale changes from run to run. In early-stage financial workflows, explainability has more value than theatrical intelligence.
AssetCupid scores six factors:
| Factor | Weight | Example |
|---|---|---|
| Goal | 25 | Capital mandate for a capital-seeking asset |
| Asset type | 20 | Infrastructure-compatible mandate |
| Amount | 20 | Target fits the mandate range |
| Geography | 15 | Market overlap |
| Risk | 10 | Compatible risk preferences |
| Horizon | 10 | Compatible duration |
if (factors.goal === 0) {
blockers.push("This mandate does not address one of the asset’s selected goals.");
}
if (factors.geography === 0) {
blockers.push("The selected markets do not overlap.");
}
return matches
.filter(({ match }) => match.blockers.length === 0 && match.economicScore >= 55)
.sort((left, right) => right.match.economicScore - left.match.economicScore);
The system can therefore say why a match ranked highly—and why another did not qualify. An LLM could later rewrite these explanations in a more conversational style, but it should not decide the score.
Trust is an execution gate
A strong 97% economic match is not a permission to transact. After a match is requested, the product checks both wallets with Cleanverse verify_apass for the selected A-Token. Results are normalized into product states such as eligible, missing A-Pass, restricted, unavailable, and asset-not-found.
sequenceDiagram
participant Owner
participant App as AssetCupid
participant CV as Cleanverse
participant Partner
Owner->>App: Request economic match
Partner->>App: Provide representative wallet
App->>CV: verify_apass(owner, A-Token)
CV-->>App: normalized eligibility result
App->>CV: verify_apass(partner, A-Token)
CV-->>App: normalized eligibility result
alt both eligible
App-->>Owner: Deal room unlocked
else either blocked
App-->>Owner: Blocked state + reason
end
The application stores verification evidence and timestamps with the match. It does not reveal identity data. It also does not make a CCP claim without a real Validator/CCP pool connected to the asset.
Monad wallet integration
The UI uses the standard EIP-1193 browser provider. It attempts to switch to Monad Testnet and, if needed, asks the wallet to add the network.
await window.ethereum.request({
method: "wallet_switchEthereumChain",
params: [{ chainId: "0x279f" }],
});
This is intentionally separate from Cleanverse verification. Wallet connection establishes the actor for the UI; Cleanverse determines whether that actor may transact with the selected asset.
Persistence and the production caveat
For hackathon development, AssetCupid uses Node’s built-in SQLite support. It persists asset profiles, generated matches, state changes, verification evidence, and deal rooms locally. On Vercel, the database path falls back to /tmp so the app can run, but that filesystem is ephemeral.
The next production milestone is to make the repository asynchronous and back it with Neon Postgres or libSQL. The product must not imply that its current serverless match records are durable.
What I would build next
- A hosted database adapter and migrations.
- Verified opportunity onboarding for actual capital, insurance, and buyer organizations.
- Real CCP pool evidence when an asset has an associated registered pool.
- Deal-room documents, roles, approvals, and audit events.
- An LLM copy layer constrained to existing deterministic facts.
The key principle will remain unchanged: live asset facts come from Cleanverse and Monad; matching is an explainable marketplace model; transaction eligibility is checked, not assumed.
Code & more: https://www.dailybuild.xyz/project/217-asset-cupid
Top comments (0)