@anywalletkit/wallet-near 0.3.0 → 0.4.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/README.md +5 -0
- package/dist/{chunk-ZWMEZA5A.js → chunk-5UHDSFWA.js} +10 -3
- package/dist/chunk-5UHDSFWA.js.map +1 -0
- package/dist/history.cjs.map +1 -1
- package/dist/history.js +1 -1
- package/dist/index.cjs +120 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +31 -4
- package/dist/index.d.ts +31 -4
- package/dist/index.js +114 -20
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/dist/chunk-ZWMEZA5A.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -45,6 +45,7 @@ __export(src_exports, {
|
|
|
45
45
|
WalletAccountReadOnlyNear: () => WalletAccountReadOnlyNear,
|
|
46
46
|
WalletManagerNear: () => WalletManagerNear,
|
|
47
47
|
YOCTO: () => YOCTO,
|
|
48
|
+
accessKeyPermissionTag: () => accessKeyPermissionTag,
|
|
48
49
|
actionKey: () => actionKey,
|
|
49
50
|
argsBytes: () => argsBytes,
|
|
50
51
|
assertWellFormed: () => assertWellFormed,
|
|
@@ -361,17 +362,34 @@ function argsBytes(args) {
|
|
|
361
362
|
if (!isPlainObject(args)) throw new TypeError("function-call args must be bytes, a string or a plain object");
|
|
362
363
|
return utf8.encode(JSON.stringify(args));
|
|
363
364
|
}
|
|
365
|
+
var FUNCTION_CALL_PERMISSION_KEYS = /* @__PURE__ */ new Set(["allowance", "receiverId", "methodNames"]);
|
|
366
|
+
function accessKeyPermissionTag(permission) {
|
|
367
|
+
const keys = isPlainObject(permission) ? Object.keys(permission) : [];
|
|
368
|
+
if (keys.length !== 1) throw new TypeError(`an access-key permission must have exactly one variant, fullAccess or functionCall (has: ${keys.join(", ") || "none"})`);
|
|
369
|
+
const variant = permission[keys[0]];
|
|
370
|
+
if (keys[0] === "fullAccess") {
|
|
371
|
+
if (!isPlainObject(variant)) throw new TypeError("fullAccess must be a plain object");
|
|
372
|
+
return 1;
|
|
373
|
+
}
|
|
374
|
+
if (keys[0] === "functionCall") {
|
|
375
|
+
if (!isPlainObject(variant) || Object.keys(variant).some((k) => !FUNCTION_CALL_PERMISSION_KEYS.has(k))) throw new TypeError("functionCall must be a plain { receiverId, methodNames, allowance? } object");
|
|
376
|
+
if (typeof variant.receiverId !== "string") throw new TypeError("functionCall.receiverId must be a string");
|
|
377
|
+
if (!Array.isArray(variant.methodNames) || !variant.methodNames.every((m) => typeof m === "string")) throw new TypeError("functionCall.methodNames must be an array of strings");
|
|
378
|
+
if (variant.allowance !== void 0 && typeof variant.allowance !== "bigint") throw new TypeError("functionCall.allowance must be a bigint or absent");
|
|
379
|
+
return 0;
|
|
380
|
+
}
|
|
381
|
+
throw new TypeError(`unknown access-key permission ${JSON.stringify(keys[0])}`);
|
|
382
|
+
}
|
|
364
383
|
function writeAccessKey(w, k) {
|
|
365
384
|
w.u64(k.nonce);
|
|
366
|
-
|
|
385
|
+
const tag = accessKeyPermissionTag(k.permission);
|
|
386
|
+
w.u8(tag);
|
|
387
|
+
if (tag === 0) {
|
|
367
388
|
const p = k.permission.functionCall;
|
|
368
|
-
w.u8(0);
|
|
369
389
|
w.option(p.allowance, (a) => w.u128(a));
|
|
370
390
|
w.string(p.receiverId);
|
|
371
391
|
w.u32(p.methodNames.length);
|
|
372
392
|
for (const m of p.methodNames) w.string(m);
|
|
373
|
-
} else {
|
|
374
|
-
w.u8(1);
|
|
375
393
|
}
|
|
376
394
|
}
|
|
377
395
|
function readAccessKey(r) {
|
|
@@ -613,6 +631,13 @@ function httpTransport(url, headers, timeoutMs) {
|
|
|
613
631
|
};
|
|
614
632
|
}
|
|
615
633
|
var isDefinitive = (e) => e instanceof NearRpcError && !/TIMEOUT|INTERNAL|UNAVAILABLE|NO_SYNCED/i.test(e.errorName ?? "");
|
|
634
|
+
var isDefinitiveBroadcast = (e) => {
|
|
635
|
+
if (!(e instanceof NearRpcError)) return false;
|
|
636
|
+
const err = e.cause;
|
|
637
|
+
const name = typeof err?.name === "string" ? err.name : void 0;
|
|
638
|
+
const causeName = typeof err?.cause?.name === "string" ? err.cause.name : void 0;
|
|
639
|
+
return causeName === "INVALID_TRANSACTION" || causeName === "PARSE_ERROR" || name === "REQUEST_VALIDATION_ERROR";
|
|
640
|
+
};
|
|
616
641
|
var BROADCAST_METHODS = /* @__PURE__ */ new Set(["send_tx", "broadcast_tx_commit", "broadcast_tx_async"]);
|
|
617
642
|
function createClient(config) {
|
|
618
643
|
if (config.provider === void 0) return void 0;
|
|
@@ -699,7 +724,7 @@ var NearRpc = class {
|
|
|
699
724
|
}
|
|
700
725
|
};
|
|
701
726
|
function failureOf(status) {
|
|
702
|
-
if (!status || !("Failure" in status)) return void 0;
|
|
727
|
+
if (!status || typeof status !== "object" || !("Failure" in status)) return void 0;
|
|
703
728
|
const f = status.Failure;
|
|
704
729
|
try {
|
|
705
730
|
const json = JSON.stringify(f);
|
|
@@ -767,8 +792,15 @@ function describeTransaction(tx, ctx = {}) {
|
|
|
767
792
|
if (DANGEROUS_ACTIONS.has(key)) {
|
|
768
793
|
dangerous ??= KIND_OF_DANGEROUS[key];
|
|
769
794
|
warnings.push({ code: "dangerous-action", message: `${key} changes the account, its keys or its code.` });
|
|
770
|
-
if (key === "addKey"
|
|
771
|
-
|
|
795
|
+
if (key === "addKey") {
|
|
796
|
+
let tag;
|
|
797
|
+
try {
|
|
798
|
+
tag = accessKeyPermissionTag(a.addKey?.accessKey?.permission);
|
|
799
|
+
} catch {
|
|
800
|
+
tag = void 0;
|
|
801
|
+
}
|
|
802
|
+
if (tag === 1) warnings.push({ code: "full-access-key", message: "Adds a FULL ACCESS key: whoever holds it owns the account." });
|
|
803
|
+
else if (tag === void 0) warnings.push({ code: "dangerous-action", message: "Malformed access-key permission: neither fullAccess nor functionCall. The wallet refuses to sign it." });
|
|
772
804
|
}
|
|
773
805
|
continue;
|
|
774
806
|
}
|
|
@@ -1116,8 +1148,7 @@ var WalletAccountReadOnlyNear = class _WalletAccountReadOnlyNear {
|
|
|
1116
1148
|
/** Status of one of this account's transactions (the `tx` RPC needs the sender). */
|
|
1117
1149
|
async getTransactionReceipt(hash) {
|
|
1118
1150
|
const o = await this.requireRpc("fetch transaction receipts").txStatus(hash, this.address);
|
|
1119
|
-
|
|
1120
|
-
return receiptOf(hash, o);
|
|
1151
|
+
return o ? receiptOf(hash, o) : void 0;
|
|
1121
1152
|
}
|
|
1122
1153
|
/** Verify an `ed25519:<base58>` (or bare base58 / hex) signature produced by `sign`. */
|
|
1123
1154
|
async verify(message, signature) {
|
|
@@ -1138,8 +1169,10 @@ var WalletAccountReadOnlyNear = class _WalletAccountReadOnlyNear {
|
|
|
1138
1169
|
};
|
|
1139
1170
|
function receiptOf(hash, o) {
|
|
1140
1171
|
const failure = outcomeFailed(o);
|
|
1141
|
-
|
|
1142
|
-
|
|
1172
|
+
if (failure) return { hash, status: 0, failure, raw: { ...o } };
|
|
1173
|
+
if (o.final_execution_status !== void 0 && /^(?:NONE|INCLUDED|INCLUDED_FINAL)$/.test(o.final_execution_status)) return void 0;
|
|
1174
|
+
const executed = typeof o.status === "object" && o.status !== null && ("SuccessValue" in o.status || "SuccessReceiptId" in o.status);
|
|
1175
|
+
return executed ? { hash, status: 1, raw: { ...o } } : void 0;
|
|
1143
1176
|
}
|
|
1144
1177
|
function parseSignature(signature) {
|
|
1145
1178
|
const s = signature.startsWith("ed25519:") ? signature.slice("ed25519:".length) : signature;
|
|
@@ -1166,6 +1199,15 @@ function assertShape(actions, depth = 0) {
|
|
|
1166
1199
|
if (!(f.args instanceof Uint8Array) && typeof f.args !== "string" && !isPlainObject(f.args)) throw new UnsafeTransactionError("malformed-action", "Function-call args must be bytes, a string or a plain object.");
|
|
1167
1200
|
if (f.gas !== void 0 && typeof f.gas !== "bigint" || f.deposit !== void 0 && typeof f.deposit !== "bigint") throw new UnsafeTransactionError("malformed-action", "Function-call gas and deposit must be bigints.");
|
|
1168
1201
|
}
|
|
1202
|
+
if ("addKey" in a) {
|
|
1203
|
+
const k = a.addKey;
|
|
1204
|
+
if (!isPlainObject(k) || !isPlainObject(k.accessKey)) throw new UnsafeTransactionError("malformed-action", "addKey must carry an accessKey object.");
|
|
1205
|
+
try {
|
|
1206
|
+
accessKeyPermissionTag(k.accessKey.permission);
|
|
1207
|
+
} catch (e) {
|
|
1208
|
+
throw new UnsafeTransactionError("malformed-action", `AddKey: ${e.message}.`);
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1169
1211
|
if ("signedDelegate" in a && depth === 0) assertShape(a.signedDelegate.delegateAction.actions, depth + 1);
|
|
1170
1212
|
}
|
|
1171
1213
|
}
|
|
@@ -1238,6 +1280,15 @@ var WalletAccountNear = class _WalletAccountNear extends WalletAccountReadOnlyNe
|
|
|
1238
1280
|
signerKind;
|
|
1239
1281
|
_disposed = false;
|
|
1240
1282
|
_readOnly;
|
|
1283
|
+
/**
|
|
1284
|
+
* Highest nonce this account has broadcast (accepted, or maybe sent). The access key is read at
|
|
1285
|
+
* `final`, which lags the block that holds our last transaction, so a second send right after
|
|
1286
|
+
* the first would sign the same nonce and be refused. A filled-in nonce is never below it + 1;
|
|
1287
|
+
* NEAR only needs nonces to increase, so a gap left by a transaction that never landed is harmless.
|
|
1288
|
+
*/
|
|
1289
|
+
_nonceFloor;
|
|
1290
|
+
/** Tail of this account's send queue: prepare → sign → broadcast runs one send at a time. */
|
|
1291
|
+
_sendTail = Promise.resolve();
|
|
1241
1292
|
constructor(signer, publicKey, path, config = {}, accountId) {
|
|
1242
1293
|
super(accountId ?? implicitAccountId(publicKey), config, ed25519Key(publicKey));
|
|
1243
1294
|
this.#signer = (0, import_core4.requireCurve)(signer, "ed25519");
|
|
@@ -1282,15 +1333,44 @@ var WalletAccountNear = class _WalletAccountNear extends WalletAccountReadOnlyNe
|
|
|
1282
1333
|
const sig = await this.#signBytes(nep413Hash(payload), "near-nep413");
|
|
1283
1334
|
return { signature: toBase64(sig), publicKey: publicKeyToString(this.publicKey), accountId: this.address };
|
|
1284
1335
|
}
|
|
1285
|
-
/**
|
|
1336
|
+
/**
|
|
1337
|
+
* Sign a fully built transaction. It gets the same hard checks as `signTransaction` (this
|
|
1338
|
+
* account's signer and key, no dangerous action unless `allowDangerousActions`, gas limit) but
|
|
1339
|
+
* no node round-trip: no preflight and no fee check.
|
|
1340
|
+
*/
|
|
1286
1341
|
async signBuilt(tx) {
|
|
1342
|
+
const own = this.#ownCopy(tx);
|
|
1343
|
+
this.assertSafe(own);
|
|
1344
|
+
return this.#signChecked(own);
|
|
1345
|
+
}
|
|
1346
|
+
/**
|
|
1347
|
+
* A plain copy of `tx`, decoded from its borsh bytes: the object the checks read is the one that
|
|
1348
|
+
* gets hashed and signed, whatever the caller does with its own object (a getter, or an array
|
|
1349
|
+
* changed while a node call is awaited).
|
|
1350
|
+
*/
|
|
1351
|
+
#ownCopy(tx) {
|
|
1352
|
+
let copy;
|
|
1353
|
+
try {
|
|
1354
|
+
copy = decodeTransaction(encodeTransaction(tx));
|
|
1355
|
+
} catch (e) {
|
|
1356
|
+
this.assertSafe(tx);
|
|
1357
|
+
throw new UnsafeTransactionError("non-canonical", `Not an encodable NEAR transaction: ${e.message}`);
|
|
1358
|
+
}
|
|
1359
|
+
return copy;
|
|
1360
|
+
}
|
|
1361
|
+
/** Sign a transaction that has already been through `assertSafe`. */
|
|
1362
|
+
async #signChecked(tx) {
|
|
1287
1363
|
assertWellFormed(tx);
|
|
1288
1364
|
const sig = await this.#signBytes(WalletAccountReadOnlyNear.hashOf(tx), "near-tx");
|
|
1289
1365
|
return { transaction: tx, signature: { keyType: "ed25519", data: sig } };
|
|
1290
1366
|
}
|
|
1291
1367
|
/** Build → hard checks → preflight → fee cap. Shared by sign / send / transfer. */
|
|
1292
1368
|
async prepare(tx, maxFee, op) {
|
|
1293
|
-
|
|
1369
|
+
let built = await this.buildTransaction(tx);
|
|
1370
|
+
if (typeof tx !== "string" && tx.nonce === void 0 && this._nonceFloor !== void 0 && built.nonce <= this._nonceFloor) {
|
|
1371
|
+
built = { ...built, nonce: this._nonceFloor + 1n };
|
|
1372
|
+
}
|
|
1373
|
+
built = this.#ownCopy(built);
|
|
1294
1374
|
this.assertSafe(built);
|
|
1295
1375
|
const estimate = await this.estimate(built);
|
|
1296
1376
|
this.assertSafe(built, estimate.fee);
|
|
@@ -1306,18 +1386,24 @@ var WalletAccountNear = class _WalletAccountNear extends WalletAccountReadOnlyNe
|
|
|
1306
1386
|
this.key();
|
|
1307
1387
|
this.requireRpc("sign transactions");
|
|
1308
1388
|
const { built } = await this.prepare(tx, (0, import_core4.feeLimit)(this._config.transactionMaxFee, options), "transaction");
|
|
1309
|
-
return toBase64(encodeSignedTransaction(await this
|
|
1389
|
+
return toBase64(encodeSignedTransaction(await this.#signChecked(built)));
|
|
1310
1390
|
}
|
|
1311
1391
|
async broadcast(signed) {
|
|
1312
1392
|
const hash = WalletAccountReadOnlyNear.txHash(signed.transaction);
|
|
1313
1393
|
const rpc = this.requireRpc("broadcast");
|
|
1394
|
+
const used = () => {
|
|
1395
|
+
const n = signed.transaction.nonce;
|
|
1396
|
+
if (this._nonceFloor === void 0 || n > this._nonceFloor) this._nonceFloor = n;
|
|
1397
|
+
};
|
|
1314
1398
|
let outcome;
|
|
1315
1399
|
try {
|
|
1316
1400
|
outcome = await rpc.sendTx(toBase64(encodeSignedTransaction(signed)));
|
|
1317
1401
|
} catch (e) {
|
|
1318
|
-
if (
|
|
1402
|
+
if (isDefinitiveBroadcast(e)) throw e;
|
|
1403
|
+
used();
|
|
1319
1404
|
throw new BroadcastUnknownError(hash, e);
|
|
1320
1405
|
}
|
|
1406
|
+
used();
|
|
1321
1407
|
const failure = outcomeFailed(outcome);
|
|
1322
1408
|
if (failure) throw new Error(`Transaction ${hash} failed on chain: ${failure}`);
|
|
1323
1409
|
return outcome.transaction?.hash ?? hash;
|
|
@@ -1326,20 +1412,35 @@ var WalletAccountNear = class _WalletAccountNear extends WalletAccountReadOnlyNe
|
|
|
1326
1412
|
* Sign and broadcast. Accepts a transaction, or base64 borsh of an unsigned /
|
|
1327
1413
|
* already-signed transaction (a signed one is checked, not re-signed).
|
|
1328
1414
|
*/
|
|
1329
|
-
|
|
1415
|
+
sendTransaction(tx, options = {}) {
|
|
1416
|
+
return this.#serial(() => this.#sendTransactionNow(tx, options));
|
|
1417
|
+
}
|
|
1418
|
+
async #sendTransactionNow(tx, options) {
|
|
1330
1419
|
this.key();
|
|
1331
1420
|
this.requireRpc("send transactions");
|
|
1332
1421
|
const presigned = typeof tx === "string" ? decodeSigned(tx) : void 0;
|
|
1333
1422
|
const { built, estimate } = await this.prepare(tx, (0, import_core4.feeLimit)(this._config.transactionMaxFee, options), "transaction");
|
|
1334
|
-
const signed = presigned ?? await this
|
|
1423
|
+
const signed = presigned ?? await this.#signChecked(built);
|
|
1335
1424
|
return { hash: await this.broadcast(signed), fee: estimate.fee, estimate };
|
|
1336
1425
|
}
|
|
1337
1426
|
/** Native NEAR (no `token`) or NEP-141 `ft_transfer` (with `storage_deposit` when the recipient needs it). */
|
|
1338
|
-
|
|
1427
|
+
transfer(o, writeOptions = {}) {
|
|
1428
|
+
return this.#serial(() => this.#transferNow(o, writeOptions));
|
|
1429
|
+
}
|
|
1430
|
+
async #transferNow(o, writeOptions) {
|
|
1339
1431
|
this.key();
|
|
1340
1432
|
this.requireRpc("transfer tokens");
|
|
1341
1433
|
const { built, estimate } = await this.prepare(await this.transferActions(o), (0, import_core4.feeLimit)(this._config.transferMaxFee, writeOptions), "transfer");
|
|
1342
|
-
return { hash: await this.broadcast(await this
|
|
1434
|
+
return { hash: await this.broadcast(await this.#signChecked(built)), fee: estimate.fee, estimate };
|
|
1435
|
+
}
|
|
1436
|
+
/**
|
|
1437
|
+
* One send at a time per account: two sends started together would read the same access-key
|
|
1438
|
+
* nonce before either is broadcast. A failed send does not block the next.
|
|
1439
|
+
*/
|
|
1440
|
+
#serial(run) {
|
|
1441
|
+
const next = this._sendTail.then(run);
|
|
1442
|
+
this._sendTail = next.then(() => void 0, () => void 0);
|
|
1443
|
+
return next;
|
|
1343
1444
|
}
|
|
1344
1445
|
/** The same account as a {@link WalletAccountReadOnlyNear}. */
|
|
1345
1446
|
toReadOnlyAccount() {
|