Every code block below is real and runnable against a locally running packages/api devserver + live Arc testnet — nothing here is illustrative pseudo-code. These blocks are extracted verbatim into docs/quickstart-verbatim.sh and run in CI; if that script fails, the fix is to this doc, not to the script.
Honesty note up front: Conduit payments settle via Permit2, which means the payer's wallet must produce a real EIP-712 signature — that step cannot be a bare curl command in any HTTP API, ours included. In your own integration this signing happens in the payer's browser wallet (see the hosted checkout flow). For this quickstart we use a small Node helper (sign-typed-data.mjs, already in this repo) to play that role non-interactively so the whole thing is scriptable end to end.
This resolves the repo root from the script's own location so it works no matter what directory you run it from (which is exactly what CI exercises — running this from a scratch directory, not a repo checkout).
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
(cd "$REPO_ROOT/packages/api" && go run ./cmd/devserver > /tmp/quickstart-devserver.log 2>&1 &)
for i in $(seq 1 30); do curl -sf http://localhost:8080/healthz > /dev/null 2>&1 && break; sleep 1; doneACCOUNT=$(curl -s -X POST http://localhost:8080/v1/accounts \
-H 'content-type: application/json' \
-d '{"name":"Quickstart Co","settle_currency":"EUR","settle_address":"0xf04a181eaB4CfABf7D13CCe64737782737cD0b22"}')
SK_KEY=$(echo "$ACCOUNT" | python3 -c 'import json,sys;print(json.load(sys.stdin)["api_key"]["key"])')INTENT=$(curl -s -X POST http://localhost:8080/v1/settlement_intents \
-H "Authorization: Bearer $SK_KEY" -H "Idempotency-Key: $(uuidgen)" -H 'content-type: application/json' \
-d '{"amount":1000000,"settle_currency":"EUR","reference":"QUICKSTART"}')
INTENT_ID=$(echo "$INTENT" | python3 -c 'import json,sys;print(json.load(sys.stdin)["id"])')PRIVATE_KEY="$(grep PRIVATE_KEY "$REPO_ROOT/packages/contracts/.env" | cut -d= -f2-)"
sign() { (cd "$REPO_ROOT/packages/contracts" && node script/sign-typed-data.mjs "$PRIVATE_KEY"); }
QUOTE=$(curl -s -X POST http://localhost:8080/v1/settlement_intents/$INTENT_ID/quote \
-H "Authorization: Bearer $SK_KEY" -H 'content-type: application/json' -d '{"pay_currency":"USDC"}')
QUOTE_MSG=$(echo "$QUOTE" | python3 -c 'import json,sys;print(json.dumps(json.load(sys.stdin)["typed_data"]["message"]))')
QUOTE_SIG=$(echo "$QUOTE" | python3 -c 'import json,sys;print(json.dumps(json.load(sys.stdin)["typed_data"]))' | sign)
PREPARE=$(curl -s -X POST http://localhost:8080/v1/settlement_intents/$INTENT_ID/prepare \
-H "Authorization: Bearer $SK_KEY" -H 'content-type: application/json' \
-d '{"quote_message":'"$QUOTE_MSG"',"quote_signature":"'"$QUOTE_SIG"'"}')
FUNDING_SIG=$(echo "$PREPARE" | python3 -c 'import json,sys;print(json.dumps(json.load(sys.stdin)["funding_typed_data"]))' | sign)
curl -s -X POST http://localhost:8080/v1/settlement_intents/$INTENT_ID/confirm \
-H "Authorization: Bearer $SK_KEY" -H 'content-type: application/json' \
-d '{"funding_signature":"'"$FUNDING_SIG"'"}'
# {"status":"settled","tx_hash":"0x..."} <- real Arc testnet transactionThat's it — a real cross-currency settlement (USDC paid in, EUR delivered), signed, submitted, and confirmed on-chain. tx_hash in the last response is a real transaction you can look up on ArcScan.
created --(quote)--> quoted --(prepare)--> funding --(confirm)--> settling --> settledThe FX side has its own nested lifecycle inside quoted→settled above:
quoted -> trade_created -> presigned -> awaiting_signature -> submitted -> settled
(or: expired / failed)See FX timing model for why the quote step has a ~3.5 second window and what that means for checkout vs. pre-priced invoices.