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

feat: add inscription calls and hooks #216

Merged
merged 89 commits into from
Sep 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
89 commits
Select commit Hold shift + click to select a range
17e645f
Merge pull request #85 from secretkeylabs/develop
m-aboelenein Mar 14, 2023
d95fe84
Merge pull request #87 from secretkeylabs/develop
m-aboelenein Mar 15, 2023
70c39a0
feat: implement required calls to inscribe service
victorkirov Jul 20, 2023
6518ca7
feat: do full BRC-20 transfer in 1 call
victorkirov Jul 24, 2023
0fdba23
fix: import/export names
victorkirov Jul 24, 2023
2eb8577
Merge branch 'develop' into victor/eng-2391-implement-1-step-brc-20-t…
victorkirov Jul 24, 2023
e37a7cb
Merge branch 'develop' into victor/eng-2391-implement-1-step-brc-20-t…
victorkirov Jul 24, 2023
b9fa9a5
Merge branch 'develop' into victor/eng-2391-implement-1-step-brc-20-t…
victorkirov Jul 25, 2023
9042413
fix: fee calc and utxo select
victorkirov Jul 25, 2023
2338ee2
fix: type issue
victorkirov Jul 25, 2023
119d0c5
feat: Add all brc-20 api calls
victorkirov Jul 28, 2023
b9ba6be
feat: implement 1 step transfer generator
victorkirov Jul 28, 2023
bcf6606
chore: add btc functions for brc-20 transfer
victorkirov Jul 28, 2023
20be088
chore: add brc-20 tests
victorkirov Jul 31, 2023
6ea85ac
Merge branch 'develop' into victor/eng-2391-implement-1-step-brc-20-t…
victorkirov Jul 31, 2023
3922c4a
Merge branch 'develop' into victor/eng-2391-implement-1-step-brc-20-t…
victorkirov Aug 1, 2023
bded70d
oopsie
victorkirov Aug 1, 2023
1c82cf8
feat: added mint functions and exponential backoff for retries
victorkirov Aug 1, 2023
dea9238
chore: add unit tests for mint
victorkirov Aug 1, 2023
dba4ff4
Merge branch 'develop' of https://github.com/secretkeylabs/xverse-cor…
victorkirov Aug 1, 2023
72f36c4
feat: add react dependencies
victorkirov Aug 2, 2023
7346486
feat: add estimate hook
victorkirov Aug 2, 2023
bc80409
chore: make bignumber a peer dependency
victorkirov Aug 2, 2023
00d07e6
feat: add brc-20 transfer execute hook
victorkirov Aug 2, 2023
cb66891
doc: add docstring for transfer hook
victorkirov Aug 2, 2023
c6f33e9
chore: tie bignumber to v 9
victorkirov Aug 2, 2023
1b2dc62
fix: make arguments an object instead of multiple arguments
victorkirov Aug 2, 2023
4fc3c65
Merge branch 'victor/eng-2391-implement-1-step-brc-20-transfers' into…
victorkirov Aug 2, 2023
e513f75
Change args to obj instead of multiple args
victorkirov Aug 2, 2023
6824de8
chore: move props to object
victorkirov Aug 2, 2023
dd37f98
chore: extract validation to method
victorkirov Aug 2, 2023
853aff1
chore: move validation to function in other hook
victorkirov Aug 2, 2023
f53fe45
chore: make complete flag better
victorkirov Aug 2, 2023
ab22213
chore: use useCallback instead of effect
victorkirov Aug 2, 2023
e8e36ce
fix: the callback dependencies for executing transfer
victorkirov Aug 2, 2023
65ee717
export hooks
victorkirov Aug 2, 2023
bfeee5e
build kick off
victorkirov Aug 2, 2023
ed61b5e
fix: fee calc resulting in fee higher than input
victorkirov Aug 2, 2023
ef77162
fix: tests
victorkirov Aug 2, 2023
476ea75
Merge branch 'victor/eng-2391-implement-1-step-brc-20-transfers' of h…
victorkirov Aug 2, 2023
781d219
allow undefined UTXOs in fee estimate until loaded
victorkirov Aug 2, 2023
5afcfee
fee hook, don't show error code if not initialised
victorkirov Aug 3, 2023
c843127
Add missing return types and add comment
victorkirov Aug 4, 2023
194ca52
Add transfer finalize call
victorkirov Aug 4, 2023
a206594
Merge branch 'develop' into victor/eng-2391-implement-1-step-brc-20-t…
victorkirov Aug 4, 2023
e8a2065
Merge branch 'victor/eng-2391-implement-1-step-brc-20-transfers' of h…
victorkirov Aug 4, 2023
a7fe12b
AAdd finalize call to transfer script and fix tests
victorkirov Aug 4, 2023
369c353
Merge branch 'victor/eng-2391-implement-1-step-brc-20-transfers' of h…
victorkirov Aug 4, 2023
76a9f34
implement CoreError
victorkirov Aug 4, 2023
4d353f1
Fix coreerror import path
victorkirov Aug 4, 2023
ecfa418
Merge branch 'develop' into victor/eng-2391-implement-1-step-brc-20-t…
victorkirov Aug 7, 2023
902b245
Merge branch 'develop' of https://github.com/secretkeylabs/xverse-cor…
victorkirov Aug 7, 2023
05b496a
Merge branch 'develop' of https://github.com/secretkeylabs/xverse-cor…
victorkirov Aug 7, 2023
de4fe55
fix issues form merge
victorkirov Aug 7, 2023
d22ceee
fix: tests
victorkirov Aug 7, 2023
ff56085
Merge branch 'victor/eng-2391-implement-1-step-brc-20-transfers' of h…
victorkirov Aug 7, 2023
21dd726
merge with develop
victorkirov Aug 8, 2023
4e02f5d
Merge branch 'develop' into victor/eng-2540-add-brc-20-transfer-hook-…
victorkirov Aug 9, 2023
d788edf
Merge branch 'develop' into victor/eng-2540-add-brc-20-transfer-hook-…
victorkirov Aug 9, 2023
ca78e37
Merge branch 'develop' into victor/eng-2540-add-brc-20-transfer-hook-…
victorkirov Aug 9, 2023
693f898
Merge branch 'develop' into victor/eng-2540-add-brc-20-transfer-hook-…
victorkirov Aug 10, 2023
6a27680
fix: extract finalize retry to utils
victorkirov Aug 15, 2023
6bb8093
Fix callback deps
victorkirov Aug 15, 2023
c956c0c
Merge branch 'develop' into victor/eng-2540-add-brc-20-transfer-hook-…
victorkirov Aug 15, 2023
d3f8209
fix: remove retry since all txns broadcast in finalise
victorkirov Aug 15, 2023
1799e3f
feat: add inscription logic and react hook
victorkirov Aug 17, 2023
c90e3c3
Merge branch 'develop' into victor/eng-2650-add-inscription-calls-and…
victorkirov Aug 17, 2023
e9bce5b
fix: tests
victorkirov Aug 17, 2023
add5a25
fix import
victorkirov Aug 17, 2023
faa2619
add executing state
victorkirov Aug 17, 2023
b35e4a8
reset error message
victorkirov Aug 17, 2023
43c84d3
set correct max content length
victorkirov Aug 22, 2023
5c4d71b
Merge branch 'develop' into victor/eng-2650-add-inscription-calls-and…
victorkirov Aug 22, 2023
59bc40e
refactor hook error handling
victorkirov Aug 22, 2023
9e43477
fix: ensure we always return a fee estimate
victorkirov Aug 22, 2023
d1a3840
refactor
victorkirov Aug 22, 2023
dbb900b
Rename inscription functions and export
victorkirov Aug 23, 2023
d11ffc5
Merge branch 'main' of https://github.com/secretkeylabs/xverse-core i…
victorkirov Aug 23, 2023
8881d11
Merge branch 'develop' of https://github.com/secretkeylabs/xverse-cor…
victorkirov Aug 23, 2023
dd04f2a
add fee rate validation
victorkirov Aug 23, 2023
b8788b5
add unit tests and corerror test extension
victorkirov Aug 25, 2023
22c3318
add service fee tests
victorkirov Aug 25, 2023
39a9745
complete fee estimate tests
victorkirov Aug 25, 2023
855e6a4
added mint tests
victorkirov Aug 25, 2023
4cc7f1c
cursed inscription avoidance
victorkirov Aug 25, 2023
5f65835
Merge branch 'develop' into victor/eng-2650-add-inscription-calls-and…
victorkirov Aug 25, 2023
8344d56
Merge branch 'develop' into victor/eng-2650-add-inscription-calls-and…
victorkirov Aug 25, 2023
364cd56
Merge branch 'develop' into victor/eng-2650-add-inscription-calls-and…
victorkirov Aug 31, 2023
a028d13
Merge branch 'develop' of https://github.com/secretkeylabs/xverse-cor…
victorkirov Sep 4, 2023
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
14 changes: 10 additions & 4 deletions api/ordinals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,18 @@
return ordinals.sort(sortOrdinalsByConfirmationTime);
}

