@mysten/sui 2.19.0 → 2.20.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.
- package/CHANGELOG.md +25 -0
- package/dist/bcs/bcs.d.mts +6 -6
- package/dist/grpc/proto/sui/forking/v1alpha/forking_service.client.d.mts +4 -4
- package/dist/grpc/proto/sui/rpc/v2/move_package_service.client.d.mts +4 -4
- package/dist/grpc/proto/sui/rpc/v2/signature_verification_service.client.d.mts +4 -4
- package/dist/transactions/intents/CoinWithBalance.d.mts.map +1 -1
- package/dist/transactions/intents/CoinWithBalance.mjs +7 -3
- package/dist/transactions/intents/CoinWithBalance.mjs.map +1 -1
- package/dist/version.mjs +1 -1
- package/dist/version.mjs.map +1 -1
- package/dist/zklogin/bcs.d.mts +14 -14
- package/dist/zklogin/bcs.d.mts.map +1 -1
- package/dist/zklogin/index.d.mts +2 -1
- package/dist/zklogin/index.mjs +2 -1
- package/dist/zklogin/publickey.d.mts +4 -0
- package/dist/zklogin/publickey.d.mts.map +1 -1
- package/dist/zklogin/publickey.mjs +6 -0
- package/dist/zklogin/publickey.mjs.map +1 -1
- package/dist/zklogin/signer.d.mts +55 -0
- package/dist/zklogin/signer.d.mts.map +1 -0
- package/dist/zklogin/signer.mjs +68 -0
- package/dist/zklogin/signer.mjs.map +1 -0
- package/docs/cryptography/index.md +225 -0
- package/docs/cryptography/keypairs.md +36 -143
- package/docs/cryptography/signers/aws-kms.md +110 -0
- package/docs/cryptography/signers/gcp-kms.md +91 -0
- package/docs/cryptography/signers/index.md +78 -0
- package/docs/cryptography/signers/ledger.md +76 -0
- package/docs/cryptography/{multisig.md → signers/multisig.md} +2 -2
- package/docs/cryptography/{passkey.md → signers/passkey.md} +1 -1
- package/docs/cryptography/{webcrypto-signer.md → signers/webcrypto.md} +6 -6
- package/docs/index.md +4 -5
- package/docs/llms-index.md +9 -4
- package/docs/migrations/sui-2.0/index.md +14 -1
- package/docs/zklogin.md +2 -2
- package/package.json +1 -1
- package/src/transactions/intents/CoinWithBalance.ts +21 -6
- package/src/version.ts +1 -1
- package/src/zklogin/index.ts +2 -0
- package/src/zklogin/publickey.ts +7 -0
- package/src/zklogin/signer.ts +121 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# @mysten/sui.js
|
|
2
2
|
|
|
3
|
+
## 2.20.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 91c4ef5: Fix `CoinWithBalance` intent failing to build after a `toJSON`/`Transaction.from`
|
|
8
|
+
round-trip. Serializing a transaction with `CoinWithBalance` as a supported intent turns the
|
|
9
|
+
intent's `balance` into a string, which was not coerced back to a `bigint` on deserialization,
|
|
10
|
+
causing `ValiError: Invalid type: Expected bigint` when building the restored transaction.
|
|
11
|
+
|
|
12
|
+
## 2.20.0
|
|
13
|
+
|
|
14
|
+
### Minor Changes
|
|
15
|
+
|
|
16
|
+
- 7452835: Add `ZkLoginSigner`, a transport- and provider-agnostic zkLogin signer. It wraps any
|
|
17
|
+
ephemeral `Signer` and transforms its signatures into zkLogin signatures using the supplied proof
|
|
18
|
+
`inputs` and `maxEpoch`:
|
|
19
|
+
`new ZkLoginSigner({ ephemeralSigner, maxEpoch, inputs, legacyAddress })`. The address is derived
|
|
20
|
+
from the proof; `legacyAddress` is a required boolean (consistent with `jwtToAddress`,
|
|
21
|
+
`toZkLoginPublicIdentifier`, and the other zkLogin address APIs). Optionally pass `address` to
|
|
22
|
+
validate the derived address (throws on mismatch) and `client` to make the derived public key able
|
|
23
|
+
to verify signatures. Like other composite signers (e.g. `MultiSigSigner`), calling `sign()`
|
|
24
|
+
directly throws — use `signTransaction` / `signPersonalMessage`.
|
|
25
|
+
|
|
26
|
+
Also adds a read-only `legacyAddress` getter to `ZkLoginPublicIdentifier`.
|
|
27
|
+
|
|
3
28
|
## 2.19.0
|
|
4
29
|
|
|
5
30
|
### Minor Changes
|
package/dist/bcs/bcs.d.mts
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
import { TypeTag as TypeTag$1 } from "./types.mjs";
|
|
2
|
-
import * as
|
|
2
|
+
import * as _mysten_bcs810 from "@mysten/bcs";
|
|
3
3
|
import { BcsType } from "@mysten/bcs";
|
|
4
4
|
|
|
5
5
|
//#region src/bcs/bcs.d.ts
|
|
6
6
|
declare const TypeTag: BcsType<string, string | TypeTag$1, string>;
|
|
7
|
-
declare function IntentMessage<T extends BcsType<any>>(T: T):
|
|
8
|
-
intent:
|
|
9
|
-
scope:
|
|
7
|
+
declare function IntentMessage<T extends BcsType<any>>(T: T): _mysten_bcs810.BcsStruct<{
|
|
8
|
+
intent: _mysten_bcs810.BcsStruct<{
|
|
9
|
+
scope: _mysten_bcs810.BcsEnum<{
|
|
10
10
|
TransactionData: null;
|
|
11
11
|
TransactionEffects: null;
|
|
12
12
|
CheckpointSummary: null;
|
|
13
13
|
PersonalMessage: null;
|
|
14
14
|
}, "IntentScope">;
|
|
15
|
-
version:
|
|
15
|
+
version: _mysten_bcs810.BcsEnum<{
|
|
16
16
|
V0: null;
|
|
17
17
|
}, "IntentVersion">;
|
|
18
|
-
appId:
|
|
18
|
+
appId: _mysten_bcs810.BcsEnum<{
|
|
19
19
|
Sui: null;
|
|
20
20
|
}, "AppId">;
|
|
21
21
|
}, string>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AdvanceCheckpointRequest, AdvanceCheckpointResponse, AdvanceClockRequest, AdvanceClockResponse, GetStatusRequest, GetStatusResponse } from "./forking_service.mjs";
|
|
2
|
-
import * as
|
|
3
|
-
import * as
|
|
2
|
+
import * as _protobuf_ts_runtime2 from "@protobuf-ts/runtime";
|
|
3
|
+
import * as _protobuf_ts_runtime_rpc2 from "@protobuf-ts/runtime-rpc";
|
|
4
4
|
import { RpcOptions, RpcTransport, ServiceInfo, UnaryCall } from "@protobuf-ts/runtime-rpc";
|
|
5
5
|
|
|
6
6
|
//#region src/grpc/proto/sui/forking/v1alpha/forking_service.client.d.ts
|
|
@@ -37,9 +37,9 @@ interface IForkingServiceClient {
|
|
|
37
37
|
declare class ForkingServiceClient implements IForkingServiceClient, ServiceInfo {
|
|
38
38
|
private readonly _transport;
|
|
39
39
|
typeName: string;
|
|
40
|
-
methods:
|
|
40
|
+
methods: _protobuf_ts_runtime_rpc2.MethodInfo<any, any>[];
|
|
41
41
|
options: {
|
|
42
|
-
[extensionName: string]:
|
|
42
|
+
[extensionName: string]: _protobuf_ts_runtime2.JsonValue;
|
|
43
43
|
};
|
|
44
44
|
constructor(_transport: RpcTransport);
|
|
45
45
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { GetDatatypeRequest, GetDatatypeResponse, GetFunctionRequest, GetFunctionResponse, GetPackageRequest, GetPackageResponse, ListPackageVersionsRequest, ListPackageVersionsResponse } from "./move_package_service.mjs";
|
|
2
|
-
import * as
|
|
3
|
-
import * as
|
|
2
|
+
import * as _protobuf_ts_runtime3 from "@protobuf-ts/runtime";
|
|
3
|
+
import * as _protobuf_ts_runtime_rpc3 from "@protobuf-ts/runtime-rpc";
|
|
4
4
|
import { RpcOptions, RpcTransport, ServiceInfo, UnaryCall } from "@protobuf-ts/runtime-rpc";
|
|
5
5
|
|
|
6
6
|
//#region src/grpc/proto/sui/rpc/v2/move_package_service.client.d.ts
|
|
@@ -31,9 +31,9 @@ interface IMovePackageServiceClient {
|
|
|
31
31
|
declare class MovePackageServiceClient implements IMovePackageServiceClient, ServiceInfo {
|
|
32
32
|
private readonly _transport;
|
|
33
33
|
typeName: string;
|
|
34
|
-
methods:
|
|
34
|
+
methods: _protobuf_ts_runtime_rpc3.MethodInfo<any, any>[];
|
|
35
35
|
options: {
|
|
36
|
-
[extensionName: string]:
|
|
36
|
+
[extensionName: string]: _protobuf_ts_runtime3.JsonValue;
|
|
37
37
|
};
|
|
38
38
|
constructor(_transport: RpcTransport);
|
|
39
39
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { VerifySignatureRequest, VerifySignatureResponse } from "./signature_verification_service.mjs";
|
|
2
|
-
import * as
|
|
3
|
-
import * as
|
|
2
|
+
import * as _protobuf_ts_runtime0 from "@protobuf-ts/runtime";
|
|
3
|
+
import * as _protobuf_ts_runtime_rpc0 from "@protobuf-ts/runtime-rpc";
|
|
4
4
|
import { RpcOptions, RpcTransport, ServiceInfo, UnaryCall } from "@protobuf-ts/runtime-rpc";
|
|
5
5
|
|
|
6
6
|
//#region src/grpc/proto/sui/rpc/v2/signature_verification_service.client.d.ts
|
|
@@ -21,9 +21,9 @@ interface ISignatureVerificationServiceClient {
|
|
|
21
21
|
declare class SignatureVerificationServiceClient implements ISignatureVerificationServiceClient, ServiceInfo {
|
|
22
22
|
private readonly _transport;
|
|
23
23
|
typeName: string;
|
|
24
|
-
methods:
|
|
24
|
+
methods: _protobuf_ts_runtime_rpc0.MethodInfo<any, any>[];
|
|
25
25
|
options: {
|
|
26
|
-
[extensionName: string]:
|
|
26
|
+
[extensionName: string]: _protobuf_ts_runtime0.JsonValue;
|
|
27
27
|
};
|
|
28
28
|
constructor(_transport: RpcTransport);
|
|
29
29
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CoinWithBalance.d.mts","names":[],"sources":["../../../src/transactions/intents/CoinWithBalance.ts"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"CoinWithBalance.d.mts","names":[],"sources":["../../../src/transactions/intents/CoinWithBalance.ts"],"mappings":";;;;;iBA+BgB,eAAA,CAAA;EACf,IAAA;EACA,OAAA;EACA;AAAA;EAEA,OAAA;EACA,IAAA;EACA,UAAA;AAAA,KACI,EAAA,EAAI,WAAA,KAAgB,iBAAA"}
|
|
@@ -2,7 +2,7 @@ import { normalizeStructTag } from "../../utils/sui-types.mjs";
|
|
|
2
2
|
import { bcs as suiBcs } from "../../bcs/index.mjs";
|
|
3
3
|
import { TransactionCommands } from "../Commands.mjs";
|
|
4
4
|
import { Inputs } from "../Inputs.mjs";
|
|
5
|
-
import { bigint, object, optional, parse, picklist, string } from "valibot";
|
|
5
|
+
import { bigint, integer, number, object, optional, parse, picklist, pipe, string, transform, union } from "valibot";
|
|
6
6
|
|
|
7
7
|
//#region src/transactions/intents/CoinWithBalance.ts
|
|
8
8
|
const COIN_WITH_BALANCE = "CoinWithBalance";
|
|
@@ -45,7 +45,11 @@ function createBalance({ type = SUI_TYPE, balance, useGasCoin = true }) {
|
|
|
45
45
|
}
|
|
46
46
|
const CoinWithBalanceData = object({
|
|
47
47
|
type: string(),
|
|
48
|
-
balance:
|
|
48
|
+
balance: pipe(union([
|
|
49
|
+
bigint(),
|
|
50
|
+
string(),
|
|
51
|
+
pipe(number(), integer())
|
|
52
|
+
]), transform((value) => BigInt(value))),
|
|
49
53
|
outputKind: optional(picklist(["coin", "balance"]))
|
|
50
54
|
});
|
|
51
55
|
async function resolveCoinBalance(transactionData, buildOptions, next) {
|
|
@@ -109,7 +113,7 @@ async function resolveCoinBalance(transactionData, buildOptions, next) {
|
|
|
109
113
|
index++;
|
|
110
114
|
continue;
|
|
111
115
|
}
|
|
112
|
-
const { type, balance } = transaction.$Intent.data;
|
|
116
|
+
const { type, balance } = parse(CoinWithBalanceData, transaction.$Intent.data);
|
|
113
117
|
const coinType = type === "gas" ? SUI_TYPE : type;
|
|
114
118
|
const totalRequired = totalByType.get(type);
|
|
115
119
|
const addressBalance = addressBalanceByType.get(type) ?? 0n;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CoinWithBalance.mjs","names":["bcs","balance"],"sources":["../../../src/transactions/intents/CoinWithBalance.ts"],"sourcesContent":["// Copyright (c) Mysten Labs, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\nimport type { InferInput } from 'valibot';\nimport { bigint, object, optional, parse, picklist, string } from 'valibot';\n\nimport { bcs } from '../../bcs/index.js';\nimport { normalizeStructTag } from '../../utils/sui-types.js';\nimport { TransactionCommands } from '../Commands.js';\nimport type { Argument } from '../data/internal.js';\nimport { Inputs } from '../Inputs.js';\nimport type { BuildTransactionOptions } from '../resolve.js';\nimport type { Transaction, TransactionResult } from '../Transaction.js';\nimport type { TransactionDataBuilder } from '../TransactionData.js';\nimport type { ClientWithCoreApi, SuiClientTypes } from '../../client/index.js';\n\nexport const COIN_WITH_BALANCE = 'CoinWithBalance';\nconst SUI_TYPE = normalizeStructTag('0x2::sui::SUI');\n\nexport function coinWithBalance({\n\ttype = SUI_TYPE,\n\tbalance,\n\tuseGasCoin = true,\n}: {\n\tbalance: bigint | number;\n\ttype?: string;\n\tuseGasCoin?: boolean;\n}): (tx: Transaction) => TransactionResult {\n\tlet coinResult: TransactionResult | null = null;\n\n\treturn (tx: Transaction) => {\n\t\tif (coinResult) {\n\t\t\treturn coinResult;\n\t\t}\n\n\t\ttx.addIntentResolver(COIN_WITH_BALANCE, resolveCoinBalance);\n\t\tconst coinType = type === 'gas' ? type : normalizeStructTag(type);\n\n\t\tcoinResult = tx.add(\n\t\t\tTransactionCommands.Intent({\n\t\t\t\tname: COIN_WITH_BALANCE,\n\t\t\t\tinputs: {},\n\t\t\t\tdata: {\n\t\t\t\t\ttype: coinType === SUI_TYPE && useGasCoin ? 'gas' : coinType,\n\t\t\t\t\tbalance: BigInt(balance),\n\t\t\t\t\toutputKind: 'coin',\n\t\t\t\t} satisfies InferInput<typeof CoinWithBalanceData>,\n\t\t\t}),\n\t\t);\n\n\t\treturn coinResult;\n\t};\n}\n\nexport function createBalance({\n\ttype = SUI_TYPE,\n\tbalance,\n\tuseGasCoin = true,\n}: {\n\tbalance: bigint | number;\n\ttype?: string;\n\tuseGasCoin?: boolean;\n}): (tx: Transaction) => TransactionResult {\n\tlet balanceResult: TransactionResult | null = null;\n\n\treturn (tx: Transaction) => {\n\t\tif (balanceResult) {\n\t\t\treturn balanceResult;\n\t\t}\n\n\t\ttx.addIntentResolver(COIN_WITH_BALANCE, resolveCoinBalance);\n\t\tconst coinType = type === 'gas' ? type : normalizeStructTag(type);\n\n\t\tbalanceResult = tx.add(\n\t\t\tTransactionCommands.Intent({\n\t\t\t\tname: COIN_WITH_BALANCE,\n\t\t\t\tinputs: {},\n\t\t\t\tdata: {\n\t\t\t\t\ttype: coinType === SUI_TYPE && useGasCoin ? 'gas' : coinType,\n\t\t\t\t\tbalance: BigInt(balance),\n\t\t\t\t\toutputKind: 'balance',\n\t\t\t\t} satisfies InferInput<typeof CoinWithBalanceData>,\n\t\t\t}),\n\t\t);\n\n\t\treturn balanceResult;\n\t};\n}\n\nconst CoinWithBalanceData = object({\n\ttype: string(),\n\tbalance: bigint(),\n\toutputKind: optional(picklist(['coin', 'balance'])),\n});\n\nexport async function resolveCoinBalance(\n\ttransactionData: TransactionDataBuilder,\n\tbuildOptions: BuildTransactionOptions,\n\tnext: () => Promise<void>,\n) {\n\ttype IntentInfo = { balance: bigint; outputKind: 'coin' | 'balance' };\n\n\tconst coinTypes = new Set<string>();\n\tconst totalByType = new Map<string, bigint>();\n\tconst intentsByType = new Map<string, IntentInfo[]>();\n\n\tif (!transactionData.sender) {\n\t\tthrow new Error('Sender must be set to resolve CoinWithBalance');\n\t}\n\n\t// First pass: scan intents, collect per-type data, and resolve zero-balance intents in place.\n\tfor (const [i, command] of transactionData.commands.entries()) {\n\t\tif (command.$kind !== '$Intent' || command.$Intent.name !== COIN_WITH_BALANCE) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst { type, balance, outputKind } = parse(CoinWithBalanceData, command.$Intent.data);\n\n\t\t// Zero-balance intents are resolved immediately — no coins or AB needed.\n\t\t// This is a 1:1 replacement so indices don't shift.\n\t\tif (balance === 0n) {\n\t\t\tconst coinType = type === 'gas' ? SUI_TYPE : type;\n\t\t\ttransactionData.replaceCommand(\n\t\t\t\ti,\n\t\t\t\tTransactionCommands.MoveCall({\n\t\t\t\t\ttarget: (outputKind ?? 'coin') === 'balance' ? '0x2::balance::zero' : '0x2::coin::zero',\n\t\t\t\t\ttypeArguments: [coinType],\n\t\t\t\t}),\n\t\t\t);\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (type !== 'gas') {\n\t\t\tcoinTypes.add(type);\n\t\t}\n\n\t\ttotalByType.set(type, (totalByType.get(type) ?? 0n) + balance);\n\n\t\tif (!intentsByType.has(type)) intentsByType.set(type, []);\n\t\tintentsByType.get(type)!.push({ balance, outputKind: outputKind ?? 'coin' });\n\t}\n\n\tif (totalByType.has('gas') && totalByType.has(SUI_TYPE)) {\n\t\tthrow new Error(\n\t\t\t'Cannot mix SUI CoinWithBalance intents that use the gas coin with ones that do not (useGasCoin: false). Use one or the other.',\n\t\t);\n\t}\n\n\tconst usedIds = new Set<string>();\n\n\tfor (const input of transactionData.inputs) {\n\t\tif (input.Object?.ImmOrOwnedObject) {\n\t\t\tusedIds.add(input.Object.ImmOrOwnedObject.objectId);\n\t\t}\n\t\tif (input.UnresolvedObject?.objectId) {\n\t\t\tusedIds.add(input.UnresolvedObject.objectId);\n\t\t}\n\t}\n\n\tconst coinsByType = new Map<string, SuiClientTypes.Coin[]>();\n\tconst addressBalanceByType = new Map<string, bigint>();\n\tconst client = buildOptions.client;\n\n\tif (!client) {\n\t\tthrow new Error(\n\t\t\t'Client must be provided to build or serialize transactions with CoinWithBalance intents',\n\t\t);\n\t}\n\n\tawait Promise.all([\n\t\t...[...coinTypes].map(async (coinType) => {\n\t\t\tconst { coins, addressBalance } = await getCoinsAndBalanceOfType({\n\t\t\t\tcoinType,\n\t\t\t\tbalance: totalByType.get(coinType)!,\n\t\t\t\tclient,\n\t\t\t\towner: transactionData.sender!,\n\t\t\t\tusedIds,\n\t\t\t});\n\n\t\t\tcoinsByType.set(coinType, coins);\n\t\t\taddressBalanceByType.set(coinType, addressBalance);\n\t\t}),\n\t\ttotalByType.has('gas')\n\t\t\t? await client.core\n\t\t\t\t\t.getBalance({\n\t\t\t\t\t\towner: transactionData.sender!,\n\t\t\t\t\t\tcoinType: SUI_TYPE,\n\t\t\t\t\t})\n\t\t\t\t\t.then(({ balance }) => {\n\t\t\t\t\t\taddressBalanceByType.set('gas', BigInt(balance.addressBalance));\n\t\t\t\t\t})\n\t\t\t: null,\n\t]);\n\n\tconst mergedCoins = new Map<string, Argument>();\n\tconst exactBalanceByType = new Map<string, boolean>();\n\tconst usedAddressBalance = new Set<string>();\n\n\t// Per-type state for Path 2 combined splits\n\ttype TypeState = { results: Argument[]; nextIntent: number };\n\tconst typeState = new Map<string, TypeState>();\n\n\tlet index = 0;\n\twhile (index < transactionData.commands.length) {\n\t\tconst transaction = transactionData.commands[index];\n\t\tif (transaction.$kind !== '$Intent' || transaction.$Intent.name !== COIN_WITH_BALANCE) {\n\t\t\tindex++;\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst { type, balance } = transaction.$Intent.data as {\n\t\t\ttype: string;\n\t\t\tbalance: bigint;\n\t\t};\n\t\tconst coinType = type === 'gas' ? SUI_TYPE : type;\n\t\tconst totalRequired = totalByType.get(type)!;\n\t\tconst addressBalance = addressBalanceByType.get(type) ?? 0n;\n\n\t\tconst commands = [];\n\t\tlet intentResult: Argument;\n\n\t\tconst intentsForType = intentsByType.get(type) ?? [];\n\t\tconst allBalance = intentsForType.every((i) => i.outputKind === 'balance');\n\n\t\tif (allBalance && addressBalance >= totalRequired) {\n\t\t\t// Path 1: All balance intents and AB sufficient — direct per-intent withdrawal.\n\t\t\t// No coins touched, enables parallel execution.\n\t\t\tcommands.push(\n\t\t\t\tTransactionCommands.MoveCall({\n\t\t\t\t\ttarget: '0x2::balance::redeem_funds',\n\t\t\t\t\ttypeArguments: [coinType],\n\t\t\t\t\targuments: [\n\t\t\t\t\t\ttransactionData.addInput(\n\t\t\t\t\t\t\t'withdrawal',\n\t\t\t\t\t\t\tInputs.FundsWithdrawal({\n\t\t\t\t\t\t\t\treservation: {\n\t\t\t\t\t\t\t\t\t$kind: 'MaxAmountU64',\n\t\t\t\t\t\t\t\t\tMaxAmountU64: String(balance),\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\ttypeArg: { $kind: 'Balance', Balance: coinType },\n\t\t\t\t\t\t\t\twithdrawFrom: { $kind: 'Sender', Sender: true },\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t),\n\t\t\t\t\t],\n\t\t\t\t}),\n\t\t\t);\n\n\t\t\tintentResult = {\n\t\t\t\t$kind: 'NestedResult',\n\t\t\t\tNestedResult: [index + commands.length - 1, 0],\n\t\t\t};\n\t\t} else {\n\t\t\t// Path 2: Merge and Split — build a merged coin, split all intents at once.\n\n\t\t\tif (!typeState.has(type)) {\n\t\t\t\tconst intents = intentsForType;\n\n\t\t\t\t// Step 1: Build sources and merge\n\t\t\t\tconst sources: Argument[] = [];\n\n\t\t\t\tif (addressBalance >= totalRequired) {\n\t\t\t\t\t// AB sufficient — source entirely from address balance, no coins needed.\n\t\t\t\t\tusedAddressBalance.add(type);\n\n\t\t\t\t\tcommands.push(\n\t\t\t\t\t\tTransactionCommands.MoveCall({\n\t\t\t\t\t\t\ttarget: '0x2::coin::redeem_funds',\n\t\t\t\t\t\t\ttypeArguments: [coinType],\n\t\t\t\t\t\t\targuments: [\n\t\t\t\t\t\t\t\ttransactionData.addInput(\n\t\t\t\t\t\t\t\t\t'withdrawal',\n\t\t\t\t\t\t\t\t\tInputs.FundsWithdrawal({\n\t\t\t\t\t\t\t\t\t\treservation: {\n\t\t\t\t\t\t\t\t\t\t\t$kind: 'MaxAmountU64',\n\t\t\t\t\t\t\t\t\t\t\tMaxAmountU64: String(totalRequired),\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\ttypeArg: { $kind: 'Balance', Balance: coinType },\n\t\t\t\t\t\t\t\t\t\twithdrawFrom: { $kind: 'Sender', Sender: true },\n\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t\tsources.push({ $kind: 'Result', Result: index + commands.length - 1 });\n\t\t\t\t} else if (type === 'gas') {\n\t\t\t\t\tsources.push({ $kind: 'GasCoin', GasCoin: true });\n\t\t\t\t} else {\n\t\t\t\t\tconst coins = coinsByType.get(type)!;\n\t\t\t\t\tconst loadedCoinBalance = coins.reduce((sum, c) => sum + BigInt(c.balance), 0n);\n\t\t\t\t\tconst abNeeded =\n\t\t\t\t\t\ttotalRequired > loadedCoinBalance ? totalRequired - loadedCoinBalance : 0n;\n\n\t\t\t\t\texactBalanceByType.set(type, loadedCoinBalance + abNeeded === totalRequired);\n\n\t\t\t\t\tfor (const coin of coins) {\n\t\t\t\t\t\tsources.push(\n\t\t\t\t\t\t\ttransactionData.addInput(\n\t\t\t\t\t\t\t\t'object',\n\t\t\t\t\t\t\t\tInputs.ObjectRef({\n\t\t\t\t\t\t\t\t\tobjectId: coin.objectId,\n\t\t\t\t\t\t\t\t\tdigest: coin.digest,\n\t\t\t\t\t\t\t\t\tversion: coin.version,\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tif (abNeeded > 0n) {\n\t\t\t\t\t\tusedAddressBalance.add(type);\n\t\t\t\t\t\tcommands.push(\n\t\t\t\t\t\t\tTransactionCommands.MoveCall({\n\t\t\t\t\t\t\t\ttarget: '0x2::coin::redeem_funds',\n\t\t\t\t\t\t\t\ttypeArguments: [coinType],\n\t\t\t\t\t\t\t\targuments: [\n\t\t\t\t\t\t\t\t\ttransactionData.addInput(\n\t\t\t\t\t\t\t\t\t\t'withdrawal',\n\t\t\t\t\t\t\t\t\t\tInputs.FundsWithdrawal({\n\t\t\t\t\t\t\t\t\t\t\treservation: {\n\t\t\t\t\t\t\t\t\t\t\t\t$kind: 'MaxAmountU64',\n\t\t\t\t\t\t\t\t\t\t\t\tMaxAmountU64: String(abNeeded),\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\ttypeArg: { $kind: 'Balance', Balance: coinType },\n\t\t\t\t\t\t\t\t\t\t\twithdrawFrom: { $kind: 'Sender', Sender: true },\n\t\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t);\n\t\t\t\t\t\tsources.push({ $kind: 'Result', Result: index + commands.length - 1 });\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst baseCoin = sources[0];\n\t\t\t\tconst rest = sources.slice(1);\n\t\t\t\tfor (let i = 0; i < rest.length; i += 500) {\n\t\t\t\t\tcommands.push(TransactionCommands.MergeCoins(baseCoin, rest.slice(i, i + 500)));\n\t\t\t\t}\n\n\t\t\t\tmergedCoins.set(type, baseCoin);\n\n\t\t\t\t// Step 2: Combined SplitCoins for all intents of this type\n\t\t\t\tconst splitCmdIndex = index + commands.length;\n\t\t\t\tcommands.push(\n\t\t\t\t\tTransactionCommands.SplitCoins(\n\t\t\t\t\t\tbaseCoin,\n\t\t\t\t\t\tintents.map((i) =>\n\t\t\t\t\t\t\ttransactionData.addInput('pure', Inputs.Pure(bcs.u64().serialize(i.balance))),\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t);\n\n\t\t\t\t// Build per-intent results, adding into_balance conversions for balance intents\n\t\t\t\tconst results: Argument[] = [];\n\t\t\t\tfor (let i = 0; i < intents.length; i++) {\n\t\t\t\t\tconst splitResult: Argument = {\n\t\t\t\t\t\t$kind: 'NestedResult',\n\t\t\t\t\t\tNestedResult: [splitCmdIndex, i],\n\t\t\t\t\t};\n\n\t\t\t\t\tif (intents[i].outputKind === 'balance') {\n\t\t\t\t\t\tcommands.push(\n\t\t\t\t\t\t\tTransactionCommands.MoveCall({\n\t\t\t\t\t\t\t\ttarget: '0x2::coin::into_balance',\n\t\t\t\t\t\t\t\ttypeArguments: [coinType],\n\t\t\t\t\t\t\t\targuments: [splitResult],\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t);\n\t\t\t\t\t\tresults.push({\n\t\t\t\t\t\t\t$kind: 'NestedResult',\n\t\t\t\t\t\t\tNestedResult: [index + commands.length - 1, 0],\n\t\t\t\t\t\t});\n\t\t\t\t\t} else {\n\t\t\t\t\t\tresults.push(splitResult);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\ttypeState.set(type, { results, nextIntent: 0 });\n\t\t\t}\n\n\t\t\tconst state = typeState.get(type)!;\n\t\t\tintentResult = state.results[state.nextIntent++];\n\t\t}\n\n\t\ttransactionData.replaceCommand(\n\t\t\tindex,\n\t\t\tcommands,\n\t\t\tintentResult as { NestedResult: [number, number] },\n\t\t);\n\n\t\t// Advance past the replacement. When commands is empty (subsequent intents\n\t\t// of a combined split), the command was removed and the next command shifted\n\t\t// into this position — so we stay at the same index.\n\t\tindex += commands.length;\n\t}\n\n\t// Step 3: Remainder handling\n\tfor (const [type, mergedCoin] of mergedCoins) {\n\t\t// When gas type used GasCoin (not AB), leftover stays in the gas coin — no remainder needed.\n\t\tif (type === 'gas' && !usedAddressBalance.has(type)) continue;\n\n\t\tconst coinType = type === 'gas' ? SUI_TYPE : type;\n\t\tconst hasBalanceIntent = intentsByType.get(type)?.some((i) => i.outputKind === 'balance');\n\t\tconst sourcedFromAB = usedAddressBalance.has(type);\n\n\t\tif (hasBalanceIntent || sourcedFromAB) {\n\t\t\t// Sourced from AB or balance intents exist: send remainder back to sender's address balance.\n\t\t\t// coin::send_funds is gasless-eligible and handles zero amounts.\n\t\t\ttransactionData.commands.push(\n\t\t\t\tTransactionCommands.MoveCall({\n\t\t\t\t\ttarget: '0x2::coin::send_funds',\n\t\t\t\t\ttypeArguments: [coinType],\n\t\t\t\t\targuments: [\n\t\t\t\t\t\tmergedCoin,\n\t\t\t\t\t\ttransactionData.addInput(\n\t\t\t\t\t\t\t'pure',\n\t\t\t\t\t\t\tInputs.Pure(bcs.Address.serialize(transactionData.sender!)),\n\t\t\t\t\t\t),\n\t\t\t\t\t],\n\t\t\t\t}),\n\t\t\t);\n\t\t} else if (exactBalanceByType.get(type)) {\n\t\t\t// Coin-only with exact match: destroy the zero-value dust coin.\n\t\t\ttransactionData.commands.push(\n\t\t\t\tTransactionCommands.MoveCall({\n\t\t\t\t\ttarget: '0x2::coin::destroy_zero',\n\t\t\t\t\ttypeArguments: [coinType],\n\t\t\t\t\targuments: [mergedCoin],\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\t// Coin-only with surplus: merged coin stays with sender as an owned object\n\t}\n\n\treturn next();\n}\n\nasync function getCoinsAndBalanceOfType({\n\tcoinType,\n\tbalance,\n\tclient,\n\towner,\n\tusedIds,\n}: {\n\tcoinType: string;\n\tbalance: bigint;\n\tclient: ClientWithCoreApi;\n\towner: string;\n\tusedIds: Set<string>;\n}): Promise<{\n\tcoins: SuiClientTypes.Coin[];\n\tbalance: bigint;\n\taddressBalance: bigint;\n\tcoinBalance: bigint;\n}> {\n\tlet remainingBalance = balance;\n\tconst coins: SuiClientTypes.Coin[] = [];\n\tconst balanceRequest = client.core.getBalance({ owner, coinType }).then(({ balance }) => {\n\t\tremainingBalance -= BigInt(balance.addressBalance);\n\n\t\treturn balance;\n\t});\n\n\tconst [allCoins, balanceResponse] = await Promise.all([loadMoreCoins(), balanceRequest]);\n\n\tif (BigInt(balanceResponse.balance) < balance) {\n\t\tthrow new Error(\n\t\t\t`Insufficient balance of ${coinType} for owner ${owner}. Required: ${balance}, Available: ${\n\t\t\t\tbalance - remainingBalance\n\t\t\t}`,\n\t\t);\n\t}\n\n\treturn {\n\t\tcoins: allCoins,\n\t\tbalance: BigInt(balanceResponse.coinBalance),\n\t\taddressBalance: BigInt(balanceResponse.addressBalance),\n\t\tcoinBalance: BigInt(balanceResponse.coinBalance),\n\t};\n\n\tasync function loadMoreCoins(cursor: string | null = null): Promise<SuiClientTypes.Coin[]> {\n\t\tconst {\n\t\t\tobjects,\n\t\t\thasNextPage,\n\t\t\tcursor: nextCursor,\n\t\t} = await client.core.listCoins({\n\t\t\towner,\n\t\t\tcoinType,\n\t\t\tcursor,\n\t\t});\n\n\t\tawait balanceRequest;\n\n\t\t// Always load all coins from the page (except already-used ones).\n\t\t// This merges all available coins rather than leaving dust.\n\t\tfor (const coin of objects) {\n\t\t\tif (usedIds.has(coin.objectId)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tcoins.push(coin);\n\t\t\tremainingBalance -= BigInt(coin.balance);\n\t\t}\n\n\t\t// Only paginate if loaded coins + AB are still insufficient\n\t\tif (remainingBalance > 0n && hasNextPage) {\n\t\t\treturn loadMoreCoins(nextCursor);\n\t\t}\n\n\t\treturn coins;\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CoinWithBalance.mjs","names":["bcs","balance"],"sources":["../../../src/transactions/intents/CoinWithBalance.ts"],"sourcesContent":["// Copyright (c) Mysten Labs, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\nimport type { InferInput } from 'valibot';\nimport {\n\tbigint,\n\tinteger,\n\tnumber,\n\tobject,\n\toptional,\n\tparse,\n\tpicklist,\n\tpipe,\n\tstring,\n\ttransform,\n\tunion,\n} from 'valibot';\n\nimport { bcs } from '../../bcs/index.js';\nimport { normalizeStructTag } from '../../utils/sui-types.js';\nimport { TransactionCommands } from '../Commands.js';\nimport type { Argument } from '../data/internal.js';\nimport { Inputs } from '../Inputs.js';\nimport type { BuildTransactionOptions } from '../resolve.js';\nimport type { Transaction, TransactionResult } from '../Transaction.js';\nimport type { TransactionDataBuilder } from '../TransactionData.js';\nimport type { ClientWithCoreApi, SuiClientTypes } from '../../client/index.js';\n\nexport const COIN_WITH_BALANCE = 'CoinWithBalance';\nconst SUI_TYPE = normalizeStructTag('0x2::sui::SUI');\n\nexport function coinWithBalance({\n\ttype = SUI_TYPE,\n\tbalance,\n\tuseGasCoin = true,\n}: {\n\tbalance: bigint | number;\n\ttype?: string;\n\tuseGasCoin?: boolean;\n}): (tx: Transaction) => TransactionResult {\n\tlet coinResult: TransactionResult | null = null;\n\n\treturn (tx: Transaction) => {\n\t\tif (coinResult) {\n\t\t\treturn coinResult;\n\t\t}\n\n\t\ttx.addIntentResolver(COIN_WITH_BALANCE, resolveCoinBalance);\n\t\tconst coinType = type === 'gas' ? type : normalizeStructTag(type);\n\n\t\tcoinResult = tx.add(\n\t\t\tTransactionCommands.Intent({\n\t\t\t\tname: COIN_WITH_BALANCE,\n\t\t\t\tinputs: {},\n\t\t\t\tdata: {\n\t\t\t\t\ttype: coinType === SUI_TYPE && useGasCoin ? 'gas' : coinType,\n\t\t\t\t\tbalance: BigInt(balance),\n\t\t\t\t\toutputKind: 'coin',\n\t\t\t\t} satisfies InferInput<typeof CoinWithBalanceData>,\n\t\t\t}),\n\t\t);\n\n\t\treturn coinResult;\n\t};\n}\n\nexport function createBalance({\n\ttype = SUI_TYPE,\n\tbalance,\n\tuseGasCoin = true,\n}: {\n\tbalance: bigint | number;\n\ttype?: string;\n\tuseGasCoin?: boolean;\n}): (tx: Transaction) => TransactionResult {\n\tlet balanceResult: TransactionResult | null = null;\n\n\treturn (tx: Transaction) => {\n\t\tif (balanceResult) {\n\t\t\treturn balanceResult;\n\t\t}\n\n\t\ttx.addIntentResolver(COIN_WITH_BALANCE, resolveCoinBalance);\n\t\tconst coinType = type === 'gas' ? type : normalizeStructTag(type);\n\n\t\tbalanceResult = tx.add(\n\t\t\tTransactionCommands.Intent({\n\t\t\t\tname: COIN_WITH_BALANCE,\n\t\t\t\tinputs: {},\n\t\t\t\tdata: {\n\t\t\t\t\ttype: coinType === SUI_TYPE && useGasCoin ? 'gas' : coinType,\n\t\t\t\t\tbalance: BigInt(balance),\n\t\t\t\t\toutputKind: 'balance',\n\t\t\t\t} satisfies InferInput<typeof CoinWithBalanceData>,\n\t\t\t}),\n\t\t);\n\n\t\treturn balanceResult;\n\t};\n}\n\n// `balance` is a bigint in memory, but serializing a transaction to JSON converts it to a\n// string. When that JSON is deserialized with `Transaction.from`, the value stays a string\n// (the intent `data` is opaque to the transaction schema), so coerce it back to a bigint here.\nconst CoinWithBalanceData = object({\n\ttype: string(),\n\tbalance: pipe(\n\t\tunion([bigint(), string(), pipe(number(), integer())]),\n\t\ttransform((value) => BigInt(value)),\n\t),\n\toutputKind: optional(picklist(['coin', 'balance'])),\n});\n\nexport async function resolveCoinBalance(\n\ttransactionData: TransactionDataBuilder,\n\tbuildOptions: BuildTransactionOptions,\n\tnext: () => Promise<void>,\n) {\n\ttype IntentInfo = { balance: bigint; outputKind: 'coin' | 'balance' };\n\n\tconst coinTypes = new Set<string>();\n\tconst totalByType = new Map<string, bigint>();\n\tconst intentsByType = new Map<string, IntentInfo[]>();\n\n\tif (!transactionData.sender) {\n\t\tthrow new Error('Sender must be set to resolve CoinWithBalance');\n\t}\n\n\t// First pass: scan intents, collect per-type data, and resolve zero-balance intents in place.\n\tfor (const [i, command] of transactionData.commands.entries()) {\n\t\tif (command.$kind !== '$Intent' || command.$Intent.name !== COIN_WITH_BALANCE) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst { type, balance, outputKind } = parse(CoinWithBalanceData, command.$Intent.data);\n\n\t\t// Zero-balance intents are resolved immediately — no coins or AB needed.\n\t\t// This is a 1:1 replacement so indices don't shift.\n\t\tif (balance === 0n) {\n\t\t\tconst coinType = type === 'gas' ? SUI_TYPE : type;\n\t\t\ttransactionData.replaceCommand(\n\t\t\t\ti,\n\t\t\t\tTransactionCommands.MoveCall({\n\t\t\t\t\ttarget: (outputKind ?? 'coin') === 'balance' ? '0x2::balance::zero' : '0x2::coin::zero',\n\t\t\t\t\ttypeArguments: [coinType],\n\t\t\t\t}),\n\t\t\t);\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (type !== 'gas') {\n\t\t\tcoinTypes.add(type);\n\t\t}\n\n\t\ttotalByType.set(type, (totalByType.get(type) ?? 0n) + balance);\n\n\t\tif (!intentsByType.has(type)) intentsByType.set(type, []);\n\t\tintentsByType.get(type)!.push({ balance, outputKind: outputKind ?? 'coin' });\n\t}\n\n\tif (totalByType.has('gas') && totalByType.has(SUI_TYPE)) {\n\t\tthrow new Error(\n\t\t\t'Cannot mix SUI CoinWithBalance intents that use the gas coin with ones that do not (useGasCoin: false). Use one or the other.',\n\t\t);\n\t}\n\n\tconst usedIds = new Set<string>();\n\n\tfor (const input of transactionData.inputs) {\n\t\tif (input.Object?.ImmOrOwnedObject) {\n\t\t\tusedIds.add(input.Object.ImmOrOwnedObject.objectId);\n\t\t}\n\t\tif (input.UnresolvedObject?.objectId) {\n\t\t\tusedIds.add(input.UnresolvedObject.objectId);\n\t\t}\n\t}\n\n\tconst coinsByType = new Map<string, SuiClientTypes.Coin[]>();\n\tconst addressBalanceByType = new Map<string, bigint>();\n\tconst client = buildOptions.client;\n\n\tif (!client) {\n\t\tthrow new Error(\n\t\t\t'Client must be provided to build or serialize transactions with CoinWithBalance intents',\n\t\t);\n\t}\n\n\tawait Promise.all([\n\t\t...[...coinTypes].map(async (coinType) => {\n\t\t\tconst { coins, addressBalance } = await getCoinsAndBalanceOfType({\n\t\t\t\tcoinType,\n\t\t\t\tbalance: totalByType.get(coinType)!,\n\t\t\t\tclient,\n\t\t\t\towner: transactionData.sender!,\n\t\t\t\tusedIds,\n\t\t\t});\n\n\t\t\tcoinsByType.set(coinType, coins);\n\t\t\taddressBalanceByType.set(coinType, addressBalance);\n\t\t}),\n\t\ttotalByType.has('gas')\n\t\t\t? await client.core\n\t\t\t\t\t.getBalance({\n\t\t\t\t\t\towner: transactionData.sender!,\n\t\t\t\t\t\tcoinType: SUI_TYPE,\n\t\t\t\t\t})\n\t\t\t\t\t.then(({ balance }) => {\n\t\t\t\t\t\taddressBalanceByType.set('gas', BigInt(balance.addressBalance));\n\t\t\t\t\t})\n\t\t\t: null,\n\t]);\n\n\tconst mergedCoins = new Map<string, Argument>();\n\tconst exactBalanceByType = new Map<string, boolean>();\n\tconst usedAddressBalance = new Set<string>();\n\n\t// Per-type state for Path 2 combined splits\n\ttype TypeState = { results: Argument[]; nextIntent: number };\n\tconst typeState = new Map<string, TypeState>();\n\n\tlet index = 0;\n\twhile (index < transactionData.commands.length) {\n\t\tconst transaction = transactionData.commands[index];\n\t\tif (transaction.$kind !== '$Intent' || transaction.$Intent.name !== COIN_WITH_BALANCE) {\n\t\t\tindex++;\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst { type, balance } = parse(CoinWithBalanceData, transaction.$Intent.data);\n\t\tconst coinType = type === 'gas' ? SUI_TYPE : type;\n\t\tconst totalRequired = totalByType.get(type)!;\n\t\tconst addressBalance = addressBalanceByType.get(type) ?? 0n;\n\n\t\tconst commands = [];\n\t\tlet intentResult: Argument;\n\n\t\tconst intentsForType = intentsByType.get(type) ?? [];\n\t\tconst allBalance = intentsForType.every((i) => i.outputKind === 'balance');\n\n\t\tif (allBalance && addressBalance >= totalRequired) {\n\t\t\t// Path 1: All balance intents and AB sufficient — direct per-intent withdrawal.\n\t\t\t// No coins touched, enables parallel execution.\n\t\t\tcommands.push(\n\t\t\t\tTransactionCommands.MoveCall({\n\t\t\t\t\ttarget: '0x2::balance::redeem_funds',\n\t\t\t\t\ttypeArguments: [coinType],\n\t\t\t\t\targuments: [\n\t\t\t\t\t\ttransactionData.addInput(\n\t\t\t\t\t\t\t'withdrawal',\n\t\t\t\t\t\t\tInputs.FundsWithdrawal({\n\t\t\t\t\t\t\t\treservation: {\n\t\t\t\t\t\t\t\t\t$kind: 'MaxAmountU64',\n\t\t\t\t\t\t\t\t\tMaxAmountU64: String(balance),\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\ttypeArg: { $kind: 'Balance', Balance: coinType },\n\t\t\t\t\t\t\t\twithdrawFrom: { $kind: 'Sender', Sender: true },\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t),\n\t\t\t\t\t],\n\t\t\t\t}),\n\t\t\t);\n\n\t\t\tintentResult = {\n\t\t\t\t$kind: 'NestedResult',\n\t\t\t\tNestedResult: [index + commands.length - 1, 0],\n\t\t\t};\n\t\t} else {\n\t\t\t// Path 2: Merge and Split — build a merged coin, split all intents at once.\n\n\t\t\tif (!typeState.has(type)) {\n\t\t\t\tconst intents = intentsForType;\n\n\t\t\t\t// Step 1: Build sources and merge\n\t\t\t\tconst sources: Argument[] = [];\n\n\t\t\t\tif (addressBalance >= totalRequired) {\n\t\t\t\t\t// AB sufficient — source entirely from address balance, no coins needed.\n\t\t\t\t\tusedAddressBalance.add(type);\n\n\t\t\t\t\tcommands.push(\n\t\t\t\t\t\tTransactionCommands.MoveCall({\n\t\t\t\t\t\t\ttarget: '0x2::coin::redeem_funds',\n\t\t\t\t\t\t\ttypeArguments: [coinType],\n\t\t\t\t\t\t\targuments: [\n\t\t\t\t\t\t\t\ttransactionData.addInput(\n\t\t\t\t\t\t\t\t\t'withdrawal',\n\t\t\t\t\t\t\t\t\tInputs.FundsWithdrawal({\n\t\t\t\t\t\t\t\t\t\treservation: {\n\t\t\t\t\t\t\t\t\t\t\t$kind: 'MaxAmountU64',\n\t\t\t\t\t\t\t\t\t\t\tMaxAmountU64: String(totalRequired),\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\ttypeArg: { $kind: 'Balance', Balance: coinType },\n\t\t\t\t\t\t\t\t\t\twithdrawFrom: { $kind: 'Sender', Sender: true },\n\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t\tsources.push({ $kind: 'Result', Result: index + commands.length - 1 });\n\t\t\t\t} else if (type === 'gas') {\n\t\t\t\t\tsources.push({ $kind: 'GasCoin', GasCoin: true });\n\t\t\t\t} else {\n\t\t\t\t\tconst coins = coinsByType.get(type)!;\n\t\t\t\t\tconst loadedCoinBalance = coins.reduce((sum, c) => sum + BigInt(c.balance), 0n);\n\t\t\t\t\tconst abNeeded =\n\t\t\t\t\t\ttotalRequired > loadedCoinBalance ? totalRequired - loadedCoinBalance : 0n;\n\n\t\t\t\t\texactBalanceByType.set(type, loadedCoinBalance + abNeeded === totalRequired);\n\n\t\t\t\t\tfor (const coin of coins) {\n\t\t\t\t\t\tsources.push(\n\t\t\t\t\t\t\ttransactionData.addInput(\n\t\t\t\t\t\t\t\t'object',\n\t\t\t\t\t\t\t\tInputs.ObjectRef({\n\t\t\t\t\t\t\t\t\tobjectId: coin.objectId,\n\t\t\t\t\t\t\t\t\tdigest: coin.digest,\n\t\t\t\t\t\t\t\t\tversion: coin.version,\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tif (abNeeded > 0n) {\n\t\t\t\t\t\tusedAddressBalance.add(type);\n\t\t\t\t\t\tcommands.push(\n\t\t\t\t\t\t\tTransactionCommands.MoveCall({\n\t\t\t\t\t\t\t\ttarget: '0x2::coin::redeem_funds',\n\t\t\t\t\t\t\t\ttypeArguments: [coinType],\n\t\t\t\t\t\t\t\targuments: [\n\t\t\t\t\t\t\t\t\ttransactionData.addInput(\n\t\t\t\t\t\t\t\t\t\t'withdrawal',\n\t\t\t\t\t\t\t\t\t\tInputs.FundsWithdrawal({\n\t\t\t\t\t\t\t\t\t\t\treservation: {\n\t\t\t\t\t\t\t\t\t\t\t\t$kind: 'MaxAmountU64',\n\t\t\t\t\t\t\t\t\t\t\t\tMaxAmountU64: String(abNeeded),\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\ttypeArg: { $kind: 'Balance', Balance: coinType },\n\t\t\t\t\t\t\t\t\t\t\twithdrawFrom: { $kind: 'Sender', Sender: true },\n\t\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t);\n\t\t\t\t\t\tsources.push({ $kind: 'Result', Result: index + commands.length - 1 });\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst baseCoin = sources[0];\n\t\t\t\tconst rest = sources.slice(1);\n\t\t\t\tfor (let i = 0; i < rest.length; i += 500) {\n\t\t\t\t\tcommands.push(TransactionCommands.MergeCoins(baseCoin, rest.slice(i, i + 500)));\n\t\t\t\t}\n\n\t\t\t\tmergedCoins.set(type, baseCoin);\n\n\t\t\t\t// Step 2: Combined SplitCoins for all intents of this type\n\t\t\t\tconst splitCmdIndex = index + commands.length;\n\t\t\t\tcommands.push(\n\t\t\t\t\tTransactionCommands.SplitCoins(\n\t\t\t\t\t\tbaseCoin,\n\t\t\t\t\t\tintents.map((i) =>\n\t\t\t\t\t\t\ttransactionData.addInput('pure', Inputs.Pure(bcs.u64().serialize(i.balance))),\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t);\n\n\t\t\t\t// Build per-intent results, adding into_balance conversions for balance intents\n\t\t\t\tconst results: Argument[] = [];\n\t\t\t\tfor (let i = 0; i < intents.length; i++) {\n\t\t\t\t\tconst splitResult: Argument = {\n\t\t\t\t\t\t$kind: 'NestedResult',\n\t\t\t\t\t\tNestedResult: [splitCmdIndex, i],\n\t\t\t\t\t};\n\n\t\t\t\t\tif (intents[i].outputKind === 'balance') {\n\t\t\t\t\t\tcommands.push(\n\t\t\t\t\t\t\tTransactionCommands.MoveCall({\n\t\t\t\t\t\t\t\ttarget: '0x2::coin::into_balance',\n\t\t\t\t\t\t\t\ttypeArguments: [coinType],\n\t\t\t\t\t\t\t\targuments: [splitResult],\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t);\n\t\t\t\t\t\tresults.push({\n\t\t\t\t\t\t\t$kind: 'NestedResult',\n\t\t\t\t\t\t\tNestedResult: [index + commands.length - 1, 0],\n\t\t\t\t\t\t});\n\t\t\t\t\t} else {\n\t\t\t\t\t\tresults.push(splitResult);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\ttypeState.set(type, { results, nextIntent: 0 });\n\t\t\t}\n\n\t\t\tconst state = typeState.get(type)!;\n\t\t\tintentResult = state.results[state.nextIntent++];\n\t\t}\n\n\t\ttransactionData.replaceCommand(\n\t\t\tindex,\n\t\t\tcommands,\n\t\t\tintentResult as { NestedResult: [number, number] },\n\t\t);\n\n\t\t// Advance past the replacement. When commands is empty (subsequent intents\n\t\t// of a combined split), the command was removed and the next command shifted\n\t\t// into this position — so we stay at the same index.\n\t\tindex += commands.length;\n\t}\n\n\t// Step 3: Remainder handling\n\tfor (const [type, mergedCoin] of mergedCoins) {\n\t\t// When gas type used GasCoin (not AB), leftover stays in the gas coin — no remainder needed.\n\t\tif (type === 'gas' && !usedAddressBalance.has(type)) continue;\n\n\t\tconst coinType = type === 'gas' ? SUI_TYPE : type;\n\t\tconst hasBalanceIntent = intentsByType.get(type)?.some((i) => i.outputKind === 'balance');\n\t\tconst sourcedFromAB = usedAddressBalance.has(type);\n\n\t\tif (hasBalanceIntent || sourcedFromAB) {\n\t\t\t// Sourced from AB or balance intents exist: send remainder back to sender's address balance.\n\t\t\t// coin::send_funds is gasless-eligible and handles zero amounts.\n\t\t\ttransactionData.commands.push(\n\t\t\t\tTransactionCommands.MoveCall({\n\t\t\t\t\ttarget: '0x2::coin::send_funds',\n\t\t\t\t\ttypeArguments: [coinType],\n\t\t\t\t\targuments: [\n\t\t\t\t\t\tmergedCoin,\n\t\t\t\t\t\ttransactionData.addInput(\n\t\t\t\t\t\t\t'pure',\n\t\t\t\t\t\t\tInputs.Pure(bcs.Address.serialize(transactionData.sender!)),\n\t\t\t\t\t\t),\n\t\t\t\t\t],\n\t\t\t\t}),\n\t\t\t);\n\t\t} else if (exactBalanceByType.get(type)) {\n\t\t\t// Coin-only with exact match: destroy the zero-value dust coin.\n\t\t\ttransactionData.commands.push(\n\t\t\t\tTransactionCommands.MoveCall({\n\t\t\t\t\ttarget: '0x2::coin::destroy_zero',\n\t\t\t\t\ttypeArguments: [coinType],\n\t\t\t\t\targuments: [mergedCoin],\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\t// Coin-only with surplus: merged coin stays with sender as an owned object\n\t}\n\n\treturn next();\n}\n\nasync function getCoinsAndBalanceOfType({\n\tcoinType,\n\tbalance,\n\tclient,\n\towner,\n\tusedIds,\n}: {\n\tcoinType: string;\n\tbalance: bigint;\n\tclient: ClientWithCoreApi;\n\towner: string;\n\tusedIds: Set<string>;\n}): Promise<{\n\tcoins: SuiClientTypes.Coin[];\n\tbalance: bigint;\n\taddressBalance: bigint;\n\tcoinBalance: bigint;\n}> {\n\tlet remainingBalance = balance;\n\tconst coins: SuiClientTypes.Coin[] = [];\n\tconst balanceRequest = client.core.getBalance({ owner, coinType }).then(({ balance }) => {\n\t\tremainingBalance -= BigInt(balance.addressBalance);\n\n\t\treturn balance;\n\t});\n\n\tconst [allCoins, balanceResponse] = await Promise.all([loadMoreCoins(), balanceRequest]);\n\n\tif (BigInt(balanceResponse.balance) < balance) {\n\t\tthrow new Error(\n\t\t\t`Insufficient balance of ${coinType} for owner ${owner}. Required: ${balance}, Available: ${\n\t\t\t\tbalance - remainingBalance\n\t\t\t}`,\n\t\t);\n\t}\n\n\treturn {\n\t\tcoins: allCoins,\n\t\tbalance: BigInt(balanceResponse.coinBalance),\n\t\taddressBalance: BigInt(balanceResponse.addressBalance),\n\t\tcoinBalance: BigInt(balanceResponse.coinBalance),\n\t};\n\n\tasync function loadMoreCoins(cursor: string | null = null): Promise<SuiClientTypes.Coin[]> {\n\t\tconst {\n\t\t\tobjects,\n\t\t\thasNextPage,\n\t\t\tcursor: nextCursor,\n\t\t} = await client.core.listCoins({\n\t\t\towner,\n\t\t\tcoinType,\n\t\t\tcursor,\n\t\t});\n\n\t\tawait balanceRequest;\n\n\t\t// Always load all coins from the page (except already-used ones).\n\t\t// This merges all available coins rather than leaving dust.\n\t\tfor (const coin of objects) {\n\t\t\tif (usedIds.has(coin.objectId)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tcoins.push(coin);\n\t\t\tremainingBalance -= BigInt(coin.balance);\n\t\t}\n\n\t\t// Only paginate if loaded coins + AB are still insufficient\n\t\tif (remainingBalance > 0n && hasNextPage) {\n\t\t\treturn loadMoreCoins(nextCursor);\n\t\t}\n\n\t\treturn coins;\n\t}\n}\n"],"mappings":"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"}
|
package/dist/version.mjs
CHANGED
package/dist/version.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.mjs","names":[],"sources":["../src/version.ts"],"sourcesContent":["// Copyright (c) Mysten Labs, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\n// This file is generated by genversion.mjs. Do not edit it directly.\n\nexport const PACKAGE_VERSION = '2.
|
|
1
|
+
{"version":3,"file":"version.mjs","names":[],"sources":["../src/version.ts"],"sourcesContent":["// Copyright (c) Mysten Labs, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\n// This file is generated by genversion.mjs. Do not edit it directly.\n\nexport const PACKAGE_VERSION = '2.20.1';\n"],"mappings":";AAKA,MAAa,kBAAkB"}
|
package/dist/zklogin/bcs.d.mts
CHANGED
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as _mysten_bcs1111 from "@mysten/bcs";
|
|
2
2
|
import { InferBcsInput } from "@mysten/bcs";
|
|
3
3
|
|
|
4
4
|
//#region src/zklogin/bcs.d.ts
|
|
5
|
-
declare const zkLoginSignature:
|
|
6
|
-
inputs:
|
|
7
|
-
proofPoints:
|
|
8
|
-
a:
|
|
5
|
+
declare const zkLoginSignature: _mysten_bcs1111.BcsStruct<{
|
|
6
|
+
inputs: _mysten_bcs1111.BcsStruct<{
|
|
7
|
+
proofPoints: _mysten_bcs1111.BcsStruct<{
|
|
8
|
+
a: _mysten_bcs1111.BcsType<string[], Iterable<string> & {
|
|
9
9
|
length: number;
|
|
10
10
|
}, string>;
|
|
11
|
-
b:
|
|
11
|
+
b: _mysten_bcs1111.BcsType<string[][], Iterable<Iterable<string> & {
|
|
12
12
|
length: number;
|
|
13
13
|
}> & {
|
|
14
14
|
length: number;
|
|
15
15
|
}, string>;
|
|
16
|
-
c:
|
|
16
|
+
c: _mysten_bcs1111.BcsType<string[], Iterable<string> & {
|
|
17
17
|
length: number;
|
|
18
18
|
}, string>;
|
|
19
19
|
}, string>;
|
|
20
|
-
issBase64Details:
|
|
21
|
-
value:
|
|
22
|
-
indexMod4:
|
|
20
|
+
issBase64Details: _mysten_bcs1111.BcsStruct<{
|
|
21
|
+
value: _mysten_bcs1111.BcsType<string, string, "string">;
|
|
22
|
+
indexMod4: _mysten_bcs1111.BcsType<number, number, "u8">;
|
|
23
23
|
}, string>;
|
|
24
|
-
headerBase64:
|
|
25
|
-
addressSeed:
|
|
24
|
+
headerBase64: _mysten_bcs1111.BcsType<string, string, "string">;
|
|
25
|
+
addressSeed: _mysten_bcs1111.BcsType<string, string, "string">;
|
|
26
26
|
}, string>;
|
|
27
|
-
maxEpoch:
|
|
28
|
-
userSignature:
|
|
27
|
+
maxEpoch: _mysten_bcs1111.BcsType<string, string | number | bigint, "u64">;
|
|
28
|
+
userSignature: _mysten_bcs1111.BcsType<Uint8Array<ArrayBufferLike>, Iterable<number>, "vector<u8>">;
|
|
29
29
|
}, string>;
|
|
30
30
|
type ZkLoginSignature = InferBcsInput<typeof zkLoginSignature>;
|
|
31
31
|
type ZkLoginSignatureInputs = ZkLoginSignature['inputs'];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bcs.d.mts","names":[],"sources":["../../src/zklogin/bcs.ts"],"mappings":";;;;cAMa,gBAAA,
|
|
1
|
+
{"version":3,"file":"bcs.d.mts","names":[],"sources":["../../src/zklogin/bcs.ts"],"mappings":";;;;cAMa,gBAAA,kBAAgB,SAAA;;;;;;;;;;;;;;;;;;;;;;;;;KAkBjB,gBAAA,GAAmB,aAAA,QAAqB,gBAAA;AAAA,KACxC,sBAAA,GAAyB,gBAAA"}
|
package/dist/zklogin/index.d.mts
CHANGED
|
@@ -3,7 +3,8 @@ import { getZkLoginSignature, parseZkLoginSignature } from "./signature.mjs";
|
|
|
3
3
|
import { genAddressSeed, getExtendedEphemeralPublicKey, hashASCIIStrToField, toBigEndianBytes, toPaddedBigEndianBytes } from "./utils.mjs";
|
|
4
4
|
import { ComputeZkLoginAddressOptions, computeZkLoginAddress, computeZkLoginAddressFromSeed, jwtToAddress } from "./address.mjs";
|
|
5
5
|
import { ZkLoginPublicIdentifier, toZkLoginPublicIdentifier } from "./publickey.mjs";
|
|
6
|
+
import { ZkLoginSigner, ZkLoginSignerOptions } from "./signer.mjs";
|
|
6
7
|
import { poseidonHash } from "./poseidon.mjs";
|
|
7
8
|
import { generateNonce, generateRandomness } from "./nonce.mjs";
|
|
8
9
|
import { decodeJwt } from "./jwt-utils.mjs";
|
|
9
|
-
export { type ComputeZkLoginAddressOptions, ZkLoginPublicIdentifier, type ZkLoginSignatureInputs, computeZkLoginAddress, computeZkLoginAddressFromSeed, decodeJwt, genAddressSeed, generateNonce, generateRandomness, getExtendedEphemeralPublicKey, getZkLoginSignature, hashASCIIStrToField, jwtToAddress, parseZkLoginSignature, poseidonHash, toBigEndianBytes, toPaddedBigEndianBytes, toZkLoginPublicIdentifier };
|
|
10
|
+
export { type ComputeZkLoginAddressOptions, ZkLoginPublicIdentifier, type ZkLoginSignatureInputs, ZkLoginSigner, type ZkLoginSignerOptions, computeZkLoginAddress, computeZkLoginAddressFromSeed, decodeJwt, genAddressSeed, generateNonce, generateRandomness, getExtendedEphemeralPublicKey, getZkLoginSignature, hashASCIIStrToField, jwtToAddress, parseZkLoginSignature, poseidonHash, toBigEndianBytes, toPaddedBigEndianBytes, toZkLoginPublicIdentifier };
|
package/dist/zklogin/index.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import { decodeJwt } from "./jwt-utils.mjs";
|
|
|
4
4
|
import { getZkLoginSignature, parseZkLoginSignature } from "./signature.mjs";
|
|
5
5
|
import { ZkLoginPublicIdentifier, toZkLoginPublicIdentifier } from "./publickey.mjs";
|
|
6
6
|
import { computeZkLoginAddress, computeZkLoginAddressFromSeed, jwtToAddress } from "./address.mjs";
|
|
7
|
+
import { ZkLoginSigner } from "./signer.mjs";
|
|
7
8
|
import { generateNonce, generateRandomness } from "./nonce.mjs";
|
|
8
9
|
|
|
9
|
-
export { ZkLoginPublicIdentifier, computeZkLoginAddress, computeZkLoginAddressFromSeed, decodeJwt, genAddressSeed, generateNonce, generateRandomness, getExtendedEphemeralPublicKey, getZkLoginSignature, hashASCIIStrToField, jwtToAddress, parseZkLoginSignature, poseidonHash, toBigEndianBytes, toPaddedBigEndianBytes, toZkLoginPublicIdentifier };
|
|
10
|
+
export { ZkLoginPublicIdentifier, ZkLoginSigner, computeZkLoginAddress, computeZkLoginAddressFromSeed, decodeJwt, genAddressSeed, generateNonce, generateRandomness, getExtendedEphemeralPublicKey, getZkLoginSignature, hashASCIIStrToField, jwtToAddress, parseZkLoginSignature, poseidonHash, toBigEndianBytes, toPaddedBigEndianBytes, toZkLoginPublicIdentifier };
|
|
@@ -17,6 +17,10 @@ declare class ZkLoginPublicIdentifier extends PublicKey {
|
|
|
17
17
|
}?: {
|
|
18
18
|
client?: ClientWithCoreApi;
|
|
19
19
|
});
|
|
20
|
+
/**
|
|
21
|
+
* Whether this identifier resolves to the deprecated legacy address derivation.
|
|
22
|
+
*/
|
|
23
|
+
get legacyAddress(): boolean;
|
|
20
24
|
static fromBytes(bytes: Uint8Array, {
|
|
21
25
|
client,
|
|
22
26
|
address,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"publickey.d.mts","names":[],"sources":["../../src/zklogin/publickey.ts"],"mappings":";;;;;;AAoBA;;cAAa,uBAAA,SAAgC,SAAA;EAAA;EASJ;;;;cAA5B,KAAA,EAAO,iBAAA;IAAqB;EAAA;IAAY,MAAA,GAAS,iBAAA;EAAA;EAAA,
|
|
1
|
+
{"version":3,"file":"publickey.d.mts","names":[],"sources":["../../src/zklogin/publickey.ts"],"mappings":";;;;;;AAoBA;;cAAa,uBAAA,SAAgC,SAAA;EAAA;EASJ;;;;cAA5B,KAAA,EAAO,iBAAA;IAAqB;EAAA;IAAY,MAAA,GAAS,iBAAA;EAAA;EAgCqB;;;EAAA,IAV9E,aAAA,CAAA;EAAA,OAIG,SAAA,CACN,KAAA,EAAO,UAAA;IAEN,MAAA;IACA,OAAA;IACA;EAAA;IACI,MAAA,GAAS,iBAAA;IAAmB,OAAA;IAAkB,aAAA;EAAA,IAA8B,uBAAA;EAAA,OAmC3E,SAAA,CAAU,OAAA,UAAiB,KAAA,EAAO,sBAAA,GAAsB,uBAAA;EAwET;;;EA9C7C,MAAA,CAAO,SAAA,EAAW,uBAAA;EAIlB,YAAA,CAAA;EA1GmC;;;EA+H5C,UAAA,CAAA,GAAc,UAAA,CAAW,WAAA;;;;EAOzB,IAAA,CAAA;EA7HwC;;;EAoIlC,MAAA,CAAO,QAAA,EAAU,UAAA,EAAY,UAAA,EAAY,UAAA,YAAsB,OAAA;EA9GjE;;;EAqHJ,qBAAA,CAAsB,OAAA,EAAS,UAAA,EAAY,SAAA,EAAW,UAAA,YAAsB,OAAA;EA9G1E;;;EA8HF,iBAAA,CAAkB,WAAA,EAAa,UAAA,EAAY,SAAA,EAAW,UAAA,YAAsB,OAAA;EA5H1E;;;EA2IO,aAAA,CAAc,OAAA;AAAA;AAAA,iBAMR,yBAAA,CACf,WAAA,UACA,GAAA,UACA,OAAA;EAAW,MAAA,GAAS,iBAAA;EAAmB,aAAA;AAAA,IACrC,uBAAA"}
|
|
@@ -29,6 +29,12 @@ var ZkLoginPublicIdentifier = class ZkLoginPublicIdentifier extends PublicKey {
|
|
|
29
29
|
this.#legacyAddress = this.#data.length !== this.#data[0] + 1 + 32;
|
|
30
30
|
if (this.#legacyAddress) this.#data = normalizeZkLoginPublicKeyBytes(this.#data, false);
|
|
31
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Whether this identifier resolves to the deprecated legacy address derivation.
|
|
34
|
+
*/
|
|
35
|
+
get legacyAddress() {
|
|
36
|
+
return this.#legacyAddress;
|
|
37
|
+
}
|
|
32
38
|
static fromBytes(bytes, { client, address, legacyAddress } = {}) {
|
|
33
39
|
let publicKey;
|
|
34
40
|
if (legacyAddress === true) publicKey = new ZkLoginPublicIdentifier(normalizeZkLoginPublicKeyBytes(bytes, true), { client });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"publickey.mjs","names":["#client","#data","#legacyAddress","#toLegacyAddress"],"sources":["../../src/zklogin/publickey.ts"],"sourcesContent":["// Copyright (c) Mysten Labs, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\nimport { fromBase64, toBase64, toHex } from '@mysten/bcs';\nimport { blake2b } from '@noble/hashes/blake2.js';\nimport { bytesToHex } from '@noble/hashes/utils.js';\n\nimport { PublicKey } from '../cryptography/publickey.js';\nimport type { PublicKeyInitData } from '../cryptography/publickey.js';\nimport { SIGNATURE_SCHEME_TO_FLAG } from '../cryptography/signature-scheme.js';\nimport { normalizeSuiAddress, SUI_ADDRESS_LENGTH } from '../utils/sui-types.js';\nimport type { ZkLoginSignatureInputs } from './bcs.js';\nimport { extractClaimValue } from './jwt-utils.js';\nimport { parseZkLoginSignature } from './signature.js';\nimport { normalizeZkLoginIssuer, toBigEndianBytes, toPaddedBigEndianBytes } from './utils.js';\nimport type { ClientWithCoreApi } from '../client/core.js';\n\n/**\n * A zkLogin public identifier\n */\nexport class ZkLoginPublicIdentifier extends PublicKey {\n\t#data: Uint8Array<ArrayBuffer>;\n\t#client?: ClientWithCoreApi;\n\t#legacyAddress: boolean;\n\n\t/**\n\t * Create a new ZkLoginPublicIdentifier object\n\t * @param value zkLogin public identifier as buffer or base-64 encoded string\n\t */\n\tconstructor(value: PublicKeyInitData, { client }: { client?: ClientWithCoreApi } = {}) {\n\t\tsuper();\n\n\t\tthis.#client = client;\n\n\t\tif (typeof value === 'string') {\n\t\t\tthis.#data = fromBase64(value);\n\t\t} else if (value instanceof Uint8Array) {\n\t\t\tthis.#data = value as Uint8Array<ArrayBuffer>;\n\t\t} else {\n\t\t\tthis.#data = Uint8Array.from(value);\n\t\t}\n\t\tthis.#legacyAddress = this.#data.length !== this.#data[0] + 1 + 32;\n\n\t\tif (this.#legacyAddress) {\n\t\t\tthis.#data = normalizeZkLoginPublicKeyBytes(this.#data, false);\n\t\t}\n\t}\n\n\tstatic fromBytes(\n\t\tbytes: Uint8Array,\n\t\t{\n\t\t\tclient,\n\t\t\taddress,\n\t\t\tlegacyAddress,\n\t\t}: { client?: ClientWithCoreApi; address?: string; legacyAddress?: boolean } = {},\n\t) {\n\t\tlet publicKey: ZkLoginPublicIdentifier;\n\n\t\tif (legacyAddress === true) {\n\t\t\tpublicKey = new ZkLoginPublicIdentifier(normalizeZkLoginPublicKeyBytes(bytes, true), {\n\t\t\t\tclient,\n\t\t\t});\n\t\t} else if (legacyAddress === false) {\n\t\t\tpublicKey = new ZkLoginPublicIdentifier(normalizeZkLoginPublicKeyBytes(bytes, false), {\n\t\t\t\tclient,\n\t\t\t});\n\t\t} else if (address) {\n\t\t\tpublicKey = new ZkLoginPublicIdentifier(normalizeZkLoginPublicKeyBytes(bytes, false), {\n\t\t\t\tclient,\n\t\t\t});\n\n\t\t\tif (publicKey.toSuiAddress() !== address) {\n\t\t\t\tpublicKey = new ZkLoginPublicIdentifier(normalizeZkLoginPublicKeyBytes(bytes, true), {\n\t\t\t\t\tclient,\n\t\t\t\t});\n\t\t\t}\n\t\t} else {\n\t\t\tpublicKey = new ZkLoginPublicIdentifier(bytes, {\n\t\t\t\tclient,\n\t\t\t});\n\t\t}\n\n\t\tif (address && publicKey.toSuiAddress() !== address) {\n\t\t\tthrow new Error('Public key bytes do not match the provided address');\n\t\t}\n\n\t\treturn publicKey;\n\t}\n\n\tstatic fromProof(address: string, proof: ZkLoginSignatureInputs) {\n\t\tconst { issBase64Details, addressSeed } = proof;\n\t\tconst iss = extractClaimValue<string>(issBase64Details, 'iss');\n\n\t\tconst legacyPublicKey = toZkLoginPublicIdentifier(BigInt(addressSeed), iss, {\n\t\t\tlegacyAddress: true,\n\t\t});\n\n\t\tif (legacyPublicKey.toSuiAddress() === address) {\n\t\t\treturn legacyPublicKey;\n\t\t}\n\n\t\tconst publicKey = toZkLoginPublicIdentifier(BigInt(addressSeed), iss, {\n\t\t\tlegacyAddress: false,\n\t\t});\n\n\t\tif (publicKey.toSuiAddress() !== address) {\n\t\t\tthrow new Error('Proof does not match address');\n\t\t}\n\n\t\treturn publicKey;\n\t}\n\n\t/**\n\t * Checks if two zkLogin public identifiers are equal\n\t */\n\toverride equals(publicKey: ZkLoginPublicIdentifier): boolean {\n\t\treturn super.equals(publicKey);\n\t}\n\n\toverride toSuiAddress(): string {\n\t\tif (this.#legacyAddress) {\n\t\t\treturn this.#toLegacyAddress();\n\t\t}\n\n\t\treturn super.toSuiAddress();\n\t}\n\n\t#toLegacyAddress() {\n\t\tconst legacyBytes = normalizeZkLoginPublicKeyBytes(this.#data, true);\n\t\tconst addressBytes = new Uint8Array(legacyBytes.length + 1);\n\t\taddressBytes[0] = this.flag();\n\t\taddressBytes.set(legacyBytes, 1);\n\t\treturn normalizeSuiAddress(\n\t\t\tbytesToHex(blake2b(addressBytes, { dkLen: 32 })).slice(0, SUI_ADDRESS_LENGTH * 2),\n\t\t);\n\t}\n\n\t/**\n\t * Return the byte array representation of the zkLogin public identifier\n\t */\n\ttoRawBytes(): Uint8Array<ArrayBuffer> {\n\t\treturn this.#data;\n\t}\n\n\t/**\n\t * Return the Sui address associated with this ZkLogin public identifier\n\t */\n\tflag(): number {\n\t\treturn SIGNATURE_SCHEME_TO_FLAG['ZkLogin'];\n\t}\n\n\t/**\n\t * Verifies that the signature is valid for for the provided message\n\t */\n\tasync verify(_message: Uint8Array, _signature: Uint8Array | string): Promise<boolean> {\n\t\tthrow Error('does not support');\n\t}\n\n\t/**\n\t * Verifies that the signature is valid for for the provided PersonalMessage\n\t */\n\tverifyPersonalMessage(message: Uint8Array, signature: Uint8Array | string): Promise<boolean> {\n\t\tconst parsedSignature = parseSerializedZkLoginSignature(signature);\n\t\tconst address = new ZkLoginPublicIdentifier(parsedSignature.publicKey).toSuiAddress();\n\n\t\treturn graphqlVerifyZkLoginSignature({\n\t\t\taddress: address,\n\t\t\tbytes: toBase64(message),\n\t\t\tsignature: parsedSignature.serializedSignature,\n\t\t\tintentScope: 'PersonalMessage',\n\t\t\tclient: this.#client,\n\t\t});\n\t}\n\n\t/**\n\t * Verifies that the signature is valid for for the provided Transaction\n\t */\n\tverifyTransaction(transaction: Uint8Array, signature: Uint8Array | string): Promise<boolean> {\n\t\tconst parsedSignature = parseSerializedZkLoginSignature(signature);\n\t\tconst address = new ZkLoginPublicIdentifier(parsedSignature.publicKey).toSuiAddress();\n\t\treturn graphqlVerifyZkLoginSignature({\n\t\t\taddress: address,\n\t\t\tbytes: toBase64(transaction),\n\t\t\tsignature: parsedSignature.serializedSignature,\n\t\t\tintentScope: 'TransactionData',\n\t\t\tclient: this.#client,\n\t\t});\n\t}\n\n\t/**\n\t * Verifies that the public key is associated with the provided address\n\t */\n\toverride verifyAddress(address: string): boolean {\n\t\treturn address === super.toSuiAddress() || address === this.#toLegacyAddress();\n\t}\n}\n\n// Derive the public identifier for zklogin based on address seed and iss.\nexport function toZkLoginPublicIdentifier(\n\taddressSeed: bigint,\n\tiss: string,\n\toptions: { client?: ClientWithCoreApi; legacyAddress: boolean },\n): ZkLoginPublicIdentifier {\n\tif (options.legacyAddress === undefined) {\n\t\tthrow new Error('legacyAddress parameter must be specified');\n\t}\n\t// Consists of iss_bytes_len || iss_bytes || padded_32_byte_address_seed.\n\tconst addressSeedBytesBigEndian = options.legacyAddress\n\t\t? toBigEndianBytes(addressSeed, 32)\n\t\t: toPaddedBigEndianBytes(addressSeed, 32);\n\n\tconst issBytes = new TextEncoder().encode(normalizeZkLoginIssuer(iss));\n\tconst tmp = new Uint8Array(1 + issBytes.length + addressSeedBytesBigEndian.length);\n\ttmp.set([issBytes.length], 0);\n\ttmp.set(issBytes, 1);\n\ttmp.set(addressSeedBytesBigEndian, 1 + issBytes.length);\n\treturn new ZkLoginPublicIdentifier(tmp, options);\n}\n\nfunction normalizeZkLoginPublicKeyBytes(bytes: Uint8Array, legacyAddress: boolean) {\n\tconst issByteLength = bytes[0] + 1;\n\tconst addressSeed = BigInt(`0x${toHex(bytes.slice(issByteLength))}`);\n\tconst seedBytes = legacyAddress\n\t\t? toBigEndianBytes(addressSeed, 32)\n\t\t: toPaddedBigEndianBytes(addressSeed, 32);\n\tconst data = new Uint8Array(issByteLength + seedBytes.length);\n\tdata.set(bytes.slice(0, issByteLength), 0);\n\tdata.set(seedBytes, issByteLength);\n\treturn data;\n}\n\nasync function graphqlVerifyZkLoginSignature({\n\taddress,\n\tbytes,\n\tsignature,\n\tintentScope,\n\tclient,\n}: {\n\taddress: string;\n\tbytes: string;\n\tsignature: string;\n\tintentScope: 'PersonalMessage' | 'TransactionData';\n\tclient?: ClientWithCoreApi;\n}) {\n\tif (!client) {\n\t\tthrow new Error(\n\t\t\t'A Sui Client (GRPC, GraphQL, or JSON RPC) is required to verify zkLogin signatures',\n\t\t);\n\t}\n\tconst resp = await client.core.verifyZkLoginSignature({\n\t\tbytes,\n\t\tsignature,\n\t\tintentScope,\n\t\taddress: address,\n\t});\n\n\treturn resp.success === true && resp.errors.length === 0;\n}\n\nexport function parseSerializedZkLoginSignature(signature: Uint8Array | string) {\n\tconst bytes = typeof signature === 'string' ? fromBase64(signature) : signature;\n\n\tif (bytes[0] !== SIGNATURE_SCHEME_TO_FLAG.ZkLogin) {\n\t\tthrow new Error('Invalid signature scheme');\n\t}\n\n\tconst signatureBytes = bytes.slice(1);\n\tconst { inputs, maxEpoch, userSignature } = parseZkLoginSignature(signatureBytes);\n\tconst { issBase64Details, addressSeed } = inputs;\n\tconst iss = extractClaimValue<string>(issBase64Details, 'iss');\n\tconst publicIdentifier = toZkLoginPublicIdentifier(BigInt(addressSeed), iss, {\n\t\tlegacyAddress: false,\n\t});\n\treturn {\n\t\tserializedSignature: toBase64(bytes),\n\t\tsignatureScheme: 'ZkLogin' as const,\n\t\tzkLogin: {\n\t\t\tinputs,\n\t\t\tmaxEpoch,\n\t\t\tuserSignature,\n\t\t\tiss,\n\t\t\taddressSeed: BigInt(addressSeed),\n\t\t},\n\t\tsignature: bytes,\n\t\tpublicKey: publicIdentifier.toRawBytes(),\n\t};\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"publickey.mjs","names":["#client","#data","#legacyAddress","#toLegacyAddress"],"sources":["../../src/zklogin/publickey.ts"],"sourcesContent":["// Copyright (c) Mysten Labs, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\nimport { fromBase64, toBase64, toHex } from '@mysten/bcs';\nimport { blake2b } from '@noble/hashes/blake2.js';\nimport { bytesToHex } from '@noble/hashes/utils.js';\n\nimport { PublicKey } from '../cryptography/publickey.js';\nimport type { PublicKeyInitData } from '../cryptography/publickey.js';\nimport { SIGNATURE_SCHEME_TO_FLAG } from '../cryptography/signature-scheme.js';\nimport { normalizeSuiAddress, SUI_ADDRESS_LENGTH } from '../utils/sui-types.js';\nimport type { ZkLoginSignatureInputs } from './bcs.js';\nimport { extractClaimValue } from './jwt-utils.js';\nimport { parseZkLoginSignature } from './signature.js';\nimport { normalizeZkLoginIssuer, toBigEndianBytes, toPaddedBigEndianBytes } from './utils.js';\nimport type { ClientWithCoreApi } from '../client/core.js';\n\n/**\n * A zkLogin public identifier\n */\nexport class ZkLoginPublicIdentifier extends PublicKey {\n\t#data: Uint8Array<ArrayBuffer>;\n\t#client?: ClientWithCoreApi;\n\t#legacyAddress: boolean;\n\n\t/**\n\t * Create a new ZkLoginPublicIdentifier object\n\t * @param value zkLogin public identifier as buffer or base-64 encoded string\n\t */\n\tconstructor(value: PublicKeyInitData, { client }: { client?: ClientWithCoreApi } = {}) {\n\t\tsuper();\n\n\t\tthis.#client = client;\n\n\t\tif (typeof value === 'string') {\n\t\t\tthis.#data = fromBase64(value);\n\t\t} else if (value instanceof Uint8Array) {\n\t\t\tthis.#data = value as Uint8Array<ArrayBuffer>;\n\t\t} else {\n\t\t\tthis.#data = Uint8Array.from(value);\n\t\t}\n\t\tthis.#legacyAddress = this.#data.length !== this.#data[0] + 1 + 32;\n\n\t\tif (this.#legacyAddress) {\n\t\t\tthis.#data = normalizeZkLoginPublicKeyBytes(this.#data, false);\n\t\t}\n\t}\n\n\t/**\n\t * Whether this identifier resolves to the deprecated legacy address derivation.\n\t */\n\tget legacyAddress(): boolean {\n\t\treturn this.#legacyAddress;\n\t}\n\n\tstatic fromBytes(\n\t\tbytes: Uint8Array,\n\t\t{\n\t\t\tclient,\n\t\t\taddress,\n\t\t\tlegacyAddress,\n\t\t}: { client?: ClientWithCoreApi; address?: string; legacyAddress?: boolean } = {},\n\t) {\n\t\tlet publicKey: ZkLoginPublicIdentifier;\n\n\t\tif (legacyAddress === true) {\n\t\t\tpublicKey = new ZkLoginPublicIdentifier(normalizeZkLoginPublicKeyBytes(bytes, true), {\n\t\t\t\tclient,\n\t\t\t});\n\t\t} else if (legacyAddress === false) {\n\t\t\tpublicKey = new ZkLoginPublicIdentifier(normalizeZkLoginPublicKeyBytes(bytes, false), {\n\t\t\t\tclient,\n\t\t\t});\n\t\t} else if (address) {\n\t\t\tpublicKey = new ZkLoginPublicIdentifier(normalizeZkLoginPublicKeyBytes(bytes, false), {\n\t\t\t\tclient,\n\t\t\t});\n\n\t\t\tif (publicKey.toSuiAddress() !== address) {\n\t\t\t\tpublicKey = new ZkLoginPublicIdentifier(normalizeZkLoginPublicKeyBytes(bytes, true), {\n\t\t\t\t\tclient,\n\t\t\t\t});\n\t\t\t}\n\t\t} else {\n\t\t\tpublicKey = new ZkLoginPublicIdentifier(bytes, {\n\t\t\t\tclient,\n\t\t\t});\n\t\t}\n\n\t\tif (address && publicKey.toSuiAddress() !== address) {\n\t\t\tthrow new Error('Public key bytes do not match the provided address');\n\t\t}\n\n\t\treturn publicKey;\n\t}\n\n\tstatic fromProof(address: string, proof: ZkLoginSignatureInputs) {\n\t\tconst { issBase64Details, addressSeed } = proof;\n\t\tconst iss = extractClaimValue<string>(issBase64Details, 'iss');\n\n\t\tconst legacyPublicKey = toZkLoginPublicIdentifier(BigInt(addressSeed), iss, {\n\t\t\tlegacyAddress: true,\n\t\t});\n\n\t\tif (legacyPublicKey.toSuiAddress() === address) {\n\t\t\treturn legacyPublicKey;\n\t\t}\n\n\t\tconst publicKey = toZkLoginPublicIdentifier(BigInt(addressSeed), iss, {\n\t\t\tlegacyAddress: false,\n\t\t});\n\n\t\tif (publicKey.toSuiAddress() !== address) {\n\t\t\tthrow new Error('Proof does not match address');\n\t\t}\n\n\t\treturn publicKey;\n\t}\n\n\t/**\n\t * Checks if two zkLogin public identifiers are equal\n\t */\n\toverride equals(publicKey: ZkLoginPublicIdentifier): boolean {\n\t\treturn super.equals(publicKey);\n\t}\n\n\toverride toSuiAddress(): string {\n\t\tif (this.#legacyAddress) {\n\t\t\treturn this.#toLegacyAddress();\n\t\t}\n\n\t\treturn super.toSuiAddress();\n\t}\n\n\t#toLegacyAddress() {\n\t\tconst legacyBytes = normalizeZkLoginPublicKeyBytes(this.#data, true);\n\t\tconst addressBytes = new Uint8Array(legacyBytes.length + 1);\n\t\taddressBytes[0] = this.flag();\n\t\taddressBytes.set(legacyBytes, 1);\n\t\treturn normalizeSuiAddress(\n\t\t\tbytesToHex(blake2b(addressBytes, { dkLen: 32 })).slice(0, SUI_ADDRESS_LENGTH * 2),\n\t\t);\n\t}\n\n\t/**\n\t * Return the byte array representation of the zkLogin public identifier\n\t */\n\ttoRawBytes(): Uint8Array<ArrayBuffer> {\n\t\treturn this.#data;\n\t}\n\n\t/**\n\t * Return the Sui address associated with this ZkLogin public identifier\n\t */\n\tflag(): number {\n\t\treturn SIGNATURE_SCHEME_TO_FLAG['ZkLogin'];\n\t}\n\n\t/**\n\t * Verifies that the signature is valid for for the provided message\n\t */\n\tasync verify(_message: Uint8Array, _signature: Uint8Array | string): Promise<boolean> {\n\t\tthrow Error('does not support');\n\t}\n\n\t/**\n\t * Verifies that the signature is valid for for the provided PersonalMessage\n\t */\n\tverifyPersonalMessage(message: Uint8Array, signature: Uint8Array | string): Promise<boolean> {\n\t\tconst parsedSignature = parseSerializedZkLoginSignature(signature);\n\t\tconst address = new ZkLoginPublicIdentifier(parsedSignature.publicKey).toSuiAddress();\n\n\t\treturn graphqlVerifyZkLoginSignature({\n\t\t\taddress: address,\n\t\t\tbytes: toBase64(message),\n\t\t\tsignature: parsedSignature.serializedSignature,\n\t\t\tintentScope: 'PersonalMessage',\n\t\t\tclient: this.#client,\n\t\t});\n\t}\n\n\t/**\n\t * Verifies that the signature is valid for for the provided Transaction\n\t */\n\tverifyTransaction(transaction: Uint8Array, signature: Uint8Array | string): Promise<boolean> {\n\t\tconst parsedSignature = parseSerializedZkLoginSignature(signature);\n\t\tconst address = new ZkLoginPublicIdentifier(parsedSignature.publicKey).toSuiAddress();\n\t\treturn graphqlVerifyZkLoginSignature({\n\t\t\taddress: address,\n\t\t\tbytes: toBase64(transaction),\n\t\t\tsignature: parsedSignature.serializedSignature,\n\t\t\tintentScope: 'TransactionData',\n\t\t\tclient: this.#client,\n\t\t});\n\t}\n\n\t/**\n\t * Verifies that the public key is associated with the provided address\n\t */\n\toverride verifyAddress(address: string): boolean {\n\t\treturn address === super.toSuiAddress() || address === this.#toLegacyAddress();\n\t}\n}\n\n// Derive the public identifier for zklogin based on address seed and iss.\nexport function toZkLoginPublicIdentifier(\n\taddressSeed: bigint,\n\tiss: string,\n\toptions: { client?: ClientWithCoreApi; legacyAddress: boolean },\n): ZkLoginPublicIdentifier {\n\tif (options.legacyAddress === undefined) {\n\t\tthrow new Error('legacyAddress parameter must be specified');\n\t}\n\t// Consists of iss_bytes_len || iss_bytes || padded_32_byte_address_seed.\n\tconst addressSeedBytesBigEndian = options.legacyAddress\n\t\t? toBigEndianBytes(addressSeed, 32)\n\t\t: toPaddedBigEndianBytes(addressSeed, 32);\n\n\tconst issBytes = new TextEncoder().encode(normalizeZkLoginIssuer(iss));\n\tconst tmp = new Uint8Array(1 + issBytes.length + addressSeedBytesBigEndian.length);\n\ttmp.set([issBytes.length], 0);\n\ttmp.set(issBytes, 1);\n\ttmp.set(addressSeedBytesBigEndian, 1 + issBytes.length);\n\treturn new ZkLoginPublicIdentifier(tmp, options);\n}\n\nfunction normalizeZkLoginPublicKeyBytes(bytes: Uint8Array, legacyAddress: boolean) {\n\tconst issByteLength = bytes[0] + 1;\n\tconst addressSeed = BigInt(`0x${toHex(bytes.slice(issByteLength))}`);\n\tconst seedBytes = legacyAddress\n\t\t? toBigEndianBytes(addressSeed, 32)\n\t\t: toPaddedBigEndianBytes(addressSeed, 32);\n\tconst data = new Uint8Array(issByteLength + seedBytes.length);\n\tdata.set(bytes.slice(0, issByteLength), 0);\n\tdata.set(seedBytes, issByteLength);\n\treturn data;\n}\n\nasync function graphqlVerifyZkLoginSignature({\n\taddress,\n\tbytes,\n\tsignature,\n\tintentScope,\n\tclient,\n}: {\n\taddress: string;\n\tbytes: string;\n\tsignature: string;\n\tintentScope: 'PersonalMessage' | 'TransactionData';\n\tclient?: ClientWithCoreApi;\n}) {\n\tif (!client) {\n\t\tthrow new Error(\n\t\t\t'A Sui Client (GRPC, GraphQL, or JSON RPC) is required to verify zkLogin signatures',\n\t\t);\n\t}\n\tconst resp = await client.core.verifyZkLoginSignature({\n\t\tbytes,\n\t\tsignature,\n\t\tintentScope,\n\t\taddress: address,\n\t});\n\n\treturn resp.success === true && resp.errors.length === 0;\n}\n\nexport function parseSerializedZkLoginSignature(signature: Uint8Array | string) {\n\tconst bytes = typeof signature === 'string' ? fromBase64(signature) : signature;\n\n\tif (bytes[0] !== SIGNATURE_SCHEME_TO_FLAG.ZkLogin) {\n\t\tthrow new Error('Invalid signature scheme');\n\t}\n\n\tconst signatureBytes = bytes.slice(1);\n\tconst { inputs, maxEpoch, userSignature } = parseZkLoginSignature(signatureBytes);\n\tconst { issBase64Details, addressSeed } = inputs;\n\tconst iss = extractClaimValue<string>(issBase64Details, 'iss');\n\tconst publicIdentifier = toZkLoginPublicIdentifier(BigInt(addressSeed), iss, {\n\t\tlegacyAddress: false,\n\t});\n\treturn {\n\t\tserializedSignature: toBase64(bytes),\n\t\tsignatureScheme: 'ZkLogin' as const,\n\t\tzkLogin: {\n\t\t\tinputs,\n\t\t\tmaxEpoch,\n\t\t\tuserSignature,\n\t\t\tiss,\n\t\t\taddressSeed: BigInt(addressSeed),\n\t\t},\n\t\tsignature: bytes,\n\t\tpublicKey: publicIdentifier.toRawBytes(),\n\t};\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { IntentScope } from "../cryptography/intent.mjs";
|
|
2
|
+
import { SignatureScheme } from "../cryptography/signature-scheme.mjs";
|
|
3
|
+
import { SignatureWithBytes, Signer } from "../cryptography/keypair.mjs";
|
|
4
|
+
import { ClientWithCoreApi } from "../client/core.mjs";
|
|
5
|
+
import { ZkLoginSignatureInputs } from "./bcs.mjs";
|
|
6
|
+
import { ZkLoginPublicIdentifier } from "./publickey.mjs";
|
|
7
|
+
|
|
8
|
+
//#region src/zklogin/signer.d.ts
|
|
9
|
+
interface ZkLoginSignerOptions {
|
|
10
|
+
/** The ephemeral signer whose signature is wrapped by the zkLogin proof. */
|
|
11
|
+
ephemeralSigner: Signer;
|
|
12
|
+
/** The maxEpoch the proof was generated for. */
|
|
13
|
+
maxEpoch: number;
|
|
14
|
+
/** The zkLogin proof inputs, including the `addressSeed`. */
|
|
15
|
+
inputs: ZkLoginSignatureInputs;
|
|
16
|
+
/**
|
|
17
|
+
* Whether this account uses the deprecated legacy address derivation. The zkLogin address is
|
|
18
|
+
* derived from the proof inputs; this flag disambiguates legacy vs. current derivation, matching
|
|
19
|
+
* the rest of the zkLogin address API (e.g. `jwtToAddress`, `toZkLoginPublicIdentifier`).
|
|
20
|
+
*
|
|
21
|
+
* A wrong flag silently yields a signer for the wrong address; pass `address` to guard against it.
|
|
22
|
+
*/
|
|
23
|
+
legacyAddress: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* The expected zkLogin address. When provided, the address derived from `inputs` + `legacyAddress`
|
|
26
|
+
* is validated against it and the constructor throws on mismatch — guarding against a wrong
|
|
27
|
+
* `legacyAddress` flag producing a signer for the wrong address.
|
|
28
|
+
*/
|
|
29
|
+
address?: string;
|
|
30
|
+
/** Optional client, threaded into the derived public key so it can verify signatures. */
|
|
31
|
+
client?: ClientWithCoreApi;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* A transport- and provider-agnostic zkLogin signer.
|
|
35
|
+
*
|
|
36
|
+
* It wraps any ephemeral {@link Signer} and, for every signing operation, transforms the ephemeral
|
|
37
|
+
* signature into a zkLogin signature using the supplied proof inputs and `maxEpoch`. Because every
|
|
38
|
+
* signing method on {@link Signer} funnels through `signWithIntent`, overriding that single method is
|
|
39
|
+
* enough to cover `signTransaction`, `signPersonalMessage`, and `signAndExecuteTransaction`.
|
|
40
|
+
*/
|
|
41
|
+
declare class ZkLoginSigner extends Signer {
|
|
42
|
+
#private;
|
|
43
|
+
constructor(options: ZkLoginSignerOptions);
|
|
44
|
+
/**
|
|
45
|
+
* The single extension point: the ephemeral signature is produced with the requested intent and
|
|
46
|
+
* then wrapped in a zkLogin signature.
|
|
47
|
+
*/
|
|
48
|
+
signWithIntent(bytes: Uint8Array, intent: IntentScope): Promise<SignatureWithBytes>;
|
|
49
|
+
sign(_data: Uint8Array): never;
|
|
50
|
+
getKeyScheme(): SignatureScheme;
|
|
51
|
+
getPublicKey(): ZkLoginPublicIdentifier;
|
|
52
|
+
}
|
|
53
|
+
//#endregion
|
|
54
|
+
export { ZkLoginSigner, ZkLoginSignerOptions };
|
|
55
|
+
//# sourceMappingURL=signer.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signer.d.mts","names":[],"sources":["../../src/zklogin/signer.ts"],"mappings":";;;;;;;;UAaiB,oBAAA;EAAA;EAEhB,eAAA,EAAiB,MAAA;;EAEjB,QAAA;EAEQ;EAAR,MAAA,EAAQ,sBAAA;EAgBkB;;;;;;;EAR1B,aAAA;EAMA;;;;;EAAA,OAAA;EAa0B;EAX1B,MAAA,GAAS,iBAAA;AAAA;;;;;;;;;cAWG,aAAA,SAAsB,MAAA;EAAA;cAQtB,OAAA,EAAS,oBAAA;;;;;EAoCN,cAAA,CACd,KAAA,EAAO,UAAA,EACP,MAAA,EAAQ,WAAA,GACN,OAAA,CAAQ,kBAAA;EAcX,IAAA,CAAK,KAAA,EAAO,UAAA;EAMZ,YAAA,CAAA,GAAgB,eAAA;EAIhB,YAAA,CAAA,GAAgB,uBAAA;AAAA"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { extractClaimValue } from "./jwt-utils.mjs";
|
|
2
|
+
import { getZkLoginSignature } from "./signature.mjs";
|
|
3
|
+
import { toZkLoginPublicIdentifier } from "./publickey.mjs";
|
|
4
|
+
import { Signer } from "../cryptography/keypair.mjs";
|
|
5
|
+
|
|
6
|
+
//#region src/zklogin/signer.ts
|
|
7
|
+
/**
|
|
8
|
+
* A transport- and provider-agnostic zkLogin signer.
|
|
9
|
+
*
|
|
10
|
+
* It wraps any ephemeral {@link Signer} and, for every signing operation, transforms the ephemeral
|
|
11
|
+
* signature into a zkLogin signature using the supplied proof inputs and `maxEpoch`. Because every
|
|
12
|
+
* signing method on {@link Signer} funnels through `signWithIntent`, overriding that single method is
|
|
13
|
+
* enough to cover `signTransaction`, `signPersonalMessage`, and `signAndExecuteTransaction`.
|
|
14
|
+
*/
|
|
15
|
+
var ZkLoginSigner = class extends Signer {
|
|
16
|
+
#ephemeralSigner;
|
|
17
|
+
#maxEpoch;
|
|
18
|
+
#inputs;
|
|
19
|
+
#legacyAddress;
|
|
20
|
+
#client;
|
|
21
|
+
#publicKey;
|
|
22
|
+
constructor(options) {
|
|
23
|
+
super();
|
|
24
|
+
this.#ephemeralSigner = options.ephemeralSigner;
|
|
25
|
+
this.#maxEpoch = options.maxEpoch;
|
|
26
|
+
this.#inputs = options.inputs;
|
|
27
|
+
this.#legacyAddress = options.legacyAddress;
|
|
28
|
+
this.#client = options.client;
|
|
29
|
+
if (options.address !== void 0) {
|
|
30
|
+
const derived = this.#derivePublicKey().toSuiAddress();
|
|
31
|
+
if (derived !== options.address) throw new Error(`zkLogin proof does not match the provided address (derived ${derived}, expected ${options.address}) — check the \`legacyAddress\` flag`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
#derivePublicKey() {
|
|
35
|
+
return this.#publicKey ??= toZkLoginPublicIdentifier(BigInt(this.#inputs.addressSeed), extractClaimValue(this.#inputs.issBase64Details, "iss"), {
|
|
36
|
+
legacyAddress: this.#legacyAddress,
|
|
37
|
+
client: this.#client
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The single extension point: the ephemeral signature is produced with the requested intent and
|
|
42
|
+
* then wrapped in a zkLogin signature.
|
|
43
|
+
*/
|
|
44
|
+
async signWithIntent(bytes, intent) {
|
|
45
|
+
const { bytes: signedBytes, signature: userSignature } = await this.#ephemeralSigner.signWithIntent(bytes, intent);
|
|
46
|
+
return {
|
|
47
|
+
bytes: signedBytes,
|
|
48
|
+
signature: getZkLoginSignature({
|
|
49
|
+
inputs: this.#inputs,
|
|
50
|
+
maxEpoch: this.#maxEpoch,
|
|
51
|
+
userSignature
|
|
52
|
+
})
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
sign(_data) {
|
|
56
|
+
throw new Error("ZkLoginSigner does not support signing directly. Use signTransaction or signPersonalMessage instead");
|
|
57
|
+
}
|
|
58
|
+
getKeyScheme() {
|
|
59
|
+
return "ZkLogin";
|
|
60
|
+
}
|
|
61
|
+
getPublicKey() {
|
|
62
|
+
return this.#derivePublicKey();
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
//#endregion
|
|
67
|
+
export { ZkLoginSigner };
|
|
68
|
+
//# sourceMappingURL=signer.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signer.mjs","names":["#ephemeralSigner","#maxEpoch","#inputs","#legacyAddress","#client","#derivePublicKey","#publicKey"],"sources":["../../src/zklogin/signer.ts"],"sourcesContent":["// Copyright (c) Mysten Labs, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\nimport type { ClientWithCoreApi } from '../client/core.js';\nimport type { IntentScope } from '../cryptography/intent.js';\nimport type { SignatureWithBytes } from '../cryptography/keypair.js';\nimport { Signer } from '../cryptography/keypair.js';\nimport type { SignatureScheme } from '../cryptography/signature-scheme.js';\nimport type { ZkLoginSignatureInputs } from './bcs.js';\nimport { extractClaimValue } from './jwt-utils.js';\nimport { toZkLoginPublicIdentifier, ZkLoginPublicIdentifier } from './publickey.js';\nimport { getZkLoginSignature } from './signature.js';\n\nexport interface ZkLoginSignerOptions {\n\t/** The ephemeral signer whose signature is wrapped by the zkLogin proof. */\n\tephemeralSigner: Signer;\n\t/** The maxEpoch the proof was generated for. */\n\tmaxEpoch: number;\n\t/** The zkLogin proof inputs, including the `addressSeed`. */\n\tinputs: ZkLoginSignatureInputs;\n\t/**\n\t * Whether this account uses the deprecated legacy address derivation. The zkLogin address is\n\t * derived from the proof inputs; this flag disambiguates legacy vs. current derivation, matching\n\t * the rest of the zkLogin address API (e.g. `jwtToAddress`, `toZkLoginPublicIdentifier`).\n\t *\n\t * A wrong flag silently yields a signer for the wrong address; pass `address` to guard against it.\n\t */\n\tlegacyAddress: boolean;\n\t/**\n\t * The expected zkLogin address. When provided, the address derived from `inputs` + `legacyAddress`\n\t * is validated against it and the constructor throws on mismatch — guarding against a wrong\n\t * `legacyAddress` flag producing a signer for the wrong address.\n\t */\n\taddress?: string;\n\t/** Optional client, threaded into the derived public key so it can verify signatures. */\n\tclient?: ClientWithCoreApi;\n}\n\n/**\n * A transport- and provider-agnostic zkLogin signer.\n *\n * It wraps any ephemeral {@link Signer} and, for every signing operation, transforms the ephemeral\n * signature into a zkLogin signature using the supplied proof inputs and `maxEpoch`. Because every\n * signing method on {@link Signer} funnels through `signWithIntent`, overriding that single method is\n * enough to cover `signTransaction`, `signPersonalMessage`, and `signAndExecuteTransaction`.\n */\nexport class ZkLoginSigner extends Signer {\n\t#ephemeralSigner: Signer;\n\t#maxEpoch: number;\n\t#inputs: ZkLoginSignatureInputs;\n\t#legacyAddress: boolean;\n\t#client?: ClientWithCoreApi;\n\t#publicKey?: ZkLoginPublicIdentifier;\n\n\tconstructor(options: ZkLoginSignerOptions) {\n\t\tsuper();\n\t\tthis.#ephemeralSigner = options.ephemeralSigner;\n\t\tthis.#maxEpoch = options.maxEpoch;\n\t\tthis.#inputs = options.inputs;\n\t\tthis.#legacyAddress = options.legacyAddress;\n\t\tthis.#client = options.client;\n\n\t\tif (options.address !== undefined) {\n\t\t\tconst derived = this.#derivePublicKey().toSuiAddress();\n\t\t\tif (derived !== options.address) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`zkLogin proof does not match the provided address (derived ${derived}, expected ` +\n\t\t\t\t\t\t`${options.address}) — check the \\`legacyAddress\\` flag`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Shared derive-and-memoize used by both the constructor's address validation and getPublicKey().\n\t// It is `#private` (non-virtual) on purpose: the constructor must derive the *base* address even\n\t// when a subclass overrides getPublicKey() — calling the overridable getPublicKey() from the\n\t// constructor would dispatch to the subclass before its fields are initialized. Laziness also\n\t// means subclasses that override getPublicKey() (e.g. EnokiKeypair) never pay for this derivation.\n\t#derivePublicKey(): ZkLoginPublicIdentifier {\n\t\treturn (this.#publicKey ??= toZkLoginPublicIdentifier(\n\t\t\tBigInt(this.#inputs.addressSeed),\n\t\t\textractClaimValue<string>(this.#inputs.issBase64Details, 'iss'),\n\t\t\t{ legacyAddress: this.#legacyAddress, client: this.#client },\n\t\t));\n\t}\n\n\t/**\n\t * The single extension point: the ephemeral signature is produced with the requested intent and\n\t * then wrapped in a zkLogin signature.\n\t */\n\toverride async signWithIntent(\n\t\tbytes: Uint8Array,\n\t\tintent: IntentScope,\n\t): Promise<SignatureWithBytes> {\n\t\tconst { bytes: signedBytes, signature: userSignature } =\n\t\t\tawait this.#ephemeralSigner.signWithIntent(bytes, intent);\n\n\t\treturn {\n\t\t\tbytes: signedBytes,\n\t\t\tsignature: getZkLoginSignature({\n\t\t\t\tinputs: this.#inputs,\n\t\t\t\tmaxEpoch: this.#maxEpoch,\n\t\t\t\tuserSignature,\n\t\t\t}),\n\t\t};\n\t}\n\n\tsign(_data: Uint8Array): never {\n\t\tthrow new Error(\n\t\t\t'ZkLoginSigner does not support signing directly. Use signTransaction or signPersonalMessage instead',\n\t\t);\n\t}\n\n\tgetKeyScheme(): SignatureScheme {\n\t\treturn 'ZkLogin';\n\t}\n\n\tgetPublicKey(): ZkLoginPublicIdentifier {\n\t\treturn this.#derivePublicKey();\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;AA8CA,IAAa,gBAAb,cAAmC,OAAO;CACzC;CACA;CACA;CACA;CACA;CACA;CAEA,YAAY,SAA+B;AAC1C,SAAO;AACP,QAAKA,kBAAmB,QAAQ;AAChC,QAAKC,WAAY,QAAQ;AACzB,QAAKC,SAAU,QAAQ;AACvB,QAAKC,gBAAiB,QAAQ;AAC9B,QAAKC,SAAU,QAAQ;AAEvB,MAAI,QAAQ,YAAY,QAAW;GAClC,MAAM,UAAU,MAAKC,iBAAkB,CAAC,cAAc;AACtD,OAAI,YAAY,QAAQ,QACvB,OAAM,IAAI,MACT,8DAA8D,QAAQ,aAClE,QAAQ,QAAQ,sCACpB;;;CAUJ,mBAA4C;AAC3C,SAAQ,MAAKC,cAAe,0BAC3B,OAAO,MAAKJ,OAAQ,YAAY,EAChC,kBAA0B,MAAKA,OAAQ,kBAAkB,MAAM,EAC/D;GAAE,eAAe,MAAKC;GAAgB,QAAQ,MAAKC;GAAS,CAC5D;;;;;;CAOF,MAAe,eACd,OACA,QAC8B;EAC9B,MAAM,EAAE,OAAO,aAAa,WAAW,kBACtC,MAAM,MAAKJ,gBAAiB,eAAe,OAAO,OAAO;AAE1D,SAAO;GACN,OAAO;GACP,WAAW,oBAAoB;IAC9B,QAAQ,MAAKE;IACb,UAAU,MAAKD;IACf;IACA,CAAC;GACF;;CAGF,KAAK,OAA0B;AAC9B,QAAM,IAAI,MACT,sGACA;;CAGF,eAAgC;AAC/B,SAAO;;CAGR,eAAwC;AACvC,SAAO,MAAKI,iBAAkB"}
|