From 8368190febdab0df118ff0a35bac61c635846ec4 Mon Sep 17 00:00:00 2001 From: louisinger Date: Tue, 7 Apr 2026 12:10:26 +0200 Subject: [PATCH 01/10] custom extension and asset --- asset.go | 73 ++++++++++++ client.go | 49 ++++++++ client_internal_test.go | 138 +++++++++++++++++++++++ sdk.go | 25 ++++- send.go | 9 +- test/e2e/extension_test.go | 222 +++++++++++++++++++++++++++++++++++++ test/e2e/utils_test.go | 45 ++++++++ 7 files changed, 558 insertions(+), 3 deletions(-) create mode 100644 test/e2e/extension_test.go diff --git a/asset.go b/asset.go index 90b79932..f877e004 100644 --- a/asset.go +++ b/asset.go @@ -2,10 +2,12 @@ package arksdk import ( "context" + "fmt" "github.com/arkade-os/arkd/pkg/ark-lib/asset" client "github.com/arkade-os/arkd/pkg/client-lib" clientTypes "github.com/arkade-os/arkd/pkg/client-lib/types" + log "github.com/sirupsen/logrus" ) func (a *arkClient) IssueAsset( @@ -33,9 +35,80 @@ func (a *arkClient) IssueAsset( return "", nil, err } + // Persist the issued AssetInfo(s) into the local AssetStore so that + // GetAssetDetails can serve lookups without an indexer round-trip. + a.persistIssuedAssets(ctx, res.IssuedAssets, controlAsset, metadata) + return res.Txid, res.IssuedAssets, nil } +// persistIssuedAssets writes one AssetInfo entry per freshly issued asset +// id into the local AssetStore. When the caller requested a new control +// asset, the first issued id is the control asset itself — all siblings +// carry its ControlAssetId back-reference. Failures are logged and +// swallowed so they cannot mask a successful on-chain issuance. +func (a *arkClient) persistIssuedAssets( + ctx context.Context, assetIds []asset.AssetId, + controlAsset clientTypes.ControlAsset, metadata []asset.Metadata, +) { + if a.store == nil || len(assetIds) == 0 { + return + } + + // Determine the control asset id to link all issued assets to. + var controlAssetId string + switch ca := controlAsset.(type) { + case clientTypes.ExistingControlAsset: + controlAssetId = ca.ID + case clientTypes.NewControlAsset: + // The service returns the control asset first when NewControlAsset + // is requested (see client-lib/asset.go IssueAsset). + if len(assetIds) > 0 { + controlAssetId = assetIds[0].String() + } + } + + for i, id := range assetIds { + info := clientTypes.AssetInfo{ + AssetId: id.String(), + ControlAssetId: controlAssetId, + Metadata: metadata, + } + // For NewControlAsset, the first id is the control asset itself: + // do not set ControlAssetId on its own row to avoid a self-loop. + if _, isNew := controlAsset.(clientTypes.NewControlAsset); isNew && i == 0 { + info.ControlAssetId = "" + } + if storeErr := a.store.AssetStore().UpsertAsset(ctx, info); storeErr != nil { + log.Warnf( + "failed to persist issued asset info for %s: %v", + id.String(), storeErr, + ) + } + } +} + +// GetAssetDetails returns the AssetInfo for the given asset id from the +// local AssetStore +// The AssetInfo is populated at issuance time by IssueAsset (see +// persistIssuedAssets above). Callers that need data about assets issued +// by other wallets should query the indexer via Indexer().GetAsset. +func (a *arkClient) GetAssetDetails( + ctx context.Context, assetId string, +) (*clientTypes.AssetInfo, error) { + if err := a.safeCheck(); err != nil { + return nil, err + } + if a.store == nil { + return nil, fmt.Errorf("asset store not initialized") + } + info, err := a.store.AssetStore().GetAsset(ctx, assetId) + if err != nil { + return nil, fmt.Errorf("getting asset details for %s: %w", assetId, err) + } + return info, nil +} + func (a *arkClient) ReissueAsset( ctx context.Context, assetId string, amount uint64, ) (string, error) { diff --git a/client.go b/client.go index 2530964a..76558d23 100644 --- a/client.go +++ b/client.go @@ -314,9 +314,58 @@ func (a *arkClient) GetTransactionHistory(ctx context.Context) ([]clientTypes.Tr sort.SliceStable(history, func(i, j int) bool { return history[i].CreatedAt.IsZero() || history[i].CreatedAt.After(history[j].CreatedAt) }) + // Derive the per-asset breakdown from AssetPacket for transactions + // that carry one. + for i := range history { + history[i].Assets = deriveAssetsFromPacket(history[i].AssetPacket, history[i].TransactionKey.String()) + } return history, nil } +// deriveAssetsFromPacket reduces an asset.Packet into a per-asset balance +// summary (one clientTypes.Asset per unique asset id), summing amounts across +// all outputs in each group. +func deriveAssetsFromPacket(pkt asset.Packet, txid string) []clientTypes.Asset { + if len(pkt) == 0 { + return nil + } + sums := make(map[string]uint64) + order := make([]string, 0, len(pkt)) + for groupIndex, group := range pkt { + assetId := group.AssetId + // issuance case + if assetId == nil && txid != "" { + derived, err := asset.NewAssetId(txid, uint16(groupIndex)) + if err == nil { + assetId = derived + } + } + + if assetId == nil { + continue + } + id := assetId.String() + if _, seen := sums[id]; !seen { + order = append(order, id) + } + for _, out := range group.Outputs { + sums[id] += out.Amount + } + } + if len(order) == 0 { + return nil + } + + result := make([]clientTypes.Asset, 0, len(order)) + for _, id := range order { + result = append(result, clientTypes.Asset{ + AssetId: id, + Amount: sums[id], + }) + } + return result +} + func (a *arkClient) GetTransactionEventChannel(_ context.Context) <-chan types.TransactionEvent { if a.txBroadcaster != nil { return a.txBroadcaster.subscribe(0) diff --git a/client_internal_test.go b/client_internal_test.go index ec108dba..db4919c5 100644 --- a/client_internal_test.go +++ b/client_internal_test.go @@ -3,7 +3,9 @@ package arksdk import ( "testing" + "github.com/arkade-os/arkd/pkg/ark-lib/asset" clientTypes "github.com/arkade-os/arkd/pkg/client-lib/types" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/stretchr/testify/require" ) @@ -131,3 +133,139 @@ func TestGroupSpentVtxosByTx(t *testing.T) { }, vtxosToSettle) }) } + + +// TestDeriveAssetsFromPacket verifies that GetTransactionHistory's derived +// Assets field aggregates AssetPacket outputs by asset id, in first-seen +// order, deriving issuance ids from the txid + group index. +func TestDeriveAssetsFromPacket(t *testing.T) { + t.Parallel() + + t.Run("nil packet returns nil", func(t *testing.T) { + t.Parallel() + require.Nil(t, deriveAssetsFromPacket(nil, "")) + }) + + t.Run("empty packet returns nil", func(t *testing.T) { + t.Parallel() + require.Nil(t, deriveAssetsFromPacket(asset.Packet{}, "")) + }) + + t.Run("single group single output", func(t *testing.T) { + t.Parallel() + pkt := mustBuildPacket(t, []testGroup{ + {id: testAssetId(t, 1), outs: []uint64{100}}, + }) + got := deriveAssetsFromPacket(pkt, "") + require.Len(t, got, 1) + require.Equal(t, testAssetId(t, 1).String(), got[0].AssetId) + require.Equal(t, uint64(100), got[0].Amount) + }) + + t.Run("multi-output group sums", func(t *testing.T) { + t.Parallel() + pkt := mustBuildPacket(t, []testGroup{ + {id: testAssetId(t, 1), outs: []uint64{50, 75, 25}}, + }) + got := deriveAssetsFromPacket(pkt, "") + require.Len(t, got, 1) + require.Equal(t, uint64(150), got[0].Amount) + }) + + t.Run("multiple asset ids preserve first-seen order", func(t *testing.T) { + t.Parallel() + pkt := mustBuildPacket(t, []testGroup{ + {id: testAssetId(t, 7), outs: []uint64{10}}, + {id: testAssetId(t, 3), outs: []uint64{20}}, + {id: testAssetId(t, 7), outs: []uint64{30}}, // same id as first group, should merge + }) + got := deriveAssetsFromPacket(pkt, "") + require.Len(t, got, 2) + require.Equal(t, testAssetId(t, 7).String(), got[0].AssetId) + require.Equal(t, uint64(40), got[0].Amount) // 10 + 30 + require.Equal(t, testAssetId(t, 3).String(), got[1].AssetId) + require.Equal(t, uint64(20), got[1].Amount) + }) + + t.Run("issuance group derives id from txid and group index", func(t *testing.T) { + t.Parallel() + + txid := testTxid(t, 9) + pkt := mustBuildPacket(t, []testGroup{ + {id: nil, outs: []uint64{12, 8}}, + }) + + got := deriveAssetsFromPacket(pkt, txid) + require.Len(t, got, 1) + derived, err := asset.NewAssetId(txid, 0) + require.NoError(t, err) + require.Equal(t, derived.String(), got[0].AssetId) + require.Equal(t, uint64(20), got[0].Amount) + }) +} + +// testGroup is a minimal test shape for building asset.Packet instances. +type testGroup struct { + id *asset.AssetId + outs []uint64 +} + +// mustBuildPacket assembles an asset.Packet from simple group descriptions. +// It constructs groups using a single fake non-nil AssetInput so that the +// reissuance/burn invariants in asset.NewAssetGroup don't reject them. +func mustBuildPacket(t *testing.T, groups []testGroup) asset.Packet { + t.Helper() + out := make([]asset.AssetGroup, 0, len(groups)) + for i, g := range groups { + outputs := make([]asset.AssetOutput, 0, len(g.outs)) + for j, amt := range g.outs { + o, err := asset.NewAssetOutput(uint16(j), amt) + require.NoError(t, err) + outputs = append(outputs, *o) + } + var inputs []asset.AssetInput + if g.id != nil { + // Build one matching input per output to satisfy input/output sum + // invariants enforced by asset.NewAssetGroup for non-issuance groups. + inputs = make([]asset.AssetInput, 0, len(g.outs)) + for k, amt := range g.outs { + in, err := asset.NewAssetInput(uint16(100+i*10+k), amt) + require.NoError(t, err) + inputs = append(inputs, *in) + } + } + grp, err := asset.NewAssetGroup(g.id, nil, inputs, outputs, nil) + require.NoError(t, err) + out = append(out, *grp) + } + return asset.Packet(out) +} + +// testAssetId fabricates a deterministic non-nil asset id from an index so +// that tests can distinguish multiple ids without pulling chain state. +func testAssetId(t *testing.T, index uint16) *asset.AssetId { + t.Helper() + h := chainhash.Hash{} + h[0] = byte(index) + id, err := asset.NewAssetId(h.String(), index) + require.NoError(t, err) + return id +} + +func testTxid(t *testing.T, seed byte) string { + t.Helper() + h := chainhash.Hash{} + h[0] = seed + return h.String() +} + +// TestPersistIssuedAssets_NilStore confirms that the convenience method +// tolerates a nil store (no panic), which matters when tests inject a +// minimal arkClient without a real store attached. +func TestPersistIssuedAssets_NilStore(t *testing.T) { + t.Parallel() + c := &arkClient{} + // Should not panic. + c.persistIssuedAssets(t.Context(), nil, nil, nil) + c.persistIssuedAssets(t.Context(), []asset.AssetId{*testAssetId(t, 1)}, clientTypes.NewControlAsset{}, nil) +} diff --git a/sdk.go b/sdk.go index 1e820b01..93129af5 100644 --- a/sdk.go +++ b/sdk.go @@ -5,6 +5,7 @@ import ( "time" "github.com/arkade-os/arkd/pkg/ark-lib/asset" + "github.com/arkade-os/arkd/pkg/ark-lib/extension" client "github.com/arkade-os/arkd/pkg/client-lib" transport "github.com/arkade-os/arkd/pkg/client-lib/client" "github.com/arkade-os/arkd/pkg/client-lib/explorer" @@ -14,6 +15,8 @@ import ( "github.com/arkade-os/go-sdk/types" ) + + var Version string type ArkClient interface { @@ -47,7 +50,16 @@ type ArkClient interface { BurnAsset( ctx context.Context, assetID string, amount uint64, ) (string, error) - SendOffChain(ctx context.Context, receivers []clientTypes.Receiver) (string, error) + SendOffChain( + ctx context.Context, receivers []clientTypes.Receiver, opts ...SendOffChainOption, + ) (string, error) + // GetAssetDetails returns the AssetInfo (id, control asset id, metadata) + // that was persisted to the local AssetStore at issuance time. It queries + // the local store only — it does NOT make an indexer round-trip. Callers + // that need supply or remote-only data should use the Indexer() directly. + // + // Returns an error if the asset is not present in the local store. + GetAssetDetails(ctx context.Context, assetId string) (*clientTypes.AssetInfo, error) RegisterIntent( ctx context.Context, vtxos []clientTypes.Vtxo, boardingUtxos []clientTypes.Utxo, notes []string, @@ -95,3 +107,14 @@ type InitWithWalletArgs struct { Password string ExplorerURL string } + +type SendOffChainOption = client.SendOption + +// WithExtension re-exports the client-lib option to append additional +// extension.Packet values to the OP_RETURN extension blob written by +// SendOffChain. +// +// 0x00 is reserved type for asset packet (auto-generated) +func WithExtension(packets ...extension.Packet) SendOffChainOption { + return client.WithExtraCustomPacket(packets...) +} \ No newline at end of file diff --git a/send.go b/send.go index 68c78279..858657de 100644 --- a/send.go +++ b/send.go @@ -8,7 +8,7 @@ import ( ) func (a *arkClient) SendOffChain( - ctx context.Context, receivers []clientTypes.Receiver, + ctx context.Context, receivers []clientTypes.Receiver, opts ...SendOffChainOption, ) (string, error) { if err := a.safeCheck(); err != nil { return "", err @@ -19,7 +19,12 @@ func (a *arkClient) SendOffChain( return "", err } - res, err := a.ArkClient.SendOffChain(ctx, receivers, client.WithVtxos(vtxos)) + // Always supply our spendable vtxos; caller options follow. + res, err := a.ArkClient.SendOffChain( + ctx, + receivers, + append([]client.SendOption{client.WithVtxos(vtxos)}, opts...)..., + ) if err != nil { return "", err } diff --git a/test/e2e/extension_test.go b/test/e2e/extension_test.go new file mode 100644 index 00000000..396084b0 --- /dev/null +++ b/test/e2e/extension_test.go @@ -0,0 +1,222 @@ +package e2e + +import ( + "testing" + "time" + + "github.com/arkade-os/arkd/pkg/ark-lib/asset" + "github.com/arkade-os/arkd/pkg/ark-lib/extension" + clientTypes "github.com/arkade-os/arkd/pkg/client-lib/types" + sdk "github.com/arkade-os/go-sdk" + "github.com/stretchr/testify/require" +) + +// TestSendWithExtraCustomPacket verifies that SendOffChain can attach an extra +// extension packet (type 0x03) and that it round-trips through the indexer and +// extension parsing (NewExtensionFromTx + GetPacketByType). +// +// It also asserts that type 0x00 (reserved for the asset packet) is rejected. +func TestSendWithExtraCustomPacket(t *testing.T) { + runForEachStoreBackend(t, func(t *testing.T, backend testStoreBackend) { + setupClient := backend.setupClient + + ctx := t.Context() + + alice := setupClient(t) + bob := setupClient(t) + + aliceTxStream := alice.GetTransactionEventChannel(ctx) + bobTxStream := bob.GetTransactionEventChannel(ctx) + + // Fund alice with enough offchain BTC to cover the send + dust change. + faucetOffchainAndWait(t, alice, aliceTxStream, 0.001) + + bobAddr, err := bob.NewOffchainAddress(ctx) + require.NoError(t, err) + require.NotEmpty(t, bobAddr) + + // Invalid case: type 0x00 is reserved for the asset packet. + _, err = alice.SendOffChain( + ctx, + []clientTypes.Receiver{{To: bobAddr, Amount: 5000}}, + sdk.WithExtension( + extension.UnknownPacket{PacketType: asset.PacketType, Data: []byte{0xff}}, + ), + ) + require.Error(t, err) + require.Contains(t, err.Error(), "reserved") + + // Valid: attach an arbitrary type-0x03 packet and recover it + // from the resulting ark tx via the indexer. + customPayload := []byte{0xde, 0xad, 0xbe, 0xef} + customPkt := extension.UnknownPacket{PacketType: 0x03, Data: customPayload} + + txid, err := alice.SendOffChain( + ctx, + []clientTypes.Receiver{{To: bobAddr, Amount: 5000}}, + sdk.WithExtension(customPkt), + ) + require.NoError(t, err) + require.NotEmpty(t, txid) + + // Wait for alice and bob events + <-aliceTxStream + <-bobTxStream + + ext := fetchExtensionFromVirtualTx(t, alice.Indexer(), ctx, txid) + + // This is a pure-BTC SendOffChain (no receiver.Assets set), so + // createAssetPacket returns an empty packet and addExtension ends + // up writing an extension envelope that contains only the custom + // packet. A follow-up sub-test below exercises the asset+extra + // combined path via IssueAsset. + got := ext.GetPacketByType(0x03) + require.NotNil(t, got) + gotSerialized, err := got.Serialize() + require.NoError(t, err) + require.Equal(t, customPayload, gotSerialized) + }) +} + +// TestGetAssetDetails verifies that IssueAsset persists AssetInfo into the +// local AssetStore and that GetAssetDetails returns the expected metadata and +// control-asset linkage. +func TestGetAssetDetails(t *testing.T) { + runForEachStoreBackend(t, func(t *testing.T, backend testStoreBackend) { + setupClient := backend.setupClient + + ctx := t.Context() + + alice := setupClient(t) + faucetOffchain(t, alice, 0.01) + + metadata := []asset.Metadata{ + {Key: []byte("name"), Value: []byte("TestToken")}, + {Key: []byte("ticker"), Value: []byte("TTK")}, + } + + txid, assetIds, err := alice.IssueAsset(ctx, 1000, nil, metadata) + require.NoError(t, err) + require.NotEmpty(t, txid) + require.Len(t, assetIds, 1) + + assetId := assetIds[0].String() + + // Give the store a tick to settle the UpsertAsset call. + time.Sleep(500 * time.Millisecond) + + info, err := alice.GetAssetDetails(ctx, assetId) + require.NoError(t, err) + require.NotNil(t, info) + require.Equal(t, assetId, info.AssetId) + require.Len(t, info.Metadata, 2) + + mdMap := make(map[string]string, len(info.Metadata)) + for _, md := range info.Metadata { + mdMap[string(md.Key)] = string(md.Value) + } + require.Equal(t, "TestToken", mdMap["name"]) + require.Equal(t, "TTK", mdMap["ticker"]) + + // Negative case: a never-issued id should return a not-found error. + _, err = alice.GetAssetDetails(ctx, "0000000000000000000000000000000000000000000000000000000000000000deadbeef") + require.Error(t, err) + + // Exercise control asset linkage via NewControlAsset. + txid2, ctrlAssetIds, err := alice.IssueAsset( + ctx, 500, + clientTypes.NewControlAsset{Amount: 1}, + []asset.Metadata{{Key: []byte("name"), Value: []byte("CtrlLinked")}}, + ) + require.NoError(t, err) + require.NotEmpty(t, txid2) + require.Len(t, ctrlAssetIds, 2) + + controlAssetId := ctrlAssetIds[0].String() + linkedAssetId := ctrlAssetIds[1].String() + + time.Sleep(500 * time.Millisecond) + + controlInfo, err := alice.GetAssetDetails(ctx, controlAssetId) + require.NoError(t, err) + require.NotNil(t, controlInfo) + require.Equal(t, controlAssetId, controlInfo.AssetId) + // The control asset row should NOT self-reference via ControlAssetId. + require.Empty( + t, + controlInfo.ControlAssetId, + "control asset row should not self-link", + ) + + linkedInfo, err := alice.GetAssetDetails(ctx, linkedAssetId) + require.NoError(t, err) + require.NotNil(t, linkedInfo) + require.Equal(t, linkedAssetId, linkedInfo.AssetId) + require.Equal( + t, + controlAssetId, linkedInfo.ControlAssetId, + "linked asset must reference the control asset", + ) + }) +} + +// TestTransactionHistoryAssets verifies that GetTransactionHistory populates +// Transaction.Assets by aggregating the AssetPacket for an asset transfer. +func TestTransactionHistoryAssets(t *testing.T) { + runForEachStoreBackend(t, func(t *testing.T, backend testStoreBackend) { + setupClient := backend.setupClient + + ctx := t.Context() + + alice := setupClient(t) + bob := setupClient(t) + faucetOffchain(t, alice, 0.01) + + const supply = 5000 + const transferAmount = 1200 + _, assetIds, err := alice.IssueAsset( + ctx, supply, nil, []asset.Metadata{{Key: []byte("name"), Value: []byte("HistAsset")}}, + ) + require.NoError(t, err) + require.Len(t, assetIds, 1) + assetId := assetIds[0].String() + + time.Sleep(1 * time.Second) + + bobAddr, err := bob.NewOffchainAddress(ctx) + require.NoError(t, err) + + transferTxid, err := alice.SendOffChain(ctx, []clientTypes.Receiver{{ + To: bobAddr, + Amount: 400, + Assets: []clientTypes.Asset{{AssetId: assetId, Amount: transferAmount}}, + }}) + require.NoError(t, err) + require.NotEmpty(t, transferTxid) + + time.Sleep(2 * time.Second) + + history, err := alice.GetTransactionHistory(ctx) + require.NoError(t, err) + require.NotEmpty(t, history) + + transferTx := findTxByID(t, history, transferTxid) + require.NotNil(t, transferTx) + require.NotEmpty(t, transferTx.Assets) + + // The derived slice must contain the transferred asset id with + // the correct amount (from alice's POV, the output amount is the + // transferred amount, possibly plus any self-change re-issued to + // alice within the same group). + var sawAsset bool + var seenAmount uint64 + for _, a := range transferTx.Assets { + if a.AssetId == assetId { + sawAsset = true + seenAmount = a.Amount + } + } + require.True(t, sawAsset) + require.NotZero(t, seenAmount) + }) +} diff --git a/test/e2e/utils_test.go b/test/e2e/utils_test.go index c78fd8d8..705edcff 100644 --- a/test/e2e/utils_test.go +++ b/test/e2e/utils_test.go @@ -2,6 +2,7 @@ package e2e import ( "bytes" + "context" "encoding/hex" "encoding/json" "fmt" @@ -13,14 +14,17 @@ import ( "testing" "time" + "github.com/arkade-os/arkd/pkg/ark-lib/extension" transport "github.com/arkade-os/arkd/pkg/client-lib/client" grpcclient "github.com/arkade-os/arkd/pkg/client-lib/client/grpc" + "github.com/arkade-os/arkd/pkg/client-lib/indexer" clientTypes "github.com/arkade-os/arkd/pkg/client-lib/types" "github.com/arkade-os/arkd/pkg/client-lib/wallet" singlekeywallet "github.com/arkade-os/arkd/pkg/client-lib/wallet/singlekey" inmemorystore "github.com/arkade-os/arkd/pkg/client-lib/wallet/singlekey/store/inmemory" sdk "github.com/arkade-os/go-sdk" "github.com/arkade-os/go-sdk/types" + "github.com/btcsuite/btcd/btcutil/psbt" "github.com/btcsuite/btcd/btcec/v2" "github.com/stretchr/testify/require" ) @@ -182,6 +186,47 @@ func faucetOffchain(t *testing.T, client sdk.ArkClient, amount float64) clientTy return vtxo } +func faucetOffchainAndWait(t *testing.T, c sdk.ArkClient, txCh <-chan types.TransactionEvent, amount float64) { + t.Helper() + + wg := &sync.WaitGroup{} + wg.Go(func() { faucetOffchain(t, c, amount) }) + wg.Wait() + <-txCh +} + +func fetchExtensionFromVirtualTx(t *testing.T, idxr indexer.Indexer, ctx context.Context, txid string) extension.Extension { + t.Helper() + + time.Sleep(2 * time.Second) // give indexer time to ingest + + virtualResp, err := idxr.GetVirtualTxs(ctx, []string{txid}) + require.NoError(t, err) + require.NotNil(t, virtualResp) + require.Len(t, virtualResp.Txs, 1) + + ptx, err := psbt.NewFromRawBytes(strings.NewReader(virtualResp.Txs[0]), true) + require.NoError(t, err) + require.NotNil(t, ptx.UnsignedTx) + + ext, err := extension.NewExtensionFromTx(ptx.UnsignedTx) + require.NoError(t, err) + require.NotEmpty(t, ext) + + return ext +} + +func findTxByID(t *testing.T, history []clientTypes.Transaction, txid string) *clientTypes.Transaction { + t.Helper() + + for i := range history { + if history[i].TransactionKey.String() == txid { + return &history[i] + } + } + return nil +} + func generateNote(t *testing.T, amount uint64) string { adminHttpClient := &http.Client{ Timeout: 15 * time.Second, From fbfce09211038014706b20b3a3808c12bca7f7fa Mon Sep 17 00:00:00 2001 From: louisinger Date: Tue, 7 Apr 2026 15:40:31 +0200 Subject: [PATCH 02/10] bump ark-lib --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index f6d72524..5a927046 100644 --- a/go.mod +++ b/go.mod @@ -5,8 +5,8 @@ go 1.26.1 replace github.com/btcsuite/btcd/btcec/v2 => github.com/btcsuite/btcd/btcec/v2 v2.3.3 require ( - github.com/arkade-os/arkd/pkg/ark-lib v0.8.1-0.20260323091657-eeb0baef6937 - github.com/arkade-os/arkd/pkg/client-lib v0.0.0-20260403181126-d372608bf69c + github.com/arkade-os/arkd/pkg/ark-lib v0.8.1-0.20260407133725-a05ab40006f3 + github.com/arkade-os/arkd/pkg/client-lib v0.0.0-20260407133725-a05ab40006f3 github.com/btcsuite/btcd v0.24.3-0.20240921052913-67b8efd3ba53 github.com/btcsuite/btcd/btcec/v2 v2.3.5 github.com/btcsuite/btcd/btcutil v1.1.5 diff --git a/go.sum b/go.sum index d9a560f7..b7146017 100644 --- a/go.sum +++ b/go.sum @@ -22,10 +22,10 @@ github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8 github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= github.com/arkade-os/arkd/api-spec v0.0.0-20260323091657-eeb0baef6937 h1:010LTrhQYsGDSA2aE8CoCrOpcZFs1JQ5wPrBomtsfOw= github.com/arkade-os/arkd/api-spec v0.0.0-20260323091657-eeb0baef6937/go.mod h1:2+6ix1UGGE22Q/rbab1dycFPPKiTyq0gldKVqVfFPWs= -github.com/arkade-os/arkd/pkg/ark-lib v0.8.1-0.20260323091657-eeb0baef6937 h1:1r8NojJwAR/Q5hanPW0PzYJrhjZU5620HRAIqs+rItQ= -github.com/arkade-os/arkd/pkg/ark-lib v0.8.1-0.20260323091657-eeb0baef6937/go.mod h1:P5ipJ1CatvH6xKxEtMUt4KhUGl1JPLcFiSdo1PGTny8= -github.com/arkade-os/arkd/pkg/client-lib v0.0.0-20260403181126-d372608bf69c h1:jXBBMgkkiwtYJv4TmWp2KmTOp9Pqnwo74YauaBh43hw= -github.com/arkade-os/arkd/pkg/client-lib v0.0.0-20260403181126-d372608bf69c/go.mod h1:Y59cvCug7rHWjERYKbbrjStn4+vXGmWu5LOqLggf7Aw= +github.com/arkade-os/arkd/pkg/ark-lib v0.8.1-0.20260407133725-a05ab40006f3 h1:AV0Lu0pCW5ZOs1bk4pRLPkkT0nkCwQ5BE59uYsONKmk= +github.com/arkade-os/arkd/pkg/ark-lib v0.8.1-0.20260407133725-a05ab40006f3/go.mod h1:P5ipJ1CatvH6xKxEtMUt4KhUGl1JPLcFiSdo1PGTny8= +github.com/arkade-os/arkd/pkg/client-lib v0.0.0-20260407133725-a05ab40006f3 h1:aNTonyjojTZx9iTzv8bPLjMbJxfjIYQY5bINmKRcNoQ= +github.com/arkade-os/arkd/pkg/client-lib v0.0.0-20260407133725-a05ab40006f3/go.mod h1:Y59cvCug7rHWjERYKbbrjStn4+vXGmWu5LOqLggf7Aw= github.com/arkade-os/arkd/pkg/errors v0.0.0-20260303153651-8615412e4dea h1:x9ZwZL+F2b9E0uBZYBVjCLGtlqIE4zahDOY4C89h3X4= github.com/arkade-os/arkd/pkg/errors v0.0.0-20260303153651-8615412e4dea/go.mod h1:NYGE+baj57ynbXNwjISJddMDpMqAWOX27dV22xqFm2A= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= From 9d8ece4ff7e49beb67e15d31f23b9d910db2beef Mon Sep 17 00:00:00 2001 From: louisinger Date: Wed, 8 Apr 2026 14:08:20 +0200 Subject: [PATCH 03/10] persist assets and fix history --- client.go | 23 +++++++++++++++---- client_internal_test.go | 8 +++++-- go.mod | 4 ++-- go.sum | 8 +++---- sdk.go | 4 +--- store/service_test.go | 4 ++++ ...260408120000_add_tx_assets_column.down.sql | 1 + ...20260408120000_add_tx_assets_column.up.sql | 1 + store/sql/sqlc/queries/models.go | 1 + store/sql/sqlc/queries/query.sql.go | 12 ++++++---- store/sql/sqlc/query.sql | 4 ++-- store/sql/tx_store.go | 17 ++++++++++++++ test/e2e/extension_test.go | 5 +++- test/e2e/utils_test.go | 22 ++++++++++++++---- 14 files changed, 87 insertions(+), 27 deletions(-) create mode 100644 store/sql/migration/20260408120000_add_tx_assets_column.down.sql create mode 100644 store/sql/migration/20260408120000_add_tx_assets_column.up.sql diff --git a/client.go b/client.go index 76558d23..1a856a15 100644 --- a/client.go +++ b/client.go @@ -314,10 +314,18 @@ func (a *arkClient) GetTransactionHistory(ctx context.Context) ([]clientTypes.Tr sort.SliceStable(history, func(i, j int) bool { return history[i].CreatedAt.IsZero() || history[i].CreatedAt.After(history[j].CreatedAt) }) - // Derive the per-asset breakdown from AssetPacket for transactions - // that carry one. + // Prefer the per-asset breakdown persisted directly on the row (populated + // by vtxosToTxs from indexer-supplied vtxo.Assets). Fall back to deriving + // from AssetPacket for rows where only the packet is available (e.g. + // transactions persisted by wallet-side session/settlement handlers that + // had the signed PSBT in hand). for i := range history { - history[i].Assets = deriveAssetsFromPacket(history[i].AssetPacket, history[i].TransactionKey.String()) + if len(history[i].Assets) == 0 { + history[i].Assets = deriveAssetsFromPacket( + history[i].AssetPacket, + history[i].TransactionKey.String(), + ) + } } return history, nil } @@ -333,7 +341,7 @@ func deriveAssetsFromPacket(pkt asset.Packet, txid string) []clientTypes.Asset { order := make([]string, 0, len(pkt)) for groupIndex, group := range pkt { assetId := group.AssetId - // issuance case + // issuance case if assetId == nil && txid != "" { derived, err := asset.NewAssetId(txid, uint16(groupIndex)) if err == nil { @@ -355,7 +363,7 @@ func deriveAssetsFromPacket(pkt asset.Packet, txid string) []clientTypes.Asset { if len(order) == 0 { return nil } - + result := make([]clientTypes.Asset, 0, len(order)) for _, id := range order { result = append(result, clientTypes.Asset{ @@ -1698,6 +1706,7 @@ func (i *arkClient) vtxosToTxs( Type: clientTypes.TxReceived, CreatedAt: vtxo.CreatedAt, SettledBy: settledBy, + Assets: client.NetVtxoAssets([]clientTypes.Vtxo{vtxo}, append(settleVtxos, spentVtxos...)), }) } @@ -1745,6 +1754,7 @@ func (i *arkClient) vtxosToTxs( Amount: forfeitAmount - resultedAmount, Type: clientTypes.TxSent, CreatedAt: vtxo.CreatedAt, + Assets: client.NetVtxoAssets(vtxosBySettledBy[sb], resultedVtxos), }) } } @@ -1789,6 +1799,7 @@ func (i *arkClient) vtxosToTxs( Type: clientTypes.TxSent, CreatedAt: vtxo.CreatedAt, SettledBy: vtxo.SettledBy, + Assets: client.NetVtxoAssets(vtxosBySpentBy[sb], resultedVtxos), }) } @@ -1965,6 +1976,7 @@ func (a *arkClient) saveSendTransaction( CreatedAt: createdAt, Hex: res.Tx, AssetPacket: res.Extension.GetAssetPacket(), + Assets: client.NetVtxoAssets(res.Inputs, newVtxos), }, }); err != nil { log.Warnf("failed to add transactions: %s, skipping adding sent transaction", err) @@ -2000,6 +2012,7 @@ func (a *arkClient) saveBatchTransaction( CreatedAt: time.Now(), Hex: res.CommitmentTx, AssetPacket: res.Extension.GetAssetPacket(), + Assets: client.NetVtxoAssets(res.VtxoInputs, res.VtxoOutputs), }, }); err != nil { log.Warnf("failed to add sent transaction: %s, skipping", err) diff --git a/client_internal_test.go b/client_internal_test.go index db4919c5..7d566c88 100644 --- a/client_internal_test.go +++ b/client_internal_test.go @@ -134,7 +134,6 @@ func TestGroupSpentVtxosByTx(t *testing.T) { }) } - // TestDeriveAssetsFromPacket verifies that GetTransactionHistory's derived // Assets field aggregates AssetPacket outputs by asset id, in first-seen // order, deriving issuance ids from the txid + group index. @@ -267,5 +266,10 @@ func TestPersistIssuedAssets_NilStore(t *testing.T) { c := &arkClient{} // Should not panic. c.persistIssuedAssets(t.Context(), nil, nil, nil) - c.persistIssuedAssets(t.Context(), []asset.AssetId{*testAssetId(t, 1)}, clientTypes.NewControlAsset{}, nil) + c.persistIssuedAssets( + t.Context(), + []asset.AssetId{*testAssetId(t, 1)}, + clientTypes.NewControlAsset{}, + nil, + ) } diff --git a/go.mod b/go.mod index 5a927046..65406def 100644 --- a/go.mod +++ b/go.mod @@ -5,8 +5,8 @@ go 1.26.1 replace github.com/btcsuite/btcd/btcec/v2 => github.com/btcsuite/btcd/btcec/v2 v2.3.3 require ( - github.com/arkade-os/arkd/pkg/ark-lib v0.8.1-0.20260407133725-a05ab40006f3 - github.com/arkade-os/arkd/pkg/client-lib v0.0.0-20260407133725-a05ab40006f3 + github.com/arkade-os/arkd/pkg/ark-lib v0.8.1-0.20260408115656-3ab1c9e46f98 + github.com/arkade-os/arkd/pkg/client-lib v0.0.0-20260408115656-3ab1c9e46f98 github.com/btcsuite/btcd v0.24.3-0.20240921052913-67b8efd3ba53 github.com/btcsuite/btcd/btcec/v2 v2.3.5 github.com/btcsuite/btcd/btcutil v1.1.5 diff --git a/go.sum b/go.sum index b7146017..0d91be8a 100644 --- a/go.sum +++ b/go.sum @@ -22,10 +22,10 @@ github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8 github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= github.com/arkade-os/arkd/api-spec v0.0.0-20260323091657-eeb0baef6937 h1:010LTrhQYsGDSA2aE8CoCrOpcZFs1JQ5wPrBomtsfOw= github.com/arkade-os/arkd/api-spec v0.0.0-20260323091657-eeb0baef6937/go.mod h1:2+6ix1UGGE22Q/rbab1dycFPPKiTyq0gldKVqVfFPWs= -github.com/arkade-os/arkd/pkg/ark-lib v0.8.1-0.20260407133725-a05ab40006f3 h1:AV0Lu0pCW5ZOs1bk4pRLPkkT0nkCwQ5BE59uYsONKmk= -github.com/arkade-os/arkd/pkg/ark-lib v0.8.1-0.20260407133725-a05ab40006f3/go.mod h1:P5ipJ1CatvH6xKxEtMUt4KhUGl1JPLcFiSdo1PGTny8= -github.com/arkade-os/arkd/pkg/client-lib v0.0.0-20260407133725-a05ab40006f3 h1:aNTonyjojTZx9iTzv8bPLjMbJxfjIYQY5bINmKRcNoQ= -github.com/arkade-os/arkd/pkg/client-lib v0.0.0-20260407133725-a05ab40006f3/go.mod h1:Y59cvCug7rHWjERYKbbrjStn4+vXGmWu5LOqLggf7Aw= +github.com/arkade-os/arkd/pkg/ark-lib v0.8.1-0.20260408115656-3ab1c9e46f98 h1:X9+BAe8y7h3NEs+xk8nrlw3v+t3U2f+Gg8jsjVCoXKA= +github.com/arkade-os/arkd/pkg/ark-lib v0.8.1-0.20260408115656-3ab1c9e46f98/go.mod h1:P5ipJ1CatvH6xKxEtMUt4KhUGl1JPLcFiSdo1PGTny8= +github.com/arkade-os/arkd/pkg/client-lib v0.0.0-20260408115656-3ab1c9e46f98 h1:sLm8ZjDUhs5PrfXvnyb41HJuwOFAP//OSCl3ALsTPIg= +github.com/arkade-os/arkd/pkg/client-lib v0.0.0-20260408115656-3ab1c9e46f98/go.mod h1:Y59cvCug7rHWjERYKbbrjStn4+vXGmWu5LOqLggf7Aw= github.com/arkade-os/arkd/pkg/errors v0.0.0-20260303153651-8615412e4dea h1:x9ZwZL+F2b9E0uBZYBVjCLGtlqIE4zahDOY4C89h3X4= github.com/arkade-os/arkd/pkg/errors v0.0.0-20260303153651-8615412e4dea/go.mod h1:NYGE+baj57ynbXNwjISJddMDpMqAWOX27dV22xqFm2A= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= diff --git a/sdk.go b/sdk.go index 93129af5..a748e7e6 100644 --- a/sdk.go +++ b/sdk.go @@ -15,8 +15,6 @@ import ( "github.com/arkade-os/go-sdk/types" ) - - var Version string type ArkClient interface { @@ -117,4 +115,4 @@ type SendOffChainOption = client.SendOption // 0x00 is reserved type for asset packet (auto-generated) func WithExtension(packets ...extension.Packet) SendOffChainOption { return client.WithExtraCustomPacket(packets...) -} \ No newline at end of file +} diff --git a/store/service_test.go b/store/service_test.go index bb830afc..1d452dd9 100644 --- a/store/service_test.go +++ b/store/service_test.go @@ -282,6 +282,10 @@ var ( Amount: 12000, Type: clientTypes.TxReceived, AssetPacket: asset.Packet(testAssetGroups), + Assets: []clientTypes.Asset{ + {AssetId: "0000000000000000000000000000000000010000000a000000000000000000000000000000000000", Amount: 500}, + {AssetId: "0000000000000000000000000000000000010000000a000000000000000000000000000000000001", Amount: 250}, + }, }, } diff --git a/store/sql/migration/20260408120000_add_tx_assets_column.down.sql b/store/sql/migration/20260408120000_add_tx_assets_column.down.sql new file mode 100644 index 00000000..5b60a001 --- /dev/null +++ b/store/sql/migration/20260408120000_add_tx_assets_column.down.sql @@ -0,0 +1 @@ +ALTER TABLE tx DROP COLUMN assets; diff --git a/store/sql/migration/20260408120000_add_tx_assets_column.up.sql b/store/sql/migration/20260408120000_add_tx_assets_column.up.sql new file mode 100644 index 00000000..2fd5e00b --- /dev/null +++ b/store/sql/migration/20260408120000_add_tx_assets_column.up.sql @@ -0,0 +1 @@ +ALTER TABLE tx ADD COLUMN assets TEXT; diff --git a/store/sql/sqlc/queries/models.go b/store/sql/sqlc/queries/models.go index 1e79d474..500f224b 100644 --- a/store/sql/sqlc/queries/models.go +++ b/store/sql/sqlc/queries/models.go @@ -50,6 +50,7 @@ type Tx struct { Hex sql.NullString SettledBy sql.NullString AssetPacket sql.NullString + Assets sql.NullString } type Utxo struct { diff --git a/store/sql/sqlc/queries/query.sql.go b/store/sql/sqlc/queries/query.sql.go index d17cec36..424d459b 100644 --- a/store/sql/sqlc/queries/query.sql.go +++ b/store/sql/sqlc/queries/query.sql.go @@ -94,8 +94,8 @@ func (q *Queries) InsertAssetVtxo(ctx context.Context, arg InsertAssetVtxoParams const insertTx = `-- name: InsertTx :exec INSERT INTO tx ( - txid, txid_type, amount, type, created_at, hex, settled_by, settled, asset_packet -) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) + txid, txid_type, amount, type, created_at, hex, settled_by, settled, asset_packet, assets +) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ` type InsertTxParams struct { @@ -108,6 +108,7 @@ type InsertTxParams struct { SettledBy sql.NullString Settled bool AssetPacket sql.NullString + Assets sql.NullString } func (q *Queries) InsertTx(ctx context.Context, arg InsertTxParams) error { @@ -121,6 +122,7 @@ func (q *Queries) InsertTx(ctx context.Context, arg InsertTxParams) error { arg.SettledBy, arg.Settled, arg.AssetPacket, + arg.Assets, ) return err } @@ -246,7 +248,7 @@ func (q *Queries) ReplaceTx(ctx context.Context, arg ReplaceTxParams) error { } const selectAllTxs = `-- name: SelectAllTxs :many -SELECT txid, txid_type, amount, type, settled, created_at, hex, settled_by, asset_packet FROM tx +SELECT txid, txid_type, amount, type, settled, created_at, hex, settled_by, asset_packet, assets FROM tx ` func (q *Queries) SelectAllTxs(ctx context.Context) ([]Tx, error) { @@ -268,6 +270,7 @@ func (q *Queries) SelectAllTxs(ctx context.Context) ([]Tx, error) { &i.Hex, &i.SettledBy, &i.AssetPacket, + &i.Assets, ); err != nil { return nil, err } @@ -423,7 +426,7 @@ func (q *Queries) SelectSpendableVtxos(ctx context.Context) ([]AssetVtxoVw, erro } const selectTxs = `-- name: SelectTxs :many -SELECT txid, txid_type, amount, type, settled, created_at, hex, settled_by, asset_packet FROM tx +SELECT txid, txid_type, amount, type, settled, created_at, hex, settled_by, asset_packet, assets FROM tx WHERE txid IN (/*SLICE:txids*/?) ` @@ -456,6 +459,7 @@ func (q *Queries) SelectTxs(ctx context.Context, txids []string) ([]Tx, error) { &i.Hex, &i.SettledBy, &i.AssetPacket, + &i.Assets, ); err != nil { return nil, err } diff --git a/store/sql/sqlc/query.sql b/store/sql/sqlc/query.sql index 85a6c70d..8436524c 100644 --- a/store/sql/sqlc/query.sql +++ b/store/sql/sqlc/query.sql @@ -64,8 +64,8 @@ DELETE FROM asset; -- name: InsertTx :exec INSERT INTO tx ( - txid, txid_type, amount, type, created_at, hex, settled_by, settled, asset_packet -) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?); + txid, txid_type, amount, type, created_at, hex, settled_by, settled, asset_packet, assets +) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?); -- name: UpdateTx :exec UPDATE tx diff --git a/store/sql/tx_store.go b/store/sql/tx_store.go index 924d6a64..1050541c 100644 --- a/store/sql/tx_store.go +++ b/store/sql/tx_store.go @@ -113,6 +113,15 @@ func (v *txStore) AddTransactions(ctx context.Context, txs []clientTypes.Transac } } + var assetsJSON sql.NullString + if len(tx.Assets) > 0 { + buf, err := json.Marshal(tx.Assets) + if err != nil { + return err + } + assetsJSON = sql.NullString{String: string(buf), Valid: true} + } + if err := querierWithTx.InsertTx( ctx, queries.InsertTxParams{ Txid: tx.TransactionKey.String(), @@ -127,6 +136,7 @@ func (v *txStore) AddTransactions(ctx context.Context, txs []clientTypes.Transac String: hex.EncodeToString(serializedAssetPacket), Valid: len(serializedAssetPacket) > 0, }, + Assets: assetsJSON, }, ); err != nil { if strings.Contains(err.Error(), "UNIQUE constraint failed") { @@ -410,6 +420,12 @@ func rowToTx(row queries.Tx) (clientTypes.Transaction, error) { return clientTypes.Transaction{}, fmt.Errorf("failed to parse asset packet: %w", err) } } + var assets []clientTypes.Asset + if row.Assets.Valid && row.Assets.String != "" { + if err := json.Unmarshal([]byte(row.Assets.String), &assets); err != nil { + return clientTypes.Transaction{}, fmt.Errorf("failed to parse assets: %w", err) + } + } return clientTypes.Transaction{ TransactionKey: clientTypes.TransactionKey{ CommitmentTxid: commitmentTxid, @@ -422,6 +438,7 @@ func rowToTx(row queries.Tx) (clientTypes.Transaction, error) { CreatedAt: createdAt, Hex: row.Hex.String, AssetPacket: assetPacket, + Assets: assets, }, nil } diff --git a/test/e2e/extension_test.go b/test/e2e/extension_test.go index 396084b0..ca0fc2ee 100644 --- a/test/e2e/extension_test.go +++ b/test/e2e/extension_test.go @@ -119,7 +119,10 @@ func TestGetAssetDetails(t *testing.T) { require.Equal(t, "TTK", mdMap["ticker"]) // Negative case: a never-issued id should return a not-found error. - _, err = alice.GetAssetDetails(ctx, "0000000000000000000000000000000000000000000000000000000000000000deadbeef") + _, err = alice.GetAssetDetails( + ctx, + "0000000000000000000000000000000000000000000000000000000000000000deadbeef", + ) require.Error(t, err) // Exercise control asset linkage via NewControlAsset. diff --git a/test/e2e/utils_test.go b/test/e2e/utils_test.go index 705edcff..3d63fba8 100644 --- a/test/e2e/utils_test.go +++ b/test/e2e/utils_test.go @@ -24,8 +24,8 @@ import ( inmemorystore "github.com/arkade-os/arkd/pkg/client-lib/wallet/singlekey/store/inmemory" sdk "github.com/arkade-os/go-sdk" "github.com/arkade-os/go-sdk/types" - "github.com/btcsuite/btcd/btcutil/psbt" "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil/psbt" "github.com/stretchr/testify/require" ) @@ -186,7 +186,12 @@ func faucetOffchain(t *testing.T, client sdk.ArkClient, amount float64) clientTy return vtxo } -func faucetOffchainAndWait(t *testing.T, c sdk.ArkClient, txCh <-chan types.TransactionEvent, amount float64) { +func faucetOffchainAndWait( + t *testing.T, + c sdk.ArkClient, + txCh <-chan types.TransactionEvent, + amount float64, +) { t.Helper() wg := &sync.WaitGroup{} @@ -195,7 +200,12 @@ func faucetOffchainAndWait(t *testing.T, c sdk.ArkClient, txCh <-chan types.Tran <-txCh } -func fetchExtensionFromVirtualTx(t *testing.T, idxr indexer.Indexer, ctx context.Context, txid string) extension.Extension { +func fetchExtensionFromVirtualTx( + t *testing.T, + idxr indexer.Indexer, + ctx context.Context, + txid string, +) extension.Extension { t.Helper() time.Sleep(2 * time.Second) // give indexer time to ingest @@ -216,7 +226,11 @@ func fetchExtensionFromVirtualTx(t *testing.T, idxr indexer.Indexer, ctx context return ext } -func findTxByID(t *testing.T, history []clientTypes.Transaction, txid string) *clientTypes.Transaction { +func findTxByID( + t *testing.T, + history []clientTypes.Transaction, + txid string, +) *clientTypes.Transaction { t.Helper() for i := range history { From aea1c47b43e56fc8c0ca385a6fbcafe853e40cd9 Mon Sep 17 00:00:00 2001 From: louisinger Date: Wed, 8 Apr 2026 21:35:42 +0200 Subject: [PATCH 04/10] fix overflow --- client.go | 16 +++++++++++++--- sdk.go | 11 ----------- send.go | 20 ++++++++++++++------ send_opts.go | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 77 insertions(+), 20 deletions(-) create mode 100644 send_opts.go diff --git a/client.go b/client.go index 1a856a15..f3172560 100644 --- a/client.go +++ b/client.go @@ -1457,7 +1457,6 @@ func (a *arkClient) handleArkTx( }) } } else { - // Otherwise, add a new spent tx to the history. inAmount := uint64(0) for _, vtxo := range myVtxos { inAmount += vtxo.Amount @@ -1466,12 +1465,23 @@ func (a *arkClient) handleArkTx( for _, vtxo := range vtxosToAdd { outAmount += vtxo.Amount } + + var amount uint64 + txType := clientTypes.TxSent + switch { + case inAmount > outAmount: + amount = inAmount - outAmount + case outAmount > inAmount: + amount = outAmount - inAmount + txType = clientTypes.TxReceived + } + txsToAdd = append(txsToAdd, clientTypes.Transaction{ TransactionKey: clientTypes.TransactionKey{ ArkTxid: arkTx.Txid, }, - Amount: inAmount - outAmount, - Type: clientTypes.TxSent, + Amount: amount, + Type: txType, CreatedAt: time.Now(), AssetPacket: assetPacket, Hex: arkTx.Tx, diff --git a/sdk.go b/sdk.go index a748e7e6..3297e784 100644 --- a/sdk.go +++ b/sdk.go @@ -5,7 +5,6 @@ import ( "time" "github.com/arkade-os/arkd/pkg/ark-lib/asset" - "github.com/arkade-os/arkd/pkg/ark-lib/extension" client "github.com/arkade-os/arkd/pkg/client-lib" transport "github.com/arkade-os/arkd/pkg/client-lib/client" "github.com/arkade-os/arkd/pkg/client-lib/explorer" @@ -106,13 +105,3 @@ type InitWithWalletArgs struct { ExplorerURL string } -type SendOffChainOption = client.SendOption - -// WithExtension re-exports the client-lib option to append additional -// extension.Packet values to the OP_RETURN extension blob written by -// SendOffChain. -// -// 0x00 is reserved type for asset packet (auto-generated) -func WithExtension(packets ...extension.Packet) SendOffChainOption { - return client.WithExtraCustomPacket(packets...) -} diff --git a/send.go b/send.go index 858657de..1727cdc9 100644 --- a/send.go +++ b/send.go @@ -14,17 +14,25 @@ func (a *arkClient) SendOffChain( return "", err } + sdkOpts, err := applySendOffChainOptions(opts...) + if err != nil { + return "", err + } + vtxos, err := a.getSpendableVtxos(ctx, false) if err != nil { return "", err } - // Always supply our spendable vtxos; caller options follow. - res, err := a.ArkClient.SendOffChain( - ctx, - receivers, - append([]client.SendOption{client.WithVtxos(vtxos)}, opts...)..., - ) + // Always supply our spendable vtxos; SDK callers cannot override them. + clientOpts := []client.SendOption{client.WithVtxos(vtxos)} + if len(sdkOpts.extraExtensionPackets) > 0 { + clientOpts = append( + clientOpts, client.WithExtraCustomPacket(sdkOpts.extraExtensionPackets...), + ) + } + + res, err := a.ArkClient.SendOffChain(ctx, receivers, clientOpts...) if err != nil { return "", err } diff --git a/send_opts.go b/send_opts.go new file mode 100644 index 00000000..dc876f19 --- /dev/null +++ b/send_opts.go @@ -0,0 +1,50 @@ +package arksdk + +import ( + "fmt" + + "github.com/arkade-os/arkd/pkg/ark-lib/extension" +) + +type SendOffChainOption func(options *sendOffChainOptions) error + +// ApplySendOffChainOptions applies the given SendOffChainOption functions to a +// new default sendOffChainOptions struct and returns the first error +// encountered, if any. Exposed for use in external (arksdk_test) test packages. +func ApplySendOffChainOptions(opts ...SendOffChainOption) error { + _, err := applySendOffChainOptions(opts...) + return err +} + +// WithExtension appends additional extension.Packet values to the OP_RETURN +// extension blob written by SendOffChain. +// +// 0x00 is reserved for the asset packet (auto-generated) and is rejected by +// the underlying client when the options are forwarded. +func WithExtension(packets ...extension.Packet) SendOffChainOption { + return func(o *sendOffChainOptions) error { + o.extraExtensionPackets = append(o.extraExtensionPackets, packets...) + return nil + } +} + +func applySendOffChainOptions(opts ...SendOffChainOption) (*sendOffChainOptions, error) { + o := newDefaultSendOffChainOptions() + for _, opt := range opts { + if opt == nil { + return nil, fmt.Errorf("send off-chain option cannot be nil") + } + if err := opt(o); err != nil { + return nil, err + } + } + return o, nil +} + +type sendOffChainOptions struct { + extraExtensionPackets []extension.Packet +} + +func newDefaultSendOffChainOptions() *sendOffChainOptions { + return &sendOffChainOptions{} +} From 072b1980a894946b6c86f873b2e5c02619cb24c5 Mon Sep 17 00:00:00 2001 From: louisinger Date: Thu, 9 Apr 2026 09:11:17 +0200 Subject: [PATCH 05/10] lint --- client.go | 5 ++++- sdk.go | 1 - store/service_test.go | 10 ++++++++-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/client.go b/client.go index f3172560..1f32108e 100644 --- a/client.go +++ b/client.go @@ -1716,7 +1716,10 @@ func (i *arkClient) vtxosToTxs( Type: clientTypes.TxReceived, CreatedAt: vtxo.CreatedAt, SettledBy: settledBy, - Assets: client.NetVtxoAssets([]clientTypes.Vtxo{vtxo}, append(settleVtxos, spentVtxos...)), + Assets: client.NetVtxoAssets( + []clientTypes.Vtxo{vtxo}, + append(settleVtxos, spentVtxos...), + ), }) } diff --git a/sdk.go b/sdk.go index 3297e784..77f83115 100644 --- a/sdk.go +++ b/sdk.go @@ -104,4 +104,3 @@ type InitWithWalletArgs struct { Password string ExplorerURL string } - diff --git a/store/service_test.go b/store/service_test.go index 1d452dd9..fd4816fd 100644 --- a/store/service_test.go +++ b/store/service_test.go @@ -283,8 +283,14 @@ var ( Type: clientTypes.TxReceived, AssetPacket: asset.Packet(testAssetGroups), Assets: []clientTypes.Asset{ - {AssetId: "0000000000000000000000000000000000010000000a000000000000000000000000000000000000", Amount: 500}, - {AssetId: "0000000000000000000000000000000000010000000a000000000000000000000000000000000001", Amount: 250}, + { + AssetId: "0000000000000000000000000000000000010000000a000000000000000000000000000000000000", + Amount: 500, + }, + { + AssetId: "0000000000000000000000000000000000010000000a000000000000000000000000000000000001", + Amount: 250, + }, }, }, } From d06e07c8c919c77db89fb348d04de8ce0355f0a0 Mon Sep 17 00:00:00 2001 From: louisinger Date: Thu, 9 Apr 2026 09:30:14 +0200 Subject: [PATCH 06/10] remove useless tests --- client_internal_test.go | 142 ---------------------------------------- 1 file changed, 142 deletions(-) diff --git a/client_internal_test.go b/client_internal_test.go index 7d566c88..ec108dba 100644 --- a/client_internal_test.go +++ b/client_internal_test.go @@ -3,9 +3,7 @@ package arksdk import ( "testing" - "github.com/arkade-os/arkd/pkg/ark-lib/asset" clientTypes "github.com/arkade-os/arkd/pkg/client-lib/types" - "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/stretchr/testify/require" ) @@ -133,143 +131,3 @@ func TestGroupSpentVtxosByTx(t *testing.T) { }, vtxosToSettle) }) } - -// TestDeriveAssetsFromPacket verifies that GetTransactionHistory's derived -// Assets field aggregates AssetPacket outputs by asset id, in first-seen -// order, deriving issuance ids from the txid + group index. -func TestDeriveAssetsFromPacket(t *testing.T) { - t.Parallel() - - t.Run("nil packet returns nil", func(t *testing.T) { - t.Parallel() - require.Nil(t, deriveAssetsFromPacket(nil, "")) - }) - - t.Run("empty packet returns nil", func(t *testing.T) { - t.Parallel() - require.Nil(t, deriveAssetsFromPacket(asset.Packet{}, "")) - }) - - t.Run("single group single output", func(t *testing.T) { - t.Parallel() - pkt := mustBuildPacket(t, []testGroup{ - {id: testAssetId(t, 1), outs: []uint64{100}}, - }) - got := deriveAssetsFromPacket(pkt, "") - require.Len(t, got, 1) - require.Equal(t, testAssetId(t, 1).String(), got[0].AssetId) - require.Equal(t, uint64(100), got[0].Amount) - }) - - t.Run("multi-output group sums", func(t *testing.T) { - t.Parallel() - pkt := mustBuildPacket(t, []testGroup{ - {id: testAssetId(t, 1), outs: []uint64{50, 75, 25}}, - }) - got := deriveAssetsFromPacket(pkt, "") - require.Len(t, got, 1) - require.Equal(t, uint64(150), got[0].Amount) - }) - - t.Run("multiple asset ids preserve first-seen order", func(t *testing.T) { - t.Parallel() - pkt := mustBuildPacket(t, []testGroup{ - {id: testAssetId(t, 7), outs: []uint64{10}}, - {id: testAssetId(t, 3), outs: []uint64{20}}, - {id: testAssetId(t, 7), outs: []uint64{30}}, // same id as first group, should merge - }) - got := deriveAssetsFromPacket(pkt, "") - require.Len(t, got, 2) - require.Equal(t, testAssetId(t, 7).String(), got[0].AssetId) - require.Equal(t, uint64(40), got[0].Amount) // 10 + 30 - require.Equal(t, testAssetId(t, 3).String(), got[1].AssetId) - require.Equal(t, uint64(20), got[1].Amount) - }) - - t.Run("issuance group derives id from txid and group index", func(t *testing.T) { - t.Parallel() - - txid := testTxid(t, 9) - pkt := mustBuildPacket(t, []testGroup{ - {id: nil, outs: []uint64{12, 8}}, - }) - - got := deriveAssetsFromPacket(pkt, txid) - require.Len(t, got, 1) - derived, err := asset.NewAssetId(txid, 0) - require.NoError(t, err) - require.Equal(t, derived.String(), got[0].AssetId) - require.Equal(t, uint64(20), got[0].Amount) - }) -} - -// testGroup is a minimal test shape for building asset.Packet instances. -type testGroup struct { - id *asset.AssetId - outs []uint64 -} - -// mustBuildPacket assembles an asset.Packet from simple group descriptions. -// It constructs groups using a single fake non-nil AssetInput so that the -// reissuance/burn invariants in asset.NewAssetGroup don't reject them. -func mustBuildPacket(t *testing.T, groups []testGroup) asset.Packet { - t.Helper() - out := make([]asset.AssetGroup, 0, len(groups)) - for i, g := range groups { - outputs := make([]asset.AssetOutput, 0, len(g.outs)) - for j, amt := range g.outs { - o, err := asset.NewAssetOutput(uint16(j), amt) - require.NoError(t, err) - outputs = append(outputs, *o) - } - var inputs []asset.AssetInput - if g.id != nil { - // Build one matching input per output to satisfy input/output sum - // invariants enforced by asset.NewAssetGroup for non-issuance groups. - inputs = make([]asset.AssetInput, 0, len(g.outs)) - for k, amt := range g.outs { - in, err := asset.NewAssetInput(uint16(100+i*10+k), amt) - require.NoError(t, err) - inputs = append(inputs, *in) - } - } - grp, err := asset.NewAssetGroup(g.id, nil, inputs, outputs, nil) - require.NoError(t, err) - out = append(out, *grp) - } - return asset.Packet(out) -} - -// testAssetId fabricates a deterministic non-nil asset id from an index so -// that tests can distinguish multiple ids without pulling chain state. -func testAssetId(t *testing.T, index uint16) *asset.AssetId { - t.Helper() - h := chainhash.Hash{} - h[0] = byte(index) - id, err := asset.NewAssetId(h.String(), index) - require.NoError(t, err) - return id -} - -func testTxid(t *testing.T, seed byte) string { - t.Helper() - h := chainhash.Hash{} - h[0] = seed - return h.String() -} - -// TestPersistIssuedAssets_NilStore confirms that the convenience method -// tolerates a nil store (no panic), which matters when tests inject a -// minimal arkClient without a real store attached. -func TestPersistIssuedAssets_NilStore(t *testing.T) { - t.Parallel() - c := &arkClient{} - // Should not panic. - c.persistIssuedAssets(t.Context(), nil, nil, nil) - c.persistIssuedAssets( - t.Context(), - []asset.AssetId{*testAssetId(t, 1)}, - clientTypes.NewControlAsset{}, - nil, - ) -} From d21a34a57e64e341de3385c2469d5ce359ae884f Mon Sep 17 00:00:00 2001 From: louisinger Date: Thu, 9 Apr 2026 11:41:35 +0200 Subject: [PATCH 07/10] bump arklib --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 65406def..7ee59749 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.26.1 replace github.com/btcsuite/btcd/btcec/v2 => github.com/btcsuite/btcd/btcec/v2 v2.3.3 require ( - github.com/arkade-os/arkd/pkg/ark-lib v0.8.1-0.20260408115656-3ab1c9e46f98 + github.com/arkade-os/arkd/pkg/ark-lib v0.8.1-0.20260409081258-41d7735b56d3 github.com/arkade-os/arkd/pkg/client-lib v0.0.0-20260408115656-3ab1c9e46f98 github.com/btcsuite/btcd v0.24.3-0.20240921052913-67b8efd3ba53 github.com/btcsuite/btcd/btcec/v2 v2.3.5 diff --git a/go.sum b/go.sum index 0d91be8a..f9199b22 100644 --- a/go.sum +++ b/go.sum @@ -22,8 +22,8 @@ github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8 github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= github.com/arkade-os/arkd/api-spec v0.0.0-20260323091657-eeb0baef6937 h1:010LTrhQYsGDSA2aE8CoCrOpcZFs1JQ5wPrBomtsfOw= github.com/arkade-os/arkd/api-spec v0.0.0-20260323091657-eeb0baef6937/go.mod h1:2+6ix1UGGE22Q/rbab1dycFPPKiTyq0gldKVqVfFPWs= -github.com/arkade-os/arkd/pkg/ark-lib v0.8.1-0.20260408115656-3ab1c9e46f98 h1:X9+BAe8y7h3NEs+xk8nrlw3v+t3U2f+Gg8jsjVCoXKA= -github.com/arkade-os/arkd/pkg/ark-lib v0.8.1-0.20260408115656-3ab1c9e46f98/go.mod h1:P5ipJ1CatvH6xKxEtMUt4KhUGl1JPLcFiSdo1PGTny8= +github.com/arkade-os/arkd/pkg/ark-lib v0.8.1-0.20260409081258-41d7735b56d3 h1:slTDywVqExnJB/Xd94eQT3/Rjh8SVTncksuxIe2O0EI= +github.com/arkade-os/arkd/pkg/ark-lib v0.8.1-0.20260409081258-41d7735b56d3/go.mod h1:P5ipJ1CatvH6xKxEtMUt4KhUGl1JPLcFiSdo1PGTny8= github.com/arkade-os/arkd/pkg/client-lib v0.0.0-20260408115656-3ab1c9e46f98 h1:sLm8ZjDUhs5PrfXvnyb41HJuwOFAP//OSCl3ALsTPIg= github.com/arkade-os/arkd/pkg/client-lib v0.0.0-20260408115656-3ab1c9e46f98/go.mod h1:Y59cvCug7rHWjERYKbbrjStn4+vXGmWu5LOqLggf7Aw= github.com/arkade-os/arkd/pkg/errors v0.0.0-20260303153651-8615412e4dea h1:x9ZwZL+F2b9E0uBZYBVjCLGtlqIE4zahDOY4C89h3X4= From cdd81fa34af99e00b730572a8962c35eacfb095b Mon Sep 17 00:00:00 2001 From: louisinger Date: Mon, 13 Apr 2026 16:23:10 +0200 Subject: [PATCH 08/10] bump arklib --- go.mod | 14 +++++++------- go.sum | 28 ++++++++++++++-------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/go.mod b/go.mod index 7ee59749..9b864f2a 100644 --- a/go.mod +++ b/go.mod @@ -1,12 +1,12 @@ module github.com/arkade-os/go-sdk -go 1.26.1 +go 1.26.2 replace github.com/btcsuite/btcd/btcec/v2 => github.com/btcsuite/btcd/btcec/v2 v2.3.3 require ( - github.com/arkade-os/arkd/pkg/ark-lib v0.8.1-0.20260409081258-41d7735b56d3 - github.com/arkade-os/arkd/pkg/client-lib v0.0.0-20260408115656-3ab1c9e46f98 + github.com/arkade-os/arkd/pkg/ark-lib v0.8.1-0.20260413135435-00ad4e807aa7 + github.com/arkade-os/arkd/pkg/client-lib v0.0.0-20260413135435-00ad4e807aa7 github.com/btcsuite/btcd v0.24.3-0.20240921052913-67b8efd3ba53 github.com/btcsuite/btcd/btcec/v2 v2.3.5 github.com/btcsuite/btcd/btcutil v1.1.5 @@ -92,18 +92,18 @@ require ( go.etcd.io/etcd/raft/v3 v3.5.15 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 // indirect - go.opentelemetry.io/otel v1.40.0 // indirect + go.opentelemetry.io/otel v1.43.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 // indirect - go.opentelemetry.io/otel/metric v1.40.0 // indirect + go.opentelemetry.io/otel/metric v1.43.0 // indirect go.opentelemetry.io/otel/sdk v1.40.0 // indirect - go.opentelemetry.io/otel/trace v1.40.0 // indirect + go.opentelemetry.io/otel/trace v1.43.0 // indirect go.opentelemetry.io/proto/otlp v1.9.0 // indirect go.uber.org/atomic v1.11.0 // indirect golang.org/x/crypto v0.48.0 // indirect golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect golang.org/x/net v0.51.0 // indirect golang.org/x/sync v0.19.0 // indirect - golang.org/x/sys v0.41.0 // indirect + golang.org/x/sys v0.42.0 // indirect golang.org/x/term v0.40.0 // indirect golang.org/x/text v0.34.0 // indirect golang.org/x/time v0.8.0 // indirect diff --git a/go.sum b/go.sum index f9199b22..2dc8daf6 100644 --- a/go.sum +++ b/go.sum @@ -22,10 +22,10 @@ github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8 github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= github.com/arkade-os/arkd/api-spec v0.0.0-20260323091657-eeb0baef6937 h1:010LTrhQYsGDSA2aE8CoCrOpcZFs1JQ5wPrBomtsfOw= github.com/arkade-os/arkd/api-spec v0.0.0-20260323091657-eeb0baef6937/go.mod h1:2+6ix1UGGE22Q/rbab1dycFPPKiTyq0gldKVqVfFPWs= -github.com/arkade-os/arkd/pkg/ark-lib v0.8.1-0.20260409081258-41d7735b56d3 h1:slTDywVqExnJB/Xd94eQT3/Rjh8SVTncksuxIe2O0EI= -github.com/arkade-os/arkd/pkg/ark-lib v0.8.1-0.20260409081258-41d7735b56d3/go.mod h1:P5ipJ1CatvH6xKxEtMUt4KhUGl1JPLcFiSdo1PGTny8= -github.com/arkade-os/arkd/pkg/client-lib v0.0.0-20260408115656-3ab1c9e46f98 h1:sLm8ZjDUhs5PrfXvnyb41HJuwOFAP//OSCl3ALsTPIg= -github.com/arkade-os/arkd/pkg/client-lib v0.0.0-20260408115656-3ab1c9e46f98/go.mod h1:Y59cvCug7rHWjERYKbbrjStn4+vXGmWu5LOqLggf7Aw= +github.com/arkade-os/arkd/pkg/ark-lib v0.8.1-0.20260413135435-00ad4e807aa7 h1:hEPi51FcHADGG+5vhYPeMG0NLtzxjKzyCZoj4Q+u3Lw= +github.com/arkade-os/arkd/pkg/ark-lib v0.8.1-0.20260413135435-00ad4e807aa7/go.mod h1:gYJkHBV9B9OkSgANCvyST24HTlZ0tSmr1mZSsj+jGAc= +github.com/arkade-os/arkd/pkg/client-lib v0.0.0-20260413135435-00ad4e807aa7 h1:9KNN9nzhccWp2jKCpIUaAxzSwz1UOWN7PPvehwJFVJE= +github.com/arkade-os/arkd/pkg/client-lib v0.0.0-20260413135435-00ad4e807aa7/go.mod h1:StCLdfzfEK2voKaoApMfUAG+P+COMKwYmCps3tZ2VTg= github.com/arkade-os/arkd/pkg/errors v0.0.0-20260303153651-8615412e4dea h1:x9ZwZL+F2b9E0uBZYBVjCLGtlqIE4zahDOY4C89h3X4= github.com/arkade-os/arkd/pkg/errors v0.0.0-20260303153651-8615412e4dea/go.mod h1:NYGE+baj57ynbXNwjISJddMDpMqAWOX27dV22xqFm2A= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= @@ -445,20 +445,20 @@ go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 h1:r6I7RJCN86bpD/FQwedZ0vSixDpwuWREjW9oRMsmqDc= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0/go.mod h1:B9yO6b04uB80CzjedvewuqDhxJxi11s7/GtiGa8bAjI= -go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms= -go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g= +go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= +go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 h1:QKdN8ly8zEMrByybbQgv8cWBcdAarwmIPZ6FThrWXJs= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0/go.mod h1:bTdK1nhqF76qiPoCCdyFIV+N/sRHYXYCTQc+3VCi3MI= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0 h1:DvJDOPmSWQHWywQS6lKL+pb8s3gBLOZUtw4N+mavW1I= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0/go.mod h1:EtekO9DEJb4/jRyN4v4Qjc2yA7AtfCBuz2FynRUWTXs= -go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g= -go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc= +go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= +go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8= go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE= -go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw= -go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg= -go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw= -go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA= +go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= +go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= +go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= +go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= @@ -541,8 +541,8 @@ golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= -golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= From 5615e5770816f5ba3c9d42a3a0cb941182b670dd Mon Sep 17 00:00:00 2001 From: louisinger Date: Mon, 13 Apr 2026 16:25:37 +0200 Subject: [PATCH 09/10] CI: go 1.26.2 --- .github/workflows/integration.yaml | 2 +- .github/workflows/unit.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/integration.yaml b/.github/workflows/integration.yaml index c9942968..0784197c 100644 --- a/.github/workflows/integration.yaml +++ b/.github/workflows/integration.yaml @@ -18,7 +18,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v6 with: - go-version: '1.26.1' + go-version: '1.26.2' - run: go mod tidy diff --git a/.github/workflows/unit.yaml b/.github/workflows/unit.yaml index 2bc748ad..e817ee95 100755 --- a/.github/workflows/unit.yaml +++ b/.github/workflows/unit.yaml @@ -18,7 +18,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v6 with: - go-version: '1.26.1' + go-version: '1.26.2' - run: go mod tidy From 58ad978f04695015f0d1a06e1ed388d5b237ab9e Mon Sep 17 00:00:00 2001 From: louisinger Date: Mon, 13 Apr 2026 16:28:00 +0200 Subject: [PATCH 10/10] Dockerfile: go1.26.2 --- test/docker/server.Dockerfile | 2 +- test/docker/wallet.Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/docker/server.Dockerfile b/test/docker/server.Dockerfile index dcde8946..cf1a394b 100644 --- a/test/docker/server.Dockerfile +++ b/test/docker/server.Dockerfile @@ -1,5 +1,5 @@ # First image used to build the sources -FROM golang:1.26.1 AS builder +FROM golang:1.26.2 AS builder ARG TARGETOS ARG TARGETARCH diff --git a/test/docker/wallet.Dockerfile b/test/docker/wallet.Dockerfile index 9531ee8b..59578036 100644 --- a/test/docker/wallet.Dockerfile +++ b/test/docker/wallet.Dockerfile @@ -1,5 +1,5 @@ # First stage: build the ark-wallet-daemon binary -FROM golang:1.26.1 AS builder +FROM golang:1.26.2 AS builder ARG VERSION ARG TARGETOS