Skip to content

feat: support USDC on order book functionality create market#1304

Merged
MicBun merged 3 commits intomainfrom
bridgeParam
Jan 26, 2026
Merged

feat: support USDC on order book functionality create market#1304
MicBun merged 3 commits intomainfrom
bridgeParam

Conversation

@MicBun
Copy link
Member

@MicBun MicBun commented Jan 26, 2026

resolves: https://github.com/truflation/website/issues/3205

Summary by CodeRabbit

  • New Features

    • Markets now accept and store structured query components; market IDs are derived from a deterministic attestation-style hash computed from those components.
  • Behavior Changes

    • Market creation API now takes query components directly and enforces uniqueness; fee transfer logic unified to a single transfer path.
  • Database

    • Added query_components column and index to support storage and lookup.
  • Tests

    • Extensive new and updated tests validating component encoding, attestation-hash compatibility, market creation, and edge cases.

✏️ Tip: You can customize this high-level summary in your review settings.

@MicBun MicBun self-assigned this Jan 26, 2026
@MicBun MicBun added the type: feat New feature or request label Jan 26, 2026
@holdex
Copy link

holdex bot commented Jan 26, 2026

Time Submission Status

Member Status Time Action Last Update
MicBun ✅ Submitted 12h Update time Jan 26, 2026, 11:57 PM

You can submit time with the command. Example:

@holdex pr submit-time 15m

See available commands to help comply with our Guidelines.

@coderabbitai
Copy link

coderabbitai bot commented Jan 26, 2026

Caution

Review failed

The pull request is closed.

📝 Walkthrough

Walkthrough

Adds a tn_utils precompile compute_attestation_hash that derives a deterministic SHA‑256 attestation hash from ABI‑encoded query components; updates order-book schema/actions to store query_components and compute market IDs via the precompile; converts order-book tests to use encoded query components.

Changes

Cohort / File(s) Summary
Precompile Implementation
extensions/tn_utils/precompiles.go
Added computeAttestationHashMethod(), computeAttestationHashHandler(), and getActionIDNumber() to ABI-decode query components, map action names to IDs, build length-prefixed attestation payload, and return SHA‑256 digest. Registered method in precompile bundle. (Note: duplicate implementation appears present.)
Precompile Tests
extensions/tn_utils/precompiles_test.go
New tests for valid inputs, determinism, error cases, and known vectors; includes ABI encoding and expected-hash construction helpers.
Order Book Schema Migration
internal/migrations/030-order-book-schema.sql
Added query_components BYTEA NOT NULL to ob_queries and index idx_ob_queries_components.
Order Book Actions & Logic
internal/migrations/032-order-book-actions.sql
Reworked create_market/related actions to accept query_components, compute $query_hash via compute_attestation_hash, validate 32‑byte result and uniqueness, store components, and return query_components and bridge in get_market_info. Simplified fee transfer path to a unified TRUF transfer call.
Test Helpers
tests/streams/order_book/test_helpers_orderbook.go
Added encodeQueryComponentsForTests() to ABI-pack (address, bytes32, string, bytes) query components and produce test inputs.
New & Updated Tests
tests/streams/order_book/create_market_with_components_test.go, hash_compatibility_test.go, and 13+ existing order-book tests
Added comprehensive market-creation tests using components; converted numerous tests to pass encoded query_components instead of SHA‑256 hashes; introduced helpers for chained balance injection and component encoding.
ERC20 Test Helper
tests/streams/utils/erc20/helper.go
Updated GetUserBalance() to map extensionAlias deterministically to an instanceID and query kwil_erc20_meta with [instanceID, userAddr].

Sequence Diagram(s)

