Skip to content
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
1 change: 1 addition & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ NEXT_PUBLIC_REGTEST_DEVNET_ARES_URL=https://bitcoin-api-gateway-regtest-devnet.z

NEXT_PUBLIC_REGTEST_DEVNET_AEGLE_URL=https://api-regtest-devnet.apollobyzeus.space

NEXT_PUBLIC_DEVNET_REDEEM_ADDRESS=Acjdw9qoQnDZMgEMtN5UvPFHQdg9zoWwKX3Tc3QQHpy8
19 changes: 19 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
NEXT_PUBLIC_SOLANA_DEVNET_RPC=

SOLANA_DEVNET_RPC=

NEXT_PUBLIC_ZEUS_SCAN_URL=

NEXT_PUBLIC_REGTEST_BITCOIN_EXPLORER_URL=

NEXT_PUBLIC_DEVNET_BOOTSTRAPPER_PROGRAM_ID=

NEXT_PUBLIC_REGTEST_DEVNET_TWO_WAY_PEG_GUARDIAN_SETTING=

NEXT_PUBLIC_REGTEST_DEVNET_HERMES_URL=

NEXT_PUBLIC_REGTEST_DEVNET_ARES_URL=

NEXT_PUBLIC_REGTEST_DEVNET_AEGLE_URL=

NEXT_PUBLIC_DEVNET_REDEEM_ADDRESS=2wUg8UcNpbSWr8p3VKkXKmZYVonHX6Y3tKpuPXgp5czq
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,4 @@ next-env.d.ts

