Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
- Feature Name: nano-contracts-support
- Start Date: 2025-01-02
- Author: @andreabadesso
- Update Date: 2025-07-07
- Author: @andreabadesso @r4mmer

# Summary

Expand All @@ -12,16 +13,19 @@ The Hathor Network is introducing nano-contracts. To maintain complete transacti

# Guide-level explanation

Currently, the wallet-service syncs with the fullnode through reliable integrations, which sends events in the exact order they occur. The daemon processes these events through a state machine (SyncMachine) that handles different types of events like new transactions, metadata changes, and voided transactions.
Currently, nano-contract transactions are any transaction with a nano-contract header, the protocol currently only supports nano-contract headers for normal transactions (version 1) and create token transactions (version 2), along with the usual tracking of inputs and outputs the main change that needs to be implemented is tracking the caller's (address caller from the header) relationship with the transaction, especially when they are not directly involved in any token transfer.

For nano-contracts, the main change is tracking the caller's relationship with the transaction, especially when they are not directly involved in token transfers. When a nano-contract transaction is received (identified by its version), we'll:
The wallet-service syncs with the fullnode through reliable integrations, which sends events in the exact order they occur. The daemon processes these events through a state machine (SyncMachine) that handles different types of events like new transactions, metadata changes, and voided transactions.

When a nano-contract transaction is received (identified by the header), we'll:

1. Process normal inputs/outputs as usual
2. Check if the caller is involved in inputs/outputs
3. If not, explicitly add the transaction to their history
4. Forward any contract-specific API calls to the fullnode
4. Update the seqnum for the caller's address
5. Forward any contract-specific API calls to the fullnode

This means that if address is the caller in a contract call that moves tokens from X to Y (e.g. a deposit action), all three addresses will see this transaction in their history:
This means that if address Z is the caller in a contract call that moves tokens from X to Y (e.g. a deposit action), all three addresses will see this transaction in their history:
Copy link
Member

Choose a reason for hiding this comment

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

This is not a problem bug "contract call that moves tokens from X to Y (e.g. a deposit action)" a deposit action moves tokens from X to the contract, not to an address Y. Maybe this address Y could be the change address.

- X and Y through normal UTXO tracking
- Z through explicit caller tracking

Expand All @@ -37,53 +41,50 @@ From the user's perspective, they will be able to:
- See all their contract interactions in their history, whether they moved tokens or just called the contract
- Create and execute contracts through the wallet-service API
- Query contract state through proxied fullnode APIs
### Seqnum tracking

The `seqnum` is a metadata of an address, it is a sequentially increasing number (with each nano contract sent from the address) that starts with 0 and is reset with each block (and round of nano contract executions) that is used to deduplicate nano contracts executions.

The `seqnum` will be cached for each address caller of a nano contract in redis and when a new block arrives the redis cache will be invalidated/cleared so that all addresses are reset to 0.
# Reference-level explanation

## Database Changes

No schema changes are required. The existing tables (`address_tx_history` and `wallet_tx_history`) will be used to track contract interactions, but we need to explicitly add entries for callers when they are not involved in inputs/outputs.

Important implementation notes:
1. The `updateAddressTablesWithTx` function needs to be modified to handle zero balances efficiently:
- Keep updating the `address` table for transaction count
- Skip `address_balance` updates when all values (balance and authorities) are zero
- Always add entries to `address_tx_history`, even for zero balances

