accounts 0.19.0 → 0.19.1
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 +8 -0
- package/dist/server/internal/handlers/feeLiquidity.d.ts +28 -0
- package/dist/server/internal/handlers/feeLiquidity.d.ts.map +1 -0
- package/dist/server/internal/handlers/feeLiquidity.js +80 -0
- package/dist/server/internal/handlers/feeLiquidity.js.map +1 -0
- package/dist/server/internal/handlers/relay.d.ts.map +1 -1
- package/dist/server/internal/handlers/relay.js +87 -44
- package/dist/server/internal/handlers/relay.js.map +1 -1
- package/package.json +5 -5
- package/src/core/AccessKey.test.ts +1 -1
- package/src/core/Provider.localnet.test.ts +1 -1
- package/src/core/adapters/privy.test.ts +1 -1
- package/src/server/internal/handlers/feeLiquidity.localnet.test.ts +398 -0
- package/src/server/internal/handlers/feeLiquidity.ts +117 -0
- package/src/server/internal/handlers/relay.localnet.test.ts +44 -8
- package/src/server/internal/handlers/relay.ts +94 -44
|
@@ -26,6 +26,7 @@ import { type Handler, from } from '../../Handler.js'
|
|
|
26
26
|
import * as Kv from '../../Kv.js'
|
|
27
27
|
import { cached } from '../kv.js'
|
|
28
28
|
import * as Tokenlist from '../tokenlist.js'
|
|
29
|
+
import * as FeeLiquidity from './feeLiquidity.js'
|
|
29
30
|
import * as Sponsorship from './sponsorship.js'
|
|
30
31
|
import * as Utils from './utils.js'
|
|
31
32
|
|
|
@@ -210,6 +211,72 @@ export function relay(options: relay.Options = {}): Handler {
|
|
|
210
211
|
),
|
|
211
212
|
]
|
|
212
213
|
|
|
214
|
+
async function fillUnsponsored() {
|
|
215
|
+
const excluded = new Set<string>()
|
|
216
|
+
const balances = new Map<string, Promise<bigint>>()
|
|
217
|
+
const liquidity = new Map<string, ReturnType<typeof FeeLiquidity.prepare>>()
|
|
218
|
+
function prepareFeeToken(token: Address) {
|
|
219
|
+
const key = token.toLowerCase()
|
|
220
|
+
let pending = liquidity.get(key)
|
|
221
|
+
if (!pending) {
|
|
222
|
+
pending = FeeLiquidity.prepare(client, { token, kv })
|
|
223
|
+
liquidity.set(key, pending)
|
|
224
|
+
void pending.catch(() => {})
|
|
225
|
+
}
|
|
226
|
+
return pending
|
|
227
|
+
}
|
|
228
|
+
let liquidityError: Error | undefined
|
|
229
|
+
for (;;) {
|
|
230
|
+
const token = features.feeTokenResolution
|
|
231
|
+
? await resolveFeeToken(client, {
|
|
232
|
+
account: from,
|
|
233
|
+
feeToken: requestFeeToken,
|
|
234
|
+
kv,
|
|
235
|
+
tokens: unsponsoredTokens,
|
|
236
|
+
excluded,
|
|
237
|
+
balances,
|
|
238
|
+
prepareFeeToken,
|
|
239
|
+
})
|
|
240
|
+
: requestFeeToken
|
|
241
|
+
if (!token && liquidityError) throw liquidityError
|
|
242
|
+
try {
|
|
243
|
+
const result = await fill(client, {
|
|
244
|
+
autoSwap,
|
|
245
|
+
feeToken: token,
|
|
246
|
+
kv,
|
|
247
|
+
resolveFeeToken: resolveFeeTokenForSwap,
|
|
248
|
+
transaction: { ...normalized, chainId, ...(token ? { feeToken: token } : {}) },
|
|
249
|
+
})
|
|
250
|
+
// Estimation can succeed even when the fee AMM cannot settle
|
|
251
|
+
// the fee. Check the filled maximum fee before asking for a signature.
|
|
252
|
+
if (token && !requestFeeToken && features.feeTokenResolution) {
|
|
253
|
+
const { gas, maxFeePerGas } = result.transaction
|
|
254
|
+
if (
|
|
255
|
+
gas &&
|
|
256
|
+
maxFeePerGas &&
|
|
257
|
+
!(await (
|
|
258
|
+
await prepareFeeToken(token)
|
|
259
|
+
)((gas * maxFeePerGas + 10n ** 12n - 1n) / 10n ** 12n))
|
|
260
|
+
)
|
|
261
|
+
throw new Error('Insufficient liquidity in FeeAMM pool for transaction fee.')
|
|
262
|
+
}
|
|
263
|
+
return { result, token }
|
|
264
|
+
} catch (error) {
|
|
265
|
+
// Never change an explicit fee-token choice or retry unrelated failures.
|
|
266
|
+
if (
|
|
267
|
+
!token ||
|
|
268
|
+
requestFeeToken ||
|
|
269
|
+
!features.feeTokenResolution ||
|
|
270
|
+
!(error instanceof Error) ||
|
|
271
|
+
!/insufficient liquidity in FeeAMM pool/i.test(error.message)
|
|
272
|
+
)
|
|
273
|
+
throw error
|
|
274
|
+
excluded.add(token.toLowerCase())
|
|
275
|
+
liquidityError = error
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
213
280
|
// When the app provides its own fee payer URL, route the fill
|
|
214
281
|
// through that service so it can sign the transaction.
|
|
215
282
|
const fillClient = externalFeePayerUrl
|
|
@@ -299,44 +366,16 @@ export function relay(options: relay.Options = {}): Handler {
|
|
|
299
366
|
filled = fill_sponsored
|
|
300
367
|
} else {
|
|
301
368
|
// Sponsor rejected — resolve fee token and fill unsponsored.
|
|
302
|
-
const
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
feeToken: requestFeeToken,
|
|
306
|
-
kv,
|
|
307
|
-
tokens: unsponsoredTokens,
|
|
308
|
-
})
|
|
309
|
-
: requestFeeToken
|
|
310
|
-
const tx_unsponsored = {
|
|
311
|
-
...baseTx,
|
|
312
|
-
...(feeToken_unsponsored ? { feeToken: feeToken_unsponsored } : {}),
|
|
313
|
-
}
|
|
314
|
-
filled = await fill(client, {
|
|
315
|
-
...options,
|
|
316
|
-
feeToken: feeToken_unsponsored,
|
|
317
|
-
transaction: tx_unsponsored,
|
|
318
|
-
})
|
|
319
|
-
feeToken = feeToken_unsponsored
|
|
369
|
+
const { result, token } = await fillUnsponsored()
|
|
370
|
+
filled = result
|
|
371
|
+
feeToken = token
|
|
320
372
|
}
|
|
321
373
|
}
|
|
322
374
|
} else {
|
|
323
375
|
// Path C: no sponsorship configured — resolve fee token, fill once.
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
feeToken: requestFeeToken,
|
|
328
|
-
kv,
|
|
329
|
-
tokens: unsponsoredTokens,
|
|
330
|
-
})
|
|
331
|
-
: requestFeeToken
|
|
332
|
-
const transaction = { ...baseTx, ...(feeToken ? { feeToken } : {}) }
|
|
333
|
-
filled = await fill(client, {
|
|
334
|
-
autoSwap,
|
|
335
|
-
feeToken,
|
|
336
|
-
kv,
|
|
337
|
-
resolveFeeToken: resolveFeeTokenForSwap,
|
|
338
|
-
transaction,
|
|
339
|
-
})
|
|
376
|
+
const { result, token } = await fillUnsponsored()
|
|
377
|
+
filled = result
|
|
378
|
+
feeToken = token
|
|
340
379
|
}
|
|
341
380
|
|
|
342
381
|
const transaction_filled = filled.transaction
|
|
@@ -859,6 +898,20 @@ async function resolveFeeToken(
|
|
|
859
898
|
const { feeToken, account, kv, tokens } = options
|
|
860
899
|
if (feeToken) return feeToken
|
|
861
900
|
if (!account) return undefined
|
|
901
|
+
const sender = account
|
|
902
|
+
|
|
903
|
+
function getBalance(token: Address) {
|
|
904
|
+
options.prepareFeeToken?.(token)
|
|
905
|
+
const key = token.toLowerCase()
|
|
906
|
+
const pending = options.balances?.get(key)
|
|
907
|
+
if (pending) return pending
|
|
908
|
+
const balance = Actions.token
|
|
909
|
+
.getBalance(client, { account: sender, token })
|
|
910
|
+
.then(({ amount }) => amount)
|
|
911
|
+
.catch(() => 0n)
|
|
912
|
+
options.balances?.set(key, balance)
|
|
913
|
+
return balance
|
|
914
|
+
}
|
|
862
915
|
|
|
863
916
|
// Cache the user's preferred fee token for `userTokenCacheTtl` seconds.
|
|
864
917
|
// The on-chain preference rarely changes; a short TTL avoids the
|
|
@@ -885,17 +938,14 @@ async function resolveFeeToken(
|
|
|
885
938
|
? Promise.all(
|
|
886
939
|
tokens.map(async (token) => ({
|
|
887
940
|
address: token,
|
|
888
|
-
balance: await
|
|
889
|
-
.getBalance(client, { account, token })
|
|
890
|
-
.then(({ amount }) => amount)
|
|
891
|
-
.catch(() => 0n),
|
|
941
|
+
balance: await getBalance(token),
|
|
892
942
|
})),
|
|
893
943
|
)
|
|
894
944
|
: [],
|
|
895
945
|
])
|
|
896
946
|
|
|
897
947
|
// If on-chain preference is set and user has balance, use it.
|
|
898
|
-
if (userToken) {
|
|
948
|
+
if (userToken && !options.excluded?.has(userToken.address.toLowerCase())) {
|
|
899
949
|
const match = balances.find(
|
|
900
950
|
(b) => b.address.toLowerCase() === userToken.address.toLowerCase() && b.balance > 0n,
|
|
901
951
|
)
|
|
@@ -904,11 +954,7 @@ async function resolveFeeToken(
|
|
|
904
954
|
// Token list may not include the preference — check on-chain directly.
|
|
905
955
|
if (!match) {
|
|
906
956
|
try {
|
|
907
|
-
|
|
908
|
-
account,
|
|
909
|
-
token: userToken.address,
|
|
910
|
-
})
|
|
911
|
-
if (balance.amount > 0n) return userToken.address
|
|
957
|
+
if ((await getBalance(userToken.address)) > 0n) return userToken.address
|
|
912
958
|
} catch {}
|
|
913
959
|
}
|
|
914
960
|
}
|
|
@@ -917,6 +963,7 @@ async function resolveFeeToken(
|
|
|
917
963
|
let best: { address: Address; balance: bigint } | undefined
|
|
918
964
|
for (const asset of balances) {
|
|
919
965
|
if (asset.balance <= 0n) continue
|
|
966
|
+
if (options.excluded?.has(asset.address.toLowerCase())) continue
|
|
920
967
|
if (!best || asset.balance > best.balance) best = asset
|
|
921
968
|
}
|
|
922
969
|
if (best) return best.address
|
|
@@ -928,6 +975,9 @@ declare namespace resolveFeeToken {
|
|
|
928
975
|
account?: Address | undefined
|
|
929
976
|
kv?: Kv.Kv | undefined
|
|
930
977
|
tokens?: readonly Address[] | undefined
|
|
978
|
+
excluded?: Set<string> | undefined
|
|
979
|
+
balances?: Map<string, Promise<bigint>> | undefined
|
|
980
|
+
prepareFeeToken?: ((token: Address) => void) | undefined
|
|
931
981
|
/** TTL in seconds for the cached `userTokens` lookup. @default 60 */
|
|
932
982
|
userTokenCacheTtl?: number | undefined
|
|
933
983
|
}
|