# Sentry Config File
.env.sentry-build-plugin
.env
65 changes: 53 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -249,15 +249,6 @@ export default function getTransactions({ solanaPubkey, bitcoinWallet }) {
const { feeRate } = useTwoWayPegConfiguration();
const config = useNetworkConfig();

// Fetch deposit transactions (combined onchain transaction and transaction in browser indexed db)
const { combinedInteractions: depositTransactions } =
useDepositInteractionsWithCache({
solanaAddress: solanaPubkey?.toBase58(),
bitcoinXOnlyPubkey: bitcoinWallet
? toXOnly(Buffer.from(bitcoinWallet.pubkey, "hex")).toString("hex")
: undefined,
});

// Fetch withdrawal transactions
const {
data: withdrawalTransactions,
Expand Down Expand Up @@ -309,6 +300,14 @@ export default function getTransactions({ solanaPubkey, bitcoinWallet }) {
// withdrawal_request_pda: "YOUR_WITHDRAWAL_REQUEST_PDA"

// Or you can specify the interaction id and fetch the interaction detail from our indexer API
const { combinedInteractions: depositTransactions } =
useDepositInteractionsWithCache({
solanaAddress: solanaPubkey?.toBase58(),
bitcoinXOnlyPubkey: bitcoinWallet
? toXOnly(Buffer.from(bitcoinWallet.pubkey, "hex")).toString("hex")
: undefined,
});

const targetTx = depositTransactions[0]; // choose the first transaction as example
const interactionSteps = await hermesFetcher(
`/api/v1/raw/layer/interactions/${targetTx.interaction_id}/steps`,
Expand Down Expand Up @@ -343,7 +342,7 @@ The ZPL defines several interaction types and statuses to track the progress of

1. `BitcoinDepositToHotReserve`: Initial deposit detected on Bitcoin network from user address to our hot reserve address
2. `VerifyDepositToHotReserveTransaction`: BitcoinSPV program verifying the deposit transaction
3. `SolanaDepositToHotReserve`: Deposit confirmed on Solana by TwoWayPeg program
3. `SolanaDepositToHotReserve`: Deposit confirmed and updated status on Solana by TwoWayPeg program
4. `AddLockToColdReserveProposal`: Zeus node send transaction to move BTC from hot reserve to cold reserve
5. `BitcoinLockToColdReserve`: Move BTC from hot reserve to cold reserve transaction is observed on Bitcoin network
6. `VerifyLockToColdReserveTransaction`: BitcoinSPV program verifying cold reserve transaction
Expand Down Expand Up @@ -577,7 +576,9 @@ The ZPL provides functionality for users to store zBTC in a custodial vault and

![Retrive](./public/graphics/retrieve.png)

Orpheus allows you to modify the retrieval address to designate an alternative Escrow token account managed by your application. By implementing this change, redemption transactions initiated by users of your application will direct funds to the application-controlled escrow rather than the user’s individual wallet. This functionality unlocks a range of decentralized finance (DeFi) use cases, including money markets, neutral trading strategies, liquidity provisioning, or the development of a Bitcoin-backed stablecoin.
By flexibly cascading sdk in Orpheus, you can set custom retrieval address to designate an alternative Escrow token account managed by your application. By implementinng this operation, redemption transactions initiated by users of your application will go to the application-controlled escrow rather than the user’s individual wallet. This functionality unlocks a range of decentralized finance (DeFi) use cases, including money markets, neutral trading strategies, liquidity provisioning, or the development of a Bitcoin-backed stablecoin.

Below is a sample implementation of creating retrieve instruction:

```typescript
constructRetrieveIx(
Expand Down Expand Up @@ -622,25 +623,34 @@ For guidance on constructing a Solana escrow, developers may consult reference i
- https://github.com/ironaddicteddog/anchor-escrow
- https://github.com/deanmlittle/native-escrow-2024

Then by ingeeniously cascade a transfer instruction, you can redeem the zBTC to a custodial escrow.

#### Implementation

```typescript
import { useState } from "react";
import { useWallet } from "@solana/wallet-adapter-react";
import { useWallet, useConnection } from "@solana/wallet-adapter-react";
import { useZplClient } from "@/contexts/ZplClientProvider";
import usePositions from "@/hooks/zpl/usePositions";
import { useNetworkConfig } from "@/hooks/misc/useNetworkConfig";
import { PublicKey, TransactionInstruction } from "@solana/web3.js";
import BigNumber from "bignumber.js";
import { BN } from "bn.js";
import { BTC_DECIMALS } from "@/utils/constant";
import {
getAssociatedTokenAddressSync,
createTransferInstruction,
createAssociatedTokenAccountInstruction,
} from "@solana/spl-token";

export default function Home() {
const [redeemAmount, setRedeemAmount] = useState(0);

const config = useNetworkConfig();
const zplClient = useZplClient();
const { publicKey: solanaPubkey } = useWallet();
const { data: positions } = usePositions(solanaPubkey);
const { connection } = useConnection();

const handleRedeem = async () => {
if (!redeemAmount || !zplClient) return;
Expand Down Expand Up @@ -680,6 +690,37 @@ export default function Home() {
);

ixs.push(retrieveIx);

const escrow_address = process.env.NEXT_PUBLIC_ESCROW_ADDRESS; // set your escrow address here or other method you like
if(escrow_address) {
const targetAddress = new PublicKey(escrow_address);
const toATA = getAssociatedTokenAddressSync(
new PublicKey(config.assetMint),
targetAddress,
true // allow off curve to approve PDA
);
// check if the associated token account of target address initialized
const info = await connection.getAccountInfo(toATA);
if (!info) {
// if not, create one
const createIx = createAssociatedTokenAccountInstruction(
solanaPubkey,
toATA,
targetAddress,
new PublicKey(config.assetMint)
);
ixs.push(createIx);
}
// add a transfer instruction to transfer the tokens to the receive_address
const transferIx = createTransferInstruction(
receiverAta,
toATA,
solanaPubkey,
BigInt(amountToRedeem.toString())
);
ixs.push(transferIx);
}

remainingAmount = remainingAmount.sub(amountToRedeem);

if (remainingAmount.eq(new BN(0))) break;
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": "orpheus",
"version": "0.1.2",
"version": "0.1.3",
"engines": {
"node": "20.18.3"
},
Expand Down
45 changes: 41 additions & 4 deletions src/components/PortfolioV2/Modals/Redeem.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { captureException } from "@sentry/nextjs";
import { getAssociatedTokenAddressSync } from "@solana/spl-token";
import {
getAssociatedTokenAddressSync,
createTransferInstruction,
createAssociatedTokenAccountInstruction,
} from "@solana/spl-token";
import { WalletSignTransactionError } from "@solana/wallet-adapter-base";
import { useWallet } from "@solana/wallet-adapter-react";
import { useWallet, useConnection } from "@solana/wallet-adapter-react";
import { PublicKey, TransactionInstruction } from "@solana/web3.js";
import BigNumber from "bignumber.js";
import { BN } from "bn.js";
Expand Down Expand Up @@ -63,6 +67,7 @@ export default function RedeemModal({
const { publicKey: solanaPubkey } = useWallet();
const { mutate: mutateBalance } = useBalance(solanaPubkey);
const { mutate: mutatePositions } = usePositions(solanaPubkey);
const { connection } = useConnection();

const [isRedeeming, setIsRedeeming] = useState(false);
const [redeemAmount, setRedeemAmount] = useState("");
Expand Down Expand Up @@ -143,7 +148,6 @@ export default function RedeemModal({
if (!twoWayPegGuardianSetting)
throw new Error("Two way peg guardian setting not found");

// TODO: You can customize the retrieve address here
const receiverAta = getAssociatedTokenAddressSync(
new PublicKey(config.assetMint),
solanaPubkey,
Expand All @@ -155,8 +159,40 @@ export default function RedeemModal({
new PublicKey(twoWayPegGuardianSetting),
receiverAta
);

ixs.push(retrieveIx);

// TODO: You can customize the retrieve address here
if (process.env.NEXT_PUBLIC_DEVNET_REDEEM_ADDRESS) {
const targetAddress = new PublicKey(
process.env.NEXT_PUBLIC_DEVNET_REDEEM_ADDRESS
);
const toATA = getAssociatedTokenAddressSync(
new PublicKey(config.assetMint),
targetAddress,
true
);
// check if the target address has an associated token account
const info = await connection.getAccountInfo(toATA);
if (!info) {
// if not, create one
const createIx = createAssociatedTokenAccountInstruction(
solanaPubkey,
toATA,
targetAddress,
new PublicKey(config.assetMint)
);
ixs.push(createIx);
}
// add a transfer instruction to transfer the tokens to the receive_address
const transferIx = createTransferInstruction(
receiverAta,
toATA,
solanaPubkey,
BigInt(amountToRedeem.toString())
);
ixs.push(transferIx);
}

remainingAmount = remainingAmount.sub(amountToRedeem);

if (remainingAmount.eq(new BN(0))) break;
Expand All @@ -179,6 +215,7 @@ export default function RedeemModal({
} else {
notifyError("Error in redeeming, please try again");
captureException(error);
console.error("Error in redeeming", error);
}
} finally {
setIsRedeeming(false);
Expand Down
4 changes: 2 additions & 2 deletions src/utils/misc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ export const getSolanaExplorerUrl = (

switch (solanaNetwork) {
case SolanaNetwork.Devnet:
return `https://solana.fm/${type}/${target}?cluster=devnet-alpha`;
return `https://explorer.solana.com/${type}/${target}?cluster=devnet`;
default:
return `https://solana.fm/${type}/${target}?cluster=devnet-alpha`;
return `https://explorer.solana.com/${type}/${target}`;
}
};

Expand Down
2 changes: 1 addition & 1 deletion src/utils/notification.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const TxSuccessMsg = ({
</div>
{txId && solanaNetwork ? (
<div className="toast-message">
Transaction has been processed. View the details on SolanaFM.
Transaction has been processed. View the details on Solana Explorer.
<br />
<Link
href={getSolanaExplorerUrl(solanaNetwork, "tx", txId)}
Expand Down