{"slug": "your-dog-walker-says-they-went-this-makes-the-walk-say-so-too", "title": "Your dog walker says they went. This makes the walk say so too.", "summary": "Proof of Walk, a new app by developer Arqam Waheed, lets dog walkers record their route on a phone, hash it in the browser, and commit a signed attestation to Solana devnet, allowing owners to verify the walk's authenticity. The app distinguishes tampered routes by detecting any change in the GPS trace, as even an 11-meter shift alters the hash completely. The project aims to reduce trust issues in dog walking by making the walk itself testify, though it does not prove a dog was actually walked.", "body_md": "*This is a submission for Weekend Challenge: Dog Days Edition*\n\nTL;DR— A walker records the walk on their phone, the route is hashed in the browser, and a 147-byte attestation signed by the walker's own key lands on Solana devnet. The owner checks the trace they were handed against the chain, and the app tells a tampered route apart from a corrupted file. Move one GPS fix eleven metres and the hash changes completely. That's the whole product.\n\nMy first idea for this challenge died on Friday night. I'd planned a Snowflake Cortex study of shelter-dog adoption data, wrote the queries, and got the same reply from every single AI function: *AI function AI_CLASSIFY is not available for trial accounts.* Not a region problem. Not a role problem. A blanket account-tier restriction that no `GRANT`\n\nfixes. I lost the whole idea in one evening.\n\nSo I went looking for a dog problem I could actually finish in a weekend, and picked the one that is genuinely about trust: paying someone else to walk your dog.\n\nIt's a strange transaction. The person you're checking up on is the same person writing the report. They were there, you weren't, and a better-looking report is worth more to them than an honest one.\n\nThe villain here is **the record being supplied by the party it judges**.\n\n**So I stopped asking the walker to be trustworthy. I made the walk testify.**\n\n**Proof of Walk** records the route on the walker's phone, hashes it in the browser, and commits a compact attestation to Solana devnet as an [SPL Memo](https://www.solana-program.com/docs/memo) signed by the walker's own key. The GPS trace never leaves the device. Only its SHA-256 goes on chain. Later the owner drops the trace file and the transaction signature into a Verify tab, the hash is recomputed locally, and the two are compared.\n\nThat's enough to prove the trace you were shown is the trace that was committed. It also means the walker can't quietly produce a better walk afterwards.\n\nWhat it does not prove: that a dog was on the end of the leash. It proves a device recorded a route. Anyone can leave a phone in a car that drives the block. This narrows the distance between \"trust me\" and \"check it\". It doesn't close it, and I'd rather write that down than let the word \"blockchain\" imply more than the thing earns.\n\n*The gap the project is actually closing. A message is a claim. An attestation is a claim you can test.*\n\n**Live app:** [proof-of-walk-jade.vercel.app](https://proof-of-walk-jade.vercel.app)\n\nNo wallet, no extension, no SOL. On a desktop with no GPS, *Run a simulated walk* produces a synthetic route and flags it `sim: 1`\n\nin the attestation itself, so a demo walk stays distinguishable from a real one on a public chain without anyone taking my word for it.\n\nYou don't have to record anything to check that the thing works. This link opens the verifier with a real devnet signature already filled in:\n\n[Open a committed walk, ready to check](https://proof-of-walk-jade.vercel.app/?tx=2q3GiHfvYBPyh8dNdYMWRTz6aPWQjjhAdJAm12g4UQ1CYAzZcNZNVAgDHJE4FmLUNVN1Qw7fzVgmgAUoSAHFhYST)\n\nPress *Load the example walk*, then *Verify*. It says **Match**.\n\nThen press *Now edit one coordinate*. That moves a single GPS fix by 0.0001 degrees, about 11 metres, the smallest change the format can express. Verify again and it says **No match**, with both hashes drawn as bars so the difference is something you see rather than something I assert.\n\nBeing able to falsify the claim in two clicks is a better argument than any paragraph I could write here.\n\n*Eleven metres, drawn. Nobody reads a 64-character hex string, so the app renders each hash as 32 bars, one per byte.*\n\n**Walkthrough video:**\n\nRunning it locally is three commands:\n\n```\ngit clone https://github.com/ArqamWaheed/proofofwalk\nnpm install\nnpm run dev\n```\n\n**Your dog walker says they went. This makes the walk say so too.**\n\nA walker opens this on their phone, taps start, and walks the dog. The route is\nrecorded on-device, hashed in the browser, and a compact attestation is committed\nto the Solana devnet as an [SPL Memo](https://www.solana-program.com/docs/memo)\nsigned by the walker's own key.\n\nThe GPS trace never leaves the device. Only the hash goes on-chain. That is enough for the owner to verify the trace they were shown is the trace that was committed — and it means the walker cannot quietly produce a better walk after the fact.\n\n** Try it** — no wallet, no extension, no SOL. On a desktop with no GPS\nuse\n\n`sim: 1`\n\non chain so they\nstay distinguishable from real ones.Interesting files, if you only open a few:\n\n`src/lib/trace.ts`\n\n: the canonical form. The bytes that get hashed, and the only file the offline verifier shares with the website.`api/relay.ts`\n\n: the fee relayer, and the four checks that stop it being a faucet.`src/lib/schema.ts`\n\n: hand-written validation for everything the walker supplies.`src/lib/freshness.ts`\n\n: the replay gap, and how a block timestamp closes it.`scripts/verify.ts`\n\n: the same verification with no website involved.The one contract that can never change is the serialisation, so it's deliberately boring:\n\n``` js\nexport function canonicalise(trace: Trace): string {\n  const rows = trace.fixes.map((f) => `${f.t},${f.lat.toFixed(5)},${f.lon.toFixed(5)}`);\n  return [`v${trace.version}`, trace.dog, ...rows].join(\"\\n\");\n}\n```\n\n`JSON.stringify`\n\nis banned from that path. Key order in an object literal is an engine-level implementation detail, and betting hash reproducibility on it would break verification silently for records already committed. Coordinates are pinned to five decimal places, about 1.1 metres, for the same reason. GPS reports far more precision than it actually has, and unpinned floats don't reproduce across devices.\n\nA test pins the output of that function to an exact string. If it ever fails, the hashed bytes have changed and every attestation already on chain has stopped verifying. That's a design decision to make, never a fixture to update.\n\nReact and Vite on the front end, `@solana/web3.js`\n\nfor transaction construction, two Vercel serverless functions. The stack isn't the interesting part. The decisions are.\n\n**Why a chain at all, honestly.** Most of this could be a database row. One part could not. The walker is the adversarial party and they're the one supplying the record, so a row in a server they can reach is a row they can edit. An SPL Memo signed by their key, in a block with a timestamp neither of us controls, is not. That's the whole bet. This project doesn't tokenise anything, mint anything, or ask anyone to hold a coin.\n\n**The walker signs, the server pays.** A dog walker has no wallet, no SOL, and no reason to learn what either is. But the record is worthless if it's the *server* asserting the walk happened. So the two roles split: the Memo instruction lists the walker's key as a signer, while a relayer key pays the fee. The Memo program logs verified signers, so what lands on chain is \"this key asserted this walk\", funded by a key the walker never holds. A memo transaction costs about 0.000005 SOL, so one devnet SOL covers roughly 200,000 walks.\n\n**A relayer that signs anything is a faucet with extra steps.** That endpoint spends money on request, so it verifies what it's signing instead of trusting the client: exactly one instruction, addressed to the Memo program, within the program's own 566-byte limit, carrying at least one signer that isn't the relayer.\n\n``` js\nconst walkerSigned = ix.keys.some((k) => k.isSigner && !k.pubkey.equals(relayer));\nif (!walkerSigned) throw new Error(\"no walker signature on the memo instruction\");\n```\n\nDrop that last check and anyone can drain it.\n\n*The trust boundary, drawn. The relayer pays for the transaction and vouches for nothing in it.*\n\n**Telling \"this is broken\" apart from \"this is a lie\".** This is the bug I shipped and then had to fix, and it's the one I'd want reviewed. The verifier originally did `JSON.parse(file) as Trace`\n\n. A TypeScript cast is a promise to the compiler, not a check at runtime, so malformed input sailed straight through. I tested four broken traces and **three of them hashed successfully**: a fix missing its timestamp, an empty `fixes`\n\narray, and a `NaN`\n\nlatitude. Each produced a stable, confident, meaningless hash.\n\nWhich the UI then reported as: *the route you were given is not the route that was committed.*\n\nThat's an accusation of fraud caused by a corrupted file. For an app whose entire job is adjudicating trust between two people, that isn't a rough edge. It's the app doing the opposite of its job. The fix was hand-written type guards at the boundary, and the verifier now reports five outcomes: a match, a genuine mismatch, an unreadable trace, a transaction that isn't a walk attestation, and no such transaction. Exactly one of those is an accusation, and only that one is coloured red.\n\n*The bug that mattered most. A corrupted file and a doctored file are not the same accusation, and the type system happily conflated them.*\n\n**A hash has no opinion about when.** I found this late. The SHA-256 proves which route was recorded and says nothing about when, which leaves a gap an adversarial walker can stand in: record one genuinely excellent walk, keep the trace, commit it again next Tuesday. The hash matches perfectly, because it really is that walk. Just not this week's. The block timestamp is the one clock in the system the walker doesn't control, so the verifier compares it against the trace's own end time and reports the gap. A long delay has innocent explanations, so it states the number and lets the owner decide. \"This took a while\" and \"you are lying to me\" are not the same sentence.\n\n*The gap I found late. The hash proves which route was recorded, never when, so the chain's own clock has to say the rest.*\n\n**The log is what a chain is actually for.** One attestation answers a narrow question. The one an owner really has is broader: what has this walker done for my dog? So the Log tab reads a key's whole committed record back off devnet with `getSignaturesForAddress`\n\n, validates every memo rather than assuming it's ours, and requires that the key actually *signed* the memo instruction. Otherwise anyone could pad someone else's log by naming their key as a read-only account. A walker can decline to record a walk and the log shows a gap. What they cannot do is delete one they already committed, or invent one they didn't. Absence is ambiguous. Presence is not.\n\n*A record, not a receipt. This is also the moment the project stopped being about a walker and started being about a dog.*\n\n**No map tiles, on purpose.** The route is drawn from the fixes themselves as a bare polyline on graph paper. A basemap would render the walker's actual neighbourhood on screen, and their home is usually at one end of the line.\n\n**Or don't trust this app either.** An app that asks you to stop taking your walker's word for it has no business asking you to take its word instead. So the same verification runs offline in about a hundred lines. `npm run verify -- <signature> docs/example-walk.json`\n\nprints the hash from the chain, the hash of your file, and who signed the memo. Exit 0 for a match, 2 for a mismatch, 3 for a file it couldn't read, because those are three different things. It shares exactly one file with the website: the definition of which bytes get hashed. Two independent implementations reaching the same hash is worth more than either one insisting it's right.\n\n*No website involved. Checking a claim shouldn't require believing a second claim about the page doing the checking.*\n\nThings I refused to do:\n\n`JSON.stringify`\n\nanywhere on the hashed path, however convenient it looks.**Two honest concessions.** First, the walker's key lives in `localStorage`\n\nand is deliberately disposable, so a walker who clears their browser starts a new log and the old one is orphaned. Real key custody was out of scope for a weekend. Second, this runs on devnet, and devnet faucet airdrops are IP rate-limited hard enough that I funded the relayer by hand rather than programmatically.\n\n`as Trace`\n\nis a promise to the compiler that costs nothing at runtime, and it let three malformed files produce confident hashes. The type system agreed with me right up until the app accused someone of fraud.`ERR_REQUIRE_ESM`\n\n, because `@solana/web3.js`\n\npulls in `rpc-websockets`\n\n, whose CommonJS build requires an ESM-only `uuid@14`\n\n. It was unreproducible locally, because Node 22.12+ permits `require()`\n\nof an ES module and my machine runs 22.19. I wrote a regression test for it, watched it pass against a deliberately broken dependency tree, and deleted it. A test that cannot fail is worse than no test.**Best Use of Solana.**\n\nSolana is load-bearing here, not decorative. The attestation is an SPL Memo, and the walker-signs/relayer-pays split is the reason the whole thing works for someone with no wallet and no crypto literacy. The Log tab reads the chain as well as writing to it, and the block timestamp is what closes the replay gap, because it's the one clock in the system the walker can't touch. If a future change made the relayer the signer, the attestation would stop meaning anything and the project would lose its reason to be on a chain at all.", "url": "https://wpnews.pro/news/your-dog-walker-says-they-went-this-makes-the-walk-say-so-too", "canonical_source": "https://dev.to/arqamwd/your-dog-walker-says-they-went-this-makes-the-walk-say-so-too-29ai", "published_at": "2026-08-16 19:19:36+00:00", "updated_at": "2026-08-16 19:42:09.843321+00:00", "lang": "en", "topics": ["developer-tools", "ai-products"], "entities": ["Arqam Waheed", "Proof of Walk", "Solana", "SPL Memo", "Vercel", "GitHub"], "alternates": {"html": "https://wpnews.pro/news/your-dog-walker-says-they-went-this-makes-the-walk-say-so-too", "markdown": "https://wpnews.pro/news/your-dog-walker-says-they-went-this-makes-the-walk-say-so-too.md", "text": "https://wpnews.pro/news/your-dog-walker-says-they-went-this-makes-the-walk-say-so-too.txt", "jsonld": "https://wpnews.pro/news/your-dog-walker-says-they-went-this-makes-the-walk-say-so-too.jsonld"}}