Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SPL Policy Docs + Examples #209

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -217,3 +217,91 @@ Note: see the [language section](Policy-language.md#appendix) for various approa
"condition": "solana.tx.instructions[0].accounts[0].account_key == '<ADDRESS>'"
}
```

#### Solana SPL token transfers -- Context and Examples

Turnkey’s policy engine supports policies for SPL token transfers. Specifically, we support creating policies for the `Transfer`, `TransferChecked` and `TransferCheckedWithFee` instructions across both the Solana Token Program and the Solana Token 2022 Program.

Some important context for using SPL token policies with Turnkey:

**Token Account Addresses**
For context, Solana implements SPL token balances for a particular wallet address by creating a whole new account called a "token account" which has a pointer in its data field labeled "ofwner" that points back to the wallet address in question. So to hold a particular token in your Solana wallet, you have to have to create a new token account meant to hold that token, owned by your Solana wallet. For policies related to the receiving token address of an SPL transfer, the token address receiving the tokens will have to be used, NOT the wallet address that is the owner for the receiving token address. This is because, while both the owning wallet address and the receiving token address are specified in the transfer instruction, the owning wallet address of the recipient token address is not specified. For this we highly recommend using the convention of “associated token addresses” to set policies that, for example, allow SPL token transfers to a particular wallet address.

For further context on associated token addresses check out Solana’s documentation on it: https://spl.solana.com/associated-token-account

:::info

An example implementation of using a policy to allow transfers to the associated token address of the intended recipient wallet address can be found in our SDK examples [here](https://github.com/tkhq/sdk/tree/main/examples/with-solana#6-running-the-create-spl-token-transfer-with-policy-example).

:::

**Mint Address Accessibility**
The mint account address of the token will only be accessible when the transaction is constructed using instructions that specify the mint address – `TransferChecked` and `TransferCheckedWithFee`. For transactions constructed using the simple `Transfer` method, the mint account will be considered empty.

Here are some example policies for SPL transfers:

#### Allow a user to sign Solana transactions that include a single instruction which is an SPL token transfer from a particular sending token address

```json JSON
{
"policyName": "Allow user <USER_ID> to sign Solana transactions that include only a single SPL Transfer FROM <TOKEN_ADDRESS>",
"effect": "EFFECT_ALLOW",
"consensus": "approvers.any(user, user.id == '<USER_ID>')",
"condition": "solana.tx.instructions.count() == 1 && solana.tx.spl_transfers.count() == 1 && solana.tx.spl_transfers.all(transfer, transfer.from == '<TOKEN_ADDRESS>')"
}
```

#### Allow a user to sign Solana transactions only if ALL of the instructions are SPL transfers TO a particular token address

```json JSON
{
"policyName": "Allow user <USER_ID> to sign Solana transactions only if ALL of the instructions are SPL transfers TO <TOKEN_ADDRESS>",
"effect": "EFFECT_ALLOW",
"consensus": "approvers.any(user, user.id == '<USER_ID>')",
"condition": "solana.tx.instructions.count() == solana.tx.spl_transfers.count() && solana.tx.spl_transfers.all(transfer, transfer.to == '<TOKEN_ADDRESS>')"
}
```

#### Allow users with a specific tag to sign Solana transactions only if ALL of the instructions are SPL token transfers with a specific address as the owner of the sending token address

```json JSON
{
"policyName": "Allow users with <USER_TAG> to sign Solana transactions only if ALL of the instructions are SPL token transfers with <OWNER_ADDRESS> as the owner of the sending token address",
"effect": "EFFECT_ALLOW",
"consensus": "approvers.any(user, user.tags.contains('<USER_TAG_ID>'))",
"condition": "solana.tx.instructions.count() == solana.tx.spl_transfers.count() && solana.tx.spl_transfers.all(transfer, transfer.owner == '<OWNER_ADDRESS>')"
}
```

#### Allow a user to sign Solana transactions that include a single instruction which is an SPL token transfer where the atomic units of the transfer are less than a threshold amount

```json Json
{
"policyName": "Allow user <USER_ID> to sign Solana transactions that include a single instruction which is an SPL token transfer where the atomic units of the transfer are less than <AMOUNT>",
"effect": "EFFECT_ALLOW",
"consensus": "approvers.any(user, user.id == '<USER_ID>')",
"condition": "solana.tx.instructions.count() == 1 && solana.tx.spl_transfers.count() == 1 && solana.tx.spl_transfers.all(transfer, transfer.amount < <AMOUNT>)"
}
```

#### Allow a user to sign Solana transactions only if ALL of the instructions are SPL token transfers where the token mint address is a particular address

```json Json
{
"policyName": "Allow <USER_ID> to sign a Solana transaction only if ALL of the instructions are SPL token transfers where the token mint address is <TOKEN_MINT_ADDRESS>",
"effect": "EFFECT_ALLOW",
"consensus": "approvers.any(user, user.id == '<USER_ID>')",
"condition": "solana.tx.instructions.count() == solana.tx.spl_transfers.count() && solana.tx.spl_transfers.all(transfer, transfer.token_mint == '<TOKEN_MINT_ADDRESS>')"
}
```

#### Allow a user to sign Solana transactions that includes a single instruction which is an SPL token transfer where one of the multisig signers of the owner is a particular address

```json Json
{
"policyName": "Allow <USER_ID> to sign a Solana transaction only if ALL of it's instructions are SPL token transfers where one of the multisig signers of the owner is <SIGNER_ADDRESS>",
"effect": "EFFECT_ALLOW",
"consensus": "approvers.any(user, user.id == '<USER_ID>')",
"condition": "solana.tx.instructions.count() == 1 && solana.tx.spl_transfers.count() == 1 && solana.tx.spl_transfers.all(transfer, transfer.signers.any(s, s == '<SIGNER_ADDRESS>'))"
}
```
10 changes: 5 additions & 5 deletions docs/documentation/concepts/policy-management/Policy-language.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,14 +172,14 @@ There are a select few activities that are not governed by policies, but rather

Our Ethereum policy language (accessible via `eth.tx`) allows for the granular governance of signing Ethereum (EVM-compatible) transactions. Our policy engine exposes a [fairly standard set of properties](https://ethereum.org/en/developers/docs/transactions/#typed-transaction-envelope) belonging to a transaction.

See the [Policy examples](./Policy-examples.md) for sample scenarios.
See the [Policy examples](./Policy-examples.mdx) for sample scenarios.

### Solana

Similarly, our Solana policy language (accessible via `solana.tx`) allows for control over signing Solana transactions. Note that there are some fundamental differences between the architecture of the two types of transactions, hence the resulting differences in policy structure. Notably, within our policy engine, a Solana transaction contains a list of Transfers, currently corresponding to native SOL transfers. Each transfer within a transaction is considered a separate entity. Here are some approaches you might take to govern native SOL transfers:

- _All_ transfers need to match the policy condition. Useful for allowlists ([example](./Policy-examples.md#allow-solana-transactions-that-include-a-transfer-with-only-one-specific-recipient))
- _Just one_ transfer needs to match the policy condition. Useful for blocklists ([example](./Policy-examples.md#deny-all-solana-transactions-transferring-to-an-undesired-address))
- Only match if there is a _single_ transfer in the transaction, _and_ that transfer meets the criteria ([example](./Policy-examples.md#allow-solana-transactions-that-have-exactly-one-transfer-with-one-specific-recipient)). This is the most secure approach, and thus most restrictive.
- _All_ transfers need to match the policy condition. Useful for allowlists ([example](./Policy-examples.mdx#allow-solana-transactions-that-include-a-transfer-with-only-one-specific-recipient))
- _Just one_ transfer needs to match the policy condition. Useful for blocklists ([example](./Policy-examples.mdx#deny-all-solana-transactions-transferring-to-an-undesired-address))
- Only match if there is a _single_ transfer in the transaction, _and_ that transfer meets the criteria ([example](./Policy-examples.mdx#allow-solana-transactions-that-have-exactly-one-transfer-with-one-specific-recipient)). This is the most secure approach, and thus most restrictive.

See the [Policy examples](./Policy-examples.md) for sample scenarios.
See the [Policy examples](./Policy-examples.mdx) for sample scenarios.
2 changes: 1 addition & 1 deletion docs/documentation/features/oauth.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ The main documentation for Google OIDC is available [here](https://github.com/tk

Apple integration is also extensively tested and supported, and is integrated into our demo wallet (hosted at https://wallet.tx.xyz). The code provides an [example component](https://github.com/tkhq/demo-embedded-wallet/blob/bf0e2292cbd2ee9cde6b241591b077fadf7ee71b/src/components/apple-auth.tsx) as well as an [example redirect handler](<https://github.com/tkhq/demo-embedded-wallet/blob/bf0e2292cbd2ee9cde6b241591b077fadf7ee71b/src/app/(landing)/oauth-callback/apple/page.tsx>).

Documentation for Apple OIDC can be foudn [here](https://developer.apple.com/documentation/sign_in_with_apple/sign_in_with_apple_rest_api/authenticating_users_with_sign_in_with_apple).
Documentation for Apple OIDC can be found [here](https://developer.apple.com/documentation/sign_in_with_apple/sign_in_with_apple_rest_api/authenticating_users_with_sign_in_with_apple).

### Facebook

Expand Down