@circle-fin/app-kit 1.17.0 → 1.18.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/earn.mjs CHANGED
@@ -2439,6 +2439,15 @@ class KitError extends Error {
2439
2439
  recoverability: 'RETRYABLE'
2440
2440
  }
2441
2441
  ],
2442
+ // DEPOSIT_INTENT_INVALID: the Veda deposit intent is missing, expired, or
2443
+ // not signed by the depositing wallet.
2444
+ [
2445
+ 380418,
2446
+ {
2447
+ errorDef: EarnError.SIGNATURE_REJECTED,
2448
+ recoverability: 'FATAL'
2449
+ }
2450
+ ],
2442
2451
  // Bridge (380_5XX)
2443
2452
  [
2444
2453
  380500,
@@ -3352,14 +3361,14 @@ var BorrowChain;
3352
3361
  symbol: 'USDC',
3353
3362
  // Arc uses native USDC with 18 decimals for gas payments (EVM standard).
3354
3363
  // Note: The ERC-20 USDC contract at usdcAddress uses 6 decimals.
3355
- // See: https://docs.arc.network/arc/references/contract-addresses
3364
+ // See: https://docs.arc.io/arc/references/contract-addresses
3356
3365
  decimals: 18
3357
3366
  },
3358
3367
  chainId: 5042002,
3359
3368
  isTestnet: true,
3360
3369
  explorerUrl: 'https://testnet.arcscan.app/tx/{hash}',
3361
3370
  rpcEndpoints: [
3362
- 'https://rpc.testnet.arc.network/'
3371
+ 'https://rpc.testnet.arc.io'
3363
3372
  ],
3364
3373
  eurcAddress: '0x89B50855Aa3bE2F677cD6303Cec089B5F319D72a',
3365
3374
  usdcAddress: '0x3600000000000000000000000000000000000000',
