Oracle3
Open-source trading engine and MCP server for prediction markets. Fee-aware no-arbitrage trading across related contracts, live on Kalshi, Polymarket and Solana or on paper.
An open-source Stellar wallet you pay with by tapping an NFC card. No unlock, no app, no confirmation.
FinanceTypeScript · RustGitHub @rylsherdamz-rgbOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Noir Wallet — contactless tap-to-pay powered by the x402 protocol on Stellar. Tap an NFC/RFID device and the linked wallet is debited instantly via Soroban smart contracts — no app, no unlock, no confirmation.
Taken from the repository README
Loading the full README. The saved excerpt is shown below.
Contactless payments powered by Stellar Tap any NFC tag, RFID sticker, or wearable to pay — no app opens, no confirmation, just go. View Pitch Deck · Architecture · Stellar Dev Guide · Deployment Evidence · PR #10 — Week 1 · Run LocallyRead the full README ↗
Contactless payments powered by Stellar Tap any NFC tag, RFID sticker, or wearable to pay — no app opens, no confirmation, just go.
Docs · View Pitch Deck · Architecture · Stellar Dev Guide · Deployment Evidence · PR #10 — Week 1 · Run Locally
[!IMPORTANT] Noir Wallet is alpha software on Stellar Testnet. The x402 tap-to-pay flow, escrow settlement, and constrained agent authorization are implemented and deployed. Mainnet contracts are deployed but production settlement and PDAX fiat bridge integration are not yet live. Treat testnet deployments as ephemeral — each redeploy is a clean slate with new contract IDs.
▶ Demo — tap-to-pay with the x402 protocol, per-device agent wallets, and the full mobile app. Original X post
▶ A 95-second, Google-style product walkthrough — narrated voiceover, the x402 tap-to-pay flow, per-device agent wallets, and the full mobile app.
Noir Wallet implements the x402 protocol — a zero-interaction payment flow where the wallet is debited immediately upon hardware tap. The user never unlocks their phone, opens an app, or confirms a transaction. The payment terminal reads the device UID, resolves the linked Stellar wallet, and executes the transfer in under 2 seconds.
Built for high-throughput environments: transit turnstiles, campus canteens, event gates, and retail checkout.
A world where:
device_registry for hardware-to-wallet mappingwallet.require_auth()| Layer | Technology |
|---|---|
| Blockchain | Stellar (testnet / mainnet) |
| Smart Contracts | Soroban (Rust) — 3 contracts |
| Frontend | React Native, Expo 57, TypeScript |
| Styling | NativeWind, Tailwind CSS |
| State | Zustand |
| NFC | react-native-nfc-manager |
| Wallet SDK | @stellar/stellar-sdk v16 |
| Testing | Vitest |
| Contract Dev | soroban-cli, Rust nightly |
📐 Full architecture reference — rendered diagrams for the system overview, device provisioning, x402 tap-to-pay, and revocation/fund-recovery flows, plus the authorization model and trust boundaries.
RFID / NFC Tag
|
v
Mobile App / POS Terminal
|
├── NFC read → SHA-256 hash device UID
├── x402 Agent (per-device signing key)
└── Soroban Contracts
|
├── device_registry ──> Map device hash → wallet
├── agent_registry ──> Authorize agent per device
└── payment_escrow ──> Pre-funded escrow → instant authorize
|
v
Stellar Network
|
┌──────┴──────┐
v v
Merchant claim PDAX Fiat Bridge
(batch settle) (PHP Cash-out)
| Contract | Description | Auth | Location |
|---|---|---|---|
| device_registry | Maps hardware device hashes to Stellar wallet addresses | wallet.require_auth() | backend/contracts/device_registry/ |
| agent_registry | On-chain agent authorization per device — allows wallet owner to authorize a signing key for tap-to-pay | wallet.require_auth() | backend/contracts/agent_registry/ |
| payment_escrow | Escrow-based settlement — wallet pre-funds, agent authorizes payments instantly, merchants claim in batch | agent auth (via agent_registry) | backend/contracts/payment_escrow/ |
Testnet Contract IDs (redeployed 2026-10-07 — constrained delegated auth + sweep-on-revoke, #[contractevent] migration):
| Contract | ID | WASM SHA-256 | Explorer |
|---|---|---|---|
| device_registry | CB4DPMGOA374JIB2ZVD4AHW5GJKQUYNOFGRYNOJ75EFH2KCJSMHOWIRA | a252a407…67bea2 | view |
| agent_registry | CBTDMJVCFQDIVWZBKAKZ2FQ3UEJNAAUFYCTJ3E4ON2MYXPLPSIXQ25JZ | b0da4885…c7a22b | view |
| payment_escrow | CA5S4S7QGHJHJWVJBYL3CXZXZTNGKXMNZVDAEQNN7NUXFP4D7BYK7HIX | 3861809c…2941ed | view |
About redeployment: each
./scripts/redeploy-contracts.shrun deploys fresh contract instances, so every deploy produces new contract IDs — a redeploy is a clean slate, not an in-place upgrade (prior on-chain state on the old instances is abandoned). The script writes the authoritative IDs, WASM hashes, and init args to a timestamped evidence file indeploy-evidence/, and when run withUPDATE_ENV=1it also patchesfrontend/.env. The IDs above are from the latest run (deploy-evidence/deploy-testnet-20261007T050558Z.md); on-chain evidence screenshots are indocs/Evidence/.
Full deployment evidence (admin, init args, WASM hashes):
deploy-evidence/.
Mainnet Contract IDs (deployed 2026-07-18):
| Contract | ID | Explorer |
|---|---|---|
| device_registry | CDSURGM4LYYRZ6U4RKBRUQPA7SGLAJZ5XXS65ACYCIU6QOO2NEEA2S45 | view |
| agent_registry | CDE2Q22BNKHLRNHUXHNL4TYRN5YPCOCO2XD53MAVYG7K2C4KZ2T6NI5T | view |
| payment_escrow | CCEIB2BLMHK7N7OX23HJ3RCMJBP2NLBMMBGUA6SB5IWJJ6JEWH6ZZPK3 | view |
| Method | Args | Auth | Description |
|---|---|---|---|
initialize | admin: Address | admin | Set contract admin (called once) |
register | wallet: Address, device_hash: BytesN<32>, agent: Address | wallet | Register a device to a wallet with its agent |
revoke | wallet: Address, device_hash: BytesN<32> | wallet | Remove the device entry (owner only), freeing the hash for re-registration |
get_device | device_hash: BytesN<32> | — | Returns DeviceInfo { owner, agent, status, created_at } |
wallet_device_count | wallet: Address | — | How many devices a wallet has registered |
wallet_device_at | wallet: Address, index: u32 | — | Enumerate a wallet's device hashes |
is_authorized | device_hash: BytesN<32>, agent: Address | — | true if the device exists, is active, and maps to this agent |
get_agent | device_hash: BytesN<32> | — | Agent for a device |
get_owner | device_hash: BytesN<32> | — | Owner for a device |
Errors: AlreadyInitialized=1, DeviceNotFound=2, NotOwner=3, AlreadyRegistered=4.
| Method | Args | Auth | Description |
|---|---|---|---|
initialize | admin: Address | admin | Set contract admin (called once) |
register_agent | wallet: Address, device_hash: BytesN<32>, agent: Address, max_amount: i128, asset: Address, expires_at: u64 | wallet | Authorize an agent under a constrained policy |
revoke_agent | wallet: Address, device_hash: BytesN<32> | wallet | Revoke agent access |
get_agent | device_hash: BytesN<32> | — | Look up agent by device hash |
get_policy | device_hash: BytesN<32> | — | Returns AgentPolicy { agent, max_amount, asset, expires_at } |
is_auth | device_hash: BytesN<32>, agent: Address | — | Agent matches and authorization has not expired |
check_payment | device_hash: BytesN<32>, agent: Address, asset: Address, amount: i128 | — | Full policy predicate: agent + asset + amount > 0 + within cap + not expired |
max_amount = 0 means uncapped; expires_at = 0 means never expires (otherwise an absolute ledger timestamp).
Errors: AlreadyInitialized=1, AgentNotFound=2, AlreadyRegistered=3, InvalidPolicy=4.
| Method | Args | Auth | Description |
|---|---|---|---|
initialize | admin: Address, agent_registry_id: Address, device_registry_id: Address | admin | Set admin + link both registries |
fund_escrow | token: Address, wallet: Address, device_hash: BytesN<32>, amount: i128 | wallet | Deposit into escrow for a device |
authorize | agent: Address, device_hash: BytesN<32>, merchant: Address, asset: Address, amount: i128, nonce: u64 | agent | Instant payment auth, enforced against the agent policy with nonce replay protection |
claim | token: Address, merchant: Address | merchant | Batch-claim all pending payments |
defund_escrow | token: Address, device_hash: BytesN<32>, amount: i128 | device owner | Wallet reclaims unused escrow funds |
sweep_on_revoke | token: Address, device_hash: BytesN<32>, agent: Address | device owner | After the agent is revoked, return the device's entire escrow balance to the owner |
balance_of | device_hash: BytesN<32> | — | Check escrow balance |
pending_balance | merchant: Address | — | Check unclaimed payments |
Errors: AlreadyInitialized=1, InsufficientBalance=2, AgentNotAuthorized=3, NotDeviceOwner=4, NothingToClaim=5, DuplicateAuth=6, AgentStillActive=7.
Noir_Wallet/
├── frontend/ # React Native Expo application
│ └── src/
│ ├── screens/ # Dashboard, POS, Device Provisioning, Agents, etc.
│ ├── components/ # BalanceCard, NumericKeypad, ReadyToTap, etc.
│ ├── services/ # Stellar SDK, NFC, API client
│ ├── store/ # Zustand state management
│ ├── hooks/ # useNfc, useProfile, custom hooks
│ ├── lib/ # soroban.ts helpers, x402 auth
│ ├── domain/ # x402 agent logic
│ ├── tasks/ # Background tasks (received-payment alerts)
│ ├── constants/ # Theme (black/gold), network config
│ └── types/ # TypeScript type definitions
├── backend/ # Soroban smart contracts (Rust)
│ └── contracts/
│ ├── device_registry/
│ ├── agent_registry/
│ └── payment_escrow/
├── assets/ # Demo video, poster, branding
├── images/ # Screenshots & diagrams
├── promo/ # Promotional materials
├── models/ # ML / design models
├── old/ # Archived code (backward compat)
├── unused/ # Parked, not-in-use components
│ └── pdax-backend/ # PDAX fiat-bridge API server (parked — not wired into the app)
└── contextimages/ # Design inspiration and moodboards
device_registry.register() on Soroban, and polls for confirmationagent_registry.register_agent() to authorize the agent for this devicepayment_escrow (one-time, per device)payment_escrow.authorize(merchant, amount)payment_escrow.claim() in batch — one transaction claims all pending paymentsdefund_escrow() unused balance at any timeNoir Wallet notifies you when money arrives at your main wallet or at any card's agent wallet, including when you are not in the app. The app needs no server for this: it reads your accounts' payments from Horizon directly.
| App state | How it checks | Delay |
|---|---|---|
| Open | Balances update live on screen; on opening, anything that arrived while you were away is notified | — |
| In the background (still running) | Polls Horizon every 30 seconds | ≤ 30 s |
| Closed or swiped away | expo-background-task job (Android WorkManager) | ~15 min or more; Android decides when it runs |
What notifies: incoming XLM or asset payments, and create_account funding (e.g. Friendbot), from someone else. Each notification reads like "Received 50.00 XLM — From GSTR…XXX3 to Black card".
What doesn't: payments you send, failed transactions, and moves between your own accounts (topping up a card from your main wallet, or a card's balance returning to it). Old payments never flood in: the first check for an account only records where it is, and each payment notifies once.
How it works
index.ts (the app entry) imports src/tasks/paymentTask.ts before the router, which defines the background task so Android can run it while the app is closed.app/_layout.tsx creates the Android Payments notification channel, asks for notification permission, takes a first snapshot of each account, and registers the background task.checkIncomingPayments in src/services/paymentNotifier.ts) fetches the latest Horizon payments for the main wallet and each card agent, keeps the ones newer than the last seen paging_token (stored per network and account in AsyncStorage), and shows one notification per payment. The filtering and wording live in src/lib/incomingPayments.ts and are unit-tested in tests/17-incoming-payments.test.ts.Limits to know
npx expo prebuild --platform android, then npx expo run:android) after pulling this; a JS reload is not enough.Test it: open the app once so it snapshots your accounts and asks for permission. Leave it in the background and send XLM to your wallet address from another account (e.g. Stellar Laboratory on Testnet); a notification appears within about 30 seconds. To test the closed-app path without waiting, call BackgroundTask.triggerTaskWorkerForTestingAsync() from a dev build.
[!TIP] Full guides: Installation · Deployment · Walkthrough · Contract reference · API reference · NFC hardware · Testing. The summary below is the short version.
npm install -g expo-cli)# Clone and install
git clone https://github.com/rylsherdamz-rgb/Noir_Wallet.git
cd Noir_Wallet/frontend
npm install
Copy .env.example to .env and configure:
| Variable | Description | Current Value |
|---|---|---|
EXPO_PUBLIC_DEVICE_REGISTRY_CONTRACT | Soroban device registry contract ID | CB4DPMGOA374JIB2ZVD4AHW5GJKQUYNOFGRYNOJ75EFH2KCJSMHOWIRA |
EXPO_PUBLIC_AGENT_REGISTRY_CONTRACT | Soroban agent registry contract ID | CBTDMJVCFQDIVWZBKAKZ2FQ3UEJNAAUFYCTJ3E4ON2MYXPLPSIXQ25JZ |
EXPO_PUBLIC_PAYMENT_ESCROW_CONTRACT | Soroban payment escrow contract ID | CA5S4S7QGHJHJWVJBYL3CXZXZTNGKXMNZVDAEQNN7NUXFP4D7BYK7HIX |
EXPO_PUBLIC_STELLAR_MASTER_KEY_ID | Stellar master key ID | (configure per deployment) |
EXPO_PUBLIC_CHANNEL_SECRET_KEY | Fee channel secret | (configure per deployment) |
EXPO_PUBLIC_ISSUER_ADDRESS | Asset issuer address | (configure per deployment) |
EXPO_PUBLIC_PDAX_API_KEY | PDAX sandbox/prod API key | (optional) |
EXPO_PUBLIC_TERMINAL_ID | x402 Terminal ID | (optional) |
cd frontend
npx expo start
NFC needs native code, so Expo Go and emulators can't link or tap cards. On an NFC Android phone, build a development client with npx expo run:android (see Installation).
cd backend
# Build all WASM
cargo build --release --target wasm32v1-none -p device-registry -p agent-registry -p payment-escrow
# Run checks (host target)
cargo check -p device-registry -p agent-registry -p payment-escrow
# Deploy to testnet (one per contract)
soroban contract deploy \
--wasm target/wasm32v1-none/release/device_registry.wasm \
--network testnet
soroban contract deploy \
--wasm target/wasm32v1-none/release/agent_registry.wasm \
--network testnet
soroban contract deploy \
--wasm target/wasm32v1-none/release/payment_escrow.wasm \
--network testnet
# Initialize contracts
soroban contract invoke --id <DEVICE_REGISTRY_ID> --network testnet -- \
initialize --admin <ADMIN_ADDR>
soroban contract invoke --id <AGENT_REGISTRY_ID> --network testnet -- \
initialize --admin <ADMIN_ADDR>
soroban contract invoke --id <ESCROW_ID> --network testnet -- \
initialize --admin <ADMIN_ADDR> --agent_registry_id <AGENT_REGISTRY_ID> --device_registry_id <DEVICE_REGISTRY_ID>
Build agent-registry and device-registry before payment-escrow (it imports their WASM). Or run all of this with ./scripts/redeploy-contracts.sh; see the Deployment guide.
cd frontend
npm test
Contract, SDK and live Testnet tests: see Testing.
We collected structured feedback from 60 testers who registered devices, funded escrows, and completed payment flows on Stellar testnet. The results shaped our roadmap — here's what mattered most to them.
Top requested improvements:
| Rank | Request | Why it mattered |
|---|---|---|
| 1 | Onboarding tutorial | Step-by-step guide for device provisioning and first payment |
| 2 | QR code scanner | Scan wallet addresses on receive and send screens |
| 3 | Dark mode toggle | Theme switch for late-night use |
| 4 | Transaction search & filters | Find payments by date, amount, or counterparty |
| 5 | Push notifications | Payment confirmations and escrow alerts |
| 6 | Better empty states | Sample data or guided prompts on first launch |
| 7 | Haptic feedback | Vibration on successful NFC tap |
| 8 | Custom NFC device names | Rename tags instead of showing hashes |
| 9 | Multi-currency display | Show USD/PHP equivalent alongside XLM |
| 10 | Spending limits per device | Configurable per-NFC-card budgets |
💡 All ten improvements are shortlisted for the v1 roadmap — starting with the onboarding tutorial and NFC device naming.
Contributions are welcome. Before you start, please read:
Use the GitHub issue templates to report bugs or request features, and the PR template when opening pull requests.
Promotional assets and a browser-based pubmat editor live in promo/pubmat/.
| Role | Name |
|---|---|
| Fullstack Developer | Richie Christian De Guzman |
| Backend Developer | Johnrick Rabara |
| UI/UX Designer | Jefferson Tuparan |
MIT