sequenceDiagram
    participant Test as Test / Client
    participant Engine as Kwil Engine
    participant Precompile as tn_utils.precompile<br/>compute_attestation_hash
    participant DB as Database (ob_queries, markets)

    Test->>Test: ABI-encode query_components
    Test->>Engine: create_market(bridge, query_components, ...)
    Engine->>Precompile: compute_attestation_hash(query_components)
    Precompile->>Precompile: ABI-unpack (address, bytes32, string, bytes)
    Precompile->>Precompile: Map action string -> uint16
    Precompile->>Precompile: Build length-prefixed payload (ver, algo, fields)
    Precompile->>Precompile: SHA-256(payload) -> 32 bytes
    Precompile-->>Engine: return 32-byte hash
    Engine->>DB: check duplicate market by hash
    DB-->>Engine: OK / Duplicate error
    Engine->>DB: INSERT ob_queries (query_components) and markets (hash, bridge, ...)
    DB-->>Engine: confirmation
    Engine-->>Test: success / error response
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested reviewers

  • williamrusdyputra
  • outerlook

Poem

🐇 I nibble bytes and pack them tight,
I hop through ABI in moonlit night,
I stitch provider, stream, action, args neat,
SHA‑256 hums — a market's heartbeat.
Hooray! A hashed trail for trades to meet.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main feature: adding USDC support to order book market creation, which is the central change across all modified files (precompile methods, migrations, and test updates).
Docstring Coverage ✅ Passed Docstring coverage is 84.17% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Fix all issues with AI agents
In `@tests/streams/order_book/buy_order_test.go`:
- Around line 87-217: The test contains a large DEBUG block that queries DB
internals (calls to app.Engine.ExecuteWithoutEngineCtx,
testerc20.GetUserBalance, getBalance/getUSDCBalance and many t.Logf lines) which
must be removed or gated before merge; either delete the entire DEBUG section
(all calls that log reward_instances, balances, pending_data, topics, logs and
related counts/rows) or wrap it behind a clear test flag/verbose switch (e.g.,
an isDebugTest boolean) so it only runs when explicitly enabled and does not
execute in regular CI runs.

In `@tests/streams/order_book/create_market_with_components_test.go`:
- Line 81: The test streamID string literals exceed 32 bytes and will be
truncated when copied into the [32]byte in encodeQueryComponentsABI, risking
collisions; update each test's streamID literal (the variables named streamID
used in create_market_with_components_test.go) so they are exactly 32 characters
long and remain unique (either remove or add characters to reach 32 chars) and
re-run tests to ensure no unintended truncation occurs.

In `@tests/streams/order_book/hash_compatibility_test.go`:
- Around line 328-329: The stream ID literals streamID1 and streamID2 in the
hash_compatibility_test (variables streamID1 and streamID2) are only 30
characters; update them to be exactly 32 characters (e.g., append two characters
or change the suffix) so the test matches the "exactly 32 characters"
expectation and avoids any padding/hash mismatches, and adjust the surrounding
comment if present to confirm 32-char requirement.

In `@tests/streams/order_book/market_creation_test.go`:
- Around line 51-55: The package-level counters trufPointCounter and
usdcPointCounter are unsafe for parallel tests because giveBalance increments
them; replace them with concurrency-safe counters (e.g., use sync/atomic
operations via atomic.Int64 or int64 variables updated with atomic.AddInt64) or
convert them to test-local state passed into giveBalance so each test controls
its own counter; update references to trufPointCounter++/usdcPointCounter++ in
giveBalance and tests to use atomic.AddInt64(&trufPointCounter, 1) or the
test-local increment function, and ensure giveBalance signature or call sites
are adjusted to accept the per-test counter if you choose the test-local
approach.
- Around line 334-349: The stream ID padding logic uses string(make([]byte,
...)) which inserts null bytes and then truncates silently; update the
construction of streamID used before encodeQueryComponents and callCreateMarket
so it builds a short base (e.g., "stlistmarkets%d"), then pad with a printable
filler (like '0' or spaces) using strings.Repeat to exactly 32 characters and
slice safely: create base := fmt.Sprintf("stlistmarkets%d", i), then streamID :=
(base + strings.Repeat("0", 32))[:32]; replace the existing streamID
creation/trim logic with this to avoid null bytes and accidental data loss when
encoding/querying.
🧹 Nitpick comments (13)
extensions/tn_utils/precompiles.go (2)

583-598: Consider whether IsTable: true is intentional.

The method returns IsTable: true but only ever returns a single hash value. While this works, it's worth confirming this is the intended behavior rather than IsTable: false with a single return field.


