@sundaeswap/sprinkles 0.1.1 → 0.2.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/dist/cjs/Sprinkle/__tests__/fill-in-struct.test.js +29 -0
- package/dist/cjs/Sprinkle/__tests__/fill-in-struct.test.js.map +1 -1
- package/dist/cjs/Sprinkle/__tests__/tx-dialog.test.js +451 -210
- package/dist/cjs/Sprinkle/__tests__/tx-dialog.test.js.map +1 -1
- package/dist/cjs/Sprinkle/index.js +344 -87
- package/dist/cjs/Sprinkle/index.js.map +1 -1
- package/dist/esm/Sprinkle/__tests__/fill-in-struct.test.js +29 -0
- package/dist/esm/Sprinkle/__tests__/fill-in-struct.test.js.map +1 -1
- package/dist/esm/Sprinkle/__tests__/tx-dialog.test.js +451 -210
- package/dist/esm/Sprinkle/__tests__/tx-dialog.test.js.map +1 -1
- package/dist/esm/Sprinkle/index.js +345 -88
- package/dist/esm/Sprinkle/index.js.map +1 -1
- package/dist/types/Sprinkle/index.d.ts +40 -3
- package/dist/types/Sprinkle/index.d.ts.map +1 -1
- package/dist/types/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +5 -3
- package/src/Sprinkle/__tests__/fill-in-struct.test.ts +48 -0
- package/src/Sprinkle/__tests__/tx-dialog.test.ts +517 -234
- package/src/Sprinkle/index.ts +365 -110
package/src/Sprinkle/index.ts
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
HotWallet,
|
|
7
7
|
type Wallet,
|
|
8
8
|
} from "@blaze-cardano/sdk";
|
|
9
|
-
import { CborSet, VkeyWitness } from "@blaze-cardano/core";
|
|
9
|
+
import { CborSet, VkeyWitness, blake2b_256, TxCBOR } from "@blaze-cardano/core";
|
|
10
10
|
import { confirm, input, password, search, select } from "@inquirer/prompts";
|
|
11
11
|
import {
|
|
12
12
|
Kind,
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
type TObject,
|
|
17
17
|
type TSchema,
|
|
18
18
|
type TString,
|
|
19
|
+
type TTuple,
|
|
19
20
|
type TUnion,
|
|
20
21
|
Type,
|
|
21
22
|
type TArray,
|
|
@@ -44,7 +45,7 @@ const isObject = (t: TSchema): t is TObject => t[Kind] === "Object";
|
|
|
44
45
|
const isRef = (t: TSchema): t is TRef => t[Kind] === "Ref";
|
|
45
46
|
const isString = (t: TSchema): t is TString => t[Kind] === "String";
|
|
46
47
|
const isThis = (t: TSchema): t is TThis => t[Kind] === "This";
|
|
47
|
-
|
|
48
|
+
const isTuple = (t: TSchema): t is TTuple => t[Kind] === "Tuple";
|
|
48
49
|
const isUnion = (t: TSchema): t is TUnion => t[Kind] === "Union";
|
|
49
50
|
// const isAny = (t: TSchema): t is TAny => t[Kind] === "Any";
|
|
50
51
|
|
|
@@ -72,6 +73,16 @@ export interface IProfileEntry {
|
|
|
72
73
|
meta: IProfileMeta;
|
|
73
74
|
}
|
|
74
75
|
|
|
76
|
+
export interface TxDialogResult {
|
|
77
|
+
action: "submitted" | "signed" | "cancelled";
|
|
78
|
+
txId?: string; // present only if action === 'submitted'
|
|
79
|
+
tx: Core.Transaction; // the (potentially signed) transaction
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface TxDialogOptions {
|
|
83
|
+
beforeSign?: () => Promise<void>;
|
|
84
|
+
}
|
|
85
|
+
|
|
75
86
|
export const NetworkSchema = Type.Union([
|
|
76
87
|
Type.Literal("mainnet"),
|
|
77
88
|
Type.Literal("preview"),
|
|
@@ -803,152 +814,380 @@ export class Sprinkle<S extends TSchema> {
|
|
|
803
814
|
this.saveProfile();
|
|
804
815
|
}
|
|
805
816
|
|
|
817
|
+
// --- TxDialog Helpers ---
|
|
818
|
+
|
|
819
|
+
/**
|
|
820
|
+
* Get the payment key hash from a HotWallet's first address
|
|
821
|
+
*/
|
|
822
|
+
private async getWalletPaymentKeyHash(
|
|
823
|
+
wallet: HotWallet,
|
|
824
|
+
): Promise<string | null> {
|
|
825
|
+
try {
|
|
826
|
+
const addresses = await wallet.getUsedAddresses();
|
|
827
|
+
const address = addresses[0];
|
|
828
|
+
if (!address) return null;
|
|
829
|
+
const paymentCredential = address.asBase()?.getPaymentCredential();
|
|
830
|
+
return paymentCredential?.hash?.toString() ?? null;
|
|
831
|
+
} catch {
|
|
832
|
+
return null;
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
/**
|
|
837
|
+
* Count the number of vkey signatures in a transaction's witness set
|
|
838
|
+
*/
|
|
839
|
+
private countSignatures(tx: Core.Transaction): number {
|
|
840
|
+
const vkeys = tx.witnessSet().vkeys();
|
|
841
|
+
return vkeys ? vkeys.size() : 0;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
/**
|
|
845
|
+
* Check if a specific public key has already signed the transaction
|
|
846
|
+
* Compares by vkey (public key bytes)
|
|
847
|
+
*/
|
|
848
|
+
private hasVkeySigned(tx: Core.Transaction, vkeyHex: string): boolean {
|
|
849
|
+
const vkeys = tx.witnessSet().vkeys();
|
|
850
|
+
if (!vkeys) return false;
|
|
851
|
+
const vkeyArray = vkeys.toCore();
|
|
852
|
+
return vkeyArray.some(([vkey]) => vkey === vkeyHex);
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
/**
|
|
856
|
+
* Get the list of required signer key hashes from the transaction body
|
|
857
|
+
*/
|
|
858
|
+
private getRequiredSigners(tx: Core.Transaction): string[] {
|
|
859
|
+
const requiredSigners = tx.body().requiredSigners();
|
|
860
|
+
if (!requiredSigners) return [];
|
|
861
|
+
return Array.from(requiredSigners.values()).map((s) => s.toString());
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
/**
|
|
865
|
+
* Compute the transaction body hash for display
|
|
866
|
+
*/
|
|
867
|
+
private getTxBodyHash(tx: Core.Transaction): string {
|
|
868
|
+
const bodyCbor = tx.body().toCbor();
|
|
869
|
+
return blake2b_256(bodyCbor);
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
/**
|
|
873
|
+
* Format a hash for display: first 8 chars + ... + last 8 chars
|
|
874
|
+
*/
|
|
875
|
+
private formatHash(hash: string): string {
|
|
876
|
+
if (hash.length <= 20) return hash;
|
|
877
|
+
return `${hash.slice(0, 8)}...${hash.slice(-8)}`;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
/**
|
|
881
|
+
* Merge signatures from source transaction into target transaction.
|
|
882
|
+
* Prevents duplicate signatures by comparing vkey (public key).
|
|
883
|
+
* Returns the count of newly added signatures.
|
|
884
|
+
*/
|
|
885
|
+
private mergeSignatures(
|
|
886
|
+
target: Core.Transaction,
|
|
887
|
+
source: Core.Transaction,
|
|
888
|
+
): number {
|
|
889
|
+
const targetWs = target.witnessSet();
|
|
890
|
+
const sourceWs = source.witnessSet();
|
|
891
|
+
|
|
892
|
+
const targetVkeys = targetWs.vkeys()?.toCore() ?? [];
|
|
893
|
+
const sourceVkeys = sourceWs.vkeys()?.toCore() ?? [];
|
|
894
|
+
|
|
895
|
+
// Find vkeys in source that aren't in target (by comparing public key)
|
|
896
|
+
const existingPubKeys = new Set(targetVkeys.map(([vkey]) => vkey));
|
|
897
|
+
const newVkeys = sourceVkeys.filter(
|
|
898
|
+
([vkey]) => !existingPubKeys.has(vkey),
|
|
899
|
+
);
|
|
900
|
+
|
|
901
|
+
if (newVkeys.length === 0) {
|
|
902
|
+
return 0;
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
// Merge the new vkeys into target
|
|
906
|
+
targetWs.setVkeys(
|
|
907
|
+
CborSet.fromCore([...targetVkeys, ...newVkeys], VkeyWitness.fromCore),
|
|
908
|
+
);
|
|
909
|
+
target.setWitnessSet(targetWs);
|
|
910
|
+
|
|
911
|
+
return newVkeys.length;
|
|
912
|
+
}
|
|
913
|
+
|
|
806
914
|
async TxDialog<P extends Provider, W extends Wallet>(
|
|
807
915
|
blaze: Blaze<P, W>,
|
|
808
916
|
tx: Core.Transaction,
|
|
809
|
-
opts?:
|
|
810
|
-
): Promise<
|
|
811
|
-
|
|
917
|
+
opts?: TxDialogOptions,
|
|
918
|
+
): Promise<TxDialogResult> {
|
|
919
|
+
let currentTx = tx;
|
|
812
920
|
let expanded = false;
|
|
921
|
+
let hasSignedThisSession = false;
|
|
922
|
+
|
|
923
|
+
// Check if wallet can sign (is HotWallet)
|
|
924
|
+
const isHotWallet = blaze.wallet instanceof HotWallet;
|
|
813
925
|
|
|
814
|
-
|
|
926
|
+
// Get wallet's vkeys for detecting if already signed
|
|
927
|
+
let walletVkeys: string[] = [];
|
|
928
|
+
if (isHotWallet) {
|
|
929
|
+
try {
|
|
930
|
+
// Sign a dummy to get the wallet's public keys
|
|
931
|
+
// We'll use this to check if wallet has already signed
|
|
932
|
+
const wallet = blaze.wallet as unknown as HotWallet;
|
|
933
|
+
const addresses = await wallet.getUsedAddresses();
|
|
934
|
+
const address = addresses[0];
|
|
935
|
+
if (address) {
|
|
936
|
+
// We can't easily get vkeys without signing, so we'll track after first sign
|
|
937
|
+
}
|
|
938
|
+
} catch {
|
|
939
|
+
// Ignore errors in setup
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
while (true) {
|
|
944
|
+
// Display transaction status
|
|
945
|
+
const txHash = this.getTxBodyHash(currentTx);
|
|
946
|
+
const sigCount = this.countSignatures(currentTx);
|
|
947
|
+
const requiredSigners = this.getRequiredSigners(currentTx);
|
|
948
|
+
|
|
949
|
+
console.log("");
|
|
950
|
+
console.log(`Transaction: ${this.formatHash(txHash)}`);
|
|
951
|
+
if (requiredSigners.length > 0) {
|
|
952
|
+
console.log(`Signatures: ${sigCount} of ${requiredSigners.length} required`);
|
|
953
|
+
console.log("Required signers:");
|
|
954
|
+
for (const signer of requiredSigners) {
|
|
955
|
+
console.log(` - ${this.formatHash(signer)}`);
|
|
956
|
+
}
|
|
957
|
+
} else {
|
|
958
|
+
console.log(`Signatures: ${sigCount}`);
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
// Display CBOR
|
|
962
|
+
const txCbor = currentTx.toCbor();
|
|
815
963
|
if (expanded) {
|
|
816
964
|
console.log("Transaction CBOR:", txCbor);
|
|
817
965
|
} else {
|
|
818
966
|
console.log("Transaction CBOR:", `${String(txCbor).slice(0, 50)}...`);
|
|
819
967
|
}
|
|
820
968
|
|
|
821
|
-
|
|
969
|
+
// Build dynamic menu choices
|
|
970
|
+
const choices: { name: string; value: string }[] = [];
|
|
822
971
|
|
|
972
|
+
// "Sign with this wallet" - only if HotWallet and hasn't signed this session
|
|
973
|
+
if (isHotWallet && !hasSignedThisSession) {
|
|
974
|
+
choices.push({ name: "Sign with this wallet", value: "sign" });
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
// CBOR options
|
|
823
978
|
if (!expanded) {
|
|
824
|
-
|
|
825
|
-
title: "Expand CBOR",
|
|
826
|
-
action: async () => {
|
|
827
|
-
expanded = true;
|
|
828
|
-
await showDialog();
|
|
829
|
-
},
|
|
830
|
-
});
|
|
979
|
+
choices.push({ name: "Expand CBOR", value: "expand" });
|
|
831
980
|
}
|
|
981
|
+
choices.push({ name: "Copy CBOR to clipboard", value: "copy" });
|
|
982
|
+
choices.push({ name: "Import signatures from CBOR", value: "import" });
|
|
832
983
|
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
} catch (e) {
|
|
841
|
-
console.log("Failed to copy to clipboard, expanding instead.");
|
|
842
|
-
expanded = true;
|
|
843
|
-
await showDialog();
|
|
844
|
-
}
|
|
845
|
-
},
|
|
984
|
+
// Submit and cancel
|
|
985
|
+
choices.push({ name: "Submit transaction", value: "submit" });
|
|
986
|
+
choices.push({ name: "Cancel", value: "cancel" });
|
|
987
|
+
|
|
988
|
+
const selection = await select({
|
|
989
|
+
message: "Select an option:",
|
|
990
|
+
choices,
|
|
846
991
|
});
|
|
847
992
|
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
await opts.beforeSign();
|
|
854
|
-
}
|
|
993
|
+
// Handle selection
|
|
994
|
+
if (selection === "sign") {
|
|
995
|
+
if (opts?.beforeSign) {
|
|
996
|
+
await opts.beforeSign();
|
|
997
|
+
}
|
|
855
998
|
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
(signer) => signer.toString() === stakeKeyHash,
|
|
874
|
-
);
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
const certs = tx.body().certs();
|
|
878
|
-
const hasCertificates = certs && certs.size() > 0;
|
|
879
|
-
const withdrawals = tx.body().withdrawals();
|
|
880
|
-
const hasWithdrawals = withdrawals && withdrawals.size > 0;
|
|
881
|
-
|
|
882
|
-
if (hasCertificates || hasWithdrawals) {
|
|
883
|
-
needsStakeKey = true;
|
|
884
|
-
}
|
|
885
|
-
}
|
|
999
|
+
// Detect if stake key signature is required
|
|
1000
|
+
let needsStakeKey = false;
|
|
1001
|
+
try {
|
|
1002
|
+
const wallet = blaze.wallet as unknown as HotWallet;
|
|
1003
|
+
const addresses = await wallet.getUsedAddresses();
|
|
1004
|
+
const userAddress = addresses[0];
|
|
1005
|
+
if (userAddress) {
|
|
1006
|
+
const stakeCredential = userAddress.asBase()?.getStakeCredential();
|
|
1007
|
+
const stakeKeyHash = stakeCredential?.hash?.toString();
|
|
1008
|
+
|
|
1009
|
+
if (stakeKeyHash) {
|
|
1010
|
+
const reqSigners = currentTx.body().requiredSigners();
|
|
1011
|
+
if (reqSigners) {
|
|
1012
|
+
const signerArray = Array.from(reqSigners.values());
|
|
1013
|
+
needsStakeKey = signerArray.some(
|
|
1014
|
+
(signer) => signer.toString() === stakeKeyHash,
|
|
1015
|
+
);
|
|
886
1016
|
}
|
|
887
1017
|
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
1018
|
+
const certs = currentTx.body().certs();
|
|
1019
|
+
const hasCertificates = certs && certs.size() > 0;
|
|
1020
|
+
const withdrawals = currentTx.body().withdrawals();
|
|
1021
|
+
const hasWithdrawals = withdrawals && withdrawals.size > 0;
|
|
1022
|
+
|
|
1023
|
+
if (hasCertificates || hasWithdrawals) {
|
|
1024
|
+
needsStakeKey = true;
|
|
892
1025
|
}
|
|
893
|
-
} catch (error) {
|
|
894
|
-
console.warn(
|
|
895
|
-
"Could not determine stake key requirement, signing with payment key only.",
|
|
896
|
-
);
|
|
897
|
-
console.warn(`Error: ${(error as Error).message}`);
|
|
898
1026
|
}
|
|
1027
|
+
}
|
|
899
1028
|
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
1029
|
+
if (needsStakeKey) {
|
|
1030
|
+
console.log("Transaction requires stake key signature.");
|
|
1031
|
+
} else {
|
|
1032
|
+
console.log("Transaction requires payment key signature only.");
|
|
1033
|
+
}
|
|
1034
|
+
} catch (error) {
|
|
1035
|
+
console.warn(
|
|
1036
|
+
"Could not determine stake key requirement, signing with payment key only.",
|
|
1037
|
+
);
|
|
1038
|
+
console.warn(`Error: ${(error as Error).message}`);
|
|
1039
|
+
}
|
|
908
1040
|
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
1041
|
+
try {
|
|
1042
|
+
if (needsStakeKey) {
|
|
1043
|
+
const wallet = blaze.wallet as unknown as HotWallet;
|
|
1044
|
+
const signed = await wallet.signTransaction(currentTx, true, true);
|
|
1045
|
+
const ws = currentTx.witnessSet();
|
|
1046
|
+
const existingVkeys = ws.vkeys()?.toCore() ?? [];
|
|
1047
|
+
|
|
1048
|
+
const signedKeys = signed.vkeys();
|
|
1049
|
+
if (!signedKeys) {
|
|
1050
|
+
throw new Error(
|
|
1051
|
+
"signTransaction: no signed keys in wallet witness response",
|
|
1052
|
+
);
|
|
1053
|
+
}
|
|
915
1054
|
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
)
|
|
921
|
-
|
|
922
|
-
"signTransaction: some keys were already signed",
|
|
923
|
-
);
|
|
924
|
-
}
|
|
1055
|
+
// Check for duplicates before adding
|
|
1056
|
+
const newSignedKeys = signedKeys.toCore();
|
|
1057
|
+
const existingPubKeys = new Set(existingVkeys.map(([vkey]) => vkey));
|
|
1058
|
+
const uniqueNewKeys = newSignedKeys.filter(
|
|
1059
|
+
([vkey]) => !existingPubKeys.has(vkey),
|
|
1060
|
+
);
|
|
925
1061
|
|
|
1062
|
+
if (uniqueNewKeys.length === 0) {
|
|
1063
|
+
console.log("Wallet has already signed this transaction.");
|
|
1064
|
+
} else {
|
|
926
1065
|
ws.setVkeys(
|
|
927
1066
|
CborSet.fromCore(
|
|
928
|
-
[...
|
|
1067
|
+
[...existingVkeys, ...uniqueNewKeys],
|
|
929
1068
|
VkeyWitness.fromCore,
|
|
930
1069
|
),
|
|
931
1070
|
);
|
|
932
|
-
|
|
933
|
-
|
|
1071
|
+
currentTx.setWitnessSet(ws);
|
|
1072
|
+
console.log(`Added ${uniqueNewKeys.length} signature(s).`);
|
|
1073
|
+
hasSignedThisSession = true;
|
|
1074
|
+
}
|
|
1075
|
+
} else {
|
|
1076
|
+
const signedTx = await blaze.signTransaction(currentTx);
|
|
1077
|
+
// Merge signatures from signed tx into current tx
|
|
1078
|
+
const added = this.mergeSignatures(currentTx, signedTx);
|
|
1079
|
+
if (added > 0) {
|
|
1080
|
+
console.log(`Added ${added} signature(s).`);
|
|
1081
|
+
hasSignedThisSession = true;
|
|
934
1082
|
} else {
|
|
935
|
-
|
|
1083
|
+
console.log("Wallet has already signed this transaction.");
|
|
936
1084
|
}
|
|
1085
|
+
}
|
|
1086
|
+
} catch (error) {
|
|
1087
|
+
console.error(`Signing failed: ${(error as Error).message}`);
|
|
1088
|
+
}
|
|
1089
|
+
// Continue loop after signing
|
|
1090
|
+
continue;
|
|
1091
|
+
}
|
|
937
1092
|
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
1093
|
+
if (selection === "expand") {
|
|
1094
|
+
expanded = true;
|
|
1095
|
+
continue;
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
if (selection === "copy") {
|
|
1099
|
+
try {
|
|
1100
|
+
const { default: clipboard } = await import("clipboardy");
|
|
1101
|
+
clipboard.writeSync(String(currentTx.toCbor()));
|
|
1102
|
+
console.log("Transaction CBOR copied to clipboard.");
|
|
1103
|
+
} catch {
|
|
1104
|
+
console.log("Failed to copy to clipboard, expanding instead.");
|
|
1105
|
+
expanded = true;
|
|
1106
|
+
}
|
|
1107
|
+
continue;
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
if (selection === "import") {
|
|
1111
|
+
const cborInput = await input({
|
|
1112
|
+
message: "Paste transaction CBOR (hex):",
|
|
941
1113
|
});
|
|
1114
|
+
|
|
1115
|
+
if (!cborInput || cborInput.trim() === "") {
|
|
1116
|
+
console.log("No CBOR provided.");
|
|
1117
|
+
continue;
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
try {
|
|
1121
|
+
const importedTx = Core.Transaction.fromCbor(
|
|
1122
|
+
TxCBOR(cborInput.trim()),
|
|
1123
|
+
);
|
|
1124
|
+
|
|
1125
|
+
// Validate body hash matches
|
|
1126
|
+
const currentHash = this.getTxBodyHash(currentTx);
|
|
1127
|
+
const importedHash = this.getTxBodyHash(importedTx);
|
|
1128
|
+
|
|
1129
|
+
if (currentHash !== importedHash) {
|
|
1130
|
+
const proceed = await confirm({
|
|
1131
|
+
message: `Warning: Imported transaction has different body hash.\nCurrent: ${this.formatHash(currentHash)}\nImported: ${this.formatHash(importedHash)}\nProceed anyway?`,
|
|
1132
|
+
default: false,
|
|
1133
|
+
});
|
|
1134
|
+
if (!proceed) {
|
|
1135
|
+
console.log("Import cancelled.");
|
|
1136
|
+
continue;
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
// Merge signatures
|
|
1141
|
+
const added = this.mergeSignatures(currentTx, importedTx);
|
|
1142
|
+
const sourceVkeys = importedTx.witnessSet().vkeys();
|
|
1143
|
+
const sourceCount = sourceVkeys ? sourceVkeys.size() : 0;
|
|
1144
|
+
const skipped = sourceCount - added;
|
|
1145
|
+
|
|
1146
|
+
if (added > 0) {
|
|
1147
|
+
console.log(`Added ${added} new signature(s).`);
|
|
1148
|
+
}
|
|
1149
|
+
if (skipped > 0) {
|
|
1150
|
+
console.log(`Skipped ${skipped} duplicate signature(s).`);
|
|
1151
|
+
}
|
|
1152
|
+
if (added === 0 && skipped === 0) {
|
|
1153
|
+
console.log("No signatures found in imported transaction.");
|
|
1154
|
+
}
|
|
1155
|
+
} catch (error) {
|
|
1156
|
+
console.error(`Failed to import CBOR: ${(error as Error).message}`);
|
|
1157
|
+
}
|
|
1158
|
+
continue;
|
|
942
1159
|
}
|
|
943
1160
|
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
1161
|
+
if (selection === "submit") {
|
|
1162
|
+
const sigCount = this.countSignatures(currentTx);
|
|
1163
|
+
if (sigCount === 0) {
|
|
1164
|
+
const proceed = await confirm({
|
|
1165
|
+
message: "Warning: Transaction has no signatures. Submit anyway?",
|
|
1166
|
+
default: false,
|
|
1167
|
+
});
|
|
1168
|
+
if (!proceed) {
|
|
1169
|
+
continue;
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
try {
|
|
1174
|
+
const txId = await blaze.submitTransaction(currentTx);
|
|
1175
|
+
console.log(`Transaction submitted: ${txId}`);
|
|
1176
|
+
return { action: "submitted", txId, tx: currentTx };
|
|
1177
|
+
} catch (error) {
|
|
1178
|
+
console.error(`Submit failed: ${(error as Error).message}`);
|
|
1179
|
+
// Continue loop to allow retry or other actions
|
|
1180
|
+
continue;
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
950
1183
|
|
|
951
|
-
|
|
1184
|
+
if (selection === "cancel") {
|
|
1185
|
+
if (hasSignedThisSession) {
|
|
1186
|
+
return { action: "signed", tx: currentTx };
|
|
1187
|
+
}
|
|
1188
|
+
return { action: "cancelled", tx: currentTx };
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
952
1191
|
}
|
|
953
1192
|
|
|
954
1193
|
async EditStruct<U extends TSchema>(
|
|
@@ -1163,6 +1402,22 @@ export class Sprinkle<S extends TSchema> {
|
|
|
1163
1402
|
return arr as TExact<U>;
|
|
1164
1403
|
}
|
|
1165
1404
|
|
|
1405
|
+
if (isTuple(type)) {
|
|
1406
|
+
const items = type.items ?? [];
|
|
1407
|
+
const result: unknown[] = [];
|
|
1408
|
+
for (let i = 0; i < items.length; i++) {
|
|
1409
|
+
const itemType = items[i] as U;
|
|
1410
|
+
const value = await this._fillInStruct(
|
|
1411
|
+
itemType,
|
|
1412
|
+
path.concat([`[${i}]`]),
|
|
1413
|
+
defs,
|
|
1414
|
+
def ? ((def as unknown[])[i] as TExact<U>) : undefined,
|
|
1415
|
+
);
|
|
1416
|
+
result.push(value);
|
|
1417
|
+
}
|
|
1418
|
+
return result as TExact<U>;
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1166
1421
|
throw new Error(
|
|
1167
1422
|
`Unable to fill in struct for type at path ${path.join(".")}`,
|
|
1168
1423
|
);
|