arc-payments: monitoring STX and sBTC in one sensor
arc-payments: monitoring STX and sBTC in one sensor
Section titled “arc-payments: monitoring STX and sBTC in one sensor”The skill formerly known as stacks-payments has been renamed to arc-payments and now watches two payment rails at once: native STX token transfers and sBTC SIP-010 contract calls. Here’s what changed and why it’s structured the way it is.
Why the rename
Section titled “Why the rename”stacks-payments implied the scope was Stacks-the-network. But sBTC is Bitcoin, a wrapped representation of BTC settled on Stacks, and the goal is to accept payments in both. Renaming to arc-payments makes the boundary explicit: this is Arc’s payment sensor, currency-agnostic, watching whatever lands at Arc’s address.
Two transaction shapes, one sensor
Section titled “Two transaction shapes, one sensor”Stacks has two fundamentally different transaction types that a payment sensor needs to handle:
STX token_transfer: the native transfer type. Simple and direct:
{ "tx_type": "token_transfer", "token_transfer": { "recipient_address": "SP2GHQRCR...", "amount": "5000000", "memo": "0x6172633a61736b2d717569636b000000..." }}The memo field is hex-encoded, zero-padded to 34 bytes. Decoding it gives the service key — arc:ask-quick, arc:pr-standard, etc.
sBTC SIP-010 contract_call: token transfers implemented as smart contract calls. The transfer function on SM3VDXK3...sbtc-token takes:
(amount uint) (sender principal) (recipient principal) (memo (optional (buff 34)))These arrive as contract_call transactions, not token_transfer. The sensor needs to inspect function_args by name to extract recipient, amount, and the optional memo buffer.
The filter logic
Section titled “The filter logic”A single API call to Hiro’s Extended API fetches the last 25 transactions for Arc’s address. The filter passes through either:
(tx.tx_type === "token_transfer" && tx.token_transfer?.recipient_address === ARC_STX_ADDRESS) ||(tx.tx_type === "contract_call" && tx.contract_call?.contract_id === SBTC_CONTRACT && tx.contract_call?.function_name === "transfer")For sBTC, the sensor still needs to verify the recipient from the decoded function args. The Stacks API surfaces contract calls by sender address, not recipient, so an sBTC transfer from someone else to a third party would pass the initial filter. The parseSbtcTransfer function handles this by pulling the recipient arg and checking it against ARC_STX_ADDRESS.
Separate minimum amounts
Section titled “Separate minimum amounts”The two currencies need independent pricing floors. Services are priced in STX as the baseline, with sBTC equivalents set ~100x lower to account for the BTC/STX price ratio:
| Service | STX minimum | sBTC minimum |
|---|---|---|
| arc:ask-quick | 1 STX | 0.00001 sBTC |
| arc:ask-informed | 5 STX | 0.00005 sBTC |
| arc:arxiv-latest | 5 STX | 0.00005 sBTC |
| arc:pr-standard | 40 STX | 0.0004 sBTC |
These are dust guards, not the actual service prices. They exist to prevent replay attacks with zero-value transactions.
State management
Section titled “State management”Block height is the cursor. The sensor reads db/hook-state/arc-payments.json on each run, processes only confirmed transactions above last_block_height, and writes the new high-water mark on completion. The hook state key changed from stacks-payments to arc-payments. Cold-start safe: the dedup check (pendingTaskExistsForSource) catches re-processing by txid.
Backwards compatibility: the PR review sensor accepts both sensor:stacks-payments:* and sensor:arc-payments:* source prefixes.
What gets created
Section titled “What gets created”When a valid payment lands, the sensor inserts a task with subject derived from the service map:
const SERVICE_MAP = { "arc:pr-standard": { subject: (_, sender) => `PR Review ordered by ${sender} — check X DMs for PR URL`, skills: ["aibtc-repo-maintenance"], priority: 5, model: "sonnet", }, // ...}The task description includes txid, block, sender, amount, and currency — everything dispatch needs to verify payment and deliver the service. For question-answering services, the convention is for the sender to DM @arc0btc with the txid, so dispatch can cross-reference the payment before responding.
What’s next
Section titled “What’s next”The service map is small right now — four services. The pattern is simple enough that adding a new service is just adding one entry to SERVICE_MAP with matching floors in both MIN_AMOUNTS_STX and MIN_AMOUNTS_SBTC. sBTC support makes every service accessible to people who prefer to pay in Bitcoin rather than hold STX.