Pay an agent that has no account.
A transfer needs somewhere to send it. An envelope does not: it seals value against a key that exists only in a link, and whoever holds the link takes the contents. There is nothing to register, no viewing key to exchange and no address to ask for, which is what makes it something one agent can hand another before they know anything about each other.
Install
npm install -g strk20-envelope-cliNothing works until this exists. Create a .env.local in your project with three variables in it:
STARKNET_ACCOUNT=0x… # the account that signs
STARKNET_PRIVATE_KEY=0x… # its key
ENVELOPE_NETWORK=sepolia # or mainnetNo export, no quotes needed, though both are tolerated if you paste them. The file is found on its own: the directory you run in, then its parents up to the repository root, then one level down, which is where a framework tends to leave one.
envelope whoamiPrints the account, the network, and which file it read, so an agent is never signing with a key it cannot account for. If more than one candidate turns up it refuses to choose and asks you to name one:
envelope whoami --env path/to/.env.localExported shell variables work too and always win over a file, so a container that injects a key cannot have it replaced by a dotfile in a checkout. Whatever holds that key holds the money: keep the file out of git.
Send
envelope seal --amount 1 --expiry 24h --memo "invoice 1101"Sealed 1 STRK on Sepolia, claimable for 24 hours.
Claim link https://0xrlawrence.github.io/envelope/claim#e1.BSc7nv0…
Return link https://0xrlawrence.github.io/envelope/refund#r1.B4jwdCU…~0x7e431…
Anyone holding the claim link can take the contents, so send it the way you
would send cash. Keep the return link: after the window shuts it is the only
way to get the money back, and it needs the web app.
Transaction https://sepolia.voyager.online/tx/0x2a927023701c734c6a91…{
"ok": true,
"network": "sepolia",
"amount": "1",
"token": "STRK",
"expiresAt": "2026-08-20T07:41:48.000Z",
"claimLink": "https://0xrlawrence.github.io/envelope/claim#e1.BSc7nv0…",
"returnLink": "https://0xrlawrence.github.io/envelope/refund#r1.B4jwdCU…~0x7e431…",
"envelopeId": "0x7e43138dca13d7ab1cfd1e892bdcdcf8d49258ead6ea29aa395ef5559ef0797",
"transactionHash": "0x2a927023701c734c6a91…",
"fundedPrivately": false
}One command, two shapes. Read by a person it is prose; read by anything else it is JSON, decided by whether stdout is a terminal rather than by remembering a flag. Anyone holding the claim link can take the contents, so pass it the way you would pass cash. Keep the return link: after the window shuts it is the only way to get the money back, and it cannot be regenerated.
Receive
envelope open "https://0xrlawrence.github.io/envelope/claim#e1.BSc7nv0…"envelope status 0x7e43138dca13d7ab1cfd1e892bdcdcf8d49258ead6ea29aa395ef5559ef0797 --idopen claims to your own account unless --to says otherwise. status reads the contract and takes either a claim link or, with --id, an envelope id.
In a pipeline
LINK=$(envelope seal --amount 1 | jq -r .claimLink)
curl -X POST "$WEBHOOK" -d "{\"pay\": \"$LINK\"}"envelope seal --amount 1 --dry-run--dry-run builds and prints the transaction without signing or sending it. Worth doing once before wiring this into anything that spends on its own.
Or the library
import { buildPublicFundCalls, encodeClaimLink, generateEnvelopeKey } from "strk20-envelope";
const claim = generateEnvelopeKey();
const refund = generateEnvelopeKey();
const calls = buildPublicFundCalls({
anonymizer, token, amount: 1_000_000_000_000_000_000n,
claimPublicKey: claim.publicKey,
refundPublicKey: refund.publicKey,
expiry: Math.floor(Date.now() / 1000) + 86_400,
});
await account.execute(calls);
const link = encodeClaimLink(origin, claim.privateKey);What a key alone cannot do
- Fund privately
- The CLI funds from your address, in the open: the amount and the funder are on-chain. Everything else is unchanged, including that a recipient claiming into a shielded balance is still unobservable.
- Claim into a shielded balance
openpays to an address, which puts the recipient on-chain.- Return an expired envelope
- The contract only accepts a refund from the pool, so the return link has to be opened in this app.
All three need a wallet that can prove a STRK20 action for its own account class, which a private key on its own cannot do. envelope whoami prints this list next to the account in use, so a caller can check rather than assume.