@circle-fin/app-kit 1.9.0 → 1.10.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/CHANGELOG.md +26 -0
- package/bridge.cjs +622 -26
- package/bridge.d.cts +147 -10
- package/bridge.d.mts +147 -10
- package/bridge.d.ts +147 -10
- package/bridge.mjs +622 -26
- package/chains.cjs +8 -2
- package/chains.d.cts +1 -0
- package/chains.d.mts +1 -0
- package/chains.d.ts +1 -0
- package/chains.mjs +8 -2
- package/context.cjs +1 -0
- package/context.d.cts +152 -12
- package/context.d.mts +152 -12
- package/context.d.ts +152 -12
- package/context.mjs +1 -0
- package/earn.cjs +859 -420
- package/earn.d.cts +521 -94
- package/earn.d.mts +521 -94
- package/earn.d.ts +521 -94
- package/earn.mjs +859 -421
- package/estimateBridge.cjs +622 -26
- package/estimateBridge.d.cts +147 -10
- package/estimateBridge.d.mts +147 -10
- package/estimateBridge.d.ts +147 -10
- package/estimateBridge.mjs +622 -26
- package/estimateSwap.cjs +810 -92
- package/estimateSwap.d.cts +147 -10
- package/estimateSwap.d.mts +147 -10
- package/estimateSwap.d.ts +147 -10
- package/estimateSwap.mjs +810 -92
- package/index.cjs +2450 -645
- package/index.d.cts +1003 -126
- package/index.d.mts +1003 -126
- package/index.d.ts +1003 -126
- package/index.mjs +2450 -645
- package/package.json +6 -6
- package/swap.cjs +810 -92
- package/swap.d.cts +147 -10
- package/swap.d.mts +147 -10
- package/swap.d.ts +147 -10
- package/swap.mjs +810 -92
- package/unifiedBalance.cjs +722 -115
- package/unifiedBalance.d.cts +222 -4
- package/unifiedBalance.d.mts +222 -4
- package/unifiedBalance.d.ts +222 -4
- package/unifiedBalance.mjs +722 -115
package/unifiedBalance.cjs
CHANGED
|
@@ -2874,7 +2874,10 @@ var EarnChain;
|
|
|
2874
2874
|
contracts: {
|
|
2875
2875
|
v1: {
|
|
2876
2876
|
wallet: GATEWAY_WALLET_EVM_TESTNET,
|
|
2877
|
-
minter: GATEWAY_MINTER_EVM_TESTNET
|
|
2877
|
+
minter: GATEWAY_MINTER_EVM_TESTNET,
|
|
2878
|
+
// DepositForHandler the GenericExecutor calls to run a fast cross-chain
|
|
2879
|
+
// deposit into the GatewayWallet above.
|
|
2880
|
+
depositForHandler: '0xD05E7D2E7d30b92c5F17d7d0fC575fce231F1A48'
|
|
2878
2881
|
}
|
|
2879
2882
|
},
|
|
2880
2883
|
forwarderSupported: {
|
|
@@ -5958,7 +5961,10 @@ var Chains = {
|
|
|
5958
5961
|
minter: zod.z.string({
|
|
5959
5962
|
required_error: 'Gateway minter address is required. Please provide a valid contract address.',
|
|
5960
5963
|
invalid_type_error: 'Gateway minter address must be a string.'
|
|
5961
|
-
}).min(1, 'Gateway minter address cannot be empty.')
|
|
5964
|
+
}).min(1, 'Gateway minter address cannot be empty.'),
|
|
5965
|
+
depositForHandler: zod.z.string({
|
|
5966
|
+
invalid_type_error: 'Gateway depositForHandler address must be a string.'
|
|
5967
|
+
}).min(1, 'Gateway depositForHandler address cannot be empty.').optional()
|
|
5962
5968
|
}).strict() // Reject any additional properties not defined in the schema
|
|
5963
5969
|
;
|
|
5964
5970
|
/**
|
|
@@ -8745,7 +8751,7 @@ function parseOrThrow(value, schema, context) {
|
|
|
8745
8751
|
}
|
|
8746
8752
|
|
|
8747
8753
|
var name = "@circle-fin/unified-balance-kit";
|
|
8748
|
-
var version = "1.
|
|
8754
|
+
var version = "1.3.0";
|
|
8749
8755
|
var pkg = {
|
|
8750
8756
|
name: name,
|
|
8751
8757
|
version: version};
|
|
@@ -16381,29 +16387,33 @@ const CIRCLE_BPS_DIVISOR = 10_000n;
|
|
|
16381
16387
|
const DEFAULT_GAS_FEE = parseUnits('0.1', USDC_DECIMALS);
|
|
16382
16388
|
/**
|
|
16383
16389
|
* Return the estimated Gateway gas fee for a chain in USDC atomic units.
|
|
16384
|
-
*
|
|
16390
|
+
* Prefers an entry in `overrides` (the real per-chain fee derived from a
|
|
16391
|
+
* prior estimate), then the static {@link GAS_FEE_BY_CHAIN} constant, and
|
|
16392
|
+
* finally a conservative 0.1 USDC fallback for unlisted chains.
|
|
16385
16393
|
*
|
|
16386
16394
|
* @param chain - The source blockchain.
|
|
16395
|
+
* @param overrides - Optional real per-chain gas fees keyed by chain.
|
|
16387
16396
|
* @returns Gas fee in USDC atomic units.
|
|
16388
|
-
*/ function getGasFee(chain) {
|
|
16389
|
-
return GAS_FEE_BY_CHAIN.get(chain) ?? DEFAULT_GAS_FEE;
|
|
16397
|
+
*/ function getGasFee(chain, overrides) {
|
|
16398
|
+
return overrides?.get(chain) ?? GAS_FEE_BY_CHAIN.get(chain) ?? DEFAULT_GAS_FEE;
|
|
16390
16399
|
}
|
|
16391
16400
|
/**
|
|
16392
16401
|
* Return the estimated forwarder fee for the destination chain
|
|
16393
16402
|
* (service fee + destination gas fee).
|
|
16394
16403
|
*
|
|
16395
16404
|
* @param destinationChain - The mint destination chain.
|
|
16405
|
+
* @param overrides - Optional real per-chain gas fees keyed by chain.
|
|
16396
16406
|
* @returns Forwarder fee in USDC atomic units.
|
|
16397
|
-
*/ function getForwarderFee(destinationChain) {
|
|
16398
|
-
const destGas = getGasFee(destinationChain);
|
|
16407
|
+
*/ function getForwarderFee(destinationChain, overrides) {
|
|
16408
|
+
const destGas = getGasFee(destinationChain, overrides);
|
|
16399
16409
|
return FORWARDER_SERVICE_FEE + destGas;
|
|
16400
16410
|
}
|
|
16401
16411
|
/**
|
|
16402
16412
|
* Estimate the fixed fees (gas + forwarder) and compute the maximum
|
|
16403
16413
|
* amount that can be drawn from this chain for a single intent,
|
|
16404
16414
|
* accounting for the 0.5 bps transfer fee if cross-chain.
|
|
16405
|
-
*/ function computeMaxDrawable(slot, forwarderFeeRemaining) {
|
|
16406
|
-
const gasFee = getGasFee(slot.chain);
|
|
16415
|
+
*/ function computeMaxDrawable(slot, forwarderFeeRemaining, overrides) {
|
|
16416
|
+
const gasFee = getGasFee(slot.chain, overrides);
|
|
16407
16417
|
let fixedFees = gasFee;
|
|
16408
16418
|
let forwarderFeeUsed = 0n;
|
|
16409
16419
|
if (forwarderFeeRemaining > 0n) {
|
|
@@ -16435,14 +16445,14 @@ const DEFAULT_GAS_FEE = parseUnits('0.1', USDC_DECIMALS);
|
|
|
16435
16445
|
* buffer), and returns the allocations for this pass.
|
|
16436
16446
|
*
|
|
16437
16447
|
* Mutates `slot.remaining` so the next pass sees reduced balances.
|
|
16438
|
-
*/ function greedyAllocate(slots, amount, destinationChain, useForwarder) {
|
|
16448
|
+
*/ function greedyAllocate(slots, amount, destinationChain, useForwarder, overrides) {
|
|
16439
16449
|
const result = [];
|
|
16440
16450
|
let remaining = amount;
|
|
16441
|
-
let forwarderFeeRemaining = useForwarder ? getForwarderFee(destinationChain) : 0n;
|
|
16451
|
+
let forwarderFeeRemaining = useForwarder ? getForwarderFee(destinationChain, overrides) : 0n;
|
|
16442
16452
|
if (remaining <= 0n) return result;
|
|
16443
16453
|
for (const slot of slots){
|
|
16444
16454
|
if (remaining <= 0n) break;
|
|
16445
|
-
const { drawable, gasFee, forwarderFeeUsed } = computeMaxDrawable(slot, forwarderFeeRemaining);
|
|
16455
|
+
const { drawable, gasFee, forwarderFeeUsed } = computeMaxDrawable(slot, forwarderFeeRemaining, overrides);
|
|
16446
16456
|
if (drawable <= 0n) continue;
|
|
16447
16457
|
// Greedy: take as much as we can from this chain
|
|
16448
16458
|
const take = remaining < drawable ? remaining : drawable // NOSONAR: This is a false positive — Math.min() only accepts number, not bigint, so the ternary is the correct pattern here.
|
|
@@ -16541,7 +16551,7 @@ const DEFAULT_GAS_FEE = parseUnits('0.1', USDC_DECIMALS);
|
|
|
16541
16551
|
// After this pass, slot.remaining reflects consumed capacity.
|
|
16542
16552
|
// -----------------------------------------------------------------------
|
|
16543
16553
|
const transferAmount = parseUnits(ctx.amountIn, USDC_DECIMALS);
|
|
16544
|
-
const allocations = greedyAllocate(slots, transferAmount, ctx.destinationChain, ctx.useForwarder);
|
|
16554
|
+
const allocations = greedyAllocate(slots, transferAmount, ctx.destinationChain, ctx.useForwarder, ctx.gasFeeOverrides);
|
|
16545
16555
|
assertFullyAllocated(allocations, transferAmount, ctx.amountIn);
|
|
16546
16556
|
// -----------------------------------------------------------------------
|
|
16547
16557
|
// 4. Phase 2 — Allocate developer fee
|
|
@@ -16549,7 +16559,7 @@ const DEFAULT_GAS_FEE = parseUnits('0.1', USDC_DECIMALS);
|
|
|
16549
16559
|
// Same-chain first here too — if the destination chain still has
|
|
16550
16560
|
// capacity, use it (same-chain fee intent = cheapest gas).
|
|
16551
16561
|
// -----------------------------------------------------------------------
|
|
16552
|
-
const developerFeeAllocations = greedyAllocate(slots, devFeeAmount, ctx.destinationChain, false);
|
|
16562
|
+
const developerFeeAllocations = greedyAllocate(slots, devFeeAmount, ctx.destinationChain, false, ctx.gasFeeOverrides);
|
|
16553
16563
|
if (devFeeAmount > 0n) {
|
|
16554
16564
|
assertFullyAllocated(developerFeeAllocations, devFeeAmount, formatUnits(devFeeAmount.toString(), USDC_DECIMALS));
|
|
16555
16565
|
}
|
|
@@ -16558,7 +16568,7 @@ const DEFAULT_GAS_FEE = parseUnits('0.1', USDC_DECIMALS);
|
|
|
16558
16568
|
// Again same ordering, same shared reduced balances.
|
|
16559
16569
|
// Same-chain first for the same reason.
|
|
16560
16570
|
// -----------------------------------------------------------------------
|
|
16561
|
-
const circleFeeAllocations = greedyAllocate(slots, circleFeeAmount, ctx.destinationChain, false);
|
|
16571
|
+
const circleFeeAllocations = greedyAllocate(slots, circleFeeAmount, ctx.destinationChain, false, ctx.gasFeeOverrides);
|
|
16562
16572
|
if (circleFeeAmount > 0n) {
|
|
16563
16573
|
assertFullyAllocated(circleFeeAllocations, circleFeeAmount, formatUnits(circleFeeAmount.toString(), USDC_DECIMALS));
|
|
16564
16574
|
}
|
|
@@ -16657,10 +16667,12 @@ const BPS_DIVISOR = 100_000n;
|
|
|
16657
16667
|
*
|
|
16658
16668
|
* Unlike `findChainNameByDomain` (which returns the display `name`),
|
|
16659
16669
|
* this returns `chain.chain` — the enum identifier expected by
|
|
16660
|
-
* {@link FeeAllocation}.
|
|
16670
|
+
* {@link FeeAllocation}. Returns `undefined` when no allocation covers the
|
|
16671
|
+
* domain so callers skip the intent rather than bucketing it under a
|
|
16672
|
+
* fabricated sentinel.
|
|
16661
16673
|
*/ function findBlockchainByDomain(domain, allocations) {
|
|
16662
16674
|
const alloc = allocations.find((a)=>a.chain.gateway.domain === domain);
|
|
16663
|
-
return alloc?.chain.chain
|
|
16675
|
+
return alloc?.chain.chain;
|
|
16664
16676
|
}
|
|
16665
16677
|
/**
|
|
16666
16678
|
* Normalize any address/salt format to lowercase bytes32 hex.
|
|
@@ -16784,6 +16796,33 @@ const BPS_DIVISOR = 100_000n;
|
|
|
16784
16796
|
};
|
|
16785
16797
|
});
|
|
16786
16798
|
}
|
|
16799
|
+
/**
|
|
16800
|
+
* Read an intent's transfer value as a BigInt, tolerating the string form
|
|
16801
|
+
* that can appear on estimate-response specs.
|
|
16802
|
+
*/ function intentValue(intent) {
|
|
16803
|
+
const { value } = intent.spec;
|
|
16804
|
+
return typeof value === 'bigint' ? value : safeBigInt(String(value), 'spec.value');
|
|
16805
|
+
}
|
|
16806
|
+
/**
|
|
16807
|
+
* Split a single intent's `maxFee` into its transfer-fee and gas-fee
|
|
16808
|
+
* components.
|
|
16809
|
+
*
|
|
16810
|
+
* `transferFee = value * GATEWAY_TRANSFER_FEE_SCALED_BPS / BPS_DIVISOR`
|
|
16811
|
+
* `gasFee = maxFee - transferFee`
|
|
16812
|
+
*
|
|
16813
|
+
* Same-chain transfers (withdrawals) do not incur a transfer fee, so the
|
|
16814
|
+
* whole `maxFee` is gas. See {@link aggregateFeesByIntent} for the caveats
|
|
16815
|
+
* on re-deriving the split locally.
|
|
16816
|
+
*/ function splitIntentFee(intent) {
|
|
16817
|
+
const { maxFee, spec } = intent;
|
|
16818
|
+
const isSameChain = spec.sourceDomain === spec.destinationDomain;
|
|
16819
|
+
const transferFee = isSameChain ? 0n : intentValue(intent) * GATEWAY_TRANSFER_FEE_SCALED_BPS / BPS_DIVISOR;
|
|
16820
|
+
const gasFee = maxFee > transferFee ? maxFee - transferFee : 0n;
|
|
16821
|
+
return {
|
|
16822
|
+
transferFee,
|
|
16823
|
+
gasFee
|
|
16824
|
+
};
|
|
16825
|
+
}
|
|
16787
16826
|
/**
|
|
16788
16827
|
* Decompose each intent's `maxFee` into a transfer fee and a gas fee,
|
|
16789
16828
|
* then aggregate both by source chain.
|
|
@@ -16808,7 +16847,6 @@ const BPS_DIVISOR = 100_000n;
|
|
|
16808
16847
|
* names from source domains.
|
|
16809
16848
|
* @returns Per-chain and total transfer/gas fee breakdowns.
|
|
16810
16849
|
*/ function aggregateFeesByIntent(estimatedIntents, allocations) {
|
|
16811
|
-
const transferFeeBps = GATEWAY_TRANSFER_FEE_SCALED_BPS;
|
|
16812
16850
|
const transferFeeByChain = new Map();
|
|
16813
16851
|
const gasFeeByChain = new Map();
|
|
16814
16852
|
let totalTransferFee = 0n;
|
|
@@ -16816,18 +16854,18 @@ const BPS_DIVISOR = 100_000n;
|
|
|
16816
16854
|
for (const intent of estimatedIntents){
|
|
16817
16855
|
const { maxFee, spec } = intent;
|
|
16818
16856
|
if (maxFee === 0n) continue;
|
|
16819
|
-
const
|
|
16820
|
-
|
|
16821
|
-
|
|
16822
|
-
|
|
16823
|
-
|
|
16857
|
+
const { transferFee, gasFee } = splitIntentFee(intent);
|
|
16858
|
+
totalTransferFee += transferFee;
|
|
16859
|
+
totalGasFee += gasFee;
|
|
16860
|
+
// Totals stay complete even if a domain can't be resolved; only the
|
|
16861
|
+
// per-chain breakdown skips it rather than inventing a placeholder chain.
|
|
16862
|
+
const chain = findBlockchainByDomain(spec.sourceDomain, allocations);
|
|
16863
|
+
if (chain === undefined) continue;
|
|
16824
16864
|
if (transferFee > 0n) {
|
|
16825
|
-
|
|
16826
|
-
transferFeeByChain.set(chainName, (transferFeeByChain.get(chainName) ?? 0n) + transferFee);
|
|
16865
|
+
transferFeeByChain.set(chain, (transferFeeByChain.get(chain) ?? 0n) + transferFee);
|
|
16827
16866
|
}
|
|
16828
16867
|
if (gasFee > 0n) {
|
|
16829
|
-
|
|
16830
|
-
gasFeeByChain.set(chainName, (gasFeeByChain.get(chainName) ?? 0n) + gasFee);
|
|
16868
|
+
gasFeeByChain.set(chain, (gasFeeByChain.get(chain) ?? 0n) + gasFee);
|
|
16831
16869
|
}
|
|
16832
16870
|
}
|
|
16833
16871
|
return {
|
|
@@ -16891,6 +16929,91 @@ const BPS_DIVISOR = 100_000n;
|
|
|
16891
16929
|
}
|
|
16892
16930
|
return fees;
|
|
16893
16931
|
}
|
|
16932
|
+
/**
|
|
16933
|
+
* Derive the real per-chain Gateway gas fee from estimated intents, keyed by
|
|
16934
|
+
* source {@link Blockchain}.
|
|
16935
|
+
*
|
|
16936
|
+
* The value is the maximum single-intent gas fee observed on each chain
|
|
16937
|
+
* (`maxFee − transferFee`) — the amount `computeAutoAllocation` must reserve
|
|
16938
|
+
* per burn intent on that chain. Gas is (near) amount-independent, so every
|
|
16939
|
+
* intent on a chain pays roughly the same; taking the max is a conservative
|
|
16940
|
+
* choice for the multi-intent-per-chain case.
|
|
16941
|
+
*
|
|
16942
|
+
* Intended for `AutoAllocationContext.gasFeeOverrides` so the corrective
|
|
16943
|
+
* re-allocation pass reserves the API's real fee instead of the static
|
|
16944
|
+
* {@link GAS_FEE_BY_CHAIN} constant.
|
|
16945
|
+
*
|
|
16946
|
+
* @param estimatedIntents - Intents with `maxFee` from {@link parseEstimateResponse}.
|
|
16947
|
+
* @param allocations - Normalised allocations used to resolve chain from source domain.
|
|
16948
|
+
* @returns Per-chain real gas fees in USDC atomic units.
|
|
16949
|
+
*
|
|
16950
|
+
* @example
|
|
16951
|
+
* ```typescript
|
|
16952
|
+
* import type { BurnIntent } from '../createIntent/types'
|
|
16953
|
+
* import type { NormalizedAllocation } from '../allocations'
|
|
16954
|
+
*
|
|
16955
|
+
* declare const estimatedIntents: BurnIntent[]
|
|
16956
|
+
* declare const allocations: NormalizedAllocation[]
|
|
16957
|
+
*
|
|
16958
|
+
* // Real per-chain gas, ready to pass as AutoAllocationContext.gasFeeOverrides
|
|
16959
|
+
* // to re-run computeAutoAllocation with the corrected reserve.
|
|
16960
|
+
* const overrides = deriveGasFeeOverrides(estimatedIntents, allocations)
|
|
16961
|
+
* ```
|
|
16962
|
+
*/ function deriveGasFeeOverrides(estimatedIntents, allocations) {
|
|
16963
|
+
const overrides = new Map();
|
|
16964
|
+
for (const intent of estimatedIntents){
|
|
16965
|
+
if (intent.maxFee === 0n) continue;
|
|
16966
|
+
const chain = findBlockchainByDomain(intent.spec.sourceDomain, allocations);
|
|
16967
|
+
if (chain === undefined) continue;
|
|
16968
|
+
const { gasFee } = splitIntentFee(intent);
|
|
16969
|
+
const prev = overrides.get(chain) ?? 0n;
|
|
16970
|
+
if (gasFee > prev) overrides.set(chain, gasFee);
|
|
16971
|
+
}
|
|
16972
|
+
return overrides;
|
|
16973
|
+
}
|
|
16974
|
+
/**
|
|
16975
|
+
* Sum the total balance each source chain must cover, keyed by source
|
|
16976
|
+
* {@link Blockchain}.
|
|
16977
|
+
*
|
|
16978
|
+
* Approximates the Gateway API's balance validation, which rejects a transfer
|
|
16979
|
+
* (`BALANCE_INSUFFICIENT_TOKEN`) when a depositor's confirmed balance on a
|
|
16980
|
+
* source chain is below `sum(intent.value + intent.maxFee)` for that
|
|
16981
|
+
* depositor's intents. This aggregates by chain across all sources, so it is
|
|
16982
|
+
* exact for the common single-depositor-per-chain wallet. When several
|
|
16983
|
+
* depositors hold USDC on the same chain, the chain-level sum can mask a
|
|
16984
|
+
* per-depositor shortfall (or a surplus on one depositor can hide it); the
|
|
16985
|
+
* API's own per-depositor `9001` remains the backstop for that case. Scope the
|
|
16986
|
+
* comparison per (depositor, chain) if that multi-depositor case must be caught
|
|
16987
|
+
* pre-submit.
|
|
16988
|
+
*
|
|
16989
|
+
* @param estimatedIntents - Intents with `maxFee` from {@link parseEstimateResponse}.
|
|
16990
|
+
* @param allocations - Normalised allocations used to resolve chain from source domain.
|
|
16991
|
+
* @returns Per-chain required amount (transfer value + fees) in USDC atomic units.
|
|
16992
|
+
*
|
|
16993
|
+
* @example
|
|
16994
|
+
* ```typescript
|
|
16995
|
+
* import { Blockchain } from '@core/chains'
|
|
16996
|
+
* import type { BurnIntent } from '../createIntent/types'
|
|
16997
|
+
* import type { NormalizedAllocation } from '../allocations'
|
|
16998
|
+
*
|
|
16999
|
+
* declare const estimatedIntents: BurnIntent[]
|
|
17000
|
+
* declare const allocations: NormalizedAllocation[]
|
|
17001
|
+
* declare const confirmedBalanceAtomic: bigint
|
|
17002
|
+
*
|
|
17003
|
+
* const required = sumRequiredPerChain(estimatedIntents, allocations)
|
|
17004
|
+
* const overDrawn =
|
|
17005
|
+
* (required.get(Blockchain.Ethereum) ?? 0n) > confirmedBalanceAtomic
|
|
17006
|
+
* ```
|
|
17007
|
+
*/ function sumRequiredPerChain(estimatedIntents, allocations) {
|
|
17008
|
+
const required = new Map();
|
|
17009
|
+
for (const intent of estimatedIntents){
|
|
17010
|
+
const chain = findBlockchainByDomain(intent.spec.sourceDomain, allocations);
|
|
17011
|
+
if (chain === undefined) continue;
|
|
17012
|
+
const amount = intentValue(intent) + intent.maxFee;
|
|
17013
|
+
required.set(chain, (required.get(chain) ?? 0n) + amount);
|
|
17014
|
+
}
|
|
17015
|
+
return required;
|
|
17016
|
+
}
|
|
16894
17017
|
|
|
16895
17018
|
/**
|
|
16896
17019
|
* Sign each adapter group: Solana one intent per signature, EVM batch per adapter.
|
|
@@ -17145,69 +17268,127 @@ const FORWARDER_POLL_TIMEOUT_MS = 300_000;
|
|
|
17145
17268
|
* non-forwarder transfer response is missing attestation or signature.
|
|
17146
17269
|
* @throws KitError Propagated from adapter signing if the user rejects
|
|
17147
17270
|
* or the signer is unavailable.
|
|
17148
|
-
*/
|
|
17149
|
-
|
|
17150
|
-
|
|
17151
|
-
|
|
17152
|
-
|
|
17153
|
-
|
|
17154
|
-
|
|
17155
|
-
|
|
17156
|
-
|
|
17157
|
-
|
|
17158
|
-
|
|
17159
|
-
|
|
17160
|
-
|
|
17161
|
-
|
|
17162
|
-
|
|
17163
|
-
|
|
17164
|
-
|
|
17165
|
-
|
|
17166
|
-
|
|
17167
|
-
|
|
17168
|
-
|
|
17169
|
-
|
|
17170
|
-
|
|
17171
|
-
|
|
17172
|
-
|
|
17173
|
-
|
|
17174
|
-
|
|
17175
|
-
|
|
17176
|
-
|
|
17177
|
-
|
|
17178
|
-
|
|
17179
|
-
|
|
17180
|
-
|
|
17181
|
-
|
|
17182
|
-
const breakdowns = balanceResults[i]?.breakdown[0]?.breakdown ?? [];
|
|
17183
|
-
for (const b of breakdowns){
|
|
17184
|
-
chainBalances.push({
|
|
17185
|
-
chain: b.chain,
|
|
17186
|
-
confirmedBalance: b.confirmedBalance,
|
|
17187
|
-
sourceIndex: i
|
|
17188
|
-
});
|
|
17271
|
+
*/ /**
|
|
17272
|
+
* Fetch confirmed per-chain USDC balances for every auto-allocation source.
|
|
17273
|
+
*
|
|
17274
|
+
* Used only on the `amountIn` (auto-allocation) path. Returns one
|
|
17275
|
+
* {@link ChainBalance} per (source, chain) pair so the greedy allocator — and
|
|
17276
|
+
* the corrective re-allocation pass — can reason about draw limits without a
|
|
17277
|
+
* second balance round-trip.
|
|
17278
|
+
*
|
|
17279
|
+
* @param params - Spend parameters (source(s) and token).
|
|
17280
|
+
* @param destChain - Resolved destination chain (used for network type).
|
|
17281
|
+
* @returns Confirmed balances tagged with their originating source index.
|
|
17282
|
+
*/ async function fetchChainBalances(params, destChain) {
|
|
17283
|
+
const rawSources = Array.isArray(params.from) ? params.from : [
|
|
17284
|
+
params.from
|
|
17285
|
+
];
|
|
17286
|
+
const sourcesArray = rawSources.filter((s)=>s != null);
|
|
17287
|
+
const networkType = destChain.isTestnet ? 'testnet' : 'mainnet';
|
|
17288
|
+
const balanceResults = await Promise.all(sourcesArray.map(async (source)=>{
|
|
17289
|
+
// When sourceAccount is set (delegate flow), scope the balance
|
|
17290
|
+
// query to the Gateway depositor — not the signer. Using the
|
|
17291
|
+
// address-only path bypasses adapter address resolution, which
|
|
17292
|
+
// would otherwise return the signer's balance (developer-
|
|
17293
|
+
// controlled) or reject an explicit address (user-controlled).
|
|
17294
|
+
let querySource;
|
|
17295
|
+
if (source.sourceAccount) {
|
|
17296
|
+
querySource = {
|
|
17297
|
+
address: source.sourceAccount
|
|
17298
|
+
};
|
|
17299
|
+
} else {
|
|
17300
|
+
querySource = {
|
|
17301
|
+
adapter: source.adapter
|
|
17302
|
+
};
|
|
17303
|
+
if ('address' in source && source.address) {
|
|
17304
|
+
querySource['address'] = source.address;
|
|
17189
17305
|
}
|
|
17190
17306
|
}
|
|
17191
|
-
|
|
17192
|
-
|
|
17193
|
-
|
|
17194
|
-
|
|
17195
|
-
chainBalances,
|
|
17196
|
-
useForwarder,
|
|
17197
|
-
...customFeeConfig ? {
|
|
17198
|
-
customFee: customFeeConfig
|
|
17199
|
-
} : {}
|
|
17307
|
+
return getBalances$1({
|
|
17308
|
+
token: params.token,
|
|
17309
|
+
sources: querySource,
|
|
17310
|
+
networkType
|
|
17200
17311
|
});
|
|
17201
|
-
|
|
17202
|
-
|
|
17203
|
-
|
|
17204
|
-
|
|
17205
|
-
|
|
17206
|
-
|
|
17207
|
-
|
|
17312
|
+
}));
|
|
17313
|
+
const chainBalances = [];
|
|
17314
|
+
for(let i = 0; i < balanceResults.length; i++){
|
|
17315
|
+
const breakdowns = balanceResults[i]?.breakdown[0]?.breakdown ?? [];
|
|
17316
|
+
for (const b of breakdowns){
|
|
17317
|
+
chainBalances.push({
|
|
17318
|
+
chain: b.chain,
|
|
17319
|
+
confirmedBalance: b.confirmedBalance,
|
|
17320
|
+
sourceIndex: i
|
|
17321
|
+
});
|
|
17322
|
+
}
|
|
17323
|
+
}
|
|
17324
|
+
return chainBalances;
|
|
17325
|
+
}
|
|
17326
|
+
/**
|
|
17327
|
+
* Build auto-allocated normalised allocations and burn intents from
|
|
17328
|
+
* pre-fetched balances.
|
|
17329
|
+
*
|
|
17330
|
+
* Performs no balance API call, so it can be re-invoked with
|
|
17331
|
+
* `gasFeeOverrides` (the real per-chain gas from a prior estimate) to correct
|
|
17332
|
+
* an over-draw without re-querying balances.
|
|
17333
|
+
*
|
|
17334
|
+
* @param params - Spend parameters (source(s), token, optional custom fee).
|
|
17335
|
+
* @param destChain - Resolved destination chain with Gateway v1 config.
|
|
17336
|
+
* @param recipientAddress - Resolved recipient address on the destination chain.
|
|
17337
|
+
* @param useForwarder - Whether the Forwarding Service path is active.
|
|
17338
|
+
* @param amountIn - Human-readable USDC amount to allocate.
|
|
17339
|
+
* @param chainBalances - Confirmed balances from {@link fetchChainBalances}.
|
|
17340
|
+
* @param gasFeeOverrides - Optional real per-chain gas fees to reserve.
|
|
17341
|
+
* @returns Normalised allocations and burn intents for the estimate/transfer API.
|
|
17342
|
+
*/ async function buildAutoAllocatedFromBalances(params, destChain, recipientAddress, useForwarder, amountIn, chainBalances, gasFeeOverrides) {
|
|
17343
|
+
const rawSources = Array.isArray(params.from) ? params.from : [
|
|
17344
|
+
params.from
|
|
17345
|
+
];
|
|
17346
|
+
const sourcesArray = rawSources.filter((s)=>s != null);
|
|
17347
|
+
const customFeeConfig = params.config?.customFee;
|
|
17348
|
+
const autoAllocResult = computeAutoAllocation({
|
|
17349
|
+
amountIn,
|
|
17350
|
+
destinationChain: destChain.chain,
|
|
17351
|
+
chainBalances,
|
|
17352
|
+
useForwarder,
|
|
17353
|
+
...customFeeConfig ? {
|
|
17354
|
+
customFee: customFeeConfig
|
|
17355
|
+
} : {},
|
|
17356
|
+
...gasFeeOverrides ? {
|
|
17357
|
+
gasFeeOverrides
|
|
17358
|
+
} : {}
|
|
17359
|
+
});
|
|
17360
|
+
const normalizedAutoAllocations = await normalizeAutoAllocations(autoAllocResult, sourcesArray);
|
|
17361
|
+
const intents = buildAutoAllocatedBurnIntents(normalizedAutoAllocations, destChain, recipientAddress, params.token, params.config?.customFee);
|
|
17362
|
+
const allocations = [
|
|
17363
|
+
...normalizedAutoAllocations.user,
|
|
17364
|
+
...normalizedAutoAllocations.devFee,
|
|
17365
|
+
...normalizedAutoAllocations.circleFee
|
|
17366
|
+
];
|
|
17367
|
+
return {
|
|
17368
|
+
allocations,
|
|
17369
|
+
intents
|
|
17370
|
+
};
|
|
17371
|
+
}
|
|
17372
|
+
/**
|
|
17373
|
+
* Resolve allocations and burn intents for the spend.
|
|
17374
|
+
*
|
|
17375
|
+
* Auto-allocation (`amountIn`) fetches balances once and returns them so the
|
|
17376
|
+
* caller can detect and correct over-draw without re-querying. Explicit
|
|
17377
|
+
* allocations return no balances (they are user-authoritative).
|
|
17378
|
+
*
|
|
17379
|
+
* @param params - Spend parameters.
|
|
17380
|
+
* @param destChain - Resolved destination chain with Gateway v1 config.
|
|
17381
|
+
* @param recipientAddress - Resolved recipient address.
|
|
17382
|
+
* @param useForwarder - Whether the Forwarding Service path is active.
|
|
17383
|
+
* @returns Allocations, intents, and (auto-allocation only) confirmed balances.
|
|
17384
|
+
*/ async function resolveAllocationsAndIntents(params, destChain, recipientAddress, useForwarder) {
|
|
17385
|
+
if (params.amountIn) {
|
|
17386
|
+
const chainBalances = await fetchChainBalances(params, destChain);
|
|
17387
|
+
const { allocations, intents } = await buildAutoAllocatedFromBalances(params, destChain, recipientAddress, useForwarder, params.amountIn, chainBalances);
|
|
17208
17388
|
return {
|
|
17209
17389
|
allocations,
|
|
17210
|
-
intents
|
|
17390
|
+
intents,
|
|
17391
|
+
chainBalances
|
|
17211
17392
|
};
|
|
17212
17393
|
}
|
|
17213
17394
|
const allocations = await normalizeAllocations(params);
|
|
@@ -17249,6 +17430,148 @@ const FORWARDER_POLL_TIMEOUT_MS = 300_000;
|
|
|
17249
17430
|
forwardingFee: undefined
|
|
17250
17431
|
};
|
|
17251
17432
|
}
|
|
17433
|
+
/** Sum confirmed balances (atomic USDC) per source chain. */ function computeAvailablePerChain(chainBalances) {
|
|
17434
|
+
const available = new Map();
|
|
17435
|
+
for (const b of chainBalances){
|
|
17436
|
+
const atomic = parseUnits(b.confirmedBalance, USDC_DECIMALS$1);
|
|
17437
|
+
available.set(b.chain, (available.get(b.chain) ?? 0n) + atomic);
|
|
17438
|
+
}
|
|
17439
|
+
return available;
|
|
17440
|
+
}
|
|
17441
|
+
/**
|
|
17442
|
+
* Detect source chains whose required draw (value + maxFee across their
|
|
17443
|
+
* intents) exceeds the confirmed balance — the condition the Gateway API
|
|
17444
|
+
* rejects with `BALANCE_INSUFFICIENT_TOKEN` at `/v1/transfer`.
|
|
17445
|
+
*
|
|
17446
|
+
* Both sides are summed per chain (see {@link sumRequiredPerChain} and
|
|
17447
|
+
* {@link computeAvailablePerChain}), so detection is exact for the common
|
|
17448
|
+
* single-depositor-per-chain wallet. When multiple depositors hold USDC on the
|
|
17449
|
+
* same chain, a chain-level surplus can mask a per-depositor shortfall; the
|
|
17450
|
+
* API's own per-depositor `9001` remains the backstop in that case.
|
|
17451
|
+
*/ function findOverdrawnChains(estimatedIntents, allocations, chainBalances) {
|
|
17452
|
+
const required = sumRequiredPerChain(estimatedIntents, allocations);
|
|
17453
|
+
const available = computeAvailablePerChain(chainBalances);
|
|
17454
|
+
const overdrawn = [];
|
|
17455
|
+
for (const [chain, req] of required){
|
|
17456
|
+
const avail = available.get(chain) ?? 0n;
|
|
17457
|
+
if (req > avail) {
|
|
17458
|
+
overdrawn.push({
|
|
17459
|
+
chain,
|
|
17460
|
+
required: req,
|
|
17461
|
+
available: avail
|
|
17462
|
+
});
|
|
17463
|
+
}
|
|
17464
|
+
}
|
|
17465
|
+
return overdrawn;
|
|
17466
|
+
}
|
|
17467
|
+
/**
|
|
17468
|
+
* Build a descriptive KitError for an auto-allocation gas shortfall that
|
|
17469
|
+
* survives the corrective re-allocation, naming the per-chain gap so the
|
|
17470
|
+
* caller sees the real cause instead of the opaque API 9001 rejection.
|
|
17471
|
+
*/ function createAutoAllocationGasError(overdrawn, cause) {
|
|
17472
|
+
const detail = overdrawn.map((o)=>`${String(o.chain)} needs ${formatUnits(o.required.toString(), USDC_DECIMALS$1)} USDC ` + `(transfer + gas) but only ${formatUnits(o.available.toString(), USDC_DECIMALS$1)} USDC is available`).join('; ');
|
|
17473
|
+
return new KitError({
|
|
17474
|
+
...BalanceError.INSUFFICIENT_GAS,
|
|
17475
|
+
recoverability: 'FATAL',
|
|
17476
|
+
message: `Insufficient USDC to cover the transfer amount plus Gateway gas fees: ${detail}. ` + `Reduce the amount or add USDC on the affected chain(s).`,
|
|
17477
|
+
...cause === undefined ? {} : {
|
|
17478
|
+
cause: {
|
|
17479
|
+
trace: {
|
|
17480
|
+
cause
|
|
17481
|
+
}
|
|
17482
|
+
}
|
|
17483
|
+
}
|
|
17484
|
+
});
|
|
17485
|
+
}
|
|
17486
|
+
/**
|
|
17487
|
+
* Validate allocations against the network/forwarder rules, call the estimate
|
|
17488
|
+
* API, and return the estimated intents with any forwarding fee.
|
|
17489
|
+
*
|
|
17490
|
+
* @param allocations - Normalised allocations for the estimate.
|
|
17491
|
+
* @param intents - Burn intents to estimate.
|
|
17492
|
+
* @param destChain - Resolved destination chain with Gateway v1 config.
|
|
17493
|
+
* @param useForwarder - Whether the Forwarding Service path is active.
|
|
17494
|
+
* @returns Estimated intents (with real maxFee) and optional forwarding fee.
|
|
17495
|
+
*/ async function validateAndEstimate(allocations, intents, destChain, useForwarder) {
|
|
17496
|
+
assertNetworkCompatibility(allocations, destChain);
|
|
17497
|
+
if (useForwarder) {
|
|
17498
|
+
assertForwarderRouteSupport(destChain, allocations);
|
|
17499
|
+
}
|
|
17500
|
+
const apiBaseUrl = getGatewayApiBaseUrl(destChain.isTestnet);
|
|
17501
|
+
const estimateBody = buildEstimateRequestBody(intents);
|
|
17502
|
+
const { entries, forwardingFee } = await fetchEstimate(apiBaseUrl, estimateBody, useForwarder, allocations);
|
|
17503
|
+
const estimatedIntents = parseEstimateResponse(entries, intents);
|
|
17504
|
+
return {
|
|
17505
|
+
estimatedIntents,
|
|
17506
|
+
forwardingFee
|
|
17507
|
+
};
|
|
17508
|
+
}
|
|
17509
|
+
/**
|
|
17510
|
+
* Fold newly-observed per-chain gas into the accumulated overrides, keeping the
|
|
17511
|
+
* higher fee per chain so a chain a later pass reveals is never under-reserved.
|
|
17512
|
+
*/ function mergeGasFeeOverrides(base, next) {
|
|
17513
|
+
const merged = new Map(base);
|
|
17514
|
+
for (const [chain, fee] of next){
|
|
17515
|
+
const prev = merged.get(chain);
|
|
17516
|
+
if (prev === undefined || fee > prev) {
|
|
17517
|
+
merged.set(chain, fee);
|
|
17518
|
+
}
|
|
17519
|
+
}
|
|
17520
|
+
return merged;
|
|
17521
|
+
}
|
|
17522
|
+
/**
|
|
17523
|
+
* Maximum corrective re-allocation passes before failing fast. One pass fixes
|
|
17524
|
+
* the common case; a second/third covers a chain that a spill only introduces
|
|
17525
|
+
* after gas is reserved. Bounds the worst case at this many extra estimate
|
|
17526
|
+
* round-trips (only ever reached when the balance genuinely falls short).
|
|
17527
|
+
*/ const MAX_CORRECTION_PASSES = 3;
|
|
17528
|
+
/**
|
|
17529
|
+
* Correct an auto-allocation over-draw: reserve the estimate's real per-chain
|
|
17530
|
+
* gas, re-allocate from the same balances, and re-estimate — repeating up to
|
|
17531
|
+
* {@link MAX_CORRECTION_PASSES} times, accumulating the real gas each pass
|
|
17532
|
+
* reveals.
|
|
17533
|
+
*
|
|
17534
|
+
* One pass fixes the common case, where the over-drawn chain was already in the
|
|
17535
|
+
* first estimate. A further pass covers a chain that a spill only introduces
|
|
17536
|
+
* once gas is reserved on the destination: that chain isn't in the first
|
|
17537
|
+
* estimate, so its real gas is unknown until it appears, and its first
|
|
17538
|
+
* re-allocation falls back to the static reserve. Each pass folds the newly
|
|
17539
|
+
* revealed gas into the overrides (see {@link mergeGasFeeOverrides}) so the
|
|
17540
|
+
* next pass reserves it too. Per-chain gas is ~amount-independent, so once
|
|
17541
|
+
* every drawn chain's real gas is known the allocation converges.
|
|
17542
|
+
*
|
|
17543
|
+
* When the shortfall is genuine — the re-allocation can't cover the amount, or
|
|
17544
|
+
* the passes are exhausted while still over-drawn — throws a gas-specific
|
|
17545
|
+
* {@link KitError} instead of submitting a doomed transfer.
|
|
17546
|
+
*/ async function correctOverdraw(opts) {
|
|
17547
|
+
let overrides = deriveGasFeeOverrides(opts.estimatedIntents, opts.allocations);
|
|
17548
|
+
let overdrawn = opts.overdrawn;
|
|
17549
|
+
for(let pass = 0; pass < MAX_CORRECTION_PASSES; pass++){
|
|
17550
|
+
let corrected;
|
|
17551
|
+
try {
|
|
17552
|
+
corrected = await buildAutoAllocatedFromBalances(opts.params, opts.destChain, opts.recipientAddress, opts.useForwarder, opts.amountIn, opts.chainBalances, overrides);
|
|
17553
|
+
} catch (err) {
|
|
17554
|
+
// Re-allocating with the real gas reserved can't cover the amount →
|
|
17555
|
+
// surface a gas-specific error instead of the opaque API rejection.
|
|
17556
|
+
if (err instanceof KitError && err.code === BalanceError.INSUFFICIENT_TOKEN.code) {
|
|
17557
|
+
throw createAutoAllocationGasError(overdrawn, err);
|
|
17558
|
+
}
|
|
17559
|
+
throw err;
|
|
17560
|
+
}
|
|
17561
|
+
const { estimatedIntents, forwardingFee } = await validateAndEstimate(corrected.allocations, corrected.intents, opts.destChain, opts.useForwarder);
|
|
17562
|
+
const stillOverdrawn = findOverdrawnChains(estimatedIntents, corrected.allocations, opts.chainBalances);
|
|
17563
|
+
if (stillOverdrawn.length === 0) {
|
|
17564
|
+
return {
|
|
17565
|
+
allocations: corrected.allocations,
|
|
17566
|
+
estimatedIntents,
|
|
17567
|
+
forwardingFee
|
|
17568
|
+
};
|
|
17569
|
+
}
|
|
17570
|
+
overdrawn = stillOverdrawn;
|
|
17571
|
+
overrides = mergeGasFeeOverrides(overrides, deriveGasFeeOverrides(estimatedIntents, corrected.allocations));
|
|
17572
|
+
}
|
|
17573
|
+
throw createAutoAllocationGasError(overdrawn);
|
|
17574
|
+
}
|
|
17252
17575
|
/**
|
|
17253
17576
|
* Validate allocations, call the estimate API, and return estimated intents.
|
|
17254
17577
|
*
|
|
@@ -17256,6 +17579,14 @@ const FORWARDER_POLL_TIMEOUT_MS = 300_000;
|
|
|
17256
17579
|
* path. Handles forwarder route validation, network compatibility, and the
|
|
17257
17580
|
* estimate API call.
|
|
17258
17581
|
*
|
|
17582
|
+
* For auto-allocation (`amountIn`), the greedy allocator reserves a static
|
|
17583
|
+
* per-chain gas fee that can undershoot the API's real fee, draining a source
|
|
17584
|
+
* (typically the destination chain) below `value + maxFee` and triggering a
|
|
17585
|
+
* `BALANCE_INSUFFICIENT_TOKEN` rejection. When the first estimate reveals such
|
|
17586
|
+
* an over-draw, a bounded corrective re-allocation reserves the real gas and
|
|
17587
|
+
* re-estimates until it converges or fails fast (see {@link correctOverdraw}).
|
|
17588
|
+
* Explicit allocations are user-authoritative and never re-allocated.
|
|
17589
|
+
*
|
|
17259
17590
|
* @param params - Spend parameters.
|
|
17260
17591
|
* @param destChain - Resolved destination chain with Gateway v1 config.
|
|
17261
17592
|
* @param recipientAddress - Resolved recipient address.
|
|
@@ -17265,15 +17596,24 @@ const FORWARDER_POLL_TIMEOUT_MS = 300_000;
|
|
|
17265
17596
|
if (useForwarder) {
|
|
17266
17597
|
assertForwarderRouteSupport(destChain);
|
|
17267
17598
|
}
|
|
17268
|
-
const { allocations, intents } = await resolveAllocationsAndIntents(params, destChain, recipientAddress, useForwarder);
|
|
17269
|
-
|
|
17270
|
-
if (
|
|
17271
|
-
|
|
17599
|
+
const { allocations, intents, chainBalances } = await resolveAllocationsAndIntents(params, destChain, recipientAddress, useForwarder);
|
|
17600
|
+
const { estimatedIntents, forwardingFee } = await validateAndEstimate(allocations, intents, destChain, useForwarder);
|
|
17601
|
+
if (params.amountIn && chainBalances) {
|
|
17602
|
+
const overdrawn = findOverdrawnChains(estimatedIntents, allocations, chainBalances);
|
|
17603
|
+
if (overdrawn.length > 0) {
|
|
17604
|
+
return correctOverdraw({
|
|
17605
|
+
params,
|
|
17606
|
+
destChain,
|
|
17607
|
+
recipientAddress,
|
|
17608
|
+
useForwarder,
|
|
17609
|
+
amountIn: params.amountIn,
|
|
17610
|
+
chainBalances,
|
|
17611
|
+
estimatedIntents,
|
|
17612
|
+
allocations,
|
|
17613
|
+
overdrawn
|
|
17614
|
+
});
|
|
17615
|
+
}
|
|
17272
17616
|
}
|
|
17273
|
-
const apiBaseUrl = getGatewayApiBaseUrl(destChain.isTestnet);
|
|
17274
|
-
const estimateBody = buildEstimateRequestBody(intents);
|
|
17275
|
-
const { entries, forwardingFee } = await fetchEstimate(apiBaseUrl, estimateBody, useForwarder, allocations);
|
|
17276
|
-
const estimatedIntents = parseEstimateResponse(entries, intents);
|
|
17277
17617
|
return {
|
|
17278
17618
|
allocations,
|
|
17279
17619
|
estimatedIntents,
|
|
@@ -18181,8 +18521,10 @@ const assertCustomFeePolicySymbol = Symbol('assertCustomFeePolicy');
|
|
|
18181
18521
|
*
|
|
18182
18522
|
* - `computeFee` — required function that receives resolved spend params
|
|
18183
18523
|
* and returns a fee as a string (or `Promise<string>`).
|
|
18184
|
-
* - `resolveFeeRecipientAddress` —
|
|
18185
|
-
* recipient address as a string (or `Promise<string>`).
|
|
18524
|
+
* - `resolveFeeRecipientAddress` — optional function that returns a
|
|
18525
|
+
* recipient address as a string (or `Promise<string>`). Omit it when
|
|
18526
|
+
* using `setFeeRecipients()`'s declarative map instead — a policy
|
|
18527
|
+
* with neither throws at spend time.
|
|
18186
18528
|
*
|
|
18187
18529
|
* @example
|
|
18188
18530
|
* ```ts
|
|
@@ -18195,7 +18537,7 @@ const assertCustomFeePolicySymbol = Symbol('assertCustomFeePolicy');
|
|
|
18195
18537
|
* ```
|
|
18196
18538
|
*/ const customFeePolicySchema = zod.z.object({
|
|
18197
18539
|
computeFee: zod.z.function().returns(zod.z.string().or(zod.z.promise(zod.z.string()))),
|
|
18198
|
-
resolveFeeRecipientAddress: zod.z.function().returns(zod.z.string().or(zod.z.promise(zod.z.string())))
|
|
18540
|
+
resolveFeeRecipientAddress: zod.z.function().returns(zod.z.string().or(zod.z.promise(zod.z.string()))).optional()
|
|
18199
18541
|
}).strict();
|
|
18200
18542
|
/**
|
|
18201
18543
|
* Assert that the provided value conforms to {@link CustomFeePolicy}.
|
|
@@ -18217,6 +18559,71 @@ const assertCustomFeePolicySymbol = Symbol('assertCustomFeePolicy');
|
|
|
18217
18559
|
validateWithStateTracking(config, customFeePolicySchema, 'UnifiedBalanceKit custom fee policy', assertCustomFeePolicySymbol);
|
|
18218
18560
|
}
|
|
18219
18561
|
|
|
18562
|
+
const assertFeeRecipientsConfigSymbol = Symbol('assertFeeRecipientsConfig');
|
|
18563
|
+
/**
|
|
18564
|
+
* Schema for validating {@link FeeRecipientsConfig}.
|
|
18565
|
+
*
|
|
18566
|
+
* Requires at least one of `evm`/`solana`, non-empty string values for
|
|
18567
|
+
* whichever keys are present, and — mirroring the `depositAccount`
|
|
18568
|
+
* validation in `deposit/validate/assertions` — an address format that
|
|
18569
|
+
* matches the given chain type (EVM hex vs Solana base58).
|
|
18570
|
+
*
|
|
18571
|
+
* @example
|
|
18572
|
+
* ```ts
|
|
18573
|
+
* const config = {
|
|
18574
|
+
* evm: '0x1234567890123456789012345678901234567890',
|
|
18575
|
+
* solana: '9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM',
|
|
18576
|
+
* }
|
|
18577
|
+
* const result = feeRecipientsConfigSchema.safeParse(config)
|
|
18578
|
+
* // result.success === true
|
|
18579
|
+
* ```
|
|
18580
|
+
*/ const feeRecipientsConfigSchema = zod.z.object({
|
|
18581
|
+
evm: zod.z.string().min(1, 'Fee recipient address is required.').optional(),
|
|
18582
|
+
solana: zod.z.string().min(1, 'Fee recipient address is required.').optional()
|
|
18583
|
+
}).strict().refine((config)=>Object.keys(config).length > 0, {
|
|
18584
|
+
message: 'At least one fee recipient (evm or solana) is required.'
|
|
18585
|
+
}).superRefine((config, ctx)=>{
|
|
18586
|
+
for (const type of Object.keys(config)){
|
|
18587
|
+
const address = config[type];
|
|
18588
|
+
if (address == null) continue;
|
|
18589
|
+
// `{ name: type, type }` is a placeholder chain identifier — only
|
|
18590
|
+
// `.type` is checked by these two helpers today, `.name` is unused.
|
|
18591
|
+
// No real ChainDefinition exists here, since validation runs before
|
|
18592
|
+
// a destination chain is resolved.
|
|
18593
|
+
if (!isValidAddressForChain(address, {
|
|
18594
|
+
name: type,
|
|
18595
|
+
type
|
|
18596
|
+
})) {
|
|
18597
|
+
const { expectedAddressFormat } = extractChainInfo({
|
|
18598
|
+
name: type,
|
|
18599
|
+
type
|
|
18600
|
+
});
|
|
18601
|
+
ctx.addIssue({
|
|
18602
|
+
code: zod.z.ZodIssueCode.custom,
|
|
18603
|
+
path: [
|
|
18604
|
+
type
|
|
18605
|
+
],
|
|
18606
|
+
message: `Invalid ${type} address "${address}". Expected ${expectedAddressFormat}.`
|
|
18607
|
+
});
|
|
18608
|
+
}
|
|
18609
|
+
}
|
|
18610
|
+
});
|
|
18611
|
+
/**
|
|
18612
|
+
* Assert that the provided value conforms to {@link FeeRecipientsConfig}.
|
|
18613
|
+
*
|
|
18614
|
+
* Throws a validation error with annotated paths if the configuration is
|
|
18615
|
+
* malformed.
|
|
18616
|
+
*
|
|
18617
|
+
* @param config - The fee recipients map to validate.
|
|
18618
|
+
*
|
|
18619
|
+
* @example
|
|
18620
|
+
* ```ts
|
|
18621
|
+
* assertFeeRecipientsConfig({ evm: '0x1234567890123456789012345678901234567890' })
|
|
18622
|
+
* ```
|
|
18623
|
+
*/ function assertFeeRecipientsConfig(config) {
|
|
18624
|
+
validateWithStateTracking(config, feeRecipientsConfigSchema, 'UnifiedBalanceKit fee recipients config', assertFeeRecipientsConfigSymbol);
|
|
18625
|
+
}
|
|
18626
|
+
|
|
18220
18627
|
function sameChain(a, b) {
|
|
18221
18628
|
return a.chain !== undefined && a.chain === b.chain;
|
|
18222
18629
|
}
|
|
@@ -19197,6 +19604,105 @@ function assertSourceAccountAddresses(from) {
|
|
|
19197
19604
|
config: params.config
|
|
19198
19605
|
};
|
|
19199
19606
|
}
|
|
19607
|
+
/**
|
|
19608
|
+
* Tracks, per {@link CustomFeePolicy} instance, which chain types have
|
|
19609
|
+
* already triggered the "falling back to resolveFeeRecipientAddress"
|
|
19610
|
+
* warning, so repeated `spend()`/`estimateSpend()` calls (e.g. live
|
|
19611
|
+
* quoting) warn once per (policy, chain type) pair rather than on every
|
|
19612
|
+
* call.
|
|
19613
|
+
*/ const warnedFeeRecipientFallbacks = new WeakMap();
|
|
19614
|
+
/**
|
|
19615
|
+
* Invoke `resolveFeeRecipientAddress` and validate its return value has a
|
|
19616
|
+
* plausible address format for `destChain`, the same check
|
|
19617
|
+
* `setFeeRecipients()` already applies at config time. Unlike the map,
|
|
19618
|
+
* the callback's return value can't be validated ahead of time, so it's
|
|
19619
|
+
* checked here instead — a malformed value throws immediately rather
|
|
19620
|
+
* than silently becoming the fee recipient.
|
|
19621
|
+
*
|
|
19622
|
+
* @internal
|
|
19623
|
+
*/ async function resolveFeeRecipientFromCallback(callback, destChain, params) {
|
|
19624
|
+
const address = await callback(destChain, params);
|
|
19625
|
+
if (!isValidAddressForChain(address, destChain)) {
|
|
19626
|
+
throw new KitError({
|
|
19627
|
+
...InputError.VALIDATION_FAILED,
|
|
19628
|
+
recoverability: 'FATAL',
|
|
19629
|
+
message: `resolveFeeRecipientAddress returned an invalid address ` + `"${address}" for chain type "${destChain.type}" ` + `(resolved destination: ${destChain.name}).`
|
|
19630
|
+
});
|
|
19631
|
+
}
|
|
19632
|
+
return address;
|
|
19633
|
+
}
|
|
19634
|
+
/**
|
|
19635
|
+
* Resolve the single fee recipient address for a spend.
|
|
19636
|
+
*
|
|
19637
|
+
* Every fee burn intent in a spend mints to the same destination
|
|
19638
|
+
* chain regardless of which source chain(s) funded it, so exactly one
|
|
19639
|
+
* recipient address — valid on `destChain` — is ever needed.
|
|
19640
|
+
*
|
|
19641
|
+
* `feeRecipients` (set via `setFeeRecipients`) takes priority over the
|
|
19642
|
+
* policy's `resolveFeeRecipientAddress` callback for any chain type it
|
|
19643
|
+
* has an entry for, since it's a direct lookup and doesn't require
|
|
19644
|
+
* invoking developer code. For a chain type `feeRecipients` doesn't
|
|
19645
|
+
* cover, it falls back to `resolveFeeRecipientAddress` if one is
|
|
19646
|
+
* configured — a warning is logged once per (policy, chain type) pair
|
|
19647
|
+
* so the fallback isn't a silent surprise, without spamming repeated
|
|
19648
|
+
* `estimateSpend()` calls used for live quoting. Throws if neither
|
|
19649
|
+
* resolves `destChain`'s type, or if `resolveFeeRecipientAddress`
|
|
19650
|
+
* resolves it to a malformed address (see
|
|
19651
|
+
* {@link resolveFeeRecipientFromCallback}).
|
|
19652
|
+
*
|
|
19653
|
+
* @internal
|
|
19654
|
+
*/ async function resolveFeeRecipient(destChain, policy, feeRecipients, params) {
|
|
19655
|
+
if (feeRecipients) {
|
|
19656
|
+
// `destChain.type` is `@core/chains`' broader `ChainType` union;
|
|
19657
|
+
// `FeeRecipientChainType` is the narrower subset this map supports
|
|
19658
|
+
// today. A type not present as a key simply has no configured
|
|
19659
|
+
// recipient, which is handled below.
|
|
19660
|
+
const type = destChain.type;
|
|
19661
|
+
const recipientAddress = feeRecipients[type];
|
|
19662
|
+
if (recipientAddress) {
|
|
19663
|
+
return recipientAddress;
|
|
19664
|
+
}
|
|
19665
|
+
if (policy.resolveFeeRecipientAddress) {
|
|
19666
|
+
const warnedTypes = warnedFeeRecipientFallbacks.get(policy);
|
|
19667
|
+
if (!warnedTypes?.has(type)) {
|
|
19668
|
+
warnedFeeRecipientFallbacks.set(policy, (warnedTypes ?? new Set()).add(type));
|
|
19669
|
+
console.warn(`setFeeRecipients() is configured but has no entry for chain ` + `type "${type}" — falling back to customFeePolicy.` + `resolveFeeRecipientAddress for this chain type. Add a ` + `"${type}" entry to setFeeRecipients() to avoid this fallback.`);
|
|
19670
|
+
}
|
|
19671
|
+
return resolveFeeRecipientFromCallback(policy.resolveFeeRecipientAddress, destChain, params);
|
|
19672
|
+
}
|
|
19673
|
+
throw new KitError({
|
|
19674
|
+
...InputError.VALIDATION_FAILED,
|
|
19675
|
+
recoverability: 'FATAL',
|
|
19676
|
+
message: `No fee recipient configured for chain type "${type}" ` + `(resolved destination: ${destChain.name}). Call setFeeRecipients() ` + `with an entry for "${type}", or provide resolveFeeRecipientAddress ` + `on the custom fee policy.`
|
|
19677
|
+
});
|
|
19678
|
+
}
|
|
19679
|
+
if (!policy.resolveFeeRecipientAddress) {
|
|
19680
|
+
throw new KitError({
|
|
19681
|
+
...InputError.VALIDATION_FAILED,
|
|
19682
|
+
recoverability: 'FATAL',
|
|
19683
|
+
message: 'No fee recipient configured — call setFeeRecipients() or provide ' + 'resolveFeeRecipientAddress on the custom fee policy.'
|
|
19684
|
+
});
|
|
19685
|
+
}
|
|
19686
|
+
return resolveFeeRecipientFromCallback(policy.resolveFeeRecipientAddress, destChain, params);
|
|
19687
|
+
}
|
|
19688
|
+
/**
|
|
19689
|
+
* Guard against a common misconfiguration: a developer sets the
|
|
19690
|
+
* declarative `feeRecipients` map expecting it alone to drive fee
|
|
19691
|
+
* collection, but no fee is ever charged without a `computeFee` from
|
|
19692
|
+
* `customFeePolicy` to determine the amount. Without this check that
|
|
19693
|
+
* misconfiguration fails silently — no fee is charged and no error is
|
|
19694
|
+
* raised.
|
|
19695
|
+
*
|
|
19696
|
+
* @internal
|
|
19697
|
+
*/ function assertFeeRecipientsHasPolicy(feeRecipients) {
|
|
19698
|
+
if (feeRecipients) {
|
|
19699
|
+
throw new KitError({
|
|
19700
|
+
...InputError.VALIDATION_FAILED,
|
|
19701
|
+
recoverability: 'FATAL',
|
|
19702
|
+
message: 'setFeeRecipients() is configured but no developer fee will be ' + 'charged: setCustomFeePolicy() must also be set to provide ' + 'computeFee, which determines the fee amount. Call ' + 'setCustomFeePolicy(), or remove setFeeRecipients() if no ' + 'developer fee is intended.'
|
|
19703
|
+
});
|
|
19704
|
+
}
|
|
19705
|
+
}
|
|
19200
19706
|
/**
|
|
19201
19707
|
* Apply a {@link CustomFeePolicy} to an adapter-only spend.
|
|
19202
19708
|
*
|
|
@@ -19205,15 +19711,20 @@ function assertSourceAccountAddresses(from) {
|
|
|
19205
19711
|
* `config.customFee` so the provider sees it.
|
|
19206
19712
|
*
|
|
19207
19713
|
* @internal
|
|
19208
|
-
*/ async function mergeCustomFeePolicyForAdapterOnly(params, policy) {
|
|
19209
|
-
if (params.config?.customFee
|
|
19714
|
+
*/ async function mergeCustomFeePolicyForAdapterOnly(params, policy, feeRecipients) {
|
|
19715
|
+
if (params.config?.customFee) {
|
|
19716
|
+
return params;
|
|
19717
|
+
}
|
|
19718
|
+
if (!policy) {
|
|
19719
|
+
assertFeeRecipientsHasPolicy(feeRecipients);
|
|
19210
19720
|
return params;
|
|
19211
19721
|
}
|
|
19212
19722
|
const destChain = resolveChainIdentifier(params.to.chain);
|
|
19213
|
-
|
|
19214
|
-
|
|
19215
|
-
|
|
19216
|
-
|
|
19723
|
+
// Resolve the recipient before computing the fee: a KitError here
|
|
19724
|
+
// (missing/unresolvable recipient) shouldn't be preceded by an
|
|
19725
|
+
// otherwise-wasted computeFee call, which may be a network request.
|
|
19726
|
+
const recipientAddress = await resolveFeeRecipient(destChain, policy, feeRecipients, params);
|
|
19727
|
+
const feeValue = await policy.computeFee(params);
|
|
19217
19728
|
return {
|
|
19218
19729
|
...params,
|
|
19219
19730
|
config: {
|
|
@@ -19243,18 +19754,35 @@ function assertSourceAccountAddresses(from) {
|
|
|
19243
19754
|
});
|
|
19244
19755
|
}
|
|
19245
19756
|
}
|
|
19246
|
-
async function mergeCustomFeeConfig(resolved, policy) {
|
|
19247
|
-
if (resolved.config?.customFee
|
|
19757
|
+
async function mergeCustomFeeConfig(resolved, policy, feeRecipients) {
|
|
19758
|
+
if (resolved.config?.customFee) {
|
|
19248
19759
|
return resolved;
|
|
19249
19760
|
}
|
|
19250
|
-
|
|
19251
|
-
|
|
19761
|
+
if (!policy) {
|
|
19762
|
+
assertFeeRecipientsHasPolicy(feeRecipients);
|
|
19252
19763
|
return resolved;
|
|
19253
19764
|
}
|
|
19254
|
-
|
|
19255
|
-
|
|
19256
|
-
|
|
19257
|
-
|
|
19765
|
+
// Skip fee resolution when there's no source chain to spend from at
|
|
19766
|
+
// all. This state can't arise from validated input today — the
|
|
19767
|
+
// caller re-checks and throws "No source chain found" right after
|
|
19768
|
+
// this returns — but skipping here isn't dead code: verified that
|
|
19769
|
+
// removing it lets a degenerate zero-allocation resolved value reach
|
|
19770
|
+
// computeFee/assertDeveloperFeeWithinBounds first, which throws a
|
|
19771
|
+
// misleading "Developer fee must be less than the total spend
|
|
19772
|
+
// amount" (0 >= 0 total allocation) instead of the correct "No
|
|
19773
|
+
// source chain found" error — or, for a real developer computeFee
|
|
19774
|
+
// that assumes a non-empty allocation, an uncaught raw exception
|
|
19775
|
+
// instead of any KitError at all. This guard exists to guarantee the
|
|
19776
|
+
// caller's clear error is what actually surfaces, not for
|
|
19777
|
+
// correctness.
|
|
19778
|
+
if (collectSourceChains(resolved).length === 0) {
|
|
19779
|
+
return resolved;
|
|
19780
|
+
}
|
|
19781
|
+
// Resolve the recipient before computing the fee: a KitError here
|
|
19782
|
+
// (missing/unresolvable recipient) shouldn't be preceded by an
|
|
19783
|
+
// otherwise-wasted computeFee call, which may be a network request.
|
|
19784
|
+
const recipientAddress = await resolveFeeRecipient(resolved.to.chain, policy, feeRecipients, resolved);
|
|
19785
|
+
const feeValue = await policy.computeFee(resolved);
|
|
19258
19786
|
return {
|
|
19259
19787
|
...resolved,
|
|
19260
19788
|
config: {
|
|
@@ -19310,14 +19838,14 @@ async function mergeCustomFeeConfig(resolved, policy) {
|
|
|
19310
19838
|
}
|
|
19311
19839
|
const destChain = resolveChainIdentifier(params.to.chain);
|
|
19312
19840
|
if (!hasExplicitAllocations(params.from)) {
|
|
19313
|
-
const merged = await mergeCustomFeePolicyForAdapterOnly(params, context.customFeePolicy);
|
|
19841
|
+
const merged = await mergeCustomFeePolicyForAdapterOnly(params, context.customFeePolicy, context.feeRecipients);
|
|
19314
19842
|
assertDeveloperFeeWithinAmount(merged);
|
|
19315
19843
|
const provider = findProviderForChain(context, normalizeToken(merged.token), destChain);
|
|
19316
19844
|
return callSpend(provider, toProviderAdapterOnlyParams(merged));
|
|
19317
19845
|
}
|
|
19318
19846
|
const resolved = await resolveSpendParams(params);
|
|
19319
19847
|
assertSpendNetworkCompatibility(resolved);
|
|
19320
|
-
const withFee = await mergeCustomFeeConfig(resolved, context.customFeePolicy);
|
|
19848
|
+
const withFee = await mergeCustomFeeConfig(resolved, context.customFeePolicy, context.feeRecipients);
|
|
19321
19849
|
assertDeveloperFeeWithinBounds(withFee);
|
|
19322
19850
|
const sourceChains = collectSourceChains(withFee);
|
|
19323
19851
|
if (sourceChains.length === 0) {
|
|
@@ -19357,14 +19885,14 @@ async function mergeCustomFeeConfig(resolved, policy) {
|
|
|
19357
19885
|
assertSpendParams(params);
|
|
19358
19886
|
const destChain = resolveChainIdentifier(params.to.chain);
|
|
19359
19887
|
if (!hasExplicitAllocations(params.from)) {
|
|
19360
|
-
const merged = await mergeCustomFeePolicyForAdapterOnly(params, context.customFeePolicy);
|
|
19888
|
+
const merged = await mergeCustomFeePolicyForAdapterOnly(params, context.customFeePolicy, context.feeRecipients);
|
|
19361
19889
|
assertDeveloperFeeWithinAmount(merged);
|
|
19362
19890
|
const provider = findProviderForChain(context, normalizeToken(merged.token), destChain);
|
|
19363
19891
|
return provider.estimateSpend(toProviderAdapterOnlyParams(merged));
|
|
19364
19892
|
}
|
|
19365
19893
|
const resolved = await resolveSpendParams(params);
|
|
19366
19894
|
assertSpendNetworkCompatibility(resolved);
|
|
19367
|
-
const withFee = await mergeCustomFeeConfig(resolved, context.customFeePolicy);
|
|
19895
|
+
const withFee = await mergeCustomFeeConfig(resolved, context.customFeePolicy, context.feeRecipients);
|
|
19368
19896
|
assertDeveloperFeeWithinBounds(withFee);
|
|
19369
19897
|
const sourceChains = collectSourceChains(withFee);
|
|
19370
19898
|
if (sourceChains.length === 0) {
|
|
@@ -20339,6 +20867,46 @@ const removeFundParamsSchema = zod.z.object({
|
|
|
20339
20867
|
*/ removeCustomFeePolicy() {
|
|
20340
20868
|
delete this.context.customFeePolicy;
|
|
20341
20869
|
}
|
|
20870
|
+
/**
|
|
20871
|
+
* Set a declarative fee recipient map, keyed by chain type. Once set,
|
|
20872
|
+
* `spend()`/`estimateSpend()` resolve the fee recipient by looking up
|
|
20873
|
+
* the spend's destination chain type in this map — taking priority
|
|
20874
|
+
* over `customFeePolicy`'s `resolveFeeRecipientAddress` callback.
|
|
20875
|
+
*
|
|
20876
|
+
* @remarks
|
|
20877
|
+
* This only controls which address a fee is sent to — it does not by
|
|
20878
|
+
* itself cause any fee to be charged. You still need
|
|
20879
|
+
* {@link UnifiedBalanceKit.setCustomFeePolicy}'s `computeFee` to
|
|
20880
|
+
* determine the fee amount; calling `setFeeRecipients` without ever
|
|
20881
|
+
* calling `setCustomFeePolicy` throws at spend time (there is no
|
|
20882
|
+
* `computeFee` to determine an amount).
|
|
20883
|
+
*
|
|
20884
|
+
* @param config - Fee recipient addresses keyed by chain type (e.g.
|
|
20885
|
+
* `{ evm: '0x...', solana: 'Sol...' }`). Provide entries for every
|
|
20886
|
+
* chain type you expect to spend to; spending to a chain type with
|
|
20887
|
+
* no matching entry throws before any fee collection is attempted.
|
|
20888
|
+
*
|
|
20889
|
+
* @example
|
|
20890
|
+
* ```typescript
|
|
20891
|
+
* kit.setFeeRecipients({
|
|
20892
|
+
* evm: '0x1234567890123456789012345678901234567890',
|
|
20893
|
+
* solana: '9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM',
|
|
20894
|
+
* })
|
|
20895
|
+
* ```
|
|
20896
|
+
*/ setFeeRecipients(config) {
|
|
20897
|
+
assertFeeRecipientsConfig(config);
|
|
20898
|
+
this.context.feeRecipients = config;
|
|
20899
|
+
}
|
|
20900
|
+
/**
|
|
20901
|
+
* Remove the declarative fee recipient map for the kit.
|
|
20902
|
+
*
|
|
20903
|
+
* @example
|
|
20904
|
+
* ```typescript
|
|
20905
|
+
* kit.removeFeeRecipients()
|
|
20906
|
+
* ```
|
|
20907
|
+
*/ removeFeeRecipients() {
|
|
20908
|
+
delete this.context.feeRecipients;
|
|
20909
|
+
}
|
|
20342
20910
|
}
|
|
20343
20911
|
|
|
20344
20912
|
// Auto-register this kit for user agent tracking
|
|
@@ -20665,6 +21233,45 @@ registerKit(`${pkg.name}/${pkg.version}`);
|
|
|
20665
21233
|
*/ removeCustomFeePolicy() {
|
|
20666
21234
|
this.kit.removeCustomFeePolicy();
|
|
20667
21235
|
}
|
|
21236
|
+
/**
|
|
21237
|
+
* Set a declarative fee recipient map, keyed by chain type.
|
|
21238
|
+
*
|
|
21239
|
+
* Once set, `spend()`/`estimateSpend()` resolve the fee recipient by
|
|
21240
|
+
* looking up the spend's destination chain type in this map — taking
|
|
21241
|
+
* priority over `customFeePolicy`'s `resolveFeeRecipientAddress`
|
|
21242
|
+
* callback.
|
|
21243
|
+
*
|
|
21244
|
+
* @remarks
|
|
21245
|
+
* This only controls which address a fee is sent to — it does not by
|
|
21246
|
+
* itself cause any fee to be charged. You still need
|
|
21247
|
+
* `setCustomFeePolicy`'s `computeFee` to determine the fee amount;
|
|
21248
|
+
* calling `setFeeRecipients` without ever calling `setCustomFeePolicy`
|
|
21249
|
+
* throws at spend time (there is no `computeFee` to determine an
|
|
21250
|
+
* amount).
|
|
21251
|
+
*
|
|
21252
|
+
* @param config - Fee recipient addresses keyed by chain type (e.g.
|
|
21253
|
+
* `{ evm: '0x...', solana: 'Sol...' }`).
|
|
21254
|
+
*
|
|
21255
|
+
* @example
|
|
21256
|
+
* ```typescript
|
|
21257
|
+
* kit.unifiedBalance.setFeeRecipients({
|
|
21258
|
+
* evm: '0x1234567890123456789012345678901234567890',
|
|
21259
|
+
* solana: '9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM',
|
|
21260
|
+
* })
|
|
21261
|
+
* ```
|
|
21262
|
+
*/ setFeeRecipients(config) {
|
|
21263
|
+
this.kit.setFeeRecipients(config);
|
|
21264
|
+
}
|
|
21265
|
+
/**
|
|
21266
|
+
* Remove the declarative fee recipient map.
|
|
21267
|
+
*
|
|
21268
|
+
* @example
|
|
21269
|
+
* ```typescript
|
|
21270
|
+
* kit.unifiedBalance.removeFeeRecipients()
|
|
21271
|
+
* ```
|
|
21272
|
+
*/ removeFeeRecipients() {
|
|
21273
|
+
this.kit.removeFeeRecipients();
|
|
21274
|
+
}
|
|
20668
21275
|
}
|
|
20669
21276
|
|
|
20670
21277
|
exports.AppKitUnifiedBalance = AppKitUnifiedBalance;
|