11import { Observable } from "rxjs" ;
2- import type { Client } from "@parity/truapi" ;
2+ import { err , ok , type Result } from "neverthrow" ;
3+ import { PASEO_NEXT_V2_INDIVIDUALITY } from "@parity/truapi" ;
4+ import {
5+ Blake2128Concat ,
6+ Bytes ,
7+ Storage ,
8+ } from "@polkadot-api/substrate-bindings" ;
9+ import type { Client , HexString , StorageResultItem } from "@parity/truapi" ;
310
411export type ChainHeadCtx = {
512 genesisHash : `0x${string } `;
@@ -12,6 +19,15 @@ export type WithChainHeadFollow = (opts: {
1219 withRuntime ?: boolean ;
1320} ) => Observable < ChainHeadCtx > ;
1421
22+ export type AccountIdForDotNsUsername = (
23+ username ?: string ,
24+ ) => Promise < Result < HexString , Error > > ;
25+
26+ const usernameOwnerOfStorage = Storage ( "Resources" ) ( "UsernameOwnerOf" , [
27+ Bytes ( ) ,
28+ Blake2128Concat ,
29+ ] ) ;
30+
1531export function createWithChainHeadFollow ( truapi : Client ) : WithChainHeadFollow {
1632 return function withChainHeadFollow ( {
1733 genesisHash,
@@ -50,3 +66,132 @@ export function createWithChainHeadFollow(truapi: Client): WithChainHeadFollow {
5066 } ) ;
5167 } ;
5268}
69+
70+ export function createAccountIdForDotNsUsername (
71+ truapi : Client ,
72+ ) : AccountIdForDotNsUsername {
73+ return async function accountIdForDotNsUsername (
74+ username ,
75+ ) : Promise < Result < HexString , Error > > {
76+ let dotNsUsername = username ;
77+ if ( dotNsUsername === undefined ) {
78+ const userIdResult = await truapi . account . getUserId ( ) ;
79+ if ( userIdResult . isErr ( ) ) {
80+ return err ( toError ( userIdResult . error ) ) ;
81+ }
82+ dotNsUsername = userIdResult . value . primaryUsername ;
83+ }
84+ if ( dotNsUsername . length === 0 ) {
85+ return err ( new Error ( "DotNS username is empty" ) ) ;
86+ }
87+
88+ const key = usernameOwnerOfStorage . enc (
89+ new TextEncoder ( ) . encode ( dotNsUsername ) ,
90+ ) as HexString ;
91+
92+ return new Promise < Result < HexString , Error > > ( ( resolve ) => {
93+ let operationId : string | null = null ;
94+ const sub = truapi . chain
95+ . followHeadSubscribe ( {
96+ request : {
97+ genesisHash : PASEO_NEXT_V2_INDIVIDUALITY . genesis ,
98+ withRuntime : false ,
99+ } ,
100+ } )
101+ . subscribe ( {
102+ next : async ( item ) => {
103+ const fail = ( reason : unknown ) => {
104+ sub . unsubscribe ( ) ;
105+ resolve ( err ( toError ( reason ) ) ) ;
106+ } ;
107+
108+ try {
109+ switch ( item . tag ) {
110+ case "Initialized" : {
111+ const result = await truapi . chain . getHeadStorage ( {
112+ genesisHash : PASEO_NEXT_V2_INDIVIDUALITY . genesis ,
113+ followSubscriptionId : sub . subscriptionId ,
114+ hash : item . value . finalizedBlockHashes [ 0 ] ,
115+ items : [ { key, queryType : "Value" } ] ,
116+ } ) ;
117+ if ( result . isErr ( ) ) {
118+ fail ( result . error ) ;
119+ return ;
120+ }
121+ if ( result . value . operation . tag !== "Started" ) {
122+ fail ( new Error ( "getHeadStorage operation limit reached" ) ) ;
123+ return ;
124+ }
125+ operationId = result . value . operation . value . operationId ;
126+ return ;
127+ }
128+ case "OperationStorageItems" :
129+ if ( item . value . operationId === operationId ) {
130+ const account = findStorageValue ( item . value . items , key ) ;
131+ if ( ! account ) {
132+ fail ( `No account owns DotNS username "${ dotNsUsername } "` ) ;
133+ return ;
134+ }
135+ sub . unsubscribe ( ) ;
136+ resolve ( ok ( account ) ) ;
137+ }
138+ return ;
139+ case "OperationStorageDone" :
140+ if ( item . value . operationId === operationId ) {
141+ fail ( `No account owns DotNS username "${ dotNsUsername } "` ) ;
142+ }
143+ return ;
144+ case "OperationError" :
145+ if ( item . value . operationId === operationId ) {
146+ fail ( `getHeadStorage failed: ${ item . value . error } ` ) ;
147+ }
148+ return ;
149+ case "OperationInaccessible" :
150+ if ( item . value . operationId === operationId ) {
151+ fail ( "getHeadStorage operation inaccessible" ) ;
152+ }
153+ return ;
154+ case "Stop" :
155+ fail (
156+ "chain head subscription stopped before username lookup finished" ,
157+ ) ;
158+ return ;
159+ }
160+ } catch ( error ) {
161+ sub . unsubscribe ( ) ;
162+ resolve ( err ( toError ( error ) ) ) ;
163+ }
164+ } ,
165+ error : ( error ) => resolve ( err ( toError ( error ) ) ) ,
166+ complete : ( ) =>
167+ resolve (
168+ err (
169+ new Error (
170+ "chain head subscription completed before username lookup finished" ,
171+ ) ,
172+ ) ,
173+ ) ,
174+ } ) ;
175+ } ) ;
176+ } ;
177+ }
178+
179+ function findStorageValue (
180+ items : StorageResultItem [ ] ,
181+ key : HexString ,
182+ ) : HexString | null {
183+ const item = items . find (
184+ ( candidate ) => candidate . key . toLowerCase ( ) === key . toLowerCase ( ) ,
185+ ) ;
186+ return item ?. value ?? null ;
187+ }
188+
189+ function toError ( value : unknown ) : Error {
190+ if ( value instanceof Error ) return value ;
191+ if ( typeof value === "string" ) return new Error ( value ) ;
192+ try {
193+ return new Error ( JSON . stringify ( value ) ) ;
194+ } catch {
195+ return new Error ( String ( value ) ) ;
196+ }
197+ }
0 commit comments