On September 17, 2026 the SEC issued an order allowing Tokenized Securities Venues (TSVs) to trade tokenized US stock on permissioned automated market makers, on public, permissionless chains. The permissioning condition is written in terms of addresses: a pool may admit only "wallet addresses that meet certain credentialing requirements," screened "through, for example, active, offchain management or onchain protocols." Item (f) of the public notice a venue must file asks it to describe "the criteria or standards used to grant a person access" and its procedures for approving "wallet addresses." The venue must also keep books and records for the life of the exemption, which runs to September 17, 2031.
The full reading of the order for issuers and venues is on insumermodel.com. This post is the code. Everything below ran against the live API today; the responses are real.
What the order asks a venue to decide
Strip the regulatory language and the venue has one recurring question: does this address satisfy our criteria right now? The criteria are the venue's to define. The Commission calls them credentialing requirements. In API terms they are conditions: rules about a wallet that are true or false at a moment in time. What the venue needs back is a yes or a no it can act on, signed so it can go in the records, and verifiable later by someone who was not in the room.
That is wallet auth: read wallet state, evaluate the condition, return a signed boolean. Boolean, not balance.
The check, as code
The criterion in this example is the simplest one a venue could adopt: the wallet holds the venue's participant pass, a soulbound ERC-721 on Base that the venue mints to onboarded participants. The pass used here is a real contract on Base mainnet and the first wallet really holds one; the second wallet, a well-known public address, does not.
npm install insumer-verify @noble/post-quantum
// admission-check.mjs: a per-trade participant check for a Tokenized Securities Venue.
// Criterion: the wallet holds the venue's participant pass (a soulbound NFT on Base).
// Output: a signed yes/no the venue can act on and keep for its books and records.
import { verifyAttestation } from "insumer-verify";
const API_KEY = process.env.INSUMER_API_KEY; // free key from POST /v1/keys/create
const PASS_CONTRACT = "0x3E2a408cc6eceba04FF9d04A5B8B05aBa8DD50ce"; // participant pass (ERC-721, soulbound) on Base
export async function admit(wallet) {
// 1. Ask one question about the wallet, against current chain state.
const res = await fetch("https://api.insumermodel.com/v1/attest", {
method: "POST",
headers: { "Content-Type": "application/json", "X-API-Key": API_KEY },
body: JSON.stringify({
wallet,
conditions: [
{ type: "nft_ownership", chainId: 8453, contractAddress: PASS_CONTRACT, label: "holds venue participant pass" },
],
}),
});
const envelope = await res.json();
if (!envelope.ok) throw new Error(`attest failed: ${JSON.stringify(envelope)}`);
// 2. Verify the signature, condition hash, freshness and expiry locally, against the published keys.
const verdict = await verifyAttestation(envelope, {
jwksUrl: "https://insumermodel.com/.well-known/jwks.json",
maxAge: 120, // seconds since attestedAt; a trade-time check, not a nightly list
});
if (!verdict.valid) throw new Error(`attestation did not verify: ${JSON.stringify(verdict.checks)}`);
// 3. Decide, and keep the signed record. Nothing about the wallet's other holdings was returned.
const { attestation, sig, kid, pqKid } = envelope.data;
return {
admitted: attestation.pass,
record: { id: attestation.id, wallet, attestedAt: attestation.attestedAt, expiresAt: attestation.expiresAt, kid, pqKid, sig, pq: verdict.checks.pq?.status },
attestation,
};
}
// Demo: one wallet that holds the pass, one that does not.
for (const wallet of ["0x259e32F4b53130003c8c364f49cE2EA9Cda5B671", "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"]) {
const d = await admit(wallet);
console.log(`${wallet.slice(0, 8)}… admitted=${d.admitted}`);
console.log(JSON.stringify(d.record, null, 2));
}
Output, unedited except for the key:
$ INSUMER_API_KEY=insr_live_… node admission-check.mjs
0x259e32… admitted=true
{
"id": "ATST-426F754343437B6B",
"wallet": "0x259e32F4b53130003c8c364f49cE2EA9Cda5B671",
"attestedAt": "2026-09-17T17:18:44.511Z",
"expiresAt": "2026-09-17T17:48:44.511Z",
"kid": "insumer-attest-v2",
"pqKid": "insumer-attest-pq1",
"sig": "jhrfmAaXnBNk2Qt03/Lb01cReGKAuogt3o2HWsgQd8yXl0HsiY5N8bZEOWvu1ADyLFMuAeTJrAi9qlrJOmnMvg==",
"pq": "verified"
}
0xd8dA6B… admitted=false
{
"id": "ATST-EC1E3190CBFD8686",
"wallet": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
"attestedAt": "2026-09-17T17:18:45.314Z",
"expiresAt": "2026-09-17T17:48:45.314Z",
"kid": "insumer-attest-v2",
"pqKid": "insumer-attest-pq1",
"sig": "tLhVTXETgEuRJMF0JLTyLExhFkwG1829o7kgR2l6CKeGWvM+91ZImJJup8D1MYQrPqes3BzpSuq1cavJiMFDgA==",
"pq": "verified"
}
Three things happened in those forty lines. The venue asked one question about the wallet. It verified the answer itself, against the published key set, without trusting the transport. And it kept a record that names the wallet, the time, the expiry, which key signed, and the signature, without any balance or holding in it.
The same call over curl, and what comes back
curl -s -X POST https://api.insumermodel.com/v1/attest \
-H "Content-Type: application/json" \
-H "X-API-Key: $INSUMER_API_KEY" \
-d '{
"wallet": "0x259e32F4b53130003c8c364f49cE2EA9Cda5B671",
"conditions": [{
"type": "nft_ownership",
"chainId": 8453,
"contractAddress": "0x3E2a408cc6eceba04FF9d04A5B8B05aBa8DD50ce",
"label": "holds venue participant pass"
}]
}'
{
"ok": true,
"data": {
"attestation": {
"id": "ATST-BF082993C4DE5358",
"pass": true,
"results": [
{
"condition": 0,
"label": "holds venue participant pass",
"type": "nft_ownership",
"chainId": 8453,
"met": true,
"evaluatedCondition": {
"type": "nft_ownership",
"chainId": 8453,
"contractAddress": "0x3E2a408cc6eceba04FF9d04A5B8B05aBa8DD50ce",
"operator": "gt",
"threshold": 0
},
"conditionHash": "0xf567d6ba13b811a2fc57443c0928d14d85b0382a1bf350b5ce01e7e7bcaeddaf",
"blockNumber": "0x310e1ae",
"blockTimestamp": "2026-09-17T17:15:43.000Z"
}
],
"passCount": 1,
"failCount": 0,
"attestedAt": "2026-09-17T17:15:44.697Z",
"expiresAt": "2026-09-17T17:45:44.697Z"
},
"sig": "89qlLZ5pVKgQTcpTylZFYrzRshxEP2w9KCkTrOU7Dt5TsTggIwV93I9ladkam7VJyJHM2GtR5g58TTyjDJ6NhA==",
"kid": "insumer-attest-v2",
"pqKid": "insumer-attest-pq1",
"pqSig": "T6nxqwT+hoBU+YyF/qS0unjfgkrkai+gBFSnYxVz… (4412 chars, ML-DSA-65)"
},
"meta": { "version": "1.0", "timestamp": "2026-09-17T17:15:44.865Z", "creditsRemaining": 7, "creditsCharged": 1 }
}
Read the response the way an examiner would. pass is the decision. results[0].met is the per-condition verdict. evaluatedCondition is the exact rule that was applied, echoed back so the caller can confirm nothing was substituted; for an NFT check the operator is gt against 0, meaning "holds at least one." conditionHash commits to that rule. blockNumber and blockTimestamp anchor the answer to a specific block, which is what "against current state" means in practice. attestedAt and expiresAt bound the verdict to a thirty-minute window. sig and kid are the ECDSA signature and the key that produced it. pqSig and pqKid are an additive ML-DSA-65 companion over the same bytes, so the record verifies under a post-quantum key too. And creditsCharged: 1 is the price: one credit, which is four cents a call on the published pay-as-you-go tier, less with volume, and five cents a call on the keyless x402 path.
What is not in the response is the point. There is no balance, no list of other tokens, no transaction history, no name. The venue learned that the criterion was met. The rest of the wallet stayed where it was, on a public ledger, visible to anyone who looks, but not copied into the venue's records.
Verifying locally: what the four checks are
The verifyAttestation call does four things before it returns valid: true. It fetches the JWKS from https://insumermodel.com/.well-known/jwks.json and verifies sig under the key named by kid. It recomputes conditionHash from evaluatedCondition and checks it matches. It checks that the attestation is not older than maxAge seconds, which the script sets to two minutes because this is a trade-time check. And it checks that expiresAt has not passed. With the post-quantum peer installed it also verifies the companion and reports it as a fifth verdict, checks.pq.status, which came back verified above. A companion that fails always fails the whole verification; one that is absent is reported, not refused, unless you set your own cutoff date.
Nothing in that verification calls the API again. The venue can re-run it on a stored record a year from now, or hand the record to an examiner, as long as the key named by kid is still published. Retired kids have stayed in the key set so far; the pre-June v1 kid is still there beside v2. A venue that wants no dependency on that can archive the JWKS document beside its records, because the signature verifies against the key, not the endpoint. That is the difference between a log line and evidence.
A record that outlives the key
Be precise about what is and is not addressed over a five-year retention period. The obvious cryptographic migration risk already is: every attestation carries an ML-DSA-65 companion signature beside the ECDSA one, over the same bytes, under a key published in the same JWKS. That is the NIST post-quantum signature standard, FIPS 204, and it is in the record whether or not the venue looks at it, so the record does not need a migration in year four to stay readable in year five. That is a statement about architecture, not a guarantee about cryptography — implementations acquire flaws, standards move, key custody can fail — but it removes the failure mode a venue would otherwise have to plan around. The other dependency is not cryptographic at all: someone has to still be publishing the key. Archiving the JWKS beside the records answers that, as above.
There is also a form of the record that has no such dependency to begin with. Add proof: "merkle" to the request and the result carries an EIP-1186 Merkle proof of the state the verdict was read from. Be exact about what verifies it, because this is where the claim is usually made too loosely. The venue keeps the proof; what it is checked against is the state root in that block's header. An examiner fetches the header by the blockNumber in the record, hashes the proof nodes up to the root, and either it matches or it does not. No key and no key set, no call to us, and no archive node either — the check needs that block's header, not the 2026 state itself. That distinction is the whole point: headers stay obtainable across the network in a way historical state does not, which is why archive nodes exist for state and nobody runs one for headers. The proof carries the state; the header only has to confirm it.
It does not apply to every criterion, and the pass check above is one it does not apply to. Three subjects can be proven today. A token_balance condition against an ERC-20 proves the balance slot in that token's storage. The same condition with contractAddress: "native" proves the account instead — subject: "account_balance", carrying the account's balance, nonce, and codeHash from its own branch of the state trie rather than a storage slot, because a native balance is a field of the account and not an entry in a mapping. That variant also proves a zero balance, which a storage proof cannot. And an erc7710_delegation condition proves the revocation slot, subject: "delegation_revocation" — positive proof that the principal had not revoked the delegation as of that block. nft_ownership and eas_attestation carry no proof at all. A proof request costs two credits rather than one, and the premium is refunded whenever no proof is delivered, whatever the reason. Proofs are available on 27 of the 33 EVM chains; whether a chain can serve one is a property of the node the request is routed through, not of the chain.
Here is that proof, on the same wallet, from a call made while this section was written. The criterion is the one a venue would naturally pair with a pass — the participant can pay for the gas its own trade will cost. Note that threshold is in display units rather than base units, so "0.001" means 0.001 ETH:
conditions: [
{ type: "token_balance", chainId: 8453, contractAddress: "native", threshold: "0.001",
label: "can pay for gas on Base (native balance >= 0.001)" },
],
proof: "merkle"
Beside the signed verdict, the result carries this:
"proof": {
"available": true,
"type": "merkle",
"subject": "account_balance",
"blockNumber": "0x3110eb7",
"balance": "0x8e17d6df1622e",
"nonce": "0x1",
"codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
"storageHash": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"accountProof": [ "0xf90211a078e7f0e1d425e8be5480...", "... nine nodes in all ..." ]
}
There is no storageProof, no mappingSlot and no storageKey, because this is the account's own branch and not a slot inside a contract. The codeHash and storageHash above are the empty-code and empty-trie hashes every plain wallet has, which is to say they identify this address as an ordinary externally owned account and disclose nothing else about it.
Checking it requires nothing from us. Fetch block 0x3110eb7 from a Base node, take the stateRoot out of the header, and hash the nine nodes up to it:
header stateRoot 0xcd24ec3d2dd2784516500fe602ec8f9f404edf7d2e29be68cb9532fa0233f72a
proof verifies to root YES (9 nodes walked)
balance in proof 2499728645382702 wei
balance claimed in resp 2499728645382702 wei
verdict met true
The same request against an address holding nothing on Base returns met: false and a proof all the same: balance: "0x0", eight nodes, and the address absent from the state trie altogether — its path runs into an empty slot in a branch node, so what the state root commits to is the absence itself. That is a proof that the balance is zero rather than a report that nothing was found, and it is the case the storage path cannot express. Both calls cost two credits, the failing one included, which is the right way round: the premium buys the proof, not the verdict.
So the durability of the record is a choice a venue makes per criterion, not once. A criterion shaped like a balance — the participant holds at least so much of the settlement asset — produces the most durable record available, and the price is narrow. The proof carries one number: the balance of the single asset the condition already named. Not the wallet's other tokens, not its transaction history, not an identity, and nothing that was not already readable by anyone with the address and an RPC endpoint. What changes is that the venue now keeps a copy of that one public number. An account proof on a native balance also carries the account's nonce and codeHash from the same branch — its transaction count and whether it is a contract — equally public, and equally silent about other assets. A criterion shaped like a pass keeps even that out of the file, and stays checkable for as long as the signing key is published. A delegation criterion gives both at once: what its proof establishes is the absence of a revocation, so there is no balance in it to carry.
Stacking criteria
Real venue criteria will have more than one part. A single request accepts up to ten conditions, and pass is true only if every one of them is met, with each condition carrying its own met, hash, and block anchor. A venue whose standard is "holds our pass and has an onboarding attestation from an approved provider" writes both into one call:
conditions: [
{ type: "nft_ownership", chainId: 8453, contractAddress: PASS_CONTRACT, label: "holds venue participant pass" },
{ type: "eas_attestation", chainId: 8453, schemaId: ONBOARDING_SCHEMA, attester: ONBOARDING_PROVIDER,
indexer: ONBOARDING_INDEXER, label: "onboarding attestation from an approved provider" },
]
The eas_attestation type checks an Ethereum Attestation Service attestation issued to the wallet by a named attester under a named schema, resolved through an indexer contract the provider co-lists in. The point for a venue is that the onboarding provider stays the onboarding provider; the venue does not receive the provider's file, only a signed verdict that a valid attestation exists for this address. Other condition types cover token balances with decimal-string thresholds, arbitrary boolean view calls on a contract the venue controls, and, for participants acting through an agent, whether a signed ERC-7710 delegation from a named principal is currently valid and unrevoked. Attestations that include a delegation condition expire in five minutes rather than thirty, because revocation is one transaction away.
Currency, evidence, minimum disclosure
Three properties of the check map onto the order's conditions, and they are the reason to run it per trade rather than per onboarding.
- Currency. The verdict is anchored to a block. A venue that checks at the moment of the trade reflects a pass revoked that morning or an attestation that expired overnight. An allow-list refreshed at onboarding does not. Where a venue keeps an allow-list in its pool contract for gas efficiency, the list can be a cache of this check, with the signed record as the control.
- Evidence. Every decision produces a signed, independently verifiable record, and on a criterion that supports a proof it can carry the chain state it was read from as well, which verifies against the block header with no key at all. The books-and-records condition lasts the life of the exemption, and the Commission has said it will monitor use closely. A folder of these records answers "how did you decide this wallet could trade at 3:14 a.m. on a Sunday" without reconstructing eligibility from raw ledger history.
- Minimum disclosure. The response contains the decision and the rule, not the holdings. A venue that adopts this pattern can answer Item (f) of its public notice with one sentence about what its access check returns and retains. Proof mode narrows that, but only for balance criteria and only by one number: the proof carries the balance of the asset the condition already named, which was public on the ledger before the check and says nothing about the rest of the wallet. The venue ends up holding a copy of one public figure, not a portfolio.
Costs, keys, and what this does not do
A free key comes from POST https://api.insumermodel.com/v1/keys/create with an email, an app name, and tier: "free"; it carries ten verification credits and a hundred requests a day, and the key string is shown once. A caller with no key at all can pay per call over x402: send the request with no credential, take the 402 quote, pay five cents in USDC on Base, Polygon, Arbitrum, or Solana, and retry with the payment header. That path is built for the case where the caller is itself software.
What the check does not do is as important to state as what it does. It does not perform sanctions screening or identity verification; it evaluates whatever conditions the venue defines, which may include an attestation from a provider that does those things. It does not enforce anything on-chain; the venue's pool contract does, and this is the input to it. It does not decide the venue's standard; the order leaves that to the venue, and the check reports whether the standard was met. And it is not an oracle of anything except the specific condition asked, at the specific block named.
The order gives venues until their first public notice to write their criteria down. Whatever those criteria are, they will be evaluated against wallet addresses, one trade at a time, for five years. The code above is one way to make each of those evaluations a signed fact instead of a spreadsheet row. The API reference is at insumermodel.com/developers/api-reference, the verifier is on npm as insumer-verify, and the order is SEC Release 34-106402, File No. 4-927.
Top comments (0)