@@ -20,6 +20,8 @@ const PODCAST_EPISODE: &str = "podcast:item:guid:";
20
20
const PODCAST_PUBLISHER : & str = "podcast:publisher:guid:" ;
21
21
const MOVIE : & str = "isan:" ;
22
22
const PAPER : & str = "doi:" ;
23
+ const BLOCKCHAIN_TX : & str = ":tx:" ;
24
+ const BLOCKCHAIN_ADDR : & str = ":address:" ;
23
25
24
26
/// NIP73 error
25
27
#[ derive( Debug , PartialEq , Eq ) ]
@@ -60,6 +62,24 @@ pub enum ExternalContentId {
60
62
Movie ( String ) ,
61
63
/// Paper
62
64
Paper ( String ) ,
65
+ /// Blockchain Transaction
66
+ BlockchainTransaction {
67
+ /// The blockchain name (e.g., "bitcoin", "ethereum")
68
+ chain : String ,
69
+ /// A lower case hex transaction id
70
+ transaction_hash : String ,
71
+ /// The chain id if one is required
72
+ chain_id : Option < String > ,
73
+ } ,
74
+ /// Blockchain Address
75
+ BlockchainAddress {
76
+ /// The blockchain name (e.g., "bitcoin", "ethereum")
77
+ chain : String ,
78
+ /// The on-chain address
79
+ address : String ,
80
+ /// The chain id if one is required
81
+ chain_id : Option < String > ,
82
+ } ,
63
83
}
64
84
65
85
impl fmt:: Display for ExternalContentId {
@@ -74,6 +94,34 @@ impl fmt::Display for ExternalContentId {
74
94
Self :: PodcastPublisher ( guid) => write ! ( f, "{PODCAST_PUBLISHER}{guid}" ) ,
75
95
Self :: Movie ( movie) => write ! ( f, "{MOVIE}{movie}" ) ,
76
96
Self :: Paper ( paper) => write ! ( f, "{PAPER}{paper}" ) ,
97
+ Self :: BlockchainTransaction {
98
+ chain,
99
+ transaction_hash,
100
+ chain_id,
101
+ } => {
102
+ write ! (
103
+ f,
104
+ "{chain}{}{BLOCKCHAIN_TX}{transaction_hash}" ,
105
+ chain_id
106
+ . as_ref( )
107
+ . map( |id| format!( ":{id}" ) )
108
+ . unwrap_or_default( )
109
+ )
110
+ }
111
+ Self :: BlockchainAddress {
112
+ chain,
113
+ address,
114
+ chain_id,
115
+ } => {
116
+ write ! (
117
+ f,
118
+ "{chain}{}{BLOCKCHAIN_ADDR}{address}" ,
119
+ chain_id
120
+ . as_ref( )
121
+ . map( |id| format!( ":{id}" ) )
122
+ . unwrap_or_default( )
123
+ )
124
+ }
77
125
}
78
126
}
79
127
}
@@ -114,6 +162,24 @@ impl FromStr for ExternalContentId {
114
162
return Ok ( Self :: Paper ( stripped. to_string ( ) ) ) ;
115
163
}
116
164
165
+ if let Some ( ( chain, hash) ) = content. split_once ( BLOCKCHAIN_TX ) {
166
+ let ( chain, chain_id) = extract_chain_id ( chain) ;
167
+ return Ok ( Self :: BlockchainTransaction {
168
+ chain,
169
+ transaction_hash : hash. to_string ( ) ,
170
+ chain_id,
171
+ } ) ;
172
+ }
173
+
174
+ if let Some ( ( chain, address) ) = content. split_once ( BLOCKCHAIN_ADDR ) {
175
+ let ( chain, chain_id) = extract_chain_id ( chain) ;
176
+ return Ok ( Self :: BlockchainAddress {
177
+ chain,
178
+ address : address. to_string ( ) ,
179
+ chain_id,
180
+ } ) ;
181
+ }
182
+
117
183
if let Ok ( url) = Url :: parse ( content) {
118
184
return Ok ( Self :: Url ( url) ) ;
119
185
}
@@ -122,6 +188,15 @@ impl FromStr for ExternalContentId {
122
188
}
123
189
}
124
190
191
+ /// Given a blockchain name returns the chain and the optional chain id if any.
192
+ fn extract_chain_id ( chain : & str ) -> ( String , Option < String > ) {
193
+ match chain. split_once ( ':' ) {
194
+ None => ( chain. to_string ( ) , None ) ,
195
+ Some ( ( chain, "" ) ) => ( chain. to_string ( ) , None ) ,
196
+ Some ( ( chain, chain_id) ) => ( chain. to_string ( ) , Some ( chain_id. to_string ( ) ) ) ,
197
+ }
198
+ }
199
+
125
200
#[ cfg( test) ]
126
201
mod tests {
127
202
use super :: * ;
@@ -164,6 +239,33 @@ mod tests {
164
239
ExternalContentId :: Paper ( "10.1000/182" . to_string( ) ) . to_string( ) ,
165
240
"doi:10.1000/182"
166
241
) ;
242
+ assert_eq ! (
243
+ ExternalContentId :: BlockchainTransaction {
244
+ chain: "bitcoin" . to_string( ) ,
245
+ transaction_hash: "txid" . to_string( ) ,
246
+ chain_id: None ,
247
+ }
248
+ . to_string( ) ,
249
+ "bitcoin:tx:txid"
250
+ ) ;
251
+ assert_eq ! (
252
+ ExternalContentId :: BlockchainTransaction {
253
+ chain: "ethereum" . to_string( ) ,
254
+ transaction_hash: "txid" . to_string( ) ,
255
+ chain_id: Some ( "100" . to_string( ) ) ,
256
+ }
257
+ . to_string( ) ,
258
+ "ethereum:100:tx:txid"
259
+ ) ;
260
+ assert_eq ! (
261
+ ExternalContentId :: BlockchainAddress {
262
+ chain: "ethereum" . to_string( ) ,
263
+ address: "onchain_address" . to_string( ) ,
264
+ chain_id: Some ( "100" . to_string( ) ) ,
265
+ }
266
+ . to_string( ) ,
267
+ "ethereum:100:address:onchain_address"
268
+ ) ;
167
269
}
168
270
169
271
#[ test]
@@ -204,6 +306,46 @@ mod tests {
204
306
ExternalContentId :: from_str( "doi:10.1000/182" ) . unwrap( ) ,
205
307
ExternalContentId :: Paper ( "10.1000/182" . to_string( ) )
206
308
) ;
309
+ assert_eq ! (
310
+ ExternalContentId :: from_str(
311
+ "bitcoin:tx:a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d"
312
+ )
313
+ . unwrap( ) ,
314
+ ExternalContentId :: BlockchainTransaction {
315
+ chain: "bitcoin" . to_string( ) ,
316
+ transaction_hash:
317
+ "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d" . to_string( ) ,
318
+ chain_id: None ,
319
+ }
320
+ ) ;
321
+ assert_eq ! (
322
+ ExternalContentId :: from_str( "ethereum:100:tx:0x98f7812be496f97f80e2e98d66358d1fc733cf34176a8356d171ea7fbbe97ccd" ) . unwrap( ) ,
323
+ ExternalContentId :: BlockchainTransaction {
324
+ chain: "ethereum" . to_string( ) ,
325
+ transaction_hash: "0x98f7812be496f97f80e2e98d66358d1fc733cf34176a8356d171ea7fbbe97ccd" . to_string( ) ,
326
+ chain_id: Some ( "100" . to_string( ) ) ,
327
+ }
328
+ ) ;
329
+ assert_eq ! (
330
+ ExternalContentId :: from_str( "bitcoin:address:1HQ3Go3ggs8pFnXuHVHRytPCq5fGG8Hbhx" )
331
+ . unwrap( ) ,
332
+ ExternalContentId :: BlockchainAddress {
333
+ chain: "bitcoin" . to_string( ) ,
334
+ address: "1HQ3Go3ggs8pFnXuHVHRytPCq5fGG8Hbhx" . to_string( ) ,
335
+ chain_id: None ,
336
+ }
337
+ ) ;
338
+ assert_eq ! (
339
+ ExternalContentId :: from_str(
340
+ "ethereum:100:address:0xd8da6bf26964af9d7eed9e03e53415d37aa96045"
341
+ )
342
+ . unwrap( ) ,
343
+ ExternalContentId :: BlockchainAddress {
344
+ chain: "ethereum" . to_string( ) ,
345
+ address: "0xd8da6bf26964af9d7eed9e03e53415d37aa96045" . to_string( ) ,
346
+ chain_id: Some ( "100" . to_string( ) ) ,
347
+ }
348
+ ) ;
207
349
}
208
350
209
351
#[ test]
0 commit comments