@andrewkimjoseph/celina-sdk 0.1.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/LICENSE +21 -0
- package/README.md +61 -0
- package/build/abis/aave-pool.d.ts +37 -0
- package/build/abis/aave-pool.js +26 -0
- package/build/abis/aave-pool.js.map +1 -0
- package/build/abis/gooddollar-identity.d.ts +61 -0
- package/build/abis/gooddollar-identity.js +38 -0
- package/build/abis/gooddollar-identity.js.map +1 -0
- package/build/clients/celo-client.d.ts +11 -0
- package/build/clients/celo-client.js +23 -0
- package/build/clients/celo-client.js.map +1 -0
- package/build/clients/ens-client.d.ts +10 -0
- package/build/clients/ens-client.js +23 -0
- package/build/clients/ens-client.js.map +1 -0
- package/build/clients/mento-sdk.d.ts +2 -0
- package/build/clients/mento-sdk.js +9 -0
- package/build/clients/mento-sdk.js.map +1 -0
- package/build/config/aave.d.ts +11 -0
- package/build/config/aave.js +55 -0
- package/build/config/aave.js.map +1 -0
- package/build/config/celina-tag.d.ts +1 -0
- package/build/config/celina-tag.js +3 -0
- package/build/config/celina-tag.js.map +1 -0
- package/build/config/chains.d.ts +935 -0
- package/build/config/chains.js +251 -0
- package/build/config/chains.js.map +1 -0
- package/build/config/gooddollar.d.ts +1 -0
- package/build/config/gooddollar.js +2 -0
- package/build/config/gooddollar.js.map +1 -0
- package/build/config/sdk-config.d.ts +6 -0
- package/build/config/sdk-config.js +8 -0
- package/build/config/sdk-config.js.map +1 -0
- package/build/index.d.ts +26 -0
- package/build/index.js +28 -0
- package/build/index.js.map +1 -0
- package/build/services/aave.service.d.ts +13 -0
- package/build/services/aave.service.js +144 -0
- package/build/services/aave.service.js.map +1 -0
- package/build/services/account.service.d.ts +13 -0
- package/build/services/account.service.js +23 -0
- package/build/services/account.service.js.map +1 -0
- package/build/services/blockchain.service.d.ts +40 -0
- package/build/services/blockchain.service.js +81 -0
- package/build/services/blockchain.service.js.map +1 -0
- package/build/services/ens.service.d.ts +30 -0
- package/build/services/ens.service.js +78 -0
- package/build/services/ens.service.js.map +1 -0
- package/build/services/gooddollar.service.d.ts +38 -0
- package/build/services/gooddollar.service.js +127 -0
- package/build/services/gooddollar.service.js.map +1 -0
- package/build/services/mento-fx.service.d.ts +48 -0
- package/build/services/mento-fx.service.js +234 -0
- package/build/services/mento-fx.service.js.map +1 -0
- package/build/services/token.service.d.ts +54 -0
- package/build/services/token.service.js +147 -0
- package/build/services/token.service.js.map +1 -0
- package/build/services/transaction.service.d.ts +16 -0
- package/build/services/transaction.service.js +93 -0
- package/build/services/transaction.service.js.map +1 -0
- package/build/types/prepared.d.ts +21 -0
- package/build/types/prepared.js +11 -0
- package/build/types/prepared.js.map +1 -0
- package/build/utils/erc20-allowance-storage.d.ts +7 -0
- package/build/utils/erc20-allowance-storage.js +44 -0
- package/build/utils/erc20-allowance-storage.js.map +1 -0
- package/build/utils/format-date.d.ts +1 -0
- package/build/utils/format-date.js +12 -0
- package/build/utils/format-date.js.map +1 -0
- package/package.json +63 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
export class BlockchainService {
|
|
2
|
+
clientFactory;
|
|
3
|
+
constructor(clientFactory) {
|
|
4
|
+
this.clientFactory = clientFactory;
|
|
5
|
+
}
|
|
6
|
+
async getNetworkStatus() {
|
|
7
|
+
const { public: client } = this.clientFactory.getClients();
|
|
8
|
+
const [chainId, blockNumber, gasPrice] = await Promise.all([
|
|
9
|
+
client.getChainId(),
|
|
10
|
+
client.getBlockNumber(),
|
|
11
|
+
client.getGasPrice(),
|
|
12
|
+
]);
|
|
13
|
+
return {
|
|
14
|
+
network: "mainnet",
|
|
15
|
+
chainId,
|
|
16
|
+
blockNumber: blockNumber.toString(),
|
|
17
|
+
gasPriceWei: gasPrice.toString(),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
async getBlock(blockId) {
|
|
21
|
+
const { public: client } = this.clientFactory.getClients();
|
|
22
|
+
const blockParams = typeof blockId === "number"
|
|
23
|
+
? { blockNumber: BigInt(blockId), includeTransactions: false }
|
|
24
|
+
: blockId === "latest" || blockId === "pending"
|
|
25
|
+
? {
|
|
26
|
+
blockTag: blockId,
|
|
27
|
+
includeTransactions: false,
|
|
28
|
+
}
|
|
29
|
+
: { blockHash: blockId, includeTransactions: false };
|
|
30
|
+
const block = await client.getBlock(blockParams);
|
|
31
|
+
if (!block) {
|
|
32
|
+
throw new Error(`Block not found: ${blockId}`);
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
number: block.number?.toString(),
|
|
36
|
+
hash: block.hash,
|
|
37
|
+
timestamp: block.timestamp.toString(),
|
|
38
|
+
parentHash: block.parentHash,
|
|
39
|
+
gasUsed: block.gasUsed.toString(),
|
|
40
|
+
gasLimit: block.gasLimit.toString(),
|
|
41
|
+
miner: block.miner,
|
|
42
|
+
transactionCount: block.transactions.length,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
async getLatestBlocks(count = 5) {
|
|
46
|
+
const { public: client } = this.clientFactory.getClients();
|
|
47
|
+
const latest = await client.getBlockNumber();
|
|
48
|
+
const start = latest - BigInt(Math.max(count - 1, 0));
|
|
49
|
+
const blocks = await Promise.all(Array.from({ length: count }, (_, index) => client.getBlock({ blockNumber: start + BigInt(index) })));
|
|
50
|
+
return blocks.filter(Boolean).map((block) => ({
|
|
51
|
+
number: block.number?.toString(),
|
|
52
|
+
hash: block.hash,
|
|
53
|
+
timestamp: block.timestamp.toString(),
|
|
54
|
+
transactionCount: block.transactions.length,
|
|
55
|
+
}));
|
|
56
|
+
}
|
|
57
|
+
async getTransaction(hash) {
|
|
58
|
+
const { public: client } = this.clientFactory.getClients();
|
|
59
|
+
const [tx, receipt] = await Promise.all([
|
|
60
|
+
client.getTransaction({ hash }),
|
|
61
|
+
client.getTransactionReceipt({ hash }),
|
|
62
|
+
]);
|
|
63
|
+
if (!tx) {
|
|
64
|
+
throw new Error(`Transaction not found: ${hash}`);
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
hash: tx.hash,
|
|
68
|
+
from: tx.from,
|
|
69
|
+
to: tx.to,
|
|
70
|
+
value: tx.value.toString(),
|
|
71
|
+
nonce: tx.nonce,
|
|
72
|
+
gas: tx.gas.toString(),
|
|
73
|
+
gasPrice: tx.gasPrice?.toString(),
|
|
74
|
+
input: tx.input,
|
|
75
|
+
blockNumber: tx.blockNumber?.toString(),
|
|
76
|
+
status: receipt?.status,
|
|
77
|
+
gasUsed: receipt?.gasUsed.toString(),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=blockchain.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blockchain.service.js","sourceRoot":"","sources":["../../src/services/blockchain.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type EnsClientFactory } from "../clients/ens-client.js";
|
|
2
|
+
export type EnsResolveChain = "celo" | "ethereum";
|
|
3
|
+
export type ResolvedRecipient = {
|
|
4
|
+
address: `0x${string}`;
|
|
5
|
+
ens?: {
|
|
6
|
+
name: string;
|
|
7
|
+
normalizedName: string;
|
|
8
|
+
resolvedVia?: "celo" | "ethereum";
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
export declare class EnsService {
|
|
12
|
+
private readonly ensClientFactory;
|
|
13
|
+
constructor(ensClientFactory: EnsClientFactory);
|
|
14
|
+
resolveEns(name: string, chain?: EnsResolveChain): Promise<{
|
|
15
|
+
name: string;
|
|
16
|
+
normalizedName: string;
|
|
17
|
+
address: `0x${string}`;
|
|
18
|
+
coinType: string;
|
|
19
|
+
chain: "ethereum";
|
|
20
|
+
resolvedVia?: undefined;
|
|
21
|
+
} | {
|
|
22
|
+
name: string;
|
|
23
|
+
normalizedName: string;
|
|
24
|
+
address: `0x${string}`;
|
|
25
|
+
coinType: string;
|
|
26
|
+
chain: "celo";
|
|
27
|
+
resolvedVia: "celo" | "ethereum";
|
|
28
|
+
}>;
|
|
29
|
+
resolveAddressOrEns(input: string): Promise<ResolvedRecipient>;
|
|
30
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { toCoinType } from "viem";
|
|
2
|
+
import { celo } from "viem/chains";
|
|
3
|
+
import { getEnsAddress, normalize } from "viem/ens";
|
|
4
|
+
import { ENS_CCIP_GATEWAY, } from "../clients/ens-client.js";
|
|
5
|
+
const ADDRESS_PATTERN = /^0x[a-fA-F0-9]{40}$/;
|
|
6
|
+
export class EnsService {
|
|
7
|
+
ensClientFactory;
|
|
8
|
+
constructor(ensClientFactory) {
|
|
9
|
+
this.ensClientFactory = ensClientFactory;
|
|
10
|
+
}
|
|
11
|
+
async resolveEns(name, chain = "celo") {
|
|
12
|
+
const trimmedName = name.trim();
|
|
13
|
+
const normalizedName = normalize(trimmedName);
|
|
14
|
+
const client = this.ensClientFactory.getClient();
|
|
15
|
+
const gatewayUrls = [ENS_CCIP_GATEWAY];
|
|
16
|
+
if (chain === "ethereum") {
|
|
17
|
+
const address = await getEnsAddress(client, {
|
|
18
|
+
name: normalizedName,
|
|
19
|
+
gatewayUrls,
|
|
20
|
+
});
|
|
21
|
+
if (!address) {
|
|
22
|
+
throw new Error(`ENS name "${trimmedName}" has no Ethereum address record`);
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
name: trimmedName,
|
|
26
|
+
normalizedName,
|
|
27
|
+
address,
|
|
28
|
+
coinType: "60",
|
|
29
|
+
chain: "ethereum",
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
const celoCoinType = toCoinType(celo.id);
|
|
33
|
+
let address = await getEnsAddress(client, {
|
|
34
|
+
name: normalizedName,
|
|
35
|
+
coinType: celoCoinType,
|
|
36
|
+
gatewayUrls,
|
|
37
|
+
});
|
|
38
|
+
let coinType = celoCoinType.toString();
|
|
39
|
+
let resolvedVia = "celo";
|
|
40
|
+
if (!address) {
|
|
41
|
+
address = await getEnsAddress(client, {
|
|
42
|
+
name: normalizedName,
|
|
43
|
+
gatewayUrls,
|
|
44
|
+
});
|
|
45
|
+
coinType = "60";
|
|
46
|
+
resolvedVia = "ethereum";
|
|
47
|
+
}
|
|
48
|
+
if (!address) {
|
|
49
|
+
throw new Error(`ENS name "${trimmedName}" could not be resolved to an address`);
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
name: trimmedName,
|
|
53
|
+
normalizedName,
|
|
54
|
+
address,
|
|
55
|
+
coinType,
|
|
56
|
+
chain: "celo",
|
|
57
|
+
resolvedVia,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
async resolveAddressOrEns(input) {
|
|
61
|
+
const trimmed = input.trim();
|
|
62
|
+
if (ADDRESS_PATTERN.test(trimmed)) {
|
|
63
|
+
return { address: trimmed };
|
|
64
|
+
}
|
|
65
|
+
const resolved = await this.resolveEns(trimmed);
|
|
66
|
+
return {
|
|
67
|
+
address: resolved.address,
|
|
68
|
+
ens: {
|
|
69
|
+
name: resolved.name,
|
|
70
|
+
normalizedName: resolved.normalizedName,
|
|
71
|
+
...(resolved.chain === "celo"
|
|
72
|
+
? { resolvedVia: resolved.resolvedVia }
|
|
73
|
+
: {}),
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=ens.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ens.service.js","sourceRoot":"","sources":["../../src/services/ens.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { CeloClientFactory } from "../clients/celo-client.js";
|
|
2
|
+
export declare class GoodDollarService {
|
|
3
|
+
private readonly clientFactory;
|
|
4
|
+
constructor(clientFactory: CeloClientFactory);
|
|
5
|
+
getWhitelistingInfo(address: `0x${string}`): Promise<{
|
|
6
|
+
address: `0x${string}`;
|
|
7
|
+
contract: "0xC361A6E67822a0EDc17D899227dd9FC50BD62F42";
|
|
8
|
+
isCurrentlyWhitelisted: boolean;
|
|
9
|
+
status: number;
|
|
10
|
+
statusLabel: string;
|
|
11
|
+
whitelistedOn: string | null;
|
|
12
|
+
lastAuthenticatedOn: string | null;
|
|
13
|
+
fieldDescriptions: {
|
|
14
|
+
whitelistedOn: string;
|
|
15
|
+
lastAuthenticatedOn: string;
|
|
16
|
+
};
|
|
17
|
+
reverification: {
|
|
18
|
+
daysSinceLastAuthentication: number;
|
|
19
|
+
currentReverificationPeriodDays: number;
|
|
20
|
+
maxReverificationPeriodDays: number;
|
|
21
|
+
daysUntilReverificationRequired: number;
|
|
22
|
+
reverificationRequiredOn: string;
|
|
23
|
+
reverificationProgressPercent: number;
|
|
24
|
+
isReverificationOverdue: boolean;
|
|
25
|
+
} | null;
|
|
26
|
+
identity: {
|
|
27
|
+
dateAuthenticated: number;
|
|
28
|
+
dateAdded: number;
|
|
29
|
+
did: string;
|
|
30
|
+
whitelistedOnChainId: number;
|
|
31
|
+
status: number;
|
|
32
|
+
authCount: number;
|
|
33
|
+
};
|
|
34
|
+
}>;
|
|
35
|
+
private effectiveAuthCount;
|
|
36
|
+
private fetchReverifyDaysOptions;
|
|
37
|
+
private buildReverificationProgress;
|
|
38
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { goodDollarIdentityAbi } from "../abis/gooddollar-identity.js";
|
|
2
|
+
import { GOODDOLLAR_IDENTITY_ADDRESS } from "../config/gooddollar.js";
|
|
3
|
+
import { formatUnixDate } from "../utils/format-date.js";
|
|
4
|
+
const STATUS_LABELS = {
|
|
5
|
+
0: "none",
|
|
6
|
+
1: "whitelisted",
|
|
7
|
+
2: "dao_contract",
|
|
8
|
+
255: "blacklisted",
|
|
9
|
+
};
|
|
10
|
+
// Post-upgrade users authenticated before this timestamp use the last reverify step.
|
|
11
|
+
const LEGACY_AUTH_CUTOFF = 1772697574;
|
|
12
|
+
function statusLabel(status) {
|
|
13
|
+
return STATUS_LABELS[status] ?? "unknown";
|
|
14
|
+
}
|
|
15
|
+
export class GoodDollarService {
|
|
16
|
+
clientFactory;
|
|
17
|
+
constructor(clientFactory) {
|
|
18
|
+
this.clientFactory = clientFactory;
|
|
19
|
+
}
|
|
20
|
+
async getWhitelistingInfo(address) {
|
|
21
|
+
const { public: client } = this.clientFactory.getClients();
|
|
22
|
+
const contract = GOODDOLLAR_IDENTITY_ADDRESS;
|
|
23
|
+
const [identityResult, isCurrentlyWhitelisted, maxReverificationPeriodDays, reverifyDaysOptions,] = await Promise.all([
|
|
24
|
+
client.readContract({
|
|
25
|
+
address: contract,
|
|
26
|
+
abi: goodDollarIdentityAbi,
|
|
27
|
+
functionName: "identities",
|
|
28
|
+
args: [address],
|
|
29
|
+
}),
|
|
30
|
+
client.readContract({
|
|
31
|
+
address: contract,
|
|
32
|
+
abi: goodDollarIdentityAbi,
|
|
33
|
+
functionName: "isWhitelisted",
|
|
34
|
+
args: [address],
|
|
35
|
+
}),
|
|
36
|
+
client.readContract({
|
|
37
|
+
address: contract,
|
|
38
|
+
abi: goodDollarIdentityAbi,
|
|
39
|
+
functionName: "authenticationPeriod",
|
|
40
|
+
}),
|
|
41
|
+
this.fetchReverifyDaysOptions(client, contract),
|
|
42
|
+
]);
|
|
43
|
+
const [dateAuthenticated, dateAdded, did, whitelistedOnChainId, status, authCount,] = identityResult;
|
|
44
|
+
const statusNum = Number(status);
|
|
45
|
+
const dateAuthenticatedNum = Number(dateAuthenticated);
|
|
46
|
+
const dateAddedNum = Number(dateAdded);
|
|
47
|
+
const authCountNum = Number(authCount);
|
|
48
|
+
let currentReverificationPeriodDays = null;
|
|
49
|
+
if (statusNum === 1 && dateAuthenticatedNum > 0 && reverifyDaysOptions.length > 0) {
|
|
50
|
+
const effectiveAuthCount = this.effectiveAuthCount(dateAuthenticatedNum, authCountNum, reverifyDaysOptions.length);
|
|
51
|
+
currentReverificationPeriodDays =
|
|
52
|
+
reverifyDaysOptions[effectiveAuthCount] ?? null;
|
|
53
|
+
}
|
|
54
|
+
const isWhitelistedStatus = statusNum === 1 && dateAddedNum > 0;
|
|
55
|
+
const reverification = statusNum === 1 && dateAuthenticatedNum > 0 && currentReverificationPeriodDays !== null
|
|
56
|
+
? this.buildReverificationProgress(dateAuthenticatedNum, currentReverificationPeriodDays, Number(maxReverificationPeriodDays))
|
|
57
|
+
: null;
|
|
58
|
+
return {
|
|
59
|
+
address,
|
|
60
|
+
contract,
|
|
61
|
+
isCurrentlyWhitelisted,
|
|
62
|
+
status: statusNum,
|
|
63
|
+
statusLabel: statusLabel(statusNum),
|
|
64
|
+
whitelistedOn: isWhitelistedStatus ? formatUnixDate(dateAddedNum) : null,
|
|
65
|
+
lastAuthenticatedOn: dateAuthenticatedNum > 0 ? formatUnixDate(dateAuthenticatedNum) : null,
|
|
66
|
+
fieldDescriptions: {
|
|
67
|
+
whitelistedOn: "When the wallet was first added to the GoodDollar whitelist.",
|
|
68
|
+
lastAuthenticatedOn: "When the wallet last verified its identity. Periodic reverification is required to remain whitelisted.",
|
|
69
|
+
},
|
|
70
|
+
reverification,
|
|
71
|
+
identity: {
|
|
72
|
+
dateAuthenticated: dateAuthenticatedNum,
|
|
73
|
+
dateAdded: dateAddedNum,
|
|
74
|
+
did,
|
|
75
|
+
whitelistedOnChainId: Number(whitelistedOnChainId),
|
|
76
|
+
status: statusNum,
|
|
77
|
+
authCount: authCountNum,
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
effectiveAuthCount(dateAuthenticated, authCount, optionsLength) {
|
|
82
|
+
if (dateAuthenticated < LEGACY_AUTH_CUTOFF) {
|
|
83
|
+
return optionsLength - 1;
|
|
84
|
+
}
|
|
85
|
+
if (authCount >= optionsLength) {
|
|
86
|
+
return 0;
|
|
87
|
+
}
|
|
88
|
+
return authCount;
|
|
89
|
+
}
|
|
90
|
+
async fetchReverifyDaysOptions(client, contract) {
|
|
91
|
+
const options = [];
|
|
92
|
+
for (let index = 0; index < 8; index++) {
|
|
93
|
+
try {
|
|
94
|
+
const value = await client.readContract({
|
|
95
|
+
address: contract,
|
|
96
|
+
abi: goodDollarIdentityAbi,
|
|
97
|
+
functionName: "reverifyDaysOptions",
|
|
98
|
+
args: [BigInt(index)],
|
|
99
|
+
});
|
|
100
|
+
options.push(Number(value));
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return options;
|
|
107
|
+
}
|
|
108
|
+
buildReverificationProgress(dateAuthenticated, currentReverificationPeriodDays, maxReverificationPeriodDays) {
|
|
109
|
+
const nowSec = Math.floor(Date.now() / 1000);
|
|
110
|
+
const daysSinceLastAuthentication = Math.floor((nowSec - dateAuthenticated) / 86400);
|
|
111
|
+
const daysUntilReverificationRequired = currentReverificationPeriodDays - daysSinceLastAuthentication;
|
|
112
|
+
const reverificationRequiredTimestamp = dateAuthenticated + currentReverificationPeriodDays * 86400;
|
|
113
|
+
const isReverificationOverdue = daysUntilReverificationRequired < 0;
|
|
114
|
+
const reverificationProgressPercent = Math.min(100, Math.max(0, Math.round((daysSinceLastAuthentication / currentReverificationPeriodDays) *
|
|
115
|
+
100)));
|
|
116
|
+
return {
|
|
117
|
+
daysSinceLastAuthentication,
|
|
118
|
+
currentReverificationPeriodDays,
|
|
119
|
+
maxReverificationPeriodDays,
|
|
120
|
+
daysUntilReverificationRequired,
|
|
121
|
+
reverificationRequiredOn: formatUnixDate(reverificationRequiredTimestamp),
|
|
122
|
+
reverificationProgressPercent,
|
|
123
|
+
isReverificationOverdue,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=gooddollar.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gooddollar.service.js","sourceRoot":"","sources":["../../src/services/gooddollar.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AACvE,OAAO,EAAE,2BAA2B,EAAE,MAAM,yBAAyB,CAAC;AACtE,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAEzD,MAAM,aAAa,GAA2B;IAC5C,CAAC,EAAE,MAAM;IACT,CAAC,EAAE,aAAa;IAChB,CAAC,EAAE,cAAc;IACjB,GAAG,EAAE,aAAa;CACnB,CAAC;AAEF,qFAAqF;AACrF,MAAM,kBAAkB,GAAG,UAAU,CAAC;AAEtC,SAAS,WAAW,CAAC,MAAc;IACjC,OAAO,aAAa,CAAC,MAAM,CAAC,IAAI,SAAS,CAAC;AAC5C,CAAC;AAED,MAAM,OAAO,iBAAiB;IACC;IAA7B,YAA6B,aAAgC;QAAhC,kBAAa,GAAb,aAAa,CAAmB;IAAG,CAAC;IAEjE,KAAK,CAAC,mBAAmB,CAAC,OAAsB;QAC9C,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC,aAAa,CAAC,UAAU,EAAE,CAAC;QAC3D,MAAM,QAAQ,GAAG,2BAA2B,CAAC;QAE7C,MAAM,CACJ,cAAc,EACd,sBAAsB,EACtB,2BAA2B,EAC3B,mBAAmB,EACpB,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YACpB,MAAM,CAAC,YAAY,CAAC;gBAClB,OAAO,EAAE,QAAQ;gBACjB,GAAG,EAAE,qBAAqB;gBAC1B,YAAY,EAAE,YAAY;gBAC1B,IAAI,EAAE,CAAC,OAAO,CAAC;aAChB,CAAC;YACF,MAAM,CAAC,YAAY,CAAC;gBAClB,OAAO,EAAE,QAAQ;gBACjB,GAAG,EAAE,qBAAqB;gBAC1B,YAAY,EAAE,eAAe;gBAC7B,IAAI,EAAE,CAAC,OAAO,CAAC;aAChB,CAAC;YACF,MAAM,CAAC,YAAY,CAAC;gBAClB,OAAO,EAAE,QAAQ;gBACjB,GAAG,EAAE,qBAAqB;gBAC1B,YAAY,EAAE,sBAAsB;aACrC,CAAC;YACF,IAAI,CAAC,wBAAwB,CAAC,MAAM,EAAE,QAAQ,CAAC;SAChD,CAAC,CAAC;QAEH,MAAM,CACJ,iBAAiB,EACjB,SAAS,EACT,GAAG,EACH,oBAAoB,EACpB,MAAM,EACN,SAAS,EACV,GAAG,cAAc,CAAC;QAEnB,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;QACjC,MAAM,oBAAoB,GAAG,MAAM,CAAC,iBAAiB,CAAC,CAAC;QACvD,MAAM,YAAY,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC;QACvC,MAAM,YAAY,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC;QAEvC,IAAI,+BAA+B,GAAkB,IAAI,CAAC;QAC1D,IAAI,SAAS,KAAK,CAAC,IAAI,oBAAoB,GAAG,CAAC,IAAI,mBAAmB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAClF,MAAM,kBAAkB,GAAG,IAAI,CAAC,kBAAkB,CAChD,oBAAoB,EACpB,YAAY,EACZ,mBAAmB,CAAC,MAAM,CAC3B,CAAC;YACF,+BAA+B;gBAC7B,mBAAmB,CAAC,kBAAkB,CAAC,IAAI,IAAI,CAAC;QACpD,CAAC;QAED,MAAM,mBAAmB,GAAG,SAAS,KAAK,CAAC,IAAI,YAAY,GAAG,CAAC,CAAC;QAEhE,MAAM,cAAc,GAClB,SAAS,KAAK,CAAC,IAAI,oBAAoB,GAAG,CAAC,IAAI,+BAA+B,KAAK,IAAI;YACrF,CAAC,CAAC,IAAI,CAAC,2BAA2B,CAC9B,oBAAoB,EACpB,+BAA+B,EAC/B,MAAM,CAAC,2BAA2B,CAAC,CACpC;YACH,CAAC,CAAC,IAAI,CAAC;QAEX,OAAO;YACL,OAAO;YACP,QAAQ;YACR,sBAAsB;YACtB,MAAM,EAAE,SAAS;YACjB,WAAW,EAAE,WAAW,CAAC,SAAS,CAAC;YACnC,aAAa,EAAE,mBAAmB,CAAC,CAAC,CAAC,cAAc,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,IAAI;YACxE,mBAAmB,EACjB,oBAAoB,GAAG,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,oBAAoB,CAAC,CAAC,CAAC,CAAC,IAAI;YACxE,iBAAiB,EAAE;gBACjB,aAAa,EACX,8DAA8D;gBAChE,mBAAmB,EACjB,wGAAwG;aAC3G;YACD,cAAc;YACd,QAAQ,EAAE;gBACR,iBAAiB,EAAE,oBAAoB;gBACvC,SAAS,EAAE,YAAY;gBACvB,GAAG;gBACH,oBAAoB,EAAE,MAAM,CAAC,oBAAoB,CAAC;gBAClD,MAAM,EAAE,SAAS;gBACjB,SAAS,EAAE,YAAY;aACxB;SACF,CAAC;IACJ,CAAC;IAEO,kBAAkB,CACxB,iBAAyB,EACzB,SAAiB,EACjB,aAAqB;QAErB,IAAI,iBAAiB,GAAG,kBAAkB,EAAE,CAAC;YAC3C,OAAO,aAAa,GAAG,CAAC,CAAC;QAC3B,CAAC;QACD,IAAI,SAAS,IAAI,aAAa,EAAE,CAAC;YAC/B,OAAO,CAAC,CAAC;QACX,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;IAEO,KAAK,CAAC,wBAAwB,CACpC,MAA6D,EAC7D,QAA4C;QAE5C,MAAM,OAAO,GAAa,EAAE,CAAC;QAC7B,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC;YACvC,IAAI,CAAC;gBACH,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC;oBACtC,OAAO,EAAE,QAAQ;oBACjB,GAAG,EAAE,qBAAqB;oBAC1B,YAAY,EAAE,qBAAqB;oBACnC,IAAI,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;iBACtB,CAAC,CAAC;gBACH,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;YAC9B,CAAC;YAAC,MAAM,CAAC;gBACP,MAAM;YACR,CAAC;QACH,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAEO,2BAA2B,CACjC,iBAAyB,EACzB,+BAAuC,EACvC,2BAAmC;QAEnC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QAC7C,MAAM,2BAA2B,GAAG,IAAI,CAAC,KAAK,CAC5C,CAAC,MAAM,GAAG,iBAAiB,CAAC,GAAG,KAAK,CACrC,CAAC;QACF,MAAM,+BAA+B,GACnC,+BAA+B,GAAG,2BAA2B,CAAC;QAChE,MAAM,+BAA+B,GACnC,iBAAiB,GAAG,+BAA+B,GAAG,KAAK,CAAC;QAC9D,MAAM,uBAAuB,GAAG,+BAA+B,GAAG,CAAC,CAAC;QACpE,MAAM,6BAA6B,GAAG,IAAI,CAAC,GAAG,CAC5C,GAAG,EACH,IAAI,CAAC,GAAG,CACN,CAAC,EACD,IAAI,CAAC,KAAK,CACR,CAAC,2BAA2B,GAAG,+BAA+B,CAAC;YAC7D,GAAG,CACN,CACF,CACF,CAAC;QAEF,OAAO;YACL,2BAA2B;YAC3B,+BAA+B;YAC/B,2BAA2B;YAC3B,+BAA+B;YAC/B,wBAAwB,EAAE,cAAc,CAAC,+BAA+B,CAAC;YACzE,6BAA6B;YAC7B,uBAAuB;SACxB,CAAC;IACJ,CAAC;CACF"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { CeloClientFactory } from "../clients/celo-client.js";
|
|
2
|
+
import { type SerializedPreparedFlow } from "../types/prepared.js";
|
|
3
|
+
export interface MentoFxParams {
|
|
4
|
+
slippageTolerance?: number;
|
|
5
|
+
deadlineMinutes?: number;
|
|
6
|
+
recipient?: `0x${string}`;
|
|
7
|
+
}
|
|
8
|
+
export declare class MentoFxService {
|
|
9
|
+
private readonly clientFactory;
|
|
10
|
+
private readonly tokenService;
|
|
11
|
+
constructor(clientFactory: CeloClientFactory);
|
|
12
|
+
private getMentoClient;
|
|
13
|
+
private resolveMentoPair;
|
|
14
|
+
private fxOptions;
|
|
15
|
+
private formatFxError;
|
|
16
|
+
private buildFxSwap;
|
|
17
|
+
private estimateCallGas;
|
|
18
|
+
private estimateSwapGasWithAllowance;
|
|
19
|
+
private baseQuoteFields;
|
|
20
|
+
getFxQuote(tokenIn: string, tokenOut: string, amount: string): Promise<{
|
|
21
|
+
protocol: "mento_fx";
|
|
22
|
+
network: "mainnet";
|
|
23
|
+
tokenIn: string;
|
|
24
|
+
tokenOut: string;
|
|
25
|
+
amountIn: string;
|
|
26
|
+
expectedOut: string;
|
|
27
|
+
routeHops: number;
|
|
28
|
+
}>;
|
|
29
|
+
estimateFx(from: `0x${string}`, tokenIn: string, tokenOut: string, amount: string, params?: MentoFxParams): Promise<{
|
|
30
|
+
from: `0x${string}`;
|
|
31
|
+
recipient: `0x${string}`;
|
|
32
|
+
amountOutMin: string;
|
|
33
|
+
approvalNeeded: boolean;
|
|
34
|
+
approvalGas: string | undefined;
|
|
35
|
+
fxGas: string;
|
|
36
|
+
slippageTolerance: number;
|
|
37
|
+
deadline: string;
|
|
38
|
+
deadlineMinutes: number;
|
|
39
|
+
protocol: "mento_fx";
|
|
40
|
+
network: "mainnet";
|
|
41
|
+
tokenIn: string;
|
|
42
|
+
tokenOut: string;
|
|
43
|
+
amountIn: string;
|
|
44
|
+
expectedOut: string;
|
|
45
|
+
routeHops: number;
|
|
46
|
+
}>;
|
|
47
|
+
prepareFx(from: `0x${string}`, tokenIn: string, tokenOut: string, amount: string, params?: MentoFxParams): Promise<SerializedPreparedFlow>;
|
|
48
|
+
}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { concat, decodeFunctionData, erc20Abi, formatUnits, } from "viem";
|
|
2
|
+
import { ChainId, deadlineFromMinutes, Mento, RouteNotFoundError, FXMarketClosedError, } from "../clients/mento-sdk.js";
|
|
3
|
+
import { CELINA_DATA_SUFFIX } from "../config/celina-tag.js";
|
|
4
|
+
import { toMentoTokenAddress } from "../config/chains.js";
|
|
5
|
+
import { ALLOWANCE_MAPPING_SLOTS, erc20AllowanceStateOverride, isLikelyTransferFailed, } from "../utils/erc20-allowance-storage.js";
|
|
6
|
+
import { serializePreparedFlow, } from "../types/prepared.js";
|
|
7
|
+
import { TokenService } from "./token.service.js";
|
|
8
|
+
const DEFAULT_SLIPPAGE = 0.5;
|
|
9
|
+
const DEFAULT_DEADLINE_MINUTES = 5;
|
|
10
|
+
function parseErc20Approve(params) {
|
|
11
|
+
try {
|
|
12
|
+
const decoded = decodeFunctionData({
|
|
13
|
+
abi: erc20Abi,
|
|
14
|
+
data: params.data,
|
|
15
|
+
});
|
|
16
|
+
if (decoded.functionName !== "approve") {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
return {
|
|
20
|
+
token: params.to,
|
|
21
|
+
spender: decoded.args[0],
|
|
22
|
+
amount: decoded.args[1],
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function taggedCalldata(data) {
|
|
30
|
+
return concat([data, CELINA_DATA_SUFFIX]);
|
|
31
|
+
}
|
|
32
|
+
function callParamsToPreparedTx(params, description) {
|
|
33
|
+
const approve = parseErc20Approve(params);
|
|
34
|
+
if (approve) {
|
|
35
|
+
return {
|
|
36
|
+
kind: "erc20",
|
|
37
|
+
to: approve.token,
|
|
38
|
+
data: taggedCalldata(params.data),
|
|
39
|
+
value: "0",
|
|
40
|
+
description,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
kind: "contract",
|
|
45
|
+
to: params.to,
|
|
46
|
+
data: taggedCalldata(params.data),
|
|
47
|
+
value: BigInt(params.value).toString(),
|
|
48
|
+
description,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
export class MentoFxService {
|
|
52
|
+
clientFactory;
|
|
53
|
+
tokenService;
|
|
54
|
+
constructor(clientFactory) {
|
|
55
|
+
this.clientFactory = clientFactory;
|
|
56
|
+
this.tokenService = new TokenService(clientFactory);
|
|
57
|
+
}
|
|
58
|
+
async getMentoClient(publicClient) {
|
|
59
|
+
return Mento.create(ChainId.CELO, publicClient);
|
|
60
|
+
}
|
|
61
|
+
resolveMentoPair(tokenIn, tokenOut) {
|
|
62
|
+
const resolvedIn = this.tokenService.resolveToken(tokenIn);
|
|
63
|
+
const resolvedOut = this.tokenService.resolveToken(tokenOut);
|
|
64
|
+
return {
|
|
65
|
+
resolvedIn,
|
|
66
|
+
resolvedOut,
|
|
67
|
+
mentoIn: toMentoTokenAddress(resolvedIn.address),
|
|
68
|
+
mentoOut: toMentoTokenAddress(resolvedOut.address),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
fxOptions(params) {
|
|
72
|
+
const slippageTolerance = params?.slippageTolerance ?? DEFAULT_SLIPPAGE;
|
|
73
|
+
const deadlineMinutes = params?.deadlineMinutes ?? DEFAULT_DEADLINE_MINUTES;
|
|
74
|
+
return {
|
|
75
|
+
slippageTolerance,
|
|
76
|
+
deadlineMinutes,
|
|
77
|
+
deadline: deadlineFromMinutes(deadlineMinutes),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
formatFxError(error, tokenIn, tokenOut) {
|
|
81
|
+
if (error instanceof RouteNotFoundError) {
|
|
82
|
+
throw new Error(`No Mento FX route for ${tokenIn} → ${tokenOut}.`);
|
|
83
|
+
}
|
|
84
|
+
if (error instanceof FXMarketClosedError) {
|
|
85
|
+
throw new Error("Mento FX market is currently closed. FX quotes and execution are unavailable until the market reopens.");
|
|
86
|
+
}
|
|
87
|
+
if (error instanceof Error && /FXMarketClosed/i.test(error.message)) {
|
|
88
|
+
throw new Error("Mento FX market is currently closed. FX quotes and execution are unavailable until the market reopens.");
|
|
89
|
+
}
|
|
90
|
+
throw error instanceof Error ? error : new Error(String(error));
|
|
91
|
+
}
|
|
92
|
+
async buildFxSwap(from, tokenIn, tokenOut, amount, params) {
|
|
93
|
+
const { public: client } = this.clientFactory.getClients();
|
|
94
|
+
const { resolvedIn, resolvedOut, mentoIn, mentoOut } = this.resolveMentoPair(tokenIn, tokenOut);
|
|
95
|
+
const recipient = params?.recipient ?? from;
|
|
96
|
+
const amountInWei = this.tokenService.parseAmount(amount, resolvedIn.decimals);
|
|
97
|
+
const { slippageTolerance, deadlineMinutes, deadline } = this.fxOptions(params);
|
|
98
|
+
const mento = await this.getMentoClient(client);
|
|
99
|
+
const { approval, swap } = await mento.swap.buildSwapTransaction(mentoIn, mentoOut, amountInWei, recipient, from, { slippageTolerance, deadline });
|
|
100
|
+
return {
|
|
101
|
+
client,
|
|
102
|
+
from,
|
|
103
|
+
recipient,
|
|
104
|
+
resolvedIn,
|
|
105
|
+
resolvedOut,
|
|
106
|
+
amount,
|
|
107
|
+
approval,
|
|
108
|
+
swap,
|
|
109
|
+
slippageTolerance,
|
|
110
|
+
deadlineMinutes,
|
|
111
|
+
deadline,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
async estimateCallGas(client, from, params) {
|
|
115
|
+
const approve = parseErc20Approve(params);
|
|
116
|
+
if (approve) {
|
|
117
|
+
const gas = await client.estimateContractGas({
|
|
118
|
+
account: from,
|
|
119
|
+
address: approve.token,
|
|
120
|
+
abi: erc20Abi,
|
|
121
|
+
functionName: "approve",
|
|
122
|
+
args: [approve.spender, approve.amount],
|
|
123
|
+
});
|
|
124
|
+
return gas.toString();
|
|
125
|
+
}
|
|
126
|
+
const gas = await client.estimateGas({
|
|
127
|
+
account: from,
|
|
128
|
+
to: params.to,
|
|
129
|
+
data: params.data,
|
|
130
|
+
value: BigInt(params.value),
|
|
131
|
+
});
|
|
132
|
+
return gas.toString();
|
|
133
|
+
}
|
|
134
|
+
async estimateSwapGasWithAllowance(client, from, params, approve) {
|
|
135
|
+
const request = {
|
|
136
|
+
account: from,
|
|
137
|
+
to: params.to,
|
|
138
|
+
data: params.data,
|
|
139
|
+
value: BigInt(params.value),
|
|
140
|
+
};
|
|
141
|
+
for (const mappingSlot of ALLOWANCE_MAPPING_SLOTS) {
|
|
142
|
+
try {
|
|
143
|
+
const gas = await client.estimateGas({
|
|
144
|
+
...request,
|
|
145
|
+
stateOverride: erc20AllowanceStateOverride(approve.token, from, approve.spender, approve.amount, mappingSlot),
|
|
146
|
+
});
|
|
147
|
+
return gas.toString();
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
if (!isLikelyTransferFailed(error)) {
|
|
151
|
+
throw error;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
throw new Error("Could not estimate Mento FX swap gas: failed to simulate ERC-20 allowance for this token.");
|
|
156
|
+
}
|
|
157
|
+
baseQuoteFields(resolvedIn, resolvedOut, amount, expectedOutWei, routeHops) {
|
|
158
|
+
return {
|
|
159
|
+
protocol: "mento_fx",
|
|
160
|
+
network: "mainnet",
|
|
161
|
+
tokenIn: resolvedIn.symbol,
|
|
162
|
+
tokenOut: resolvedOut.symbol,
|
|
163
|
+
amountIn: amount,
|
|
164
|
+
expectedOut: formatUnits(expectedOutWei, resolvedOut.decimals),
|
|
165
|
+
routeHops,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
async getFxQuote(tokenIn, tokenOut, amount) {
|
|
169
|
+
const { public: client } = this.clientFactory.getClients();
|
|
170
|
+
const { resolvedIn, resolvedOut, mentoIn, mentoOut } = this.resolveMentoPair(tokenIn, tokenOut);
|
|
171
|
+
const amountInWei = this.tokenService.parseAmount(amount, resolvedIn.decimals);
|
|
172
|
+
try {
|
|
173
|
+
const mento = await this.getMentoClient(client);
|
|
174
|
+
const [expectedOutWei, route] = await Promise.all([
|
|
175
|
+
mento.quotes.getAmountOut(mentoIn, mentoOut, amountInWei),
|
|
176
|
+
mento.routes.findRoute(mentoIn, mentoOut),
|
|
177
|
+
]);
|
|
178
|
+
return this.baseQuoteFields(resolvedIn, resolvedOut, amount, expectedOutWei, route.path.length);
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
this.formatFxError(error, resolvedIn.symbol, resolvedOut.symbol);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
async estimateFx(from, tokenIn, tokenOut, amount, params) {
|
|
185
|
+
try {
|
|
186
|
+
const built = await this.buildFxSwap(from, tokenIn, tokenOut, amount, params);
|
|
187
|
+
const { client, resolvedIn, resolvedOut, approval, swap, recipient, slippageTolerance, deadlineMinutes, deadline, } = built;
|
|
188
|
+
const approvalParsed = approval ? parseErc20Approve(approval) : null;
|
|
189
|
+
const approvalGas = approval
|
|
190
|
+
? await this.estimateCallGas(client, from, approval)
|
|
191
|
+
: undefined;
|
|
192
|
+
const fxGas = approvalParsed !== null
|
|
193
|
+
? await this.estimateSwapGasWithAllowance(client, from, swap.params, approvalParsed)
|
|
194
|
+
: await this.estimateCallGas(client, from, swap.params);
|
|
195
|
+
return {
|
|
196
|
+
...this.baseQuoteFields(resolvedIn, resolvedOut, amount, swap.expectedAmountOut, swap.route.path.length),
|
|
197
|
+
from,
|
|
198
|
+
recipient,
|
|
199
|
+
amountOutMin: formatUnits(swap.amountOutMin, resolvedOut.decimals),
|
|
200
|
+
approvalNeeded: approval !== null,
|
|
201
|
+
approvalGas,
|
|
202
|
+
fxGas,
|
|
203
|
+
slippageTolerance,
|
|
204
|
+
deadline: deadline.toString(),
|
|
205
|
+
deadlineMinutes,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
catch (error) {
|
|
209
|
+
this.formatFxError(error, tokenIn, tokenOut);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
async prepareFx(from, tokenIn, tokenOut, amount, params) {
|
|
213
|
+
try {
|
|
214
|
+
const built = await this.buildFxSwap(from, tokenIn, tokenOut, amount, params);
|
|
215
|
+
const { resolvedIn, resolvedOut, approval, swap, recipient } = built;
|
|
216
|
+
const steps = [];
|
|
217
|
+
if (approval) {
|
|
218
|
+
steps.push(callParamsToPreparedTx(approval, `Approve ${resolvedIn.symbol} for Mento FX`));
|
|
219
|
+
}
|
|
220
|
+
steps.push(callParamsToPreparedTx(swap.params, `Swap ${amount} ${resolvedIn.symbol} → ~${formatUnits(swap.expectedAmountOut, resolvedOut.decimals)} ${resolvedOut.symbol}`));
|
|
221
|
+
const flow = {
|
|
222
|
+
network: "mainnet",
|
|
223
|
+
from,
|
|
224
|
+
summary: `Mento FX: ${amount} ${resolvedIn.symbol} → ${resolvedOut.symbol}${recipient !== from ? ` (recipient ${recipient})` : ""}`,
|
|
225
|
+
steps,
|
|
226
|
+
};
|
|
227
|
+
return serializePreparedFlow(flow);
|
|
228
|
+
}
|
|
229
|
+
catch (error) {
|
|
230
|
+
this.formatFxError(error, tokenIn, tokenOut);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
//# sourceMappingURL=mento-fx.service.js.map
|