@@ -9554,7 +9563,7 @@ const swapTokenEnumSchema = z.enum([
9554
9563
  }
9555
9564
 
9556
9565
  var name$4 = "@circle-fin/bridge-kit";
9557
- var version$4 = "1.15.3";
9566
+ var version$4 = "1.16.0";
9558
9567
  var pkg$4 = {
9559
9568
  name: name$4,
9560
9569
  version: version$4};
@@ -11350,7 +11359,7 @@ const validateQuoteItemSchema = z.object({
11350
11359
  registerKit(`${pkg$4.name}/${pkg$4.version}`);
11351
11360
 
11352
11361
  var name$3 = "@circle-fin/swap-kit";
11353
- var version$3 = "1.8.0";
11362
+ var version$3 = "1.8.1";
11354
11363
  var pkg$3 = {
11355
11364
  name: name$3,
11356
11365
  version: version$3};
@@ -14611,6 +14620,47 @@ const abiParameterSchema = z.object({
14611
14620
  */ function hasSignTypedData(adapter) {
14612
14621
  return typeof adapter === 'object' && adapter !== null && 'signTypedData' in adapter && typeof adapter.signTypedData === 'function';
14613
14622
  }
14623
+ /**
14624
+ * Type guard to check if an adapter can actually produce an EIP-712
14625
+ * typed-data signature.
14626
+ *
14627
+ * @remarks
14628
+ * Strengthens {@link hasSignTypedData}: having a `signTypedData` method
14629
+ * does not guarantee it can succeed. Adapters whose signer is delegated
14630
+ * (e.g. through a signing strategy backed by a smart contract account)
14631
+ * expose the method but reject typed-data payloads at runtime. Such
14632
+ * adapters report their real capability through an optional
14633
+ * `supportsSignTypedData()` method, which this guard consults when
14634
+ * present. Adapters without the capability method are assumed able to
14635
+ * sign, preserving the previous duck-typing behavior.
14636
+ *
14637
+ * @param adapter - The adapter to check
14638
+ * @returns True if calling `signTypedData` can be expected to succeed
14639
+ *
14640
+ * @example
14641
+ * ```typescript
14642
+ * import { canSignTypedData } from '@core/adapter-evm'
14643
+ *
14644
+ * if (canSignTypedData(adapter)) {
14645
+ * const signature = await adapter.signTypedData(typedData, context)
14646
+ * } else {
14647
+ * // take an on-chain approval path instead of a permit signature
14648
+ * }
14649
+ * ```
14650
+ */ function canSignTypedData(adapter) {
14651
+ if (!hasSignTypedData(adapter)) {
14652
+ return false;
14653
+ }
14654
+ if (typeof adapter.supportsSignTypedData === 'function') {
14655
+ // The value is `boolean` per the interface, but a plain-JS adapter may
14656
+ // return anything; treat it as untrusted and coerce to a strict
14657
+ // boolean. Comparing an `unknown` (not a `boolean`) also keeps the
14658
+ // lint autofix from stripping this as a redundant `=== true`.
14659
+ const supported = adapter.supportsSignTypedData();
14660
+ return supported === true;
14661
+ }
14662
+ return true;
14663
+ }
14614
14664
 
14615
14665
  /** Inputs for {@link checkErc3009ValidityWindow}. */ /**
14616
14666
  * Check an ERC-3009 authorization validity window.
@@ -15457,7 +15507,7 @@ new Set(Object.values(Blockchain));
15457
15507
  registerKit(`${pkg$3.name}/${pkg$3.version}`);
15458
15508
 
15459
15509
  var name$2 = "@circle-fin/earn-kit";
15460
- var version$2 = "1.9.0";
15510
+ var version$2 = "1.10.0";
15461
15511
  var pkg$2 = {
15462
15512
  name: name$2,
15463
15513
  version: version$2};
@@ -15564,6 +15614,61 @@ const EARN_BRIDGE_USDC_ERC3009_DOMAINS = {
15564
15614
  *
15565
15615
  * @internal
15566
15616
  */ const EARN_BRIDGE_MAX_VALIDITY_SECONDS = 86_400n;
15617
+ /**
15618
+ * Static fields of the EIP-712 domain for the `DepositIntent` typed-data
15619
+ * intent. `chainId` is excluded here — it must come from the deposit's
15620
+ * actual chain (see {@link buildDepositIntentTypedData}), since a mismatched
15621
+ * `chainId` in the domain separator would make every intent signature
15622
+ * invalid.
15623
+ *
15624
+ * `verifyingContract` is intentionally omitted — the intent is verified
15625
+ * off-chain by the Earn Service, not by a deployed contract.
15626
+ *
15627
+ * @internal
15628
+ */ const DEPOSIT_INTENT_DOMAIN = {
15629
+ name: 'Circle EarnKit',
15630
+ version: '1'
15631
+ };
15632
+ /**
15633
+ * EIP-712 type definition for the `DepositIntent` struct.
15634
+ *
15635
+ * @internal
15636
+ */ const DEPOSIT_INTENT_TYPES = {
15637
+ DepositIntent: [
15638
+ {
15639
+ name: 'wallet',
15640
+ type: 'address'
15641
+ },
15642
+ {
15643
+ name: 'vault',
15644
+ type: 'address'
15645
+ },
15646
+ {
15647
+ name: 'amount',
15648
+ type: 'uint256'
15649
+ },
15650
+ {
15651
+ name: 'deadline',
15652
+ type: 'uint256'
15653
+ }
15654
+ ]
15655
+ };
15656
+ /**
15657
+ * Lifetime of a `DepositIntent` signature, in seconds.
15658
+ *
15659
+ * The Earn Service rejects a deadline more than 300 seconds ahead of its own
15660
+ * clock. 240 seconds leaves room for clock skew between the client and the
15661
+ * service while still covering the deposit request that follows the
15662
+ * signature.
15663
+ *
15664
+ * @internal
15665
+ */ const DEPOSIT_INTENT_TTL_SECONDS = 240;
15666
+ /**
15667
+ * Protocol value the Earn Service reports for Veda vaults. A deposit into a
15668
+ * Veda vault needs a signed `DepositIntent`.
15669
+ *
15670
+ * @internal
15671
+ */ const VEDA_PROTOCOL = 'veda';
15567
15672
  /**
15568
15673
  * Display symbol for vault share tokens.
15569
15674
  *
@@ -18401,7 +18506,7 @@ const bridgeDepositPrepareReviewSchema = z.object({
18401
18506
  }
18402
18507
 
18403
18508
  var name$1 = "@circle-fin/provider-earn-service";
18404
- var version$1 = "1.7.2";
18509
+ var version$1 = "1.8.0";
18405
18510
  var pkg$1 = {
18406
18511
  name: name$1,
18407
18512
  version: version$1};
@@ -18753,8 +18858,9 @@ function toPositionInfo(data) {
18753
18858
  * Build signed deposit instructions via the Earn Service API.
18754
18859
  *
18755
18860
  * Call `POST /v1/earnKit/deposit` with the vault address, amount,
18756
- * wallet address, and chain. Return EIP-712 signed execution
18757
- * parameters for on-chain submission.
18861
+ * wallet address, and chain, plus the signed `DepositIntent` when one is
18862
+ * given. Return EIP-712 signed execution parameters for on-chain
18863
+ * submission.
18758
18864
  *
18759
18865
  * @param params - Deposit parameters
18760
18866
  * @returns Signed execution parameters for the deposit
@@ -18768,7 +18874,8 @@ function toPositionInfo(data) {
18768
18874
  vaultAddress: params.vaultAddress,
18769
18875
  amount: params.amount,
18770
18876
  address: params.address,
18771
- chain: params.chain
18877
+ chain: params.chain,
18878
+ ...params.intent
18772
18879
  };
18773
18880
  try {
18774
18881
  const response = await pollApiPost(url.toString(), requestBody, isDepositResponse, pollingConfig);
@@ -18780,6 +18887,232 @@ function toPositionInfo(data) {
18780
18887
  }
18781
18888
  }
18782
18889
 
18890
+ /**
18891
+ * Build the version-1 EarnKit `DepositIntent` EIP-712 typed data.
18892
+ *
18893
+ * The signed struct binds the depositing `wallet`, the target `vault`, the
18894
+ * deposit `amount` in the asset's smallest unit, and a signature `deadline`.
18895
+ * The Earn Service rebuilds the same struct from the deposit request and
18896
+ * rejects the deposit unless `wallet` signed it. The domain intentionally
18897
+ * omits `verifyingContract` because the service verifies the signature
18898
+ * off-chain.
18899
+ *
18900
+ * `domain.chainId` comes from the deposit's chain rather than a constant: the
18901
+ * domain separator must match what the service rebuilds, and a wrong
18902
+ * `chainId` would make every intent signature invalid.
18903
+ *
18904
+ * This function returns the fully-typed {@link DepositIntentTypedData} with
18905
+ * no assertion. {@link resolveDepositIntent} casts it to the adapter's
18906
+ * generic `EIP712Domain` shape only where it calls `adapter.signTypedData`,
18907
+ * since that shared interface always requires `verifyingContract`. The
18908
+ * adapter's runtime `eip712DomainSchema` treats `verifyingContract` as
18909
+ * optional, so the cast is safe at runtime. BorrowKit's
18910
+ * `buildWebhookRegistrationTypedData` works around the same gap.
18911
+ *
18912
+ * @param params - Wallet, vault, base-unit amount, deadline, and chain id.
18913
+ * @returns EIP-712 typed data ready to be cast for `adapter.signTypedData`.
18914
+ *
18915
+ * @example
18916
+ * ```typescript
18917
+ * const typedData = buildDepositIntentTypedData({
18918
+ * wallet: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
18919
+ * vault: '0x00000000000000000000000000000000000000a1',
18920
+ * amount: 100_000_000n,
18921
+ * deadline: 1_700_000_000n,
18922
+ * chainId: 5_042_002,
18923
+ * })
18924
+ * ```
18925
+ *
18926
+ * @internal
18927
+ */ function buildDepositIntentTypedData(params) {
18928
+ return {
18929
+ domain: {
18930
+ ...DEPOSIT_INTENT_DOMAIN,
18931
+ chainId: params.chainId
18932
+ },
18933
+ types: DEPOSIT_INTENT_TYPES,
18934
+ primaryType: 'DepositIntent',
18935
+ message: {
18936
+ wallet: params.wallet,
18937
+ vault: params.vault,
18938
+ amount: params.amount,
18939
+ deadline: params.deadline
18940
+ }
18941
+ };
18942
+ }
18943
+ /**
18944
+ * Earn Service error code for a missing, expired, or wrongly signed
18945
+ * `DepositIntent` (`DEPOSIT_INTENT_INVALID`).
18946
+ */ const DEPOSIT_INTENT_INVALID_CODE = 380418;
18947
+ /**
18948
+ * Sign a `DepositIntent` when the target vault needs one.
18949
+ *
18950
+ * A Veda vault mints shares straight to the depositing wallet, so the Earn
18951
+ * Service only builds a Veda deposit for a wallet that signed a
18952
+ * `DepositIntent` for the same vault and amount. The vault lookup exists only
18953
+ * to find out whether the vault is a Veda vault (and its asset decimals, to
18954
+ * sign the amount). Other vaults ignore the intent, so this returns no intent
18955
+ * for them and the wallet sees no extra signature prompt. A vault the service
18956
+ * does not return is also treated as not needing an intent; the deposit call
18957
+ * then reports why.
18958
+ *
18959
+ * A failed vault lookup does not stop the deposit: this returns the lookup
18960
+ * error with no intent, so a non-Veda deposit works as before. A Veda deposit
18961
+ * then gets `DEPOSIT_INTENT_INVALID` from the service, and
18962
+ * {@link resolveDepositError} reports the lookup error instead. The lookup
18963
+ * fails either by throwing or by returning the vault in its `errors` list (for
18964
+ * example `VAULT_REFRESH_BUSY`); both are kept the same way.
18965
+ *
18966
+ * @param params - Adapter, resolved chain, wallet, vault, human-readable
18967
+ * amount, and service config.
18968
+ * @returns The signed intent when the vault needs one, and the lookup error
18969
+ * when the vault lookup failed.
18970
+ * @throws {@link KitError} `InputError.VALIDATION_FAILED` when the vault needs
18971
+ * an intent but the chain is not EVM, the adapter cannot sign typed data, or
18972
+ * the amount has more decimal places than the vault asset.
18973
+ *
18974
+ * @example
18975
+ * ```typescript
18976
+ * const { intent } = await resolveDepositIntent({
18977
+ * adapter,
18978
+ * chain: ArcTestnet,
18979
+ * address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
18980
+ * vaultAddress: '0x00000000000000000000000000000000000000a1',
18981
+ * amount: '100',
18982
+ * config,
18983
+ * })
18984
+ * // intent -> { intentSignature: '0x…', intentDeadline: 1700000240 }
18985
+ * ```
18986
+ *
18987
+ * @internal
18988
+ */ async function resolveDepositIntent(params) {
18989
+ let lookup;
18990
+ try {
18991
+ lookup = await fetchVaults({
18992
+ // `resolveAdapterContext` already rejected chains the Earn Service does
18993
+ // not support, so the chain is an Earn chain here.
18994
+ vaults: [
18995
+ {
18996
+ chain: params.chain,
18997
+ vaultAddress: params.vaultAddress
18998
+ }
18999
+ ],
19000
+ config: params.config
19001
+ });
19002
+ } catch (lookupError) {
19003
+ return {
19004
+ lookupError
19005
+ };
19006
+ }
19007
+ const vaultError = lookup.errors.find((candidate)=>isSameAddress(candidate.vaultAddress, params.vaultAddress));
19008
+ if (vaultError !== undefined) {
19009
+ // Same handling as a thrown lookup error, mapped with the Earn Service
19010
+ // error table so it keeps its recoverability (VAULT_REFRESH_BUSY retries).
19011
+ return {
19012
+ lookupError: parseEarnApiError({
19013
+ responseBody: {
19014
+ code: vaultError.code,
19015
+ message: vaultError.message
19016
+ }
19017
+ }, {
19018
+ operation: 'getVaults'
19019
+ })
19020
+ };
19021
+ }
19022
+ const vault = lookup.vaults.find((candidate)=>isSameAddress(candidate.address, params.vaultAddress));
19023
+ if (vault?.protocol.toLowerCase() !== VEDA_PROTOCOL) {
19024
+ return {};
19025
+ }
19026
+ if (params.chain.type !== 'evm') {
19027
+ throw createValidationFailedError('from.chain', params.chain.chain, 'Veda vault deposits need an EVM chain to sign the deposit intent');
19028
+ }
19029
+ if (!canSignTypedData(params.adapter)) {
19030
+ throw createValidationFailedError('from.adapter', params.adapter, 'adapter must support signTypedData for Veda vault deposits');
19031
+ }
19032
+ const wallet = assertHexAddress('from.address', params.address, 'Depositor address must be a 0x-prefixed 20-byte hex address.');
19033
+ const assetDecimals = vault.liquidityProfile.totalDeposits.decimals;
19034
+ // Amount.parse truncates extra digits, but the service rejects them. Fail
19035
+ // before the wallet prompt instead of asking to sign a deposit that cannot
19036
+ // go through.
19037
+ assertDecimalPlaces('amount', params.amount, assetDecimals, `amount must have at most ${String(assetDecimals)} decimal places for the vault asset`);
19038
+ const deadline = Math.floor(Date.now() / 1000) + DEPOSIT_INTENT_TTL_SECONDS;
19039
+ const typedData = buildDepositIntentTypedData({
19040
+ wallet,
19041
+ vault: params.vaultAddress,
19042
+ amount: Amount.parse(params.amount, {
19043
+ decimals: assetDecimals
19044
+ }).raw,
19045
+ deadline: BigInt(deadline),
19046
+ chainId: params.chain.chainId
19047
+ });
19048
+ // `typedData.domain` intentionally omits `verifyingContract` (the Earn
19049
+ // Service verifies off-chain), but the shared `signTypedData` signature
19050
+ // requires the standard `EIP712Domain` shape. The adapter's runtime
19051
+ // `eip712DomainSchema` treats `verifyingContract` as optional, so this cast
19052
+ // is safe at runtime.
19053
+ const intentSignature = await params.adapter.signTypedData(typedData, {
19054
+ chain: params.chain,
19055
+ address: wallet
19056
+ });
19057
+ return {
19058
+ intent: {
19059
+ intentSignature,
19060
+ intentDeadline: deadline
19061
+ }
19062
+ };
19063
+ }
19064
+ /**
19065
+ * Pick the error to report for a failed deposit request.
19066
+ *
19067
+ * When the vault lookup failed, the SDK sent the deposit without an intent.
19068
+ * If the service then answers `DEPOSIT_INTENT_INVALID`, the vault is a Veda
19069
+ * vault and the real cause is the failed lookup, not a bad signature. Report
19070
+ * the lookup error, which keeps its own recoverability (often retryable),
19071
+ * instead of a fatal signature rejection.
19072
+ *
19073
+ * When the SDK did send an intent and the service still answers
19074
+ * `DEPOSIT_INTENT_INVALID`, the error stays a fatal signature rejection, but
19075
+ * its message adds that the intent may have expired: the deadline starts
19076
+ * before the wallet prompt, so a slow signer can submit an expired intent.
19077
+ * Calling `deposit()` again signs a new one. Every other error is reported as
19078
+ * is.
19079
+ *
19080
+ * @param resolution - The result of {@link resolveDepositIntent}.
19081
+ * @param error - The error from the deposit request.
19082
+ * @returns The error to throw.
19083
+ *
19084
+ * @example
19085
+ * ```typescript
19086
+ * try {
19087
+ * return await fetchDeposit({ ...params, intent: resolution.intent })
19088
+ * } catch (error) {
19089
+ * throw resolveDepositError(resolution, error)
19090
+ * }
19091
+ * ```
19092
+ *
19093
+ * @internal
19094
+ */ function resolveDepositError(resolution, error) {
19095
+ if (resolution.lookupError !== undefined && isKitError(error) && isEarnServiceCode(error.cause?.trace, DEPOSIT_INTENT_INVALID_CODE)) {
19096
+ return resolution.lookupError;
19097
+ }
19098
+ if (resolution.intent !== undefined && isKitError(error) && isEarnServiceCode(error.cause?.trace, DEPOSIT_INTENT_INVALID_CODE)) {
19099
+ return new KitError({
19100
+ code: error.code,
19101
+ name: error.name,
19102
+ type: error.type,
19103
+ recoverability: error.recoverability,
19104
+ message: `${error.message} The deposit intent may have expired before the deposit was submitted (it is valid for ${String(DEPOSIT_INTENT_TTL_SECONDS)} seconds from when the SDK asks the wallet to sign). Call deposit() again to sign a new intent.`,
19105
+ ...error.cause !== undefined && {
19106
+ cause: error.cause
19107
+ }
19108
+ });
19109
+ }
19110
+ return error;
19111
+ }
19112
+ function isEarnServiceCode(trace, code) {
19113
+ return typeof trace === 'object' && trace !== null && 'earnServiceCode' in trace && trace.earnServiceCode === code;
19114
+ }
19115
+
18783
19116
  /**
18784
19117
  * Build cross-chain deposit instructions via the bridge API.
18785
19118
  *
@@ -20008,13 +20341,31 @@ function finishElapsedWait(lastStatus, lastError) {
20008
20341
  const adapterContractAddress = requireAdapterContract(chain);
20009
20342
  const { adapter } = params.from;
20010
20343
  const vaultAddress = assertHexAddress('vaultAddress', params.vaultAddress, 'Vault address must be a 0x-prefixed 20-byte hex address.');
20011
- const { execId, executionParams, signature } = await this.runPhase(ctx, 'deposit', 'fetchParams', async ()=>fetchDeposit({
20344
+ const { execId, executionParams, signature } = await this.runPhase(ctx, 'deposit', 'fetchParams', async ()=>{
20345
+ // Veda vaults mint shares straight to `address`, so the service
20346
+ // needs proof that `address` asked for this deposit. Sign it here,
20347
+ // inside fetchParams, so a rejected prompt fails this phase.
20348
+ const resolution = await resolveDepositIntent({
20349
+ adapter,
20350
+ chain,
20351
+ address,
20012
20352
  vaultAddress,
20013
20353
  amount: params.amount,
20014
- address,
20015
- chain: apiChain,
20016
20354
  config
20017
- }), ()=>undefined);
20355
+ });
20356
+ try {
20357
+ return await fetchDeposit({
20358
+ vaultAddress,
20359
+ amount: params.amount,
20360
+ address,
20361
+ chain: apiChain,
20362
+ intent: resolution.intent,
20363
+ config
20364
+ });
20365
+ } catch (error) {
20366
+ throw resolveDepositError(resolution, error);
20367
+ }
20368
+ }, ()=>undefined);
20018
20369
  validateExecutionDeadline(executionParams);
20019
20370
  const transactionReportContext = {
20020
20371
  execId,
@@ -23021,7 +23372,7 @@ registerKit(`${pkg$2.name}/${pkg$2.version}`);
23021
23372
  };
23022
23373
 
23023
23374
  var name = "@circle-fin/borrow-kit";
23024
- var version = "1.1.0";
23375
+ var version = "1.1.1";
23025
23376
  var pkg = {
23026
23377
  name: name,
23027
23378
  version: version};