612-635: Consider caching ABI type definitions for performance.

The ABI type definitions (addressType, bytes32Type, etc.) are recreated on every handler invocation. Since these are immutable, consider defining them once at package level to avoid repeated allocations.

♻️ Suggested optimization
// Package-level ABI argument definition (computed once)
var queryComponentsABIArgs gethAbi.Arguments

func init() {
    addressType, _ := gethAbi.NewType("address", "", nil)
    bytes32Type, _ := gethAbi.NewType("bytes32", "", nil)
    stringType, _ := gethAbi.NewType("string", "", nil)
    bytesType, _ := gethAbi.NewType("bytes", "", nil)
    
    queryComponentsABIArgs = gethAbi.Arguments{
        {Type: addressType, Name: "data_provider"},
        {Type: bytes32Type, Name: "stream_id"},
        {Type: stringType, Name: "action_id"},
        {Type: bytesType, Name: "args"},
    }
}

Then in the handler, simply use:

-    // Define ABI type for query components: (address, bytes32, string, bytes)
-    addressType, err := gethAbi.NewType("address", "", nil)
-    // ... (all type definitions)
-    args := gethAbi.Arguments{...}
-
-    // Decode ABI
-    decoded, err := args.Unpack(queryComponents)
+    // Decode ABI using pre-defined arguments
+    decoded, err := queryComponentsABIArgs.Unpack(queryComponents)
internal/migrations/030-order-book-schema.sql (1)

95-97: Consider deferring this index until query patterns are established.

The comment states this index is for "potential future queries." Indexes on BYTEA columns can be expensive and are typically only useful for exact-match lookups. Consider adding this index when concrete query patterns justify it.

tests/streams/order_book/test_helpers_orderbook.go (2)

16-72: Code duplication with encodeQueryComponents in market_creation_test.go.

This function largely duplicates encodeQueryComponents defined in market_creation_test.go (lines 611-650). The only difference is the default args handling (lines 17-32). Consider consolidating these into a single helper to avoid maintenance burden.

♻️ Suggested consolidation

Either:

  1. Move the shared ABI encoding logic to a single location and have both call sites use it
  2. Or make encodeQueryComponentsForTests the canonical implementation and remove the duplicate from market_creation_test.go
// In test_helpers_orderbook.go - keep this as the canonical version
// In market_creation_test.go - replace encodeQueryComponents calls with encodeQueryComponentsForTests

62-65: Stream ID silently truncated if longer than 32 bytes.

If streamID exceeds 32 characters, it will be silently truncated. While test stream IDs appear carefully sized, adding validation would prevent subtle bugs.

♻️ Optional: Add length validation
 	// Convert stream ID to bytes32 (pad with zeros on the right)
 	var streamIDBytes [32]byte
+	if len(streamID) > 32 {
+		return nil, fmt.Errorf("streamID exceeds 32 bytes: %d", len(streamID))
+	}
 	copy(streamIDBytes[:], []byte(streamID))
tests/streams/order_book/rewards_test.go (1)

740-743: Minor: Extra blank lines after encoding.

Lines 741-742 have unnecessary blank lines between the encoding call and require.NoError. While not a bug, it's inconsistent with other tests in the file.

♻️ Remove extra blank lines
 		queryComponents, err := encodeQueryComponentsForTests(user1.Address(), "sttest00000000000000000000000044", "get_record", []byte{0x01})
-
-
-
 		require.NoError(t, err)
tests/streams/order_book/market_creation_test.go (1)

609-650: Duplicate of encodeQueryComponentsForTests.

This encodeQueryComponents function is nearly identical to encodeQueryComponentsForTests in test_helpers_orderbook.go. Consider removing this duplicate and using the shared helper instead.

tests/streams/order_book/hash_compatibility_test.go (2)

59-62: Global mutable state may cause test isolation issues.

The package-level variables hashCompatBalanceCounter and hashCompatLastPoint are shared across tests. While you reset hashCompatLastPoint at the start of each test function, if tests are ever run in parallel (e.g., via t.Parallel()), this could cause race conditions or flaky tests.

