Skip to content

Commit

Permalink
Merge pull request #179 from input-output-hk/hrajchert/cip45
Browse files Browse the repository at this point in the history
Initial Mobile support (ussing cip45)
  • Loading branch information
hrajchert authored Jan 29, 2024
2 parents 76b2336 + 9135b5e commit ffaa183
Show file tree
Hide file tree
Showing 9 changed files with 324 additions and 13 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

### @marlowe.io/runtime-lifecycle

- Added support for contract bundles in the `lifecycle.contracts.createContract` function. ([PR-167](https://github.com/input-output-hk/marlowe-ts-sdk/pull/167))
- Feat (PLT-9089): Added support for contract bundles in the `lifecycle.contracts.createContract` function. ([PR-167](https://github.com/input-output-hk/marlowe-ts-sdk/pull/167))

4 changes: 4 additions & 0 deletions changelog.d/20240122_133315_hrajchert_cip45.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

### @marlowe.io/wallet

- Feat: Added a `@marlowe.io/wallet/peer-connect` module to enable mobile support by adapting to the [cardano-peer-connect](https://github.com/fabianbormann/cardano-peer-connect) library. ([PR-179](https://github.com/input-output-hk/marlowe-ts-sdk/pull/179))
18 changes: 18 additions & 0 deletions examples/cip45-flow/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# CIP-45 flow

This example is a Work In Progress. It shows how to use the `@marlowe.io/wallet/peer-connect` module together with the [cardano-peer-connect](https://github.com/fabianbormann/cardano-peer-connect) library.

## How to run

1. Build the project
```bash
$ npm i
$ npm run build
```
2. Serve the examples folder
```bash
$ npm run serve-dev
```
3. Open the browser at http://localhost:1337/examples/cip45-flow/
4. Use a cip45 wallet like [Eternl](https://eternl.io/) and scan the QR code to connect to the wallet
5. After the prompt to connect, you can click the create contract button.
41 changes: 41 additions & 0 deletions examples/cip45-flow/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>CIP45 test Flow</title>
<!-- This import map tells the browser what version of the SDK to use, the route is rewritten by the
local web server to point to the correct version -->
<script src="/importmap"></script>
</head>
<body>
<h1>CIP45 test Flow</h1>
<div>
<h2>Setup Runtime</h2>

<label for="runtimeUrl">URL to a Marlowe Runtime instance:</label>
<input
id="runtimeUrl"
type="text"
autocomplete="on"
placeholder="http://localhost:32952"
/>
</div>
<div id="qr-code"></div>
<div id="connected-wallet" style="display: none">
<h2>Connected Wallet</h2>
<div id="connected-wallet-address"></div>
<div id="connected-wallet-balance"></div>
<button id="create-contract">Create contract</button>
<button id="wallet-flow">Wallet flow</button>
<button id="disconnect-wallet">Disconnect Wallet</button>
</div>
<span id="connection-id"></span>
<h2>Console</h2>
<input id="clear-console" type="button" value="Clear console" />
<div id="console"></div>

<script src="https://fabianbormann.github.io/cardano-peer-connect/latest/index.js"></script>
<script type="module" src="./index.js"></script>
</body>
</html>
170 changes: 170 additions & 0 deletions examples/cip45-flow/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import { mkRestClient } from "@marlowe.io/runtime-rest-client";
import { MarloweJSON } from "@marlowe.io/adapter/codec";
import { clearConsole, log, logJSON } from "../js/poc-helpers.js";
import { mkPeerConnectAdapter } from "@marlowe.io/wallet/peer-connect";
import { mkRuntimeLifecycle } from "@marlowe.io/runtime-lifecycle";

import * as H from "../js/poc-helpers.js";

window.restClient = null;
function getRestClient() {
if (window.restClient === null) {
const runtimeURL = H.getRuntimeUrl();
window.restClient = mkRestClient(runtimeURL);
}
return window.restClient;
}
const runtimeUrlInput = document.getElementById("runtimeUrl");
runtimeUrlInput.addEventListener("change", () => {
window.restClient = null;
});

const clearConsoleButton = document.getElementById("clear-console");
clearConsoleButton.addEventListener("click", clearConsole);

H.setupLocalStorageRuntimeUrl();

const adapter = mkPeerConnectAdapter();
adapter.onDeleteWallet(async (walletId) => {
updateDisconnectedWalletStatus();
});
adapter.onNewWallet(async (walletId, wallet) => {
updateConnectedWalletStatus(wallet);
});
window.adapter = adapter;

// Give your app some basic information that will be displayed to the client wallet when he is connecting to your DApp.
const dAppInfo = {
name: "Cip45 test",
url: "http://localhost:1337/examples",
};
// Define a function that will be called when the client tries to connect to your DApp.
const verifyConnection = (walletInfo, callback) => {
logJSON("walletInfo", walletInfo);
callback(
//
window.confirm(
`Do you want to connect to wallet ${walletInfo.name} (${walletInfo.address})?`
)
);
};

function updateDisconnectedWalletStatus() {
document.getElementById("connected-wallet").style.display = "none";
document.getElementById("qr-code").style.display = "block";
}

async function updateConnectedWalletStatus(wallet) {
const address = await wallet.getChangeAddress();
const balance = await wallet.getLovelaces();
const addressElm = document.getElementById("connected-wallet-address");
const balanceElm = document.getElementById("connected-wallet-balance");
addressElm.textContent = address;
balanceElm.textContent = balance;

document.getElementById("connected-wallet").style.display = "block";
document.getElementById("qr-code").style.display = "none";
}

const dAppConnect = new CardanoPeerConnect.DAppPeerConnect({
dAppInfo: dAppInfo,
verifyConnection: verifyConnection,
onApiInject: adapter.adaptApiInject,
onApiEject: adapter.adaptApiEject,
});
window.dAppConnect = dAppConnect;

document
.getElementById("disconnect-wallet")
.addEventListener("click", async () => {
// NOTE: dAppConnect doesn't have a disconnect method, and this is currently
// not working. It's not clear how to disconnect from the wallet.
// https://github.com/fabianbormann/cardano-peer-connect/issues/57
logJSON("adapter", adapter);
logJSON("dAppConnect", dAppConnect);
const walletInfo = { version: 1, name: "a", icon: "bubu" };
dAppConnect.meerkat.api.disconnect(
// dAppConnect.dAppInfo.address,
dAppConnect.connectedWallet,
walletInfo,
(connectStatus) => {
console.log(connectStatus);
debugger;
}
);
});

document
.getElementById("create-contract")
.addEventListener("click", async () => {
const wallet = adapter.getWallet();
const runtime = mkRuntimeLifecycle({
runtimeURL: H.getRuntimeUrl(),
wallet,
});

const [contractId, txId] = await runtime.contracts.createContract({
contract: "close",
tags: {
"cip-45": "true",
},
});

log(`contractId: ${contractId}`);
log(`waiting for txId ${txId}`);
await wallet.waitConfirmation(txId);
log("transaction confirmed");
});

document.getElementById("wallet-flow").addEventListener("click", async () => {
const wallet = adapter.getWallet();
log(`<h2>CIP-45 Wallet Extension</h2>`);
log("");
log("Reading Wallet information...");
log("");

const isMainnet = await wallet.isMainnet();
log(`* <b>Network</b>: ${isMainnet ? "Mainnet" : "Testnet"}`);
log("");

const lovelaces = await wallet.getLovelaces();
log("- <b>Lovelaces</b>: " + lovelaces);
const tokensResult = await wallet.getTokens();
log("");

log(`- <b>Tokens</b>: (${tokensResult.length} tokens)`);
tokensResult.map((token) => {
const tokenName =
token.assetId.assetName == "" ? "lovelaces" : token.assetId.assetName;
log(`&nbsp;&nbsp;&nbsp; <i>${tokenName}</i> - ${token.quantity}`);
});
log("");

const changeAddress = await wallet.getChangeAddress();
log("- <b>Change Address</b>: " + changeAddress);
log("");

const usedAddresses = await wallet.getUsedAddresses();
log(`- <b>Used Addresses</b>: (${usedAddresses.length} addresses)`);
usedAddresses.map((usedAddress) =>
log("&nbsp;&nbsp;&nbsp; - " + usedAddress)
);
log("");

const collaterals = await wallet.getCollaterals();
log(`- <b>Collaterals</b>: (${collaterals.length} collaterals)`);
collaterals.map((collateral) => log("&nbsp;&nbsp;&nbsp; - " + collateral));
log("");

const utxos = await wallet.getUTxOs();
log(`- <b>UTxOs</b>: (${utxos.length} utxos)`);
utxos.map((utxo) => log("&nbsp;&nbsp;&nbsp; - " + utxo));
log("");
log("Wallet flow done 🎉");
});
// This is the code (identifier) that the client needs to enter into the wallet to connect to your dapp
const clientConnectCode = dAppConnect.getAddress();
document.getElementById("connection-id").innerText = clientConnectCode;

// Create and insert a QR code on your DApp, so the user can scan it easily in their app
dAppConnect.generateQRCode(document.getElementById("qr-code"));
2 changes: 2 additions & 0 deletions jsdelivr-npm-importmap.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ const importMap = {
"https://cdn.jsdelivr.net/npm/@marlowe.io/[email protected]/dist/bundled/esm/browser.js",
"@marlowe.io/wallet/lucid":
"https://cdn.jsdelivr.net/npm/@marlowe.io/[email protected]/dist/bundled/esm/lucid.js",
"@marlowe.io/wallet/peer-connect":
"https://cdn.jsdelivr.net/npm/@marlowe.io/[email protected]/dist/bundled/esm/peer-connect.js",
"@marlowe.io/runtime-rest-client":
"https://cdn.jsdelivr.net/npm/@marlowe.io/[email protected]/dist/bundled/esm/runtime-rest-client.js",
"@marlowe.io/runtime-rest-client/contract":
Expand Down
11 changes: 8 additions & 3 deletions packages/wallet/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,19 @@
"import": "./dist/esm/lucid/index.js",
"require": "./dist/bundled/cjs/lucid.cjs",
"types": "./dist/esm/lucid/index.d.ts"
},
"./peer-connect": {
"import": "./dist/esm/peer-connect/index.js",
"require": "./dist/bundled/cjs/peer-connect.cjs",
"types": "./dist/esm/peer-connect/index.d.ts"
}
},
"dependencies": {
"@47ng/codec": "1.1.0",
"@blockfrost/blockfrost-js": "^5.4.0",
"fp-ts": "^2.16.1",
"io-ts": "2.2.21",
"newtype-ts": "0.3.5",
"@47ng/codec": "1.1.0",
"lucid-cardano": "0.10.7",
"@blockfrost/blockfrost-js": "^5.4.0"
"newtype-ts": "0.3.5"
}
}
45 changes: 36 additions & 9 deletions packages/wallet/src/browser/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,10 @@ export async function mkBrowserWallet(

// DISCUSSION: This can currently wait forever. Maybe we should add
// an abort controller or a timeout
const waitConfirmation =
/**
* @hidden
*/
export const waitConfirmation =
(di: ExtensionDI) =>
(txHash: string, checkInterval = 3000) => {
return new Promise<boolean>((txConfirm) => {
Expand All @@ -118,55 +121,79 @@ const waitConfirmation =
});
};

const signTx =
/**
* @hidden
*/
export const signTx =
({ extension }: ExtensionDI) =>
(tx: string) => {
return extension.signTx(tx, true);
};

const getChangeAddress =
/**
* @hidden
*/
export const getChangeAddress =
({ extension }: ExtensionDI) =>
async () => {
const changeAddress = await extension.getChangeAddress();
return deserializeAddress(changeAddress);
};

const getUsedAddresses =
/**
* @hidden
*/
export const getUsedAddresses =
({ extension }: ExtensionDI) =>
async () => {
const usedAddresses = await extension.getUsedAddresses();
return usedAddresses.map(deserializeAddress);
};

const getUTxOs =
/**
* @hidden
*/
export const getUTxOs =
({ extension }: ExtensionDI) =>
async () => {
const utxos = (await extension.getUtxos()) ?? [];
return utxos.map(deserializeTxOutRef);
};

const getCollaterals =
/**
* @hidden
*/
export const getCollaterals =
({ extension }: ExtensionDI) =>
async () => {
const collaterals = (await extension.experimental.getCollateral()) ?? [];
return collaterals.map(deserializeTxOutRef);
};

const isMainnet =
/**
* @hidden
*/
export const isMainnet =
({ extension }: ExtensionDI) =>
async () => {
const networkId = await extension.getNetworkId();
return networkId == 1;
};

const getTokens =
/**
* @hidden
*/
export const getTokens =
({ extension }: ExtensionDI) =>
async () => {
const balances = await extension.getBalance();
return valueToTokens(deserializeValue(balances));
};

const getLovelaces =
/**
* @hidden
*/
export const getLovelaces =
({ extension }: ExtensionDI) =>
async () => {
const balances = await extension.getBalance();
Expand Down
Loading

0 comments on commit ffaa183

Please sign in to comment.