export async function getOrdinalIdFromUtxo(utxo: UTXO) {
export async function getOrdinalIdsFromUtxo(utxo: UTXO): Promise<string[]> {
const ordinalContentUrl = `${XVERSE_INSCRIBE_URL}/v1/inscriptions/utxo/${utxo.txid}/${utxo.vout}`;

const ordinalIds = await axios.get<string[]>(ordinalContentUrl);
if (ordinalIds.data.length > 0) {
return ordinalIds.data[ordinalIds.data.length - 1];
const { data: ordinalIds } = await axios.get<string[]>(ordinalContentUrl);

return ordinalIds;
}

export async function getOrdinalIdFromUtxo(utxo: UTXO) {
const ordinalIds = await getOrdinalIdsFromUtxo(utxo);
if (ordinalIds.length > 0) {
return ordinalIds[ordinalIds.length - 1];
} else {
return null;
}
Expand All @@ -90,8 +96,8 @@
timeout: 30000,
transformResponse: [(data) => parseOrdinalTextContentData(data)],
})
.then((response) => response!.data)

Check warning on line 99 in api/ordinals.ts

View workflow job for this annotation

GitHub Actions / test

Forbidden non-null assertion
.catch((error) => {

Check warning on line 100 in api/ordinals.ts

View workflow job for this annotation

GitHub Actions / test

'error' is defined but never used
return '';
});
}
Expand Down Expand Up @@ -121,9 +127,9 @@
})
.then((response) => {
if (response.data) {
const responseTokensList = response!.data;

Check warning on line 130 in api/ordinals.ts

View workflow job for this annotation

GitHub Actions / test

Forbidden non-null assertion
const tokensList: Array<FungibleToken> = [];
responseTokensList.forEach((responseToken: any) => {

Check warning on line 132 in api/ordinals.ts

View workflow job for this annotation

GitHub Actions / test

Unexpected any. Specify a different type
const token: FungibleToken = {
name: responseToken.ticker,
balance: responseToken.overallBalance,
Expand All @@ -146,7 +152,7 @@
return [];
}
})
.catch((error) => {

Check warning on line 155 in api/ordinals.ts

View workflow job for this annotation

GitHub Actions / test

'error' is defined but never used
return [];
});
}
Expand All @@ -165,7 +171,7 @@
});
return transactions;
})
.catch((error) => {

Check warning on line 174 in api/ordinals.ts

View workflow job for this annotation

GitHub Actions / test

'error' is defined but never used
return [];
});
}
Expand Down
29 changes: 29 additions & 0 deletions api/xverseInscribe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ import {
Brc20ExecuteOrderResponse,
Brc20FinalizeTransferOrderRequest,
Brc20FinalizeTransferOrderResponse,
InscriptionCostEstimateRequest,
InscriptionCostEstimateResponse,
InscriptionCreateOrderRequest,
InscriptionCreateOrderResponse,
InscriptionExecuteOrderRequest,
InscriptionExecuteOrderResponse,
NetworkType,
} from 'types';

