# Wallets
## How to add a new wallet
**1. Update prisma.schema**
- add enum value to `WalletName` enum
- run `npx prisma migrate dev --create-only`
**2. Update migration file**
- append `COMMIT` to the `ALTER TYPE` statement in the migration
- insert new row into `ẀalletTemplate`
- run `npx prisma migrate dev`
Example migration:
```sql
ALTER TYPE "WalletName" ADD VALUE 'PHOENIX'; COMMIT;
INSERT INTO "WalletTemplate" (name, "sendProtocols", "recvProtocols")
VALUES (
'PHOENIX',
ARRAY[]::"WalletSendProtocolName"[],
ARRAY['BOLT12']::"WalletRecvProtocolName"[]
);
```
**3. Customize how the wallet looks on the client via [wallets/lib/wallets.json](/wallets/lib/wallets.json)**
Example:
```json
{
// must be same name as wallet template
"name": "PHOENIX",
// name to show in client
"displayName": "Phoenix",
// image to show in client
// (dark mode will use /path/to/image-dark.png)
"image": "/path/to/image.png",
// url (planned) to show in client
"url": "https://phoenix.acinq.co/"
}
```
_If the wallet supports a lightning address and the domain is different than the url, you can pass an object to `url`. Here is Zeus as an example:_
```json
{
"name": "ZEUS",
"displayName": "Zeus",
"image": "/wallets/zeus.svg",
"url": {
"wallet": "https://zeusln.com/",
// different domain for lightning address
"lud16Domain": "zeuspay.com"
}
},
```
That's it!
## How to add a new protocol
**1. Update prisma.schema**
- add enum value to `WalletProtocolName` enum
- add enum value to `WalletRecvProtocolName` or `WalletSendProtocolName`
- add table to store protocol config
- run `npx prisma migrate dev --create-only`
Example
```diff
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index 9a113797..12505333 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -1199,6 +1199,7 @@ enum WalletProtocolName {
LNC
CLN_REST
LND_GRPC
+ BOLT12
}
enum WalletSendProtocolName {
@@ -1218,6 +1219,7 @@ enum WalletRecvProtocolName {
LN_ADDR
CLN_REST
LND_GRPC
+ BOLT12
}
enum WalletProtocolStatus {
@@ -1288,6 +1290,7 @@ model WalletProtocol {
walletRecvLightningAddress WalletRecvLightningAddress?
walletRecvCLNRest WalletRecvCLNRest?
walletRecvLNDGRPC WalletRecvLNDGRPC?
+ walletRecvBolt12 WalletRecvBolt12?
@@unique(name: "WalletProtocol_walletId_send_name_key", [walletId, send, name])
}
@@ -1429,3 +1432,12 @@ model WalletRecvLNDGRPC {
macaroon String
cert String?
}
+
+model WalletRecvBolt12 {
+ id Int @id @default(autoincrement())
+ createdAt DateTime @default(now()) @map("created_at")
+ updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
+ protocolId Int @unique
+ protocol WalletProtocol @relation(fields: [protocolId], references: [id], onDelete: Cascade)
+ offer String
+}
```
**2. Update migration file**
- add required triggers (`wallet_to_jsonb` and `wallet_clear_vault` if send protocol) to migration file
- run `npx prisma migrate dev`
Example
```sql
-- AlterEnum
ALTER TYPE "WalletProtocolName" ADD VALUE 'BOLT12';
-- AlterEnum
ALTER TYPE "WalletRecvProtocolName" ADD VALUE 'BOLT12';
-- CreateTable
CREATE TABLE "WalletRecvBolt12" (
"id" SERIAL NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"protocolId" INTEGER NOT NULL,
"offer" TEXT NOT NULL,
CONSTRAINT "WalletRecvBolt12_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "WalletRecvBolt12_protocolId_key" ON "WalletRecvBolt12"("protocolId");
-- AddForeignKey
ALTER TABLE "WalletRecvBolt12" ADD CONSTRAINT "WalletRecvBolt12_protocolId_fkey" FOREIGN KEY ("protocolId") REFERENCES "WalletProtocol"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- vvv Add trigger below manually vvv
CREATE TRIGGER wallet_to_jsonb
AFTER INSERT OR UPDATE ON "WalletRecvBolt12"
FOR EACH ROW
EXECUTE PROCEDURE wallet_to_jsonb();
-- if protocol is for sending you also need to add the wallet_clear_vault trigger:
-- CREATE TRIGGER wallet_clear_vault
-- AFTER DELETE ON "WalletSendClinkDebit"
-- FOR EACH ROW
-- EXECUTE PROCEDURE wallet_clear_vault();
```
**3. Add protocol lib file**
- add file to [wallets/lib/protocols](/wallets/lib/protocols) (see [JSDoc](/wallets/lib/protocols/index.js) for details)
- import in index.js file and add to default export
Example
```js
// wallets/lib/protocols/bolt12.js
export default [
{
// same as enum value we added
name: 'BOLT12',
displayName: 'BOLT12',
send: false,
fields: [
{
name: 'offer',
type: 'text',
label: 'offer',
placeholder: 'lno...',
validate: offerValidator,
required: true,
}
],
relationName: 'walletRecvBolt12'
}
]
```
```diff
diff --git a/wallets/lib/protocols/index.js b/wallets/lib/protocols/index.js
index 8caa5f52..58f5ab86 100644
--- a/wallets/lib/protocols/index.js
+++ b/wallets/lib/protocols/index.js
@@ -7,6 +7,7 @@ import lnbitsSuite from './lnbits'
import phoenixdSuite from './phoenixd'
import blinkSuite from './blink'
import webln from './webln'
+import bolt12 from './bolt12'
/**
* Protocol names as used in the database
@@ -44,5 +45,6 @@ export default [
...phoenixdSuite,
...lnbitsSuite,
...blinkSuite,
- webln
+ webln,
+ bolt12
]
```
**4. Add protocol method file**
- if protocol to receive payments: Add file to [wallets/server/protocols](/wallets/server/protocols) (see [JSDoc](/wallets/server/protocols/index.js) for details)
- if protocol to send payments: Add file to [wallets/client/protocols](/wallets/client/protocols) (see [JSDoc](/wallets/client/protocols/index.js) for details)
- import in index.js file and add to default export
Example
```js
// wallets/server/protocols/bolt12.js
// same as enum value we added
export const name = 'BOLT12'
export async function createInvoice ({ msats, description, expiry }, config, { signal }) {
/* ... code to create invoice using protocol config ... */
}
export async function testCreateInvoice ({ url }, { signal }) {
return await createInvoice(
{ msats: 1000, description: 'SN test invoice', expiry: 1 },
{ url },
{ signal }
)
}
```
```diff
diff --git a/wallets/server/protocols/index.js b/wallets/server/protocols/index.js
index 26c292d9..3ac88ae1 100644
--- a/wallets/server/protocols/index.js
+++ b/wallets/server/protocols/index.js
@@ -5,6 +5,7 @@ import * as clnRest from './clnRest'
import * as phoenixd from './phoenixd'
import * as blink from './blink'
import * as lndGrpc from './lndGrpc'
+import * as bolt12 from './bolt12'
export * from './util'
@@ -56,5 +57,6 @@ export default [
clnRest,
phoenixd,
blink,
- lndGrpc
+ lndGrpc,
+ bolt12
]
```
**5. Update GraphQL code**
- add GraphQL type
- add GraphQL type to `WalletProtocolConfig` union
- add GraphQL type to `WalletProtocolFields` fragment via spread operator (...)
- add GraphQL mutation to upsert protocol
- resolve GraphQL type in `mapWalletResolveTypes` function
Example
```diff
diff --git a/api/typeDefs/wallet.js b/api/typeDefs/wallet.js
index 3c1fffd1..af3858a5 100644
--- a/api/typeDefs/wallet.js
+++ b/api/typeDefs/wallet.js
@@ -38,6 +38,7 @@ const typeDefs = gql`
upsertWalletRecvLNDGRPC(walletId: ID, templateId: ID, enabled: Boolean!, networkTests: Boolean, socket: String!, macaroon: String!, cert: String): WalletRecvLNDGRPC!
upsertWalletSendLNC(walletId: ID, templateId: ID, enabled: Boolean!, pairingPhrase: VaultEntryInput!, localKey: VaultEntryInput!, remoteKey: VaultEntryInput!, serverHost: VaultEntryInput!): WalletSendLNC!
upsertWalletSendWebLN(walletId: ID, templateId: ID, enabled: Boolean!): WalletSendWebLN!
+ upsertWalletRecvBolt12(walletId: ID, templateId: ID, enabled: Boolean!, networkTests: Boolean, offer: String!): WalletRecvBolt12!
removeWalletProtocol(id: ID!): Boolean
updateWalletEncryption(keyHash: String!, wallets: [WalletEncryptionUpdate!]!): Boolean
updateKeyHash(keyHash: String!): Boolean
@@ -111,6 +112,7 @@ const typeDefs = gql`
| WalletRecvLightningAddress
| WalletRecvCLNRest
| WalletRecvLNDGRPC
+ | WalletRecvBolt12
type WalletSettings {
receiveCreditsBelowSats: Int!
@@ -207,6 +209,11 @@ const typeDefs = gql`
cert: String
}
+ type WalletRecvBolt12 {
+ id: ID!
+ offer: String!
+ }
+
input AutowithdrawSettings {
autoWithdrawThreshold: Int!
autoWithdrawMaxFeePercent: Float!
diff --git a/wallets/client/fragments/protocol.js b/wallets/client/fragments/protocol.js
index d1a65ff4..138d1a62 100644
--- a/wallets/client/fragments/protocol.js
+++ b/wallets/client/fragments/protocol.js
@@ -109,3 +109,11 @@ export const UPSERT_WALLET_SEND_WEBLN = gql`
}
}
`
+
+export const UPSERT_WALLET_RECEIVE_BOLT12 = gql`
+ mutation upsertWalletRecvBolt12($walletId: ID, $templateId: ID, $enabled: Boolean!, $networkTests: Boolean, $offer: String!) {
+ upsertWalletRecvBolt12(walletId: $walletId, templateId: $templateId, enabled: $enabled, networkTests: $networkTests, offer: $offer) {
+ id
+ }
+ }
+`
diff --git a/wallets/client/fragments/wallet.js b/wallets/client/fragments/wallet.js
index c301f5c1..73d59e6d 100644
--- a/wallets/client/fragments/wallet.js
+++ b/wallets/client/fragments/wallet.js
@@ -106,6 +106,10 @@ const WALLET_PROTOCOL_FIELDS = gql`
macaroon
cert
}
+ ... on WalletRecvBolt12 {
+ id
+ offer
+ }
}
}
`
diff --git a/wallets/server/resolvers/util.js b/wallets/server/resolvers/util.js
index 0155a422..ced4b399 100644
--- a/wallets/server/resolvers/util.js
+++ b/wallets/server/resolvers/util.js
@@ -19,6 +19,8 @@ export function mapWalletResolveTypes (wallet) {
return 'WalletRecvCLNRest'
case 'LND_GRPC':
return 'WalletRecvLNDGRPC'
+ case 'BOLT12':
+ return 'WalletRecvBolt12'
default:
return null
}
```