Search projects, hackathons, sponsors and tech

    ← All projects

    Securtxn

    SecurTxn is control layer that verifies identity before money moves

    Demo

    About

    THE PROBLEM About $226 billion a year moves in B2B stablecoin payments, and none of it can be recalled. When a payout goes to the wrong recipient, there is no chargeback and no reversal. What exists instead is a recovery industry: Binance processes roughly 4,000 deposit-retrieval requests a month, and Kraken charges $200 to $500 per attempt. Recovering a misdirected payment is a support ticket. Not sending one is a control. Traditional banking solved this in 2020 with Confirmed Payee, which checks the name on the account before a transfer goes out. Stablecoin rails have no equivalent. A wallet address carries no identity, so nothing on the payment path can answer the only question that matters: does this address actually belong to the vendor on the invoice? https://www.forbes.com/councils/forbestechcouncil/2026/08/06/why-stablecoins-still-have-a-payment-recovery-problem/ SecurTxn is that missing control layer. WHAT IT DOES SecurTxn sits in front of a stablecoin payout and refuses to let it proceed until three independent questions are answered: 1. Is the payee who they claim to be? 2. Is a live human approving this specific payment? 3. Is this address allowed to hold the asset at all? Each is answered by a different system, and none of them can be satisfied by the person trying to get paid. HOW IT WORKS Onboarding. A vendor connects a wallet and completes a real DigiLocker consent flow, which returns their Aadhaar and PAN documents with a verifiable XML signature. They then sign an EIP-712 wallet control proof and an identity binding that ties the DigiLocker subject to that specific address. Only the combination makes a wallet CONFIRMED. Identity documents are encrypted at rest and reduced to HMAC digests for matching. On-chain KYC (Hedera). The platform issues a verifiable credential for the confirmed wallet and calls grantKyc on a tokenised bond deployed with the Hedera Asset Tokenization Studio (security 0.0.10444329, internal KYC activated). This is the control that cannot be talked around: the token itself rejects transfers to addresses that were never granted. An unverified wallet does not fail our validation, it reverts on chain. Consent before matching. When a payment is raised, the payee is notified and must accept it with an EIP-712 signature before any identity check runs. This ordering is deliberate. Running a match without consent would turn the product into a lookup service that tells a stranger whether a given address belongs to a given PAN. The match (Chainlink CRE). The identity comparison runs inside a Confidential Workflow on a Chainlink DON, in a TEE. The enclave never sees a name or a PAN. It receives HMAC digests, compares them as opaque bytes, and returns one of four verdicts: MATCHED, IDENTITY_MISMATCH, WALLET_NOT_ON_FILE or VENDOR_NOT_FOUND. There is a single reason code for any identity failure, because reporting which field mismatched would hand a prober a second bit of information. The enclave has no crypto library and does no name scoring, so there is nothing in it to leak. Proof of personhood (World ID). The sender passes a World ID Selfie Check on every payment, unconditionally. The payee is challenged only when the risk engine asks for one: a first payment from this sender, an amount above their tier ceiling, a recently rotated wallet, a stale check, or a recent identity mismatch. Signals are role-qualified as sender:<paymentId> and payee:<paymentId>, so one human acting as both sides of a payment cannot satisfy both checks with the same proof. Settlement. The payout moves as an HTS token through an HTLC on Hedera testnet. The payee claims it by revealing the preimage from their own key, which makes the reveal itself the receipt. Nobody can produce it on their behalf, and an unclaimed payment returns to the sender after the timelock. Evidence. Every decision writes an evidence record. Records are batched into a Merkle tree and anchored to a Hedera Consensus Service topic (0.0.10454706). Each anchor reads itself back from a mirror node after writing, so an anchor is verified against the public record rather than merely submitted. Any single record can produce a standalone inclusion proof, and altering a record after anchoring makes its proof stop matching. Exception desk. A refusal is not a dead end. Blocked payments open a case with the reason code, the evidence trail, and a path to resolution, because the finance team still has an invoice to pay. WHAT IS REAL This is deployed and runs end to end at https://www.securtxn.com with no mocks in the path. Two people have completed the full flow with their own DigiLocker identities, their own World ID enrolments, a verdict from the deployed TEE workflow, and a real token transfer on Hedera testnet. One thing stated plainly: the asset moved in the demo is stUSDC, a test stablecoin we issued on Hedera testnet, not Circle's USDC. The mechanics are identical, an HTS fungible token moving between KYC gated accounts through the escrow contract, but it should not be described as transferring USDC. TECH STACK ========== Hedera: Asset Tokenization Studio bond with internal KYC, HTS token, HTLC escrow contract, Hedera Consensus Service for evidence anchoring, mirror node verification. Chainlink CRE: Confidential Workflow with a TEE handler, deployed to a DON, secrets held in the Vault DON, digest-only matching. World ID: Selfie Check proof of personhood, role-qualified signals, nullifier continuity enforced against enrolment. Identity: DigiLocker via the Sandbox aggregator, Aadhaar and PAN with XML signature verification. Application: TypeScript, Fastify, Prisma, PostgreSQL, Next.js, viem, EIP-712 throughout.

    How it's made

    ARCHITECTURE A pnpm monorepo. Fastify and Prisma over PostgreSQL (Supabase) for the API, Next.js for the web app, viem for all chain work, Zod at every boundary, and a shared package holding the EIP-712 struct definitions. Chain, compliance, anchoring and identity are interfaces with a real and a mock implementation selected by env var, so the test suite never depends on a public network being up while the deployed instance always uses the real one. API and web run on Render, the database on Supabase, the workflow on a Chainlink DON, the contracts on Hedera testnet. CHAINLINK CRE, AND THE CONSTRAINT THAT SHAPED THE DESIGN The identity match runs in a Confidential Workflow on a TEE. The obvious design was "encrypt the vendor record, decrypt inside the enclave, compare it." That is impossible. The CRE SDK bans node:crypto outright, and the declared globals are only TextEncoder/Decoder, Buffer, atob/btoa, URL and console. No crypto.subtle, nothing. There is no primitive inside the enclave to decrypt with. So we inverted it. At onboarding we store HMAC(pan, pepper) and HMAC(normalise(legalName), pepper). At match time the API hashes the sender's typed values the same way, and the enclave compares digest against digest. No crypto is needed inside because the hashing already happened outside. The pepper never leaves our server and the enclave never sees an identity. It is not that the enclave is trusted with PII, it is that there is no PII in it to leak. The cost is real and worth stating. Exact digests remove the near-miss band, so a typo is now a flat mismatch rather than a 0.95 similarity score. We think that is a net gain, because fuzzy name matching is precisely how a lookalike payee slips through. Normalisation does the work instead: "MERIDIAN COMPONENTS PRIVATE LIMITED" and "Meridian Components Pvt Ltd" reduce to the same digest. The second CRE problem is that the HTTP trigger is fire and forget. workflows.execute returns an execution id and nothing else, and there is no API to read a workflow's return value, so a workflow cannot be called as a synchronous "is this payee verified?" RPC. The workflow POSTs its verdict to a callback endpoint and the caller awaits the database row. That suits the confidential design rather than fighting it, because the verdict is exactly the one value that is supposed to leave the enclave. The callback has to be idempotent. In DON mode every observing node runs the handler and every node POSTs the same verdict, so five callbacks arrive for one logical answer. In TEE mode exactly one does. Both are correct, so later callbacks are accepted and ignored. One trap worth naming: a CRE execution reaching SUCCESS only means the handler did not throw. A matcher that always answered MATCHED would look identical across every case. Our end to end script therefore asserts the verdict for five different inputs rather than the execution status. The enclave authenticates to the vendor lookup with a JWT held in the Vault DON and released directly into the attested enclave. HEDERA Three separate uses, each doing something the others cannot. The tokenised bond is deployed with the Asset Tokenization Studio with internalKycActivated set to true. That single flag is the integration: false and the token enforces nothing, leaving grantKyc with nothing to gate. The issuing account holds the KYC_MANAGER, INTERNAL_KYC_MANAGER and SSI_MANAGER roles and is registered as an issuer, without which grantKyc reverts with AccountIsNotIssuer. The demonstration that matters is the failure: a transfer to a wallet that never went through grantKyc reverts on chain, not in our validation. The cash leg is an HTS fungible token moving through an HTLC contract. HTS tokens answer the ERC-20 interface at their long zero address, 0x followed by the token number in hex padded to 40 characters, which is how viem can talk to them at all. Accounts are created with maxAutoAssociations set to -1, because a receiver with no free association slots rejects an incoming token transfer. The balance card surfaces that field for exactly this reason. Evidence anchoring uses the Consensus Service. Records are batched into a Merkle tree and the root is submitted to a topic. Anchoring then reads itself back from a mirror node and compares the published root against the recorded one. Mirror nodes lag consensus by a few seconds, so the read back retries briefly, but a root mismatch throws immediately and is never retried away: a disagreement between our row and the public record is the one failure this whole mechanism exists to surface. WORLD ID Selfie Check, with signals role qualified as sender:<paymentId> and payee:<paymentId>. This is not cosmetic. The nullifier uniqueness index is (nullifier, action, signal), so a bare payment id means one human acting as both sides of a payment collides with their own earlier proof. The asymmetry is deliberate too: the sender is checked on every payment unconditionally, while the payee is challenged only on risk, because the payee is the counterparty and friction there costs you the payment. HACKY AND NOTABLE The EIP-712 structs live in one shared package imported by the signer, the verifier and the tests. A second copy that merely reorders two fields produces signatures that fail with no useful error, because the recovered address is simply a different address. Next.js rewrites default to afterFiles, which runs only when no page matched. We had pages at /vendors and /payments, so the API proxy was silently shadowed: calls returned 200 with Next's HTML, JSON.parse produced a string, and the first visible symptom was an undefined id three steps later. beforeFiles fixes it. The API's token minting endpoint is registered only when NODE_ENV is not production, deliberately, because an approver token is what stands between a payment and a human agreeing to it. That broke the entire deployed app, since the web console minted its own tokens. The fix is a Next route handler signing HS256 tokens server side for two roles only, agent and approver, never issuer or bridge. The JWT is hand rolled with node:crypto rather than adding a library, because the payload is two claims and a new dependency means a lockfile change on a deployment that installs with a frozen lockfile. grantKyc is idempotent. The chain outlives our database and the wallet key lives in the browser's localStorage, so a reset database plus a reconnected wallet asks the chain for a state it is already in. The contract answers with a bare custom error revert that surfaces as an unhelpful 500. A grant is a state, not an event. Every foreign key is onDelete: Restrict, so evidence is never silently orphaned. Even wiping the demo database has to be done in strict reverse dependency order. DigiLocker returned a 403 whose body we were deliberately withholding from logs to avoid leaking PII. The actual message was "Insufficient credits." We now surface the message and transaction id only, never the data. Separately, a user who grants Aadhaar consent but not PAN produced an opaque 400 much later, so there is now a pre-flight scope check naming the document that is missing.