sdk: move terra functions to sdk
Change-Id: Idb255bc0c63189f69f5e79efce4a801e2aeedf46
This commit is contained in:
parent
cea46cdfbe
commit
2a317ab923
|
@ -10,6 +10,7 @@ import { zeroPad } from "ethers/lib/utils";
|
|||
import { useCallback, useEffect, useRef } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useEthereumProvider } from "../../contexts/EthereumProviderContext";
|
||||
import { useConnectedWallet } from "@terra-money/wallet-provider";
|
||||
import { useSolanaWallet } from "../../contexts/SolanaWalletContext";
|
||||
import {
|
||||
selectTransferAmount,
|
||||
|
@ -58,6 +59,7 @@ function Send() {
|
|||
const isSendComplete = useSelector(selectTransferIsSendComplete);
|
||||
const { signer, signerAddress } = useEthereumProvider();
|
||||
const { wallet } = useSolanaWallet();
|
||||
const terraWallet = useConnectedWallet();
|
||||
const solPK = wallet?.publicKey;
|
||||
const sourceParsedTokenAccount = useSelector(
|
||||
selectTransferSourceParsedTokenAccount
|
||||
|
@ -154,7 +156,13 @@ function Send() {
|
|||
(async () => {
|
||||
dispatch(setIsSending(true));
|
||||
try {
|
||||
const vaaBytes = await transferFromTerra(undefined);
|
||||
const vaaBytes = await transferFromTerra(
|
||||
terraWallet,
|
||||
sourceAsset,
|
||||
amount,
|
||||
"",
|
||||
targetChain,
|
||||
);
|
||||
console.log("bytes in transfer", vaaBytes);
|
||||
vaaBytes && dispatch(setSignedVAAHex(uint8ArrayToHex(vaaBytes)));
|
||||
} catch (e) {
|
||||
|
|
|
@ -31,7 +31,7 @@ function Source() {
|
|||
const sourceAsset = useSelector(selectTransferSourceAsset);
|
||||
const uiAmountString = useSelector(selectTransferSourceBalanceString);
|
||||
const amount = useSelector(selectTransferAmount);
|
||||
const isSourceComplete = useSelector(selectTransferIsSourceComplete);
|
||||
const isSourceComplete = true; // useSelector(selectTransferIsSourceComplete);
|
||||
const shouldLockFields = useSelector(selectTransferShouldLockFields);
|
||||
const handleSourceChange = useCallback(
|
||||
(event) => {
|
||||
|
|
|
@ -1,17 +1,19 @@
|
|||
import {
|
||||
attestFromEth as attestEthTx,
|
||||
attestFromSolana as attestSolanaTx,
|
||||
attestFromTerra as attestTerraTx,
|
||||
CHAIN_ID_ETH,
|
||||
CHAIN_ID_SOLANA,
|
||||
CHAIN_ID_TERRA,
|
||||
getEmitterAddressEth,
|
||||
getEmitterAddressSolana,
|
||||
getEmitterAddressTerra,
|
||||
parseSequenceFromLogEth,
|
||||
parseSequenceFromLogSolana,
|
||||
parseSequenceFromLogTerra,
|
||||
} from "@certusone/wormhole-sdk";
|
||||
import Wallet from "@project-serum/sol-wallet-adapter";
|
||||
import { Connection } from "@solana/web3.js";
|
||||
import { MsgExecuteContract } from "@terra-money/terra.js";
|
||||
import { ConnectedWallet as TerraConnectedWallet } from "@terra-money/wallet-provider";
|
||||
import { ethers } from "ethers";
|
||||
import {
|
||||
|
@ -24,6 +26,7 @@ import {
|
|||
} from "./consts";
|
||||
import { getSignedVAAWithRetry } from "./getSignedVAAWithRetry";
|
||||
import { signSendConfirmAndGet } from "./solana";
|
||||
import { waitForTerraExecution } from "./terra";
|
||||
|
||||
export async function attestFromEth(
|
||||
signer: ethers.Signer | undefined,
|
||||
|
@ -80,24 +83,21 @@ export async function attestFromTerra(
|
|||
wallet: TerraConnectedWallet | undefined,
|
||||
asset: string | undefined
|
||||
) {
|
||||
const nonceConst = Math.random() * 100000;
|
||||
const nonceBuffer = Buffer.alloc(4);
|
||||
nonceBuffer.writeUInt32LE(nonceConst, 0);
|
||||
wallet &&
|
||||
(await wallet.post({
|
||||
msgs: [
|
||||
new MsgExecuteContract(
|
||||
wallet.terraAddress,
|
||||
TERRA_TOKEN_BRIDGE_ADDRESS,
|
||||
{
|
||||
register_asset_hook: {
|
||||
asset_id: asset,
|
||||
},
|
||||
},
|
||||
{ uluna: 1000 }
|
||||
),
|
||||
],
|
||||
memo: "Create Wrapped",
|
||||
}));
|
||||
return null;
|
||||
if (!wallet || !asset) return;
|
||||
const infoMaybe = await attestTerraTx(
|
||||
TERRA_TOKEN_BRIDGE_ADDRESS,
|
||||
wallet,
|
||||
asset
|
||||
);
|
||||
const info = await waitForTerraExecution(wallet, infoMaybe);
|
||||
const sequence = parseSequenceFromLogTerra(info);
|
||||
const emitterAddress = await getEmitterAddressTerra(
|
||||
TERRA_TOKEN_BRIDGE_ADDRESS
|
||||
);
|
||||
const result = await getSignedVAAWithRetry(
|
||||
CHAIN_ID_TERRA,
|
||||
emitterAddress,
|
||||
sequence
|
||||
);
|
||||
return result && result.vaaBytes;
|
||||
}
|
||||
|
|
|
@ -2,12 +2,11 @@ import {
|
|||
postVaaSolana,
|
||||
createWrappedOnEth as createWrappedOnEthTx,
|
||||
createWrappedOnSolana as createWrappedOnSolanaTx,
|
||||
createWrappedOnTerra as createWrappedOnTerraTx,
|
||||
} from "@certusone/wormhole-sdk";
|
||||
import Wallet from "@project-serum/sol-wallet-adapter";
|
||||
import { Connection } from "@solana/web3.js";
|
||||
import { ethers } from "ethers";
|
||||
import { fromUint8Array } from "js-base64";
|
||||
import { MsgExecuteContract } from "@terra-money/terra.js";
|
||||
import { ConnectedWallet as TerraConnectedWallet } from "@terra-money/wallet-provider";
|
||||
import {
|
||||
ETH_TOKEN_BRIDGE_ADDRESS,
|
||||
|
@ -31,26 +30,12 @@ export async function createWrappedOnTerra(
|
|||
wallet: TerraConnectedWallet | undefined,
|
||||
signedVAA: Uint8Array
|
||||
) {
|
||||
console.log("creating wrapped");
|
||||
console.log("PROGRAM:", TERRA_TOKEN_BRIDGE_ADDRESS);
|
||||
console.log("BRIDGE:", TERRA_BRIDGE_ADDRESS);
|
||||
console.log("VAA:", signedVAA);
|
||||
wallet &&
|
||||
(await wallet.post({
|
||||
msgs: [
|
||||
new MsgExecuteContract(
|
||||
wallet.terraAddress,
|
||||
TERRA_TOKEN_BRIDGE_ADDRESS,
|
||||
{
|
||||
submit_vaa: {
|
||||
data: fromUint8Array(signedVAA),
|
||||
},
|
||||
},
|
||||
{ uluna: 1000 }
|
||||
),
|
||||
],
|
||||
memo: "Create Wrapped",
|
||||
}));
|
||||
if (!wallet) return;
|
||||
await createWrappedOnTerraTx(
|
||||
TERRA_TOKEN_BRIDGE_ADDRESS,
|
||||
wallet,
|
||||
signedVAA
|
||||
);
|
||||
}
|
||||
|
||||
export async function createWrappedOnSolana(
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
ETH_TOKEN_BRIDGE_ADDRESS,
|
||||
SOLANA_HOST,
|
||||
SOL_TOKEN_BRIDGE_ADDRESS,
|
||||
TERRA_TEST_TOKEN_ADDRESS,
|
||||
} from "./consts";
|
||||
|
||||
export interface StateSafeWormholeWrappedInfo {
|
||||
|
@ -57,7 +58,7 @@ export async function getOriginalAssetTerra(
|
|||
mintAddress: string
|
||||
): Promise<StateSafeWormholeWrappedInfo> {
|
||||
return {
|
||||
assetAddress: "",
|
||||
assetAddress: TERRA_TEST_TOKEN_ADDRESS,
|
||||
chainId: 3,
|
||||
isWrapped: false,
|
||||
};
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
import { TxResult, ConnectedWallet as TerraConnectedWallet } from "@terra-money/wallet-provider";
|
||||
import { TxInfo, LCDClient } from "@terra-money/terra.js";
|
||||
|
||||
// TODO: Loop txInfo for timed out transactions.
|
||||
// lcd.tx.txInfo(transaction.result.txhash);
|
||||
export async function waitForTerraExecution(
|
||||
wallet: TerraConnectedWallet,
|
||||
transaction: TxResult
|
||||
) {
|
||||
const lcd = new LCDClient({
|
||||
URL: wallet.network.lcd,
|
||||
chainID: "columbus-4",
|
||||
});
|
||||
return transaction;
|
||||
}
|
|
@ -7,12 +7,15 @@ import {
|
|||
getEmitterAddressSolana,
|
||||
parseSequenceFromLogEth,
|
||||
parseSequenceFromLogSolana,
|
||||
parseSequenceFromLogTerra,
|
||||
transferFromEth as transferFromEthTx,
|
||||
transferFromSolana as transferFromSolanaTx,
|
||||
} from "@certusone/wormhole-sdk";
|
||||
import { Wallet as TerraWallet } from "@terra-money/wallet-provider";
|
||||
import { fromUint8Array } from 'js-base64';
|
||||
import { ConnectedWallet as TerraConnectedWallet, TxResult } from "@terra-money/wallet-provider";
|
||||
import Wallet from "@project-serum/sol-wallet-adapter";
|
||||
import { Connection } from "@solana/web3.js";
|
||||
import { MsgExecuteContract } from "@terra-money/terra.js";
|
||||
import { ethers } from "ethers";
|
||||
import { arrayify, parseUnits, zeroPad } from "ethers/lib/utils";
|
||||
import { hexToUint8Array } from "./array";
|
||||
|
@ -22,6 +25,8 @@ import {
|
|||
SOLANA_HOST,
|
||||
SOL_BRIDGE_ADDRESS,
|
||||
SOL_TOKEN_BRIDGE_ADDRESS,
|
||||
TERRA_TOKEN_BRIDGE_ADDRESS,
|
||||
TERRA_TEST_TOKEN_ADDRESS,
|
||||
} from "./consts";
|
||||
import { getSignedVAAWithRetry } from "./getSignedVAAWithRetry";
|
||||
import { signSendConfirmAndGet } from "./solana";
|
||||
|
@ -114,7 +119,44 @@ export async function transferFromSolana(
|
|||
}
|
||||
|
||||
export async function transferFromTerra(
|
||||
wallet: TerraWallet | undefined,
|
||||
wallet: TerraConnectedWallet | undefined,
|
||||
asset: string,
|
||||
amount: string,
|
||||
targetAddressStr: string | undefined,
|
||||
targetChain: ChainId,
|
||||
) {
|
||||
return null;
|
||||
if (!wallet) return;
|
||||
const result: TxResult = wallet && await wallet.post({
|
||||
msgs: [
|
||||
new MsgExecuteContract(
|
||||
wallet.terraAddress,
|
||||
TERRA_TOKEN_BRIDGE_ADDRESS,
|
||||
{
|
||||
initiate_transfer: {
|
||||
asset: TERRA_TEST_TOKEN_ADDRESS,
|
||||
amount: amount,
|
||||
recipient_chain: targetChain,
|
||||
recipient: targetAddressStr,
|
||||
fee: 1000,
|
||||
nonce: 0,
|
||||
},
|
||||
},
|
||||
{ uluna: 1000 }
|
||||
),
|
||||
],
|
||||
memo: "Complete Transfer",
|
||||
});
|
||||
console.log(result);
|
||||
const sequence = parseSequenceFromLogTerra(result);
|
||||
console.log(sequence);
|
||||
const emitterAddress = await getEmitterAddressSolana(
|
||||
SOL_TOKEN_BRIDGE_ADDRESS
|
||||
);
|
||||
console.log(emitterAddress);
|
||||
const { vaaBytes } = await getSignedVAAWithRetry(
|
||||
CHAIN_ID_TERRA,
|
||||
emitterAddress,
|
||||
sequence
|
||||
);
|
||||
return vaaBytes;
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -38,6 +38,7 @@
|
|||
"@typechain/ethers-v5": "^7.0.1",
|
||||
"@types/long": "^4.0.1",
|
||||
"@types/node": "^16.6.1",
|
||||
"@types/react": "^17.0.19",
|
||||
"copy-dir": "^1.3.0",
|
||||
"ethers": "^5.4.4",
|
||||
"prettier": "^2.3.2",
|
||||
|
@ -50,6 +51,9 @@
|
|||
"@project-serum/sol-wallet-adapter": "^0.2.5",
|
||||
"@solana/spl-token": "^0.1.8",
|
||||
"@solana/web3.js": "^1.24.0",
|
||||
"@terra-money/terra.js": "^1.8.10",
|
||||
"@terra-money/wallet-provider": "^1.2.4",
|
||||
"js-base64": "^3.6.1",
|
||||
"protobufjs": "^6.11.2",
|
||||
"rxjs": "^7.3.0"
|
||||
}
|
||||
|
|
|
@ -13,3 +13,9 @@ export async function getEmitterAddressSolana(programAddress: string) {
|
|||
zeroPad(new PublicKey(emitter_address(programAddress)).toBytes(), 32)
|
||||
).toString("hex");
|
||||
}
|
||||
|
||||
export async function getEmitterAddressTerra(programAddress: string) {
|
||||
// Testnet Hardcoded
|
||||
// TODO: HumanAddr -> CanonicalAddr
|
||||
return "000000000000000000000000784999135aaa8a3ca5914468852fdddbddd8789d";
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { TransactionResponse } from "@solana/web3.js";
|
||||
import { ContractReceipt } from "ethers";
|
||||
import { TxResult } from "@terra-money/wallet-provider";
|
||||
import { Implementation__factory } from "../ethers-contracts";
|
||||
|
||||
export function parseSequenceFromLogEth(
|
||||
|
@ -17,6 +18,24 @@ export function parseSequenceFromLogEth(
|
|||
return sequence.toString();
|
||||
}
|
||||
|
||||
export function parseSequenceFromLogTerra(result: TxResult): string {
|
||||
// Scan for the Sequence attribute in all the outputs of the transaction.
|
||||
// TODO: Make this not horrible.
|
||||
let sequence = "";
|
||||
const jsonLog = JSON.parse(result.result.raw_log);
|
||||
jsonLog.map((row: any) => {
|
||||
row.events.map((event: any) => {
|
||||
event.attributes.map((attribute: any) => {
|
||||
if(attribute.key === "message.sequence") {
|
||||
sequence = attribute.value;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
console.log('Terra Sequence: ', sequence);
|
||||
return sequence.toString();
|
||||
}
|
||||
|
||||
const SOLANA_SEQ_LOG = "Program log: Sequence: ";
|
||||
export function parseSequenceFromLogSolana(info: TransactionResponse) {
|
||||
// TODO: better parsing, safer
|
||||
|
|
|
@ -3,6 +3,8 @@ import { ethers } from "ethers";
|
|||
import { Bridge__factory } from "../ethers-contracts";
|
||||
import { getBridgeFeeIx, ixFromRust } from "../solana";
|
||||
import { createNonce } from "../utils/createNonce";
|
||||
import { ConnectedWallet as TerraConnectedWallet } from "@terra-money/wallet-provider";
|
||||
import { MsgExecuteContract } from "@terra-money/terra.js";
|
||||
|
||||
export async function attestFromEth(
|
||||
tokenBridgeAddress: string,
|
||||
|
@ -15,6 +17,30 @@ export async function attestFromEth(
|
|||
return receipt;
|
||||
}
|
||||
|
||||
export async function attestFromTerra(
|
||||
tokenBridgeAddress: string,
|
||||
wallet: TerraConnectedWallet,
|
||||
asset: string,
|
||||
) {
|
||||
const nonce = Math.round(Math.random() * 100000);
|
||||
return await wallet.post({
|
||||
msgs: [
|
||||
new MsgExecuteContract(
|
||||
wallet.terraAddress,
|
||||
tokenBridgeAddress,
|
||||
{
|
||||
create_asset_meta: {
|
||||
asset_address: asset,
|
||||
nonce: nonce,
|
||||
},
|
||||
},
|
||||
{ uluna: 10000 }
|
||||
),
|
||||
],
|
||||
memo: "Create Wrapped",
|
||||
});
|
||||
}
|
||||
|
||||
export async function attestFromSolana(
|
||||
connection: Connection,
|
||||
bridgeAddress: string,
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
import { Connection, PublicKey, Transaction } from "@solana/web3.js";
|
||||
import { ethers } from "ethers";
|
||||
import { Bridge__factory } from "../ethers-contracts";
|
||||
import { ConnectedWallet as TerraConnectedWallet } from "@terra-money/wallet-provider";
|
||||
import { MsgExecuteContract } from "@terra-money/terra.js";
|
||||
import { ixFromRust } from "../solana";
|
||||
import { fromUint8Array } from "js-base64";
|
||||
|
||||
export async function createWrappedOnEth(
|
||||
tokenBridgeAddress: string,
|
||||
|
@ -14,6 +17,31 @@ export async function createWrappedOnEth(
|
|||
return receipt;
|
||||
}
|
||||
|
||||
export async function createWrappedOnTerra(
|
||||
tokenBridgeAddress: string,
|
||||
wallet: TerraConnectedWallet,
|
||||
signedVAA: Uint8Array,
|
||||
) {
|
||||
console.log(tokenBridgeAddress);
|
||||
console.log(wallet.terraAddress);
|
||||
console.log(signedVAA);
|
||||
await wallet.post({
|
||||
msgs: [
|
||||
new MsgExecuteContract(
|
||||
wallet.terraAddress,
|
||||
tokenBridgeAddress,
|
||||
{
|
||||
submit_vaa: {
|
||||
data: fromUint8Array(signedVAA),
|
||||
},
|
||||
},
|
||||
{ uluna: 1000 }
|
||||
),
|
||||
],
|
||||
memo: "Create Wrapped",
|
||||
});
|
||||
}
|
||||
|
||||
export async function createWrappedOnSolana(
|
||||
connection: Connection,
|
||||
bridgeAddress: string,
|
||||
|
|
|
@ -2,6 +2,7 @@ import { Connection, PublicKey } from "@solana/web3.js";
|
|||
import { ethers } from "ethers";
|
||||
import { Bridge__factory } from "../ethers-contracts";
|
||||
import { ChainId } from "../utils";
|
||||
import { ConnectedWallet as TerraConnectedWallet } from "@terra-money/wallet-provider";
|
||||
|
||||
/**
|
||||
* Returns a foreign asset address on Ethereum for a provided native chain and asset address, AddressZero if it does not exist
|
||||
|
@ -25,6 +26,15 @@ export async function getForeignAssetEth(
|
|||
}
|
||||
}
|
||||
|
||||
export async function getForeignAssetTerra(
|
||||
tokenBridgeAddress: string,
|
||||
wallet: TerraConnectedWallet,
|
||||
originChain: ChainId,
|
||||
originAsset: Uint8Array
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a foreign asset address on Solana for a provided native chain and asset address
|
||||
* @param connection
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { Connection, PublicKey } from "@solana/web3.js";
|
||||
import { ethers } from "ethers";
|
||||
import { Bridge__factory } from "../ethers-contracts";
|
||||
import { ConnectedWallet as TerraConnectedWallet } from "@terra-money/wallet-provider";
|
||||
|
||||
/**
|
||||
* Returns whether or not an asset address on Ethereum is a wormhole wrapped asset
|
||||
|
@ -19,6 +20,14 @@ export async function getIsWrappedAssetEth(
|
|||
return await tokenBridge.isWrappedAsset(assetAddress);
|
||||
}
|
||||
|
||||
export async function getIsWrappedAssetTerra(
|
||||
tokenBridgeAddress: string,
|
||||
wallet: TerraConnectedWallet,
|
||||
assetAddress: string
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not an asset on Solana is a wormhole wrapped asset
|
||||
* @param connection
|
||||
|
|
|
@ -2,8 +2,9 @@ import { Connection, PublicKey } from "@solana/web3.js";
|
|||
import { ethers } from "ethers";
|
||||
import { arrayify } from "ethers/lib/utils";
|
||||
import { TokenImplementation__factory } from "../ethers-contracts";
|
||||
import { ChainId, CHAIN_ID_ETH, CHAIN_ID_SOLANA } from "../utils";
|
||||
import { ChainId, CHAIN_ID_ETH, CHAIN_ID_SOLANA, CHAIN_ID_TERRA } from "../utils";
|
||||
import { getIsWrappedAssetEth } from "./getIsWrappedAsset";
|
||||
import { ConnectedWallet as TerraConnectedWallet } from "@terra-money/wallet-provider";
|
||||
|
||||
export interface WormholeWrappedInfo {
|
||||
isWrapped: boolean;
|
||||
|
@ -48,6 +49,18 @@ export async function getOriginalAssetEth(
|
|||
};
|
||||
}
|
||||
|
||||
export async function getOriginalAssetTerra(
|
||||
tokenBridgeAddress: string,
|
||||
wallet: TerraConnectedWallet,
|
||||
wrappedAddress: string
|
||||
): Promise<WormholeWrappedInfo> {
|
||||
return {
|
||||
isWrapped: false,
|
||||
chainId: CHAIN_ID_TERRA,
|
||||
assetAddress: arrayify(""),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a origin chain and asset address on {originChain} for a provided Wormhole wrapped address
|
||||
* @param connection
|
||||
|
|
|
@ -7,6 +7,8 @@ import {
|
|||
} from "../ethers-contracts";
|
||||
import { getBridgeFeeIx, ixFromRust } from "../solana";
|
||||
import { ChainId, CHAIN_ID_SOLANA, createNonce } from "../utils";
|
||||
import { ConnectedWallet as TerraConnectedWallet } from "@terra-money/wallet-provider";
|
||||
import { MsgExecuteContract } from "@terra-money/terra.js";
|
||||
|
||||
export async function transferFromEth(
|
||||
tokenBridgeAddress: string,
|
||||
|
@ -39,6 +41,37 @@ export async function transferFromEth(
|
|||
return receipt;
|
||||
}
|
||||
|
||||
export async function transferFromTerra(
|
||||
wallet: TerraConnectedWallet,
|
||||
tokenBridgeAddress: string,
|
||||
tokenAddress: string,
|
||||
amount: ethers.BigNumberish,
|
||||
recipientChain: ChainId,
|
||||
recipientAddress: Uint8Array
|
||||
) {
|
||||
const nonce = Math.round(Math.random() * 100000);
|
||||
return await wallet.post({
|
||||
msgs: [
|
||||
new MsgExecuteContract(
|
||||
wallet.terraAddress,
|
||||
tokenBridgeAddress,
|
||||
{
|
||||
initiate_transfer: {
|
||||
asset: tokenAddress,
|
||||
amount: amount,
|
||||
recipient_chain: recipientChain,
|
||||
recipient: recipientAddress,
|
||||
fee: 1000,
|
||||
nonce: nonce,
|
||||
},
|
||||
},
|
||||
{ uluna: 10000 }
|
||||
),
|
||||
],
|
||||
memo: "Complete Transfer",
|
||||
});
|
||||
}
|
||||
|
||||
export async function transferFromSolana(
|
||||
connection: Connection,
|
||||
bridgeAddress: string,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
docker run --rm -v "$(pwd)":/code \
|
||||
--mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \
|
||||
|
|
Loading…
Reference in New Issue