@augustdigital/sdk 9.7.1 → 9.9.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.
@@ -8,6 +8,7 @@ exports.isNonceParsing = isNonceParsing;
8
8
  exports.safeWaitForTx = safeWaitForTx;
9
9
  exports.safeSendTx = safeSendTx;
10
10
  exports.tryRecoverTxHash = tryRecoverTxHash;
11
+ exports.getDepositRoute = getDepositRoute;
11
12
  exports.vaultApprove = vaultApprove;
12
13
  exports.approve = approve;
13
14
  exports.vaultDeposit = vaultDeposit;
@@ -24,6 +25,7 @@ exports.swapRouterDeposit = swapRouterDeposit;
24
25
  const ethers_1 = require("ethers");
25
26
  const abis_1 = require("../../abis");
26
27
  const core_1 = require("../../core");
28
+ const types_1 = require("../../types");
27
29
  const swap_quotes_1 = require("../../services/swap-quotes");
28
30
  const adapter_helpers_1 = require("./adapter.helpers");
29
31
  const utils_1 = require("./utils");
@@ -428,6 +430,191 @@ function throwIfInsufficientFunds(isInsufficient, operation, error, context) {
428
430
  return;
429
431
  throw new core_1.AugustValidationError('ACCOUNT_NOT_FUNDED', `${operation} failed: insufficient native balance to cover gas/network fees`, { cause: error, context });
430
432
  }
