stellar-shade 0.0.1 → 0.0.2

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/index.d.cts CHANGED
@@ -45,17 +45,48 @@ interface WithdrawReceipt {
45
45
  /** Amount withdrawn in whole units */
46
46
  amount: number;
47
47
  }
48
+ /**
49
+ * External transaction signer.
50
+ *
51
+ * Given the base64 XDR of a prepared transaction, return the base64 XDR of the
52
+ * signed transaction. This is exactly the shape of a browser wallet's signing
53
+ * call (e.g. Freighter's `signTransaction`), allowing the client to sign
54
+ * without ever handling a raw secret key.
55
+ */
56
+ type TransactionSigner = (xdr: string, context: {
57
+ /** Network passphrase the transaction must be signed against. */
58
+ networkPassphrase: string;
59
+ /** The account expected to sign (public key, G...). */
60
+ address: string;
61
+ }) => Promise<string>;
48
62
  /** Options for sending to a stealth address. */
49
63
  interface SendOpts {
50
64
  /** Asset to send. Default: native XLM. Format: "CODE:ISSUER" */
51
65
  asset?: string;
66
+ /**
67
+ * Sign with an external wallet instead of a raw secret key. When provided,
68
+ * the `senderSecret` argument of `send()` is treated as the sender's public
69
+ * key (G...) and is never used to sign.
70
+ */
71
+ signTransaction?: TransactionSigner;
52
72
  }
53
73
  /** Options for withdrawing from a stealth address. */
