Skip to content

Commit 67332ce

Browse files
authored
feat: support for Stacks 2.1 stx-transfer event memo field (#1285)
* feat: support for Stacks 2.1 stx-transfer event memo field (the `stx-transfer-memo` Clarity2 function) * fix: remove null memo field from different asset transfer events * test: unit tests for Stacks 2.1 stx-transfer event memo field
1 parent 7e5752a commit 67332ce

File tree

11 files changed

+223
-12
lines changed

11 files changed

+223
-12
lines changed

docs/entities/transaction-events/asset-types/transaction-event-asset.schema.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
},
2121
"value": {
2222
"type": "string"
23+
},
24+
"memo": {
25+
"type": "string"
2326
}
2427
}
2528
}

docs/generated.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -747,6 +747,7 @@ export interface TransactionEventAsset {
747747
recipient?: string;
748748
amount?: string;
749749
value?: string;
750+
memo?: string;
750751
}
751752
/**
752753
* GET request that returns address balances

src/api/controllers/db-controller.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,9 @@ export function parseDbEvent(dbEvent: DbEvent): TransactionEvent {
243243
amount: dbEvent.amount.toString(10),
244244
},
245245
};
246+
if (dbEvent.asset_event_type_id === DbAssetEventTypeId.Transfer && dbEvent.memo) {
247+
event.asset.memo = dbEvent.memo;
248+
}
246249
return event;
247250
}
248251
case DbEventTypeId.FungibleTokenAsset: {

src/datastore/common.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,7 @@ interface DbAssetEvent extends DbEventBase {
325325
export interface DbStxEvent extends DbAssetEvent {
326326
event_type: DbEventTypeId.StxAsset;
327327
amount: bigint;
328+
memo?: string;
328329
}
329330

330331
interface DbContractAssetEvent extends DbAssetEvent {
@@ -1009,6 +1010,7 @@ export interface StxEventInsertValues {
10091010
sender: string | null;
10101011
recipient: string | null;
10111012
amount: bigint;
1013+
memo: PgBytea | null;
10121014
}
10131015

10141016
export interface MinerRewardInsertValues {

src/datastore/helpers.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,7 @@ export function parseDbEvents(
398398
sender?: string | undefined;
399399
recipient?: string | undefined;
400400
amount: string;
401+
memo?: string;
401402
}[],
402403
ftResults: {
403404
event_index: number;
@@ -469,6 +470,9 @@ export function parseDbEvents(
469470
event_type: DbEventTypeId.StxAsset,
470471
amount: BigInt(result.amount),
471472
};
473+
if (result.memo) {
474+
event.memo = result.memo;
475+
}
472476
events[rowIndex++] = event;
473477
}
474478
for (const result of ftResults) {
@@ -554,6 +558,7 @@ export function parseTxsWithAssetTransfers(
554558
event_recipient?: string | undefined;
555559
event_asset_identifier?: string | undefined;
556560
event_value?: string | undefined;
561+
event_memo?: string | undefined;
557562
})[],
558563
stxAddress: string
559564
) {
@@ -567,6 +572,7 @@ export function parseTxsWithAssetTransfers(
567572
amount: bigint;
568573
sender?: string;
569574
recipient?: string;
575+
memo?: string;
570576
}[];
571577
ft_transfers: {
572578
asset_identifier: string;
@@ -606,6 +612,7 @@ export function parseTxsWithAssetTransfers(
606612
amount: eventAmount,
607613
sender: r.event_sender,
608614
recipient: r.event_recipient,
615+
memo: r.event_memo,
609616
});
610617
if (r.event_sender === stxAddress) {
611618
txResult.stx_sent += eventAmount;

src/datastore/pg-store.ts

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -959,6 +959,9 @@ export class PgStore {
959959
includeUnanchored: boolean;
960960
includePruned?: boolean;
961961
}): Promise<DbMempoolTx[]> {
962+
if (args.txIds.length === 0) {
963+
return [];
964+
}
962965
return this.sql.begin(async client => {
963966
const result = await this.sql<MempoolTxQueryResult[]>`
964967
SELECT ${unsafeCols(this.sql, [...MEMPOOL_TX_COLUMNS, abiColumn('mempool_txs')])}
@@ -1441,10 +1444,11 @@ export class PgStore {
14411444
sender?: string;
14421445
recipient?: string;
14431446
amount: string;
1447+
memo?: string;
14441448
}[]
14451449
>`
14461450
SELECT
1447-
event_index, tx_id, tx_index, block_height, canonical, asset_event_type_id, sender, recipient, amount
1451+
event_index, tx_id, tx_index, block_height, canonical, asset_event_type_id, sender, recipient, amount, memo
14481452
FROM stx_events
14491453
WHERE (tx_id, index_block_hash) IN (VALUES ${sql.unsafe(transactionValues)})
14501454
AND microblock_canonical = true AND event_index BETWEEN ${eventIndexStart} AND ${eventIndexEnd}
@@ -1554,10 +1558,11 @@ export class PgStore {
15541558
sender?: string;
15551559
recipient?: string;
15561560
amount: string;
1561+
memo?: string;
15571562
}[]
15581563
>`
15591564
SELECT
1560-
event_index, tx_id, tx_index, block_height, canonical, asset_event_type_id, sender, recipient, amount
1565+
event_index, tx_id, tx_index, block_height, canonical, asset_event_type_id, sender, recipient, amount, memo
15611566
FROM stx_events
15621567
WHERE tx_id = ${args.txId} AND index_block_hash = ${args.indexBlockHash}
15631568
AND microblock_canonical = true AND event_index BETWEEN ${eventIndexStart} AND ${eventIndexEnd}
@@ -1643,7 +1648,7 @@ export class PgStore {
16431648
return this.sql.begin(async sql => {
16441649
const refValue = args.addressOrTxId.address ?? args.addressOrTxId.txId;
16451650
const isAddress = args.addressOrTxId.address !== undefined;
1646-
const emptyEvents = sql`SELECT NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL`;
1651+
const emptyEvents = sql`SELECT NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL`;
16471652
const eventsResult = await sql<
16481653
{
16491654
tx_id: string;
@@ -1653,6 +1658,7 @@ export class PgStore {
16531658
sender: string;
16541659
recipient: string;
16551660
amount: number;
1661+
memo: string;
16561662
unlock_height: number;
16571663
asset_identifier: string;
16581664
contract_identifier: string;
@@ -1669,7 +1675,7 @@ export class PgStore {
16691675
SELECT
16701676
tx_id, event_index, tx_index, block_height, locked_address as sender, NULL as recipient,
16711677
locked_amount as amount, unlock_height, NULL as asset_identifier, NULL as contract_identifier,
1672-
'0'::bytea as value, NULL as topic,
1678+
'0'::bytea as value, NULL as topic, null::bytea as memo,
16731679
${DbEventTypeId.StxLock}::integer as event_type_id, 0 as asset_event_type_id
16741680
FROM stx_lock_events
16751681
WHERE ${isAddress ? sql`locked_address = ${refValue}` : sql`tx_id = ${refValue}`}
@@ -1684,7 +1690,7 @@ export class PgStore {
16841690
SELECT
16851691
tx_id, event_index, tx_index, block_height, sender, recipient,
16861692
amount, 0 as unlock_height, NULL as asset_identifier, NULL as contract_identifier,
1687-
'0'::bytea as value, NULL as topic,
1693+
'0'::bytea as value, NULL as topic, memo,
16881694
${DbEventTypeId.StxAsset}::integer as event_type_id, asset_event_type_id
16891695
FROM stx_events
16901696
WHERE ${
@@ -1703,7 +1709,7 @@ export class PgStore {
17031709
SELECT
17041710
tx_id, event_index, tx_index, block_height, sender, recipient,
17051711
amount, 0 as unlock_height, asset_identifier, NULL as contract_identifier,
1706-
'0'::bytea as value, NULL as topic,
1712+
'0'::bytea as value, NULL as topic, null::bytea as memo,
17071713
${DbEventTypeId.FungibleTokenAsset}::integer as event_type_id, asset_event_type_id
17081714
FROM ft_events
17091715
WHERE ${
@@ -1722,7 +1728,7 @@ export class PgStore {
17221728
SELECT
17231729
tx_id, event_index, tx_index, block_height, sender, recipient,
17241730
0 as amount, 0 as unlock_height, asset_identifier, NULL as contract_identifier,
1725-
value, NULL as topic,
1731+
value, NULL as topic, null::bytea as memo,
17261732
${DbEventTypeId.NonFungibleTokenAsset}::integer as event_type_id,
17271733
asset_event_type_id
17281734
FROM nft_events
@@ -1742,7 +1748,7 @@ export class PgStore {
17421748
SELECT
17431749
tx_id, event_index, tx_index, block_height, NULL as sender, NULL as recipient,
17441750
0 as amount, 0 as unlock_height, NULL as asset_identifier, contract_identifier,
1745-
value, topic,
1751+
value, topic, null::bytea as memo,
17461752
${DbEventTypeId.SmartContractLog}::integer as event_type_id,
17471753
0 as asset_event_type_id
17481754
FROM contract_logs
@@ -1783,6 +1789,9 @@ export class PgStore {
17831789
canonical: true,
17841790
asset_event_type_id: r.asset_event_type_id,
17851791
};
1792+
if (event.event_type === DbEventTypeId.StxAsset && r.memo) {
1793+
event.memo = r.memo;
1794+
}
17861795
return event;
17871796
});
17881797
}
@@ -2191,6 +2200,7 @@ export class PgStore {
21912200
recipient?: string;
21922201
asset_identifier: string;
21932202
amount?: string;
2203+
memo?: string;
21942204
unlock_height?: string;
21952205
value?: string;
21962206
} & { count: number })[]
@@ -2199,25 +2209,25 @@ export class PgStore {
21992209
FROM(
22002210
SELECT
22012211
'stx_lock' as asset_type, event_index, tx_id, microblock_sequence, tx_index, block_height, canonical, 0 as asset_event_type_id,
2202-
locked_address as sender, '' as recipient, '<stx>' as asset_identifier, locked_amount as amount, unlock_height, null::bytea as value
2212+
locked_address as sender, '' as recipient, '<stx>' as asset_identifier, locked_amount as amount, unlock_height, null::bytea as value, null::bytea as memo
22032213
FROM stx_lock_events
22042214
WHERE canonical = true AND microblock_canonical = true AND locked_address = ${stxAddress} AND block_height <= ${blockHeight}
22052215
UNION ALL
22062216
SELECT
22072217
'stx' as asset_type, event_index, tx_id, microblock_sequence, tx_index, block_height, canonical, asset_event_type_id,
2208-
sender, recipient, '<stx>' as asset_identifier, amount::numeric, null::numeric as unlock_height, null::bytea as value
2218+
sender, recipient, '<stx>' as asset_identifier, amount::numeric, null::numeric as unlock_height, null::bytea as value, memo
22092219
FROM stx_events
22102220
WHERE canonical = true AND microblock_canonical = true AND (sender = ${stxAddress} OR recipient = ${stxAddress}) AND block_height <= ${blockHeight}
22112221
UNION ALL
22122222
SELECT
22132223
'ft' as asset_type, event_index, tx_id, microblock_sequence, tx_index, block_height, canonical, asset_event_type_id,
2214-
sender, recipient, asset_identifier, amount, null::numeric as unlock_height, null::bytea as value
2224+
sender, recipient, asset_identifier, amount, null::numeric as unlock_height, null::bytea as value, null::bytea as memo
22152225
FROM ft_events
22162226
WHERE canonical = true AND microblock_canonical = true AND (sender = ${stxAddress} OR recipient = ${stxAddress}) AND block_height <= ${blockHeight}
22172227
UNION ALL
22182228
SELECT
22192229
'nft' as asset_type, event_index, tx_id, microblock_sequence, tx_index, block_height, canonical, asset_event_type_id,
2220-
sender, recipient, asset_identifier, null::numeric as amount, null::numeric as unlock_height, value
2230+
sender, recipient, asset_identifier, null::numeric as amount, null::numeric as unlock_height, value, null::bytea as memo
22212231
FROM nft_events
22222232
WHERE canonical = true AND microblock_canonical = true AND (sender = ${stxAddress} OR recipient = ${stxAddress}) AND block_height <= ${blockHeight}
22232233
) asset_events
@@ -2253,6 +2263,9 @@ export class PgStore {
22532263
event_type: DbEventTypeId.StxAsset,
22542264
amount: BigInt(row.amount ?? 0),
22552265
};
2266+
if (row.memo) {
2267+
event.memo = row.memo;
2268+
}
22562269
return event;
22572270
} else if (row.asset_type === 'ft') {
22582271
const event: DbFtEvent = {
@@ -2468,6 +2481,7 @@ export class PgStore {
24682481
event_amount?: string;
24692482
event_sender?: string;
24702483
event_recipient?: string;
2484+
event_memo?: string;
24712485
})[]
24722486
>`
24732487
WITH transactions AS (
@@ -2508,6 +2522,7 @@ export class PgStore {
25082522
events.amount as event_amount,
25092523
events.sender as event_sender,
25102524
events.recipient as event_recipient,
2525+
events.memo as event_memo,
25112526
${this.sql.unsafe(abiColumn('transactions'))}
25122527
FROM transactions
25132528
LEFT JOIN events ON transactions.tx_id = events.tx_id

src/datastore/pg-write-store.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -709,6 +709,7 @@ export class PgWriteStore extends PgStore {
709709
sender: event.sender ?? null,
710710
recipient: event.recipient ?? null,
711711
amount: event.amount,
712+
memo: event.memo ?? null,
712713
}));
713714
const res = await sql`
714715
INSERT INTO stx_events ${sql(values)}
@@ -874,6 +875,7 @@ export class PgWriteStore extends PgStore {
874875
sender: event.sender ?? null,
875876
recipient: event.recipient ?? null,
876877
amount: event.amount,
878+
memo: event.memo ?? null,
877879
};
878880
await sql`
879881
INSERT INTO stx_events ${sql(values)}

src/event-stream/core-node-message.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ export interface StxTransferEvent extends CoreNodeEventBase {
4343
recipient: string;
4444
sender: string;
4545
amount: string;
46+
/** Hex-encoded string. Only provided when a memo was specified in the Clarity `stx-transfer?` function (requires a Stacks 2.1 contract). */
47+
memo?: string;
4648
};
4749
}
4850

src/event-stream/event-server.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,7 @@ function parseDataStoreTxEventData(
453453
sender: event.stx_transfer_event.sender,
454454
recipient: event.stx_transfer_event.recipient,
455455
amount: BigInt(event.stx_transfer_event.amount),
456+
memo: event.stx_transfer_event.memo ? '0x' + event.stx_transfer_event.memo : undefined,
456457
};
457458
dbTx.stxEvents.push(entry);
458459
break;

src/migrations/1588252682585_stx_events.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export async function up(pgm: MigrationBuilder): Promise<void> {
5656
},
5757
sender: 'string',
5858
recipient: 'string',
59+
memo: 'bytea',
5960
});
6061

6162
pgm.createIndex('stx_events', 'tx_id', { method: 'hash' });

0 commit comments

Comments
 (0)