tusdt-sdk 0.1.0
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.
- package/LICENSE +21 -0
- package/README.md +167 -0
- package/dist/chunk-5HS7VSOA.js +1844 -0
- package/dist/chunk-5HS7VSOA.js.map +1 -0
- package/dist/client.d.ts +2 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +3 -0
- package/dist/client.js.map +1 -0
- package/dist/contract/events.d.ts +39 -0
- package/dist/contract/events.d.ts.map +1 -0
- package/dist/contract/finalized-events.d.ts +32 -0
- package/dist/contract/finalized-events.d.ts.map +1 -0
- package/dist/contract/helpers.d.ts +19 -0
- package/dist/contract/helpers.d.ts.map +1 -0
- package/dist/contract/payment-listener.d.ts +88 -0
- package/dist/contract/payment-listener.d.ts.map +1 -0
- package/dist/contract/queries.d.ts +23 -0
- package/dist/contract/queries.d.ts.map +1 -0
- package/dist/contract/transactions.d.ts +36 -0
- package/dist/contract/transactions.d.ts.map +1 -0
- package/dist/contract/tusdt-contract.d.ts +186 -0
- package/dist/contract/tusdt-contract.d.ts.map +1 -0
- package/dist/core/constants.d.ts +10 -0
- package/dist/core/constants.d.ts.map +1 -0
- package/dist/core/errors.d.ts +85 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/types.d.ts +97 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/validation.d.ts +30 -0
- package/dist/core/validation.d.ts.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +19 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +204 -0
- package/dist/server.js.map +1 -0
- package/dist/signer/types.d.ts +2 -0
- package/dist/signer/types.d.ts.map +1 -0
- package/metadata/tusdt_erc20.json +1133 -0
- package/package.json +83 -0
package/dist/server.js
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { tusdt_erc20_default, ZERO_ADDRESS } from './chunk-5HS7VSOA.js';
|
|
2
|
+
export { ConnectionError, ContractError, DispatchError, DryRunError, InsufficientAllowanceError, InsufficientBalanceError, NotControllerError, TimeoutError, TusdtContract, TusdtSdkError, U64_MAX, ValidationError, ZERO_BALANCE, executeApprove, executeTransfer, executeTransferFrom, getSignerAddress, isKeyringPair, mapContractError, queryAllowance, queryBalanceOf, queryController, queryTotalSupply, safeApprove, subscribeApproval, subscribeTransfer, subscribeTransferFrom, subscribeTransferTo, validateAddress, validateBalance, validateOverflowSafe, watchFinalizedTransfers, watchFinalizedTransfersTo } from './chunk-5HS7VSOA.js';
|
|
3
|
+
import { Contract } from 'dedot/contracts';
|
|
4
|
+
|
|
5
|
+
var DEFAULT_RECONNECT = {
|
|
6
|
+
maxRetries: Infinity,
|
|
7
|
+
baseDelayMs: 1e3,
|
|
8
|
+
maxDelayMs: 3e4
|
|
9
|
+
};
|
|
10
|
+
var DEDUP_SET_MAX = 1e4;
|
|
11
|
+
function parseBlockNumber(raw) {
|
|
12
|
+
if (typeof raw === "number") return raw;
|
|
13
|
+
return Number(String(raw ?? 0));
|
|
14
|
+
}
|
|
15
|
+
function backoffDelay(attempt, base, max) {
|
|
16
|
+
const exponential = base * Math.pow(2, attempt);
|
|
17
|
+
const capped = Math.min(exponential, max);
|
|
18
|
+
const jitter = capped * (0.75 + Math.random() * 0.5);
|
|
19
|
+
return Math.round(jitter);
|
|
20
|
+
}
|
|
21
|
+
var TusdtPaymentListener = class {
|
|
22
|
+
client;
|
|
23
|
+
contract;
|
|
24
|
+
config;
|
|
25
|
+
reconnectConfig;
|
|
26
|
+
_running = false;
|
|
27
|
+
_lastBlockNumber = 0;
|
|
28
|
+
_unsub = null;
|
|
29
|
+
_reconnectTimer = null;
|
|
30
|
+
_reconnectAttempt = 0;
|
|
31
|
+
_stopped = false;
|
|
32
|
+
// Deduplication: set of `${blockHash}:${txIndex}` keys
|
|
33
|
+
_seen = /* @__PURE__ */ new Set();
|
|
34
|
+
constructor(client, contractAddress, config) {
|
|
35
|
+
this.client = client;
|
|
36
|
+
this.config = config;
|
|
37
|
+
if (config.reconnect === false) {
|
|
38
|
+
this.reconnectConfig = null;
|
|
39
|
+
} else {
|
|
40
|
+
this.reconnectConfig = {
|
|
41
|
+
...DEFAULT_RECONNECT,
|
|
42
|
+
...config.reconnect ?? {}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
this.contract = new Contract(
|
|
46
|
+
client,
|
|
47
|
+
tusdt_erc20_default,
|
|
48
|
+
contractAddress,
|
|
49
|
+
{ defaultCaller: ZERO_ADDRESS }
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
/** Whether the listener is currently subscribed and processing blocks. */
|
|
53
|
+
get isRunning() {
|
|
54
|
+
return this._running;
|
|
55
|
+
}
|
|
56
|
+
/** The last finalized block number processed by this listener. */
|
|
57
|
+
get lastBlockNumber() {
|
|
58
|
+
return this._lastBlockNumber;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Start listening for finalized Transfer events to the configured receiver wallet.
|
|
62
|
+
*
|
|
63
|
+
* Subscribes to `chain_subscribeFinalizedHeads` and filters for Transfer events.
|
|
64
|
+
* If the listener is already running, this is a no-op.
|
|
65
|
+
*
|
|
66
|
+
* @throws If the initial subscription fails and reconnection is disabled.
|
|
67
|
+
*/
|
|
68
|
+
async start() {
|
|
69
|
+
if (this._running) return;
|
|
70
|
+
this._stopped = false;
|
|
71
|
+
this._reconnectAttempt = 0;
|
|
72
|
+
await this._subscribe();
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Stop the listener, unsubscribe, and clean up all timers.
|
|
76
|
+
*
|
|
77
|
+
* Safe to call multiple times. After stopping, the listener can be restarted with `start()`.
|
|
78
|
+
*/
|
|
79
|
+
async stop() {
|
|
80
|
+
this._stopped = true;
|
|
81
|
+
this._running = false;
|
|
82
|
+
if (this._reconnectTimer !== null) {
|
|
83
|
+
clearTimeout(this._reconnectTimer);
|
|
84
|
+
this._reconnectTimer = null;
|
|
85
|
+
}
|
|
86
|
+
if (this._unsub) {
|
|
87
|
+
try {
|
|
88
|
+
this._unsub();
|
|
89
|
+
} catch {
|
|
90
|
+
}
|
|
91
|
+
this._unsub = null;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// ─── Internal ────────────────────────────────────────────────
|
|
95
|
+
async _subscribe() {
|
|
96
|
+
if (this._stopped) return;
|
|
97
|
+
try {
|
|
98
|
+
const unsub = await this.client.rpc.chain_subscribeFinalizedHeads(
|
|
99
|
+
async (header) => {
|
|
100
|
+
await this._processHeader(header);
|
|
101
|
+
}
|
|
102
|
+
);
|
|
103
|
+
this._unsub = typeof unsub === "function" ? unsub : () => {
|
|
104
|
+
};
|
|
105
|
+
this._running = true;
|
|
106
|
+
this._reconnectAttempt = 0;
|
|
107
|
+
} catch (error) {
|
|
108
|
+
this._running = false;
|
|
109
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
110
|
+
if (this.reconnectConfig && !this._stopped) {
|
|
111
|
+
this._reportError(new Error(`Subscription failed, will reconnect: ${err.message}`));
|
|
112
|
+
this._scheduleReconnect();
|
|
113
|
+
} else {
|
|
114
|
+
throw err;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
async _processHeader(header) {
|
|
119
|
+
try {
|
|
120
|
+
const blockHash = header.hash?.toString() ?? header.parentHash?.toString() ?? "";
|
|
121
|
+
const blockNumber = parseBlockNumber(header.number);
|
|
122
|
+
if (blockNumber <= this._lastBlockNumber && this._lastBlockNumber > 0) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const events = await this.client.query.system.events.at(blockHash);
|
|
126
|
+
const transferEvents = this.contract.events.Transfer.filter(events);
|
|
127
|
+
for (let i = 0; i < transferEvents.length; i++) {
|
|
128
|
+
const evt = transferEvents[i];
|
|
129
|
+
const data = evt.data ?? evt;
|
|
130
|
+
const to = data.to?.toString() ?? null;
|
|
131
|
+
if (to !== this.config.receiverWallet) continue;
|
|
132
|
+
const dedupKey = `${blockHash}:${i}`;
|
|
133
|
+
if (this._seen.has(dedupKey)) continue;
|
|
134
|
+
this._seen.add(dedupKey);
|
|
135
|
+
if (this._seen.size > DEDUP_SET_MAX) {
|
|
136
|
+
const entries = Array.from(this._seen);
|
|
137
|
+
for (let j = 0; j < entries.length / 2; j++) {
|
|
138
|
+
this._seen.delete(entries[j]);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const event = {
|
|
142
|
+
from: data.from?.toString() ?? null,
|
|
143
|
+
to,
|
|
144
|
+
value: BigInt(data.value ?? 0),
|
|
145
|
+
blockHash,
|
|
146
|
+
blockNumber,
|
|
147
|
+
txIndex: i
|
|
148
|
+
};
|
|
149
|
+
try {
|
|
150
|
+
await this.config.onPayment(event);
|
|
151
|
+
} catch (callbackError) {
|
|
152
|
+
this._reportError(
|
|
153
|
+
callbackError instanceof Error ? callbackError : new Error(`Payment callback error: ${String(callbackError)}`)
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
this._lastBlockNumber = blockNumber;
|
|
158
|
+
} catch (error) {
|
|
159
|
+
this._reportError(
|
|
160
|
+
error instanceof Error ? error : new Error(String(error))
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
_scheduleReconnect() {
|
|
165
|
+
if (this._stopped || !this.reconnectConfig) return;
|
|
166
|
+
const { maxRetries, baseDelayMs, maxDelayMs } = this.reconnectConfig;
|
|
167
|
+
if (this._reconnectAttempt >= maxRetries) {
|
|
168
|
+
this._reportError(
|
|
169
|
+
new Error(`Max reconnection attempts (${maxRetries}) reached. Giving up.`)
|
|
170
|
+
);
|
|
171
|
+
this._running = false;
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
const delay = backoffDelay(this._reconnectAttempt, baseDelayMs, maxDelayMs);
|
|
175
|
+
this._reconnectAttempt++;
|
|
176
|
+
this._reconnectTimer = setTimeout(async () => {
|
|
177
|
+
this._reconnectTimer = null;
|
|
178
|
+
if (!this._stopped) {
|
|
179
|
+
await this._subscribe();
|
|
180
|
+
}
|
|
181
|
+
}, delay);
|
|
182
|
+
}
|
|
183
|
+
_reportError(error) {
|
|
184
|
+
if (this.config.onError) {
|
|
185
|
+
try {
|
|
186
|
+
this.config.onError(error);
|
|
187
|
+
} catch {
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
// src/server.ts
|
|
194
|
+
async function createSigner(suri, type = "sr25519") {
|
|
195
|
+
const { Keyring } = await import('@polkadot/keyring');
|
|
196
|
+
const { cryptoWaitReady } = await import('@polkadot/util-crypto');
|
|
197
|
+
await cryptoWaitReady();
|
|
198
|
+
const keyring = new Keyring({ type });
|
|
199
|
+
return keyring.addFromUri(suri);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export { TusdtPaymentListener, createSigner };
|
|
203
|
+
//# sourceMappingURL=server.js.map
|
|
204
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/contract/payment-listener.ts","../src/server.ts"],"names":[],"mappings":"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","file":"server.js","sourcesContent":["import { Contract } from 'dedot/contracts';\nimport type { SubstrateClient } from './tusdt-contract.js';\nimport type {\n AccountId,\n FinalizedTransferEvent,\n Unsubscribe,\n} from '../core/types.js';\nimport { ZERO_ADDRESS } from '../core/constants.js';\n\nimport tusdtErc20Metadata from '../../metadata/tusdt_erc20.json';\n\n// ---------------------------------------------------------------------------\n// Public configuration types\n// ---------------------------------------------------------------------------\n\n/** Reconnection strategy configuration. */\nexport interface ReconnectConfig {\n /** Maximum reconnection attempts before giving up. Default: Infinity (never give up). */\n maxRetries?: number;\n /** Base delay in milliseconds for the first retry. Default: 1000. */\n baseDelayMs?: number;\n /** Maximum delay cap in milliseconds. Default: 30000. */\n maxDelayMs?: number;\n}\n\n/** Configuration for TusdtPaymentListener. */\nexport interface PaymentListenerConfig {\n /** SS58 address of the wallet to watch for incoming transfers. */\n receiverWallet: AccountId;\n /** Called for each incoming transfer to the receiver wallet (finalized). */\n onPayment: (event: FinalizedTransferEvent) => void | Promise<void>;\n /** Called when an error occurs during block processing or callback execution. */\n onError?: (error: Error) => void;\n /** Reconnection strategy. Pass `false` to disable reconnection entirely. */\n reconnect?: ReconnectConfig | false;\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nconst DEFAULT_RECONNECT: Required<ReconnectConfig> = {\n maxRetries: Infinity,\n baseDelayMs: 1_000,\n maxDelayMs: 30_000,\n};\n\nconst DEDUP_SET_MAX = 10_000;\n\n/** Safely parse block number from header, which may be a number, bigint, or stringifiable object. */\nfunction parseBlockNumber(raw: unknown): number {\n if (typeof raw === 'number') return raw;\n return Number(String(raw ?? 0));\n}\n\n/** Compute delay with exponential backoff and jitter. */\nfunction backoffDelay(attempt: number, base: number, max: number): number {\n const exponential = base * Math.pow(2, attempt);\n const capped = Math.min(exponential, max);\n // Add ±25% jitter to prevent thundering herd\n const jitter = capped * (0.75 + Math.random() * 0.5);\n return Math.round(jitter);\n}\n\n// ---------------------------------------------------------------------------\n// TusdtPaymentListener\n// ---------------------------------------------------------------------------\n\n/**\n * Production-grade listener for incoming TUSDT transfers on finalized blocks.\n *\n * Designed for backend payment detection and reconciliation:\n * - **Finalized blocks only** -- immutable, no reorg risk\n * - **Automatic reconnection** with exponential backoff\n * - **Event deduplication** -- monotonic block tracking + bounded event ID set\n * - **Error isolation** -- callback errors don't kill the listener\n *\n * Usage:\n * ```ts\n * import { DedotClient, WsProvider } from 'dedot';\n * import { TusdtPaymentListener, createSigner } from 'tusdt-sdk/server';\n *\n * const client = await DedotClient.legacy(new WsProvider('wss://...'));\n * const listener = new TusdtPaymentListener(client, '5Cp7...', {\n * receiverWallet: '5HhJ...',\n * onPayment: (event) => {\n * console.log(`Received ${event.value} from ${event.from} at block ${event.blockNumber}`);\n * },\n * onError: (err) => console.error('Listener error:', err),\n * });\n *\n * await listener.start();\n * // ... later ...\n * await listener.stop();\n * ```\n */\nexport class TusdtPaymentListener {\n private readonly client: SubstrateClient;\n private readonly contract: Contract<any>;\n private readonly config: PaymentListenerConfig;\n private readonly reconnectConfig: Required<ReconnectConfig> | null;\n\n private _running = false;\n private _lastBlockNumber = 0;\n private _unsub: Unsubscribe | null = null;\n private _reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n private _reconnectAttempt = 0;\n private _stopped = false;\n\n // Deduplication: set of `${blockHash}:${txIndex}` keys\n private readonly _seen = new Set<string>();\n\n constructor(\n client: SubstrateClient,\n contractAddress: AccountId,\n config: PaymentListenerConfig,\n ) {\n this.client = client;\n this.config = config;\n\n // Build reconnect config\n if (config.reconnect === false) {\n this.reconnectConfig = null;\n } else {\n this.reconnectConfig = {\n ...DEFAULT_RECONNECT,\n ...(config.reconnect ?? {}),\n };\n }\n\n // Construct contract from bundled metadata (same pattern as TusdtContract)\n this.contract = new Contract(\n client as any,\n tusdtErc20Metadata as any,\n contractAddress,\n { defaultCaller: ZERO_ADDRESS },\n );\n }\n\n /** Whether the listener is currently subscribed and processing blocks. */\n get isRunning(): boolean {\n return this._running;\n }\n\n /** The last finalized block number processed by this listener. */\n get lastBlockNumber(): number {\n return this._lastBlockNumber;\n }\n\n /**\n * Start listening for finalized Transfer events to the configured receiver wallet.\n *\n * Subscribes to `chain_subscribeFinalizedHeads` and filters for Transfer events.\n * If the listener is already running, this is a no-op.\n *\n * @throws If the initial subscription fails and reconnection is disabled.\n */\n async start(): Promise<void> {\n if (this._running) return;\n this._stopped = false;\n this._reconnectAttempt = 0;\n await this._subscribe();\n }\n\n /**\n * Stop the listener, unsubscribe, and clean up all timers.\n *\n * Safe to call multiple times. After stopping, the listener can be restarted with `start()`.\n */\n async stop(): Promise<void> {\n this._stopped = true;\n this._running = false;\n\n if (this._reconnectTimer !== null) {\n clearTimeout(this._reconnectTimer);\n this._reconnectTimer = null;\n }\n\n if (this._unsub) {\n try {\n this._unsub();\n } catch {\n // Ignore unsubscribe errors during shutdown\n }\n this._unsub = null;\n }\n }\n\n // ─── Internal ────────────────────────────────────────────────\n\n private async _subscribe(): Promise<void> {\n if (this._stopped) return;\n\n try {\n const unsub = await this.client.rpc.chain_subscribeFinalizedHeads(\n async (header: any) => {\n await this._processHeader(header);\n },\n );\n\n this._unsub = typeof unsub === 'function' ? unsub : () => {};\n this._running = true;\n this._reconnectAttempt = 0; // Reset on successful subscription\n } catch (error) {\n this._running = false;\n const err = error instanceof Error ? error : new Error(String(error));\n\n if (this.reconnectConfig && !this._stopped) {\n this._reportError(new Error(`Subscription failed, will reconnect: ${err.message}`));\n this._scheduleReconnect();\n } else {\n throw err;\n }\n }\n }\n\n private async _processHeader(header: any): Promise<void> {\n try {\n const blockHash =\n header.hash?.toString() ?? header.parentHash?.toString() ?? '';\n const blockNumber = parseBlockNumber(header.number);\n\n // Monotonic block dedup: skip blocks we've already processed\n if (blockNumber <= this._lastBlockNumber && this._lastBlockNumber > 0) {\n return;\n }\n\n const events = await this.client.query.system.events.at(blockHash);\n const transferEvents = this.contract.events.Transfer.filter(events);\n\n for (let i = 0; i < transferEvents.length; i++) {\n const evt = transferEvents[i];\n const data = evt.data ?? evt;\n const to = data.to?.toString() ?? null;\n\n // Filter: only events to our receiver wallet\n if (to !== this.config.receiverWallet) continue;\n\n // Event-level dedup\n const dedupKey = `${blockHash}:${i}`;\n if (this._seen.has(dedupKey)) continue;\n this._seen.add(dedupKey);\n\n // Prune dedup set if it grows too large\n if (this._seen.size > DEDUP_SET_MAX) {\n const entries = Array.from(this._seen);\n // Remove the oldest half\n for (let j = 0; j < entries.length / 2; j++) {\n this._seen.delete(entries[j]);\n }\n }\n\n const event: FinalizedTransferEvent = {\n from: data.from?.toString() ?? null,\n to,\n value: BigInt(data.value ?? 0),\n blockHash,\n blockNumber,\n txIndex: i,\n };\n\n // Invoke callback with error isolation\n try {\n await this.config.onPayment(event);\n } catch (callbackError) {\n this._reportError(\n callbackError instanceof Error\n ? callbackError\n : new Error(`Payment callback error: ${String(callbackError)}`),\n );\n }\n }\n\n this._lastBlockNumber = blockNumber;\n } catch (error) {\n // Block processing error -- report but don't kill the subscription\n this._reportError(\n error instanceof Error ? error : new Error(String(error)),\n );\n }\n }\n\n private _scheduleReconnect(): void {\n if (this._stopped || !this.reconnectConfig) return;\n\n const { maxRetries, baseDelayMs, maxDelayMs } = this.reconnectConfig;\n\n if (this._reconnectAttempt >= maxRetries) {\n this._reportError(\n new Error(`Max reconnection attempts (${maxRetries}) reached. Giving up.`),\n );\n this._running = false;\n return;\n }\n\n const delay = backoffDelay(this._reconnectAttempt, baseDelayMs, maxDelayMs);\n this._reconnectAttempt++;\n\n this._reconnectTimer = setTimeout(async () => {\n this._reconnectTimer = null;\n if (!this._stopped) {\n await this._subscribe();\n }\n }, delay);\n }\n\n private _reportError(error: Error): void {\n if (this.config.onError) {\n try {\n this.config.onError(error);\n } catch {\n // Swallow errors from the error handler itself\n }\n }\n }\n}\n","// Node.js entrypoint: \"./server\"\n// Re-exports everything from the root plus backend-specific helpers.\nexport * from './index.js';\n\n// ─── Payment Listener ────────────────────────────────────────────\nexport { TusdtPaymentListener } from './contract/payment-listener.js';\nexport type { PaymentListenerConfig, ReconnectConfig } from './contract/payment-listener.js';\n\n/**\n * Create an IKeyringPair signer from a secret URI (e.g., \"//Alice\", \"//Bob\").\n *\n * This is a convenience helper for backend and testing environments.\n * Uses dynamic imports to avoid hard errors when @polkadot/keyring is not installed.\n *\n * Requires peer dependencies:\n * - @polkadot/keyring\n * - @polkadot/util-crypto\n *\n * @param suri - Secret URI (e.g., \"//Alice\", \"//Bob\", or a mnemonic)\n * @param type - Key type. Default: 'sr25519'\n * @returns An IKeyringPair that can be passed as a TusdtSigner\n */\nexport async function createSigner(\n suri: string,\n type: 'sr25519' | 'ed25519' = 'sr25519',\n): Promise<import('./core/types.js').KeyringPairSigner> {\n const { Keyring } = await import('@polkadot/keyring');\n const { cryptoWaitReady } = await import('@polkadot/util-crypto');\n await cryptoWaitReady();\n const keyring = new Keyring({ type });\n return keyring.addFromUri(suri) as unknown as import('./core/types.js').KeyringPairSigner;\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/signer/types.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,WAAW,EACX,iBAAiB,EACjB,oBAAoB,GACrB,MAAM,kBAAkB,CAAC"}
|