Consider encapsulating this state within a test helper struct passed to each test, or document that these tests must not run in parallel.


479-514: Duplicate ABI encoding helper function.

This function encodeQueryComponentsABIHashCompat is nearly identical to encodeQueryComponentsABI in create_market_with_components_test.go and possibly encodeQueryComponentsForTests in test_helpers_orderbook.go. Consider consolidating into a single shared helper to reduce duplication and ensure consistency.

tests/streams/order_book/create_market_with_components_test.go (3)

43-46: Global mutable state for balance tracking.

Same concern as the other test file - these package-level variables could cause issues if tests run in parallel. The comment notes it uses a different starting counter (200) to avoid conflicts, which helps when running tests sequentially but doesn't prevent race conditions in parallel execution.


84-84: Inconsistent helper function usage.

This test uses encodeQueryComponentsForTests while all other tests in this file use the local encodeQueryComponentsABI function. If these helpers have even slightly different implementations, tests could pass/fail inconsistently.

Consider using a single helper consistently throughout the file.


241-243: Missing assertion for error message content.

The comment indicates the error should mention "decoding or invalid components", but this is only logged, not asserted. Consider adding an assertion to verify the error message provides meaningful feedback.

 		require.Error(t, err, "malformed ABI should be rejected")
-		// Error should mention decoding or invalid components
-		t.Logf("Malformed ABI error: %v", err)
+		// Verify error message is meaningful
+		require.True(t, 
+			strings.Contains(err.Error(), "decode") || 
+			strings.Contains(err.Error(), "invalid") ||
+			strings.Contains(err.Error(), "components"),
+			"error should mention decoding or invalid components: %v", err)
internal/migrations/032-order-book-actions.sql (1)

278-280: Documentation references outdated hash mechanism.

The comment mentions "SHA256 hash" but the implementation now computes hashes via tn_utils.compute_attestation_hash. Consider updating the comment to reflect the current mechanism.

- * - $query_hash: The SHA256 hash of the market's attestation query
+ * - $query_hash: The attestation hash of the market's query components

Comment on lines +87 to +217
// DEBUG: Check balances AFTER initialization
trufBal, err := getBalance(ctx, platform, userAddr.Address())
require.NoError(t, err)
usdcBal, err := getUSDCBalance(ctx, platform, userAddr.Address())
require.NoError(t, err)
t.Logf("DEBUG: After init - TRUF: %s (expected 100e18), USDC: %s (expected 100e18)", trufBal.String(), usdcBal.String())

// DEBUG: Try to get balance directly from DB to see if it's there
balanceStr, err := testerc20.GetUserBalance(ctx, platform, testUSDCExtensionName, userAddr.Address())
t.Logf("DEBUG: USDC balance from GetUserBalance: %s (err: %v)", balanceStr, err)

// DEBUG: Check what extension instances exist
app := &common.App{DB: platform.DB, Engine: platform.Engine}
var instanceCount int
_ = app.Engine.ExecuteWithoutEngineCtx(ctx, app.DB, `{kwil_erc20_meta}SELECT COUNT(*) FROM reward_instances`, nil, func(r *common.Row) error {
instanceCount = int(r.Values[0].(int64))
return nil
})
t.Logf("DEBUG: Total ERC20 instances in DB: %d", instanceCount)

// DEBUG: List all instances with sync status
_ = app.Engine.ExecuteWithoutEngineCtx(ctx, app.DB, `{kwil_erc20_meta}
SELECT escrow_address, synced, erc20_address, erc20_decimals
FROM reward_instances`, nil, func(r *common.Row) error {
escrow := r.Values[0].([]byte)
synced := r.Values[1].(bool)
var erc20Addr []byte
if r.Values[2] != nil {
erc20Addr = r.Values[2].([]byte)
}
var decimals *int64
if r.Values[3] != nil {
d := r.Values[3].(int64)
decimals = &d
}
t.Logf("DEBUG: Instance escrow=0x%x synced=%v erc20=0x%x decimals=%v",
escrow, synced, erc20Addr, decimals)
return nil
})

