CS used to be about being able to code. If you could write good code, then you had the skill. Increasingly, that’s no longer the case; AI writes boilerplate now. It writes it fast, and most of the time correctly, better than a human would’ve.
So the scarce skill has moved beyond that: knowing what to tell the machine, in enough detail and with enough understanding of the system, that what it builds is what you actually intended.
You architect, AI implements.
That only works if you understand the architecture. If you can’t hold the whole system in your head, knowing what each part does, what each part trusts, and where each part meets, then you can’t specify it, and you can’t check it either. You’ll just accept whatever the AI gives you, because you have no basis to disagree. That’s the difference between using AI and being used by it; you can’t “just do whatever’s best” your way through it.
I learned this the practical way. I applied for a job, advanced to the intro call, spent about a week inside the company’s system, designed a piece of it down to the byte, and then found out the company was probably not real. The work wasn’t wasted, though; it was the clearest proof I’ve had of the thesis: systems thinking is everything.
This is especially true in Web3, and especially on Solana. Stuff here changes by the week. New programs, standards, versions, upgrades, etc. An AI trained months ago doesn’t know about them. It’ll confidently answer with old information, and if you don’t know the field yourself, you’ll ship that staleness. (Sure, things like Solana Developer MCP help, but Solana docs are rarely the entire stack.)
I ran into this directly; Solana’s finality used to take ~13 seconds under the old TowerBFT voting system. Then Alpenglow went live on mainnet on October 21, 2026, and finality dropped to ~150 milliseconds. Unless you asked it very specifically, an AI right now would still tell you 13 seconds, and it would “correct” you for saying otherwise. You have to stay current on your own so you catch the machine when it’s out of date. In a system that moves money irreversibly, a wrong assumption could cause disaster.
The job was Smart Contract Engineer (Rust, Solana), at a company called Saifuro. Their product is an authorization layer for AI-agent payments. (simple idea to state, hard to build well.)
An AI agent wants to make a payment. Before it does, it asks Saifuro: is this payment allowed? Saifuro checks the request against a mandate, which is the rules the organization wrote for that agent: how much, to who, in what category. Then it returns a verdict: allow, deny, or review. The verdict is a signed JWT (a token carrying the decision), signed with Saifuro's private key using ES256 (ECDSA on the P-256 curve). Anyone holding Saifuro's public key can verify that Saifuro really evaluated this exact request and returned this exact answer, without needing a Saifuro account and without trusting the person handing them the token.
On top of that sits an escrow. A buyer and seller make a deal in USDC (on Solana or Base). The buyer sends funds into a contract/address Saifuro provides. The funds release when the deal's condition is met (when a named attestor says the work is done or refund automatically if the deadline passes). Release is the contract's job, not an API call. That’s the part that makes this system interesting, and it’s the part my role would’ve owned.
My part started where the chain starts. Their API team owns the endpoints, the policy check, the keys, the webhooks. My job was the on-chain program: the escrow state machine and the verifier that checks Saifuro's signed verdicts on-chain before anything moves.
But then it fell apart; I looked deeper into the company near the time of the intro call, and found a bunch of red flags.
All linked LinkedIn profiles were broken, and no team member had a lengthy digital footprint.
CryptoJobsList removed their job listings after user reports. Their credentials were cheap or self-asserted: a hundred-dollar Wyoming LLC, an IETF draft, a community-group listing, and complex documentation. Things anyone can do, especially with the help of AI.
Their hiring flow also matched a documented campaign by BlueNoroff (a subgroup of the Lazarus group): Ghosthire (Kaspersky, Oct 2025). The playbook: move applicants to a Calendly call, send the meeting link minutes before, then claim an audio problem and get the target to download a "fix." The download is malware.
Of course, I didn’t prove Saifuro is BlueNoroff. But the playbooks are similar.
Here’s a diagram I made of the escrow creation flow in Canva.
Before any of that, I spent days inside their docs, learning the system properly. Here’s the system, as I understood it:
A verdict token has three parts, separated by dots: header, payload, signature. The header says what kind of token it is and names the signing key: the "kid," short for key ID. The payload carries the claims: who issued it, the decision ID, when it was issued, when it expires (five minutes later), the verdict itself, the mandate it was checked against, the policy version, a one-time nonce for replay protection, and the original payment request.
The signature is 64 bytes: two 32-byte numbers, R and S, produced by signing a SHA-256 hash of the header and payload with Saifuro's private key.
Verification has two parts. The signature: fetch the public key for that kid, check the signature against the hash of the header and payload. If it passes, the payload is authenticated. The claims: is the verdict "allow"? Does the amount, currency, and recipient match your deal? Is the mandate one of yours? Has this nonce been seen before? Is it expired?
A signed verdict proves that the key holder evaluated this specific request and returned this specific answer, at that moment.
Escrow works in states:
Created: the deal exists and waits for funding.
Funded: the buyer's USDC arrived.
Delivered: the condition was attested.
Released: the seller got paid.
Refunded: the deadline passed and the buyer got their money back.
The attestor is whoever the deal chooses to say "done". It could be an agent, or a role like a procurement lead. Note: if the attestor is a role rather than a specific agent, the docs record the role but don’t authenticate it, so any key from that organization could attest.
But why does the program need to verify the verdict at all, if the API already decided? Because submission to the blockchain is permissionless. Anyone can submit a transaction to a program. The program can’t call the API or read its database. It can only check the signature on the slip it was handed. So the on-chain check exists for the same reason the off-chain check exists: trust the key, not the messenger.
While waiting for the call, out of curiosity and because the system was genuinely cool to me, I designed my side of the system and drew it (which you can see above). The technical term would be a system flowchart, or an architecture and transaction-construction diagram: it shows the flow from the API request to the on-chain state, with the exact bytes of the transaction that creates the escrow. I’ll walk through each section below.
It starts with a POST to the escrow endpoint: seller, amount, chain set to Solana, condition, attestor, deadline. The API runs its policy check, with the seller as the recipient and the category set to escrow. Then a verdict is generated. If it’s deny or review, stop: the response is just the decision; no escrow created. An approved review issues a fresh allow, but it still creates no escrow, because creation is attached to the original POST, not to the verdict. Only a direct allow continues.
Programs are passive. Nothing happens on-chain until someone submits a transaction with an initialize instruction. The API deciding "yes" doesn’t create anything; a transaction does.
The backend holds the verdict token; it signs it, and the caller never receives it. The backend is the only party that can build a verdict-carrying transaction; if the buyer built the transaction, no on-chain creation-verdict check would be possible at all.
The backend splits the token on its dots into three chunks. Chunk 3 decodes into the 64-byte signature: R in bytes 0 to 31, S in bytes 32 to 63. This is a part I found interesting.
Solana's secp256r1 precompile, which is the built-in program that verifies P-256 signatures without running normal program code, rejects signatures where S is in the upper half of the curve order. Saifuro's signer doesn’t normalize S, so about half of verdicts carry a high S. The fix is done at build time: if S is greater than half the curve order, replace it with the curve order minus S. High S and low S are both cryptographically equivalent. That all sounds a bit confusing, so let’s visualize it.
P-256’s curve is defined by:
y² = x³ - 3x + b
So we can see the curve cleanly in Desmos, we’ll choose a small value for b, like 5.
Here is:
y² = x³ - 3x + 5
(drawn over the real numbers for visualization; the real curve lives in a finite field)
On the curve, each x value corresponds to two y values. This means the curve is non-injective:
Also note that the curve is symmetric about the x-axis.
Solana’s secp256r1 precompile program rejects S values on the higher portion of the curve (red). It only allows low S values (green):
Since Saifuro’s generated S could be either high or low, the transaction would fail 50% of the time if S is blindly passed into the precompile. We need to make sure that it is always a low S.
Think of a clock. Imagine a line sliced vertically through it. The numbers to the left and right are symmetric across that line. For example, 9 and 3, 7 and 5, 10 and 2. If we are passed 9, we need to convert it to the corresponding symmetric counterpart; which is 3. All we need to do is 12 - 9 = 3. Therefore, the equation to convert any high number to low becomes:
12 - highₙ = lowₙ
Similarly, to convert a high S on the curve to low, we do:
curve order - highₛ = lowₛ
The curve order is an extremely large number. In decimal:
115792089210356248762697446949407573529996955224135760342422259061068512044369
Now, we always provide a low S to the precompile.
Before building the transaction, the backend computes two addresses off-chain. The escrow state account is a PDA; computed from seeds ("escrow" + the escrow ID) and the program ID. The vault is the escrow PDA's associated token account for USDC.
Computing an address is not creating the account. A PDA address can be computed with zero transactions. The account only exists once the program creates it. The API response returns the vault address, and the buyer needs that address to fund the deal.
This is probably the part of the diagram I’m most proud of. It started as a mistake I made which I fixed properly.
My first version had a cron job polling the public key endpoint and auto-updating the on-chain key store whenever a new key appeared. That’s a bad design. Whoever can change what is served at that endpoint (through the domain, DNS, or a CDN in front of it) could add a key and mint verdicts every verifier accepts.
A cron job would turn that endpoint compromise into instant on-chain trust with no human oversight. Rotations are rare and planned. They should be coordinated, not automated.
The replacement is a fixed two-slot key store with a fixed size of 162 bytes. Bytes 0 to 31 hold the update authority: a 3-of-5 multisig address. Slot 0 holds the current key: a 33-byte compressed public key plus a 32-byte key ID. Slot 1 holds the previous key, during overlap only, in the same format.
A single key should never hold the write authority, because that key could swap the stored key and forge approvals for every escrow. That’s a single point of total compromise.
The rotation runs like this:
Days ahead, the team announces it: date, time, new key ID, and the SHA-256 fingerprint of the new compressed public key, and signers confirm they will be online.
The new key is published to the public endpoint a day before it signs.
In the rotation window, with the team online, one multisig transaction copies slot 0 into slot 1 and writes the new key into slot 0. Every signer independently verifies the proposed key.
At cutover, the backend starts signing with the new key and the old key never signs again.
From cutover to 25 hours after it, both keys verify but only the new one signs.
After the 25 hours, a second multisig transaction zeroes slot 1 (the fields are filled with zero bytes; size is still 162 bytes).
The program never hardcodes slot 0 as the pubkey to always verify with. It reads the verdict's key ID, checks slot 0 then slot 1, and verifies with whichever matches.
The transaction has two instructions, in order.
Instruction 0 is the secp256r1 precompile. Its data is 113 bytes: one byte for the count, one reserved byte, then an offsets table of seven two-byte little-endian values, then the 64-byte signature, then the 33-byte compressed public key.
The signature is at offset 16 and lives in this instruction's own data, marked with the value ff ff, which means "self." The public key sits at offset 80, also self. The message (chunk 1, a “.”, chunk 2, raw ASCII) is at offset 115 inside instruction 1, because the precompile can’t read accounts or other instructions' data on its own; it can only be pointed at bytes.
Instruction 1 is the initialize call. It has 9 accounts. At first, my AI only provided 8 and left out the ATA Program (ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL). Another example of the need to know your field well.
The data: 8 bytes of discriminator, 32 bytes each for buyer and seller wallet addresses, 8 bytes for the amount as a little-endian integer (USDC at six decimals), 8 bytes for the deadline as a Unix timestamp, 1 byte for the condition type (attestation), 4 bytes for the attestor length, then the attestor string itself, eg "role:procurement_lead". 1 byte for the arbiter option (none). Then, from byte 115 onward, chunk 1, a dot, and chunk 2, raw ASCII.
Then assembly: fetch a fresh blockhash from an RPC, set the backend as fee payer, and sign with the backend keypair. The transaction confirms in ~150 milliseconds, and the caller gets a response: escrow ID, state "created," and the contract field holding the vault address (which the buyer needs to send USDC to), among others.
After "created" is the rest of the lifecycle: funding, attestation, release or refund.
I haven’t worked in this field for 10 years, nor do I hold a master's in cryptography, Rust, or Solana. But I understood this system in depth.
AI helped me understand it. It did lookups, arithmetic, and boilerplate, and it explained things I hadn’t seen before. But there’s a difference between letting AI explain something to you and saying yes to everything it suggests.
I caught a bunch of the AI’s mistakes and critiqued its explanations. None of those catches came from being smarter than it. They came from understanding the system well enough to check its work. That’s systems thinking.
The position probably wasn’t real, which is something I’m bummed out about. It was a job I really wanted, because the idea genuinely interested me and was something I obsessed over for a week.
But I’m glad I did the work anyway. I read a company's docs end to end. I understood its architecture and its thesis. I designed a piece of its system down to exact bytes, and I can explain every part of it. I learned a lot of new stuff: ECDSA and P-256 signatures, precompiles, key rotation under multisig governance, escrow state machines…
…and of course, how to research a company before giving it your time.
None of that goes away because the job wasn’t real. It goes with me into the next system I build, the next design I review, and the next time an AI hands me an answer I need to check. Coding is no longer the skill; thinking is.