Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
865123f
feat(arkade): scaffold NArk.Arkade package + opcodes / scriptnum / tw…
Kukks May 3, 2026
9f08e5d
test(arkade): vendor introspector testdata fixtures
Kukks May 3, 2026
6e4a745
feat(arkade): script codec, introspector packet TLV, REST client + tests
Kukks May 3, 2026
a7fa3fb
feat(arkade): ArkadeNofNMultisigTapScript + README usage section
Kukks May 3, 2026
4f45e37
feat(arkade): IArkadeBoundScriptBuilder interface + tighter witness s…
Kukks May 3, 2026
1f4c13b
feat(arkade): IExtensionPacket integration + PSBT co-sign helper
Kukks May 3, 2026
0aeb705
feat(arkade): IBatchSessionExtension + ArkadeBatchSessionExtension
Kukks May 3, 2026
1f108ea
refactor(arkade): rename Introspector → Emulator to match arkade-os/e…
Kukks Jun 4, 2026
25b9159
feat(arkade): align Emulator wire-format + REST with arkade-os/emulator
Kukks Jun 4, 2026
daab412
feat(arkade): add OP_DIGEST opcode, fix comments since ts-sdk catched up
d4rp4t Jul 5, 2026
e3d4427
fix(arkade): add BigNum 520-byte cap and ArkadeWitnessHash for ts-sdk…
d4rp4t Jul 5, 2026
aa05a61
refractor(arkade): rename ArkadeScriptHash to ArkadeTweak
d4rp4t Jul 5, 2026
f444863
feat(arkade): add simple program artifact parser
d4rp4t Jul 5, 2026
280372c
feat(arkade): add program compiler
d4rp4t Jul 5, 2026
6d872c8
feat(arkade): wire compiled Program functions into ArkContract + emul…
d4rp4t Jul 6, 2026
996dbfa
feat(arkade): add ArkProgramContractTransformer for spendable Program…
d4rp4t Jul 6, 2026
bbd5e1e
test(arkade): add tests for Arkade Programs
d4rp4t Jul 6, 2026
c409e1a
feat(arkade): wire emulator co-signing into the offchain spend path
d4rp4t Jul 8, 2026
466bc37
feat(testS): cover emulator OP_RETURN merge + covenant submit routing
d4rp4t Jul 8, 2026
937dcd0
feat(arkade): support call-time witness + explicit path selection
d4rp4t Jul 8, 2026
38f7c57
refractor(arkade): clarify models
d4rp4t Jul 8, 2026
b722808
fix(core): don't sign tx when user is not a signer
d4rp4t Jul 14, 2026
60f9947
feat(tests): add tests, run arkade tests in separate action since it …
d4rp4t Jul 14, 2026
c5aba03
feat(arkade): move signers to params
d4rp4t Jul 15, 2026
9995c77
feat(arkade): typed params
d4rp4t Jul 15, 2026
3754b66
feat(arkade): add missing program name metadata
d4rp4t Jul 15, 2026
edac3ad
feat(arkade): add non-interactive swaps Arkade Programs
d4rp4t Jul 15, 2026
fbada7f
feat(arkade-intents): add solver discovery
d4rp4t Jul 15, 2026
a005869
chore(arkade-intents): move intents stuff to separate project
d4rp4t Jul 15, 2026
8c61a2a
feat(arkade-intents): add swap monitoring
d4rp4t Jul 15, 2026
aae455f
feat(arkade-intents): add storage
d4rp4t Jul 15, 2026
f53e580
chore(arkade): cleanup namespaces before it becomes annoying
d4rp4t Jul 15, 2026
a9815f1
ArkadePrograms -> ArkadeIntentPrograms
d4rp4t Jul 15, 2026
0fb68c6
feat(arkade-intents): finish up swaps service
d4rp4t Jul 15, 2026
a5cc25b
feat(tests): test non-interactive swaps
d4rp4t Jul 16, 2026
72f7d31
fix(arkade): add contract scope to contracts
d4rp4t Jul 17, 2026
5328362
fix(tests): name parameter for ArkTransactionBuilderSubmitRoutingTest…
d4rp4t Jul 17, 2026
e08ba1b
fix(arkade): pass cltv and csv values to ArkCoin in ArkProgramContrac…
d4rp4t Jul 17, 2026
978090c
fix ArkadeHtlcTests.cs namespace
d4rp4t Jul 17, 2026
e6cc66c
fix(ci): add Arkade project to CI slnf, reuse action for Arkade proje…
d4rp4t Jul 17, 2026
6d089a9
fix(e2e): check SpentByTransactionId for empty string, not null
d4rp4t Jul 17, 2026
fbc2836
refractor(arkade): rename ParseCovenantSegment to ParseArkadeScriptSe…
d4rp4t Jul 17, 2026
b794cd3
chore(e2e): don't pin arkd version
d4rp4t Jul 18, 2026
cd2ea5f
fix(arkade): separate arkade script and tapscript witnesses
d4rp4t Jul 18, 2026
c06534c
feat(tests): port emulator tweaking tests. remove unused fixture. don…
d4rp4t Jul 18, 2026
fc51ede
add swaps example to dotnet-sdk
d4rp4t Jul 18, 2026
28c8a62
fix(arkade-intents): fix example wallet and tests
d4rp4t Jul 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .env.regtest
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ BITCOIN_LOW_FEE=true
# signer is S2, with S1 declared deprecated at a far-future cutoff (1900000000 ≈ year 2030). The
# far-future cutoff keeps S1 inside its collaborative-sweep window, so arkd still co-signs S1 spends
# (regime 1) and the sweeper can migrate an S1 VTXO onto the current S2 signer.
ARKD_IMAGE=ghcr.io/arkade-os/arkd:v0.9.12-rc.0
ARKD_WALLET_IMAGE=ghcr.io/arkade-os/arkd-wallet:v0.9.12-rc.0
# ARKD_IMAGE=ghcr.io/arkade-os/arkd:v0.9.12-rc.0
# ARKD_WALLET_IMAGE=ghcr.io/arkade-os/arkd-wallet:v0.9.12-rc.0
ARKD_WALLET_SIGNER_KEY=9f80812085352f57a5e8104d6406b4d3b191cfdeb96e1321d6284d8d9bb6e8a1
ARKD_WALLET_DEPRECATED_SIGNER_KEYS=cca401847e4d3635339747f84b7353160acb51951ad287a19f8148835372f1ef:1900000000
# SDK tests hardcode "ark" for docker exec — keep the override container name consistent.
Expand Down
8 changes: 7 additions & 1 deletion .github/actions/e2e-setup/action.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
name: E2E Setup
description: Configure .NET + Node, start regtest infrastructure, build. Requires checkout to have already run.