Expand All @@ -18,6 +24,26 @@ const apiClient = axios.create({
baseURL: XVERSE_INSCRIBE_URL,
});

const getInscriptionFeeEstimate = async (
requestBody: InscriptionCostEstimateRequest,
): Promise<InscriptionCostEstimateResponse> => {
const response = await apiClient.post<InscriptionCostEstimateResponse>('/v1/inscriptions/cost-estimate', requestBody);
return response.data;
};

const createInscriptionOrder = async (
requestBody: InscriptionCreateOrderRequest,
): Promise<InscriptionCreateOrderResponse> => {
const response = await apiClient.post<InscriptionCreateOrderResponse>('/v1/inscriptions/place-order', requestBody);
return response.data;
};
const executeInscriptionOrder = async (
requestBody: InscriptionExecuteOrderRequest,
): Promise<InscriptionExecuteOrderResponse> => {
const response = await apiClient.post<InscriptionExecuteOrderResponse>('/v1/inscriptions/execute-order', requestBody);
return response.data;
};

const getBrc20TransferFees = async (
tick: string,
amount: number,
Expand Down Expand Up @@ -169,6 +195,9 @@ const finalizeBrc20TransferOrder = async (
};

export default {
getInscriptionFeeEstimate,
createInscriptionOrder,
executeInscriptionOrder,
getBrc20TransferFees,
createBrc20TransferOrder,
getBrc20MintFees,
Expand Down
2 changes: 2 additions & 0 deletions hooks/brc20/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { default as useBrc20TransferExecute } from './useBrc20TransferExecute';
export { default as useBrc20TransferFees } from './useBrc20TransferFees';
182 changes: 182 additions & 0 deletions hooks/brc20/useBrc20TransferExecute.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
import { useCallback, useState } from 'react';

import { NetworkType, UTXO } from 'types';
import { CoreError } from '../../utils/coreError';

import { BRC20ErrorCode, ExecuteTransferProgressCodes, brc20TransferExecute } from '../../transactions/brc20';

export enum ErrorCode {
INSUFFICIENT_FUNDS = 'INSUFFICIENT_FUNDS',
INVALID_TICK = 'INVALID_TICK',
INVALID_AMOUNT = 'INVALID_AMOUNT',
INVALID_FEE_RATE = 'INVALID_FEE_RATE',
BROADCAST_FAILED = 'BROADCAST_FAILED',
SERVER_ERROR = 'SERVER_ERROR',
}

type Props = {
seedPhrase: string;
accountIndex: number;
addressUtxos: UTXO[];
tick: string;
amount: number;
revealAddress: string;
changeAddress: string;
recipientAddress: string;
feeRate: number;
network: NetworkType;
};

const validateProps = (props: Props) => {
const { addressUtxos, tick, amount, feeRate } = props;

if (!addressUtxos.length) {
return ErrorCode.INSUFFICIENT_FUNDS;
}

if (tick.length !== 4) {
return ErrorCode.INVALID_TICK;
}

if (amount <= 0) {
return ErrorCode.INVALID_AMOUNT;
}

if (feeRate <= 0) {
return ErrorCode.INVALID_FEE_RATE;
}
return undefined;
};

/**
*
* @param seedPhrase - The seed phrase of the wallet
* @param accountIndex - The account index of the seed phrase to use
* @param addressUtxos - The UTXOs in the bitcoin address which will be used for payment
* @param tick - The 4 letter BRC-20 token name
* @param amount - The amount of the BRC-20 token to transfer
* @param revealAddress - The address where the balance of the BRC-20 token lives. This is usually the ordinals address.
* @param changeAddress - The address where change SATS will be sent to. Should be the Bitcoin address of the wallet.
* @param recipientAddress - The address where the BRC-20 tokens will be sent to.
* @param feeRate - The desired fee rate for the transactions
* @param network - The network to broadcast the transactions on (Mainnet or Testnet)
* @returns
*/
const useBrc20TransferExecute = (props: Props) => {
const {
seedPhrase,
accountIndex,
addressUtxos,
tick,
amount,
revealAddress,
changeAddress,
recipientAddress,
feeRate,
network,
} = props;
const [running, setRunning] = useState(false);
const [commitTransactionId, setCommitTransactionId] = useState<string | undefined>();
const [revealTransactionId, setRevealTransactionId] = useState<string | undefined>();
const [transferTransactionId, setTransferTransactionId] = useState<string | undefined>();
const [progress, setProgress] = useState<ExecuteTransferProgressCodes | undefined>();
const [errorCode, setErrorCode] = useState<ErrorCode | undefined>();

const executeTransfer = useCallback(() => {
if (running || !!transferTransactionId) return;

const innerProps = {
seedPhrase,
accountIndex,
addressUtxos,
tick,
amount,
revealAddress,
changeAddress,
recipientAddress,
feeRate,
network,
};

const validationErrorCode = validateProps(innerProps);
setErrorCode(validationErrorCode);

if (validationErrorCode) {
return;
}

// if we get to here, that means that the transfer is valid and we can try to execute it but we don't want to
// be able to accidentally execute it again if something goes wrong, so we set the running flag
setRunning(true);

const runTransfer = async () => {
try {
const transferGenerator = await brc20TransferExecute(innerProps);

let done = false;
do {
const itt = await transferGenerator.next();
done = itt.done ?? false;

if (done) {
const result = itt.value as {
revealTransactionId: string;
commitTransactionId: string;
transferTransactionId: string;
};
setCommitTransactionId(result.commitTransactionId);
setRevealTransactionId(result.revealTransactionId);
setTransferTransactionId(result.transferTransactionId);
setProgress(undefined);
} else {
setProgress(itt.value as ExecuteTransferProgressCodes);
}
} while (!done);
} catch (e) {
let finalErrorCode: string | undefined;
if (CoreError.isCoreError(e)) {
finalErrorCode = e.code;
}

switch (finalErrorCode) {
case BRC20ErrorCode.FAILED_TO_FINALIZE:
setErrorCode(ErrorCode.BROADCAST_FAILED);
break;
case BRC20ErrorCode.INSUFFICIENT_FUNDS:
setErrorCode(ErrorCode.INSUFFICIENT_FUNDS);
break;
default:
setErrorCode(ErrorCode.SERVER_ERROR);
break;
}
}
};

runTransfer();
}, [
seedPhrase,
accountIndex,
addressUtxos,
tick,
amount,
revealAddress,
changeAddress,
recipientAddress,
feeRate,
network,
running,
transferTransactionId,
]);

return {
executeTransfer,
transferTransactionId,
commitTransactionId,
revealTransactionId,
complete: !!transferTransactionId,
progress,
errorCode,
};
};

export default useBrc20TransferExecute;
124 changes: 124 additions & 0 deletions hooks/brc20/useBrc20TransferFees.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { useEffect, useState } from 'react';

import { UTXO } from 'types';
import { brc20TransferEstimateFees } from '../../transactions/brc20';

type CommitValueBreakdown = {
commitChainFee: number;
revealChainFee: number;
revealServiceFee: number;
transferChainFee: number;
transferUtxoValue: number;
};

export enum ErrorCode {
UTXOS_MISSING = 'UTXOS_MISSING',
INSUFFICIENT_FUNDS = 'INSUFFICIENT_FUNDS',
INVALID_TICK = 'INVALID_TICK',
INVALID_AMOUNT = 'INVALID_AMOUNT',
INVALID_FEE_RATE = 'INVALID_FEE_RATE',
SERVER_ERROR = 'SERVER_ERROR',
}

type Props = {
addressUtxos: UTXO[] | undefined;
tick: string;
amount: number;
feeRate: number;
revealAddress: string;
};

const validateProps = (props: Props) => {
const { addressUtxos, tick, amount, feeRate } = props;

if (!addressUtxos) {
return ErrorCode.UTXOS_MISSING;
}

if (!addressUtxos.length) {
return ErrorCode.INSUFFICIENT_FUNDS;
}

if (tick.length !== 4) {
return ErrorCode.INVALID_TICK;
}

if (amount <= 0) {
return ErrorCode.INVALID_AMOUNT;
}

if (feeRate <= 0) {
return ErrorCode.INVALID_FEE_RATE;
}

return null;
};

/**
* Estimates the fees for a BRC-20 1-step transfer
* @param addressUtxos - The UTXOs in the bitcoin address which will be used for payment
* @param tick - The 4 letter BRC-20 token name
* @param amount - The amount of the BRC-20 token to transfer
* @param feeRate - The desired fee rate for the transactions
* @param revealAddress - The address where the balance of the BRC-20 token lives. This is usually the ordinals address.
*/
const useBrc20TransferFees = (props: Props) => {
const { addressUtxos, tick, amount, feeRate, revealAddress } = props;
const [commitValue, setCommitValue] = useState<number | undefined>();
const [commitValueBreakdown, setCommitValueBreakdown] = useState<CommitValueBreakdown | undefined>();
const [isInitialised, setIsInitialised] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [errorCode, setErrorCode] = useState<ErrorCode | undefined>();

useEffect(() => {
const validationErrorCode = validateProps(props);

if (validationErrorCode) {
setErrorCode(validationErrorCode);

if (validationErrorCode !== ErrorCode.UTXOS_MISSING) {
setIsInitialised(true);
}

return;
}

setIsInitialised(true);
setIsLoading(true);
setErrorCode(undefined);

const runEstimate = async () => {
try {
const result = await brc20TransferEstimateFees({
addressUtxos: addressUtxos!,
tick,
amount,
revealAddress,
feeRate,
});
setCommitValue(result.commitValue);
setCommitValueBreakdown(result.valueBreakdown);
} catch (e) {
if (e.message === 'Not enough funds at selected fee rate') {
setErrorCode(ErrorCode.INSUFFICIENT_FUNDS);
} else {
setErrorCode(ErrorCode.SERVER_ERROR);
}
}

setIsLoading(false);
};

runEstimate();
}, [addressUtxos, tick, amount, revealAddress, feeRate]);

return {
commitValue,
commitValueBreakdown,
isLoading,
errorCode: isInitialised ? errorCode : undefined,
isInitialised,
};
};

export default useBrc20TransferFees;
2 changes: 2 additions & 0 deletions hooks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './brc20';
export * from './inscriptions';
2 changes: 2 additions & 0 deletions hooks/inscriptions/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { default as useInscriptionExecute } from './useInscriptionExecute';
export { default as useInscriptionFees } from './useInscriptionFees';
Loading
Loading