Skip to content

Commit

Permalink
feat: solana on rango, support for pre signed solana swap transaction…
Browse files Browse the repository at this point in the history
…s, legacy transactions, fix: some changelly api calls, chore: changelly types and documentation links
  • Loading branch information
NickKelly1 committed Sep 23, 2024
1 parent f5c1134 commit f978b42
Show file tree
Hide file tree
Showing 16 changed files with 2,416 additions and 859 deletions.
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

1 comment on commit f978b42

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.