Skip to content

Commit 459dcd7

Browse files
Update playground transaction example for DotNS signer (#220)
* Update playground transaction example for DotNS signer * Update DotNS example helper default * Return DotNS helper lookup as Result * Update dotli submodule to dotli-community * Log fetched DotNS account in transaction example * Resolve DotNS helper username from account --------- Co-authored-by: Filippo <filippo@parity.io>
1 parent dd68f7d commit 459dcd7

10 files changed

Lines changed: 206 additions & 6 deletions

File tree

.gitmodules

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
[submodule "hosts/dotli"]
22
path = hosts/dotli
3-
url = https://github.com/paritytech/dotli.git
3+
url = https://github.com/paritytech/dotli-community
44
branch = main

hosts/dotli

Submodule dotli updated 280 files

js/packages/truapi/src/well-known-chains.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,10 @@ export const PASEO_NEXT_V2_ASSET_HUB = {
1515
genesis:
1616
"0xbf0488dbe9daa1de1c08c5f743e26fdc2a4ecd74cf87dd1b4b1eeb99ae4ef19f",
1717
} as const satisfies WellKnownChain;
18+
19+
export const PASEO_NEXT_V2_INDIVIDUALITY = {
20+
name: "Paseo Next v2 Individuality",
21+
network: "Testnet",
22+
genesis:
23+
"0xc5af1826b31493f08b7e2a823842f98575b806a784126f28da9608c68665afa5",
24+
} as const satisfies WellKnownChain;

playground/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@
2323
"dependencies": {
2424
"@monaco-editor/react": "^4",
2525
"@parity/truapi": "link:../js/packages/truapi",
26+
"@polkadot-api/substrate-bindings": "^0.12.0",
2627
"monaco-editor": "^0.52",
28+
"neverthrow": "^8.2.0",
2729
"next": "15.5.18",
2830
"react": "^19.2.1",
2931
"react-dom": "^19.2.1",

playground/src/lib/example-helpers.ts

Lines changed: 146 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
import { 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

411
export 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+
1531
export 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+
}

playground/src/lib/example-runner.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import { transform } from "sucrase";
22
import type { Subscription, TrUApiClient } from "@parity/truapi";
3-
import { createWithChainHeadFollow, type WithChainHeadFollow } from "./example-helpers";
3+
import {
4+
createAccountIdForDotNsUsername,
5+
createWithChainHeadFollow,
6+
type AccountIdForDotNsUsername,
7+
type WithChainHeadFollow,
8+
} from "./example-helpers";
49

510
export type LogEntry = {
611
level: "log" | "error" | "warn";
@@ -60,6 +65,7 @@ const AsyncFunction = Object.getPrototypeOf(
6065
__console: ConsoleShim,
6166
__rxjs: unknown,
6267
withChainHeadFollow: WithChainHeadFollow,
68+
accountIdForDotNsUsername: AccountIdForDotNsUsername,
6369
__truapi: unknown,
6470
assert: typeof exampleAssert,
6571
) => Promise<unknown>;
@@ -99,6 +105,7 @@ export async function runExample(opts: {
99105
c: ConsoleShim,
100106
rxjs: unknown,
101107
withChainHeadFollow: WithChainHeadFollow,
108+
accountIdForDotNsUsername: AccountIdForDotNsUsername,
102109
truapiPkg: unknown,
103110
assert: typeof exampleAssert,
104111
) => Promise<unknown>;
@@ -108,6 +115,7 @@ export async function runExample(opts: {
108115
"__console",
109116
"__rxjs",
110117
"withChainHeadFollow",
118+
"accountIdForDotNsUsername",
111119
"__truapi",
112120
"assert",
113121
body,
@@ -141,11 +149,15 @@ export async function runExample(opts: {
141149

142150
const [rxjs, truapiPkg] = await Promise.all([getRxjs(), getTruapiPkg()]);
143151
const withChainHeadFollow = createWithChainHeadFollow(trackingClient as TrUApiClient);
152+
const accountIdForDotNsUsername = createAccountIdForDotNsUsername(
153+
trackingClient as TrUApiClient,
154+
);
144155
const promise = run(
145156
trackingClient,
146157
consoleShim,
147158
rxjs,
148159
withChainHeadFollow,
160+
accountIdForDotNsUsername,
149161
truapiPkg,
150162
exampleAssert,
151163
);

playground/src/lib/monaco-setup.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@ export function setupMonaco(m: Monaco): void {
9595
` genesisHash: \`0x\${string}\`;`,
9696
` withRuntime?: boolean;`,
9797
` }): import("rxjs").Observable<ChainHeadCtx>;`,
98+
` /** Resolve a DotNS username to the owning raw AccountId32 hex string. Defaults to truapi.account.getUserId(). */`,
99+
` function accountIdForDotNsUsername(username?: string): Promise<import("neverthrow").Result<\`0x\${string}\`, Error>>;`,
98100
` /**`,
99101
` * Assert a condition, throwing when it does not hold. Examples signal`,
100102
` * failure explicitly with \`assert(...)\`; the diagnosis marks an example`,

playground/yarn.lock

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,11 @@
371371
resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.18.tgz#beac6228e60e3ee08ce7a20b7f61b3dc516d4b10"
372372
integrity sha512-LIu5me6QTANCd25E7I5uIEfvgQ06RK7tvHAbYo3zCb3VpxQEPvMcSpd87NwUABDT6MbGPdEGR5VRiK4PPTJhQg==
373373

374+
"@noble/hashes@^1.8.0":
375+
version "1.8.0"
376+
resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.8.0.tgz#cee43d801fcef9644b11b8194857695acd5f815a"
377+
integrity sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==
378+
374379
"@nodelib/fs.scandir@2.1.5":
375380
version "2.1.5"
376381
resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz"
@@ -408,6 +413,21 @@
408413
dependencies:
409414
playwright "1.59.1"
410415

416+
"@polkadot-api/substrate-bindings@^0.12.0":
417+
version "0.12.0"
418+
resolved "https://registry.yarnpkg.com/@polkadot-api/substrate-bindings/-/substrate-bindings-0.12.0.tgz#2b9cd9ba1b7e29c4a1d0be0575504c02cb435c78"
419+
integrity sha512-cIjDeJRHW6g3z+/55UzpoG4LG1N0HbT4x3NvZsQkYg4eoio9Sw7Pw2aZZX86pWemxc7vQbNw7WSz2Gz+ckdX6Q==
420+
dependencies:
421+
"@noble/hashes" "^1.8.0"
422+
"@polkadot-api/utils" "0.1.2"
423+
"@scure/base" "^1.2.5"
424+
scale-ts "^1.6.1"
425+
426+
"@polkadot-api/utils@0.1.2":
427+
version "0.1.2"
428+
resolved "https://registry.yarnpkg.com/@polkadot-api/utils/-/utils-0.1.2.tgz#45471371183efaa2fc52f40d84326d84e49c7297"
429+
integrity sha512-yhs5k2a8N1SBJcz7EthZoazzLQUkZxbf+0271Xzu42C5AEM9K9uFLbsB+ojzHEM72O5X8lPtSwGKNmS7WQyDyg==
430+
411431
"@rollup/rollup-linux-x64-gnu@^4.24.0":
412432
version "4.60.4"
413433
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.4.tgz#23c9bf79771d804fb87415eb0767569f273261e5"
@@ -423,6 +443,11 @@
423443
resolved "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.16.1.tgz"
424444
integrity sha512-TvZbIpeKqGQQ7X0zSCvPH9riMSFQFSggnfBjFZ1mEoILW+UuXCKwOoPcgjMwiUtRqFZ8jWhPJc4um14vC6I4ag==
425445

446+
"@scure/base@^1.2.5":
447+
version "1.2.6"
448+
resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.2.6.tgz#ca917184b8231394dd8847509c67a0be522e59f6"
449+
integrity sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==
450+
426451
"@swc/helpers@0.5.15":
427452
version "0.5.15"
428453
resolved "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz"

rust/crates/truapi-codegen/src/ts/examples.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ declare const truapi: Client;
1717

1818
/// Shared ambient declarations for every example. Mirrors the runtime
1919
/// helpers injected by `playground/src/lib/example-helpers.ts` so each
20-
/// generated example can reference `withChainHeadFollow` without redefining it.
20+
/// generated example can reference helpers like `withChainHeadFollow` without
21+
/// redefining them.
2122
const EXAMPLE_AMBIENT_DTS: &str = r#"// Auto-generated by truapi-codegen. Do not edit.
2223
import type { Observable } from "rxjs";
2324
@@ -42,6 +43,8 @@ declare global {
4243
genesisHash: `0x${string}`;
4344
withRuntime?: boolean;
4445
}): Observable<ChainHeadCtx>;
46+
/** Resolve a DotNS username to the owning raw AccountId32 hex string. Defaults to truapi.account.getUserId(). */
47+
function accountIdForDotNsUsername(username?: string): Promise<import("neverthrow").Result<`0x${string}`, Error>>;
4548
/**
4649
* Assert a condition, throwing when it does not hold. Examples signal
4750
* failure explicitly with `assert(...)`; the playground's diagnosis marks

rust/crates/truapi/src/api/signing.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,12 @@ pub trait Signing: Send + Sync {
4949
/// ```ts
5050
/// import { PASEO_NEXT_V2_ASSET_HUB } from "@parity/truapi";
5151
///
52+
/// const signerResult = await accountIdForDotNsUsername();
53+
/// assert(signerResult.isOk(), "accountIdForDotNsUsername failed:", signerResult);
54+
/// console.log("fetched user account:", signerResult.value);
55+
///
5256
/// const result = await truapi.signing.createTransactionWithLegacyAccount({
53-
/// signer: "0x0000000000000000000000000000000000000000000000000000000000000000",
57+
/// signer: signerResult.value,
5458
/// genesisHash: PASEO_NEXT_V2_ASSET_HUB.genesis,
5559
/// callData: "0x0000",
5660
/// extensions: [],

0 commit comments

Comments
 (0)