Skip to content

Commit

Permalink
Merge pull request #80 from DIG-Network/release/v0.0.1-alpha.86
Browse files Browse the repository at this point in the history
Release/v0.0.1 alpha.86
  • Loading branch information
MichaelTaylor3D authored Sep 25, 2024
2 parents 3c89624 + 7c3a34d commit 18fc7f6
Show file tree
Hide file tree
Showing 5 changed files with 153 additions and 47 deletions.
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,19 @@

All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.

### [0.0.1-alpha.86](https://github.com/DIG-Network/dig-chia-sdk/compare/v0.0.1-alpha.85...v0.0.1-alpha.86) (2024-09-25)


### Features

* add retry logic to keep selecting coins until it finds one ([0ef5514](https://github.com/DIG-Network/dig-chia-sdk/commit/0ef5514ebf73f2ec356b04e943411b6efc0bde57))


### Bug Fixes

* public ip logic ([b722fa0](https://github.com/DIG-Network/dig-chia-sdk/commit/b722fa0c6391eb7cfebc49be5ce4e50fae44483f))
* public ip logic ([3642458](https://github.com/DIG-Network/dig-chia-sdk/commit/3642458eb44611496f70cb26c12a411164b9e77d))

### [0.0.1-alpha.85](https://github.com/DIG-Network/dig-chia-sdk/compare/v0.0.1-alpha.84...v0.0.1-alpha.85) (2024-09-25)


Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@dignetwork/dig-sdk",
"version": "0.0.1-alpha.85",
"version": "0.0.1-alpha.86",
"description": "",
"type": "commonjs",
"main": "./dist/index.js",
Expand Down
133 changes: 98 additions & 35 deletions src/blockchain/Wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,10 @@ export class Wallet {
return mnemonic;
}

public static async importWallet(walletName: string, seed?: string): Promise<string> {
public static async importWallet(
walletName: string,
seed?: string
): Promise<string> {
const mnemonic = seed || (await askForMnemonicInput()).providedMnemonic;
if (!bip39.validateMnemonic(mnemonic)) {
throw new Error("Provided mnemonic is invalid.");
Expand All @@ -94,7 +97,9 @@ export class Wallet {
return mnemonic;
}

public static async importWalletFromChia(walletName: string): Promise<string> {
public static async importWalletFromChia(
walletName: string
): Promise<string> {
const chiaRoot = getChiaRoot();
const certificateFolderPath = `${chiaRoot}/config/ssl`;
const config = getChiaConfig();
Expand Down Expand Up @@ -169,18 +174,24 @@ export class Wallet {
return Object.keys(config);
}

private static async getWalletFromKeyring(walletName: string): Promise<string | null> {
private static async getWalletFromKeyring(
walletName: string
): Promise<string | null> {
const nconfManager = new NconfManager(KEYRING_FILE);
if (await nconfManager.configExists()) {
const encryptedData: EncryptedData | null = await nconfManager.getConfigValue(walletName);
const encryptedData: EncryptedData | null =
await nconfManager.getConfigValue(walletName);
if (encryptedData) {
return decryptData(encryptedData);
}
}
return null;
}

private static async saveWalletToKeyring(walletName: string, mnemonic: string): Promise<void> {
private static async saveWalletToKeyring(
walletName: string,
mnemonic: string
): Promise<void> {
const nconfManager = new NconfManager(KEYRING_FILE);
const encryptedData = encryptData(mnemonic);
await nconfManager.setConfigValue(walletName, encryptedData);
Expand All @@ -189,7 +200,10 @@ export class Wallet {
public async createKeyOwnershipSignature(nonce: string): Promise<string> {
const message = `Signing this message to prove ownership of key.\n\nNonce: ${nonce}`;
const privateSyntheticKey = await this.getPrivateSyntheticKey();
const signature = signMessage(Buffer.from(message, "utf-8"), privateSyntheticKey);
const signature = signMessage(
Buffer.from(message, "utf-8"),
privateSyntheticKey
);
return signature.toString("hex");
}

Expand All @@ -212,37 +226,76 @@ export class Wallet {
feeBigInt: bigint,
omitCoins: Coin[] = []
): Promise<Coin[]> {
const cache = new FileCache<{ coinId: string; expiry: number }>(path.join(USER_DIR_PATH, "reserved_coins"));
const cachedReservedCoins = cache.getCachedKeys();
const now = Date.now();
const omitCoinIds = omitCoins.map((coin) => getCoinId(coin).toString("hex"));

cachedReservedCoins.forEach((coinId) => {
const reservation = cache.get(coinId);
if (reservation && reservation.expiry > now) {
omitCoinIds.push(coinId);
} else {
cache.delete(coinId);
}
});

const ownerPuzzleHash = await this.getOwnerPuzzleHash();

const coinsResp = await peer.getAllUnspentCoins(
ownerPuzzleHash,
MIN_HEIGHT,
Buffer.from(MIN_HEIGHT_HEADER_HASH, "hex")
const cache = new FileCache<{ coinId: string; expiry: number }>(
path.join(USER_DIR_PATH, "reserved_coins")
);

const unspentCoins = coinsResp.coins.filter(
(coin) => !omitCoinIds.includes(getCoinId(coin).toString("hex"))
);
const ownerPuzzleHash = await this.getOwnerPuzzleHash();

const selectedCoins = selectCoins(unspentCoins, feeBigInt + coinAmount);
if (selectedCoins.length === 0) {
throw new Error("No unspent coins available.");
// Define a function to attempt selecting unspent coins
const trySelectCoins = async (): Promise<Coin[]> => {
const now = Date.now();
const omitCoinIds = omitCoins.map((coin) =>
getCoinId(coin).toString("hex")
);

// Update omitCoinIds with currently valid reserved coins
const cachedReservedCoins = cache.getCachedKeys();

cachedReservedCoins.forEach((coinId) => {
const reservation = cache.get(coinId);
if (reservation && reservation.expiry > now) {
if (!omitCoinIds.includes(coinId)) {
omitCoinIds.push(coinId);
}
} else {
cache.delete(coinId);
}
});

const coinsResp = await peer.getAllUnspentCoins(
ownerPuzzleHash,
MIN_HEIGHT,
Buffer.from(MIN_HEIGHT_HEADER_HASH, "hex")
);

const unspentCoins = coinsResp.coins.filter(
(coin) => !omitCoinIds.includes(getCoinId(coin).toString("hex"))
);

const selectedCoins = selectCoins(unspentCoins, feeBigInt + coinAmount);
return selectedCoins;
};

let selectedCoins: Coin[] = [];
let retry = true;

while (retry) {
selectedCoins = await trySelectCoins();

if (selectedCoins.length > 0) {
// Coins have been successfully selected
retry = false;
} else {
const now = Date.now();
// Check if there are any valid cached reserved coins left
const cachedReservedCoins = cache.getCachedKeys().filter((coinId) => {
const reservation = cache.get(coinId);
return reservation && reservation.expiry > now;
});

if (cachedReservedCoins.length > 0) {
// Wait 10 seconds and try again
console.log("No unspent coins available. Waiting 10 seconds...");
await new Promise((resolve) => setTimeout(resolve, 10000));
} else {
// No unspent coins and no reserved coins
throw new Error("No unspent coins available.");
}
}
}

// Reserve the selected coins
selectedCoins.forEach((coin) => {
const coinId = getCoinId(coin).toString("hex");
cache.set(coinId, { coinId, expiry: Date.now() + CACHE_DURATION });
Expand All @@ -251,13 +304,23 @@ export class Wallet {
return selectedCoins;
}

public static async calculateFeeForCoinSpends(peer: Peer, coinSpends: CoinSpend[] | null): Promise<bigint> {
public static async calculateFeeForCoinSpends(
peer: Peer,
coinSpends: CoinSpend[] | null
): Promise<bigint> {
return BigInt(1000000);
}

public static async isCoinSpendable(peer: Peer, coinId: Buffer): Promise<boolean> {
public static async isCoinSpendable(
peer: Peer,
coinId: Buffer
): Promise<boolean> {
try {
return await peer.isCoinSpent(coinId, MIN_HEIGHT, Buffer.from(MIN_HEIGHT_HEADER_HASH, "hex"));
return await peer.isCoinSpent(
coinId,
MIN_HEIGHT,
Buffer.from(MIN_HEIGHT_HEADER_HASH, "hex")
);
} catch (error) {
return false;
}
Expand Down
48 changes: 39 additions & 9 deletions src/utils/network.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,62 @@
import superagent from 'superagent';
/*
* Stopgap until better solution for finding public IPS found
*/
import superagent from "superagent";

const MAX_RETRIES = 5;
const RETRY_DELAY = 2000; // in milliseconds

// Regular expression for validating both IPv4 and IPv6 addresses
const ipv4Regex =
/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
const ipv6Regex =
/^(([0-9a-fA-F]{1,4}:){7}([0-9a-fA-F]{1,4}|:)|(([0-9a-fA-F]{1,4}:){1,7}|:):(([0-9a-fA-F]{1,4}:){1,6}|:):([0-9a-fA-F]{1,4}|:):([0-9a-fA-F]{1,4}|:)|::)$/;

// Helper function to validate the IP address
const isValidIp = (ip: string): boolean => {
return ipv4Regex.test(ip) || ipv6Regex.test(ip);
};

export const getPublicIpAddress = async (): Promise<string | undefined> => {
const publicIp = process.env.PUBLIC_IP;

if (publicIp) {
console.log('Public IP address from env:', publicIp);
return publicIp;
console.log("Public IP address from env:", publicIp);
if (isValidIp(publicIp)) {
return publicIp;
}
console.error("Invalid public IP address in environment variable");
return undefined;
}

let attempt = 0;

while (attempt < MAX_RETRIES) {
try {
const response = await superagent.get('https://api.datalayer.storage/user/v1/get_user_ip');
const response = await superagent.get(
"https://api.datalayer.storage/user/v1/get_user_ip"
);

if (response.body && response.body.success) {
return response.body.ip_address;
} else {
throw new Error('Failed to retrieve public IP address');
const ipAddress = response.body.ip_address;

if (isValidIp(ipAddress)) {
return ipAddress;
}
throw new Error("Invalid IP address format received");
}
throw new Error("Failed to retrieve public IP address");
} catch (error: any) {
attempt++;
console.error(`Error fetching public IP address (Attempt ${attempt}):`, error.message);
console.error(
`Error fetching public IP address (Attempt ${attempt}):`,
error.message
);

if (attempt >= MAX_RETRIES) {
throw new Error('Could not retrieve public IP address after several attempts');
throw new Error(
"Could not retrieve public IP address after several attempts"
);
}

await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY));
Expand Down

0 comments on commit 18fc7f6

Please sign in to comment.