2. The `voided` column in history tables applies to callers as well - if a nano-contract transaction is voided, all history entries (including the caller's) must be marked as voided
The relationship between a nano contract caller and the transaction will be made through the existing `address_tx_history` and `wallet_tx_history` tables, which require no changes but will have to be forced when the caller is not involved in any inputs/outputs.

## Daemon Changes

### SyncMachine Updates
The `handleVertexAccepted` service will be enhanced to handle nano-contract transactions:

1. Detect nano-contract transactions (by version)
2. Extract the caller information from the transaction metadata
1. Detect nano-contract transactions
2. Extract the caller information from the nano header
3. If the caller is not involved in any inputs or outputs:
- Use `updateAddressTablesWithTx` with a zero balance to add the transaction to their history
- The function will handle incrementing transaction count and adding history entries
- Use `getAddressBalanceMap` to add a zero balance HTR entry to the transaction addresses.
- The `updateAddressTablesWithTx` function will handle incrementing transaction count and adding history entries.

Example flow:
```typescript
// In handleVertexAccepted
if (isNanoContractTx(metadata)) {
const { caller } = metadata;
const addresses = new Set([
...inputs.map(i => i.address),
...outputs.map(o => o.address)
]);

// If caller is not in inputs or outputs, add to history
if (!addresses.has(caller)) {
// Create a map with zero balance for the HTR token
const zeroBalanceMap = {
[caller]: TokenBalanceMap.fromStringMap({
'00': { unlocked: 0, locked: 0 }
})
};

await updateAddressTablesWithTx(mysql, txId, timestamp, zeroBalanceMap);
}
// In getAddressBalanceMap
// ...
for (const header of headers) {
if (header.id !== '10') {
// We currently only handle nano contract headers
continue;
}

const address = header.nc_address;

if (addressBalanceMap[address]) {
// Already have balance for the nc_address
continue;
}

// Create an empty balance HTR entry if nc_address did not already have balance on the tx
const emptyHTR = new TokenBalanceMap();
const balance = emptyHTR.get(constants.NATIVE_TOKEN_UID)
emptyHTR.set(constants.NATIVE_TOKEN_UID, balance);
addressBalanceMap[address] = emptyHTR;
}
```

Expand All @@ -94,7 +95,7 @@ Scenario 1: X (caller) sends to Y
- Y appears in history (output)

Scenario 2: Z (caller) triggers X to send to Y
- Z appears in history (zero balance entry via updateAddressTablesWithTx)
- Z appears in history (zero balance entry via getAddressBalanceMap)
- X appears in history (input)
- Y appears in history (output)

Expand All @@ -103,6 +104,17 @@ Scenario 3: Y (caller) triggers X to send to Y
- Y appears in history (output)
```

### Seqnum tracking

The `seqnum` should be easily searched by address and listed (for clearing when a block arrives).
Copy link
Member

Choose a reason for hiding this comment

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

I was thinking about this tracking. We will set the current seqnum every time a new nano transaction arrives from the reliable integrations event. When building a new nano tx in the wallet lib, we need to know the next seqnum to use, so (i) we must have a way to fetch this seqnum from the wallet service, and (ii) how long does it take for the new event to be processed and the seqnum updated? Should we have a way to update this seqnum as soon as the transaction is pushed to the network (if done using the wallet service APIs)?


We could make it work with sparse keys with a common prefix but a better alternative is to use a JSON key, where each key will be the address and the value will be the seqnum.

- Clearing the cache can be done with [JSON.CLEAR](https://redis.io/docs/latest/commands/json.clear/)
Copy link
Contributor

Choose a reason for hiding this comment

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

I like the idea of using redis since we already have it in the stack

But can you describe better how this is going to work?

I mean, when are we going to clear cache? Or is the number only going up for all addresses?

- Incrementing the seqnum could be done with either [JSON.NUMINCRBY](https://redis.io/docs/latest/commands/json.numincrby/) or [JSON.GET](https://redis.io/docs/latest/commands/json.get/) + [JSON.SET](https://redis.io/docs/latest/commands/json.set/)

JSON keys can be inspected in many ways, but a good for debugging is [JSON.DEBUG MEMORY](https://redis.io/docs/latest/commands/json.debug-memory/) that returns the size of the key in bytes, so we know how much space it takes.

## Wallet Service Changes

### Proxy Endpoints
Expand All @@ -122,7 +134,6 @@ All nano contract endpoints will be proxied transparently, maintaining the same
- Preserve request parameters and body
- Handle errors appropriately (including 404s)
- Maintain proper authentication and rate limiting

# Drawbacks

1. Additional storage requirements for tracking caller information
Expand Down Expand Up @@ -162,4 +173,4 @@ Alternative approaches considered:
- Would require duplicating transaction data
- Increases complexity of history queries
- Makes it harder to maintain consistency
- Rejected due to unnecessary data duplication
- Rejected due to unnecessary data duplication