feat(wallet): expose claimable vouchers (#37)

Co-authored-by: Daniel <sanchez.quiros.daniel@gmail.com>
This commit is contained in:
Andrus Salumets 2026-06-18 16:10:34 +02:00 committed by GitHub
parent 3ad929ee4e
commit c9829f2fa6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 108 additions and 1 deletions

View File

@ -846,6 +846,36 @@ std::string LogosBlockchainModule::channel_deposit_with_notes(
return bytes_to_hex(reinterpret_cast<const uint8_t*>(&value), ADDRESS_BYTES);
}
std::string LogosBlockchainModule::wallet_get_claimable_vouchers() {
if (!node) {
return "Error: The node is not running.";
}
auto [value, error] = get_claimable_vouchers(node, nullptr);
if (!is_ok(&error)) {
return "Error: Failed to get claimable vouchers: " + std::to_string(error);
}
json obj;
obj["tip"] = bytes_to_hex(reinterpret_cast<const uint8_t*>(&value.tip), ADDRESS_BYTES);
obj["vouchers"] = json::array();
for (size_t i = 0; i < value.len; ++i) {
const ClaimableVoucher& voucher = value.vouchers[i];
obj["vouchers"].push_back({
{"commitment", bytes_to_hex(reinterpret_cast<const uint8_t*>(&voucher.commitment), ADDRESS_BYTES)},
{"nullifier", bytes_to_hex(reinterpret_cast<const uint8_t*>(&voucher.nullifier), ADDRESS_BYTES)},
});
}
const OperationStatus free_status = free_claimable_vouchers(value);
if (!is_ok(&free_status)) {
fprintf(stderr, "Failed to free claimable vouchers. Error: %d\n", free_status);
}
return obj.dump();
}
// Blend
std::string LogosBlockchainModule::blend_join_as_core_node(
@ -992,4 +1022,3 @@ std::string LogosBlockchainModule::get_cryptarchia_info() {
}
return obj.dump();
}

View File

@ -84,6 +84,7 @@ public:
const std::string& optional_tip_hex
);
std::string leader_claim();
std::string wallet_get_claimable_vouchers();
// Channel
// Amount-based deposit: the binding selects funding notes itself (splitting a

View File

