Container

Make any Satoshi carry value, give meaning to Bitcoin Networking.

Container

Created At

ETHGlobal New Delhi

Project Description

Container is a tiny, end-to-end tool that treats Bitcoin’s smallest unit—the satoshi—as a portable container for verifiable metadata and value pointers. Instead of inventing another token or side asset, Container lets you attach a compact, signed manifest to the spend of a specific satoshi, then rely on any standard Bitcoin wallet for transfers. Ownership is simply control of the UTXO; provenance is the satoshi’s transaction history.

A Container manifest is a small JSON describing what the satoshi “carries”: an IPFS CID for media or documents, a pointer to a BRC-20 issuance/context, an identifier for a real-world asset (RWA), a license or agreement hash, plus creator and timestamp fields. The manifest is hashed (SHA-256) and anchored onchain. If it’s tiny, Container embeds a compact pointer directly in OP_RETURN; if larger, it uploads to IPFS and anchors the CID. Either way, the chain contains an immutable breadcrumb that anyone can resolve.

The flow is deliberately minimal. With a single Python CLI command, you: (1) choose the exact UTXO you want to spend (so you control which satoshis move), (2) load a manifest JSON from a file, (3) set a fee rate you’re comfortable with, and (4) broadcast. Container returns a TXID, a human-friendly Container ID, and, if used, the IPFS CID. A companion inspect command decodes any Container transaction, showing the OP_RETURN payload and fetching the manifest via a public gateway.

Under the hood, Container uses Bitcoin Core RPC (PSBT, funding, signing, broadcast) and optional IPFS (local daemon or Pinata). It’s intentionally small and hackable: a single script you can read, fork, and demo live. Because transfers are just normal Bitcoin spends, wallet compatibility is immediate; no special plugin is required to move a Container.

Use cases include cryptographically signed receipts, art provenance, ticketing, micro-licenses, escrow proofs, RWA references, or simply “shipping” a bundle of links and hashes with a unit of money. For Ethereum builders, the manifest’s CID can be consumed by EVM apps, letting smart contracts reference Bitcoin-anchored artifacts without bridging value.

Constraints & ethics: Standard relay policy disallows 1-sat outputs as dust, so Container defaults to a dust-safe output (e.g., 546 sats). Exact ordinal-index selection of an individual sat within a UTXO is out of scope for this MVP; Container prioritizes clear UTXO control and explicit IDs. Manifests are public; keep sensitive content offchain and encrypted. The result is a pragmatic, portable way to make one satoshi carry meaning—clear enough to use today, small enough to extend tomorrow.

How it's Made

I built Container as a single, self-contained Python CLI that does two jobs: create (build, fund, sign, and broadcast a Bitcoin tx carrying an OP_RETURN pointer to a manifest) and inspect (decode a tx’s OP_RETURN and optionally fetch the manifest).

Core structure

argparse defines create and inspect commands plus flags like --manifest, --recipient, --input-utxo, --satoshi-id, --sats, and --fee-rate.

A small config loader reads ~/.satoship_config.json (override with --config) for rpc_url, optional change_address, optional private_key_wif, a default preferred_fee_rate_sat_per_vb, and optional IPFS settings (local API or Pinata).

The script uses python-bitcoinrpc (AuthServiceProxy) to talk to Bitcoin Core.

Manifest pipeline

--manifest must be valid JSON. I validate a minimal schema (satoship_version, ship_id, payload_type, metadata, created_by, created_at) and inject payload_sha256 = sha256(manifest_bytes).

If --satoshi-id is passed, it overrides ship_id so the human-readable Container ID you care about is recorded in the anchor.

Compact OP_RETURN pointer

I compute a tiny pointer JSON: {"sid": <ship_id>, "v":"0.1", "sha256": <hash>}.

If IPFS is configured, I upload the manifest and add "cid": "<IPFS CID>".

I then size-check the encoded payload against standard relay policy (~80 bytes). If it’s too big, I drop the CID; if still too big, I fall back to the raw 32-byte SHA-256 so the OP_RETURN always fits.

Transaction construction

Inputs: if you provide --input-utxo TXID:VOUT, I force include that UTXO so you control which coins/satoshis are spent. (True ordinal-level selection is out of scope; this MVP guarantees UTXO control.)

Outputs: one “container output” to --recipient (default 546 sats for dust safety) plus the nulldata OP_RETURN with our pointer.

I call createrawtransaction, then fundrawtransaction with a custom fee rate. I convert sat/vB → BTC/kB as sat_per_vb * 1e-5 because Core expects BTC/kB.

Signing: if private_key_wif is set, I use signrawtransactionwithkey; otherwise signrawtransactionwithwallet. Finally sendrawtransaction returns the txid.

Inspect mode

Fetches the decoded tx (getrawtransaction true), locates the nulldata output, parses scriptPubKey.asm to extract the OP_RETURN payload, tries to decode JSON, and if a CID is present, fetches the manifest via a configurable IPFS gateway.

Notable hacky bits

Dynamic pointer shrinking (CID → hash) to always fit OP_RETURN.

Precise fee control with sat/vB to BTC/kB conversion.

Deterministic, human-friendly Container ID recorded onchain while keeping the full manifest verifiable via SHA-256 (and IPFS when available).

background image mobile

Join the mailing list

Get the latest news and updates