Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for Rango and (almost) Changelly on Solana #516

Merged
merged 3 commits into from
Sep 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions packages/extension/src/libs/keyring/public-keyring.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,26 @@ class PublicKeyRing {
walletType: WalletType.mnemonic,
isHardware: false,
};
allKeys["77hREDDaAiimedtD9bR1JDMgYLW3AA5yPvD91pvrueRp"] = {
address: "77hREDDaAiimedtD9bR1JDMgYLW3AA5yPvD91pvrueRp",
basePath: "m/44'/501'/0'/1",
name: "fake sol acc 1",
pathIndex: 0,
publicKey: "0x0",
signerType: SignerType.ed25519sol,
walletType: WalletType.mnemonic,
isHardware: false,
};
allKeys["tQvduDby4rvC6VU4rSirhVWuRYxbJz3rvUrVMkUWsZP"] = {
address: "tQvduDby4rvC6VU4rSirhVWuRYxbJz3rvUrVMkUWsZP",
basePath: "m/44'/501'/0'/1",
name: "fake sol acc 2",
pathIndex: 0,
publicKey: "0x0",
signerType: SignerType.ed25519sol,
walletType: WalletType.mnemonic,
isHardware: false,
};
}
return allKeys;
}
Expand Down
101 changes: 76 additions & 25 deletions packages/extension/src/ui/action/views/swap/libs/send-transactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import BitcoinAPI from "@/providers/bitcoin/libs/api";
import SolanaAPI from "@/providers/solana/libs/api";
import {
VersionedTransaction as SolanaVersionedTransaction,
Transaction as SolanaLegacyTransaction,
PublicKey,
SendTransactionError,
} from "@solana/web3.js";
Expand Down Expand Up @@ -192,35 +193,88 @@ export const executeSwap = async (
// Execute each transaction in-order one-by-one
for (const enkSolTx of enkSolTxs) {
// Transform the Enkrypt representation of the transaction into the Solana lib's representation
const tx = SolanaVersionedTransaction.deserialize(
Buffer.from(enkSolTx.serialized, "base64")
);

// Sign the transaction message
// Use the keyring running in the background script
const sigRes = await sendUsingInternalMessengers({
method: InternalMethods.sign,
params: [bufferToHex(tx.message.serialize()), options.from],
});
let serialized: Uint8Array;
switch (enkSolTx.kind) {
case "versioned": {
// Sign Versioned transaction
// (note: the transaction may already be signed by a third party,
// like Rango exchange)

// Did we fail to sign?
if (sigRes.error != null) {
throw new Error(
`Failed to sign Solana swap transaction: ${sigRes.error.code} ${sigRes.error.message}`
);
}
const tx = SolanaVersionedTransaction.deserialize(
Buffer.from(enkSolTx.serialized, "base64")
);

// Sign the transaction message
// Use the keyring running in the background script
const sigRes = await sendUsingInternalMessengers({
method: InternalMethods.sign,
params: [bufferToHex(tx.message.serialize()), options.from],
});

// Did we fail to sign?
if (sigRes.error != null) {
throw new Error(
`Failed to sign Solana versioned swap transaction: ${sigRes.error.code} ${sigRes.error.message}`
);
}

// Add signature to the transaction
tx.addSignature(
new PublicKey(options.network.displayAddress(options.from.address)),
hexToBuffer(JSON.parse(sigRes.result!))
);

serialized = tx.serialize();

break;
}

case "legacy": {
// Sign Versioned transaction
// (note: the transaction may already be signed by a third party,
// like Rango exchange)

// Add signature to the transaction
tx.addSignature(
new PublicKey(options.network.displayAddress(options.from.address)),
hexToBuffer(JSON.parse(sigRes.result!))
);
const tx = SolanaLegacyTransaction.from(
Buffer.from(enkSolTx.serialized, "base64")
);

// Sign the transaction message
// Use the keyring running in the background script
const sigRes = await sendUsingInternalMessengers({
method: InternalMethods.sign,
params: [bufferToHex(tx.serialize()), options.from],
});

// Did we fail to sign?
if (sigRes.error != null) {
throw new Error(
`Failed to sign Solana legacy swap transaction: ${sigRes.error.code} ${sigRes.error.message}`
);
}

// Add signature to the transaction
tx.addSignature(
new PublicKey(options.network.displayAddress(options.from.address)),
hexToBuffer(JSON.parse(sigRes.result!))
);

serialized = tx.serialize();

break;
}

default:
enkSolTx.kind satisfies never;
throw new Error(
`Cannot send Solana transaction: unexpected kind ${enkSolTx.kind}`
);
}

// Send the transaction
let txHash: string;
try {
// TODO: don't skip preflight
txHash = await conn.sendRawTransaction(tx.serialize());
txHash = await conn.sendRawTransaction(serialized);
} catch (err) {
// Log error info if possible
// The Solana web3 library prompts you to call getLogs if your error is of type
Expand Down Expand Up @@ -263,9 +317,6 @@ export const executeSwap = async (
network: options.network.name,
});

// TODO:get the status of the transaction?
// activity.status = // success | failed | pending

solTxHashes.push(txHash);
}

Expand Down
85 changes: 72 additions & 13 deletions packages/extension/src/ui/action/views/swap/libs/solana-gasvals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@ import { GasFeeType, GasPriceTypes } from "@/providers/common/types";
import SolanaAPI from "@/providers/solana/libs/api";
import { SolanaNetwork } from "@/providers/solana/types/sol-network";
import { fromBase } from "@enkryptcom/utils";
import { VersionedTransaction } from "@solana/web3.js";
import {
VersionedTransaction as SolanaVersionedTransaction,
Transaction as SolanaLegacyTransaction,
VersionedMessage,
Message,
} from "@solana/web3.js";
import BigNumber from "bignumber.js";
import { toBN } from "web3-utils";

Expand All @@ -11,7 +16,7 @@ import { toBN } from "web3-utils";
* (not nice but convenient)
*/
export const getSolanaTransactionFees = async (
txs: VersionedTransaction[],
txs: (SolanaVersionedTransaction | SolanaLegacyTransaction)[],
network: SolanaNetwork,
price: number,
additionalFee: ReturnType<typeof toBN>
Expand All @@ -21,8 +26,12 @@ export const getSolanaTransactionFees = async (
let latestBlockHash = await conn.getLatestBlockhash();
for (let i = 0, len = txs.length; i < len; i++) {
const tx = txs[i];

// Use the latest block hash in-case it's fallen too far behind
tx.message.recentBlockhash = latestBlockHash.blockhash;
// (can't change block hash if it's already signed)
if (!tx.signatures.length) {
updateBlockHash(tx, latestBlockHash.blockhash);
}

// Not sure why but getFeeForMessage sometimes returns null, so we will retry
// with small backoff in-case it helps
Expand All @@ -36,27 +45,34 @@ export const getSolanaTransactionFees = async (
throw new Error(
`Failed to get fee for Solana VersionedTransaction ${i + 1}` +
` after ${backoff.length} attempts.` +
` Transaction block hash ${tx.message.recentBlockhash} possibly expired.`
` Transaction block hash` +
`${getRecentBlockHash(tx)} possibly expired.`
);
}
if (backoff[attempt] > 0) {
// wait before retrying
await new Promise((res) => {
return setTimeout(res, backoff[attempt]);
});
await new Promise((res) => setTimeout(res, backoff[attempt]));
}
// Update the block hash in-case it caused 0 fees to be returned
if (attempt > 0) {
latestBlockHash = await conn.getLatestBlockhash();
tx.message.recentBlockhash = latestBlockHash.blockhash;
if (!tx.signatures.length) {
console.warn(
`Cannot update block hash for signed transaction` +
` ${i + 1}, retrying getFeeForMessage using the same` +
` block hash ${getRecentBlockHash(tx)}`
);
} else {
latestBlockHash = await conn.getLatestBlockhash();
updateBlockHash(tx, latestBlockHash.blockhash);
}
}

/** Base fee + priority fee (Don't know why this returns null sometimes) */
const feeResult = await conn.getFeeForMessage(tx.message);
const feeResult = await conn.getFeeForMessage(getMessage(tx));
if (feeResult.value == null) {
console.warn(
`Failed to get fee for Solana VersionedTransaction` +
` ${i + 1}. Transaction block hash ${tx.message.recentBlockhash}` +
`Failed to get fee for Solana VersionedTransaction ${i + 1}.` +
` Transaction block hash ${getRecentBlockHash(tx)}` +
` possibly expired. Attempt ${attempt + 1}/${backoff.length}.`
);
} else {
Expand All @@ -74,7 +90,6 @@ export const getSolanaTransactionFees = async (
// Convert from lamports to SOL
const feesumsol = fromBase(feesumlamp.toString(), network.decimals);

// TODO: give different fees for different priority levels
return {
[GasPriceTypes.REGULAR]: {
nativeValue: feesumsol,
Expand All @@ -84,3 +99,47 @@ export const getSolanaTransactionFees = async (
},
};
};

function getRecentBlockHash(
tx: SolanaVersionedTransaction | SolanaLegacyTransaction
): string {
return getMessage(tx).recentBlockhash;
}

function updateBlockHash(
tx: SolanaVersionedTransaction | SolanaLegacyTransaction,
recentBlockHash: string
): void {
switch ((tx as SolanaVersionedTransaction).version) {
case 0:
case "legacy":
(tx as SolanaVersionedTransaction).message.recentBlockhash =
recentBlockHash;
break;
case undefined:
(tx as SolanaLegacyTransaction).recentBlockhash = recentBlockHash;
break;
default:
throw new Error(
`Cannot set block hash for Solana transaction: unexpected Solana transaction` +
` type ${Object.getPrototypeOf(tx).constructor.name}`
);
}
}

function getMessage(
tx: SolanaVersionedTransaction | SolanaLegacyTransaction
): Message | VersionedMessage {
switch ((tx as SolanaVersionedTransaction).version) {
case 0:
case "legacy":
return (tx as SolanaVersionedTransaction).message;
case undefined:
return (tx as SolanaLegacyTransaction).compileMessage();
default:
throw new Error(
`Cannot get Solana transaction message: unexpected Solana transaction` +
` type ${Object.getPrototypeOf(tx).constructor.name}`
);
}
}
32 changes: 24 additions & 8 deletions packages/extension/src/ui/action/views/swap/libs/swap-txs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ import BitcoinAPI from "@/providers/bitcoin/libs/api";
import { getTxInfo as getBTCTxInfo } from "@/providers/bitcoin/libs/utils";
import { toBN } from "web3-utils";
import { BTCTxInfo } from "@/providers/bitcoin/ui/types";
import { VersionedTransaction as SolanaVersionedTransaction } from "@solana/web3.js";
import {
VersionedTransaction as SolanaVersionedTransaction,
Transaction as SolanaLegacyTransaction,
} from "@solana/web3.js";

export const getSubstrateNativeTransation = async (
network: SubstrateNetwork,
Expand Down Expand Up @@ -97,7 +100,7 @@ export const getEVMTransaction = async (
export const getSwapTransactions = async (
networkName: SupportedNetworkName,
transactions: TransactionType[]
) => {
): Promise<any[]> => {
const netInfo = getNetworkInfoByName(networkName);
const network = await getNetworkByName(
networkName as unknown as NetworkNames
Expand All @@ -112,12 +115,25 @@ export const getSwapTransactions = async (
const allTxs = await Promise.all(txPromises);
return allTxs;
} else if (netInfo.type === NetworkType.Solana) {
const solTxs = (transactions as EnkryptSolanaTransaction[]).map(
(enkSolTx) =>
SolanaVersionedTransaction.deserialize(
Buffer.from(enkSolTx.serialized, "base64")
)
);
const solTxs: (SolanaVersionedTransaction | SolanaLegacyTransaction)[] = (
transactions as EnkryptSolanaTransaction[]
).map(function (enkSolTx) {
switch (enkSolTx.kind) {
case "legacy":
return SolanaLegacyTransaction.from(
Buffer.from(enkSolTx.serialized, "base64")
);
case "versioned":
return SolanaVersionedTransaction.deserialize(
Buffer.from(enkSolTx.serialized, "base64")
);
default:
enkSolTx.kind satisfies never;
throw new Error(
`Cannot deserialize Solana transaction: Unexpected kind: ${enkSolTx.kind}`
);
}
});
return solTxs;
} else if (netInfo.type === NetworkType.Substrate) {
if (transactions.length > 1)
Expand Down
1 change: 1 addition & 0 deletions packages/swap/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"dependencies": {
"@enkryptcom/types": "workspace:^",
"@enkryptcom/utils": "workspace:^",
"@solana/spl-token": "^0.4.8",
"@solana/web3.js": "^1.95.3",
"bignumber.js": "^9.1.2",
"eventemitter3": "^5.0.1",
Expand Down
10 changes: 10 additions & 0 deletions packages/swap/src/configs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,19 @@ const TOKEN_LISTS: {
[NetworkNames.Telos]: `https://raw.githubusercontent.com/enkryptcom/dynamic-data/main/swaplists/${SupportedNetworkName.Telos}.json`,
};

/**
* ```sh
* curl -sL https://raw.githubusercontent.com/enkryptcom/dynamic-data/main/swaplists/changelly.json | jq '.' -C | less -R
* ```
*/
const CHANGELLY_LIST =
"https://raw.githubusercontent.com/enkryptcom/dynamic-data/main/swaplists/changelly.json";

/**
* ```sh
* curl -sL https://raw.githubusercontent.com/enkryptcom/dynamic-data/main/swaplists/top-tokens.json | jq '.' -C | less -R
* ```
*/
const TOP_TOKEN_INFO_LIST =
"https://raw.githubusercontent.com/enkryptcom/dynamic-data/main/swaplists/top-tokens.json";

Expand Down
Loading
Loading