// DEBUG: Check user balances table (correct table is 'balances', column is 'address')
var balanceCount int
_ = app.Engine.ExecuteWithoutEngineCtx(ctx, app.DB, `{kwil_erc20_meta}SELECT COUNT(*) FROM balances WHERE address = $addr`,
map[string]any{"$addr": []byte(userAddr.Bytes())}, func(r *common.Row) error {
balanceCount = int(r.Values[0].(int64))
return nil
})
t.Logf("DEBUG: User has %d balance records in balances table", balanceCount)

// DEBUG: Show actual balance values for this user
_ = app.Engine.ExecuteWithoutEngineCtx(ctx, app.DB, `{kwil_erc20_meta}
SELECT b.balance, r.escrow_address, r.erc20_address
FROM balances b
JOIN reward_instances r ON b.reward_id = r.id
WHERE b.address = $addr`,
map[string]any{"$addr": []byte(userAddr.Bytes())}, func(r *common.Row) error {
balance := r.Values[0].(*types.Decimal).String()
escrow := "0x" + hex.EncodeToString(r.Values[1].([]byte))
erc20 := "0x" + hex.EncodeToString(r.Values[2].([]byte))
t.Logf("DEBUG: Balance=%s escrow=%s erc20=%s", balance, escrow, erc20)
return nil
})

// DEBUG: Check if ordered-sync namespace exists
var namespaceExists bool
err = app.Engine.ExecuteWithoutEngineCtx(ctx, app.DB, `SELECT 1`, nil, func(r *common.Row) error {
namespaceExists = true
return nil
})
t.Logf("DEBUG: Can execute queries: %v (err: %v)", namespaceExists, err)

// DEBUG: Check if ordered-sync pending_data were stored
var pendingCount int
err = app.Engine.ExecuteWithoutEngineCtx(ctx, app.DB, `{kwil_ordered_sync}SELECT COUNT(*) FROM pending_data`, nil, func(r *common.Row) error {
pendingCount = int(r.Values[0].(int64))
return nil
})
t.Logf("DEBUG: Total ordered-sync pending_data: %d (err: %v)", pendingCount, err)

// DEBUG: Check topics table
var topicCount int
err = app.Engine.ExecuteWithoutEngineCtx(ctx, app.DB, `{kwil_ordered_sync}SELECT COUNT(*) FROM topics`, nil, func(r *common.Row) error {
topicCount = int(r.Values[0].(int64))
return nil
})
t.Logf("DEBUG: Total ordered-sync topics: %d (err: %v)", topicCount, err)

// DEBUG: List pending_data points with topic info
_ = app.Engine.ExecuteWithoutEngineCtx(ctx, app.DB, `{kwil_ordered_sync}
SELECT p.point, p.previous_point, t.name, t.last_processed_point
FROM pending_data p
JOIN topics t ON p.topic_id = t.id
ORDER BY p.point`, nil, func(r *common.Row) error {
point := r.Values[0].(int64)
var prevPoint *int64
if r.Values[1] != nil {
p := r.Values[1].(int64)
prevPoint = &p
}
topicName := r.Values[2].(string)
var lastProcessed *int64
if r.Values[3] != nil {
lp := r.Values[3].(int64)
lastProcessed = &lp
}
var lpStr string
if lastProcessed != nil {
lpStr = fmt.Sprintf("%d", *lastProcessed)
} else {
lpStr = "nil"
}
var prevStr string
if prevPoint != nil {
prevStr = fmt.Sprintf("%d", *prevPoint)
} else {
prevStr = "nil"
}
t.Logf("DEBUG: pending point=%d prev=%s topic=%s last_processed=%s MATCH=%v",
point, prevStr, topicName, lpStr, (prevPoint == nil && lastProcessed == nil) || (prevPoint != nil && lastProcessed != nil && *prevPoint == *lastProcessed))
return nil
})

