@d9-network/ink 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,404 @@
1
+ import { beforeAll, afterAll, describe, it, expect } from "bun:test";
2
+ import { Binary, type SS58String, createClient, type CreateClientOptions } from "polkadot-api";
3
+ import { getPolkadotSigner } from "polkadot-api/signer";
4
+ import { getWsProvider } from "polkadot-api/ws-provider";
5
+ import { withPolkadotSdkCompat } from "polkadot-api/polkadot-sdk-compat";
6
+ import { withLegacy } from "@polkadot-api/legacy-provider";
7
+ import { sr25519CreateDerive } from "@polkadot-labs/hdkd";
8
+ import { entropyToMiniSecret, mnemonicToEntropy, ss58Decode } from "@polkadot-labs/hdkd-helpers";
9
+ import { AccountId } from "@polkadot-api/substrate-bindings";
10
+ import { contracts, d9, getMetadata } from "@polkadot-api/descriptors";
11
+ import { createD9InkSdk, type D9InkContractFromDescriptor } from "../src";
12
+
13
+ const TEST_ADDRESSES = {
14
+ testUser: "zcb3U8vYqWLmd5huhyS7nMiMnqDQfWJrmjQcpMhkWmn7krH",
15
+ usdtContract: "uLj9DRUujbpCyK7USZY5ebGbxdtKoWvdRvGyyUsoLWDsNng",
16
+ };
17
+
18
+ const ENDPOINT = process.env.TEST_ENDPOINT || "wss://mainnet.d9network.com:40300";
19
+
20
+ // Get SS58 prefix from existing address (D9 network prefix)
21
+ const [, D9_SS58_PREFIX] = ss58Decode(TEST_ADDRESSES.testUser);
22
+
23
+ // Create AccountId codec with D9 prefix for encoding addresses
24
+ const d9AccountId = AccountId(D9_SS58_PREFIX);
25
+
26
+ /**
27
+ * Create a signer from mnemonic for testing
28
+ */
29
+ function createSignerFromMnemonic(mnemonic: string) {
30
+ const entropy = mnemonicToEntropy(mnemonic);
31
+ const miniSecret = entropyToMiniSecret(entropy);
32
+ const derive = sr25519CreateDerive(miniSecret);
33
+ const keyPair = derive("");
34
+ const signer = getPolkadotSigner(keyPair.publicKey, "Sr25519", keyPair.sign);
35
+
36
+ // Encode address with D9 prefix
37
+ const address = d9AccountId.dec(keyPair.publicKey) as SS58String;
38
+
39
+ return { signer, publicKey: keyPair.publicKey, address };
40
+ }
41
+
42
+ type CreateInkSdkClientOptions = {
43
+ endpoint: string;
44
+ wsProvider?: Parameters<typeof getWsProvider>[1];
45
+ metadata?: CreateClientOptions;
46
+ };
47
+
48
+ function createInkSdkClient(options: CreateInkSdkClientOptions) {
49
+ const provider = getWsProvider(options.endpoint, {
50
+ innerEnhancer: withLegacy(),
51
+ ...options.wsProvider,
52
+ });
53
+
54
+ const client = createClient(withPolkadotSdkCompat(provider), {
55
+ ...options.metadata,
56
+ async getMetadata(codeHash) {
57
+ // Prefer upstream metadata if present, otherwise fallback to embedded descriptors metadata.
58
+ const custom = options.metadata?.getMetadata
59
+ ? await options.metadata.getMetadata(codeHash)
60
+ : null;
61
+ if (custom) return custom;
62
+ const metadata = await getMetadata(codeHash);
63
+ return metadata ?? null;
64
+ },
65
+ });
66
+
67
+ const api = client.getTypedApi(d9);
68
+ const inkSdk = createD9InkSdk(client, { typedApi: api });
69
+
70
+ return {
71
+ client,
72
+ api,
73
+ inkSdk,
74
+ };
75
+ }
76
+
77
+ type InkSdkClientReturn = ReturnType<typeof createInkSdkClient>;
78
+
79
+ const NETWORK_TIMEOUT = 30000;
80
+
81
+ describe("D9 Ink SDK", () => {
82
+ let d9Client: InkSdkClientReturn;
83
+ let usdtContract: D9InkContractFromDescriptor<typeof contracts.usdt>;
84
+
85
+ beforeAll(async () => {
86
+ d9Client = createInkSdkClient({
87
+ endpoint: ENDPOINT,
88
+ });
89
+ usdtContract = d9Client.inkSdk.getContract(contracts.usdt, TEST_ADDRESSES.usdtContract);
90
+ });
91
+
92
+ afterAll(() => {
93
+ d9Client.client.destroy();
94
+ });
95
+
96
+ describe("USDT Contract Queries", () => {
97
+ it(
98
+ "should query PSP22::balance_of",
99
+ async () => {
100
+ const result = await usdtContract.query("PSP22::balance_of", {
101
+ origin: TEST_ADDRESSES.testUser,
102
+ args: { owner: TEST_ADDRESSES.testUser },
103
+ });
104
+
105
+ console.log("Query result:", result);
106
+
107
+ expect(result.success).toBe(true);
108
+ if (result.success) {
109
+ console.log("Balance:", result.value);
110
+ console.log("Gas consumed:", result.gasConsumed);
111
+ console.log("Gas required:", result.gasRequired);
112
+ expect(typeof result.value).toBe("bigint");
113
+ }
114
+ },
115
+ { timeout: NETWORK_TIMEOUT },
116
+ );
117
+
118
+ it(
119
+ "should query PSP22::total_supply",
120
+ async () => {
121
+ const result = await usdtContract.query("PSP22::total_supply", {
122
+ origin: TEST_ADDRESSES.testUser,
123
+ });
124
+
125
+ console.log("Total supply result:", result);
126
+
127
+ expect(result.success).toBe(true);
128
+ if (result.success) {
129
+ console.log("Total supply:", result.value);
130
+ expect(typeof result.value).toBe("bigint");
131
+ expect(result.value).toBeGreaterThan(0n);
132
+ }
133
+ },
134
+ { timeout: NETWORK_TIMEOUT },
135
+ );
136
+
137
+ it(
138
+ "should query PSP22::allowance",
139
+ async () => {
140
+ const result = await usdtContract.query("PSP22::allowance", {
141
+ origin: TEST_ADDRESSES.testUser,
142
+ args: {
143
+ owner: TEST_ADDRESSES.testUser,
144
+ spender: TEST_ADDRESSES.usdtContract,
145
+ },
146
+ });
147
+
148
+ console.log("Allowance result:", result);
149
+
150
+ expect(result.success).toBe(true);
151
+ if (result.success) {
152
+ console.log("Allowance:", result.value);
153
+ expect(typeof result.value).toBe("bigint");
154
+ }
155
+ },
156
+ { timeout: NETWORK_TIMEOUT },
157
+ );
158
+ });
159
+
160
+ describe("Query Result Features", () => {
161
+ it(
162
+ "should include gas information in query result",
163
+ async () => {
164
+ const result = await usdtContract.query("PSP22::balance_of", {
165
+ origin: TEST_ADDRESSES.testUser,
166
+ args: { owner: TEST_ADDRESSES.testUser },
167
+ });
168
+
169
+ expect(result.success).toBe(true);
170
+ if (result.success) {
171
+ expect(result.gasConsumed).toBeDefined();
172
+ expect(result.gasConsumed.refTime).toBeGreaterThan(0n);
173
+ expect(result.gasRequired).toBeDefined();
174
+ expect(result.gasRequired.refTime).toBeGreaterThan(0n);
175
+ }
176
+ },
177
+ { timeout: NETWORK_TIMEOUT },
178
+ );
179
+
180
+ it(
181
+ "should provide send() method on query result",
182
+ async () => {
183
+ const result = await usdtContract.query("PSP22::balance_of", {
184
+ origin: TEST_ADDRESSES.testUser,
185
+ args: { owner: TEST_ADDRESSES.testUser },
186
+ });
187
+
188
+ expect(result.success).toBe(true);
189
+ if (result.success) {
190
+ expect(typeof result.send).toBe("function");
191
+ const sendable = result.send();
192
+ expect(typeof sendable.signAndSubmit).toBe("function");
193
+ expect(typeof sendable.getEncodedData).toBe("function");
194
+ }
195
+ },
196
+ { timeout: NETWORK_TIMEOUT },
197
+ );
198
+ });
199
+
200
+ describe("Send Transaction (Dry Run)", () => {
201
+ it(
202
+ "should create sendable transaction for PSP22::transfer",
203
+ async () => {
204
+ const sendable = usdtContract.send("PSP22::transfer", {
205
+ origin: TEST_ADDRESSES.testUser,
206
+ args: {
207
+ to: TEST_ADDRESSES.usdtContract,
208
+ value: 0n,
209
+ data: Binary.fromBytes(new Uint8Array(0)),
210
+ },
211
+ });
212
+
213
+ expect(sendable).toBeDefined();
214
+ expect(typeof sendable.signAndSubmit).toBe("function");
215
+ expect(typeof sendable.getEncodedData).toBe("function");
216
+
217
+ // Verify encoded data is generated
218
+ const encodedData = sendable.getEncodedData();
219
+ expect(encodedData).toBeInstanceOf(Uint8Array);
220
+ expect(encodedData.length).toBeGreaterThan(0);
221
+
222
+ console.log("Encoded transfer data length:", encodedData.length);
223
+ },
224
+ { timeout: NETWORK_TIMEOUT },
225
+ );
226
+
227
+ it(
228
+ "should create sendable transaction for PSP22::approve",
229
+ async () => {
230
+ const sendable = usdtContract.send("PSP22::approve", {
231
+ origin: TEST_ADDRESSES.testUser,
232
+ args: {
233
+ spender: TEST_ADDRESSES.usdtContract,
234
+ value: 1000n,
235
+ },
236
+ });
237
+
238
+ expect(sendable).toBeDefined();
239
+ const encodedData = sendable.getEncodedData();
240
+ expect(encodedData).toBeInstanceOf(Uint8Array);
241
+ expect(encodedData.length).toBeGreaterThan(0);
242
+
243
+ console.log("Encoded approve data length:", encodedData.length);
244
+ },
245
+ { timeout: NETWORK_TIMEOUT },
246
+ );
247
+ });
248
+
249
+ describe("Contract Instance Properties", () => {
250
+ it("should have correct contract address", () => {
251
+ expect(usdtContract.address).toBe(TEST_ADDRESSES.usdtContract);
252
+ });
253
+
254
+ it("should have metadata", () => {
255
+ expect(usdtContract.metadata).toBeDefined();
256
+ expect(usdtContract.metadata.spec).toBeDefined();
257
+ expect(usdtContract.metadata.spec.messages).toBeDefined();
258
+ expect(usdtContract.metadata.spec.messages.length).toBeGreaterThan(0);
259
+ });
260
+
261
+ it("should have getStorage method", () => {
262
+ const storage = usdtContract.getStorage();
263
+ expect(storage).toBeDefined();
264
+ expect(typeof storage.getRoot).toBe("function");
265
+ expect(typeof storage.getNested).toBe("function");
266
+ });
267
+
268
+ it("should have filterEvents method", () => {
269
+ expect(typeof usdtContract.filterEvents).toBe("function");
270
+ });
271
+ });
272
+
273
+ describe("Multiple Queries Consistency", () => {
274
+ it(
275
+ "should return consistent results for same query",
276
+ async () => {
277
+ const result1 = await usdtContract.query("PSP22::balance_of", {
278
+ origin: TEST_ADDRESSES.testUser,
279
+ args: { owner: TEST_ADDRESSES.testUser },
280
+ });
281
+
282
+ const result2 = await usdtContract.query("PSP22::balance_of", {
283
+ origin: TEST_ADDRESSES.testUser,
284
+ args: { owner: TEST_ADDRESSES.testUser },
285
+ });
286
+
287
+ expect(result1.success).toBe(true);
288
+ expect(result2.success).toBe(true);
289
+
290
+ if (result1.success && result2.success) {
291
+ expect(result1.value).toBe(result2.value);
292
+ }
293
+ },
294
+ { timeout: NETWORK_TIMEOUT },
295
+ );
296
+ });
297
+ });
298
+
299
+ // USDT token decimals (standard for USDT)
300
+ const USDT_DECIMALS = 2n;
301
+
302
+ /**
303
+ * Real transaction tests - only run when TEST_WALLET_MNEMONIC is set
304
+ */
305
+ describe.skipIf(!process.env.TEST_WALLET_MNEMONIC)(
306
+ "USDT Real Transfer Test",
307
+ () => {
308
+ let d9Client: InkSdkClientReturn;
309
+ let usdtContract: D9InkContractFromDescriptor<typeof contracts.usdt>;
310
+ let wallet: ReturnType<typeof createSignerFromMnemonic>;
311
+
312
+ const TX_TIMEOUT = 120000; // 2 minutes for real transaction
313
+
314
+ beforeAll(async () => {
315
+ const mnemonic = process.env.TEST_WALLET_MNEMONIC!;
316
+ wallet = createSignerFromMnemonic(mnemonic);
317
+
318
+ console.log("Test wallet address:", wallet.address);
319
+
320
+ d9Client = createInkSdkClient({
321
+ endpoint: ENDPOINT,
322
+ });
323
+
324
+ usdtContract = d9Client.inkSdk.getContract(contracts.usdt, TEST_ADDRESSES.usdtContract);
325
+ });
326
+
327
+ afterAll(() => {
328
+ d9Client.client.destroy();
329
+ });
330
+
331
+ it(
332
+ "should transfer 0.01 USDT to self",
333
+ async () => {
334
+ // First, query current balance
335
+ const balanceBefore = await usdtContract.query("PSP22::balance_of", {
336
+ origin: wallet.address,
337
+ args: { owner: wallet.address },
338
+ });
339
+
340
+ expect(balanceBefore.success).toBe(true);
341
+ if (!balanceBefore.success) {
342
+ throw new Error(`Failed to query balance: ${balanceBefore.error}`);
343
+ }
344
+
345
+ // response is now directly bigint (MessageResult is unwrapped by SDK)
346
+ const currentBalance = balanceBefore.value;
347
+ const balanceInUsdt = Number(currentBalance) / Number(10n ** USDT_DECIMALS);
348
+
349
+ console.log("Balance before transfer:", currentBalance, `(${balanceInUsdt} USDT)`);
350
+ console.log("Transfer amount:", 0.01, `(0.01 USDT)`);
351
+
352
+ // Skip test if insufficient balance
353
+ if (currentBalance < 1) {
354
+ console.log(
355
+ `⚠️ Skipping transfer test: insufficient balance (need 0.01 USDT, have ${balanceInUsdt} USDT)`,
356
+ );
357
+ return;
358
+ }
359
+
360
+ // Perform dry-run first to get gas estimation
361
+ const queryResult = await usdtContract.query("PSP22::transfer", {
362
+ origin: wallet.address,
363
+ args: {
364
+ to: wallet.address,
365
+ value: 1n,
366
+ data: Binary.fromBytes(new Uint8Array(0)),
367
+ },
368
+ });
369
+
370
+ expect(queryResult.success).toBe(true);
371
+ if (!queryResult.success) {
372
+ throw new Error(`Transfer dry-run failed: ${queryResult.error}`);
373
+ }
374
+
375
+ console.log("Gas required:", queryResult.gasRequired);
376
+
377
+ // Execute real transfer using send() from query result
378
+ const txResult = await queryResult.send().signAndSubmit(wallet.signer);
379
+
380
+ console.log("Transaction result:", txResult);
381
+
382
+ expect(txResult.ok).toBe(true);
383
+ if (txResult.ok) {
384
+ console.log("Transaction hash:", txResult.txHash);
385
+ console.log("Block hash:", txResult.block.hash);
386
+ console.log("Block number:", txResult.block.number);
387
+ }
388
+
389
+ // Verify balance after (should be the same since we transferred to self)
390
+ const balanceAfter = await usdtContract.query("PSP22::balance_of", {
391
+ origin: wallet.address,
392
+ args: { owner: wallet.address },
393
+ });
394
+
395
+ expect(balanceAfter.success).toBe(true);
396
+ if (balanceAfter.success) {
397
+ console.log("Balance after transfer:", balanceAfter.value);
398
+ // Balance should remain the same (transfer to self)
399
+ expect(balanceAfter.value).toBe(balanceBefore.value);
400
+ }
401
+ },
402
+ { timeout: TX_TIMEOUT },
403
+ );
404
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,12 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "Bundler",
6
+ "strict": true,
7
+ "noEmit": true,
8
+ "types": ["bun"],
9
+ "skipLibCheck": true
10
+ },
11
+ "include": ["src/**/*", "test/**/*", "scripts/**/*"]
12
+ }
@@ -0,0 +1,13 @@
1
+ import { defineConfig } from "tsdown"
2
+
3
+ export default defineConfig({
4
+ entry: ["./src/index.ts"],
5
+ outDir: "./dist",
6
+ format: ["esm", "cjs"],
7
+ sourcemap: true,
8
+ clean: true,
9
+ dts: true,
10
+ exports: {
11
+ devExports: true
12
+ },
13
+ })