433
+ /**
434
+ * Shared catch path for the SwapRouter write methods, with the same recovery
435
+ * and classification as `vaultDeposit`: recover a nonce-parse hash, pass typed
436
+ * SDK errors through, log (demoting user rejections and unfunded accounts),
437
+ * map insufficient funds to `ACCOUNT_NOT_FUNDED`, and wrap the rest.
438
+ * @internal
439
+ */
440
+ async function handleSwapRouterWriteError(e, tx, signer, wait, operation, label, context) {
441
+ const recovered = await tryRecoverTxHash(e, signer, wait);
442
+ if (recovered) {
443
+ core_1.Logger.log.warn(`${operation}:nonce-fallback`, { hash: recovered });
444
+ return recovered;
445
+ }
446
+ if (e instanceof core_1.AugustSDKError)
447
+ throw e;
448
+ const ctx = { ...context, ...localTxBroadcastContext(tx, e) };
449
+ const insufficientFunds = (0, core_1.isInsufficientFundsError)(e);
450
+ (0, core_1.logChainError)(operation, e, (0, core_1.isUserRejectionError)(e) || insufficientFunds, ctx);
451
+ throwIfInsufficientFunds(insufficientFunds, label, e, ctx);
452
+ throw new core_1.AugustSDKError('UNKNOWN', `${label} failed: ${e instanceof Error ? e.message : 'Unknown error'}`, { cause: e, context: ctx });
453
+ }
454
+ /**
455
+ * Whether a router-registered vault takes `asset` as-is: its reference asset,
456
+ * or, for a Tokenized Vault V2 (backend `evm-2` and router `vaultType`), an
457
+ * asset on the vault's assets whitelist. Shared by the deposit-route resolver
458
+ * and the `swapRouterDeposit` dispatch so neither swaps an asset the vault
459
+ * accepts.
460
+ * @internal
461
+ */
462
+ async function isAcceptedDirectly(signer, target, asset, isMultiAssetVault, vaultInfo) {
463
+ const lower = asset.toLowerCase();
464
+ return (lower === vaultInfo.referenceAsset.toLowerCase() ||
465
+ (isMultiAssetVault &&
466
+ vaultInfo.vaultType === types_1.SwapRouterVaultType.TokenizedVaultV2 &&
467
+ (await (0, core_1.getWhitelistedAssets)(signer, await (0, core_1.createContract)({
468
+ address: target,
469
+ provider: signer,
470
+ abi: abis_1.ABI_TOKENIZED_VAULT_V2,
471
+ }).assetsWhitelistAddress())).some((a) => a.toLowerCase() === lower)));
472
+ }
473
+ /** @internal */
474
+ async function resolveDepositRoute(signer, args) {
475
+ const { target, chainId, actualDepositAsset, isMultiAssetVault } = args;
476
+ const asset = actualDepositAsset.toLowerCase();
477
+ const isNativeToken = actualDepositAsset === ethers_1.ZeroAddress;
478
+ const isAdapterDeposit = asset !== args.underlyingAsset.toLowerCase();
479
+ const wrapper = (0, core_1.getVaultAdapterConfig)(target)?.wrapperAddress;
480
+ const native = (reason) => ({
481
+ via: 'native',
482
+ spender: isNativeToken
483
+ ? null
484
+ : resolveSpender({
485
+ target,
486
+ isMultiAssetVault,
487
+ isAdapterDeposit,
488
+ adapterWrapperAddress: wrapper,
489
+ }),
490
+ reason,
491
+ });
492
+ const originCode = (0, core_1.getOriginCode)();
493
+ if (!originCode)
494
+ return native('no-origin-code');
495
+ const router = chainId ? (0, core_1.getSwapRouterAddress)(chainId) : undefined;
496
+ if (!chainId || !router)
497
+ return native('no-router');
498
+ if (!core_1.ORIGIN_ATTRIBUTION_CHAINS.includes(chainId)) {
499
+ return native('chain-not-supported');
500
+ }
501
+ if (args.isDepositWithPermit)
502
+ return native('permit');
503
+ if (isNativeToken)
504
+ return native('native-token');
505
+ if (isAdapterDeposit && !isMultiAssetVault && wrapper) {
506
+ return native('adapter');
507
+ }
508
+ const vaultInfo = await (0, core_1.readSwapRouterVaultInfo)(signer, chainId, target);
509
+ if (!vaultInfo || vaultInfo.referenceAsset === ethers_1.ZeroAddress) {
510
+ return native('vault-not-registered');
511
+ }
512
+ if (!(await isAcceptedDirectly(signer, target, actualDepositAsset, isMultiAssetVault, vaultInfo))) {
513
+ return native('asset-not-accepted');
514
+ }
515
+ if (!(await (0, core_1.readSwapRouterTokenWhitelisted)(signer, chainId, actualDepositAsset))) {
516
+ return native('router-token-not-whitelisted');
517
+ }
518
+ if (!(await (0, core_1.readOriginInfo)(chainId, originCode, () => signer)).registered) {
519
+ if ((0, core_1.isStrictOrigin)()) {
520
+ throw new core_1.AugustValidationError('INVALID_INPUT', `attribution.originCode is not registered on the SwapRouter for chain ${chainId} and attribution.strictOrigin is set. Register it with addOrigin, or unset strictOrigin to fall back to a native deposit.`, { context: { chainId, target } });
521
+ }
522
+ return native('origin-not-registered');
523
+ }
524
+ return { via: 'router', spender: router };
525
+ }
526
+ /**
527
+ * Resolve whether a deposit can go through the SwapRouter carrying the
528
+ * instance origin code (`router`) or takes the vault's native path
529
+ * (`native`), and which address to approve. Read-only. `vaultDeposit` and
530
+ * `approve` follow this route, so integrators that manage approvals
531
+ * themselves must approve `route.spender`, not the vault.
532
+ *
533
+ * The route is `router` only when an origin code is configured, the chain is
534
+ * Ethereum mainnet ({@link ORIGIN_ATTRIBUTION_CHAINS}) and has a SwapRouter,
535
+ * the vault is registered on it, the vault accepts the asset directly (its
536
+ * reference asset, or a Tokenized Vault V2 whitelisted asset — deposited
537
+ * as-is, never swapped), the router whitelists the asset, the deposit is not a
538
+ * permit, native-token or adapter deposit, and the code is registered.
539
+ * Otherwise it is `native` with a `reason`.
540
+ *
541
+ * @param signer - ethers `Signer` / `Wallet` used for the reads.
542
+ * @param options - {@link IGetDepositRouteOptions}; `depositAsset` defaults
543
+ * to the vault's underlying.
544
+ * @returns {@link IDepositRoute}.
545
+ * @throws AugustValidationError on an invalid `target`, or an unregistered
546
+ * code under `attribution.strictOrigin`.
547
+ * @throws AugustSDKError when a backend, vault or router read fails.
548
+ * @worstCaseRpcCalls 7 — the backend vault row, `asset()`, the router's
549
+ * `vaultInfo`, the vault's `assetsWhitelistAddress()` and
550
+ * `getWhitelistedAssets()` (Tokenized Vault V2 non-reference asset only),
551
+ * the router's `whitelistedTokens(asset)`, and the router's `origins`. The
552
+ * router reads and `getWhitelistedAssets()` are cached for 5 minutes. No
553
+ * router read without a code or off Ethereum mainnet.
554
+ * @example
555
+ * ```ts
556
+ * const route = await augustSdk.evm.getDepositRoute({ target: vault, depositAsset: usdt });
557
+ * if (route.spender) await approveToken(usdt, route.spender, amount);
558
+ * ```
559
+ */
560
+ async function getDepositRoute(signer, options) {
561
+ const { target, depositAsset } = options;
562
+ if (!(0, core_1.checkAddress)(target, console)) {
563
+ throw new core_1.AugustValidationError('INVALID_ADDRESS', 'getDepositRoute: invalid target address');
564
+ }
565
+ try {
566
+ const tokenizedVault = (await (0, core_1.fetchTokenizedVault)(target))?.[0];
567
+ const isMultiAssetVault = (0, core_1.getVaultVersionV2)(tokenizedVault) === 'evm-2';
568
+ const underlyingAsset = (await (0, core_1.createContract)({
569
+ address: target,
570
+ provider: signer,
571
+ abi: isMultiAssetVault ? abis_1.ABI_TOKENIZED_VAULT_V2 : abis_1.ABI_LENDING_POOLS,
572
+ }).asset());
573
+ return await resolveDepositRoute(signer, {
574
+ target,
575
+ chainId: tokenizedVault?.chain ?? options.chainId,
576
+ actualDepositAsset: depositAsset || underlyingAsset,
577
+ underlyingAsset,
578
+ isMultiAssetVault,
579
+ isDepositWithPermit: options.isDepositWithPermit,
580
+ });
581
+ }
582
+ catch (e) {
583
+ if (e instanceof core_1.AugustSDKError)
584
+ throw e;
585
+ throw new core_1.AugustSDKError('UNKNOWN', `getDepositRoute failed: ${e instanceof Error ? e.message : 'Unknown error'}`, { cause: e, context: { target, chainId: options.chainId } });
586
+ }
587
+ }
588
+ /**
589
+ * Report whether a deposit carried the instance origin code: the bounded
590
+ * `sdk.attribution.origin` tag (`applied` | `skipped` | `none`), the code only
591
+ * as a span attribute, and — when a configured code was not applied — a
592
+ * warning with the resolver's reason, which the logger also forwards to
593
+ * Sentry as a breadcrumb.
594
+ * @internal
595
+ */
596
+ function trackOriginAttribution(route, context, operation = 'vaultDeposit') {
597
+ const status = route.via === 'router'
598
+ ? 'applied'
599
+ : route.reason === 'no-origin-code'
600
+ ? 'none'
601
+ : 'skipped';
602
+ if (status === 'skipped') {
603
+ core_1.Logger.log.warn(`${operation}:attribution-skipped`, {
604
+ reason: route.reason,
605
+ ...context,
606
+ });
607
+ }
608
+ if (!(0, core_1.isAnalyticsEnabled)())
609
+ return;
610
+ const sentry = (0, core_1.getSentrySDK)();
611
+ sentry?.setTag('sdk.attribution.origin', status);
612
+ if (status === 'applied') {
613
+ sentry
614
+ ?.getActiveSpan()
615
+ ?.setAttribute('sdk.attribution.origin_code', (0, core_1.resolveOriginCode)(undefined, context.chainId));
616
+ }
617
+ }
431
618
  async function approveCore(signer, options) {
432
619
  const { wallet, target, wait, amount, depositAsset } = options;
433
620
  const [goodWallet, goodPool] = [
@@ -444,7 +631,6 @@ async function approveCore(signer, options) {
444
631
  try {
445
632
  const tokenizedVault = (await (0, core_1.fetchTokenizedVault)(target))?.[0];
446
633
  const vaultVersion = (0, core_1.getVaultVersionV2)(tokenizedVault);
447
- const adapterConfig = (0, core_1.getVaultAdapterConfig)(target);
448
634
  const isMultiAssetVault = vaultVersion === 'evm-2';
449
635
  const poolContract = isMultiAssetVault
450
636
  ? (0, core_1.createContract)({
@@ -483,13 +669,14 @@ async function approveCore(signer, options) {
483
669
  // Native tokens travel as msg.value — no ERC-20 allowance to grant.
484
670
  if (isNativeToken)
485
671
  return { kind: 'native' };
486
- const isAdapterDeposit = actualDepositAsset.toLowerCase() !== underlyingAsset.toLowerCase();
487
- const spenderAddress = resolveSpender({
672
+ const spenderAddress = (await resolveDepositRoute(signer, {
488
673
  target,
674
+ chainId: tokenizedVault?.chain ?? options.chainId,
675
+ actualDepositAsset,
676
+ underlyingAsset,
489
677
  isMultiAssetVault,
490
- isAdapterDeposit,
491
- adapterWrapperAddress: adapterConfig?.wrapperAddress,
492
- });
678
+ isDepositWithPermit: options.isDepositWithPermit,
679
+ })).spender;
493
680
  const depositTokenDecimals = await resolveDepositTokenDecimals({
494
681
  actualDepositAsset,
495
682
  underlyingAsset,
@@ -547,14 +734,18 @@ async function approveCore(signer, options) {
547
734
  * Approve a vault (or the appropriate adapter wrapper) to spend a token
548
735
  * ahead of a separate deposit call. Spender routing mirrors
549
736
  * {@link vaultDeposit} (multi-asset → vault, adapter → wrapper, standard →
550
- * vault). Returns `undefined` when the existing allowance already covers
551
- * `amount` **or** when the deposit asset is native.
737
+ * vault, SwapRouter when {@link getDepositRoute} routes there). Returns
738
+ * `undefined` when the existing allowance already covers `amount` **or**
739
+ * when the deposit asset is native.
552
740
  *
553
741
  * @returns Transaction hash when an approval was sent, `undefined` when
554
742
  * the existing allowance already covers `amount` or the deposit asset is
555
743
  * native (no ERC-20 allowance applies). Use {@link approve} for a
556
744
  * discriminated `ApproveResult` if you need to tell those two cases apart.
557
745
  * @throws AugustValidationError on invalid wallet/target/amount.
746
+ * @throws AugustValidationError when `attribution.strictOrigin` is set and the
747
+ * configured origin code is not registered on the SwapRouter (the spender
748
+ * follows {@link getDepositRoute}).
558
749
  *
559
750
  * @example
560
751
  * ```ts
@@ -600,6 +791,9 @@ async function vaultApprove(signer, options) {
600
791
  * ```
601
792
  *
602
793
  * @throws AugustValidationError on invalid wallet/target/amount.
794
+ * @throws AugustValidationError when `attribution.strictOrigin` is set and the
795
+ * configured origin code is not registered on the SwapRouter (the spender
796
+ * follows {@link getDepositRoute}).
603
797
  * @throws AugustSDKError when the approval submission fails.
604
798
  */
605
799
  async function approve(signer, options) {
@@ -611,6 +805,11 @@ async function approve(signer, options) {
611
805
  * Paraswap adapter, native-token wrapper, depositWithPermit) based on the
612
806
  * vault version and `depositAsset`.
613
807
  *
808
+ * With `attribution.originCode` configured, a deposit the SwapRouter can take
809
+ * directly goes through `SwapRouter.deposit` carrying that code, and the
810
+ * router is approved instead of the vault — see {@link getDepositRoute} for
811
+ * the rules and the fallback reasons. Without a code nothing changes.
812
+ *
614
813
  * Amounts are encoded against the **deposit token's** decimals, not the
615
814
  * vault's share decimals. The SDK reads the appropriate decimals via the
616
815
  * vault metadata and the asset's ERC-20.
@@ -626,6 +825,8 @@ async function approve(signer, options) {
626
825
  * @returns Deposit transaction hash.
627
826
  * @throws AugustValidationError on invalid wallet/target/amount or missing
628
827
  * adapter config when `depositAsset` differs from the vault underlying.
828
+ * @throws AugustValidationError when `attribution.strictOrigin` is set and the
829
+ * configured origin code is not registered on the SwapRouter.
629
830
  * @throws AugustSDKError when the deposit submission or on-chain call fails.
630
831
  *
631
832
  * @example
@@ -708,13 +909,23 @@ async function vaultDeposit(signer, options) {
708
909
  actualDepositAsset === '0x0000000000000000000000000000000000000000';
709
910
  const isAdapterDeposit = actualDepositAsset.toLowerCase() !== underlyingAsset.toLowerCase();
710
911
  const isMultiAssetVault = vaultVersion === 'evm-2';
711
- // vaultDeposit is the NATIVE path only — it never routes through the
712
- // SwapRouter. Any-token swap deposits are the explicit, opt-in job of
713
- // {@link swapRouterDeposit}; keeping them out of here is what stops a
714
- // natively-accepted asset from being silently swapped.
912
+ // vaultDeposit never swaps. Any-token swap deposits are the explicit,
913
+ // opt-in job of {@link swapRouterDeposit}; keeping them out of here is what
914
+ // stops a natively-accepted asset from being silently swapped. The only
915
+ // SwapRouter use is a direct deposit carrying a configured origin code.
715
916
  if (isAdapterDeposit && !isMultiAssetVault && !adapterConfig) {
716
917
  throw new core_1.AugustValidationError('INVALID_INPUT', `vaultDeposit: depositAsset ${actualDepositAsset} differs from vault underlying ${underlyingAsset} but no adapter is configured for vault ${target}. For an any-token deposit into a SwapRouter-eligible vault, use swapRouterDeposit instead.`);
717
918
  }
919
+ const routeChainId = tokenizedVault?.chain ?? chainId;
920
+ const route = await resolveDepositRoute(signer, {
921
+ target,
922
+ chainId: routeChainId,
923
+ actualDepositAsset,
924
+ underlyingAsset,
925
+ isMultiAssetVault,
926
+ isDepositWithPermit: options?.isDepositWithPermit,
927
+ });
928
+ trackOriginAttribution(route, { chainId: routeChainId, target });
718
929
  const depositTokenDecimals = await resolveDepositTokenDecimals({
719
930
  actualDepositAsset,
720
931
  underlyingAsset,
@@ -725,12 +936,7 @@ async function vaultDeposit(signer, options) {
725
936
  });
726
937
  const normalizedAmt = (0, core_1.toNormalizedBn)(amount, depositTokenDecimals);
727
938
  if (!isNativeToken) {
728
- const spenderAddress = resolveSpender({
729
- target,
730
- isMultiAssetVault,
731
- isAdapterDeposit,
732
- adapterWrapperAddress: adapterConfig?.wrapperAddress,
733
- });
939
+ const spenderAddress = route.spender;
734
940
  const depositTokenContract = (0, core_1.createContract)({
735
941
  address: actualDepositAsset,
736
942
  provider: signer,
@@ -746,7 +952,16 @@ async function vaultDeposit(signer, options) {
746
952
  }
747
953
  }
748
954
  // Route to appropriate deposit method
749
- if (isMultiAssetVault) {
955
+ if (route.via === 'router') {
956
+ depositTx = await (0, core_1.createContract)({
957
+ address: route.spender,
958
+ provider: signer,
959
+ abi: abis_1.ABI_SWAP_ROUTER,
960
+ })
961
+ .connect(signer)
962
+ .deposit((0, core_1.resolveOriginCode)(undefined, routeChainId), normalizedAmt.raw, target, actualDepositAsset, wallet);
963
+ }
964
+ else if (isMultiAssetVault) {
750
965
  if (options?.isDepositWithPermit) {
751
966
  // depositWithPermit flow
752
967
  // build structure hash of permit off chain
@@ -1428,7 +1643,13 @@ async function dispatchViaSwapRouter(args) {
1428
1643
  }
1429
1644
  const isUnderlying = args.actualDepositAsset.toLowerCase() ===
1430
1645
  args.underlyingAsset.toLowerCase();
1431
- if (isUnderlying) {
1646
+ const vaultInfo = !isUnderlying && args.isMultiAssetVault
1647
+ ? await (0, core_1.readSwapRouterVaultInfo)(args.signer, args.chainId, args.target)
1648
+ : undefined;
1649
+ if (isUnderlying ||
1650
+ (vaultInfo &&
1651
+ (await isAcceptedDirectly(args.signer, args.target, args.actualDepositAsset, true, vaultInfo)) &&
1652
+ (await (0, core_1.readSwapRouterTokenWhitelisted)(args.signer, args.chainId, args.actualDepositAsset)))) {
1432
1653
  return depositViaSwapRouter(args.signer, {
1433
1654
  chainId: args.chainId,
1434
1655
  vault: args.target,
@@ -1506,6 +1727,31 @@ async function dispatchViaSwapRouter(args) {
1506
1727
  wait: args.wait,
1507
1728
  });
1508
1729
  }
1730
+ /**
1731
+ * Resolve the origin code a SwapRouter write method sends and check its
1732
+ * registration before any approval or transaction, so an unregistered code
1733
+ * never costs the user a reverted deposit. A code passed to the call must be
1734
+ * registered or the call throws. An unregistered instance code falls back to
1735
+ * the zero sentinel with the same `attribution-skipped` signal as
1736
+ * `vaultDeposit`, or throws under `attribution.strictOrigin`. The zero
1737
+ * sentinel is sent without a read.
1738
+ * @internal
1739
+ */
1740
+ async function resolveRouterOriginCode(signer, operation, chainId, vault, provided) {
1741
+ const code = (0, core_1.resolveOriginCode)(provided, chainId);
1742
+ if (code === core_1.ORIGIN_CODES.default ||
1743
+ (await (0, core_1.readOriginInfo)(chainId, code, () => signer)).registered) {
1744
+ return code;
1745
+ }
1746
+ if (provided !== undefined) {
1747
+ throw new core_1.AugustValidationError('INVALID_INPUT', `${operation}: originCode is not registered on the SwapRouter for chain ${chainId}. Register it with addOrigin (check it with getOriginInfo), or pass a registered code.`, { context: { chainId, vault } });
1748
+ }
1749
+ if ((0, core_1.isStrictOrigin)()) {
1750
+ throw new core_1.AugustValidationError('INVALID_INPUT', `${operation}: attribution.originCode is not registered on the SwapRouter for chain ${chainId} and attribution.strictOrigin is set. Register it with addOrigin, or unset strictOrigin to deposit without an origin code.`, { context: { chainId, vault } });
1751
+ }
1752
+ trackOriginAttribution({ via: 'native', spender: null, reason: 'origin-not-registered' }, { chainId, target: vault }, operation);
1753
+ return core_1.ORIGIN_CODES.default;
1754
+ }
1509
1755
  /** @internal */
1510
1756
  function resolveSwapRouterOrThrow(chainId) {
1511
1757
  const router = (0, core_1.getSwapRouterAddress)(chainId);
@@ -1599,7 +1845,9 @@ async function resolveSignerEOA(signer, callerContext) {
1599
1845
  * @returns The transaction hash of the `swapAndDeposit` call.
1600
1846
  *
1601
1847
  * @throws {@link AugustValidationError} for unsupported chains, invalid
1602
- * addresses, empty/oversized swap arrays.
1848
+ * addresses, empty/oversized swap arrays, or an origin code not registered on
1849
+ * the router (a per-call code, or the instance code under
1850
+ * `attribution.strictOrigin`), before any approval.
1603
1851
  * @throws {@link AugustSDKError} on unrecoverable contract or RPC failure.
1604
1852
  *
1605
1853
  * @example
@@ -1637,20 +1885,29 @@ async function swapAndDeposit(signer, options) {
1637
1885
  }
1638
1886
  ensureSwapsValid(swaps);
1639
1887
  const routerAddress = resolveSwapRouterOrThrow(chainId);
1640
- const eoa = await resolveSignerEOA(signer, 'swapAndDeposit');
1641
- for (const [tokenIn, totalIn] of totalInputPerToken(swaps)) {
1642
- await ensureAllowance(signer, tokenIn, eoa, routerAddress, totalIn);
1888
+ let tx;
1889
+ try {
1890
+ const origin = await resolveRouterOriginCode(signer, 'swapAndDeposit', chainId, vault, originCode);
1891
+ const eoa = await resolveSignerEOA(signer, 'swapAndDeposit');
1892
+ for (const [tokenIn, totalIn] of totalInputPerToken(swaps)) {
1893
+ await ensureAllowance(signer, tokenIn, eoa, routerAddress, totalIn);
1894
+ }
1895
+ const routerContract = (0, core_1.createContract)({
1896
+ address: routerAddress,
1897
+ provider: signer,
1898
+ abi: abis_1.ABI_SWAP_ROUTER,
1899
+ });
1900
+ tx = (await routerContract
1901
+ .connect(signer)
1902
+ .swapAndDeposit(origin, vault, receiver, swaps));
1903
+ if (wait)
1904
+ await safeWaitForTx(tx);
1905
+ core_1.Logger.log.info('swapAndDeposit:tx_hash', tx.hash);
1906
+ return tx.hash;
1907
+ }
1908
+ catch (e) {
1909
+ return handleSwapRouterWriteError(e, tx, signer, wait, 'swapAndDeposit', 'Swap and deposit', { chainId, vault });
1643
1910
  }
1644
- const routerContract = (0, core_1.createContract)({
1645
- address: routerAddress,
1646
- provider: signer,
1647
- abi: abis_1.ABI_SWAP_ROUTER,
1648
- });
1649
- const { hash } = await safeSendTx(() => routerContract
1650
- .connect(signer)
1651
- .swapAndDeposit((0, core_1.resolveOriginCode)(originCode), vault, receiver, swaps), signer, wait);
1652
- core_1.Logger.log.info('swapAndDeposit:tx_hash', hash);
1653
- return hash;
1654
1911
  }
1655
1912
  /**
1656
1913
  * Deposit a vault's reference asset directly through the SwapRouter — no
@@ -1662,7 +1919,11 @@ async function swapAndDeposit(signer, options) {
1662
1919
  * @returns The transaction hash of the `deposit` call.
1663
1920
  *
1664
1921
  * @throws {@link AugustValidationError} for unsupported chains, invalid
1665
- * addresses, or zero amount.
1922
+ * addresses, zero amount, or an origin code not registered on the router (a
1923
+ * per-call code, or the instance code under `attribution.strictOrigin`),
1924
+ * before any approval; with code `ACCOUNT_NOT_FUNDED` when the account cannot
1925
+ * cover gas.
1926
+ * @throws {@link AugustSDKError} on unrecoverable contract or RPC failure.
1666
1927
  */
1667
1928
  async function depositViaSwapRouter(signer, options) {
1668
1929
  const { chainId, vault, receiver, asset, amount, originCode, wait } = options;
@@ -1676,18 +1937,27 @@ async function depositViaSwapRouter(signer, options) {
1676
1937
  throw new core_1.AugustValidationError('INVALID_INPUT', 'depositViaSwapRouter: amount must be greater than zero');
1677
1938
  }
1678
1939
  const routerAddress = resolveSwapRouterOrThrow(chainId);
1679
- const eoa = await resolveSignerEOA(signer, 'depositViaSwapRouter');
1680
- await ensureAllowance(signer, asset, eoa, routerAddress, amount);
1681
- const routerContract = (0, core_1.createContract)({
1682
- address: routerAddress,
1683
- provider: signer,
1684
- abi: abis_1.ABI_SWAP_ROUTER,
1685
- });
1686
- const { hash } = await safeSendTx(() => routerContract
1687
- .connect(signer)
1688
- .deposit((0, core_1.resolveOriginCode)(originCode), amount, vault, asset, receiver), signer, wait);
1689
- core_1.Logger.log.info('depositViaSwapRouter:tx_hash', hash);
1690
- return hash;
1940
+ let tx;
1941
+ try {
1942
+ const origin = await resolveRouterOriginCode(signer, 'depositViaSwapRouter', chainId, vault, originCode);
1943
+ const eoa = await resolveSignerEOA(signer, 'depositViaSwapRouter');
1944
+ await ensureAllowance(signer, asset, eoa, routerAddress, amount);
1945
+ const routerContract = (0, core_1.createContract)({
1946
+ address: routerAddress,
1947
+ provider: signer,
1948
+ abi: abis_1.ABI_SWAP_ROUTER,
1949
+ });
1950
+ tx = (await routerContract
1951
+ .connect(signer)
1952
+ .deposit(origin, amount, vault, asset, receiver));
1953
+ if (wait)
1954
+ await safeWaitForTx(tx);
1955
+ core_1.Logger.log.info('depositViaSwapRouter:tx_hash', tx.hash);
1956
+ return tx.hash;
1957
+ }
1958
+ catch (e) {
1959
+ return handleSwapRouterWriteError(e, tx, signer, wait, 'depositViaSwapRouter', 'SwapRouter deposit', { chainId, vault });
1960
+ }
1691
1961
  }
1692
1962
  /**
1693
1963
  * Deposit native ETH (or chain-native equivalent) into a vault via the
@@ -1710,7 +1980,11 @@ async function depositViaSwapRouter(signer, options) {
1710
1980
  * @returns The transaction hash of the `depositNativeToken` call.
1711
1981
  *
1712
1982
  * @throws {@link AugustValidationError} for unsupported chains, invalid
1713
- * addresses, or zero amount.
1983
+ * addresses, zero amount, or an origin code not registered on the router (a
1984
+ * per-call code, or the instance code under `attribution.strictOrigin`),
1985
+ * before any approval; with code `ACCOUNT_NOT_FUNDED` when the account cannot
1986
+ * cover gas.
1987
+ * @throws {@link AugustSDKError} on unrecoverable contract or RPC failure.
1714
1988
  */
1715
1989
  async function depositNativeViaSwapRouter(signer, options) {
1716
1990
  const { chainId, vault, receiver, amount, originCode, wait } = options;
@@ -1723,18 +1997,27 @@ async function depositNativeViaSwapRouter(signer, options) {
1723
1997
  throw new core_1.AugustValidationError('INVALID_INPUT', 'depositNativeViaSwapRouter: amount must be greater than zero');
1724
1998
  }
1725
1999
  const routerAddress = resolveSwapRouterOrThrow(chainId);
1726
- const routerContract = (0, core_1.createContract)({
1727
- address: routerAddress,
1728
- provider: signer,
1729
- abi: abis_1.ABI_SWAP_ROUTER,
1730
- });
1731
- const { hash } = await safeSendTx(() => routerContract
1732
- .connect(signer)
1733
- .depositNativeToken((0, core_1.resolveOriginCode)(originCode), vault, receiver, {
1734
- value: amount,
1735
- }), signer, wait);
1736
- core_1.Logger.log.info('depositNativeViaSwapRouter:tx_hash', hash);
1737
- return hash;
2000
+ let tx;
2001
+ try {
2002
+ const origin = await resolveRouterOriginCode(signer, 'depositNativeViaSwapRouter', chainId, vault, originCode);
2003
+ const routerContract = (0, core_1.createContract)({
2004
+ address: routerAddress,
2005
+ provider: signer,
2006
+ abi: abis_1.ABI_SWAP_ROUTER,
2007
+ });
2008
+ tx = (await routerContract
2009
+ .connect(signer)
2010
+ .depositNativeToken(origin, vault, receiver, {
2011
+ value: amount,
2012
+ }));
2013
+ if (wait)
2014
+ await safeWaitForTx(tx);
2015
+ core_1.Logger.log.info('depositNativeViaSwapRouter:tx_hash', tx.hash);
2016
+ return tx.hash;
2017
+ }
2018
+ catch (e) {
2019
+ return handleSwapRouterWriteError(e, tx, signer, wait, 'depositNativeViaSwapRouter', 'SwapRouter native deposit', { chainId, vault });
2020
+ }
1738
2021
  }
1739
2022
  /**
1740
2023
  * Whether a vault can take a native-ETH (chain-native token) SwapRouter
@@ -1810,21 +2093,22 @@ async function isNativeSwapRouterDepositSupported(chainId, vault, options) {
1810
2093
  }
1811
2094
  /**
1812
2095
  * Deposit into an August vault through the on-chain `SwapRouter`, selecting the
1813
- * correct router path from the deposit asset. This is the high-level, explicit
1814
- * counterpart to the SwapRouter branch inside {@link vaultDeposit}.
2096
+ * correct router path from the deposit asset.
1815
2097
  *
1816
- * Where `vaultDeposit` decides whether to use the SwapRouter from the
1817
- * `VAULTS_USING_SWAP_ROUTER` allowlist, this function always routes through the
1818
- * SwapRouter because the caller asked it to. Making the intent explicit is the
2098
+ * {@link vaultDeposit} never swaps; this function always routes through the
2099
+ * SwapRouter, because the caller asked it to. Keeping swaps opt-in is the
1819
2100
  * point: a native-deposit vault (e.g. a multi-asset Tokenized Vault V2 whose
1820
2101
  * non-reference assets are accepted directly) is never accidentally swapped —
1821
- * the regression that implicit, allowlist-driven routing caused. Use
1822
- * `vaultDeposit` for native / adapter deposits and this for SwapRouter deposits.
2102
+ * the regression that implicit, allowlist-driven routing inside `vaultDeposit`
2103
+ * caused before it was removed. Use `vaultDeposit` for native / adapter
2104
+ * deposits and this for SwapRouter deposits.
1823
2105
  *
1824
2106
  * The vault's reference asset and its decimals are read on-chain (never trusted
1825
2107
  * from the caller — a wrong reference asset would mis-route the swap). The path
1826
2108
  * is then chosen from `depositAsset`:
1827
- * - equals the reference asset → direct router deposit, no swap
2109
+ * - equals the reference asset, or is an asset a Tokenized Vault V2 accepts
2110
+ * as-is that the router also whitelists (e.g. USDT into Sentora USD) →
2111
+ * direct router deposit, no swap and no swap fee
1828
2112
  * ({@link depositViaSwapRouter});
1829
2113
  * - the zero address → native-token deposit ({@link depositNativeViaSwapRouter}),
1830
2114
  * valid only when the reference asset is the chain's wrapped-native token —
@@ -1835,23 +2119,36 @@ async function isNativeSwapRouterDepositSupported(chainId, vault, options) {
1835
2119
  * the chain's whitelisted aggregator, fail-closed on router/selector drift)
1836
2120
  * bundled into {@link swapAndDeposit}.
1837
2121
  *
1838
- * An `originCode` is forwarded verbatim to whichever of those three entry points
1839
- * runs, so a partner's origin fee accrues on every path. Omitting it sends the
1840
- * all-zero sentinel, which the router reads as "no origin fee".
2122
+ * The origin code — the per-call `originCode`, else the SDK instance's
2123
+ * `attribution.originCode` on Ethereum mainnet, else the all-zero sentinel
2124
+ * ("no origin fee") — is forwarded to whichever of those three entry points
2125
+ * runs, so a partner's origin fee accrues on every path.
1841
2126
  *
1842
2127
  * Side effects: reads tokenized-vault metadata, the vault's reference asset and
1843
- * decimals, and (on the swap path) one Paraswap quote; sends one ERC-20
1844
- * `approve` to the SwapRouter when allowance is short, then the deposit tx.
2128
+ * decimals, for a Tokenized Vault V2 non-reference asset the router's
2129
+ * `vaultInfo` and `whitelistedTokens` and the vault's assets whitelist, the
2130
+ * origin code's registration, and (on the swap path) one Paraswap quote;
2131
+ * sends one ERC-20 `approve` to the SwapRouter when allowance is short, then
2132
+ * the deposit tx.
1845
2133
  *
1846
2134
  * @param signer - ethers `Signer` or `Wallet` that signs the approval + deposit.
1847
2135
  * @param options - {@link ISwapRouterDepositOptions}.
1848
2136
  * @returns The transaction hash of the router deposit call.
1849
2137
  * @throws {@link AugustValidationError} for an unsupported chain (no SwapRouter
1850
2138
  * deployment), an invalid vault or deposit-asset address, a missing or zero
1851
- * amount, a native deposit into a non-wrapped-native vault, or drift between
1852
- * the aggregator quote and the on-chain router whitelist.
1853
- * @worstCaseRpcCalls 5 on the swap path — tokenized-vault metadata, reference
1854
- * asset + decimals, deposit-token decimals, the quote, and the allowance read.
2139
+ * amount, a native deposit into a non-wrapped-native vault, drift between
2140
+ * the aggregator quote and the on-chain router whitelist, or an origin code
2141
+ * not registered on the router (a per-call code, or the instance code under
2142
+ * `attribution.strictOrigin`), before any approval; with code
2143
+ * `ACCOUNT_NOT_FUNDED` when the account cannot cover gas.
2144
+ * @throws {@link AugustSDKError} on unrecoverable contract or RPC failure.
2145
+ * @worstCaseRpcCalls 10 on the swap path into a Tokenized Vault V2 —
2146
+ * tokenized-vault metadata, reference asset + decimals, deposit-token
2147
+ * decimals, the router's `vaultInfo` and `whitelistedTokens`, the vault's
2148
+ * `assetsWhitelistAddress()` and `getWhitelistedAssets()`, the quote, the
2149
+ * origin code's `origins` (none for the zero sentinel), and the allowance
2150
+ * read. The router reads and `getWhitelistedAssets()` are cached for 5
2151
+ * minutes.
1855
2152
  * @example
1856
2153
  * ```ts
1857
2154
  * // USDT deposited into a USDC-reference vault → swapped to USDC en route.
@@ -1865,6 +2162,15 @@ async function isNativeSwapRouterDepositSupported(chainId, vault, options) {
1865
2162
  * ```
1866
2163
  */
1867
2164
  async function swapRouterDeposit(signer, options) {
2165
+ try {
2166
+ return await swapRouterDepositCore(signer, options);
2167
+ }
2168
+ catch (e) {
2169
+ return handleSwapRouterWriteError(e, undefined, signer, options.wait, 'swapRouterDeposit', 'Swap router deposit', { chainId: options.chainId, vault: options.vault });
2170
+ }
2171
+ }
2172
+ /** @internal */
2173
+ async function swapRouterDepositCore(signer, options) {
1868
2174
  const { chainId, vault, depositAsset, amount, receiver, slippageBps, originCode, wait, } = options;
1869
2175
  if (!(0, core_1.checkAddress)(vault, console, 'contract')) {
1870
2176
  throw new core_1.AugustValidationError('INVALID_ADDRESS', `swapRouterDeposit: invalid vault address "${vault}"`);
@@ -1941,6 +2247,7 @@ async function swapRouterDeposit(signer, options) {
1941
2247
  actualDepositAsset: depositAsset,
1942
2248
  underlyingAsset,
1943
2249
  isNativeToken,
2250
+ isMultiAssetVault,
1944
2251
  depositTokenDecimals,
1945
2252
  normalizedAmt,
1946
2253
  slippageBps,