Arrekta Studio: issuer console and holder portal
2026-10-28. Design for a production application that lets businesses create and run coins on
Arrekta, and lets their holders see and use them. It is built only on the public SDKs
(arrekta-sdk, @arrekta/wallet-core) as documented on the docs site: if Studio needs something
the SDK can't do, the SDK gets it first, with docs and tests. Studio is the SDK's most demanding
user.
Decisions taken with the owner (2026-10-28):
- issuer keys use a shared 2-of-3 multisig;
- users are issuers and their holders;
- TypeScript throughout.
1. What others do, and what we take from them
Blockstream AMP is the closest relative: an issuer platform for Liquid assets [1][2].
- What it has:
- per-wallet and per-asset transfer restrictions (allowlists, burn permission);
- category-based rules;
- a hook into the issuer's own authorization API;
- ownership and balance reporting;
- tracking of reissuance and burns;
- asset registry registration;
- delegation of user management to third parties.
- What we take: categories of holders, restrictions as first-class objects, ownership reports, and delegation through roles.
- Where we differ: restrictions are enforced by the chain where possible: freeze and clawback are consensus rules (PROTOCOL.md §7), not a platform co-signature. Holders keep their own keys.
ERC-3643 (T-REX) is the permissioned-token standard on Ethereum [3][4].
- What it has: owner versus agent roles; sensitive actions (forced transfers, freezes, mint/burn) given to agents; audit checklists that ask for every privileged key to sit behind a multisig.
- What we take: separate roles inside an issuer organisation, and every privileged action behind the 2-of-3, by construction rather than by convention.
BitGo's 2-of-3 is the custody model [5][6]:
- the user key starts and signs;
- the platform key co-signs only after checking the organisation's policies;
- an offline backup key, together with either other key, recovers the wallet.
- What we take: the same three roles, with policy checks before every co-signature.
- What's ours: keys are post-quantum (ML-DSA), and Arrekta's policy locks (
thresh) express 2-of-3 natively, with no contract code.
Sablier is the reference for airdrops and vesting [7]:
- it separates push distributions from claim (Merkle) distributions;
- vesting can be absolute (one shared schedule) or relative (from each claim);
- distribution contracts that hold value need audits.
- What's ours: vesting is a consensus policy (
vesting/after), so there is no vesting contract to audit. Push distributions are batched transactions. Claim campaigns come later (§4.3).
2. Custody: the issuer's 2-of-3
Every organisation has one control lock:
thresh(2, key(issuer), key(platform), key(backup)).
| Key | Held by | Used for |
|---|---|---|
| issuer | an org member: in the browser (encrypted with the user's passkey or password), the Arrekta extension, or a hardware/air-gapped signer (arrekta-ur QR) | starts and signs every action |
| platform | Studio's co-signer service (HSM/KMS in production; an encrypted file on devnet) | co-signs only after the policy engine (§2.1) approves |
| backup | the issuer, offline: generated in the browser at onboarding and shown once as 24 words | with the issuer key, recovers everything without Studio: the issuer can always leave |
What the control lock holds:
- each asset's authority token (needed to mint) and control token (needed to freeze and claw back);
- the treasury: unissued and undistributed supply.
So minting, freezing, clawing back and paying from the treasury all need two of the three. Studio can never act alone. Neither can a stolen browser key. Signing uses PST (protocol §16):
- the browser builds the PST with the SDK and signs it with the issuer key;
- the co-signer checks the policy, signs, finalizes and broadcasts.
2.1 Policy engine (before every platform co-signature)
As built, this is the wallet service's policy engine (rules, approvers, quorum, lock, freeze),
edited on Studio's Policy page. The Studio co-signer signs only after it says ready.
Rules are per organisation, versioned, and changing them is itself an approved action:
- approvals: N org members with the right role must approve before Studio co-signs (four-eyes);
- limits: a cap per mint and per day, a maximum airdrop size, and a velocity limit on treasury payouts;
- destinations: treasury payouts only to allowlisted addresses or registered holders;
- controls: freeze and clawback require a reason code, are always logged, and can require compliance-role approval;
- time: an optional delay on large actions, during which an owner can cancel.
The co-signer re-derives everything it checks from the PST itself, using the SDK's
pstSummary, pstFee and pstStatus. It never trusts a description sent alongside the PST.
3. Users and roles
Issuer side (an organisation):
| Role | Can |
|---|---|
| owner | everything, including policies, members and keys |
| admin | assets, holders, distributions; approve |
| operator | prepare actions (mint, airdrop, vesting); cannot approve their own |
| compliance | holders' KYC status, freeze/unfreeze/clawback requests and approvals, reports |
| viewer | read only |
Holder side: anyone with a Arrekta wallet.
- Sign-in: the holder signs a challenge with their own key: the SDK's
signMessage, origin bound, so nothing to remember and no password database. - What they see:
- balances of the issuer's assets;
- vesting schedules and unlock dates;
- airdrops received;
- claims (later);
- documents the issuer publishes.
- Their keys: holders' keys never touch Studio.
4. Features
4.1 First release
-
Organisations:
- sign-up with passkeys (WebAuthn) plus an email recovery link;
- members, invitations and roles;
- the key ceremony: create or import the issuer key, generate the backup key, receive the platform key, build the control lock.
-
Create a coin:
- ticker, name and decimals;
- fixed or mintable supply;
- optional freeze and clawback, with the required
controls_policytext; - a ARRK-AMS-1 metadata document, hashed and published.
The issuance pays authority and control tokens to the control lock.
-
Treasury and supply: issued, circulating, frozen and burned amounts, live from the indexer; mint within policy.
-
Holders:
- a holder registry (address, label, category, KYC status from the issuer's own process, notes);
- the on-chain holder list from the indexer, reconciled with the registry;
- CSV import and export.
-
Controls: freeze/unfreeze per holder and clawback, each with a reason and approvals, reflected live.
-
Airdrops (push):
- recipients from CSV, a holder category, or a snapshot of holders at a height (pro rata or fixed amount);
- a dry run shows totals, fees and the number of transactions;
- batches are planned under the mempool's transaction-size limit (§6);
- each batch is co-signed under policy;
- progress, retry and resume;
- every payout is linked to its txid.
-
Vesting grants:
- pay a holder into a
vesting(holder, unlock_height)lock, or athresh/afterschedule in tranches; - the chain enforces the lock; the holder portal shows the schedule.
- pay a holder into a
-
Audit log: every action, approval, co-signature and txid, hash-chained and exportable.
-
Holder portal: sign in with a wallet, see balances, vesting and history.
4.1a Branded holder portals (built 2026-10-28)
Each organisation can give its holders a portal at /h/<slug>, with no code. It's one shared
Studio, but holders see the issuer's brand:
- Settings (the Holder portal tab):
- name, address, brand colour (text colour picked for contrast), welcome text, featured coin, support link;
- a logo, which must be PNG, JPEG or WebP: its bytes are checked against the declared type, and SVG is refused because it can carry script.
- What holders do there:
- create or restore a device key and sign in with Arrekta;
- see the featured coin and the issuer's other coins, with ARRK for fees;
- receive (address with copy) and send; restricted coins go through the authorizer;
- redeem the issuer's invite codes and see recent activity.
4.1b Onboarding, offerings, money in and out (built 2026-10-29)
Phase 1 of the competitive research (docs/research/STUDIO_COMPETITIVE_RESEARCH.md): the business layer
that Securitize and Tokeny sell and Studio lacked. Third parties plug in through adapters, and
each has a built-in manual fallback, so nothing here needs a contract to run.
- Onboarding (console: Investors → Applications; portal: Onboarding tab):
- settings per organisation: individuals and/or entities, profile fields, required documents per applicant type, accreditation self-certification (off, ask, required), blocked countries, KYC validity, auto-approval;
- applicants fill in details, upload PDF/PNG/JPEG files (type-checked by their bytes, hashed with SHA3-256, at most 10 MB), and submit;
- the review queue approves, rejects or asks for more. Approval creates the investor and
signs
kyc,jurisdiction,investor_typeand optionallyaccreditedclaims with the organisation's Studio claims key (a sealed per-organisation key, trusted once by the owner as a claim issuer), then links the applicant's account as a holder: no invite code.
- KYC providers: manual review, or Sumsub: the applicant verifies on Sumsub's hosted page
(
websdkLink, so no third-party script runs under Studio's CSP), and theapplicantReviewedwebhook is verified withx-payload-digest(HMAC-SHA256 over the raw body). A GREEN result can approve automatically when nothing else needs a human. - Offerings (console: Capital → Offerings): a registered coin, price in USD/EUR/GBP/CHF or ARRK, minimum and maximum per investor, hard cap, open/close dates, required claims, and documents to acknowledge. The price is fixed once it opens.
- Subscriptions:
- by bank transfer: the order shows the issuer's bank details and a unique reference
(
DCS-XXXXX-XXXXX), and an operator marks it paid; - by card through Stripe Checkout, confirmed by its signed webhook (
Stripe-Signature, 5-minute tolerance, amount and currency checked); - in ARRK to a fresh treasury address per order, seen on chain by the payment watcher. A paid order becomes an issuer-service allocation, and the next distribution run delivers it through the 2-of-3 treasury. Unpaid orders expire after 14 days; paid ones can be turned into a refund (allocation cancelled, refund reference recorded).
- by bank transfer: the order shows the issuer's bank details and a unique reference
(
- Redemptions: terms per coin (open, price, currency, minimum, notice). The holder requests; the issuer accepts and gets a treasury address for the coins to come back to (seen on chain, or confirmed by hand); the issuer pays out and records the reference.
- Document room (Investors → Documents; portal: Documents tab): visibility public,
applicants and investors, approved investors, or team only. An acknowledgement is a statement
naming the file's SHA3-256, signed with the investor's account key (
signMessage, origin = Studio's domain) and verified on the server. - Payments settings (Capital → Payments): bank instructions, Stripe keys (sealed), ARRK on/off. Studio never holds money.
Every step is in the hash-chained audit log. test/e2e.ts covers the whole flow on the devnet,
including forged webhooks and acknowledgements, the cap and the minimum ticket.
Also built with it:
- the holder returns redemption coins from the portal in one click (restricted coins go through the authorizer, which now accepts a return to the issuer's treasury);
- refunding a card order refunds it through Stripe (
POST /v1/refunds, idempotent per order); - CSV exports of orders and applications (formula injection neutralised);
- an Overview "Go to market" checklist and capital tiles.
Not covered yet: redemption coins are matched by balance at the order's address (any sender); there is no email to applicants yet.
4.1c Servicing (built 2026-10-29)
Phase 2 of the research plan:
- Holder votes (Governance → Votes; portal: Votes tab):
- a proposal names a coin, 2–10 options, a record block, a closing time, a quorum and a pass mark;
- opening it snapshots every holder's balance at the record block from the indexer, without the issuer's treasury;
- a holder votes by signing a statement naming the option, proposal, coin and block with their account key. Studio computes the weight from the snapshot and the investor's own addresses (from the issuer service), never from the voter. One vote per holder, final;
- closing (by hand or when time is up) publishes the tally and a SHA-256 over the canonical
record: proposal, snapshot, and every vote with its key and signature. The record is public at
/api/public/proposals/{id}, so anyone can recheck both the signatures and the result.
- Valuations (NAV) on each coin's Valuation tab: value per token, date, method and an optional supporting document, with history and a sparkline. Holders see the latest in the portal; the public coin page shows it with the votes.
- Inbox: everything waiting on the signed-in member's role, across transfers to approve or sign, controls to approve or execute, applications, orders and votes. It drives the sidebar badges.
- Reports: CSV exports of the cap table at any block (with investor names where known), the investor register with claims, orders and applications.
- Instrument templates when creating a coin: equity (share class), bond (coupon, frequency, maturity, face value), fund units (NAV currency, strategy) and stablecoin (peg, reserve reports). Each sets sensible controls; the terms go into the metadata document whose hash is on chain, and show on the coin's summary.
Not covered yet: offerings and redemptions use a fixed price, not the latest NAV; share classes are one coin each. (Bond coupons are now scheduled: §4.1e.)
4.1d Integration (built 2026-10-29)
Phase 3 of the research plan:
- API keys (Distribution → Developers): org-scoped
dcs_…keys sent asAuthorization: Bearer. A key acts as the member who made it, with the lower of its role and theirs. It works only on that organisation's endpoints, never on the holder portal, and never signs (signing needs the owner's device). Stored as SHA-256 and shown once; revocable; last use recorded. Requests with a key carry no cookie, so the CSRF header is not required for them. - Webhooks: every audited action of the organisation (catalogue at
GET /api/events) can be POSTed to up to 10 endpoints. Each delivery is signed withStudio-Signature: t=…,v1=HMAC-SHA256(secret, t + "." + body)and retried with exponential backoff (8 attempts, about a day). There is a delivery log, a ping, and manual retry. Targets must be public https; local and http targets are allowed only on regtest. - Email (Distribution → Email): notices to applicants and investors: application received, approved, rejected or needing more; order instructions; payment received; redemption accepted or paid; refunds; votes opening. They go to an outbox, sent by Postmark or recorded as a dry run (the default). The sender address is validated (no header injection), and there's a test send.
- Bulk investor import (Investors → Import CSV): up to 1000 rows of investors verified
elsewhere.
kyc,accreditedandcountrybecome claims signed with the Studio claims key. Optional invite codes come back in a downloadable results file. Bad rows are reported, not fatal. - KYC expiry and renewal: approved investors whose KYC claim expires within 30 days appear in the inbox. "Request KYC renewal" reopens the application (the investor gets an email), and approving it again signs fresh claims.
- Compliance presets when creating a restricted coin: US Reg D 506(c) (KYC + accredited), US Reg S (KYC, no US persons, 40-day lock-up from issue), EU investors (KYC + EU-27 jurisdiction), and under 2,000 holders. They map onto the issuer service's transfer rules. They are a starting point, not legal advice.
Found and fixed while building this: members who joined after activation are not issuer officers. Their onboarding writes (creating investors, adding claims signed by the Studio key) now go through the owner's principal after Studio's role check. Approving and executing controls still uses each member's own identity, because quorum depends on who approves.
4.1e Scheduled payouts: coupons, maturity, recurring distributions (built 2026-10-29)
Each coin's Payouts tab, following the design of Hedera Asset Tokenization Studio's mass-payout service (Apache-2.0; credited in THIRD_PARTY_NOTICES.md, design reimplemented, no code copied):
- Kinds:
- corporate actions: bond coupons and the principal at maturity;
- payouts: one-off, or recurring weekly to yearly with an optional end date.
- Amounts: fixed per coin, or a total split pro rata. Paid in ARRK or one of the organisation's coins (for example its stablecoin).
- Bond schedules come from the bond's terms (rate, frequency, face value, maturity): one coupon per period of face × rate ÷ periods per year, then the principal. Payment dates are counted from the first payment, so there is no drift; a month-end date stays month-end. Each is recorded N days before it pays.
- Lifecycle:
- Scheduled. When the record date comes, the snapshot is the indexed block at that moment, and the run is recorded with its eligible holders: treasury left out, and for restricted coins only registered addresses.
- Awaiting signature. On the payment date, once that block is buried, it becomes a reward run. For per-coin amounts the total is per coin × eligible units, so the largest-remainder split pays each holder exactly. Its treasury transfers (up to 100 recipients each) wait for the owner's signature, and the Studio co-signer completes them.
- Completed. Failures (for example a treasury short of the payout coin) are kept with the reason, appear in the inbox, and can be retried. Runs not yet declared can be cancelled, singly or as a whole schedule. "Pay now" brings a run forward.
- Checked: unit tests for the date and coupon maths. The e2e run pays a per-coin distribution on the devnet and confirms a holder received exactly the amount × their units at the record block. It also creates a bond schedule and cancels it.
4.2 Next
- Reward programmes ("farming" without pooled custody):
- snapshots of holdings over time (time-weighted);
- a published formula;
- scheduled payout runs (airdrops under policy);
- each run's inputs and results published, with a hash, so anyone can recompute it.
- Webhooks and reports (holders, supply, actions) for the issuer's back office.
- Reserve attestations for stablecoin-style assets (the
reserve_attestation_urlmetadata).
4.3 Later
- Claim campaigns (pull): a Merkle root of
(address, amount)is published, and each holder claims with a proof. Each claim is paid by the co-signer under policy, so no contract holds the pool. - Payment channels for holders (instant transfers), through channeld.
- Cross-chain redemption using swaps and finality proofs (INTEROP.md).
5. Architecture (as built)
The first draft put a policy engine and a holder registry inside Studio. The platform already
has both, audited by their own end-to-end checks. So Studio is the product layer on top of them,
and it reaches them only through arrekta-sdk:
browser (React) ── device key in a wallet-core vault: signs sign-ins, key batches, PSTs, claims
│ /api/* (session cookie; writes need the x-studio header)
studio API (Fastify) ── one gateway API key per member, so the services see real principals
│ arrekta-sdk: gateway · wallet service · issuer service · indexer · node · faucet
gateway ─┬─ wallet service 2-of-3 treasury [issuer device, Studio co-signer, offline backup]
│ policy engine, approvals, PST collection, attestation
├─ issuer service investors, signed claims, rules, managed addresses, assignments,
│ distributions, dividends, governed controls, attestations, public log
└─ indexer cap tables at any height, supply history
studio co-signer ── its own container and seed; signs only Studio-started transfers that the
wallet service marked ready, after checking the PST attestation
| Studio part | Where | Notes |
|---|---|---|
| API + web app | apps/studio/server, apps/studio/web | container arrekta-studio, http://127.0.0.1:28083 |
| Platform co-signer | apps/studio/server/cosigner.ts | container arrekta-studio-cosigner; no API |
| Database | own Postgres (studio-postgres) | accounts, sessions, orgs, members (sealed gateway keys), invites, hash-chained audit; applications, documents, acknowledgements, offerings, orders, redemption terms, sealed provider credentials |
| Providers | apps/studio/server/providers.ts | Sumsub (KYC) and Stripe (cards): request signing and webhook verification |
| End-to-end check | apps/studio/test/e2e.ts | drives the API exactly like the browser |
Browser keys (one recovery phrase per device):
- account
m/9000'/0'/0'/0'/0': sign-in and holder address; - co-signer identity
m/9000'/0'/0'/2'/0'; - treasury keys on account 1;
- claims key
m/9000'/0'/0'/3'/0'.
Roles:
- the owner holds wallet slot 0 and signs;
- admins and compliance members invited before activation become issuer officers;
- operators prepare actions;
- viewers read.
6. What building Studio found and fixed
| Area | Problem | Fix |
|---|---|---|
| SDK | no clients for the gateway, wallet service, issuer service, indexer or faucet; no Sign in with Arrekta; multisig fees underestimated | added to both SDKs with shared fixtures (sdk/fixtures/platform.json) and the guide "Issuer platform (SDK)" |
| SDK | services publish raw ML-DSA-65 keys | serviceKey / service_key accept raw or serialized keys |
| Indexer | asset amounts as JSON numbers (lose precision above 2^53 in JavaScript) | decimal strings; the issuer service reads both |
| Gateway | event pruning SQL passed a float to make_interval(days => …) | interval in seconds |
| Wallet service | two co-signers registering keys at the same moment could both miss the other, leaving complete key sets without addresses | registrations serialize on the wallet row |
| Miner (both nodes) | a transfer to a lock plus a higher-fee freeze of that lock in one template made every block invalid: the devnet halted | templates skip transactions that conflict with an earlier freeze in the same block (or the chain's), with regression tests in Rust and Python |
| Issuer service | restricted coins could only move to managed addresses, so a holder could not return them to the treasury for redemption | the authorizer accepts outputs to the issuer's own treasury addresses; sender rules still apply (e2e covers both) |
| Studio | members invited after an organisation was created could not open it ("wallet not found"): the services show a treasury only to its co-signers and officers | read-only views use the owner's principal after Studio's role check; writes keep the member's own |
| Soak tool | coin query capped at 1000 UTXOs, all asset coins after a day: every soak payment failed | ARRK and asset coins fetched separately |
Open service gaps:
- Officers are fixed at issuer creation. (Studio works around it for reads and onboarding; quorum approvals still need officers.) A governed "change officers" action (quorum-approved) is needed before larger teams can join later.
- Only the owner's device signs (wallet slot 0). Several signing members need either more co-signer slots, or a key-sharing ceremony.
7. Security notes
- Issuer keys in the browser are encrypted at rest (wallet-core vault); every signing action
shows an exact preview (
pstSummary). - Sessions use HttpOnly SameSite cookies and a strict CSP. Write calls need a CSRF token. Logins and approvals are rate-limited.
- The co-signer:
- verifies the approval records' signatures, so studio-api can't fake an approval;
- re-derives amounts and destinations from the PST;
- refuses anything outside policy;
- logs every decision.
- Backup key: generated client-side and never sent to Studio. With the issuer key it can sweep everything from the control lock to a lock the issuer alone holds. A documented exit drill is included.
- Before mainnet: an external audit of the co-signer and the policy engine.
8. Milestones
Status 2026-10-28: milestones 1–3 are built and pass the end-to-end check; 4 is next.
- SDK additions (§6) with docs, then the Studio skeleton (api, web, co-signer, Postgres, containers), org sign-up, roles and the key ceremony on devnet.
- Create a coin, treasury and supply, holder registry with indexer reconciliation, controls with approvals, audit log.
- Airdrops (batched, resumable), vesting grants, holder portal.
- Reward programmes, webhooks and reports. Hardening: rate limits, CSP review, load tests, the exit drill.
Sources
- Blockstream AMP2 restriction management: https://enterprise.blockstream.com/docs/amp/issuer-guide/managing-restrictions; AMP overview: https://docs.liquid.net/docs/blockstream-amp-overview
- Blockstream, "Blockstream AMP: issue securities, stablecoins, attested assets, and more": https://blog.blockstream.com/en-blockstream-amp-issue-securities-stablecoins-attested-assets-and-more/
- ERC-3643: https://eips.ethereum.org/EIPS/eip-3643
- QuillAudits, ERC-3643 (T-REX) security audit checklist: https://www.quillaudits.com/research/erc-3643-audit-checklist
- BitGo developer docs, wallets overview: https://developers.bitgo.com/guides/wallets/overview
- BitGo, multisig wallet transaction models and signature rules: https://www.bitgo.com/resources/blog/multisig-wallet-transactions.md
- Sablier docs, airdrops: https://docs.sablier.com/concepts/airdrops