Contractless-Web3-Wallets/sdk/README.md

311 lines
9.3 KiB
Markdown
Raw Normal View History

# Contractless Browser Wallet JavaScript
`contractless.js` is the website client for the Contractless browser wallet.
It detects the injected wallet provider and manages a temporary,
origin-bound application session.
## Application ID
Choose a readable application ID containing 5 to 32 letters, numbers,
underscores, or hyphens, and keep it unchanged for the lifetime of the
application. It only needs to distinguish applications using the same website
origin. The wallet binds the ID to the website's actual browser origin, so the
same ID used by a different website does not share access.
Example:
```text
example-marketplace
```
## Installation
Download `contractless.js` with the application and import it as an ES module:
```js
import {
ContractlessWallet,
ContractlessWalletNotInstalledError
} from "./contractless.js";
const wallet = new ContractlessWallet({
appId: "example-marketplace",
api: {
url: "https://api.contractless.dev",
address: "dedicated-application-address.cltc",
publicKey: "dedicated-application-public-key",
signature: "precomputed-signature-for-aced",
apiKey: "optional-public-quota-key"
}
});
```
## Detecting The Wallet
```js
if (!wallet.isInstalled()) {
// Show the application's Contractless Wallet installation link.
}
```
The provider is injected at document start. Applications that execute
immediately can use `waitForContractlessWallet()` before showing an
installation notice.
## Connecting
Use a server-generated nonce when the signed proof will authenticate a user
with an application server:
```js
const connection = await wallet.connect({
message: "Sign in to Example Marketplace",
timestamp: Math.floor(Date.now() / 1000),
nonce: "4f3c2a1b9d8e7f60514233445566778899aabbccddeeff001122334455667788"
});
console.log(connection.address);
console.log(connection.signature);
console.log(connection.proof);
```
After approval, the wallet generates a 256-bit access key. The library stores
it in the website tab's `sessionStorage`. The matching authorization remains
inside extension session storage.
## Reading Balances
Applications can request every balance owned by the connected wallet:
```js
const balances = await wallet.balances();
```
Use `getBalance()` when only one base coin, token, NFT, or RWA balance is
needed. Missing assets return a zero balance instead of an error:
```js
const cltc = await wallet.getBalance({ asset: "CLTC" });
const nft = await wallet.getBalance({ asset: "FLOWERPRINCESS", nftSeries: 0 });
console.log(cltc.balance, cltc.balance_atomic);
```
Applications may use these methods to warn about an insufficient balance before
requesting a transaction. The wallet still performs its own checks, and the
node remains the final authority because balances can change between lookup
and broadcast.
## Transaction Errors
Rejected transaction Promises contain the useful verification reason returned
by the node, such as an insufficient balance, an unregistered participant, or
a fee below the required minimum. Applications should display the Error
message to the user instead of replacing it with a generic failure notice.
The wallet checks directly named participants before showing transfer, swap,
and loan approvals. This catches unregistered receivers and counterparties
before the user is asked to sign.
The key is reusable for that wallet unlock session. It is not consumed after
one request.
The session becomes invalid when:
- The Contractless wallet locks.
- The active wallet changes.
- The configured wallet lock period expires.
- The application calls `disconnect()`.
- The website tab's session storage is cleared.
The SDK removes the stored website session as soon as its expiration time is
reached. Applications can update their interface when that happens:
```js
window.addEventListener("contractless:sessionExpired", () => {
showWalletAsDisconnected();
});
```
If an application later requests an account, signature, or transaction without
a usable session, the SDK automatically opens the wallet connection flow. A
locked wallet asks the user to unlock it, creates a new session after approval,
and then continues the original request. The website never reuses an expired
access key.
## Reading The Connected Address
```js
const accounts = await wallet.accounts();
```
An empty array means the application no longer has a valid wallet session.
## Signing A Text Message
```js
const proof = await wallet.signMessage({
message: "Confirm profile ownership",
nonce: "1188aabbccddeeff00112233445566778899aabbccddeeff00112233445566"
});
```
Text signatures are domain-separated from transactions and cannot move
wallet assets. Website connection and text-signature messages are limited to
75 UTF-8 bytes. The wallet calculates this limit from bytes rather than visible
character count.
## Sending A Transaction
```js
const result = await wallet.sendTransaction({
transaction: {
txtype: 2,
time: Math.floor(Date.now() / 1000),
value: "2500000000",
coin: "CLTC ",
nft_series: 0,
sender: "sender-address.cltc",
receiver: "receiver-address.cltc",
txfee: "25000000"
}
});
console.log(result.txid);
console.log(result.bytes);
```
Applications must provide the complete transaction object. The wallet
validates and serializes that object, calculates its hash locally, displays
the signed fields, and signs only the hash it calculated. After approval, the
wallet broadcasts the signed bytes through its configured Contractless API.
The transaction ID and signed bytes are returned only after that API accepts
the broadcast. Arbitrary hash signing is not exposed.
## Swaps And Loan Creation
Swaps and loan creation use two independent wallet signatures. The application
sends the exact same unsigned transaction to two separate computers. Neither
party receives or validates the other party's signature before signing.
The first party's browser runs:
```js
const result = await wallet.signDualTransaction({
transaction,
signerSlot: 1
});
await sendSignatureToApplication({
transactionId: result.txid,
result
});
```
The second party's browser independently runs:
```js
const result = await wallet.signDualTransaction({
transaction,
signerSlot: 2
});
await sendSignatureToApplication({
transactionId: result.txid,
result
});
```
Each response contains the same transaction hash, canonical unsigned data,
unsigned wire-byte prefix, the signer's slot, public key, and that wallet's
signature. `sendSignatureToApplication()` represents an application endpoint,
database, matchmaking service, or other shared coordination layer. It is not a
wallet SDK function.
After the shared application has received both results, it returns them to one
client for final verification, assembly, and broadcast:
```js
const { transaction, signer1, signer2 } = await getCompletedSigningRequest(
transactionId
);
const complete = await wallet.completeDualTransaction({
transaction,
signer1,
signer2
});
const broadcast = await wallet.broadcastTransaction({
bytes: complete.bytes_hex
});
console.log(complete.hash);
console.log(complete.bytes_hex);
console.log(broadcast);
```
The final client does not sign again. It only invokes the browser core to
compare the transaction data, verify both Falcon signatures, and construct the
complete bytes. An application backend implementing the same Contractless
codec may instead perform verification, assembly, and broadcasting itself.
For swaps, signer slot 1 is `sender1` and signer slot 2 is `sender2`. For loan
creation, signer slot 1 is the `lender` and signer slot 2 is the `borrower`.
The application may store pending details locally, but it must never store a
wallet private key or encryption key.
`completeDualTransaction()` checks that both wallets signed the same hash,
that each public key belongs to the address assigned to its signer slot, and
that both Falcon signatures are valid. It does not ask either wallet to sign
again.
## Direct API Identity
An application can use a dedicated, registered, zero-balance wallet identity
for public API lookups and broadcasting already signed transactions. Generate
its reusable handshake signature with the node CLI:
```bash
./sign_message "aced"
```
Configure that address, its public key, and the returned signature in the SDK
constructor as shown above. This proof permits the API and node handshake; it
does not permit the application to sign transactions or spend from the
identity.
Applications can use the same identity for API lookups:
```js
const balance = await wallet.apiRequest("/api/v1/balances/base", {
query: {
address: userAddress,
coin: "CLTC"
}
});
```
`apiRequest()` accepts any enabled `/api/v1` route, optional query values, an
HTTP method, and an optional JSON body. It returns the API's `data` value and
throws when the API rejects the request.
These values are visible to anyone who can inspect the website. Use a dedicated
address that holds no funds. An API key embedded in browser JavaScript is also
public and should only identify traffic or select a quota. Services requiring
a secret API key or HMAC secret must proxy API requests through a backend.
Contractless API and PHP integration source:
- https://contractless.dev/contractless/Contractless-PHP-API
- https://contractless.dev/contractless/Contractless-PHP-RPC
## Disconnecting
```js
await wallet.disconnect();
```
Applications must never ask users to paste private keys, wallet files,
encryption keys, or access keys into a website.