@chipi-stack/backend 14.15.1 → 14.16.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.
package/dist/index.js CHANGED
@@ -1958,9 +1958,9 @@ var ChipiTransactions = class {
1958
1958
  /**
1959
1959
  * Get on-chain transaction status
1960
1960
  */
1961
- async getTransactionStatus(hash11, bearerToken) {
1961
+ async getTransactionStatus(hash13, bearerToken) {
1962
1962
  return this.client.get({
1963
- endpoint: `${shared.API_ENDPOINTS.TRANSACTIONS}/${hash11}/status`,
1963
+ endpoint: `${shared.API_ENDPOINTS.TRANSACTIONS}/${hash13}/status`,
1964
1964
  bearerToken
1965
1965
  });
1966
1966
  }
@@ -3547,6 +3547,122 @@ var ChipiAi = class {
3547
3547
  });
3548
3548
  }
3549
3549
  };
3550
+ var STARKNET_SIGNATURE_VALID = "0x56414c4944";
3551
+ function toFeltHex(value) {
3552
+ return starknet.num.toHex(BigInt(value));
3553
+ }
3554
+ function normalizeStarknetAddress(address) {
3555
+ return `0x${BigInt(address).toString(16).padStart(64, "0")}`;
3556
+ }
3557
+ function isContractNotFound(err) {
3558
+ const e = err;
3559
+ if (e?.baseError?.code === 20 || e?.code === 20) return true;
3560
+ return /contract not found/i.test(e?.message ?? String(err));
3561
+ }
3562
+ async function isValidStarknetSignature(provider, accountAddress, messageHash, signature) {
3563
+ try {
3564
+ const sig = signature.map(toFeltHex);
3565
+ const result = await provider.callContract({
3566
+ contractAddress: normalizeStarknetAddress(accountAddress),
3567
+ entrypoint: "is_valid_signature",
3568
+ calldata: [toFeltHex(messageHash), toFeltHex(sig.length), ...sig]
3569
+ });
3570
+ const first = result?.[0];
3571
+ if (first !== void 0 && BigInt(first) === BigInt(STARKNET_SIGNATURE_VALID)) {
3572
+ return { valid: true };
3573
+ }
3574
+ return { valid: false, reason: "INVALID_SIGNATURE" };
3575
+ } catch (err) {
3576
+ if (isContractNotFound(err)) return { valid: false, reason: "ACCOUNT_NOT_DEPLOYED" };
3577
+ return {
3578
+ valid: false,
3579
+ reason: "INVALID_SIGNATURE",
3580
+ detail: err instanceof Error ? err.message : String(err)
3581
+ };
3582
+ }
3583
+ }
3584
+ var COUNTERFACTUAL_ACCOUNT_CLASSES = {
3585
+ /** OpenZeppelin AccountUpgradeable 3.0 (Bramble's default account). */
3586
+ OPENZEPPELIN_V3: "0x01d1777db36cdd06dd62cfde77b1b6ae06412af95d57a13dc40ac77b8a702381",
3587
+ /** Argent X / Ready 0.4 (`WALLET_CLASS_HASHES.READY`). */
3588
+ ARGENT_V0_4: "0x036078334509b514626504edc9fb252328d1a240e4e948bef8d0c08dff45927f"
3589
+ };
3590
+ function starkVerify(r, s, messageHash, publicKey) {
3591
+ const x = BigInt(publicKey).toString(16).padStart(64, "0");
3592
+ const sig = new starknet.ec.starkCurve.Signature(BigInt(r), BigInt(s));
3593
+ for (const prefix of ["02", "03"]) {
3594
+ try {
3595
+ if (starknet.ec.starkCurve.verify(sig, toFeltHex(messageHash), prefix + x)) return true;
3596
+ } catch {
3597
+ }
3598
+ }
3599
+ return false;
3600
+ }
3601
+ function verifyUndeployedStarknetSignature(deployment, messageHash, signature) {
3602
+ const classHash = normalizeStarknetAddress(deployment.class_hash);
3603
+ const calldata = deployment.calldata.map(toFeltHex);
3604
+ const sig = signature.map(toFeltHex);
3605
+ let publicKey;
3606
+ let r;
3607
+ let s;
3608
+ if (classHash === COUNTERFACTUAL_ACCOUNT_CLASSES.OPENZEPPELIN_V3) {
3609
+ if (calldata.length !== 1 || sig.length !== 2) {
3610
+ return { valid: false, reason: "INVALID_SIGNATURE" };
3611
+ }
3612
+ [publicKey] = calldata;
3613
+ [r, s] = sig;
3614
+ } else if (classHash === COUNTERFACTUAL_ACCOUNT_CLASSES.ARGENT_V0_4) {
3615
+ if (calldata.length !== 3 || BigInt(calldata[0]) !== 0n || BigInt(calldata[2]) !== 1n) {
3616
+ return { valid: false, reason: "UNSUPPORTED_ACCOUNT_CLASS" };
3617
+ }
3618
+ if (sig.length !== 5 || BigInt(sig[0]) !== 1n || BigInt(sig[1]) !== 0n) {
3619
+ return { valid: false, reason: "INVALID_SIGNATURE" };
3620
+ }
3621
+ if (BigInt(sig[2]) !== BigInt(calldata[1])) {
3622
+ return { valid: false, reason: "INVALID_SIGNATURE" };
3623
+ }
3624
+ publicKey = calldata[1];
3625
+ [, , , r, s] = sig;
3626
+ } else {
3627
+ return { valid: false, reason: "UNSUPPORTED_ACCOUNT_CLASS" };
3628
+ }
3629
+ const expected = starknet.hash.calculateContractAddressFromHash(
3630
+ toFeltHex(deployment.salt),
3631
+ classHash,
3632
+ calldata,
3633
+ 0
3634
+ );
3635
+ if (normalizeStarknetAddress(expected) !== normalizeStarknetAddress(deployment.address)) {
3636
+ return { valid: false, reason: "ADDRESS_MISMATCH" };
3637
+ }
3638
+ return starkVerify(r, s, messageHash, publicKey) ? { valid: true } : { valid: false, reason: "INVALID_SIGNATURE" };
3639
+ }
3640
+
3641
+ // src/x402/facilitator.ts
3642
+ var TRANSFER_SELECTOR2 = BigInt(starknet.hash.getSelectorFromName("transfer"));
3643
+ function shhhTransferMatches(oeCalldata, payload) {
3644
+ try {
3645
+ const felts = oeCalldata.map((v) => BigInt(v));
3646
+ const [, , , executeBefore, callsLen] = felts;
3647
+ if (callsLen !== 1n) return { ok: false, reason: "SHHH payment must be exactly one transfer" };
3648
+ const [to, selector, len, recipient, low, high] = felts.slice(5, 11);
3649
+ if (to !== BigInt(payload.asset) || selector !== TRANSFER_SELECTOR2 || len !== 3n) {
3650
+ return { ok: false, reason: "SHHH payment call is not a transfer of the payment asset" };
3651
+ }
3652
+ if (recipient !== BigInt(payload.toAddress)) {
3653
+ return { ok: false, reason: "SHHH payment pays a different recipient" };
3654
+ }
3655
+ if (low + (high << 128n) !== BigInt(payload.amount)) {
3656
+ return { ok: false, reason: "SHHH payment amount differs from the declared amount" };
3657
+ }
3658
+ if (executeBefore <= BigInt(Math.floor(Date.now() / 1e3))) {
3659
+ return { ok: false, reason: "SHHH payment has expired (execute_before in the past)" };
3660
+ }
3661
+ return { ok: true };
3662
+ } catch {
3663
+ return { ok: false, reason: "Malformed SHHH oeCalldata" };
3664
+ }
3665
+ }
3550
3666
  var InMemoryNonceStore = class {
3551
3667
  constructor() {
3552
3668
  this.used = /* @__PURE__ */ new Set();
@@ -3589,11 +3705,18 @@ var X402Facilitator = class _X402Facilitator {
3589
3705
  * Verify a payment signature is valid (doesn't execute the transfer).
3590
3706
  *
3591
3707
  * Checks:
3592
- * 1. Nonce has not been used (replay protection)
3593
- * 2. Payment has not expired (validUntil > now)
3594
- * 3. Asset is USDC mainnet
3595
- * 4. Amount is positive
3596
- * 5. Addresses are non-empty
3708
+ * 1. x402 version is 1
3709
+ * 2. Network is starknet-mainnet
3710
+ * 3. Nonce has not been used (replay protection)
3711
+ * 4. Payment has not expired (validUntil > now)
3712
+ * 5. Asset is USDC mainnet
3713
+ * 6. Amount is positive
3714
+ * 7. Addresses are non-empty
3715
+ * 8. Signature: with a `provider`, the payer account's `is_valid_signature`
3716
+ * must return 'VALID'. Without one, only its presence is checked (not a
3717
+ * cryptographic check). SHHH payloads must sign exactly one transfer
3718
+ * of `amount` of `asset` to `toAddress`; their signature is checked
3719
+ * on-chain at settle.
3597
3720
  */
3598
3721
  async verify(payment) {
3599
3722
  const { payload } = payment;
@@ -3649,6 +3772,8 @@ var X402Facilitator = class _X402Facilitator {
3649
3772
  if (!Array.isArray(oeCalldata) || oeCalldata.length === 0) {
3650
3773
  return { valid: false, reason: "Missing SHHH oeCalldata" };
3651
3774
  }
3775
+ const bound = shhhTransferMatches(oeCalldata, payment.payload);
3776
+ if (!bound.ok) return { valid: false, reason: bound.reason };
3652
3777
  return { valid: true };
3653
3778
  }
3654
3779
  if (!this.provider) {
@@ -3669,22 +3794,25 @@ var X402Facilitator = class _X402Facilitator {
3669
3794
  valid_until: String(payload.validUntil),
3670
3795
  nonce: payload.nonce
3671
3796
  });
3672
- const msgHash = starknet.typedData.getMessageHash(paymentTypedData, payload.fromAddress);
3797
+ let msgHash;
3673
3798
  try {
3674
- const result = await this.provider.callContract({
3675
- contractAddress: payload.fromAddress,
3676
- entrypoint: "is_valid_signature",
3677
- calldata: [msgHash, "2", payload.signature.r, payload.signature.s]
3678
- });
3679
- const VALID = "0x56414c4944";
3680
- if (result[0] === VALID || result[0] === BigInt(VALID).toString()) {
3681
- return { valid: true };
3682
- }
3683
- return { valid: false, reason: "Invalid SNIP-12 signature" };
3799
+ msgHash = starknet.typedData.getMessageHash(paymentTypedData, payload.fromAddress);
3684
3800
  } catch (err) {
3685
3801
  const message = err instanceof Error ? err.message : String(err);
3686
- return { valid: false, reason: `Signature verification failed: ${message}` };
3802
+ return { valid: false, reason: `Malformed payment: ${message}` };
3687
3803
  }
3804
+ const check = await isValidStarknetSignature(this.provider, payload.fromAddress, msgHash, [
3805
+ payload.signature.r,
3806
+ payload.signature.s
3807
+ ]);
3808
+ if (check.valid) return { valid: true };
3809
+ if (check.reason === "ACCOUNT_NOT_DEPLOYED") {
3810
+ return { valid: false, reason: "Payer account is not deployed" };
3811
+ }
3812
+ return {
3813
+ valid: false,
3814
+ reason: check.detail ? `Signature verification failed: ${check.detail}` : "Invalid SNIP-12 signature"
3815
+ };
3688
3816
  }
3689
3817
  /**
3690
3818
  * Settle a verified payment by executing the USDC transfer via paymaster.
@@ -3813,7 +3941,7 @@ var signTypedData = async ({
3813
3941
  throw error;
3814
3942
  }
3815
3943
  };
3816
- function toFeltHex(x) {
3944
+ function toFeltHex2(x) {
3817
3945
  return "0x" + x.toString(16).padStart(64, "0");
3818
3946
  }
3819
3947
  var signTypedDataArray = async ({
@@ -3843,7 +3971,7 @@ var signTypedDataArray = async ({
3843
3971
  messageHash,
3844
3972
  ownerIndex: 0n
3845
3973
  });
3846
- return envelope.map(toFeltHex);
3974
+ return envelope.map(toFeltHex2);
3847
3975
  }
3848
3976
  const provider = new starknet.RpcProvider({ nodeUrl: shared.WALLET_RPC_ENDPOINTS[walletType] });
3849
3977
  const account = new starknet.Account({ provider, address: wallet.publicKey, signer: privateKeyDecrypted });
@@ -4128,8 +4256,8 @@ var ChipiSDK = class {
4128
4256
  /**
4129
4257
  * Get on-chain transaction status (supports polling for confirmation)
4130
4258
  */
4131
- async getTransactionStatus(hash11, bearerToken) {
4132
- return this.transactions.getTransactionStatus(hash11, this.resolveBearerToken(bearerToken));
4259
+ async getTransactionStatus(hash13, bearerToken) {
4260
+ return this.transactions.getTransactionStatus(hash13, this.resolveBearerToken(bearerToken));
4133
4261
  }
4134
4262
  /**
4135
4263
  * Prepare a wallet upgrade (e.g., READY → CHIPI or CHIPI v29 → v33).
@@ -4501,6 +4629,106 @@ function x402Middleware(config) {
4501
4629
  next();
4502
4630
  };
4503
4631
  }
4632
+ var SIWS_DOMAIN_NAME = "Sign in with Starknet";
4633
+ var SIWS_DOMAIN_VERSION = "1";
4634
+ var SIWS_CHAIN_ID = "SN_MAIN";
4635
+ var DEFAULT_STATEMENT = "Sign in. This request costs nothing and moves no funds.";
4636
+ function assertPrintableAscii(field, value) {
4637
+ if (!/^[\x20-\x7e]*$/.test(value)) {
4638
+ throw new Error(`SIWS ${field} must be single-line printable ASCII`);
4639
+ }
4640
+ }
4641
+ function createStarknetSignInTypedData(params) {
4642
+ const message = {
4643
+ domain: params.domain,
4644
+ address: normalizeStarknetAddress(params.address),
4645
+ statement: params.statement ?? DEFAULT_STATEMENT,
4646
+ uri: params.uri,
4647
+ nonce: params.nonce,
4648
+ issuedAt: params.issuedAt ?? Math.floor(Date.now() / 1e3),
4649
+ expirationTime: params.expirationTime
4650
+ };
4651
+ for (const field of ["domain", "statement", "uri", "nonce"]) {
4652
+ assertPrintableAscii(field, message[field]);
4653
+ }
4654
+ if (!/^[A-Za-z0-9]{8,}$/.test(message.nonce)) {
4655
+ throw new Error("SIWS nonce must be at least 8 letters or digits");
4656
+ }
4657
+ if (!Number.isInteger(message.issuedAt) || !Number.isInteger(message.expirationTime)) {
4658
+ throw new Error("SIWS times must be whole unix seconds");
4659
+ }
4660
+ if (message.expirationTime <= message.issuedAt) {
4661
+ throw new Error("SIWS expirationTime must be after issuedAt");
4662
+ }
4663
+ return {
4664
+ types: {
4665
+ StarknetDomain: [
4666
+ { name: "name", type: "shortstring" },
4667
+ { name: "version", type: "shortstring" },
4668
+ { name: "chainId", type: "shortstring" },
4669
+ { name: "revision", type: "shortstring" }
4670
+ ],
4671
+ SignIn: [
4672
+ { name: "domain", type: "string" },
4673
+ { name: "address", type: "ContractAddress" },
4674
+ { name: "statement", type: "string" },
4675
+ { name: "uri", type: "string" },
4676
+ { name: "nonce", type: "string" },
4677
+ { name: "issuedAt", type: "timestamp" },
4678
+ { name: "expirationTime", type: "timestamp" }
4679
+ ]
4680
+ },
4681
+ primaryType: "SignIn",
4682
+ domain: {
4683
+ name: SIWS_DOMAIN_NAME,
4684
+ version: SIWS_DOMAIN_VERSION,
4685
+ chainId: SIWS_CHAIN_ID,
4686
+ revision: "1"
4687
+ },
4688
+ message: { ...message }
4689
+ };
4690
+ }
4691
+ var MAX_SKEW_SECONDS = 60;
4692
+ async function verifyStarknetSignIn(params) {
4693
+ const { typedData: typedData2, signature, provider, expected, deploymentData } = params;
4694
+ const raw = typedData2?.message;
4695
+ if (!raw || !Array.isArray(signature) || signature.length === 0) {
4696
+ return { ok: false, reason: "MALFORMED" };
4697
+ }
4698
+ if (typedData2.domain?.chainId !== SIWS_CHAIN_ID) return { ok: false, reason: "WRONG_CHAIN" };
4699
+ let canonical;
4700
+ let message;
4701
+ try {
4702
+ canonical = createStarknetSignInTypedData({
4703
+ domain: String(raw.domain),
4704
+ uri: String(raw.uri),
4705
+ address: String(raw.address),
4706
+ nonce: String(raw.nonce),
4707
+ statement: String(raw.statement),
4708
+ issuedAt: Number(raw.issuedAt),
4709
+ expirationTime: Number(raw.expirationTime)
4710
+ });
4711
+ message = canonical.message;
4712
+ } catch {
4713
+ return { ok: false, reason: "MALFORMED" };
4714
+ }
4715
+ if (message.domain !== expected.domain) return { ok: false, reason: "WRONG_DOMAIN" };
4716
+ if (message.uri !== expected.uri) return { ok: false, reason: "WRONG_URI" };
4717
+ if (message.nonce !== expected.nonce) return { ok: false, reason: "WRONG_NONCE" };
4718
+ const now = expected.now ?? Math.floor(Date.now() / 1e3);
4719
+ if (now >= message.expirationTime) return { ok: false, reason: "EXPIRED" };
4720
+ if (message.issuedAt > now + MAX_SKEW_SECONDS) return { ok: false, reason: "NOT_YET_VALID" };
4721
+ const messageHash = starknet.typedData.getMessageHash(canonical, message.address);
4722
+ const onChain = await isValidStarknetSignature(provider, message.address, messageHash, signature);
4723
+ if (onChain.valid) return { ok: true, address: message.address, message };
4724
+ if (onChain.reason !== "ACCOUNT_NOT_DEPLOYED") return { ok: false, reason: "INVALID_SIGNATURE" };
4725
+ if (!deploymentData) return { ok: false, reason: "ACCOUNT_NOT_DEPLOYED" };
4726
+ if (normalizeStarknetAddress(deploymentData.address) !== message.address) {
4727
+ return { ok: false, reason: "ADDRESS_MISMATCH" };
4728
+ }
4729
+ const offChain = verifyUndeployedStarknetSignature(deploymentData, messageHash, signature);
4730
+ return offChain.valid ? { ok: true, address: message.address, message } : { ok: false, reason: offChain.reason };
4731
+ }
4504
4732
 
4505
4733
  // src/shhh/index.ts
4506
4734
  init_snip12_hash();
@@ -5413,6 +5641,7 @@ Object.defineProperty(exports, "Currency", {
5413
5641
  });
5414
5642
  exports.APPLE_ISSUER = APPLE_ISSUER;
5415
5643
  exports.BOOTSTRAP_DOMAIN_SEPARATOR = BOOTSTRAP_DOMAIN_SEPARATOR;
5644
+ exports.COUNTERFACTUAL_ACCOUNT_CLASSES = COUNTERFACTUAL_ACCOUNT_CLASSES;
5416
5645
  exports.ChipiAi = ChipiAi;
5417
5646
  exports.ChipiBrowserSDK = ChipiBrowserSDK;
5418
5647
  exports.ChipiClient = ChipiClient;
@@ -5432,6 +5661,10 @@ exports.OP_ROTATE_OWNER = OP_ROTATE_OWNER;
5432
5661
  exports.OP_SET_THRESHOLD = OP_SET_THRESHOLD;
5433
5662
  exports.ROLE_GUARDIAN = ROLE_GUARDIAN;
5434
5663
  exports.ROLE_OWNER = ROLE_OWNER;
5664
+ exports.SIWS_CHAIN_ID = SIWS_CHAIN_ID;
5665
+ exports.SIWS_DOMAIN_NAME = SIWS_DOMAIN_NAME;
5666
+ exports.SIWS_DOMAIN_VERSION = SIWS_DOMAIN_VERSION;
5667
+ exports.STARKNET_SIGNATURE_VALID = STARKNET_SIGNATURE_VALID;
5435
5668
  exports.SessionTxVerifier = SessionTxVerifier;
5436
5669
  exports.ShhhAccount = ShhhAccount;
5437
5670
  exports.TIMELOCK_SECONDS = TIMELOCK_SECONDS;
@@ -5476,6 +5709,7 @@ exports.computeISignerId = computeISignerId;
5476
5709
  exports.computeOeHash = computeOeHash;
5477
5710
  exports.computeShhhAddress = computeShhhAddress;
5478
5711
  exports.computeSnip12Hash = computeSnip12Hash;
5712
+ exports.createStarknetSignInTypedData = createStarknetSignInTypedData;
5479
5713
  exports.ed25519OwnerId = ed25519OwnerId;
5480
5714
  exports.ed25519OwnerPubkeyParam = ed25519OwnerPubkeyParam;
5481
5715
  exports.ed25519PubkeyFelts = ed25519PubkeyFelts;
@@ -5491,6 +5725,7 @@ exports.hashOutsideExecutionStruct = hashOutsideExecutionStruct;
5491
5725
  exports.hashStarknetDomain = hashStarknetDomain;
5492
5726
  exports.isReadyToExecute = isReadyToExecute;
5493
5727
  exports.isThinkDecision = isThinkDecision;
5728
+ exports.isValidStarknetSignature = isValidStarknetSignature;
5494
5729
  exports.jwtAppleSubHash = jwtAppleSubHash;
5495
5730
  exports.jwtAppleSubOwnerId = jwtAppleSubOwnerId;
5496
5731
  exports.jwtAppleSubPubkeyFelts = jwtAppleSubPubkeyFelts;
@@ -5499,6 +5734,7 @@ exports.locateSubClaim = locateSubClaim;
5499
5734
  exports.messageHashToNonceB64url = messageHashToNonceB64url;
5500
5735
  exports.migrateChipiWalletToShhh = migrateChipiWalletToShhh;
5501
5736
  exports.normalizeEntrypoints = normalizeEntrypoints;
5737
+ exports.normalizeStarknetAddress = normalizeStarknetAddress;
5502
5738
  exports.ownerCommitment = ownerCommitment;
5503
5739
  exports.parseAppleIdToken = parseAppleIdToken;
5504
5740
  exports.parseDerEcdsaSignature = parseDerEcdsaSignature;
@@ -5515,6 +5751,8 @@ exports.starkOwnerId = starkOwnerId;
5515
5751
  exports.starkPubkeyFelts = starkPubkeyFelts;
5516
5752
  exports.submitViaAccount = submitViaAccount;
5517
5753
  exports.validateSpendingPolicyConfig = validateSpendingPolicyConfig;
5754
+ exports.verifyStarknetSignIn = verifyStarknetSignIn;
5755
+ exports.verifyUndeployedStarknetSignature = verifyUndeployedStarknetSignature;
5518
5756
  exports.waitForTransaction = waitForTransaction;
5519
5757
  exports.webauthnOwnerId = webauthnOwnerId;
5520
5758
  exports.webauthnOwnerPubkeyParam = webauthnOwnerPubkeyParam;