54
74
  interface WithdrawOpts {
55
75
  /** Stealth keys (need view + spend private keys) */
56
76
  keys: StealthKeys;
57
- /** Secret key of account paying the Soroban invocation fee */
58
- feePayer: string;
77
+ /**
78
+ * Secret key of the account paying the Soroban invocation fee.
79
+ * Optional when `signTransaction` + `feePayerAddress` are supplied instead.
80
+ */
81
+ feePayer?: string;
82
+ /**
83
+ * Sign the fee-payer side with an external wallet instead of `feePayer`.
84
+ * The stealth withdraw authorization is still produced internally from
85
+ * `keys`; only the transaction envelope signature is delegated.
86
+ */
87
+ signTransaction?: TransactionSigner;
88
+ /** Fee payer's public key (G...). Required when using `signTransaction`. */
89
+ feePayerAddress?: string;
59
90
  /** Relay URL for fee-bumped submission (privacy-preserving) */
60
91
  relay?: string;
61
92
  /** Asset to withdraw. Default: native XLM. Format: "CODE:ISSUER" */
@@ -121,12 +152,27 @@ declare class StealthClient {
121
152
  * Derives a one-time stealth address from the recipient's meta-address,
122
153
  * deposits tokens into the pool contract, and records the announcement.
123
154
  *
155
+ * Signing: by default the transaction is signed with `senderSecret` (a
156
+ * Stellar secret key). If `opts.signTransaction` is provided, signing is
157
+ * delegated to that external signer (e.g. a browser wallet) and the
158
+ * `senderSecret` argument is treated as the sender's public key instead.
159
+ *
124
160
  * @param metaAddress - Recipient's meta-address (st:stellar:... format)
125
161
  * @param amount - Amount in whole units (e.g. 100 = 100 XLM)
126
- * @param senderSecret - Sender's Stellar secret key
127
- * @param opts - Optional: asset to send
162
+ * @param senderSecret - Sender's secret key, or public key when using `opts.signTransaction`
163
+ * @param opts - Optional: asset to send, external signer
128
164
  */
129
165
  send(metaAddress: string, amount: number, senderSecret: string, opts?: SendOpts): Promise<SendReceipt>;
166
+ /**
167
+ * Sign a prepared transaction either with a local secret key (legacy path)
168
+ * or by delegating to an external signer that returns signed XDR.
169
+ *
170
+ * @param prepared - The prepared transaction to sign
171
+ * @param secretOrPublic - Secret key (local signing) or public key (external)
172
+ * @param publicKey - Signer's public key (G...)
173
+ * @param signer - Optional external signer; when present, no secret is used
174
+ */
175
+ private signTx;
130
176
  /**
131
177
  * Scan for stealth payments you received.
132
178
  *
@@ -156,4 +202,4 @@ declare class StealthClient {
156
202
  withdraw(stealthAddress: string, destination: string, opts: WithdrawOpts): Promise<WithdrawReceipt>;
157
203
  }
158
204
 
159
- export { type Balance, type ClientConfig, type Payment, type SendOpts, type SendReceipt, StealthClient, type StealthKeys, type WithdrawOpts, type WithdrawReceipt };
205
+ export { type Balance, type ClientConfig, type Payment, type SendOpts, type SendReceipt, StealthClient, type StealthKeys, type TransactionSigner, type WithdrawOpts, type WithdrawReceipt };
package/dist/index.d.ts CHANGED
@@ -45,17 +45,48 @@ interface WithdrawReceipt {
45
45
  /** Amount withdrawn in whole units */
46
46
  amount: number;
47
47
  }
48
+ /**
49
+ * External transaction signer.
50
+ *
51
+ * Given the base64 XDR of a prepared transaction, return the base64 XDR of the
52
+ * signed transaction. This is exactly the shape of a browser wallet's signing
53
+ * call (e.g. Freighter's `signTransaction`), allowing the client to sign
54
+ * without ever handling a raw secret key.
55
+ */
56
+ type TransactionSigner = (xdr: string, context: {
57
+ /** Network passphrase the transaction must be signed against. */
58
+ networkPassphrase: string;
59
+ /** The account expected to sign (public key, G...). */
60
+ address: string;
61
+ }) => Promise<string>;
48
62
  /** Options for sending to a stealth address. */
49
63
  interface SendOpts {
50
64
  /** Asset to send. Default: native XLM. Format: "CODE:ISSUER" */
51
65
  asset?: string;
66
+ /**
67
+ * Sign with an external wallet instead of a raw secret key. When provided,
68
+ * the `senderSecret` argument of `send()` is treated as the sender's public
69
+ * key (G...) and is never used to sign.
70
+ */
71
+ signTransaction?: TransactionSigner;
52
72
  }
53
73
  /** Options for withdrawing from a stealth address. */
54
74
  interface WithdrawOpts {
55
75
  /** Stealth keys (need view + spend private keys) */
56
76
  keys: StealthKeys;
57
- /** Secret key of account paying the Soroban invocation fee */
58
- feePayer: string;
77
+ /**
78
+ * Secret key of the account paying the Soroban invocation fee.
79
+ * Optional when `signTransaction` + `feePayerAddress` are supplied instead.
80
+ */
81
+ feePayer?: string;
82
+ /**
83
+ * Sign the fee-payer side with an external wallet instead of `feePayer`.
84
+ * The stealth withdraw authorization is still produced internally from
85
+ * `keys`; only the transaction envelope signature is delegated.
86
+ */
87
+ signTransaction?: TransactionSigner;
88
+ /** Fee payer's public key (G...). Required when using `signTransaction`. */
89
+ feePayerAddress?: string;
59
90
  /** Relay URL for fee-bumped submission (privacy-preserving) */
60
91
  relay?: string;
61
92
  /** Asset to withdraw. Default: native XLM. Format: "CODE:ISSUER" */
@@ -121,12 +152,27 @@ declare class StealthClient {
121
152
  * Derives a one-time stealth address from the recipient's meta-address,
122
153
  * deposits tokens into the pool contract, and records the announcement.
123
154
  *
155
+ * Signing: by default the transaction is signed with `senderSecret` (a
156
+ * Stellar secret key). If `opts.signTransaction` is provided, signing is
157
+ * delegated to that external signer (e.g. a browser wallet) and the
158
+ * `senderSecret` argument is treated as the sender's public key instead.
159
+ *
124
160
  * @param metaAddress - Recipient's meta-address (st:stellar:... format)
125
161
  * @param amount - Amount in whole units (e.g. 100 = 100 XLM)
126
- * @param senderSecret - Sender's Stellar secret key
127
- * @param opts - Optional: asset to send
162
+ * @param senderSecret - Sender's secret key, or public key when using `opts.signTransaction`
163
+ * @param opts - Optional: asset to send, external signer
128
164
  */
129
165
  send(metaAddress: string, amount: number, senderSecret: string, opts?: SendOpts): Promise<SendReceipt>;
166
+ /**
167
+ * Sign a prepared transaction either with a local secret key (legacy path)
168
+ * or by delegating to an external signer that returns signed XDR.
169
+ *
170
+ * @param prepared - The prepared transaction to sign
171
+ * @param secretOrPublic - Secret key (local signing) or public key (external)
172
+ * @param publicKey - Signer's public key (G...)
173
+ * @param signer - Optional external signer; when present, no secret is used
174
+ */
175
+ private signTx;
130
176
  /**
131
177
  * Scan for stealth payments you received.
132
178
  *
@@ -156,4 +202,4 @@ declare class StealthClient {
156
202
  withdraw(stealthAddress: string, destination: string, opts: WithdrawOpts): Promise<WithdrawReceipt>;
157
203
  }
158
204
 
159
- export { type Balance, type ClientConfig, type Payment, type SendOpts, type SendReceipt, StealthClient, type StealthKeys, type WithdrawOpts, type WithdrawReceipt };
205
+ export { type Balance, type ClientConfig, type Payment, type SendOpts, type SendReceipt, StealthClient, type StealthKeys, type TransactionSigner, type WithdrawOpts, type WithdrawReceipt };
package/dist/index.js CHANGED
@@ -576,15 +576,20 @@ var StealthClient = class {
576
576
  * Derives a one-time stealth address from the recipient's meta-address,
577
577
  * deposits tokens into the pool contract, and records the announcement.
578
578
  *
579
+ * Signing: by default the transaction is signed with `senderSecret` (a
580
+ * Stellar secret key). If `opts.signTransaction` is provided, signing is
581
+ * delegated to that external signer (e.g. a browser wallet) and the
582
+ * `senderSecret` argument is treated as the sender's public key instead.
583
+ *
579
584
  * @param metaAddress - Recipient's meta-address (st:stellar:... format)
580
585
  * @param amount - Amount in whole units (e.g. 100 = 100 XLM)
581
- * @param senderSecret - Sender's Stellar secret key
582
- * @param opts - Optional: asset to send
586
+ * @param senderSecret - Sender's secret key, or public key when using `opts.signTransaction`
587
+ * @param opts - Optional: asset to send, external signer
583
588
  */
584
589
  async send(metaAddress, amount, senderSecret, opts) {
585
590
  if (amount <= 0) throw new Error("Amount must be positive");
586
591
  const { spendPubKey, viewPubKey } = decodeMetaAddress(metaAddress);
587
- const senderKeypair = Keypair.fromSecret(senderSecret);
592
+ const senderPublicKey = opts?.signTransaction ? senderSecret : Keypair.fromSecret(senderSecret).publicKey();
588
593
  const tokenAddress = resolveTokenAddress(opts?.asset, this.networkPassphrase);
589
594
  const stroops = BigInt(Math.round(amount * 1e7));
590
595
  const ephemeralPrivKey = new Uint8Array(randomBytes(32));
@@ -594,14 +599,14 @@ var StealthClient = class {
594
599
  ephemeralPrivKey
595
600
  );
596
601
  const contract = new Contract(this.contractId);
597
- const account = await this.server.getAccount(senderKeypair.publicKey());
602
+ const account = await this.server.getAccount(senderPublicKey);
598
603
  const depositTx = new TransactionBuilder(account, {
599
604
  fee: "100",
600
605
  networkPassphrase: this.networkPassphrase
601
606
  }).addOperation(
602
607
  contract.call(
603
608
  "deposit",
604
- new StellarSdk.Address(senderKeypair.publicKey()).toScVal(),
609
+ new StellarSdk.Address(senderPublicKey).toScVal(),
605
610
  new StellarSdk.Address(tokenAddress).toScVal(),
606
611
  nativeToScVal(stroops, { type: "i128" }),
607
612
  nativeToScVal(Buffer.from(stealth.stealthPubKey)),
@@ -610,8 +615,13 @@ var StealthClient = class {
610
615
  )
611
616
  ).setTimeout(30).build();
612
617
  const prepared = await this.server.prepareTransaction(depositTx);
613
- prepared.sign(senderKeypair);
614
- const result = await this.server.sendTransaction(prepared);
618
+ const signed = await this.signTx(
619
+ prepared,
620
+ senderSecret,
621
+ senderPublicKey,
622
+ opts?.signTransaction
623
+ );
624
+ const result = await this.server.sendTransaction(signed);
615
625
  if (result.status === "ERROR") {
616
626
  throw new Error("Transaction submission failed");
617
627
  }
@@ -623,6 +633,27 @@ var StealthClient = class {
623
633
  txHash: result.hash
624
634
  };
625
635
  }
636
+ /**
637
+ * Sign a prepared transaction either with a local secret key (legacy path)
638
+ * or by delegating to an external signer that returns signed XDR.
639
+ *
640
+ * @param prepared - The prepared transaction to sign
641
+ * @param secretOrPublic - Secret key (local signing) or public key (external)
642
+ * @param publicKey - Signer's public key (G...)
643
+ * @param signer - Optional external signer; when present, no secret is used
644
+ */
645
+ async signTx(prepared, secretOrPublic, publicKey, signer) {
646
+ if (signer) {
647
+ const signedXdr = await signer(prepared.toXDR(), {
648
+ networkPassphrase: this.networkPassphrase,
649
+ address: publicKey
650
+ });
651
+ const tx = TransactionBuilder.fromXDR(signedXdr, this.networkPassphrase);
652
+ return tx;
653
+ }
654
+ prepared.sign(Keypair.fromSecret(secretOrPublic));
655
+ return prepared;
656
+ }
626
657
  /**
627
658
  * Scan for stealth payments you received.
628
659
  *
@@ -764,9 +795,15 @@ var StealthClient = class {
764
795
  nonce
765
796
  );
766
797
  const signature = signWithStealthKey(messageHash, stealthPrivKey);
767
- const feePayerKeypair = Keypair.fromSecret(opts.feePayer);
798
+ if (!opts.signTransaction && !opts.feePayer) {
799
+ throw new Error("Provide either opts.feePayer or opts.signTransaction + opts.feePayerAddress");
800
+ }
801
+ if (opts.signTransaction && !opts.feePayerAddress) {
802
+ throw new Error("opts.feePayerAddress is required when using opts.signTransaction");
803
+ }
804
+ const feePayerPublicKey = opts.signTransaction ? opts.feePayerAddress : Keypair.fromSecret(opts.feePayer).publicKey();
768
805
  const contract = new Contract(this.contractId);
769
- const feePayerAccount = await this.server.getAccount(feePayerKeypair.publicKey());
806
+ const feePayerAccount = await this.server.getAccount(feePayerPublicKey);
770
807
  const withdrawTx = new TransactionBuilder(feePayerAccount, {
771
808
  fee: "100",
772
809
  networkPassphrase: this.networkPassphrase
@@ -782,14 +819,19 @@ var StealthClient = class {
782
819
  )
783
820
  ).setTimeout(30).build();
784
821
  const prepared = await this.server.prepareTransaction(withdrawTx);
785
- prepared.sign(feePayerKeypair);
822
+ const signedTx = await this.signTx(
823
+ prepared,
824
+ opts.feePayer ?? "",
825
+ feePayerPublicKey,
826
+ opts.signTransaction
827
+ );
786
828
  let txHash;
787
829
  if (opts.relay) {
788
830
  const url = opts.relay.endsWith("/relay") ? opts.relay : `${opts.relay}/relay`;
789
831
  const res = await fetch(url, {
790
832
  method: "POST",
791
833
  headers: { "Content-Type": "application/json" },
792
- body: JSON.stringify({ xdr: prepared.toEnvelope().toXDR("base64") })
834
+ body: JSON.stringify({ xdr: signedTx.toEnvelope().toXDR("base64") })
793
835
  });
794
836
  if (!res.ok) {
795
837
  const err = await res.json();
@@ -798,7 +840,7 @@ var StealthClient = class {
798
840
  const data = await res.json();
799
841
  txHash = data.txHash;
800
842
  } else {
801
- const result = await this.server.sendTransaction(prepared);
843
+ const result = await this.server.sendTransaction(signedTx);
802
844
  if (result.status === "ERROR") throw new Error("Transaction submission failed");
803
845
  if (result.status === "PENDING") {
804
846
  await waitForTransaction(this.server, result.hash);