-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
38ad828
commit c35adc0
Showing
17 changed files
with
625 additions
and
672 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,14 @@ | ||
ADMIN_USER_ADDR=0xaaa... | ||
MEMBER_USER_ADDR=-0xbbb... | ||
SYSTEM_MANAGER_KEY=0xddd... | ||
MEMBER_USER_CRED=0xeee... | ||
# Already provisioned | ||
AUTH_HOST=http://localhost:3005 | ||
VAULT_HOST=http://localhost:3011 | ||
CLIENT_ID=approval-example-xx | ||
CLIENT_SECRET=... # Get this when you provision your client | ||
|
||
AUTH_HOST=https://auth.armory.narval.xyz | ||
AUTH_CLIENT_ID=narval-example | ||
VAULT_HOST=https://vault.armory.narval.xyz | ||
VAULT_CLIENT_ID=narval-example | ||
# Datastore signing key set when provisioning the client, do not change this. | ||
# This MUST match the one you set during provision | ||
DATA_STORE_SIGNER_ADDRESS=0x000c0d191308a336356bee3813cc17f6868972c4 # This is the Root from dev.fixture.ts | ||
DATA_STORE_SIGNER_PRIVATE_KEY=0xa95b097938cc1d1a800d2b10d2a175f979613c940868460fd66830059fc1e418 | ||
|
||
AUTH_API_KEY=armory-admin-api-key | ||
VAULT_API_KEY=vault-admin-api-key | ||
# User Credentials - these can be changed & they'll be used when running `setup` | ||
ADMIN_USER_PRIVATE_KEY=0x454c9f13f6591f6482b17bdb6a671a7294500c7dd126111ce1643b03b6aeb354 # Alice from dev.fixture.ts | ||
MEMBER_USER_PRIVATE_KEY=0x569a6614716a76fdb9cf21b842d012add85e680b51fd4fb773109a93c6c4f307 # Bob from dev.fixture.ts |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
/* eslint-disable no-console */ | ||
import { hexSchema } from '@narval-xyz/armory-sdk/policy-engine-shared' | ||
import 'dotenv/config' | ||
import { getArmoryClients } from './armory.sdk' | ||
import { buildEntities, policies } from './data' | ||
|
||
// Setup is going to assume we already have a Client provisioned, and we have a PK for the datastore (same key for policies & entities) | ||
// Setup will bootstrap the initial policy & entities | ||
// 1. Create a system-manager user with the same PK as the datastore, so it can generate new Accounts & update data itself. | ||
// 2. Create an Admin User, from a PK in the .env | ||
// 3. Create a Member User, from a PK in the .env | ||
// 4. Set the policies we'll be using | ||
|
||
const main = async () => { | ||
console.log('🚀 Starting...\n') | ||
const dataStoreSignerPrivateKey = hexSchema.parse(process.env.DATA_STORE_SIGNER_PRIVATE_KEY) | ||
const adminUserPrivateKey = hexSchema.parse(process.env.ADMIN_USER_PRIVATE_KEY) | ||
const memberUserPrivateKey = hexSchema.parse(process.env.MEMBER_USER_PRIVATE_KEY) | ||
const vaultHost = process.env.VAULT_HOST | ||
const authHost = process.env.AUTH_HOST | ||
const clientId = process.env.CLIENT_ID | ||
const clientSecret = process.env.CLIENT_SECRET | ||
|
||
if (!authHost || !vaultHost || !clientId || !clientSecret) { | ||
throw new Error('Missing configuration') | ||
} | ||
const armory = await getArmoryClients(dataStoreSignerPrivateKey, { | ||
clientId, | ||
clientSecret, | ||
vaultHost, | ||
authHost | ||
}) | ||
|
||
console.log('🔒 Setting policies...\n') | ||
await armory.policyStoreClient.signAndPush(policies) | ||
|
||
console.log('🏗️ Setting initial entity data... \n') | ||
const entities = buildEntities({ | ||
adminUserPrivateKey, | ||
memberUserPrivateKey, | ||
dataStoreSignerPrivateKey | ||
}) | ||
await armory.entityStoreClient.signAndPush(entities) | ||
|
||
console.log('✅ Setup completed successfully \n') | ||
} | ||
|
||
main().catch(console.error) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
/* eslint-disable no-console */ | ||
import { AddressBookAccountEntity } from '@narval-xyz/armory-sdk/policy-engine-shared' | ||
import 'dotenv/config' | ||
import { uniqBy } from 'lodash/fp' | ||
import { getArmoryClientsFromEnv } from './armory.sdk' | ||
|
||
// Adds the below Destination account into the Address Book | ||
const main = async () => { | ||
console.log('🚀 Add Destination - Starting...\n') | ||
const armory = await getArmoryClientsFromEnv() | ||
|
||
console.log('🏗️ Adding `0x9f38879167acCf7401351027EE3f9247A71cd0c5` as an `internal` destination... \n') | ||
|
||
const destination: AddressBookAccountEntity = { | ||
chainId: 1, | ||
address: '0x9f38879167acCf7401351027EE3f9247A71cd0c5', // Engineering account from dev.fixture.ts | ||
id: 'eip155:1:0x9f38879167acCf7401351027EE3f9247A71cd0c5', | ||
classification: 'internal' | ||
} | ||
|
||
const entities = await armory.entityStoreClient.fetch() | ||
const addressBook = uniqBy('id', [...entities.data.addressBook, destination]) | ||
entities.data.addressBook = addressBook | ||
|
||
await armory.entityStoreClient.signAndPush(entities.data) | ||
|
||
console.log('✅ Add Destination - Complete \n') | ||
} | ||
|
||
main().catch(console.error) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
/* eslint-disable no-console */ | ||
import { AccountEntity } from '@narval-xyz/armory-sdk/policy-engine-shared' | ||
import 'dotenv/config' | ||
import { uniqBy } from 'lodash/fp' | ||
import { getArmoryClientsFromEnv } from './armory.sdk' | ||
|
||
// Adds the below managed account into the data store | ||
const main = async () => { | ||
console.log('🚀 Add Account - Starting...\n') | ||
const armory = await getArmoryClientsFromEnv() | ||
|
||
console.log('🏗️ Adding `0x76d1b7f9b3F69C435eeF76a98A415332084A856F` as an managed account... \n') | ||
|
||
const account: AccountEntity = { | ||
id: 'acct-ops-account-b', | ||
address: '0x76d1b7f9b3F69C435eeF76a98A415332084A856F', // Operations account from dev.fixture.ts | ||
accountType: 'eoa' | ||
} | ||
|
||
const entities = await armory.entityStoreClient.fetch() | ||
const accounts = uniqBy('id', [...entities.data.accounts, account]) | ||
entities.data.accounts = accounts | ||
|
||
await armory.entityStoreClient.signAndPush(entities.data) | ||
|
||
console.log('✅ Add Account - Complete \n') | ||
} | ||
|
||
main().catch(console.error) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
/* eslint-disable no-console */ | ||
import { | ||
AuthClient, | ||
AuthConfig, | ||
Decision, | ||
Request, | ||
TransactionRequest, | ||
buildSignerEip191, | ||
privateKeyToJwk | ||
} from '@narval-xyz/armory-sdk' | ||
import { hexSchema } from '@narval-xyz/armory-sdk/policy-engine-shared' | ||
import 'dotenv/config' | ||
import { v4 } from 'uuid' | ||
|
||
const transactionRequest = { | ||
from: '0x0301e2724a40E934Cce3345928b88956901aA127', // Account A | ||
to: '0x76d1b7f9b3F69C435eeF76a98A415332084A856F', // Account B | ||
chainId: 1, | ||
value: '0x429D069189E0000', // 0.3 ETH | ||
gas: 123n, | ||
maxFeePerGas: 789n, | ||
maxPriorityFeePerGas: 456n, | ||
nonce: 193 | ||
} as TransactionRequest | ||
|
||
const nonce = v4() | ||
const request: Request = { | ||
action: 'signTransaction', | ||
resourceId: 'acct-treasury-account-a', // account.id in data.ts | ||
transactionRequest, | ||
nonce | ||
} | ||
|
||
const main = async () => { | ||
console.log( | ||
`🚀 Transferring 0.3 ETH - from \x1b[32m${request.resourceId}\x1b[0m to \x1b[34macct-ops-account-b\x1b[0m \n` | ||
) | ||
const memberUserPrivateKey = hexSchema.parse(process.env.MEMBER_USER_PRIVATE_KEY) | ||
const host = process.env.AUTH_HOST | ||
const clientId = process.env.CLIENT_ID | ||
if (!host || !clientId) { | ||
throw new Error('Missing configuration') | ||
} | ||
|
||
const authJwk = privateKeyToJwk(memberUserPrivateKey) | ||
const signer = buildSignerEip191(memberUserPrivateKey) | ||
const authConfig: AuthConfig = { | ||
host, | ||
clientId, | ||
signer: { | ||
jwk: authJwk, | ||
alg: 'EIP191', | ||
sign: signer | ||
} | ||
} | ||
const auth = new AuthClient(authConfig) | ||
|
||
// Make the authorization request | ||
const response = await auth.authorize(request) | ||
|
||
switch (response.decision) { | ||
case Decision.PERMIT: { | ||
console.log('✅ Transaction approved \n') | ||
console.log('🔐 Approval token: \n', response.accessToken.value) | ||
break | ||
} | ||
case Decision.CONFIRM: { | ||
console.log('🔐 Request needs approvals', { authId: response.authId }, '\n') | ||
console.table(response.approvals.missing) | ||
// ... existing code ... | ||
console.log(`To approve, run:\n\n\t\x1b[1m\x1b[33mtsx 5-approve-transfer.ts ${response.authId}\x1b[0m\n`) | ||
// ... existing code ... | ||
break | ||
} | ||
case Decision.FORBID: { | ||
console.error('❌ Unauthorized') | ||
console.log('🔍 Response', response, '\n') | ||
} | ||
} | ||
} | ||
|
||
main().catch(console.error) |
59 changes: 59 additions & 0 deletions
59
examples/approvals-by-spending-limit/5-approve-transfer.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
/* eslint-disable no-console */ | ||
import { AuthClient, AuthConfig, Decision, buildSignerEip191, privateKeyToJwk } from '@narval-xyz/armory-sdk' | ||
import { hexSchema } from '@narval-xyz/armory-sdk/policy-engine-shared' | ||
import 'dotenv/config' | ||
import minimist from 'minimist' | ||
|
||
const main = async () => { | ||
const args = minimist(process.argv.slice(2)) | ||
const authId = args._[0] | ||
|
||
console.log(`\x1b[32m🚀 Approving Transfer as Admin\x1b[0m - \x1b[36m${authId}\x1b[0m \n`) | ||
|
||
const adminUserPrivateKey = hexSchema.parse(process.env.ADMIN_USER_PRIVATE_KEY) | ||
const host = process.env.AUTH_HOST | ||
const clientId = process.env.CLIENT_ID | ||
if (!host || !clientId) { | ||
throw new Error('Missing configuration') | ||
} | ||
|
||
const authJwk = privateKeyToJwk(adminUserPrivateKey) | ||
const signer = buildSignerEip191(adminUserPrivateKey) | ||
const authConfig: AuthConfig = { | ||
host, | ||
clientId, | ||
signer: { | ||
jwk: authJwk, | ||
alg: 'EIP191', | ||
sign: signer | ||
} | ||
} | ||
const auth = new AuthClient(authConfig) | ||
|
||
const authRequest = await auth.getAuthorizationById(authId) | ||
console.log('🔎 Found pending request') | ||
|
||
await auth.approve(authId) | ||
const approvedAuthorizationRequest = await auth.getAuthorizationById(authId) | ||
const result = approvedAuthorizationRequest.evaluations.find(({ decision }) => decision === Decision.PERMIT) | ||
|
||
switch (result?.decision) { | ||
case Decision.PERMIT: { | ||
console.log('✅ Transaction approved \n') | ||
console.log('🔐 Approval token: \n', result.signature) | ||
break | ||
} | ||
case Decision.CONFIRM: { | ||
console.log('🔐 Request still needs approvals', { authId: approvedAuthorizationRequest.id }, '\n') | ||
console.table(result.approvalRequirements?.missing) | ||
break | ||
} | ||
case Decision.FORBID: | ||
default: { | ||
console.error('❌ Unauthorized') | ||
console.log('🔍 Response', result, '\n') | ||
} | ||
} | ||
} | ||
|
||
main().catch(console.error) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,29 @@ | ||
# Collect approval before authorizing a request | ||
Setup | ||
|
||
`npm i` (run from this directory) | ||
|
||
Run each command in order | ||
|
||
1. Set up Policies & base Entity data (see data.ts) - as the system manager | ||
|
||
`tsx 1-setup.ts` | ||
|
||
2. Add a new Destination to the address book - as the system manager | ||
|
||
`tsx 2-add-destination.ts` | ||
|
||
3. Add a second Account to be managed - as the system manager | ||
|
||
`tsx 3-add-account.ts` | ||
|
||
4. Transfer from Account A to Account B - as the Member User | ||
|
||
Run the transfer 4 times in a row; the 4th requires an Approval | ||
|
||
`tsx 4-transfer-a-to-b.ts` | ||
|
||
5. Use the ID from the pending request to approve - as the Admin User | ||
|
||
`tsx 5-approve-transfer.ts $REQUEST_ID` | ||
|
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.