ark-vtxo-model
VTXO lifecycle, domain model, states, expiration, and tree construction in Ark protocol
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
VTXO lifecycle, domain model, states, expiration, and tree construction in Ark protocol
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
Analyze a project repository and add it to the Arkadian documentation registry with standardized structure. Use when: user provides a path to a new project to onboard.
Analyse an Arkadian orchestrator session - read transcripts, logs, and artifacts to understand what happened and suggest improvements. Use when: user wants to debug or review a past session.
Generate validated operational SOPs for Ark ecosystem projects by testing commands before documenting them. Use when: user wants to create or audit SOPs for a project.
Remove a project from the Arkadian documentation registry and delete all associated documentation files. Use when: user wants to deregister a project.
Update project documentation based on new commits and changes in the repository. Use when: user wants to sync docs after project changes.
Self-improvement loop for arkadian. Launches arkadian -d with a GitHub issue, polls until done, evaluates per-agent criteria, reads transcripts on failure, improves prompts, deletes worktree, and relaunches. Loops until passing or max iterations.
| name | ark-vtxo-model |
| description | VTXO lifecycle, domain model, states, expiration, and tree construction in Ark protocol |
Use this skill when:
A VTXO (Virtual Transaction Output) is an off-chain output in Ark:
Standard VTXO: Owner + ASP | Owner after Timeout
Taproot Output
├── Key path: Unspendable (NUMS point)
└── Script tree:
├── Leaf 0 (Forfeit): Owner + ASP multisig
└── Leaf 1 (Exit): Owner after CSV delay
| State | Description |
|---|---|
| Created | VTXO exists, not yet settled on-chain |
| Preconfirmed | Created in a round, commitment not yet mined |
| Settled | Commitment tx confirmed on-chain |
| Spent | Used as input to another Ark transaction |
| Swept | ASP reclaimed after expiration |
| Unrolled | User performed unilateral exit |
type Vtxo struct {
Outpoint // Txid:VOut identifier
Amount uint64 // Satoshi value
PubKey string // Owner's taproot key (hex)
CommitmentTxids []string // Chain of commitment txids to root
RootCommitmentTxid string // First commitment in chain
SettledBy string // Commitment txid that settled this
SpentBy string // Forfeit/checkpoint txid that spent this
ArkTxid string // Ark TX that consumed this VTXO
Spent bool // Has been spent
Unrolled bool // User performed unilateral exit
Swept bool // ASP reclaimed after expiry
Preconfirmed bool // Created but commitment not confirmed
ExpiresAt int64 // Unix timestamp when VTXO expires
CreatedAt int64 // Unix timestamp of creation
}
Source: arkd/internal/core/domain/vtxo.go:38-53
type Vtxo struct {
Outpoint // Txid:VOut identifier
Script string // VTXO script (hex encoded)
Amount uint64 // Satoshi value
CommitmentTxids []string // Chain to root commitment
ExpiresAt time.Time // When VTXO expires
CreatedAt time.Time // When VTXO was created
Preconfirmed bool // Commitment not yet confirmed
Swept bool // ASP reclaimed
Unrolled bool // User exited unilaterally
Spent bool // Has been spent
SpentBy string // Tx that spent this
SettledBy string // Commitment that settled
ArkTxid string // Ark TX ID
}
Source: go-sdk/types/types.go:74-88
type Outpoint struct {
Txid string
VOut uint32
}
func (k *Outpoint) FromString(s string) error {
parts := strings.Split(s, ":")
if len(parts) != 2 {
return fmt.Errorf("invalid outpoint string: %s", s)
}
k.Txid = parts[0]
vout, _ := strconv.ParseUint(parts[1], 10, 32)
k.VOut = uint32(vout)
return nil
}
func (k Outpoint) String() string {
return fmt.Sprintf("%s:%d", k.Txid, k.VOut)
}
Source: arkd/internal/core/domain/vtxo.go:15-36
// Notes have no commitment chain - they're direct outputs
func (v Vtxo) IsNote() bool {
return len(v.CommitmentTxids) <= 0 && v.RootCommitmentTxid == ""
}
Source: arkd/internal/core/domain/vtxo.go:61-63
// VTXOs that aren't swept and aren't notes require forfeit tx when spent
func (v Vtxo) RequiresForfeit() bool {
return !v.Swept && !v.IsNote()
}
Source: arkd/internal/core/domain/vtxo.go:65-67
func (v Vtxo) TapKey() (*btcec.PublicKey, error) {
pubkeyBytes, err := hex.DecodeString(v.PubKey)
if err != nil {
return nil, err
}
return schnorr.ParsePubKey(pubkeyBytes) // Returns x-only pubkey
}
Source: arkd/internal/core/domain/vtxo.go:73-79
func (v Vtxo) OutputScript() ([]byte, error) {
pubkey, err := v.TapKey()
if err != nil {
return nil, err
}
return script.P2TRScript(pubkey) // Returns P2TR scriptPubKey
}
Source: arkd/internal/core/domain/vtxo.go:81-87
// A VTXO is settled when its commitment tx is confirmed
func (v Vtxo) IsSettled() bool {
return v.SettledBy != ""
}
Source: arkd/internal/core/domain/vtxo.go:69-71
// Recoverable = swept by ASP but user can still claim via unilateral exit
func (v Vtxo) IsRecoverable() bool {
return v.Swept && !v.Spent
}
Source: go-sdk/types/types.go:96-98
func (v Vtxo) Address(server *btcec.PublicKey, net arklib.Network) (string, error) {
buf, _ := hex.DecodeString(v.Script)
pubkeyBytes := buf[2:] // Skip OP_1 and push opcode
pubkey, _ := schnorr.ParsePubKey(pubkeyBytes)
a := &arklib.Address{
HRP: net.Addr,
Signer: server,
VtxoTapKey: pubkey,
}
return a.EncodeV0() // Returns bech32m Ark address
}
Source: go-sdk/types/types.go:100-119
type Round struct {
Id string
Stage Stage
Intents map[string]Intent // User requests in this round
CommitmentTxid string // On-chain anchor
CommitmentTx string // Commitment transaction
VtxoTree tree.FlatTxTree // Tree of VTXO transactions
Connectors tree.FlatTxTree // Connector tree
VtxoTreeExpiration int64 // When VTXOs expire (seconds)
// ...
}
Source: arkd/internal/core/domain/round.go:41-59
const (
RoundUndefinedStage RoundStage = iota
RoundRegistrationStage // Collecting user intents
RoundFinalizationStage // Building/signing tree
)
func (r *Round) IsStarted() bool {
return !r.IsFailed() && !r.IsEnded() && r.Stage != empty
}
func (r *Round) IsEnded() bool {
return !r.IsFailed() && r.Stage.Code == int(RoundFinalizationStage) && r.Stage.Ended
}
func (r *Round) IsFailed() bool {
return r.Stage.Failed
}
Source: arkd/internal/core/domain/round.go:11-16, 249-260
func (r *Round) ExpiryTimestamp() int64 {
if r.IsEnded() {
return time.Unix(r.EndingTimestamp, 0).Add(
time.Second * time.Duration(r.VtxoTreeExpiration),
).Unix()
}
return -1 // Round not ended yet
}
Source: arkd/internal/core/domain/round.go:262-269
const (
VtxosAdded VtxoEventType = iota
VtxosSpent
VtxosUpdated
)
type VtxoEvent struct {
Type VtxoEventType
Vtxos []Vtxo
}
Source: go-sdk/types/types.go:144-163
type ForfeitTx struct {
Txid string // Transaction ID
Tx string // Signed transaction hex
}
// Forfeit txs are required when ending finalization for non-note VTXOs
func (r *Round) EndFinalization(forfeitTxs []ForfeitTx, finalCommitmentTx string) ([]Event, error) {
if len(forfeitTxs) <= 0 {
for _, intent := range r.Intents {
for _, in := range intent.Inputs {
if in.RequiresForfeit() {
return nil, fmt.Errorf("missing list of signed forfeit txs")
}
}
}
}
// ...
}
Source: arkd/internal/core/domain/round.go:36-39, 165-176
| Purpose | File | Key Types/Functions |
|---|---|---|
| Server VTXO model | arkd/internal/core/domain/vtxo.go | Vtxo, Outpoint, IsNote, RequiresForfeit |
| Client VTXO model | go-sdk/types/types.go | Vtxo, VtxoEvent, VtxoEventType |
| Round domain | arkd/internal/core/domain/round.go | Round, RoundStage, ForfeitTx |
| VTXO script | arkd/pkg/ark-lib/script/vtxo_script.go | VtxoScript, DefaultVtxoScript |
| Tree structure | arkd/pkg/ark-lib/tree/tx_tree.go | TxTree, FlatTxTree |
func canSpendOffchain(vtxo Vtxo) bool {
return !vtxo.Spent &&
!vtxo.Swept &&
!vtxo.Unrolled &&
vtxo.IsSettled()
}
// VTXOs expire based on the round's VtxoTreeExpiration
expiresAt := round.EndingTimestamp + round.VtxoTreeExpiration
func getSpendingPath(vtxo Vtxo) string {
if vtxo.IsNote() {
return "direct" // No forfeit needed
}
if vtxo.Swept {
return "unilateral_exit" // Must use exit path after delay
}
return "forfeit" // Normal cooperative spend
}
func NewVtxoFromRound(round *Round, outpoint Outpoint, amount uint64, pubkey string) Vtxo {
return Vtxo{
Outpoint: outpoint,
Amount: amount,
PubKey: pubkey,
RootCommitmentTxid: round.CommitmentTxid,
ExpiresAt: round.ExpiryTimestamp(),
CreatedAt: round.EndingTimestamp,
Preconfirmed: true, // Until commitment confirms
}
}
┌─────────────────────────────────────────────────────────────┐
│ VTXO LIFECYCLE │
├─────────────────────────────────────────────────────────────┤
│ │
│ User Intent ──► Round Registration ──► Tree Construction │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ PRECONFIRMED │ │
│ │ (commitment tx in mempool, not confirmed) │ │
│ └────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ (commitment confirmed) │
│ ┌────────────────────────────────────────────────────┐ │
│ │ SETTLED │ │
│ │ (on-chain, spendable, has expiration) │ │
│ └────────────────────────────────────────────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌────────┐ ┌─────────┐ ┌──────────┐ │
│ │ SPENT │ │ UNROLLED│ │ SWEPT │ │
│ │(forfeit│ │(unilat. │ │(ASP re- │ │
│ │ path) │ │ exit) │ │ claimed) │ │
│ └────────┘ └─────────┘ └──────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
Commitment Chain: VTXOs have a chain of commitment txids back to root. All must be valid for the VTXO to be spendable.
Preconfirmed State: A VTXO is preconfirmed until its commitment tx is mined. Don't treat preconfirmed VTXOs as fully secure.
Expiration Handling: VTXOs expire! Always check ExpiresAt before attempting to spend. Expired VTXOs need refresh (new round participation).
Swept vs Unrolled:
Notes are Special: Notes don't require forfeit transactions. Check IsNote() before requiring forfeit.
PubKey Format: Server stores pubkey as hex-encoded x-only (32 bytes). Client stores full script.
Time Zones: ExpiresAt is Unix timestamp (UTC). Always use time.Unix() for conversions.
Concurrent Updates: VTXOs can be updated from multiple sources (rounds, sweeps, settlements). Use version control or proper locking.
Client vs Server Model: Field names differ slightly. Client uses time.Time, server uses int64 for timestamps.
Recoverable VTXOs: If swept but not spent, user can still recover via unilateral exit (but must wait for CSV delay).
Skill Owner: ark-developer Repos: arkd, go-sdk