@kwespay/widget 1.0.8 → 1.0.10

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.
@@ -7,15 +7,16 @@ class KwesPayError extends Error {
7
7
  }
8
8
  }
9
9
 
10
- const ENDPOINT = "https://d502-154-161-98-26.ngrok-free.app/graphql";
10
+ const ENDPOINT = "https://839e-154-161-230-28.ngrok-free.app/graphql";
11
11
  const TESTNET_CONTRACTS = {
12
- sepolia: "0x39bE436D6A34d0990cb71c9cBD24a5361d85e00B",
13
- baseSepolia: "0x7515b1b1BcA33E7a9ccBd5E2b93771884654De77",
14
- polygonAmoy: "0xD31dF3eBd220Fd3e190A346F8927819295d28980",
15
- liskTestnet: "0xd04A78a998146EBAD04c2b68E020C06Dc3b3717f",
12
+ sepolia: "0xFC0A3A00008B79f080648A12FFb07dDE3C34F789",
13
+ baseSepolia: "0x8662EEA4eACCAef3e5c33c50Ac3a2b9B1B0033c7",
14
+ polygonAmoy: "0xc46E4D2005014990F600741A2Ce189f8118CB58e",
15
+ liskTestnet: "0xe0daD0d9e7413AA06859d26A552fD6a07855B070",
16
+ mezoTestnet: "0xa430B2e0D1273464809f8541286058e90781DA9C",
16
17
  };
17
18
  const MAINNET_CONTRACTS = {
18
- // lisk: "0x...",
19
+ // lisk: "0x...",
19
20
  };