inputs:
profile:
description: regtest compose profile(s) to start, comma-separated (e.g. "boltz,delegate" or "solver,boltz").
required: false
default: boltz,delegate

runs:
using: composite
steps:
Expand All @@ -12,7 +18,7 @@ runs:
node-version: '20'
- name: Start regtest infrastructure
shell: bash
run: node regtest/regtest.mjs start --profile boltz,delegate
run: node regtest/regtest.mjs start --profile ${{ inputs.profile }}
- name: Restore and build
shell: bash
run: dotnet build NArk.CI.slnf
39 changes: 38 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,47 @@ jobs:
needs: build
uses: ./.github/workflows/e2e-rotation.yml

arkade-e2e:
name: Arkade E2E Tests (emulator)
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v4
with:
submodules: true
fetch-depth: 0
# `solver` pulls in `ark` (arkd) + `emulator`, brings up the mock pricefeed + solver, and runs
# solver-init (fund + mint asset + register pairs) so the swap fulfill E2E has a live market.
# `boltz` adds fulmine (:7003) — the funding endpoint the shared test harness uses to top up
# wallets. EMULATOR_SECRET_KEY comes from regtest/.env.defaults (deterministic).
- uses: ./.github/actions/e2e-setup
with:
profile: solver,boltz
- name: Run Arkade E2E tests
run: dotnet test NArk.Tests.End2End --no-build --verbosity normal --filter "FullyQualifiedName~Arkade"
timeout-minutes: 15
- name: Collect docker logs on failure
if: failure()
run: |
docker logs arkd > /tmp/arkd-logs.txt 2>&1 || true
docker logs emulator > /tmp/emulator-logs.txt 2>&1 || true
docker logs solver > /tmp/solver-logs.txt 2>&1 || true
docker logs solver-init > /tmp/solver-init-logs.txt 2>&1 || true
docker logs bitcoin > /tmp/bitcoin-logs.txt 2>&1 || true
- name: Upload logs
if: failure()
uses: actions/upload-artifact@v4
with:
name: arkade-docker-logs
path: /tmp/*-logs.txt
- name: Stop infrastructure
if: always()
run: node regtest/regtest.mjs clean || true

publish:
name: Publish NuGet Packages
runs-on: ubuntu-latest
needs: [build, e2e, e2e-swaps, e2e-recovery, e2e-rotation]
needs: [build, e2e, e2e-swaps, e2e-recovery, e2e-rotation, arkade-e2e]
if: github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/')
permissions:
contents: write
Expand Down
5 changes: 3 additions & 2 deletions .github/workflows/e2e-core.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ jobs:
fetch-depth: 0
- uses: ./.github/actions/e2e-setup
# Swap and recovery tests run in dedicated parallel workflows (e2e-swaps, e2e-recovery).
# Rotation tests run in e2e-rotation. Run everything else here.
# Rotation tests run in e2e-rotation. Arkade covenant tests need the emulator profile,
# so they run in the arkade-e2e job. Run everything else here.
- name: Run E2E tests
run: dotnet test NArk.Tests.End2End --no-build --verbosity normal --filter "TestCategory!=RealRotation&TestCategory!=Swaps&TestCategory!=Recovery"
run: dotnet test NArk.Tests.End2End --no-build --verbosity normal --filter "TestCategory!=RealRotation&TestCategory!=Swaps&TestCategory!=Recovery&FullyQualifiedName!~Arkade"
timeout-minutes: 20
- name: Collect docker logs on failure
if: failure()
Expand Down
66 changes: 66 additions & 0 deletions NArk.Abstractions/Batches/IBatchSessionExtension.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using NBitcoin;

namespace NArk.Abstractions.Batches;

/// <summary>
/// Plug-point invoked by <c>BatchSession</c> at PSBT-emitting points in the
/// batch flow. Lets extensions co-sign / mutate the PSBTs without
/// <c>BatchSession</c> needing to know about specific co-signers (emulator,
/// future custody services, etc.).
/// </summary>
/// <remarks>
/// <para>
/// Extensions are stateless across batches — they receive the current
/// spending coins on every call so they can decide per-batch whether to
/// engage (most often "are any of these coins arkade-bound?").
/// </para>
/// <para>
/// Registered extensions are invoked in DI registration order. An extension
/// that has nothing to do for a given batch should return the input PSBTs
/// unchanged from <see cref="CoSignAsync"/> (and <c>false</c> from
/// <see cref="ShouldHandleAsync"/> if the whole batch is irrelevant — that
/// short-circuits per-phase calls).
/// </para>
/// </remarks>
public interface IBatchSessionExtension
{
/// <summary>
/// Cheap "is this batch relevant to me?" check, called once at batch
/// session initialization. <c>BatchSession</c> skips invoking
/// <see cref="CoSignAsync"/> on this extension for the rest of the
/// batch when this returns <c>false</c>.
/// </summary>
Task<bool> ShouldHandleAsync(IReadOnlyList<ArkCoin> spendingCoins, CancellationToken cancellationToken);

/// <summary>
/// Co-sign / mutate the supplied PSBTs at the given phase of the batch
/// flow. The returned list replaces the input (same length, same order);
/// extensions can pass through unchanged when there's nothing to do.
/// </summary>
Task<IReadOnlyList<PSBT>> CoSignAsync(
BatchExtensionPhase phase,
IReadOnlyList<PSBT> psbts,
IReadOnlyList<ArkCoin> spendingCoins,
CancellationToken cancellationToken);
}

/// <summary>
/// The point in the batch flow at which an <see cref="IBatchSessionExtension"/>
/// is being invoked.
/// </summary>
public enum BatchExtensionPhase
{
/// <summary>
/// After tree signing aggregates a partially-signed Ark tx tree, before
/// the signatures are submitted upstream. Extension co-signs leaves whose
/// inputs need additional signatures (emulator for arkade-bound
/// inputs, etc.).
/// </summary>
PostTreeSigning = 1,

/// <summary>
/// Before forfeit-tx submission and commitment-tx finalisation. Extension
/// co-signs forfeits and (optionally) the commitment for inputs it owns.
/// </summary>
PreForfeitFinalization = 2,
}
42 changes: 42 additions & 0 deletions NArk.Abstractions/Helpers/PsbtHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ public static class PsbtHelpers
private const string VtxoTreeExpiry = "expiry";
private const string Cosigner = "cosigner";
private const string ConditionWitness = "condition";
private const string PrevArkTx = "prevarktx";
private const string PrevoutTx = "prevouttx";
private const byte ArkPsbtFieldKeyType = 222;

/// <summary>
Expand Down Expand Up @@ -57,6 +59,46 @@ public static void SetArkFieldTapTree(this PSBTInput psbtInput, TapScript[] leav
psbtInput.Unknown[new[] { ArkPsbtFieldKeyType }.Concat(Encoding.UTF8.GetBytes(VtxoTaprootTree)).ToArray()] =
EncodeTaprootTree(leaves);

/// <summary>
/// Sets the <c>prevarktx</c> ark field — the raw previous Arkade transaction
/// an introspection opcode reads for this input. Stored under
/// <c>[0xde]||"prevarktx"</c> (emulator <c>ArkFieldPrevArkTx</c>).
/// </summary>
public static void SetArkFieldPrevArkTx(this PSBTInput psbtInput, Transaction prevArkTx)
{
ArgumentNullException.ThrowIfNull(prevArkTx);
psbtInput.Unknown[new[] { ArkPsbtFieldKeyType }.Concat(Encoding.UTF8.GetBytes(PrevArkTx)).ToArray()] =
prevArkTx.ToBytes();
}

/// <summary>
/// Sets the <c>prevouttx</c> ark field — the raw previous output transaction
/// an introspection opcode reads for this input (e.g. on the emulator's
/// <c>/v1/onchain-tx</c> path). Stored under <c>[0xde]||"prevouttx"</c>
/// (emulator <c>ArkFieldPrevoutTx</c>).
/// </summary>
public static void SetArkFieldPrevoutTx(this PSBTInput psbtInput, Transaction prevoutTx)
{
ArgumentNullException.ThrowIfNull(prevoutTx);
psbtInput.Unknown[new[] { ArkPsbtFieldKeyType }.Concat(Encoding.UTF8.GetBytes(PrevoutTx)).ToArray()] =
prevoutTx.ToBytes();
}

/// <summary>Reads the <c>prevarktx</c> ark field, or <c>null</c> if absent.</summary>
public static Transaction? GetArkFieldPrevArkTx(this PSBTInput psbtInput, Network network) =>
GetArkFieldTransaction(psbtInput, PrevArkTx, network);

/// <summary>Reads the <c>prevouttx</c> ark field, or <c>null</c> if absent.</summary>
public static Transaction? GetArkFieldPrevoutTx(this PSBTInput psbtInput, Network network) =>
GetArkFieldTransaction(psbtInput, PrevoutTx, network);

private static Transaction? GetArkFieldTransaction(PSBTInput psbtInput, string fieldName, Network network)
{
ArgumentNullException.ThrowIfNull(network);
var key = new[] { ArkPsbtFieldKeyType }.Concat(Encoding.UTF8.GetBytes(fieldName)).ToArray();
return psbtInput.Unknown.TryGetValue(key, out var value) ? Transaction.Load(value, network) : null;
}


// Encodes taproot script leaves per PSBT spec: {depth version script_length script}* (no leaf count prefix).
// Param: leaves — array of tapscript byte arrays.
Expand Down
Loading
Loading