uvd-x402-sdk 2.10.1 → 2.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/adapters/index.d.mts +1 -1
  2. package/dist/adapters/index.d.ts +1 -1
  3. package/dist/adapters/index.js.map +1 -1
  4. package/dist/adapters/index.mjs.map +1 -1
  5. package/dist/backend/index.d.mts +1 -1
  6. package/dist/backend/index.d.ts +1 -1
  7. package/dist/backend/index.js.map +1 -1
  8. package/dist/backend/index.mjs.map +1 -1
  9. package/dist/{index-C60c_e5z.d.mts → index-C6Vxnneo.d.mts} +1 -1
  10. package/dist/{index-VIOUicmO.d.ts → index-DmJGKD9r.d.ts} +1 -1
  11. package/dist/{index-D-dO_FoP.d.mts → index-fIhvHqCQ.d.mts} +18 -22
  12. package/dist/{index-D-dO_FoP.d.ts → index-fIhvHqCQ.d.ts} +18 -22
  13. package/dist/index.d.mts +2 -2
  14. package/dist/index.d.ts +2 -2
  15. package/dist/index.js.map +1 -1
  16. package/dist/index.mjs.map +1 -1
  17. package/dist/providers/algorand/index.d.mts +42 -15
  18. package/dist/providers/algorand/index.d.ts +42 -15
  19. package/dist/providers/algorand/index.js +198 -32
  20. package/dist/providers/algorand/index.js.map +1 -1
  21. package/dist/providers/algorand/index.mjs +198 -32
  22. package/dist/providers/algorand/index.mjs.map +1 -1
  23. package/dist/providers/evm/index.d.mts +1 -1
  24. package/dist/providers/evm/index.d.ts +1 -1
  25. package/dist/providers/evm/index.js.map +1 -1
  26. package/dist/providers/evm/index.mjs.map +1 -1
  27. package/dist/providers/near/index.d.mts +1 -1
  28. package/dist/providers/near/index.d.ts +1 -1
  29. package/dist/providers/near/index.js.map +1 -1
  30. package/dist/providers/near/index.mjs.map +1 -1
  31. package/dist/providers/solana/index.d.mts +1 -1
  32. package/dist/providers/solana/index.d.ts +1 -1
  33. package/dist/providers/solana/index.js.map +1 -1
  34. package/dist/providers/solana/index.mjs.map +1 -1
  35. package/dist/providers/stellar/index.d.mts +1 -1
  36. package/dist/providers/stellar/index.d.ts +1 -1
  37. package/dist/providers/stellar/index.js.map +1 -1
  38. package/dist/providers/stellar/index.mjs.map +1 -1
  39. package/dist/react/index.d.mts +3 -3
  40. package/dist/react/index.d.ts +3 -3
  41. package/dist/react/index.js.map +1 -1
  42. package/dist/react/index.mjs.map +1 -1
  43. package/dist/utils/index.d.mts +1 -1
  44. package/dist/utils/index.d.ts +1 -1
  45. package/dist/utils/index.js.map +1 -1
  46. package/dist/utils/index.mjs.map +1 -1
  47. package/package.json +6 -1
  48. package/src/providers/algorand/index.ts +261 -40
  49. package/src/types/index.ts +18 -22
@@ -684,36 +684,113 @@ function uint8ArrayToBase64(bytes) {
684
684
  }
685
685
  var algosdk = null;
686
686
  var PeraWalletConnect = null;
687
+ var LuteConnect = null;
687
688
  async function loadAlgorandDeps() {
688
689
  if (!algosdk) {
689
690
  algosdk = await import('algosdk');
690
691
  }
692
+ }
693
+ async function loadPeraWallet() {
691
694
  if (!PeraWalletConnect) {
692
695
  const peraModule = await import('@perawallet/connect');
693
696
  PeraWalletConnect = peraModule.PeraWalletConnect;
694
697
  }
695
698
  }