20
21
  const CONTRACT_ADDRESSES = {
21
22
  ...TESTNET_CONTRACTS,
@@ -105,6 +106,7 @@ const GQL_CREATE_TRANSACTION = `
105
106
  tokenAddress
106
107
  amountBaseUnits
107
108
  chainId
109
+ deadline
108
110
  expiresAt
109
111
  transaction {
110
112
  transactionReference
@@ -2624,7 +2626,7 @@ function encodeFunctionData(parameters) {
2624
2626
 
2625
2627
  const ZERO_ADDRESS$1 = "0x0000000000000000000000000000000000000000";
2626
2628
  const PAYMENT_ABI = parseAbi([
2627
- "function createPayment(bytes32 paymentId, string vendorId, address token, uint256 amount, bytes backendSignature) external payable",
2629
+ "function createPayment(bytes32 paymentId, string vendorId, address token, uint256 amount, uint256 deadline, bytes backendSignature) external payable",
2628
2630
  ]);
2629
2631
  const ERC20_ABI = parseAbi([
2630
2632
  "function allowance(address owner, address spender) view returns (uint256)",
@@ -2724,6 +2726,7 @@ class ContractService {
2724
2726
  params.vendorIdentifier,
2725
2727
  ensure0x(params.tokenAddress),
2726
2728
  amount,
2729
+ BigInt(params.deadline),
2727
2730
  ensure0x(params.backendSignature),
2728
2731
  ],
2729
2732
  });
@@ -2734,7 +2737,7 @@ class ContractService {
2734
2737
  data,
2735
2738
  };
2736
2739
  if (isNative)
2737
- txParams["value"] = `0x${amount.toString(16)}`;
2740
+ txParams["value"] = `0x${(amount + (amount * 50n) / 10000n).toString(16)}`;
2738
2741
  let txHash;
2739
2742
  try {
2740
2743
  txHash = (await this.provider.request({
@@ -2803,8 +2806,8 @@ class PaymentService {
2803
2806
  throw new KwesPayError(`Wallet is on chain ${confirmed} but payment requires chain ${expectedChainId} (${expectedNetwork}). Please switch your network and retry.`, "WRONG_NETWORK");
2804
2807
  }
2805
2808
  }
2806
- async ensureSufficientBalance(walletAddress, tokenAddress, amountBaseUnits, onStatus) {
2807
- const amount = BigInt(amountBaseUnits);
2809
+ async ensureSufficientBalance(walletAddress, tokenAddress, totalBaseUnits, onStatus) {
2810
+ const total = BigInt(totalBaseUnits);
2808
2811
  const isNative = tokenAddress === ZERO_ADDRESS;
2809
2812
  onStatus?.("Checking balance", "Verifying wallet balance…");
2810
2813
  const nativeRaw = (await this.provider.request({
@@ -2813,14 +2816,14 @@ class PaymentService {
2813
2816
  }));
2814
2817
  const nativeBalance = nativeRaw && nativeRaw !== "0x" ? BigInt(nativeRaw) : 0n;
2815
2818
  if (isNative) {
2816
- const required = amount + GAS_BUFFER;
2819
+ const required = total + GAS_BUFFER;
2817
2820
  if (nativeBalance < required) {
2818
- throw new KwesPayError(`Insufficient native balance. Required ~${(Number(required) / 1e18).toFixed(8)} ETH (payment + gas), available: ${(Number(nativeBalance) / 1e18).toFixed(8)} ETH.`, "INSUFFICIENT_BALANCE");
2821
+ throw new KwesPayError(`Insufficient native balance. Required ~${(Number(required) / 1e18).toFixed(8)} (payment + fee + gas), available: ${(Number(nativeBalance) / 1e18).toFixed(8)}.`, "INSUFFICIENT_BALANCE");
2819
2822
  }
2820
2823
  return;
2821
2824
  }
2822
2825
  if (nativeBalance < GAS_BUFFER) {
2823
- throw new KwesPayError(`Insufficient gas balance. Need at least ${(Number(GAS_BUFFER) / 1e18).toFixed(8)} ETH for gas, available: ${(Number(nativeBalance) / 1e18).toFixed(8)} ETH.`, "INSUFFICIENT_BALANCE");
2826
+ throw new KwesPayError(`Insufficient gas balance. Need at least ${(Number(GAS_BUFFER) / 1e18).toFixed(8)} for gas, available: ${(Number(nativeBalance) / 1e18).toFixed(8)}.`, "INSUFFICIENT_BALANCE");
2824
2827
  }
2825
2828
  const balanceData = "0x70a08231" + walletAddress.slice(2).toLowerCase().padStart(64, "0");
2826
2829
  const raw = (await this.provider.request({
@@ -2828,33 +2831,44 @@ class PaymentService {
2828
2831
  params: [{ to: tokenAddress, data: balanceData }, "latest"],
2829
2832
  }));
2830
2833
  const tokenBalance = raw && raw !== "0x" ? BigInt(raw) : 0n;
2831
- if (tokenBalance < amount) {
2832
- throw new KwesPayError(`Insufficient token balance. Required: ${amount.toString()}, available: ${tokenBalance.toString()} (base units).`, "INSUFFICIENT_BALANCE");
2834
+ if (tokenBalance < total) {
2835
+ throw new KwesPayError(`Insufficient token balance. Required: ${total.toString()} (amount + fee), available: ${tokenBalance.toString()} (base units).`, "INSUFFICIENT_BALANCE");
2833
2836
  }
2834
2837
  }
2835
2838
  async pay(params) {
2836
- const { provider, payload, onStatus } = params;
2839
+ const { payload, onStatus } = params;
2837
2840
  if (!payload.paymentIdBytes32 || !payload.backendSignature) {
2838
2841
  throw new KwesPayError("Invalid transaction payload — missing paymentIdBytes32 or backendSignature", "TRANSACTION_FAILED");
2839
2842
  }
2840
2843
  if (!payload.vendorIdentifier) {
2841
2844
  throw new KwesPayError("Invalid transaction payload — missing vendorIdentifier", "TRANSACTION_FAILED");
2842
2845
  }
2846
+ if (!payload.totalBaseUnits) {
2847
+ throw new KwesPayError("Invalid transaction payload — missing totalBaseUnits", "TRANSACTION_FAILED");
2848
+ }
2849
+ if (!payload.deadline) {
2850
+ throw new KwesPayError("Invalid transaction payload — missing deadline", "TRANSACTION_FAILED");
2851
+ }
2852
+ if (BigInt(payload.totalBaseUnits) < BigInt(payload.amountBaseUnits)) {
2853
+ throw new KwesPayError("Invalid transaction payload — totalBaseUnits cannot be less than amountBaseUnits", "TRANSACTION_FAILED");
2854
+ }
2843
2855
  await this.ensureCorrectNetwork(payload.chainId, payload.network, onStatus);
2844
2856
  const accounts = (await this.provider.request({
2845
2857
  method: "eth_requestAccounts",
2846
2858
  }));
2847
2859
  const walletAddress = accounts[0];
2848
- await this.ensureSufficientBalance(walletAddress, payload.tokenAddress, payload.amountBaseUnits, onStatus);
2860
+ await this.ensureSufficientBalance(walletAddress, payload.tokenAddress, payload.totalBaseUnits, onStatus);
2849
2861
  const contractAddress = resolveContractAddress(payload.network);
2850
- await this.contractService.ensureApproval(payload.tokenAddress, payload.amountBaseUnits, contractAddress, payload.chainId, onStatus);
2862
+ await this.contractService.ensureApproval(payload.tokenAddress, payload.totalBaseUnits, contractAddress, payload.chainId, onStatus);
2851
2863
  const { txHash, blockNumber } = await this.contractService.createPayment({
2852
2864
  paymentIdBytes32: payload.paymentIdBytes32,
2853
2865
  vendorIdentifier: payload.vendorIdentifier,
2854
2866
  tokenAddress: payload.tokenAddress,
2855
2867
  amountBaseUnits: payload.amountBaseUnits,
2868
+ totalBaseUnits: payload.totalBaseUnits,
2856
2869
  backendSignature: payload.backendSignature,
2857
2870
  contractAddress,
2871
+ deadline: payload.deadline,
2858
2872
  chainId: payload.chainId,
2859
2873
  }, onStatus);
2860
2874
  return {
@@ -2866,6 +2880,14 @@ class PaymentService {
2866
2880
  }
2867
2881
  }
2868
2882
 
2883
+ const PLATFORM_FEE_BPS = 50n; // 0.5%
2884
+ const BASIS_POINTS = 10000n;
2885
+ function computeFee(amountBaseUnits) {
2886
+ return (BigInt(amountBaseUnits) * PLATFORM_FEE_BPS) / BASIS_POINTS;
2887
+ }
2888
+ function computeTotal(amountBaseUnits) {
2889
+ return (BigInt(amountBaseUnits) + computeFee(amountBaseUnits)).toString();
2890
+ }
2869
2891
  class KwesPayClient {
2870
2892
  constructor(config) {
2871
2893
  if (!config.apiKey)
@@ -2897,6 +2919,7 @@ class KwesPayClient {
2897
2919
  },
2898
2920
  };
2899
2921
  }
2922
+ // getQuote (price preview only — no transaction created)
2900
2923
  async getQuote(params) {
2901
2924
  const data = await gqlRequest(GQL_CREATE_QUOTE, {
2902
2925
  input: {
@@ -2910,25 +2933,24 @@ class KwesPayClient {
2910
2933
  const q = data.createQuote;
2911
2934
  if (!q.success) {
2912
2935
  const msg = q.message ?? "Quote creation failed";
2913
- const code = msg.toLowerCase().includes("expired")
2914
- ? "QUOTE_EXPIRED"
2915
- : msg.toLowerCase().includes("key")
2916
- ? "INVALID_KEY"
2917
- : "UNKNOWN";
2918
- throw new KwesPayError(msg, code);
2936
+ throw new KwesPayError(msg, _quoteErrCode(msg));
2919
2937
  }
2920
2938
  return {
2921
2939
  quoteId: q.quoteId,
2922
2940
  cryptoCurrency: q.cryptoCurrency,
2923
2941
  tokenAddress: q.tokenAddress,
2924
2942
  amountBaseUnits: q.amountBaseUnits,
2943
+ // totalBaseUnits = amountBaseUnits + fee — safe to compute here for UI
2944
+ totalBaseUnits: computeTotal(q.amountBaseUnits),
2925
2945
  displayAmount: q.displayAmount,
2926
2946
  network: q.network,
2927
2947
  chainId: q.chainId,
2928
2948
  expiresAt: q.expiresAt,
2929
2949
  };
2930
2950
  }
2951
+ // quote() — full flow: price + transaction, returns wallet-ready payload
2931
2952
  async quote(params) {
2953
+ // Step 1 — get price & lock quote
2932
2954
  const quoteData = await gqlRequest(GQL_CREATE_QUOTE, {
2933
2955
  input: {
2934
2956
  vendorIdentifier: params.vendorIdentifier,
@@ -2941,13 +2963,9 @@ class KwesPayClient {
2941
2963
  const q = quoteData.createQuote;
2942
2964
  if (!q.success) {
2943
2965
  const msg = q.message ?? "Quote creation failed";
2944
- const code = msg.toLowerCase().includes("expired")
2945
- ? "QUOTE_EXPIRED"
2946
- : msg.toLowerCase().includes("key")
2947
- ? "INVALID_KEY"
2948
- : "UNKNOWN";
2949
- throw new KwesPayError(msg, code);
2966
+ throw new KwesPayError(msg, _quoteErrCode(msg));
2950
2967
  }
2968
+ // Step 2 — create transaction (backend signs payment params incl. deadline)
2951
2969
  const txData = await gqlRequest(GQL_CREATE_TRANSACTION, {
2952
2970
  input: {
2953
2971
  quoteId: q.quoteId,
@@ -2957,23 +2975,27 @@ class KwesPayClient {
2957
2975
  const t = txData.createTransaction;
2958
2976
  if (!t.success) {
2959
2977
  const msg = t.message ?? "Transaction creation failed";
2960
- const code = msg.toLowerCase().includes("expired")
2961
- ? "QUOTE_EXPIRED"
2962
- : msg.toLowerCase().includes("already been used")
2963
- ? "QUOTE_USED"
2964
- : msg.toLowerCase().includes("not found")
2965
- ? "QUOTE_NOT_FOUND"
2966
- : msg.toLowerCase().includes("key")
2967
- ? "INVALID_KEY"
2968
- : "UNKNOWN";
2969
- throw new KwesPayError(msg, code);
2978
+ throw new KwesPayError(msg, _txErrCode(msg));
2979
+ }
2980
+ // deadline must be present — it's part of the on-chain signature
2981
+ if (!t.deadline) {
2982
+ throw new KwesPayError("Backend did not return a deadline. Cannot construct a valid payment.", "TRANSACTION_FAILED");
2970
2983
  }
2984
+ // totalBaseUnits: what the customer must actually send (amount + fee).
2985
+ // We compute this from the signed amountBaseUnits using the same formula
2986
+ // the contract uses: (amount * 50) / 10000. We do NOT trust a
2987
+ // client-side totalBaseUnits for security — but computing it here is safe
2988
+ // because amountBaseUnits came from the backend-signed response.
2989
+ const amountBaseUnits = t.amountBaseUnits;
2990
+ const totalBaseUnits = computeTotal(amountBaseUnits);
2971
2991
  return {
2972
2992
  paymentIdBytes32: t.paymentIdBytes32,
2973
2993
  backendSignature: t.backendSignature,
2974
2994
  tokenAddress: t.tokenAddress,
2975
- amountBaseUnits: t.amountBaseUnits,
2995
+ amountBaseUnits,
2996
+ totalBaseUnits,
2976
2997
  chainId: t.chainId,
2998
+ deadline: t.deadline, // ← from backend, part of signed hash
2977
2999
  expiresAt: t.expiresAt,
2978
3000
  transactionReference: t.transaction.transactionReference,
2979
3001
  transactionStatus: t.transaction.transactionStatus,
@@ -2981,13 +3003,13 @@ class KwesPayClient {
2981
3003
  vendorIdentifier: params.vendorIdentifier,
2982
3004
  };
2983
3005
  }
3006
+ // pay()
2984
3007
  async pay(params) {
2985
3008
  return new PaymentService(params.provider).pay(params);
2986
3009
  }
3010
+ // status polling
2987
3011
  async getTransactionStatus(transactionReference) {
2988
- const data = await gqlRequest(GQL_TRANSACTION_STATUS, {
2989
- transactionReference,
2990
- });
3012
+ const data = await gqlRequest(GQL_TRANSACTION_STATUS, { transactionReference });
2991
3013
  const r = data.getTransactionStatus;
2992
3014
  return {
2993
3015
  transactionReference: r.transactionReference,
@@ -3036,5 +3058,26 @@ class KwesPayClient {
3036
3058
  });
3037
3059
  }
3038
3060
  }
3061
+ // Error code helpers
3062
+ function _quoteErrCode(msg) {
3063
+ const m = msg.toLowerCase();
3064
+ if (m.includes("expired"))
3065
+ return "QUOTE_EXPIRED";
3066
+ if (m.includes("key"))
3067
+ return "INVALID_KEY";
3068
+ return "UNKNOWN";
3069
+ }
3070
+ function _txErrCode(msg) {
3071
+ const m = msg.toLowerCase();
3072
+ if (m.includes("expired"))
3073
+ return "QUOTE_EXPIRED";
3074
+ if (m.includes("already been used"))
3075
+ return "QUOTE_USED";
3076
+ if (m.includes("not found"))
3077
+ return "QUOTE_NOT_FOUND";
3078
+ if (m.includes("key"))
3079
+ return "INVALID_KEY";
3080
+ return "UNKNOWN";
3081
+ }
3039
3082
 
3040
3083
  export { KwesPayClient, KwesPayError };