accounts 0.7.2 → 0.8.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 +23 -0
- package/README.md +19 -20
- package/dist/cli/Provider.d.ts +1 -1
- package/dist/cli/Provider.d.ts.map +1 -1
- package/dist/cli/Provider.js +4 -1
- package/dist/cli/Provider.js.map +1 -1
- package/dist/cli/keyring.js +1 -1
- package/dist/cli/keyring.js.map +1 -1
- package/dist/core/Account.d.ts +2 -0
- package/dist/core/Account.d.ts.map +1 -1
- package/dist/core/Account.js.map +1 -1
- package/dist/core/Adapter.d.ts +9 -1
- package/dist/core/Adapter.d.ts.map +1 -1
- package/dist/core/Dialog.d.ts +16 -1
- package/dist/core/Dialog.d.ts.map +1 -1
- package/dist/core/Dialog.js +40 -3
- package/dist/core/Dialog.js.map +1 -1
- package/dist/core/Messenger.d.ts +15 -0
- package/dist/core/Messenger.d.ts.map +1 -1
- package/dist/core/Messenger.js.map +1 -1
- package/dist/core/Provider.d.ts +2 -0
- package/dist/core/Provider.d.ts.map +1 -1
- package/dist/core/Provider.js +24 -6
- package/dist/core/Provider.js.map +1 -1
- package/dist/core/Remote.d.ts +7 -1
- package/dist/core/Remote.d.ts.map +1 -1
- package/dist/core/Remote.js +18 -2
- package/dist/core/Remote.js.map +1 -1
- package/dist/core/Schema.d.ts +17 -3
- package/dist/core/Schema.d.ts.map +1 -1
- package/dist/core/Store.d.ts +2 -0
- package/dist/core/Store.d.ts.map +1 -1
- package/dist/core/Store.js +12 -7
- package/dist/core/Store.js.map +1 -1
- package/dist/core/TrustedHosts.d.ts.map +1 -1
- package/dist/core/TrustedHosts.js +2 -0
- package/dist/core/TrustedHosts.js.map +1 -1
- package/dist/core/WebAuthnCeremony.d.ts +8 -0
- package/dist/core/WebAuthnCeremony.d.ts.map +1 -1
- package/dist/core/WebAuthnCeremony.js.map +1 -1
- package/dist/core/adapters/dialog.d.ts +3 -1
- package/dist/core/adapters/dialog.d.ts.map +1 -1
- package/dist/core/adapters/dialog.js +8 -5
- package/dist/core/adapters/dialog.js.map +1 -1
- package/dist/core/adapters/local.js +4 -4
- package/dist/core/adapters/local.js.map +1 -1
- package/dist/core/adapters/webAuthn.d.ts.map +1 -1
- package/dist/core/adapters/webAuthn.js +7 -2
- package/dist/core/adapters/webAuthn.js.map +1 -1
- package/dist/core/zod/rpc.d.ts +17 -7
- package/dist/core/zod/rpc.d.ts.map +1 -1
- package/dist/core/zod/rpc.js +5 -1
- package/dist/core/zod/rpc.js.map +1 -1
- package/dist/react/Remote.d.ts +2 -0
- package/dist/react/Remote.d.ts.map +1 -1
- package/dist/react/Remote.js +69 -0
- package/dist/react/Remote.js.map +1 -1
- package/dist/react-native/Provider.d.ts.map +1 -1
- package/dist/react-native/Provider.js +4 -1
- package/dist/react-native/Provider.js.map +1 -1
- package/dist/react-native/adapter.d.ts +1 -1
- package/dist/react-native/adapter.d.ts.map +1 -1
- package/dist/react-native/adapter.js +2 -0
- package/dist/react-native/adapter.js.map +1 -1
- package/dist/server/CliAuth.d.ts +82 -11
- package/dist/server/CliAuth.d.ts.map +1 -1
- package/dist/server/CliAuth.js +82 -11
- package/dist/server/CliAuth.js.map +1 -1
- package/dist/server/internal/handlers/codeAuth.d.ts +2 -2
- package/dist/server/internal/handlers/codeAuth.js +2 -2
- package/dist/server/internal/handlers/relay.d.ts.map +1 -1
- package/dist/server/internal/handlers/relay.js +182 -88
- package/dist/server/internal/handlers/relay.js.map +1 -1
- package/dist/server/internal/handlers/utils.d.ts +2 -2
- package/dist/server/internal/handlers/utils.d.ts.map +1 -1
- package/dist/server/internal/handlers/utils.js +7 -2
- package/dist/server/internal/handlers/utils.js.map +1 -1
- package/dist/server/internal/handlers/webAuthn.d.ts +2 -0
- package/dist/server/internal/handlers/webAuthn.d.ts.map +1 -1
- package/dist/server/internal/handlers/webAuthn.js +20 -9
- package/dist/server/internal/handlers/webAuthn.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/Provider.test.ts +3 -1
- package/src/cli/Provider.ts +4 -2
- package/src/cli/keyring.ts +1 -1
- package/src/core/Account.ts +2 -0
- package/src/core/Adapter.ts +9 -1
- package/src/core/Dialog.browser.test.ts +3 -3
- package/src/core/Dialog.ts +51 -4
- package/src/core/Messenger.ts +12 -0
- package/src/core/Provider.ts +46 -18
- package/src/core/Remote.ts +26 -4
- package/src/core/Store.ts +12 -4
- package/src/core/TrustedHosts.ts +1 -0
- package/src/core/WebAuthnCeremony.ts +8 -0
- package/src/core/adapters/dialog.ts +10 -5
- package/src/core/adapters/local.ts +4 -4
- package/src/core/adapters/webAuthn.ts +7 -2
- package/src/core/zod/rpc.ts +5 -1
- package/src/react/Remote.ts +76 -0
- package/src/react-native/Provider.ts +9 -1
- package/src/react-native/adapter.ts +3 -1
- package/src/server/CliAuth.ts +82 -11
- package/src/server/internal/handlers/codeAuth.ts +2 -2
- package/src/server/internal/handlers/relay.test.ts +351 -1
- package/src/server/internal/handlers/relay.ts +207 -93
- package/src/server/internal/handlers/utils.ts +8 -2
- package/src/server/internal/handlers/webAuthn.ts +24 -12
|
@@ -2,7 +2,7 @@ import type { RpcRequest } from 'ox'
|
|
|
2
2
|
import { SignatureEnvelope, TxEnvelopeTempo } from 'ox/tempo'
|
|
3
3
|
import { parseUnits } from 'viem'
|
|
4
4
|
import { fillTransaction, sendTransactionSync } from 'viem/actions'
|
|
5
|
-
import { tempoModerato } from 'viem/chains'
|
|
5
|
+
import { tempo, tempoModerato } from 'viem/chains'
|
|
6
6
|
import { Actions, Addresses, Capabilities, Tick, Transaction } from 'viem/tempo'
|
|
7
7
|
import { afterAll, afterEach, beforeAll, describe, expect, test } from 'vp/test'
|
|
8
8
|
|
|
@@ -195,6 +195,92 @@ describe('behavior: with feePayer', () => {
|
|
|
195
195
|
})
|
|
196
196
|
})
|
|
197
197
|
|
|
198
|
+
describe('behavior: with app-provided feePayer URL', () => {
|
|
199
|
+
let appServer: Server
|
|
200
|
+
let walletServer: Server
|
|
201
|
+
let client: ReturnType<typeof getClient<typeof chain>>
|
|
202
|
+
|
|
203
|
+
beforeAll(async () => {
|
|
204
|
+
// App relay: has a fee payer account and signs transactions.
|
|
205
|
+
appServer = await createServer(
|
|
206
|
+
relay({
|
|
207
|
+
chains: [chain],
|
|
208
|
+
transports: { [chain.id]: http() },
|
|
209
|
+
feePayer: {
|
|
210
|
+
account: feePayerAccount,
|
|
211
|
+
name: 'App Sponsor',
|
|
212
|
+
url: 'https://app.example.com',
|
|
213
|
+
},
|
|
214
|
+
}).listener,
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
// Wallet relay: no fee payer configured — proxies to app relay.
|
|
218
|
+
walletServer = await createServer(
|
|
219
|
+
relay({
|
|
220
|
+
chains: [chain],
|
|
221
|
+
transports: { [chain.id]: http() },
|
|
222
|
+
}).listener,
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
client = getClient({ transport: http(walletServer.url) })
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
afterAll(() => {
|
|
229
|
+
appServer.close()
|
|
230
|
+
walletServer.close()
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
test('default: proxies fill to app relay and returns sponsored tx', async () => {
|
|
234
|
+
const { transaction } = await fillTransaction(client, {
|
|
235
|
+
account: userAccount.address,
|
|
236
|
+
calls: [transferCall()],
|
|
237
|
+
feePayer: appServer.url as never,
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
expect(transaction.feePayerSignature).toBeDefined()
|
|
241
|
+
expect(transaction.gas).toBeDefined()
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
test('behavior: relays sponsor metadata from app relay', async () => {
|
|
245
|
+
const result = await fillTransaction(client, {
|
|
246
|
+
account: userAccount.address,
|
|
247
|
+
calls: [transferCall()],
|
|
248
|
+
feePayer: appServer.url as never,
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
expect(result.capabilities?.sponsored).toBe(true)
|
|
252
|
+
expect(result.capabilities?.sponsor).toMatchInlineSnapshot(`
|
|
253
|
+
{
|
|
254
|
+
"address": "${feePayerAccount.address}",
|
|
255
|
+
"name": "App Sponsor",
|
|
256
|
+
"url": "https://app.example.com",
|
|
257
|
+
}
|
|
258
|
+
`)
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
test('behavior: sponsored tx from app relay can be signed and broadcast', async () => {
|
|
262
|
+
const { transaction } = await fillTransaction(client, {
|
|
263
|
+
account: userAccount.address,
|
|
264
|
+
calls: [transferCall()],
|
|
265
|
+
feePayer: appServer.url as never,
|
|
266
|
+
})
|
|
267
|
+
const serialized = (await Transaction.serialize(transaction as never)) as `0x76${string}`
|
|
268
|
+
const envelope = TxEnvelopeTempo.deserialize(serialized)
|
|
269
|
+
const signature = await userAccount.sign({
|
|
270
|
+
hash: TxEnvelopeTempo.getSignPayload(envelope),
|
|
271
|
+
})
|
|
272
|
+
const signed = TxEnvelopeTempo.serialize(envelope, {
|
|
273
|
+
signature: SignatureEnvelope.from(signature),
|
|
274
|
+
})
|
|
275
|
+
const receipt = (await getClient().request({
|
|
276
|
+
method: 'eth_sendRawTransactionSync' as never,
|
|
277
|
+
params: [signed],
|
|
278
|
+
})) as { feePayer?: string | undefined }
|
|
279
|
+
|
|
280
|
+
expect(receipt.feePayer).toBe(feePayerAccount.address.toLowerCase())
|
|
281
|
+
})
|
|
282
|
+
})
|
|
283
|
+
|
|
198
284
|
describe('behavior: chainId path parameter', () => {
|
|
199
285
|
let server: Server
|
|
200
286
|
let client: ReturnType<typeof getClient<typeof chain>>
|
|
@@ -850,6 +936,218 @@ describe('behavior: conditional sponsoring', () => {
|
|
|
850
936
|
})
|
|
851
937
|
})
|
|
852
938
|
|
|
939
|
+
describe('behavior: path A — guaranteed sponsorship (no validate)', () => {
|
|
940
|
+
let server: Server
|
|
941
|
+
let client: ReturnType<typeof getClient<typeof chain>>
|
|
942
|
+
let requests: RpcRequest.RpcRequest[] = []
|
|
943
|
+
|
|
944
|
+
beforeAll(async () => {
|
|
945
|
+
server = await createServer(
|
|
946
|
+
relay({
|
|
947
|
+
chains: [chain],
|
|
948
|
+
features: 'all',
|
|
949
|
+
transports: { [chain.id]: http() },
|
|
950
|
+
feePayer: {
|
|
951
|
+
account: feePayerAccount,
|
|
952
|
+
name: 'Path A Sponsor',
|
|
953
|
+
},
|
|
954
|
+
onRequest: async (request) => {
|
|
955
|
+
requests.push(request)
|
|
956
|
+
},
|
|
957
|
+
}).listener,
|
|
958
|
+
)
|
|
959
|
+
client = getClient({ transport: http(server.url) })
|
|
960
|
+
})
|
|
961
|
+
|
|
962
|
+
afterAll(() => {
|
|
963
|
+
server.close()
|
|
964
|
+
})
|
|
965
|
+
|
|
966
|
+
afterEach(() => {
|
|
967
|
+
requests = []
|
|
968
|
+
})
|
|
969
|
+
|
|
970
|
+
test('behavior: skips fee token resolution and sponsors', async () => {
|
|
971
|
+
const result = await fillTransaction(client, {
|
|
972
|
+
account: userAccount.address,
|
|
973
|
+
calls: [transferCall()],
|
|
974
|
+
})
|
|
975
|
+
|
|
976
|
+
expect(result.transaction.feePayerSignature).toBeDefined()
|
|
977
|
+
expect(result.capabilities?.sponsored).toBe(true)
|
|
978
|
+
expect(result.capabilities?.sponsor?.name).toBe('Path A Sponsor')
|
|
979
|
+
// Only one fill request — no fee token resolution round-trip.
|
|
980
|
+
expect(requests.filter((r) => r.method === 'eth_fillTransaction')).toHaveLength(1)
|
|
981
|
+
})
|
|
982
|
+
|
|
983
|
+
test('behavior: returns fee even when no feeToken in request', async () => {
|
|
984
|
+
const result = await fillTransaction(client, {
|
|
985
|
+
account: userAccount.address,
|
|
986
|
+
calls: [transferCall()],
|
|
987
|
+
})
|
|
988
|
+
|
|
989
|
+
expect(result.capabilities?.fee).toBeDefined()
|
|
990
|
+
expect(result.capabilities?.fee?.decimals).toBeTypeOf('number')
|
|
991
|
+
expect(result.capabilities?.fee?.symbol).toBeTypeOf('string')
|
|
992
|
+
expect(result.capabilities?.fee?.formatted).toBeTypeOf('string')
|
|
993
|
+
})
|
|
994
|
+
|
|
995
|
+
test('behavior: simulate and sign run concurrently with fill', async () => {
|
|
996
|
+
const result = await fillTransaction(client, {
|
|
997
|
+
account: userAccount.address,
|
|
998
|
+
calls: [transferCall()],
|
|
999
|
+
})
|
|
1000
|
+
|
|
1001
|
+
// All capabilities are present despite parallel execution.
|
|
1002
|
+
expect(result.transaction.feePayerSignature).toBeDefined()
|
|
1003
|
+
expect(result.capabilities?.sponsored).toBe(true)
|
|
1004
|
+
})
|
|
1005
|
+
})
|
|
1006
|
+
|
|
1007
|
+
describe('behavior: path B — conditional sponsorship (validate)', () => {
|
|
1008
|
+
let server: Server
|
|
1009
|
+
let client: ReturnType<typeof getClient<typeof chain>>
|
|
1010
|
+
let requests: RpcRequest.RpcRequest[] = []
|
|
1011
|
+
|
|
1012
|
+
// Reject accounts[3], approve everyone else.
|
|
1013
|
+
const rejectedSender = accounts[3]!
|
|
1014
|
+
|
|
1015
|
+
beforeAll(async () => {
|
|
1016
|
+
const rpc = getClient()
|
|
1017
|
+
await Actions.token.mintSync(rpc, {
|
|
1018
|
+
account: accounts[0]!,
|
|
1019
|
+
token: addresses.alphaUsd,
|
|
1020
|
+
amount: parseUnits('100', 6),
|
|
1021
|
+
to: rejectedSender.address,
|
|
1022
|
+
})
|
|
1023
|
+
await Actions.fee.setUserToken(rpc, { account: rejectedSender, token: addresses.alphaUsd })
|
|
1024
|
+
|
|
1025
|
+
server = await createServer(
|
|
1026
|
+
relay({
|
|
1027
|
+
chains: [chain],
|
|
1028
|
+
features: 'all',
|
|
1029
|
+
transports: { [chain.id]: http() },
|
|
1030
|
+
feePayer: {
|
|
1031
|
+
account: feePayerAccount,
|
|
1032
|
+
name: 'Path B Sponsor',
|
|
1033
|
+
validate: (request) =>
|
|
1034
|
+
request.from?.toLowerCase() !== rejectedSender.address.toLowerCase(),
|
|
1035
|
+
},
|
|
1036
|
+
onRequest: async (request) => {
|
|
1037
|
+
requests.push(request)
|
|
1038
|
+
},
|
|
1039
|
+
}).listener,
|
|
1040
|
+
)
|
|
1041
|
+
client = getClient({ transport: http(server.url) })
|
|
1042
|
+
})
|
|
1043
|
+
|
|
1044
|
+
afterAll(() => {
|
|
1045
|
+
server.close()
|
|
1046
|
+
})
|
|
1047
|
+
|
|
1048
|
+
afterEach(() => {
|
|
1049
|
+
requests = []
|
|
1050
|
+
})
|
|
1051
|
+
|
|
1052
|
+
test('behavior: approved sender gets sponsorship with parallel fills', async () => {
|
|
1053
|
+
const result = await fillTransaction(client, {
|
|
1054
|
+
account: userAccount.address,
|
|
1055
|
+
calls: [transferCall()],
|
|
1056
|
+
})
|
|
1057
|
+
|
|
1058
|
+
expect(result.transaction.feePayerSignature).toBeDefined()
|
|
1059
|
+
expect(result.capabilities?.sponsored).toBe(true)
|
|
1060
|
+
expect(result.capabilities?.sponsor?.name).toBe('Path B Sponsor')
|
|
1061
|
+
})
|
|
1062
|
+
|
|
1063
|
+
test('behavior: rejected sender gets unsponsored tx from parallel fill', async () => {
|
|
1064
|
+
const result = await fillTransaction(client, {
|
|
1065
|
+
account: rejectedSender.address,
|
|
1066
|
+
calls: [transferCall()],
|
|
1067
|
+
})
|
|
1068
|
+
|
|
1069
|
+
expect(result.transaction.feePayerSignature).toBeUndefined()
|
|
1070
|
+
expect(result.capabilities?.sponsored).toBe(false)
|
|
1071
|
+
expect(result.capabilities?.sponsor).toBeUndefined()
|
|
1072
|
+
})
|
|
1073
|
+
|
|
1074
|
+
test('behavior: rejected tx can be signed and broadcast by sender', async () => {
|
|
1075
|
+
const result = await fillTransaction(client, {
|
|
1076
|
+
account: rejectedSender.address,
|
|
1077
|
+
calls: [transferCall()],
|
|
1078
|
+
})
|
|
1079
|
+
|
|
1080
|
+
const serialized = (await Transaction.serialize(result.transaction as never)) as `0x76${string}`
|
|
1081
|
+
const envelope = TxEnvelopeTempo.deserialize(serialized)
|
|
1082
|
+
const signature = await rejectedSender.sign({
|
|
1083
|
+
hash: TxEnvelopeTempo.getSignPayload(envelope),
|
|
1084
|
+
})
|
|
1085
|
+
const signed = TxEnvelopeTempo.serialize(envelope, {
|
|
1086
|
+
signature: SignatureEnvelope.from(signature),
|
|
1087
|
+
})
|
|
1088
|
+
const receipt = (await getClient().request({
|
|
1089
|
+
method: 'eth_sendRawTransactionSync' as never,
|
|
1090
|
+
params: [signed],
|
|
1091
|
+
})) as { feePayer?: string | undefined }
|
|
1092
|
+
|
|
1093
|
+
expect(receipt.feePayer).not.toBe(feePayerAccount.address.toLowerCase())
|
|
1094
|
+
})
|
|
1095
|
+
})
|
|
1096
|
+
|
|
1097
|
+
describe('behavior: path C — no sponsorship', () => {
|
|
1098
|
+
let server: Server
|
|
1099
|
+
let client: ReturnType<typeof getClient<typeof chain>>
|
|
1100
|
+
let requests: RpcRequest.RpcRequest[] = []
|
|
1101
|
+
|
|
1102
|
+
beforeAll(async () => {
|
|
1103
|
+
server = await createServer(
|
|
1104
|
+
relay({
|
|
1105
|
+
chains: [chain],
|
|
1106
|
+
features: 'all',
|
|
1107
|
+
transports: { [chain.id]: http() },
|
|
1108
|
+
onRequest: async (request) => {
|
|
1109
|
+
requests.push(request)
|
|
1110
|
+
},
|
|
1111
|
+
}).listener,
|
|
1112
|
+
)
|
|
1113
|
+
client = getClient({ transport: http(server.url) })
|
|
1114
|
+
})
|
|
1115
|
+
|
|
1116
|
+
afterAll(() => {
|
|
1117
|
+
server.close()
|
|
1118
|
+
})
|
|
1119
|
+
|
|
1120
|
+
afterEach(() => {
|
|
1121
|
+
requests = []
|
|
1122
|
+
})
|
|
1123
|
+
|
|
1124
|
+
test('behavior: resolves fee token and fills without sponsorship', async () => {
|
|
1125
|
+
const result = await fillTransaction(client, {
|
|
1126
|
+
account: userAccount.address,
|
|
1127
|
+
calls: [transferCall()],
|
|
1128
|
+
})
|
|
1129
|
+
|
|
1130
|
+
expect(result.transaction.feePayerSignature).toBeUndefined()
|
|
1131
|
+
expect(result.capabilities?.sponsored).toBe(false)
|
|
1132
|
+
expect(result.capabilities?.fee).toBeDefined()
|
|
1133
|
+
// Single fill — no speculative second fill.
|
|
1134
|
+
expect(requests.filter((r) => r.method === 'eth_fillTransaction')).toHaveLength(1)
|
|
1135
|
+
})
|
|
1136
|
+
|
|
1137
|
+
test('behavior: simulate and autoSwap metadata resolve concurrently', async () => {
|
|
1138
|
+
const result = await fillTransaction(client, {
|
|
1139
|
+
account: userAccount.address,
|
|
1140
|
+
calls: [transferCall()],
|
|
1141
|
+
})
|
|
1142
|
+
|
|
1143
|
+
// Capabilities are populated despite parallel execution.
|
|
1144
|
+
expect(result.transaction.gas).toBeDefined()
|
|
1145
|
+
expect(result.transaction.nonce).toBeDefined()
|
|
1146
|
+
expect(result.capabilities?.fee).toBeDefined()
|
|
1147
|
+
expect(result.capabilities?.sponsored).toBe(false)
|
|
1148
|
+
})
|
|
1149
|
+
})
|
|
1150
|
+
|
|
853
1151
|
describe('behavior: fee token resolution', () => {
|
|
854
1152
|
const feeTokenAccount = accounts[0]!
|
|
855
1153
|
const preferredToken = addresses.alphaUsd
|
|
@@ -1064,3 +1362,55 @@ describe('behavior: error capabilities', () => {
|
|
|
1064
1362
|
`)
|
|
1065
1363
|
})
|
|
1066
1364
|
})
|
|
1365
|
+
|
|
1366
|
+
describe('behavior: mainnet autoSwap with USDC.e → PathUSD', () => {
|
|
1367
|
+
let server: Server
|
|
1368
|
+
let mainnetClient: ReturnType<typeof getClient<typeof tempo>>
|
|
1369
|
+
|
|
1370
|
+
const mainnetUsdce = '0x20c000000000000000000000b9537d11c60e8b50' as const
|
|
1371
|
+
const mainnetPathUsd = '0x20c0000000000000000000000000000000000000' as const
|
|
1372
|
+
const mainnetSender = '0xb472f3ca15f34Db22d43FA503043F1e6541AC085' as const
|
|
1373
|
+
|
|
1374
|
+
beforeAll(async () => {
|
|
1375
|
+
server = await createServer(
|
|
1376
|
+
relay({
|
|
1377
|
+
chains: [tempo],
|
|
1378
|
+
features: 'all',
|
|
1379
|
+
transports: { [tempo.id]: http('https://rpc.presto.tempo.xyz') },
|
|
1380
|
+
}).listener,
|
|
1381
|
+
)
|
|
1382
|
+
mainnetClient = getClient({ chain: tempo, transport: http(server.url) })
|
|
1383
|
+
})
|
|
1384
|
+
|
|
1385
|
+
afterAll(() => {
|
|
1386
|
+
server.close()
|
|
1387
|
+
})
|
|
1388
|
+
|
|
1389
|
+
test('behavior: auto-swaps USDC.e → PathUSD when sender has USDC.e but no PathUSD', async () => {
|
|
1390
|
+
const result = await fillTransaction(mainnetClient, {
|
|
1391
|
+
account: mainnetSender,
|
|
1392
|
+
calls: [
|
|
1393
|
+
Actions.token.transfer.call({
|
|
1394
|
+
token: mainnetPathUsd,
|
|
1395
|
+
to: recipient.address,
|
|
1396
|
+
amount: parseUnits('0.5', 6),
|
|
1397
|
+
}),
|
|
1398
|
+
],
|
|
1399
|
+
})
|
|
1400
|
+
|
|
1401
|
+
// Expected: relay detects InsufficientBalance for PathUSD, auto-swaps
|
|
1402
|
+
// USDC.e → PathUSD via DEX, and returns a filled transaction with swap calls.
|
|
1403
|
+
const { transaction, capabilities } = result
|
|
1404
|
+
expect(transaction.gas).toBeDefined()
|
|
1405
|
+
expect(transaction.nonce).toBeDefined()
|
|
1406
|
+
expect(transaction.calls!.length).toBeGreaterThanOrEqual(3) // approve + swap + transfer
|
|
1407
|
+
|
|
1408
|
+
expect(capabilities?.autoSwap).toBeDefined()
|
|
1409
|
+
expect(capabilities?.autoSwap?.maxIn.token.toLowerCase()).toBe(mainnetUsdce.toLowerCase())
|
|
1410
|
+
expect(capabilities?.autoSwap?.minOut.token.toLowerCase()).toBe(mainnetPathUsd.toLowerCase())
|
|
1411
|
+
expect(capabilities?.sponsored).toBe(false)
|
|
1412
|
+
|
|
1413
|
+
// Should NOT have requireFunds — autoSwap should handle it.
|
|
1414
|
+
expect((capabilities as Record<string, unknown>)?.requireFunds).toBeUndefined()
|
|
1415
|
+
})
|
|
1416
|
+
})
|