@ -16,6 +16,7 @@ static uint8_t s_mockAddr1[32];
static uint8_t s_mockAddr2[32];
static uint8_t s_mockAddr3[32];
static uint8_t* s_mockAddrs[] = { s_mockAddr0, s_mockAddr1, s_mockAddr2, s_mockAddr3 };
static ClaimableVoucher s_mockClaimableVouchers[4];
extern "C" {
@ -169,6 +170,34 @@ OperationStatus free_known_addresses(KnownAddresses addrs) {
return 0;
}
FfiClaimableVouchersResult get_claimable_vouchers(LogosBlockchainNode* node, const HeaderId* optional_tip) {
LOGOS_CMOCK_RECORD("get_claimable_vouchers");
FfiClaimableVouchersResult result;
int err = LOGOS_CMOCK_RETURN(int, "get_claimable_vouchers_error");
result.error = err;
if (err == 0) {
int count = LOGOS_CMOCK_RETURN(int, "get_claimable_vouchers_count");
if (count > 4) count = 4;
memset(result.value.tip, 0xAA, sizeof(HeaderId));
for (int i = 0; i < count; ++i) {
memset(s_mockClaimableVouchers[i].commitment, 0x10 + i, sizeof(Hash));
memset(s_mockClaimableVouchers[i].nullifier, 0x20 + i, sizeof(Hash));
}
result.value.vouchers = s_mockClaimableVouchers;
result.value.len = static_cast<size_t>(count);
} else {
memset(result.value.tip, 0, sizeof(HeaderId));
result.value.vouchers = nullptr;
result.value.len = 0;
}
return result;
}
OperationStatus free_claimable_vouchers(ClaimableVouchers vouchers) {
LOGOS_CMOCK_RECORD("free_claimable_vouchers");
return 0;
}
// Wallet-notes mock storage (up to 4 notes)
static WalletNote s_mockNotes[4];

View File

@ -85,6 +85,17 @@ typedef struct {
size_t len;
} KnownAddresses;
typedef struct {
Hash commitment;
Hash nullifier;
} ClaimableVoucher;
typedef struct {
HeaderId tip;
ClaimableVoucher* vouchers;
size_t len;
} ClaimableVouchers;
// A single spendable wallet note (UTXO): its note ID and value.
typedef struct {
NoteId id;
@ -115,6 +126,7 @@ typedef struct { TxHash value; OperationStatus error; } FfiLeaderClaimResult;
typedef struct { Hash value; OperationStatus error; } FfiChannelDepositResult;
typedef struct { KnownAddresses value; OperationStatus error; } KnownAddressesResult;
typedef struct { WalletNotes value; OperationStatus error; } FfiWalletNotesResult;
typedef struct { ClaimableVouchers value; OperationStatus error; } FfiClaimableVouchersResult;
typedef struct { Hash value; OperationStatus error; } BlendHashResult;
typedef struct { char* value; OperationStatus error; } StringResult;
typedef struct { CryptarchiaInfo* value; OperationStatus error; } CryptarchiaInfoResult;
@ -181,6 +193,8 @@ FfiChannelDepositResult channel_deposit(LogosBlockchainNode* node, const Channel
FfiChannelDepositResult channel_deposit_with_notes(
LogosBlockchainNode* node,
const ChannelDepositWithNotesArguments* arguments);
FfiClaimableVouchersResult get_claimable_vouchers(LogosBlockchainNode* node, const HeaderId* optional_tip);
OperationStatus free_claimable_vouchers(ClaimableVouchers vouchers);
// Blend
BlendHashResult blend_join_as_core_node(

View File

@ -881,6 +881,40 @@ LOGOS_TEST(wallet_get_known_addresses_returns_empty_on_ffi_failure) {
delete module;
}
LOGOS_TEST(wallet_get_claimable_vouchers_returns_json) {
auto t = LogosTestContext("blockchain_module");
TempDir tmpDir;
auto* module = createStartedModule(t, tmpDir);
LOGOS_ASSERT_TRUE(module != nullptr);
t.mockCFunction("get_claimable_vouchers_error").returns(0);
t.mockCFunction("get_claimable_vouchers_count").returns(2);
std::string result = module->wallet_get_claimable_vouchers();
LOGOS_ASSERT_FALSE(starts_with(result, "Error:"));
LOGOS_ASSERT_TRUE(contains(result, "\"vouchers\""));
LOGOS_ASSERT_TRUE(contains(result, std::string(64, 'a')));
LOGOS_ASSERT_TRUE(contains(result, std::string(64, '1')));
LOGOS_ASSERT_TRUE(contains(result, "20202020"));
LOGOS_ASSERT(t.cFunctionCalled("get_claimable_vouchers"));
LOGOS_ASSERT(t.cFunctionCalled("free_claimable_vouchers"));
delete module;
}
LOGOS_TEST(wallet_get_claimable_vouchers_returns_error_on_ffi_failure) {
auto t = LogosTestContext("blockchain_module");
TempDir tmpDir;
auto* module = createStartedModule(t, tmpDir);
LOGOS_ASSERT_TRUE(module != nullptr);
t.mockCFunction("get_claimable_vouchers_error").returns(1);
std::string result = module->wallet_get_claimable_vouchers();
LOGOS_ASSERT_TRUE(starts_with(result, "Error:"));
LOGOS_ASSERT_TRUE(contains(result, "Failed to get claimable vouchers"));
delete module;
}
// Blend
LOGOS_TEST(blend_join_as_core_node_returns_declaration_id) {