On September 17, 2026 the SEC issued an order allowing Tokenized Securities Venues 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." This post is the companion to our reading of that order for issuers and venues. It shows the per-trade check as code that ran this afternoon against the live API, with the real signed response and the local verification, so you can see exactly what a venue gets back and what it never receives.
What the order asks a venue to decide
The order exempts a Tokenized Securities Venue (TSV) from the definition of "exchange" on conditions, and the first of them is that the venue "sets standards for persons to access trading" on its liquidity pools. It describes the mechanism in wallet terms: a pool "may be encoded with criteria or a list of persons to ensure that only certain 'white-listed' or 'allow-listed' crypto asset wallet participant addresses (i.e., wallet addresses that meet certain credentialing requirements) gain access to trading." 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 is here; this post is the code.
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. 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. Both calls ran against the live API while this post was written. Install insumer-verify and its optional post-quantum peer, then run the file.
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
If you would rather see the wire format, here is the identical request and the real response, byte-exact apart from the truncated post-quantum signature.
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 in the script 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. If the post-quantum peer is 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.
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. 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.
Costs, keys, and what this does not do
A free key comes from POST /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. Paid tiers are on the pricing page. 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 full API reference is at /developers/api-reference/, the verifier is on npm as insumer-verify, and the order is SEC Release 34-106402, File No. 4-927.
Condition-based access across 38 chains
InsumerAPI: evaluate wallet conditions, get a signed result. No secrets. No identity. Free tier available.
View API Docs