wative-core 2.4.6 → 2.5.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/CHANGELOG.md +18 -0
- package/README.md +47 -5
- package/dist/artifacts/evm/index.cjs +1 -1
- package/dist/artifacts/evm/index.d.cts +2 -2
- package/dist/artifacts/evm/index.d.ts +2 -2
- package/dist/artifacts/evm/index.js +1 -1
- package/dist/artifacts/svm/index.cjs +1 -1
- package/dist/artifacts/svm/index.d.cts +3 -3
- package/dist/artifacts/svm/index.d.ts +3 -3
- package/dist/artifacts/svm/index.js +1 -1
- package/dist/{container-provider-CCBOKuHv.d.cts → container-provider-BCG7wTYx.d.cts} +1 -1
- package/dist/{container-provider-COf_p1JR.d.ts → container-provider-DMZGR5MP.d.ts} +1 -1
- package/dist/{evm-B5b_UPYy.d.ts → evm-BZoe2IFx.d.ts} +1 -1
- package/dist/{evm-BMB0sT3U.d.cts → evm-BeI4m7M5.d.cts} +1 -1
- package/dist/{file-sink-778TcqKz.d.cts → file-sink-BLqWAqHB.d.cts} +2 -2
- package/dist/{file-sink-Bu1YeJSR.d.ts → file-sink-CuPr45EN.d.ts} +2 -2
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +55 -7
- package/dist/index.d.ts +55 -7
- package/dist/index.js +1 -1
- package/dist/index.node.cjs +1 -1
- package/dist/index.node.d.cts +6 -6
- package/dist/index.node.d.ts +6 -6
- package/dist/index.node.js +1 -1
- package/dist/node/index.cjs +1 -1
- package/dist/node/index.d.cts +3 -3
- package/dist/node/index.d.ts +3 -3
- package/dist/node/index.js +1 -1
- package/dist/{svm-CZ-D916M.d.ts → svm-BQMaUXEA.d.ts} +1 -1
- package/dist/{svm-BRKxogvC.d.cts → svm-Bi74PhrA.d.cts} +1 -1
- package/dist/{workspace-Clmfomrc.d.cts → workspace-ZNpsa6W7.d.cts} +36 -11
- package/dist/{workspace-Clmfomrc.d.ts → workspace-ZNpsa6W7.d.ts} +36 -11
- package/examples/04-network-management.test.cjs +5 -4
- package/examples/07-custom-provider.test.cjs +1 -1
- package/examples/20-custom-chain-dialect.test.cjs +155 -0
- package/examples/21-token-transfer.test.cjs +85 -0
- package/examples/README.md +5 -2
- package/package.json +1 -1
|
@@ -44,7 +44,7 @@ interface NetworkInit {
|
|
|
44
44
|
};
|
|
45
45
|
multicallAddress?: string;
|
|
46
46
|
color?: string;
|
|
47
|
-
vm:
|
|
47
|
+
vm: VmToken;
|
|
48
48
|
}
|
|
49
49
|
declare class Network {
|
|
50
50
|
readonly slug: Slug;
|
|
@@ -58,7 +58,7 @@ declare class Network {
|
|
|
58
58
|
};
|
|
59
59
|
readonly multicallAddress?: string;
|
|
60
60
|
readonly color?: string;
|
|
61
|
-
readonly vm:
|
|
61
|
+
readonly vm: VmToken;
|
|
62
62
|
|
|
63
63
|
get symbol(): string;
|
|
64
64
|
|
|
@@ -83,6 +83,10 @@ declare class Network {
|
|
|
83
83
|
static readonly Solana: Network;
|
|
84
84
|
static readonly SolanaTestnet: Network;
|
|
85
85
|
static readonly SolanaDevnet: Network;
|
|
86
|
+
static readonly HyperEVM: Network;
|
|
87
|
+
static readonly HyperEVMTestnet: Network;
|
|
88
|
+
static readonly Robinhood: Network;
|
|
89
|
+
static readonly RobinhoodTestnet: Network;
|
|
86
90
|
static builtin(slug: Slug): Network | null;
|
|
87
91
|
static all(): readonly Network[];
|
|
88
92
|
static isBuiltinSlug(slug: Slug): boolean;
|
|
@@ -107,6 +111,7 @@ type SuiAddress = Brand<string, "SuiAddress">;
|
|
|
107
111
|
type ChainId = Brand<number, "ChainId">;
|
|
108
112
|
type AssetId = Brand<number, "AssetId">;
|
|
109
113
|
type ChainVM = "evm" | "svm" | "sui";
|
|
114
|
+
type VmToken = ChainVM | (string & {});
|
|
110
115
|
|
|
111
116
|
type NetworkLike = Network | ChainId | string;
|
|
112
117
|
|
|
@@ -229,8 +234,22 @@ interface SvmTxBuildParams {
|
|
|
229
234
|
addressLookupTables?: ReadonlyArray<string>;
|
|
230
235
|
memo?: string;
|
|
231
236
|
}
|
|
237
|
+
|
|
238
|
+
type AssetRef = {
|
|
239
|
+
native: true;
|
|
240
|
+
} | {
|
|
241
|
+
address: string;
|
|
242
|
+
} | {
|
|
243
|
+
symbol: string;
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
interface TransferRequest {
|
|
247
|
+
to: string;
|
|
248
|
+
asset?: AssetRef;
|
|
249
|
+
amount: bigint;
|
|
250
|
+
}
|
|
232
251
|
interface Transaction {
|
|
233
|
-
readonly vm:
|
|
252
|
+
readonly vm: VmToken;
|
|
234
253
|
readonly status: TransactionStatus;
|
|
235
254
|
sign(): Promise<this> | this;
|
|
236
255
|
send(): TransactionTracker;
|
|
@@ -257,7 +276,7 @@ interface TransactionStateChange {
|
|
|
257
276
|
}
|
|
258
277
|
interface TransactionReceipt {
|
|
259
278
|
readonly hash: string;
|
|
260
|
-
readonly vm:
|
|
279
|
+
readonly vm: VmToken;
|
|
261
280
|
readonly blockNumber: number | null;
|
|
262
281
|
readonly blockHash: string | null;
|
|
263
282
|
readonly index: number | null;
|
|
@@ -431,7 +450,7 @@ declare class SvmTransaction implements Transaction {
|
|
|
431
450
|
|
|
432
451
|
interface AddressInit {
|
|
433
452
|
publicKey: EvmAddress | SvmAddress | SuiAddress;
|
|
434
|
-
vm:
|
|
453
|
+
vm: VmToken;
|
|
435
454
|
network: Network;
|
|
436
455
|
ciphertext: string;
|
|
437
456
|
assets?: Asset[];
|
|
@@ -440,7 +459,7 @@ interface AddressInit {
|
|
|
440
459
|
declare class Address {
|
|
441
460
|
#private;
|
|
442
461
|
readonly publicKey: EvmAddress | SvmAddress | SuiAddress;
|
|
443
|
-
readonly vm:
|
|
462
|
+
readonly vm: VmToken;
|
|
444
463
|
readonly network: Network;
|
|
445
464
|
readonly assets: AssetCollection;
|
|
446
465
|
|
|
@@ -467,6 +486,8 @@ declare class Address {
|
|
|
467
486
|
buildTransaction(params: EvmTxBuildParams): EvmTransaction;
|
|
468
487
|
buildTransaction(params: SvmTxBuildParams): SvmTransaction;
|
|
469
488
|
|
|
489
|
+
transfer(req: TransferRequest): Transaction;
|
|
490
|
+
|
|
470
491
|
signTransaction<T extends Transaction>(transaction: T): T;
|
|
471
492
|
|
|
472
493
|
sendTransaction(transaction: Transaction): TransactionTracker;
|
|
@@ -477,6 +498,10 @@ declare class Address {
|
|
|
477
498
|
refreshBalances(assetId: AssetId): Settling<AssetBalance>;
|
|
478
499
|
readonly _wallet: Wallet;
|
|
479
500
|
}
|
|
501
|
+
declare class EvmSigner extends Address {
|
|
502
|
+
}
|
|
503
|
+
declare class SvmSigner extends Address {
|
|
504
|
+
}
|
|
480
505
|
|
|
481
506
|
interface WalletInit {
|
|
482
507
|
id: number;
|
|
@@ -494,12 +519,12 @@ declare class Wallet {
|
|
|
494
519
|
|
|
495
520
|
_addAddress(address: Address): void;
|
|
496
521
|
_setAddresses(addresses: Address[]): void;
|
|
497
|
-
get evm():
|
|
498
|
-
get svm():
|
|
522
|
+
get evm(): EvmSigner | null;
|
|
523
|
+
get svm(): SvmSigner | null;
|
|
499
524
|
get sui(): Address | null;
|
|
500
525
|
|
|
501
526
|
toJson(): ReadonlyArray<{
|
|
502
|
-
vm:
|
|
527
|
+
vm: VmToken;
|
|
503
528
|
publicKey: string;
|
|
504
529
|
network: {
|
|
505
530
|
slug: Slug;
|
|
@@ -542,7 +567,7 @@ interface PersistedWallet {
|
|
|
542
567
|
}
|
|
543
568
|
interface PersistedAddress {
|
|
544
569
|
publicKey: string;
|
|
545
|
-
vm:
|
|
570
|
+
vm: VmToken;
|
|
546
571
|
networkSlug: string;
|
|
547
572
|
ciphertext: string;
|
|
548
573
|
}
|
|
@@ -774,4 +799,4 @@ declare class Workspace {
|
|
|
774
799
|
} | null;
|
|
775
800
|
}
|
|
776
801
|
|
|
777
|
-
export { Account as A, type
|
|
802
|
+
export { type WativeErrorOpts as $, Account as A, type SimulationResult as B, type ChainId as C, type SinkConfig as D, type EvmAddress as E, type Slug as F, type SvmAddress as G, SvmSigner as H, SvmTransaction as I, type SvmTxBuildParams as J, type TransactionReceipt as K, type LogLevel as L, type TransactionStateChange as M, Network as N, type OpenOptions as O, Provider as P, type TransactionStatus as Q, Record$1 as R, type Settling as S, type Transaction as T, type TransactionTracker as U, type TransferRequest as V, type VmToken as W, Wallet as X, type WalletCollection as Y, WativeError as Z, type WativeErrorCode as _, type AccountCollection as a, Workspace as a0, type WorkspaceConfig as a1, newMnemonic as a2, type SuiAddress as a3, type AccountOrgType as b, Address as c, Asset as d, type AssetBalance as e, type AssetCollection as f, type AssetId as g, type AssetInitObject as h, type AssetRef as i, type ChainVM as j, ConsoleSink as k, type ContainerState as l, type EvmRawTx as m, EvmSigner as n, EvmTransaction as o, type EvmTxBuildParams as p, EvmTxType as q, type LogRecord as r, type LogSink as s, Logger as t, type LoggerConfig as u, type NetworkCollection as v, type NetworkInit as w, type NetworkLike as x, type RecordInit as y, type RecordType as z };
|
|
@@ -44,7 +44,7 @@ interface NetworkInit {
|
|
|
44
44
|
};
|
|
45
45
|
multicallAddress?: string;
|
|
46
46
|
color?: string;
|
|
47
|
-
vm:
|
|
47
|
+
vm: VmToken;
|
|
48
48
|
}
|
|
49
49
|
declare class Network {
|
|
50
50
|
readonly slug: Slug;
|
|
@@ -58,7 +58,7 @@ declare class Network {
|
|
|
58
58
|
};
|
|
59
59
|
readonly multicallAddress?: string;
|
|
60
60
|
readonly color?: string;
|
|
61
|
-
readonly vm:
|
|
61
|
+
readonly vm: VmToken;
|
|
62
62
|
|
|
63
63
|
get symbol(): string;
|
|
64
64
|
|
|
@@ -83,6 +83,10 @@ declare class Network {
|
|
|
83
83
|
static readonly Solana: Network;
|
|
84
84
|
static readonly SolanaTestnet: Network;
|
|
85
85
|
static readonly SolanaDevnet: Network;
|
|
86
|
+
static readonly HyperEVM: Network;
|
|
87
|
+
static readonly HyperEVMTestnet: Network;
|
|
88
|
+
static readonly Robinhood: Network;
|
|
89
|
+
static readonly RobinhoodTestnet: Network;
|
|
86
90
|
static builtin(slug: Slug): Network | null;
|
|
87
91
|
static all(): readonly Network[];
|
|
88
92
|
static isBuiltinSlug(slug: Slug): boolean;
|
|
@@ -107,6 +111,7 @@ type SuiAddress = Brand<string, "SuiAddress">;
|
|
|
107
111
|
type ChainId = Brand<number, "ChainId">;
|
|
108
112
|
type AssetId = Brand<number, "AssetId">;
|
|
109
113
|
type ChainVM = "evm" | "svm" | "sui";
|
|
114
|
+
type VmToken = ChainVM | (string & {});
|
|
110
115
|
|
|
111
116
|
type NetworkLike = Network | ChainId | string;
|
|
112
117
|
|
|
@@ -229,8 +234,22 @@ interface SvmTxBuildParams {
|
|
|
229
234
|
addressLookupTables?: ReadonlyArray<string>;
|
|
230
235
|
memo?: string;
|
|
231
236
|
}
|
|
237
|
+
|
|
238
|
+
type AssetRef = {
|
|
239
|
+
native: true;
|
|
240
|
+
} | {
|
|
241
|
+
address: string;
|
|
242
|
+
} | {
|
|
243
|
+
symbol: string;
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
interface TransferRequest {
|
|
247
|
+
to: string;
|
|
248
|
+
asset?: AssetRef;
|
|
249
|
+
amount: bigint;
|
|
250
|
+
}
|
|
232
251
|
interface Transaction {
|
|
233
|
-
readonly vm:
|
|
252
|
+
readonly vm: VmToken;
|
|
234
253
|
readonly status: TransactionStatus;
|
|
235
254
|
sign(): Promise<this> | this;
|
|
236
255
|
send(): TransactionTracker;
|
|
@@ -257,7 +276,7 @@ interface TransactionStateChange {
|
|
|
257
276
|
}
|
|
258
277
|
interface TransactionReceipt {
|
|
259
278
|
readonly hash: string;
|
|
260
|
-
readonly vm:
|
|
279
|
+
readonly vm: VmToken;
|
|
261
280
|
readonly blockNumber: number | null;
|
|
262
281
|
readonly blockHash: string | null;
|
|
263
282
|
readonly index: number | null;
|
|
@@ -431,7 +450,7 @@ declare class SvmTransaction implements Transaction {
|
|
|
431
450
|
|
|
432
451
|
interface AddressInit {
|
|
433
452
|
publicKey: EvmAddress | SvmAddress | SuiAddress;
|
|
434
|
-
vm:
|
|
453
|
+
vm: VmToken;
|
|
435
454
|
network: Network;
|
|
436
455
|
ciphertext: string;
|
|
437
456
|
assets?: Asset[];
|
|
@@ -440,7 +459,7 @@ interface AddressInit {
|
|
|
440
459
|
declare class Address {
|
|
441
460
|
#private;
|
|
442
461
|
readonly publicKey: EvmAddress | SvmAddress | SuiAddress;
|
|
443
|
-
readonly vm:
|
|
462
|
+
readonly vm: VmToken;
|
|
444
463
|
readonly network: Network;
|
|
445
464
|
readonly assets: AssetCollection;
|
|
446
465
|
|
|
@@ -467,6 +486,8 @@ declare class Address {
|
|
|
467
486
|
buildTransaction(params: EvmTxBuildParams): EvmTransaction;
|
|
468
487
|
buildTransaction(params: SvmTxBuildParams): SvmTransaction;
|
|
469
488
|
|
|
489
|
+
transfer(req: TransferRequest): Transaction;
|
|
490
|
+
|
|
470
491
|
signTransaction<T extends Transaction>(transaction: T): T;
|
|
471
492
|
|
|
472
493
|
sendTransaction(transaction: Transaction): TransactionTracker;
|
|
@@ -477,6 +498,10 @@ declare class Address {
|
|
|
477
498
|
refreshBalances(assetId: AssetId): Settling<AssetBalance>;
|
|
478
499
|
readonly _wallet: Wallet;
|
|
479
500
|
}
|
|
501
|
+
declare class EvmSigner extends Address {
|
|
502
|
+
}
|
|
503
|
+
declare class SvmSigner extends Address {
|
|
504
|
+
}
|
|
480
505
|
|
|
481
506
|
interface WalletInit {
|
|
482
507
|
id: number;
|
|
@@ -494,12 +519,12 @@ declare class Wallet {
|
|
|
494
519
|
|
|
495
520
|
_addAddress(address: Address): void;
|
|
496
521
|
_setAddresses(addresses: Address[]): void;
|
|
497
|
-
get evm():
|
|
498
|
-
get svm():
|
|
522
|
+
get evm(): EvmSigner | null;
|
|
523
|
+
get svm(): SvmSigner | null;
|
|
499
524
|
get sui(): Address | null;
|
|
500
525
|
|
|
501
526
|
toJson(): ReadonlyArray<{
|
|
502
|
-
vm:
|
|
527
|
+
vm: VmToken;
|
|
503
528
|
publicKey: string;
|
|
504
529
|
network: {
|
|
505
530
|
slug: Slug;
|
|
@@ -542,7 +567,7 @@ interface PersistedWallet {
|
|
|
542
567
|
}
|
|
543
568
|
interface PersistedAddress {
|
|
544
569
|
publicKey: string;
|
|
545
|
-
vm:
|
|
570
|
+
vm: VmToken;
|
|
546
571
|
networkSlug: string;
|
|
547
572
|
ciphertext: string;
|
|
548
573
|
}
|
|
@@ -774,4 +799,4 @@ declare class Workspace {
|
|
|
774
799
|
} | null;
|
|
775
800
|
}
|
|
776
801
|
|
|
777
|
-
export { Account as A, type
|
|
802
|
+
export { type WativeErrorOpts as $, Account as A, type SimulationResult as B, type ChainId as C, type SinkConfig as D, type EvmAddress as E, type Slug as F, type SvmAddress as G, SvmSigner as H, SvmTransaction as I, type SvmTxBuildParams as J, type TransactionReceipt as K, type LogLevel as L, type TransactionStateChange as M, Network as N, type OpenOptions as O, Provider as P, type TransactionStatus as Q, Record$1 as R, type Settling as S, type Transaction as T, type TransactionTracker as U, type TransferRequest as V, type VmToken as W, Wallet as X, type WalletCollection as Y, WativeError as Z, type WativeErrorCode as _, type AccountCollection as a, Workspace as a0, type WorkspaceConfig as a1, newMnemonic as a2, type SuiAddress as a3, type AccountOrgType as b, Address as c, Asset as d, type AssetBalance as e, type AssetCollection as f, type AssetId as g, type AssetInitObject as h, type AssetRef as i, type ChainVM as j, ConsoleSink as k, type ContainerState as l, type EvmRawTx as m, EvmSigner as n, EvmTransaction as o, type EvmTxBuildParams as p, EvmTxType as q, type LogRecord as r, type LogSink as s, Logger as t, type LoggerConfig as u, type NetworkCollection as v, type NetworkInit as w, type NetworkLike as x, type RecordInit as y, type RecordType as z };
|
|
@@ -8,13 +8,14 @@ const { join } = require("node:path");
|
|
|
8
8
|
|
|
9
9
|
const { Workspace, Network } = require("wative-core");
|
|
10
10
|
|
|
11
|
-
test("network —
|
|
11
|
+
test("network — 14 pre-loaded networks ship at workspace open", async () => {
|
|
12
12
|
const root = mkdtempSync(join(tmpdir(), "wative-net-builtin-"));
|
|
13
13
|
const ws = await Workspace.open({ path: root, password: "wsp-pwd" });
|
|
14
14
|
|
|
15
15
|
const slugs = ws.networks.map((n) => n.slug).sort();
|
|
16
16
|
assert.deepStrictEqual(slugs, [
|
|
17
|
-
"arbitrum", "arbitrum-sepolia", "base", "bnbchain", "ethereum",
|
|
17
|
+
"arbitrum", "arbitrum-sepolia", "base", "bnbchain", "ethereum",
|
|
18
|
+
"hyperevm", "hyperevm-testnet", "optimism", "robinhood", "robinhood-testnet",
|
|
18
19
|
"sepolia", "solana", "solana-devnet", "solana-testnet",
|
|
19
20
|
]);
|
|
20
21
|
|
|
@@ -42,7 +43,7 @@ test("network — add a brand-new user network", async () => {
|
|
|
42
43
|
});
|
|
43
44
|
await ws.networks.add(monad);
|
|
44
45
|
|
|
45
|
-
assert.strictEqual(ws.networks.length,
|
|
46
|
+
assert.strictEqual(ws.networks.length, 15);
|
|
46
47
|
assert.strictEqual(ws.networks.bySlug("monad-testnet").chainId, 10143);
|
|
47
48
|
await assert.rejects(ws.networks.add(monad), (err) => err.code === "PARAMETER_ERROR");
|
|
48
49
|
|
|
@@ -64,7 +65,7 @@ test("network — override a pre-loaded RPC URL via update()", async () => {
|
|
|
64
65
|
ws.networks.bySlug("ethereum").rpcUrl,
|
|
65
66
|
"https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY",
|
|
66
67
|
);
|
|
67
|
-
assert.strictEqual(ws.networks.length,
|
|
68
|
+
assert.strictEqual(ws.networks.length, 14);
|
|
68
69
|
|
|
69
70
|
await ws.lock();
|
|
70
71
|
});
|
|
@@ -128,7 +128,7 @@ test("custom provider — full lifecycle against an in-memory backend", async ()
|
|
|
128
128
|
assert.strictEqual(ws.locked, false);
|
|
129
129
|
|
|
130
130
|
const slugs = ws.networks.map((n) => n.slug).sort();
|
|
131
|
-
assert.strictEqual(slugs.length,
|
|
131
|
+
assert.strictEqual(slugs.length, 14);
|
|
132
132
|
assert.ok(slugs.includes("ethereum"));
|
|
133
133
|
assert.ok(slugs.includes("arbitrum-sepolia"));
|
|
134
134
|
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
// 20 — Custom chain: subclass ChainDialect and register a vm to add a blockchain the library does not ship; the "xvm" chain, its toy formats and stand-in signer are illustrative only.
|
|
2
|
+
|
|
3
|
+
const test = require("node:test");
|
|
4
|
+
const assert = require("node:assert");
|
|
5
|
+
const { mkdtempSync } = require("node:fs");
|
|
6
|
+
const { tmpdir } = require("node:os");
|
|
7
|
+
const { join } = require("node:path");
|
|
8
|
+
|
|
9
|
+
const { Workspace, Network, Address, ChainDialect, registerDialect } = require("wative-core");
|
|
10
|
+
|
|
11
|
+
// A super-simple, dependency-free digest standing in for a real hash — illustrative only, not collision-resistant.
|
|
12
|
+
function toyHash(str) {
|
|
13
|
+
let h = 0x811c9dc5;
|
|
14
|
+
for (let i = 0; i < str.length; i++) {
|
|
15
|
+
h ^= str.charCodeAt(i);
|
|
16
|
+
h = Math.imul(h, 0x01000193) >>> 0;
|
|
17
|
+
}
|
|
18
|
+
return h.toString(16).padStart(8, "0");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// The signer subclass a chain author exports, mirroring EvmSigner / SvmSigner.
|
|
22
|
+
class XvmSigner extends Address {}
|
|
23
|
+
|
|
24
|
+
// The dialect a chain author writes: the keyless ChainDialect contract for "xvm".
|
|
25
|
+
class XvmDialect extends ChainDialect {
|
|
26
|
+
get vm() {
|
|
27
|
+
return "xvm";
|
|
28
|
+
}
|
|
29
|
+
get curve() {
|
|
30
|
+
return "ed25519";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
signMessage(ctx, message) {
|
|
34
|
+
const raw = ctx._signBytes(new TextEncoder().encode(`xvm:${message}`));
|
|
35
|
+
return `xvm-sig:${Buffer.from(raw).toString("hex")}`;
|
|
36
|
+
}
|
|
37
|
+
signMessageEncoded(ctx, message, _encoding) {
|
|
38
|
+
return { signature: this.signMessage(ctx, message), messageHash: `0x${toyHash(message)}` };
|
|
39
|
+
}
|
|
40
|
+
signTypedData(ctx, typedData, _chainId) {
|
|
41
|
+
const payload = JSON.stringify(typedData);
|
|
42
|
+
return {
|
|
43
|
+
signature: this.signMessage(ctx, payload),
|
|
44
|
+
domainSeparator: `0x${toyHash(`domain:${payload}`)}`,
|
|
45
|
+
structHash: `0x${toyHash(`struct:${payload}`)}`,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
buildTransaction(ctx, params) {
|
|
49
|
+
return { vm: "xvm", from: String(ctx.publicKey), to: params.to, value: params.value ?? 0n, nonce: params.nonce ?? 0 };
|
|
50
|
+
}
|
|
51
|
+
transfer(ctx, req) {
|
|
52
|
+
const tx = { vm: "xvm", from: String(ctx.publicKey), to: req.to, amount: req.amount };
|
|
53
|
+
if (req.asset && req.asset.address) tx.token = req.asset.address;
|
|
54
|
+
return tx;
|
|
55
|
+
}
|
|
56
|
+
derive(seed, index) {
|
|
57
|
+
const h = toyHash(`${Buffer.from(seed).toString("hex")}:${index}`);
|
|
58
|
+
return { publicKey: `xvm1${h}${index}`, privateKey: `xsk1${h}${index}` };
|
|
59
|
+
}
|
|
60
|
+
addressFromPrivateKey(privateKey) {
|
|
61
|
+
if (typeof privateKey !== "string" || !privateKey.startsWith("xsk1")) throw new Error("malformed xvm private key");
|
|
62
|
+
return `xvm1${privateKey.slice(4)}`;
|
|
63
|
+
}
|
|
64
|
+
privateKeyMatches(privateKey, publicKey) {
|
|
65
|
+
try {
|
|
66
|
+
return this.addressFromPrivateKey(privateKey) === publicKey;
|
|
67
|
+
} catch {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// One call teaches the library about "xvm" and opens the vm whitelist to that token.
|
|
74
|
+
registerDialect("xvm", () => new XvmDialect());
|
|
75
|
+
|
|
76
|
+
test("a registered vm becomes a first-class network; an unknown vm is refused", async () => {
|
|
77
|
+
const root = mkdtempSync(join(tmpdir(), "wative-xvm-net-"));
|
|
78
|
+
const ws = await Workspace.open({ path: root, password: "wsp-pwd" });
|
|
79
|
+
|
|
80
|
+
const xnet = new Network({
|
|
81
|
+
slug: "xvm-testnet",
|
|
82
|
+
name: "XVM Testnet",
|
|
83
|
+
chainId: 909090,
|
|
84
|
+
rpcUrl: "https://rpc.xvm.example",
|
|
85
|
+
nativeCurrency: { name: "XVM", symbol: "XVM", decimals: 9 },
|
|
86
|
+
vm: "xvm",
|
|
87
|
+
});
|
|
88
|
+
await ws.networks.add(xnet);
|
|
89
|
+
assert.strictEqual(ws.networks.bySlug("xvm-testnet").vm, "xvm");
|
|
90
|
+
|
|
91
|
+
assert.throws(
|
|
92
|
+
() =>
|
|
93
|
+
new Network({
|
|
94
|
+
slug: "nope-net",
|
|
95
|
+
name: "Nope",
|
|
96
|
+
chainId: 111111,
|
|
97
|
+
rpcUrl: "https://nope.example",
|
|
98
|
+
nativeCurrency: { name: "N", symbol: "N", decimals: 9 },
|
|
99
|
+
vm: "totally-unregistered",
|
|
100
|
+
}),
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
await ws.lock();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("the custom dialect signs, builds a tx, and transfers via a keyless ctx", () => {
|
|
107
|
+
const dialect = new XvmDialect();
|
|
108
|
+
assert.strictEqual(dialect.vm, "xvm");
|
|
109
|
+
assert.strictEqual(dialect.curve, "ed25519");
|
|
110
|
+
|
|
111
|
+
const ctx = {
|
|
112
|
+
publicKey: "xvm1deadbeef0",
|
|
113
|
+
vm: "xvm",
|
|
114
|
+
network: null,
|
|
115
|
+
assets: [],
|
|
116
|
+
_signBytes: (bytes) => new Uint8Array([bytes.length & 0xff, 0x42, 0x99]),
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const sig = dialect.signMessage(ctx, "hello");
|
|
120
|
+
assert.ok(sig.startsWith("xvm-sig:"));
|
|
121
|
+
|
|
122
|
+
const tx = dialect.buildTransaction(ctx, { to: "xvm1recipient", value: 1000n, nonce: 7 });
|
|
123
|
+
assert.strictEqual(tx.vm, "xvm");
|
|
124
|
+
assert.strictEqual(tx.from, "xvm1deadbeef0");
|
|
125
|
+
assert.strictEqual(tx.to, "xvm1recipient");
|
|
126
|
+
assert.strictEqual(tx.nonce, 7);
|
|
127
|
+
|
|
128
|
+
const native = dialect.transfer(ctx, { to: "xvm1bob", amount: 500n });
|
|
129
|
+
assert.strictEqual(native.to, "xvm1bob");
|
|
130
|
+
assert.strictEqual(native.amount, 500n);
|
|
131
|
+
assert.strictEqual(native.token, undefined);
|
|
132
|
+
|
|
133
|
+
const token = dialect.transfer(ctx, { to: "xvm1bob", asset: { address: "xtoken1" }, amount: 5n });
|
|
134
|
+
assert.strictEqual(token.token, "xtoken1");
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("the custom dialect derives addresses and round-trips private keys", () => {
|
|
138
|
+
const dialect = new XvmDialect();
|
|
139
|
+
const seed = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]);
|
|
140
|
+
|
|
141
|
+
const { publicKey, privateKey } = dialect.derive(seed, 0);
|
|
142
|
+
assert.ok(publicKey.startsWith("xvm1"));
|
|
143
|
+
assert.ok(privateKey.startsWith("xsk1"));
|
|
144
|
+
|
|
145
|
+
assert.strictEqual(dialect.addressFromPrivateKey(privateKey), publicKey);
|
|
146
|
+
assert.ok(dialect.privateKeyMatches(privateKey, publicKey));
|
|
147
|
+
assert.ok(!dialect.privateKeyMatches("xsk1wrongkey", publicKey));
|
|
148
|
+
assert.ok(!dialect.privateKeyMatches("not-even-an-xvm-key", publicKey));
|
|
149
|
+
|
|
150
|
+
assert.notStrictEqual(dialect.derive(seed, 1).publicKey, publicKey);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("XvmSigner extends Address — the per-chain signer pattern", () => {
|
|
154
|
+
assert.ok(XvmSigner.prototype instanceof Address);
|
|
155
|
+
});
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// 21 — Token-aware transfer(): one intent lowered per chain — native, ERC-20 by address, SPL by mint, and by symbol. Built offline; amounts are raw base units.
|
|
2
|
+
|
|
3
|
+
const test = require("node:test");
|
|
4
|
+
const assert = require("node:assert");
|
|
5
|
+
const { mkdtempSync } = require("node:fs");
|
|
6
|
+
const { tmpdir } = require("node:os");
|
|
7
|
+
const { join } = require("node:path");
|
|
8
|
+
|
|
9
|
+
const { Workspace } = require("wative-core");
|
|
10
|
+
|
|
11
|
+
const MNEMONIC =
|
|
12
|
+
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
|
|
13
|
+
|
|
14
|
+
// Real mainnet token identifiers, used only for their format; the EVM address is lowercase and gets canonicalized.
|
|
15
|
+
const USDC_EVM = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48";
|
|
16
|
+
const USDC_SVM_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
|
|
17
|
+
|
|
18
|
+
// A workspace with two wallets: wallet 0 sends, wallet 1's public keys are valid recipients on each chain.
|
|
19
|
+
async function twoWallets() {
|
|
20
|
+
const root = mkdtempSync(join(tmpdir(), "wative-transfer-"));
|
|
21
|
+
const ws = await Workspace.open({ path: root, password: "wsp-pwd" });
|
|
22
|
+
const acc = await ws.accounts.create("Desk", "wsp-pwd", MNEMONIC);
|
|
23
|
+
await acc.deriveWallets(1);
|
|
24
|
+
return { ws, w0: acc.wallets[0], w1: acc.wallets[1] };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
test("transfer() — native value on EVM and SVM (asset omitted)", async () => {
|
|
28
|
+
const { ws, w0, w1 } = await twoWallets();
|
|
29
|
+
|
|
30
|
+
const evmTx = w0.evm.transfer({ to: String(w1.evm.publicKey), amount: 1_000_000_000_000_000n });
|
|
31
|
+
assert.strictEqual(String(evmTx.to).toLowerCase(), String(w1.evm.publicKey).toLowerCase());
|
|
32
|
+
assert.strictEqual(evmTx.value, 1_000_000_000_000_000n);
|
|
33
|
+
|
|
34
|
+
const svmTx = w0.svm.transfer({ to: String(w1.svm.publicKey), amount: 5000n });
|
|
35
|
+
assert.ok(svmTx);
|
|
36
|
+
|
|
37
|
+
await ws.lock();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("transfer() — EVM ERC-20 by contract address", async () => {
|
|
41
|
+
const { ws, w0, w1 } = await twoWallets();
|
|
42
|
+
const tx = w0.evm.transfer({ to: String(w1.evm.publicKey), asset: { address: USDC_EVM }, amount: 1_000_000n });
|
|
43
|
+
|
|
44
|
+
assert.strictEqual(String(tx.to).toLowerCase(), USDC_EVM.toLowerCase());
|
|
45
|
+
assert.strictEqual(tx.value, 0n);
|
|
46
|
+
assert.strictEqual(String(tx.data).slice(0, 10), "0xa9059cbb");
|
|
47
|
+
assert.strictEqual((String(tx.data).length - 2) / 2, 68);
|
|
48
|
+
|
|
49
|
+
await ws.lock();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("transfer() — SVM SPL by mint prepends an idempotent recipient-ATA create", async () => {
|
|
53
|
+
const { ws, w0, w1 } = await twoWallets();
|
|
54
|
+
const tx = w0.svm.transfer({ to: String(w1.svm.publicKey), asset: { address: USDC_SVM_MINT }, amount: 1_000_000n });
|
|
55
|
+
|
|
56
|
+
assert.ok(Array.isArray(tx.instructions));
|
|
57
|
+
assert.strictEqual(tx.instructions.length, 2);
|
|
58
|
+
|
|
59
|
+
await ws.lock();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("transfer() — by token symbol resolves to native vs token", async () => {
|
|
63
|
+
const { ws, w0, w1 } = await twoWallets();
|
|
64
|
+
|
|
65
|
+
const usdc = w0.evm.transfer({ to: String(w1.evm.publicKey), asset: { symbol: "USDC" }, amount: 1_000_000n });
|
|
66
|
+
assert.strictEqual(String(usdc.data).slice(0, 10), "0xa9059cbb");
|
|
67
|
+
|
|
68
|
+
const eth = w0.evm.transfer({ to: String(w1.evm.publicKey), asset: { symbol: "ETH" }, amount: 777n });
|
|
69
|
+
assert.strictEqual(eth.value, 777n);
|
|
70
|
+
|
|
71
|
+
await ws.lock();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("transfer() — an out-of-range amount is a PARAMETER_ERROR", async () => {
|
|
75
|
+
const { ws, w0, w1 } = await twoWallets();
|
|
76
|
+
let code;
|
|
77
|
+
try {
|
|
78
|
+
w0.evm.transfer({ to: String(w1.evm.publicKey), asset: { address: USDC_EVM }, amount: -1n });
|
|
79
|
+
} catch (e) {
|
|
80
|
+
code = e.code;
|
|
81
|
+
}
|
|
82
|
+
assert.strictEqual(code, "PARAMETER_ERROR");
|
|
83
|
+
|
|
84
|
+
await ws.lock();
|
|
85
|
+
});
|
package/examples/README.md
CHANGED
|
@@ -34,8 +34,8 @@ All tests work fully offline — no RPC calls, no real networks. Each test creat
|
|
|
34
34
|
| [01-quick-start.test.cjs](01-quick-start.test.cjs) | Open a workspace, create an HD account, derive 5 wallets, inspect EVM + Solana addresses, lock. |
|
|
35
35
|
| [02-hd-account.test.cjs](02-hd-account.test.cjs) | HD-specific flows — deriving / slicing wallets, dumping the mnemonic, account-level passwords with `resetPassword`, round-trip across lock + reopen. |
|
|
36
36
|
| [03-pk-account.test.cjs](03-pk-account.test.cjs) | PK accounts — creating from a private key, importing more keys, and the three key formats accepted (EVM hex, Solana base58, `solana-keygen` JSON) with the chain inferred from the key. Also shows each import producing an address on both chains, duplicate-import rejection, and dropping wallets. |
|
|
37
|
-
| [04-network-management.test.cjs](04-network-management.test.cjs) | The
|
|
38
|
-
| [05-asset-management.test.cjs](05-asset-management.test.cjs) | The
|
|
37
|
+
| [04-network-management.test.cjs](04-network-management.test.cjs) | The 14 pre-loaded networks, adding a brand-new network, overriding a pre-loaded RPC URL via `update()`, dropping user networks, the built-in protection guard. |
|
|
38
|
+
| [05-asset-management.test.cjs](05-asset-management.test.cjs) | The 29 pre-loaded tokens, adding a custom user token, id collision rejection, contract-address collision rejection, dropping user tokens, `workspace.filter(q, "Asset")` search. |
|
|
39
39
|
| [06-address-signing.test.cjs](06-address-signing.test.cjs) | Personal-message signing on EVM and Solana, building a transaction fully offline (no RPC). |
|
|
40
40
|
| [07-custom-provider.test.cjs](07-custom-provider.test.cjs) | Taking over the record layer as well, by extending `Provider` rather than `ContainerProvider` — for storage that has its own encryption or its own idea of a record. Longer than [19](19-simple-provider.test.cjs); start there unless you need this. |
|
|
41
41
|
| [08-persistence.test.cjs](08-persistence.test.cjs) | Round-trip across `lock()` + reopen — multiple accounts, user networks, custom assets, and logger configuration. |
|
|
@@ -50,6 +50,8 @@ All tests work fully offline — no RPC calls, no real networks. Each test creat
|
|
|
50
50
|
| [17-locked-state.test.cjs](17-locked-state.test.cjs) | What locking enforces — which operations refuse and with which error code, that a locked account holds no mnemonic in the clear, and that unlocking restores identical behaviour. |
|
|
51
51
|
| [18-envelope-v3.test.cjs](18-envelope-v3.test.cjs) | `HybridProviderV3` — writing a workspace that derives one key for the whole workspace instead of one per stored secret, reopening it, and confirming a wrong password is still refused. |
|
|
52
52
|
| [19-simple-provider.test.cjs](19-simple-provider.test.cjs) | The short way to write a storage backend — extend `ContainerProvider` and implement six methods that move bytes. Runs a whole workspace on a `Map`, signs from a derived address, reopens it, and checks that nothing readable reaches the store. |
|
|
53
|
+
| [20-custom-chain-dialect.test.cjs](20-custom-chain-dialect.test.cjs) | Adding a whole new blockchain — subclass `ChainDialect` and `registerDialect` its vm, the same shape as extending `Provider`. A fictional `xvm` chain becomes a first-class network (and an unregistered vm is still refused), its dialect signs / builds a transaction / transfers / derives through the keyless `ChainCtx`, and `XvmSigner` names the per-chain signer just as `EvmSigner` / `SvmSigner` do. |
|
|
54
|
+
| [21-token-transfer.test.cjs](21-token-transfer.test.cjs) | `address.transfer({ to, asset, amount })` — one intent lowered per chain: native value, EVM ERC-20 by contract address, SVM SPL by mint (with an idempotent recipient-ATA create), and by token `symbol`. All built offline; `amount` is raw base units. |
|
|
53
55
|
|
|
54
56
|
## Adapting for your project
|
|
55
57
|
|
|
@@ -69,3 +71,4 @@ map.
|
|
|
69
71
|
- **No real money.** The mnemonics and private keys used in these tests are well-known testing values. Never fund them.
|
|
70
72
|
- **No network calls.** Tests sign offline only. To actually broadcast a transaction, configure a real RPC URL on the network and call `address.sendTransaction(tx)` — see the README for the full lifecycle.
|
|
71
73
|
- **Custom Provider example** uses a deliberately simple cipher (XOR). Real Providers should seal data with an authenticated cipher — see the bundled `HybridProvider` for a reference implementation.
|
|
74
|
+
- **Custom chain example** uses a fictional chain with a toy hash, toy address/transaction formats, and a stand-in signer — illustrative only. A real chain declares a real curve and real formats; the example shows the extension *shape*, not production cryptography.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wative-core",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.0",
|
|
4
4
|
"description": "Hyperscale crypto wallet management library — multi-workspace, multi-chain, originally designed for on-chain market makers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.node.cjs",
|