Skip to content

Commit

Permalink
base near example
Browse files Browse the repository at this point in the history
  • Loading branch information
PiVortex committed Nov 7, 2024
1 parent e1d2bfb commit 378f30a
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 76 deletions.
Original file line number Diff line number Diff line change
@@ -1,74 +1,16 @@
---
id: near-example
title: Controlling a NEAR account with a contract
id: contract
title: Contract
---

import {Github, Language} from "@site/src/components/codetabs"

This example is of a `simple subscription service` that allows a user to subscribe to an arbitary service and allows the contract to charge them 5 NEAR tokens every month. For most chains an account as a single key, the power of NEARs account model combined with chain signatures is that you can add an `MPC controlled key` to your account and allow a contract to control your account through code and limited actions (including ones that require a full access key to sign). You can also dervie and use new implicit NEAR accounts via the MPC contract as you do with other chains but this usecase is cooler.

This concept also enables:
- **Account recovery**: allow a contract to add a new private key to your account after preset conditions are met.
- **Trail accounts**: the [Keypom](https://github.com/keypom) contract uses this concept to create trial accounts that can only peform a limited number of actions (including those that require a full access key) and can be upgraded to a full account upon the completion of specified actions. These accounts are also multichain.
- **DCA service**: a contract that allows a DEX to buy a token for a user every fixed period with a pre defined amount of USDC.
- **and more...**

These were all previously possible - before chain signatures - since a NEAR account is also a smart contract, but this required the user to consume a lot of $NEAR in storage costs to upload the contract to the account and it lacked flexability. This approach is much more scalable and new account services can be switched in and out easily.

Since a NEAR account is also a multichain account, any dervied foreign accounts associated with the NEAR account also inherit these account services.

---

# Running the example

This example has contracts written in rust and scripts to interact with the contract in NodeJS.

Go ahead and clone the repository to get started:

```bash
# Clone the repository
git clone https://github.com/PiVortex/subscription-example.git

# Navigate to the scripts directory
cd subscription-example/scripts

# Install the dependencies
npm install
```

To interact with the contract you will need three different accounts. A subscriber, an admin and a contract. Run the following command to create the accounts and deploy the contract:

```bash
npm run setup
```

To subscribe to the service run the following command:

```bash
npm run subscribe
```

To charge the subscriber from the admin account run the command:

```bash
npm run charge
```

To unsubscribe from the service run the command:

```bash
npm run unsubscribe
```

---

# Contract overview

Feel free to explore the contract code in full. In this tutorial we assume a basic understanding of Rust and NEAR contracts and will only look at the relevant parts relevant to chain signatures. The main data the contract stores is a map of the subscribers and when they last paid the subscription fee.

---

## Constructing the transaction
### Constructing the transaction

We only want the smart contract to be able to sign transactions to charge the subscriber 5 NEAR tokens, no other transactions should be allowed to be signed. This is why we construct the transaction inside of the contract with the `omni-transaction-rs` library.

Expand Down Expand Up @@ -114,7 +56,7 @@ The MPC contract takes a `transaction payload` as an argument instead of the tra
start="76" end="81" />
</Language>

## Calling the MPC contract
### Calling the MPC contract

In our `signer.rs` file we have defined the interface for the `sign` method on the MPC contract.

Expand All @@ -124,7 +66,7 @@ In our `signer.rs` file we have defined the interface for the `sign` method on t
start="40" end="43" />
</Language>

As input it takes the payload, the path and the key_version. The `path` determines which public key the MPC contract should use to sign the transaction in this example the path is the account Id of the subscriber, so each subscriber has a unique identifiable key. The full path is a combination of the `predeccessor` to the MPC contract (the subscription contract) along with the path given as the argument. The addition of the predeccessor means that only this contract is able to sign transactions for the given key. The `key_version` states which key type is being used. Currently the only key type supported is secp256k1 which has a key version of `0`.
As input it takes the payload, the path and the key_version. The `path` determines which public key the MPC contract should use to sign the transaction in this example the path is the account Id of the subscriber, so each subscriber has a unique identifiable key. The `key_version` states which key type is being used. Currently the only key type supported is secp256k1 which has a key version of `0`.

<Language language="rust" showSingleFName={true}>
<Github fname="signer.rs"
Expand All @@ -144,7 +86,7 @@ We attach a small amount of gas to the callback and use a `gas weight of 0` so t

---

## Reconstructing the signature
### Reconstructing the signature

Once the transaction has been signed by the MPC contract we can reconstruct the signature and add it to the transaction. You could decide to reconstruct the signature and add it to the transaction in the client side, but an advantage of doing it in the contract is that you can return a fully signed transaction from the contract which can be straight away braodcasted to the network instead of having to store the transaction in the frontend. This also makes it much easier for indexers/relayers to get transactions and broadcast them, making it less likely that transactions will be signed without being sent.

Expand Down Expand Up @@ -172,7 +114,9 @@ The final step is to `deserlialize` the transaction we passed and add the signat
start="131" end="139" />
</Language>

## Recieve payment method
---

### Recieve payment method

Check failure on line 119 in docs/2.build/1.chain-abstraction/chain-signatures/chain-signatures-contract/controlling-near-accounts/contract.md

View workflow job for this annotation

GitHub Actions / runner / misspell

[misspell] reported by reviewdog 🐶 "Recieve" is a misspelling of "Receive" Raw Output: ./docs/2.build/1.chain-abstraction/chain-signatures/chain-signatures-contract/controlling-near-accounts/contract.md:119:4: "Recieve" is a misspelling of "Receive"

Once the signed transaction is relayed to the `NEAR network` it will call the pay_subscription method in the contract. You can see that we are only updating the state of the contract here when the transaction has been accepted by the network.

Expand All @@ -181,13 +125,3 @@ Once the signed transaction is relayed to the `NEAR network` it will call the pa
url="https://github.com/PiVortex/subscription-example/blob/main/contract/src/lib.rs#L61-L79"
start="61" end="79" />
</Language>

# Scripts overview

We implement a few scripts to interact with the contract. This is not just as simple as calling a method in the contract as we will have to do manage the MPC public key added to the subscriber account.

## Subscribe

## Charge

## Unsubscribe
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
---
id: introduction
title: Controlling a NEAR account
sidebar_label: Overview
---

This example is of a `simple subscription service` that allows a user to subscribe to an arbitary service and allows the contract to charge them 5 NEAR tokens every month. For most chains an account as a single key, the power of NEARs account model combined with chain signatures is that you can add an `MPC controlled key` to your account and allow a contract to control your account through code and limited actions (including ones that require a full access key to sign). You can also dervie and use new implicit NEAR accounts via the MPC contract as you do with other chains but this usecase is cooler.

Check failure on line 7 in docs/2.build/1.chain-abstraction/chain-signatures/chain-signatures-contract/controlling-near-accounts/introduction.md

View workflow job for this annotation

GitHub Actions / runner / misspell

[misspell] reported by reviewdog 🐶 "arbitary" is a misspelling of "arbitrary" Raw Output: ./docs/2.build/1.chain-abstraction/chain-signatures/chain-signatures-contract/controlling-near-accounts/introduction.md:7:89: "arbitary" is a misspelling of "arbitrary"

This concept also enables:
- **Account recovery**: allow a contract to add a new private key to your account after preset conditions are met.
- **Trail accounts**: the [Keypom](https://github.com/keypom) contract uses this concept to create trial accounts that can only peform a limited number of actions (including those that require a full access key) and can be upgraded to a full account upon the completion of specified actions. These accounts are also multichain.
- **DCA service**: a contract that allows a DEX to buy a token for a user every fixed period with a pre defined amount of USDC.
- **and more...**

These were all previously possible - before chain signatures - since a NEAR account is also a smart contract, but this required the user to consume a lot of $NEAR in storage costs to upload the contract to the account and it lacked flexability. This approach is much more scalable and new account services can be switched in and out easily.

Since a NEAR account is also a multichain account, any dervied foreign accounts associated with the NEAR account also inherit these account services.

---

# Running the example

This example has contracts written in rust and scripts to interact with the contract in NodeJS.

Go ahead and clone the repository to get started:

```bash
# Clone the repository
git clone https://github.com/PiVortex/subscription-example.git

# Navigate to the scripts directory
cd subscription-example/scripts

# Install the dependencies
npm install
```

To interact with the contract you will need three different accounts. A subscriber, an admin and a contract. Run the following command to create the accounts and deploy the contract:

```bash
npm run setup
```

To subscribe to the service run the following command:

```bash
npm run subscribe
```

To charge the subscriber from the admin account run the command:

```bash
npm run charge
```

To unsubscribe from the service run the command:

```bash
npm run unsubscribe
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
---
id: scripts
title: Scripts
---

import {Github, Language} from "@site/src/components/codetabs"


We implement a few scripts to interact with the contract. This is not just as simple as calling a method in the contract as we will have to do manage the MPC public key added to the subscriber account.

---

## Key derivation

You'll see in each of these scripts that we are using a file called [derive-mpc-key.js](https://github.com/PiVortex/subscription-example/blob/main/scripts/utils/derive-mpc-key.js), as the name suggests this is used to derive the public key of the MPC key being used to sign the transactions on behalf of the subscriber. Feel free to look into this code but you don't particularly need to understand it.

However, there are some things worth noting. NEAR accounts usually use ed25519 keys but they also support secp256k1 keys. As mentioned previously we are using `secp256k1` keys here since this is the only key type currently supported by the MPC contract. You can also see that the full `path` is a combination of the `predeccessor` to the MPC contract (the subscription contract) along with the path given as the argument. The inclustion of the predeccessor means that only this contract is able to sign transactions for the given key.

---

## Subscribe

To start a new subscription we simply derive the MPC key for the subscriber by inputting the `contractAccountId` as the `predecessorId` and the `subscriberAccountId` as the `derviationPath` and add it to the account. We then call the `subscribe` method in the contract.

<Language language="javascript" showSingleFName={true}>
<Github fname="start-subscription.js"
url="https://github.com/PiVortex/subscription-example/blob/main/scripts/start-subscription.js#L32-L46"
start="32" end="46" />
</Language>

---

## Charge

In the charge script we will call the `charge_subscription` method from the `admin` account to charge a payment from the `subscriber` account. To call this method we need to supply the off-chain data to construct the transaction as mentioned before. We need:
- The subscriber's MPC controlled `public_key` again, as this is the public key that the MPC will produce a signature for.
- The `next nonce` of the key, that ensures that the transaction is unique.
- A `block hash` within the last 24 hours, this is used to ensure that the transaction was recently signed. If the signed transaction is not relayed within the last 24 hours of the block hash supplied then the network will reject the transaction.

These details are available via RPC calls.

<Language language="javascript" showSingleFName={true}>
<Github fname="charge-subscription.js"
url="https://github.com/PiVortex/subscription-example/blob/main/scripts/charge-subscription.js#L33-L56"
start="33" end="56" />
</Language>

The admin then calls the `charge_subscription` method with the input details and the account Id of the subscriber being charged. We are attatching 0.1 NEAR as deposit for the MPC contract which in most cases will be more than enough and we will be refunded any excess. The admin is attatching a deposit and not just the contract because refunds are given to the original `signer` of the call to the MPC.

<Language language="javascript" showSingleFName={true}>
<Github fname="charge-subscription.js"
url="https://github.com/PiVortex/subscription-example/blob/main/scripts/charge-subscription.js#L58-L68"
start="58" end="68" />
</Language>

We then fetch the result (which is the signed transaction) from the transaction outcome, convert it to a `Uint8Array`, serialize it to `base64` and then broadcast it to the network.

<Language language="javascript" showSingleFName={true}>
<Github fname="charge-subscription.js"
url="https://github.com/PiVortex/subscription-example/blob/main/scripts/charge-subscription.js#L70-L77"
start="70" end="77" />
</Language>

This will execute the method call to the contract from the subscriber charging the subscriber 5 NEAR tokens, as long as the subscriber has enough funds in their account.

---

## Unsubscribe

To unsubscribe from the service the subscriber calls the `unsubscribe` method in the contract. While the contract will no longer have access to the path of the subscriber sign transactions for the user it is best practice to remove the MPC key from the account just incase the contract is compromised.

<Language language="javascript" showSingleFName={true}>
<Github fname="end-subscription.js"
url="https://github.com/PiVortex/subscription-example/blob/main/scripts/end-subscription.js#L32-L46"
start="32" end="46" />
</Language>
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
id: overview
title: Chain signature in a NEAR contract
title: Using Chain Signatures in a Contract
---

import Tabs from '@theme/Tabs';
Expand Down
12 changes: 12 additions & 0 deletions website/sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,18 @@ const sidebar = {
"items": [
"concepts/abstraction/chain-signatures",
'build/chain-abstraction/chain-signatures/chain-signatures',
{
"Implementing Contract Logic": [
"build/chain-abstraction/chain-signatures/chain-signatures-contract/overview",
{
"Controlling Existing NEAR Accounts": [
"build/chain-abstraction/chain-signatures/chain-signatures-contract/controlling-near-accounts/introduction",
"build/chain-abstraction/chain-signatures/chain-signatures-contract/controlling-near-accounts/contract",
"build/chain-abstraction/chain-signatures/chain-signatures-contract/controlling-near-accounts/scripts",
]
},
]
},
// 'build/chain-abstraction/nft-chain-keys',
]
},
Expand Down

0 comments on commit 378f30a

Please sign in to comment.