699
+ async function loadLuteWallet() {
700
+ if (!LuteConnect) {
701
+ try {
702
+ const luteModule = await import('lute-connect');
703
+ LuteConnect = luteModule.default;
704
+ } catch {
705
+ LuteConnect = null;
706
+ }
707
+ }
708
+ }
709
+ function isLuteAvailable() {
710
+ if (typeof window === "undefined") return false;
711
+ const win = window;
712
+ return !!(win.algorand || win.lute);
713
+ }
696
714
  var AlgorandProvider = class {
697
- id = "pera";
698
- name = "Pera Wallet";
715
+ id = "algorand";
716
+ name = "Algorand Wallet";
699
717
  networkType = "algorand";
718
+ // Active wallet type
719
+ walletType = null;
720
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
721
+ luteWallet = null;
700
722
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
701
723
  peraWallet = null;
702
724
  address = null;
703
725
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
704
726
  algodClients = /* @__PURE__ */ new Map();
705
727
  /**
706
- * Check if Pera Wallet is available
707
- * Note: Pera works as a WalletConnect modal, so it's always "available"
728
+ * Check if any Algorand wallet is available
729
+ * Returns true if Lute extension is installed OR we can use Pera (always available via WalletConnect)
708
730
  */
709
731
  isAvailable() {
710
732
  return typeof window !== "undefined";
711
733
  }
712
734
  /**
713
- * Connect to Pera Wallet
735
+ * Get the name of the currently connected wallet
736
+ */
737
+ getWalletName() {
738
+ if (this.walletType === "lute") return "Lute Wallet";
739
+ if (this.walletType === "pera") return "Pera Wallet";
740
+ return "Algorand Wallet";
741
+ }
742
+ /**
743
+ * Connect to Algorand wallet
744
+ * Priority: Lute (desktop extension) > Pera (mobile via WalletConnect)
714
745
  */
715
746
  async connect(_chainName) {
716
747
  await loadAlgorandDeps();
748
+ if (isLuteAvailable()) {
749
+ try {
750
+ return await this.connectLute();
751
+ } catch (error) {
752
+ console.warn("Lute connection failed, falling back to Pera:", error);
753
+ }
754
+ }
755
+ return await this.connectPera();
756
+ }
757
+ /**
758
+ * Connect to Lute Wallet (desktop browser extension)
759
+ */
760
+ async connectLute() {
761
+ await loadLuteWallet();
762
+ if (!LuteConnect) {
763
+ throw new X402Error("Lute Wallet SDK not available", "WALLET_NOT_FOUND");
764
+ }
765
+ try {
766
+ this.luteWallet = new LuteConnect("402milly");
767
+ const genesisId = "mainnet-v1.0";
768
+ const accounts = await this.luteWallet.connect(genesisId);
769
+ if (!accounts || accounts.length === 0) {
770
+ throw new X402Error("No accounts returned from Lute Wallet", "WALLET_CONNECTION_REJECTED");
771
+ }
772
+ this.address = accounts[0];
773
+ this.walletType = "lute";
774
+ return accounts[0];
775
+ } catch (error) {
776
+ if (error instanceof X402Error) throw error;
777
+ if (error instanceof Error) {
778
+ if (error.message.includes("rejected") || error.message.includes("cancelled")) {
779
+ throw new X402Error("Connection rejected by user", "WALLET_CONNECTION_REJECTED");
780
+ }
781
+ }
782
+ throw new X402Error(
783
+ `Failed to connect Lute Wallet: ${error instanceof Error ? error.message : "Unknown error"}`,
784
+ "UNKNOWN_ERROR",
785
+ error
786
+ );
787
+ }
788
+ }
789
+ /**
790
+ * Connect to Pera Wallet (mobile via WalletConnect)
791
+ */
792
+ async connectPera() {
793
+ await loadPeraWallet();
717
794
  if (!PeraWalletConnect) {
718
795
  throw new X402Error("Failed to load Pera Wallet SDK", "WALLET_NOT_FOUND");
719
796
  }
@@ -722,6 +799,7 @@ var AlgorandProvider = class {
722
799
  const accounts = await this.peraWallet.reconnectSession();
723
800
  if (accounts.length > 0) {
724
801
  this.address = accounts[0];
802
+ this.walletType = "pera";
725
803
  return accounts[0];
726
804
  }
727
805
  const newAccounts = await this.peraWallet.connect();
@@ -729,8 +807,10 @@ var AlgorandProvider = class {
729
807
  throw new X402Error("No accounts returned from Pera Wallet", "WALLET_CONNECTION_REJECTED");
730
808
  }
731
809
  this.address = newAccounts[0];
810
+ this.walletType = "pera";
732
811
  this.peraWallet.connector?.on("disconnect", () => {
733
812
  this.address = null;
813
+ this.walletType = null;
734
814
  });
735
815
  return newAccounts[0];
736
816
  } catch (error) {
@@ -747,17 +827,21 @@ var AlgorandProvider = class {
747
827
  }
748
828
  }
749
829
  /**
750
- * Disconnect from Pera Wallet
830
+ * Disconnect from wallet
751
831
  */
752
832
  async disconnect() {
753
- if (this.peraWallet) {
833
+ if (this.walletType === "lute" && this.luteWallet) {
834
+ this.luteWallet = null;
835
+ }
836
+ if (this.walletType === "pera" && this.peraWallet) {
754
837
  try {
755
838
  await this.peraWallet.disconnect();
756
839
  } catch {
757
840
  }
841
+ this.peraWallet = null;
758
842
  }
759
- this.peraWallet = null;
760
843
  this.address = null;
844
+ this.walletType = null;
761
845
  this.algodClients.clear();
762
846
  }
763
847
  /**
@@ -793,15 +877,17 @@ var AlgorandProvider = class {
793
877
  }
794
878
  }
795
879
  /**
796
- * Create Algorand ASA transfer payment
880
+ * Create Algorand atomic group payment (GoPlausible x402-avm spec)
881
+ *
882
+ * Transaction structure (atomic group):
883
+ * - Transaction 0: Fee payment (UNSIGNED) - facilitator -> facilitator, covers all fees
884
+ * - Transaction 1: ASA transfer (SIGNED) - client -> merchant
797
885
  *
798
- * Transaction structure:
799
- * 1. ASA Transfer from user to recipient
800
- * 2. Facilitator pays transaction fees
886
+ * The facilitator signs transaction 0 and submits the complete atomic group.
801
887
  */
802
888
  async signPayment(paymentInfo, chainConfig) {
803
889
  await loadAlgorandDeps();
804
- if (!this.peraWallet || !this.address) {
890
+ if (!this.address || !this.walletType) {
805
891
  throw new X402Error("Wallet not connected", "WALLET_NOT_CONNECTED");
806
892
  }
807
893
  if (!algosdk) {
@@ -810,28 +896,85 @@ var AlgorandProvider = class {
810
896
  const algodClient = await this.getAlgodClient(chainConfig);
811
897
  const recipient = paymentInfo.recipients?.algorand || paymentInfo.recipient;
812
898
  const assetId = parseInt(chainConfig.usdc.address, 10);
899
+ const facilitatorAddress = paymentInfo.facilitator;
900
+ if (!facilitatorAddress) {
901
+ throw new X402Error(
902
+ "Facilitator address required for Algorand payments. Set paymentInfo.facilitator",
903
+ "PAYMENT_FAILED"
904
+ );
905
+ }
813
906
  const amount = Math.floor(parseFloat(paymentInfo.amount) * 1e6);
814
907
  try {
815
908
  const suggestedParams = await algodClient.getTransactionParams().do();
816
- const txn = algosdk.makeAssetTransferTxnWithSuggestedParamsFromObject({
909
+ const feeTxn = algosdk.makePaymentTxnWithSuggestedParamsFromObject({
910
+ sender: facilitatorAddress,
911
+ receiver: facilitatorAddress,
912
+ // self-transfer
913
+ amount: 0,
914
+ suggestedParams: {
915
+ ...suggestedParams,
916
+ fee: 2e3,
917
+ // Covers both transactions (1000 each)
918
+ flatFee: true
919
+ }
920
+ });
921
+ const paymentTxn = algosdk.makeAssetTransferTxnWithSuggestedParamsFromObject({
817
922
  sender: this.address,
818
923
  receiver: recipient,
819
924
  amount: BigInt(amount),
820
925
  assetIndex: assetId,
821
- suggestedParams,
926
+ suggestedParams: {
927
+ ...suggestedParams,
928
+ fee: 0,
929
+ // Fee paid by transaction 0
930
+ flatFee: true
931
+ },
822
932
  note: new TextEncoder().encode("x402 payment via uvd-x402-sdk")
823
933
  });
824
- const signedTxns = await this.peraWallet.signTransaction([[{ txn }]]);
825
- if (!signedTxns || signedTxns.length === 0) {
826
- throw new X402Error("No signed transaction returned", "SIGNATURE_REJECTED");
934
+ const txnGroup = algosdk.assignGroupID([feeTxn, paymentTxn]);
935
+ const unsignedFeeTxnBytes = algosdk.encodeUnsignedTransaction(txnGroup[0]);
936
+ const unsignedFeeTxnBase64 = uint8ArrayToBase64(unsignedFeeTxnBytes);
937
+ let signedPaymentTxnBytes;
938
+ if (this.walletType === "lute" && this.luteWallet) {
939
+ const feeTxnBase64 = uint8ArrayToBase64(txnGroup[0].toByte());
940
+ const paymentTxnBase64 = uint8ArrayToBase64(txnGroup[1].toByte());
941
+ const signedTxns = await this.luteWallet.signTxns([
942
+ { txn: feeTxnBase64, signers: [] },
943
+ // Don't sign - facilitator will
944
+ { txn: paymentTxnBase64 }
945
+ // Sign this one
946
+ ]);
947
+ if (!signedTxns || signedTxns.length < 2 || !signedTxns[1]) {
948
+ throw new X402Error("No signed transaction returned", "SIGNATURE_REJECTED");
949
+ }
950
+ const signedResult = signedTxns[1];
951
+ signedPaymentTxnBytes = this.decodeSignedTxn(signedResult);
952
+ } else if (this.walletType === "pera" && this.peraWallet) {
953
+ const signedTxns = await this.peraWallet.signTransaction([
954
+ [
955
+ { txn: txnGroup[0], signers: [] },
956
+ // Don't sign - facilitator will
957
+ { txn: txnGroup[1] }
958
+ // Sign this one
959
+ ]
960
+ ]);
961
+ if (!signedTxns || signedTxns.length < 2 || !signedTxns[1]) {
962
+ throw new X402Error("No signed transaction returned", "SIGNATURE_REJECTED");
963
+ }
964
+ signedPaymentTxnBytes = signedTxns[1];
965
+ } else {
966
+ throw new X402Error("No wallet available for signing", "WALLET_NOT_CONNECTED");
827
967
  }
828
- const signedTxn = signedTxns[0];
968
+ const signedPaymentTxnBase64 = uint8ArrayToBase64(signedPaymentTxnBytes);
829
969
  const payload = {
830
- from: this.address,
831
- to: recipient,
832
- amount: amount.toString(),
833
- assetId,
834
- signedTxn: uint8ArrayToBase64(signedTxn)
970
+ paymentIndex: 1,
971
+ // Index of the payment transaction in the group
972
+ paymentGroup: [
973
+ unsignedFeeTxnBase64,
974
+ // Transaction 0: unsigned fee tx
975
+ signedPaymentTxnBase64
976
+ // Transaction 1: signed payment tx
977
+ ]
835
978
  };
836
979
  return JSON.stringify(payload);
837
980
  } catch (error) {
@@ -850,6 +993,29 @@ var AlgorandProvider = class {
850
993
  );
851
994
  }
852
995
  }
996
+ /**
997
+ * Decode signed transaction from wallet response (handles various formats)
998
+ */
999
+ decodeSignedTxn(signedResult) {
1000
+ if (signedResult instanceof Uint8Array) {
1001
+ return signedResult;
1002
+ } else if (typeof signedResult === "string") {
1003
+ try {
1004
+ return Uint8Array.from(atob(signedResult), (c) => c.charCodeAt(0));
1005
+ } catch {
1006
+ const standardBase64 = signedResult.replace(/-/g, "+").replace(/_/g, "/");
1007
+ return Uint8Array.from(atob(standardBase64), (c) => c.charCodeAt(0));
1008
+ }
1009
+ } else if (ArrayBuffer.isView(signedResult)) {
1010
+ return new Uint8Array(
1011
+ signedResult.buffer,
1012
+ signedResult.byteOffset,
1013
+ signedResult.byteLength
1014
+ );
1015
+ } else {
1016
+ throw new X402Error("Unexpected signed transaction format", "PAYMENT_FAILED");
1017
+ }
1018
+ }
853
1019
  /**
854
1020
  * Encode Algorand payment as X-PAYMENT header
855
1021
  *
@@ -860,14 +1026,15 @@ var AlgorandProvider = class {
860
1026
  */
861
1027
  encodePaymentHeader(paymentPayload, chainConfig, version = 1) {
862
1028
  const payload = JSON.parse(paymentPayload);
863
- const networkName = chainConfig?.name || "algorand";
1029
+ let networkName;
1030
+ if (chainConfig?.name === "algorand-testnet") {
1031
+ networkName = "algorand-testnet";
1032
+ } else {
1033
+ networkName = "algorand-mainnet";
1034
+ }
864
1035
  const payloadData = {
865
- from: payload.from,
866
- to: payload.to,
867
- amount: payload.amount,
868
- assetId: payload.assetId,
869
- signedTxn: payload.signedTxn,
870
- ...payload.note && { note: payload.note }
1036
+ paymentIndex: payload.paymentIndex,
1037
+ paymentGroup: payload.paymentGroup
871
1038
  };
872
1039
  const x402Payload = version === 2 ? {
873
1040
  x402Version: 2,
@@ -879,7 +1046,6 @@ var AlgorandProvider = class {
879
1046
  x402Version: 1,
880
1047
  scheme: "exact",
881
1048
  network: networkName,
882
- // Plain chain name for v1
883
1049
  payload: payloadData
884
1050
  };
885
1051
  return btoa(JSON.stringify(x402Payload));