// DEBUG: Check finalized status
_ = app.Engine.ExecuteWithoutEngineCtx(ctx, app.DB, `{kwil_ordered_sync}SELECT topic, point, finalized FROM logs ORDER BY point`, nil, func(r *common.Row) error {
topic := r.Values[0].(string)
point := r.Values[1].(int64)
finalized := r.Values[2].(bool)
t.Logf("DEBUG: Log topic=%s point=%d finalized=%v", topic, point, finalized)
return nil
})

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove extensive DEBUG logging before merge.

This block contains ~130 lines of debug code that queries internal tables (reward_instances, balances, pending_data, topics, logs), checks singleton state, and logs detailed internal state. While useful during development, this verbose debugging should be removed or gated behind a test flag before merging.

🧹 Suggested cleanup

Either:

  1. Remove the DEBUG blocks entirely
  2. Or wrap them in a conditional:
+const debugBuyOrderTests = false // Set to true for debugging

 // In testBuyOrderSuccessful:
+if debugBuyOrderTests {
   // DEBUG: Check balances AFTER initialization
   trufBal, err := getBalance(ctx, platform, userAddr.Address())
   // ... rest of debug code ...
+}
🤖 Prompt for AI Agents
In `@tests/streams/order_book/buy_order_test.go` around lines 87 - 217, The test
contains a large DEBUG block that queries DB internals (calls to
app.Engine.ExecuteWithoutEngineCtx, testerc20.GetUserBalance,
getBalance/getUSDCBalance and many t.Logf lines) which must be removed or gated
before merge; either delete the entire DEBUG section (all calls that log
reward_instances, balances, pending_data, topics, logs and related counts/rows)
or wrap it behind a clear test flag/verbose switch (e.g., an isDebugTest
boolean) so it only runs when explicitly enabled and does not execute in regular
CI runs.

Comment on lines 51 to 55
var (
twoTRUF = mustParseBigInt(marketFee)
pointCounter int64 = 100 // Start from 100 to avoid conflicts with other tests
twoTRUF = mustParseBigInt(marketFee)
trufPointCounter int64 = 100 // Counter for TRUF bridge (hoodi_tt)
usdcPointCounter int64 = 10000 // Counter for USDC bridge (hoodi_tt2) - far apart to avoid conflicts
)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Global point counters may cause issues with parallel test execution.

trufPointCounter and usdcPointCounter are package-level variables that are incremented in giveBalance. If tests run in parallel (e.g., with t.Parallel()), this could cause race conditions or unexpected point values.

🔧 Suggested fix: Use atomic counters or test-local state
 var (
 	twoTRUF               = mustParseBigInt(marketFee)
-	trufPointCounter     int64 = 100   // Counter for TRUF bridge (hoodi_tt)
-	usdcPointCounter     int64 = 10000 // Counter for USDC bridge (hoodi_tt2) - far apart to avoid conflicts
+	trufPointCounter     atomic.Int64  // Counter for TRUF bridge (hoodi_tt)
+	usdcPointCounter     atomic.Int64  // Counter for USDC bridge (hoodi_tt2)
 )
+
+func init() {
+	trufPointCounter.Store(100)
+	usdcPointCounter.Store(10000)
+}

And use trufPointCounter.Add(1) instead of trufPointCounter++.

🤖 Prompt for AI Agents
In `@tests/streams/order_book/market_creation_test.go` around lines 51 - 55, The
package-level counters trufPointCounter and usdcPointCounter are unsafe for
parallel tests because giveBalance increments them; replace them with
concurrency-safe counters (e.g., use sync/atomic operations via atomic.Int64 or
int64 variables updated with atomic.AddInt64) or convert them to test-local
state passed into giveBalance so each test controls its own counter; update
references to trufPointCounter++/usdcPointCounter++ in giveBalance and tests to
use atomic.AddInt64(&trufPointCounter, 1) or the test-local increment function,
and ensure giveBalance signature or call sites are adjusted to accept the
per-test counter if you choose the test-local approach.

@MicBun
Copy link
Member Author

MicBun commented Jan 26, 2026

Log: the patch is too large, will work the rest on the next PRs

@MicBun MicBun merged commit 0143dfa into main Jan 26, 2026
4 of 6 checks passed
@MicBun MicBun deleted the bridgeParam branch January 26, 2026 23:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type: feat New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant